Split CodePaths and FileCodeSnippet into multiple files
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import * as React from 'react';
|
||||
import { ChangeEvent, SetStateAction, useCallback } from 'react';
|
||||
import { VSCodeDropdown, VSCodeOption } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { CodeFlow } from '../../../remote-queries/shared/analysis-result';
|
||||
|
||||
const getCodeFlowName = (codeFlow: CodeFlow) => {
|
||||
const filePath = codeFlow.threadFlows[codeFlow.threadFlows.length - 1].fileLink.filePath;
|
||||
return filePath.substring(filePath.lastIndexOf('/') + 1);
|
||||
};
|
||||
|
||||
type CodeFlowsDropdownProps = {
|
||||
codeFlows: CodeFlow[];
|
||||
setSelectedCodeFlow: (value: SetStateAction<CodeFlow>) => void;
|
||||
}
|
||||
|
||||
export const CodeFlowsDropdown = ({
|
||||
codeFlows,
|
||||
setSelectedCodeFlow
|
||||
}: CodeFlowsDropdownProps) => {
|
||||
const handleChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedOption = e.target;
|
||||
const selectedIndex = selectedOption.value as unknown as number;
|
||||
setSelectedCodeFlow(codeFlows[selectedIndex]);
|
||||
}, [setSelectedCodeFlow, codeFlows]);
|
||||
|
||||
return (
|
||||
<VSCodeDropdown onChange={handleChange}>
|
||||
{codeFlows.map((codeFlow, index) =>
|
||||
<VSCodeOption
|
||||
key={index}
|
||||
value={index}
|
||||
>
|
||||
{getCodeFlowName(codeFlow)}
|
||||
</VSCodeOption>
|
||||
)}
|
||||
</VSCodeDropdown>
|
||||
);
|
||||
};
|
||||
30
extensions/ql-vscode/src/view/common/CodePaths/CodePath.tsx
Normal file
30
extensions/ql-vscode/src/view/common/CodePaths/CodePath.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { AnalysisMessage, CodeFlow, ResultSeverity } from '../../../remote-queries/shared/analysis-result';
|
||||
import { ThreadPath } from './ThreadPath';
|
||||
|
||||
type CodePathProps = {
|
||||
codeFlow: CodeFlow;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
}
|
||||
|
||||
export const CodePath = ({
|
||||
codeFlow,
|
||||
message,
|
||||
severity
|
||||
}: CodePathProps) => (
|
||||
<>
|
||||
{codeFlow.threadFlows.map((threadFlow, index) =>
|
||||
<ThreadPath
|
||||
key={index}
|
||||
threadFlow={threadFlow}
|
||||
step={index + 1}
|
||||
message={message}
|
||||
severity={severity}
|
||||
isSource={index === 0}
|
||||
isSink={index === codeFlow.threadFlows.length - 1}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -1,239 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { ChangeEvent, SetStateAction, useCallback, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VSCodeDropdown, VSCodeLink, VSCodeOption, VSCodeTag } from '@vscode/webview-ui-toolkit/react';
|
||||
import { VSCodeLink } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { Overlay } from '@primer/react';
|
||||
|
||||
import { CodeFlow, AnalysisMessage, ResultSeverity, ThreadFlow } from '../../../remote-queries/shared/analysis-result';
|
||||
import { SectionTitle } from '../SectionTitle';
|
||||
import { VerticalSpace } from '../VerticalSpace';
|
||||
import { FileCodeSnippet } from '../FileCodeSnippet';
|
||||
|
||||
const StyledCloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 4em;
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-editor-foreground);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none
|
||||
}
|
||||
`;
|
||||
|
||||
const OverlayContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 2em;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-editor-foreground);
|
||||
overflow-y: scroll;
|
||||
`;
|
||||
|
||||
const CloseButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<StyledCloseButton onClick={onClick} tabIndex={-1}>
|
||||
<span className="codicon codicon-chrome-close" />
|
||||
</StyledCloseButton>
|
||||
);
|
||||
|
||||
const PathsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const PathDetailsContainer = styled.div`
|
||||
padding: 0;
|
||||
border: 0;
|
||||
`;
|
||||
|
||||
const PathDropdownContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
padding: 0 0 0 0.2em;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 55em;
|
||||
margin-bottom: 1.5em;
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
`;
|
||||
|
||||
const TitleContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
const TagContainer = styled.div`
|
||||
padding: 0;
|
||||
border: none;
|
||||
`;
|
||||
import { AnalysisMessage, CodeFlow, ResultSeverity } from '../../../remote-queries/shared/analysis-result';
|
||||
import { CodePathsOverlay } from './CodePathsOverlay';
|
||||
|
||||
const ShowPathsLink = styled(VSCodeLink)`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
type ThreadPathProps = {
|
||||
threadFlow: ThreadFlow;
|
||||
step: number;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
isSource?: boolean;
|
||||
isSink?: boolean;
|
||||
}
|
||||
|
||||
const ThreadPath = ({
|
||||
threadFlow,
|
||||
step,
|
||||
message,
|
||||
severity,
|
||||
isSource,
|
||||
isSink,
|
||||
}: ThreadPathProps) => (
|
||||
<Container>
|
||||
<HeaderContainer>
|
||||
<TitleContainer>
|
||||
<SectionTitle>Step {step}</SectionTitle>
|
||||
</TitleContainer>
|
||||
{isSource &&
|
||||
<TagContainer>
|
||||
<VSCodeTag>Source</VSCodeTag>
|
||||
</TagContainer>
|
||||
}
|
||||
{isSink &&
|
||||
<TagContainer>
|
||||
<VSCodeTag>Sink</VSCodeTag>
|
||||
</TagContainer>
|
||||
}
|
||||
</HeaderContainer>
|
||||
|
||||
<FileCodeSnippet
|
||||
fileLink={threadFlow.fileLink}
|
||||
codeSnippet={threadFlow.codeSnippet}
|
||||
highlightedRegion={threadFlow.highlightedRegion}
|
||||
severity={severity}
|
||||
message={isSink ? message : threadFlow.message}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
type CodePathProps = {
|
||||
codeFlow: CodeFlow;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
}
|
||||
|
||||
const CodePath = ({
|
||||
codeFlow,
|
||||
message,
|
||||
severity
|
||||
}: CodePathProps) => (
|
||||
<>
|
||||
{codeFlow.threadFlows.map((threadFlow, index) =>
|
||||
<ThreadPath
|
||||
key={index}
|
||||
threadFlow={threadFlow}
|
||||
step={index + 1}
|
||||
message={message}
|
||||
severity={severity}
|
||||
isSource={index === 0}
|
||||
isSink={index === codeFlow.threadFlows.length - 1}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const getCodeFlowName = (codeFlow: CodeFlow) => {
|
||||
const filePath = codeFlow.threadFlows[codeFlow.threadFlows.length - 1].fileLink.filePath;
|
||||
return filePath.substring(filePath.lastIndexOf('/') + 1);
|
||||
};
|
||||
|
||||
type CodeFlowsDropdownProps = {
|
||||
codeFlows: CodeFlow[];
|
||||
setSelectedCodeFlow: (value: SetStateAction<CodeFlow>) => void;
|
||||
}
|
||||
|
||||
const CodeFlowsDropdown = ({
|
||||
codeFlows,
|
||||
setSelectedCodeFlow
|
||||
}: CodeFlowsDropdownProps) => {
|
||||
const handleChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedOption = e.target;
|
||||
const selectedIndex = selectedOption.value as unknown as number;
|
||||
setSelectedCodeFlow(codeFlows[selectedIndex]);
|
||||
}, [setSelectedCodeFlow, codeFlows]);
|
||||
|
||||
return (
|
||||
<VSCodeDropdown onChange={handleChange}>
|
||||
{codeFlows.map((codeFlow, index) =>
|
||||
<VSCodeOption
|
||||
key={index}
|
||||
value={index}
|
||||
>
|
||||
{getCodeFlowName(codeFlow)}
|
||||
</VSCodeOption>
|
||||
)}
|
||||
</VSCodeDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
type CodePathsOverlayProps = {
|
||||
codeFlows: CodeFlow[];
|
||||
ruleDescription: string;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const CodePathsOverlay = ({
|
||||
codeFlows,
|
||||
ruleDescription,
|
||||
message,
|
||||
severity,
|
||||
onClose,
|
||||
}: CodePathsOverlayProps) => {
|
||||
const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]);
|
||||
|
||||
return (
|
||||
<OverlayContainer>
|
||||
<CloseButton onClick={onClose} />
|
||||
|
||||
<SectionTitle>{ruleDescription}</SectionTitle>
|
||||
<VerticalSpace size={2} />
|
||||
|
||||
<PathsContainer>
|
||||
<PathDetailsContainer>
|
||||
{codeFlows.length} paths available: {selectedCodeFlow.threadFlows.length} steps in
|
||||
</PathDetailsContainer>
|
||||
<PathDropdownContainer>
|
||||
<CodeFlowsDropdown codeFlows={codeFlows} setSelectedCodeFlow={setSelectedCodeFlow} />
|
||||
</PathDropdownContainer>
|
||||
</PathsContainer>
|
||||
|
||||
<VerticalSpace size={2} />
|
||||
<CodePath
|
||||
codeFlow={selectedCodeFlow}
|
||||
severity={severity}
|
||||
message={message}
|
||||
/>
|
||||
<VerticalSpace size={3} />
|
||||
</OverlayContainer>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
codeFlows: CodeFlow[],
|
||||
ruleDescription: string,
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AnalysisMessage, CodeFlow, ResultSeverity } from '../../../remote-queries/shared/analysis-result';
|
||||
import { SectionTitle } from '../SectionTitle';
|
||||
import { VerticalSpace } from '../VerticalSpace';
|
||||
import { CodeFlowsDropdown } from './CodeFlowsDropdown';
|
||||
import { CodePath } from './CodePath';
|
||||
|
||||
const StyledCloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 4em;
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-editor-foreground);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none
|
||||
}
|
||||
`;
|
||||
|
||||
const OverlayContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 2em;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: var(--vscode-editor-background);
|
||||
color: var(--vscode-editor-foreground);
|
||||
overflow-y: scroll;
|
||||
`;
|
||||
|
||||
const CloseButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<StyledCloseButton onClick={onClick} tabIndex={-1}>
|
||||
<span className="codicon codicon-chrome-close" />
|
||||
</StyledCloseButton>
|
||||
);
|
||||
|
||||
const PathsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const PathDetailsContainer = styled.div`
|
||||
padding: 0;
|
||||
border: 0;
|
||||
`;
|
||||
|
||||
const PathDropdownContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
padding: 0 0 0 0.2em;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
type CodePathsOverlayProps = {
|
||||
codeFlows: CodeFlow[];
|
||||
ruleDescription: string;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const CodePathsOverlay = ({
|
||||
codeFlows,
|
||||
ruleDescription,
|
||||
message,
|
||||
severity,
|
||||
onClose,
|
||||
}: CodePathsOverlayProps) => {
|
||||
const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]);
|
||||
|
||||
return (
|
||||
<OverlayContainer>
|
||||
<CloseButton onClick={onClose} />
|
||||
|
||||
<SectionTitle>{ruleDescription}</SectionTitle>
|
||||
<VerticalSpace size={2} />
|
||||
|
||||
<PathsContainer>
|
||||
<PathDetailsContainer>
|
||||
{codeFlows.length} paths available: {selectedCodeFlow.threadFlows.length} steps in
|
||||
</PathDetailsContainer>
|
||||
<PathDropdownContainer>
|
||||
<CodeFlowsDropdown codeFlows={codeFlows} setSelectedCodeFlow={setSelectedCodeFlow} />
|
||||
</PathDropdownContainer>
|
||||
</PathsContainer>
|
||||
|
||||
<VerticalSpace size={2} />
|
||||
<CodePath
|
||||
codeFlow={selectedCodeFlow}
|
||||
severity={severity}
|
||||
message={message}
|
||||
/>
|
||||
<VerticalSpace size={3} />
|
||||
</OverlayContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VSCodeTag } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { AnalysisMessage, ResultSeverity, ThreadFlow } from '../../../remote-queries/shared/analysis-result';
|
||||
import { SectionTitle } from '../SectionTitle';
|
||||
import { FileCodeSnippet } from '../FileCodeSnippet';
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 55em;
|
||||
margin-bottom: 1.5em;
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
`;
|
||||
|
||||
const TitleContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
const TagContainer = styled.div`
|
||||
padding: 0;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
type ThreadPathProps = {
|
||||
threadFlow: ThreadFlow;
|
||||
step: number;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
isSource?: boolean;
|
||||
isSink?: boolean;
|
||||
}
|
||||
|
||||
export const ThreadPath = ({
|
||||
threadFlow,
|
||||
step,
|
||||
message,
|
||||
severity,
|
||||
isSource,
|
||||
isSink,
|
||||
}: ThreadPathProps) => (
|
||||
<Container>
|
||||
<HeaderContainer>
|
||||
<TitleContainer>
|
||||
<SectionTitle>Step {step}</SectionTitle>
|
||||
</TitleContainer>
|
||||
{isSource &&
|
||||
<TagContainer>
|
||||
<VSCodeTag>Source</VSCodeTag>
|
||||
</TagContainer>
|
||||
}
|
||||
{isSink &&
|
||||
<TagContainer>
|
||||
<VSCodeTag>Sink</VSCodeTag>
|
||||
</TagContainer>
|
||||
}
|
||||
</HeaderContainer>
|
||||
|
||||
<FileCodeSnippet
|
||||
fileLink={threadFlow.fileLink}
|
||||
codeSnippet={threadFlow.codeSnippet}
|
||||
highlightedRegion={threadFlow.highlightedRegion}
|
||||
severity={severity}
|
||||
message={isSink ? message : threadFlow.message}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { HighlightedRegion } from '../../../remote-queries/shared/analysis-result';
|
||||
import { parseHighlightedLine, shouldHighlightLine } from '../../../pure/sarif-utils';
|
||||
|
||||
const replaceSpaceAndTabChar = (text: string) => text.replaceAll(' ', '\u00a0').replaceAll('\t', '\u00a0\u00a0\u00a0\u00a0');
|
||||
|
||||
const HighlightedSpan = styled.span`
|
||||
background-color: var(--vscode-editor-findMatchHighlightBackground);
|
||||
`;
|
||||
|
||||
const PlainCode = ({ text }: { text: string }) => {
|
||||
return <span>{replaceSpaceAndTabChar(text)}</span>;
|
||||
};
|
||||
|
||||
const HighlightedCode = ({ text }: { text: string }) => {
|
||||
return <HighlightedSpan>{replaceSpaceAndTabChar(text)}</HighlightedSpan>;
|
||||
};
|
||||
|
||||
export const CodeSnippetCode = ({
|
||||
line,
|
||||
lineNumber,
|
||||
highlightedRegion
|
||||
}: {
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
highlightedRegion?: HighlightedRegion
|
||||
}) => {
|
||||
if (!highlightedRegion || !shouldHighlightLine(lineNumber, highlightedRegion)) {
|
||||
return <PlainCode text={line} />;
|
||||
}
|
||||
|
||||
const partiallyHighlightedLine = parseHighlightedLine(line, lineNumber, highlightedRegion);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PlainCode text={partiallyHighlightedLine.plainSection1} />
|
||||
<HighlightedCode text={partiallyHighlightedLine.highlightedSection} />
|
||||
<PlainCode text={partiallyHighlightedLine.plainSection2} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AnalysisMessage, HighlightedRegion, ResultSeverity } from '../../../remote-queries/shared/analysis-result';
|
||||
import { CodeSnippetCode } from './CodeSnippetCode';
|
||||
import { CodeSnippetMessage } from './CodeSnippetMessage';
|
||||
|
||||
const MessageContainer = styled.div`
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
`;
|
||||
|
||||
const LineContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const LineNumberContainer = styled.div`
|
||||
border-style: none;
|
||||
padding: 0.01em 0.5em 0.2em;
|
||||
`;
|
||||
|
||||
const CodeSnippetLineCodeContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
border-style: none;
|
||||
padding: 0.01em 0.5em 0.2em 1.5em;
|
||||
word-break: break-word;
|
||||
`;
|
||||
|
||||
type CodeSnippetLineProps = {
|
||||
line: string,
|
||||
lineIndex: number,
|
||||
startingLineIndex: number,
|
||||
highlightedRegion?: HighlightedRegion,
|
||||
severity?: ResultSeverity,
|
||||
message?: AnalysisMessage,
|
||||
messageChildren?: React.ReactNode,
|
||||
};
|
||||
|
||||
export const CodeSnippetLine = ({
|
||||
line,
|
||||
lineIndex,
|
||||
startingLineIndex,
|
||||
highlightedRegion,
|
||||
severity,
|
||||
message,
|
||||
messageChildren
|
||||
}: CodeSnippetLineProps) => {
|
||||
const shouldShowMessage = message &&
|
||||
severity &&
|
||||
highlightedRegion &&
|
||||
highlightedRegion.endLine == startingLineIndex + lineIndex;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LineContainer>
|
||||
<LineNumberContainer>{startingLineIndex + lineIndex}</LineNumberContainer>
|
||||
<CodeSnippetLineCodeContainer>
|
||||
<CodeSnippetCode
|
||||
line={line}
|
||||
lineNumber={startingLineIndex + lineIndex}
|
||||
highlightedRegion={highlightedRegion}
|
||||
/>
|
||||
</CodeSnippetLineCodeContainer>
|
||||
</LineContainer>
|
||||
{shouldShowMessage &&
|
||||
<MessageContainer>
|
||||
<CodeSnippetMessage
|
||||
message={message}
|
||||
severity={severity}
|
||||
>
|
||||
{messageChildren}
|
||||
</CodeSnippetMessage>
|
||||
</MessageContainer>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VSCodeLink } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import { AnalysisMessage, ResultSeverity } from '../../../remote-queries/shared/analysis-result';
|
||||
import { createRemoteFileRef } from '../../../pure/location-link-utils';
|
||||
import { VerticalSpace } from '../VerticalSpace';
|
||||
|
||||
const getSeverityColor = (severity: ResultSeverity) => {
|
||||
switch (severity) {
|
||||
case 'Recommendation':
|
||||
return 'var(--vscode-editorInfo-foreground)';
|
||||
case 'Warning':
|
||||
return 'var(--vscode-editorWarning-foreground)';
|
||||
case 'Error':
|
||||
return 'var(--vscode-editorError-foreground)';
|
||||
}
|
||||
};
|
||||
|
||||
const MessageText = styled.div`
|
||||
font-size: small;
|
||||
padding-left: 0.5em;
|
||||
`;
|
||||
|
||||
type CodeSnippetMessageContainerProps = {
|
||||
severity: ResultSeverity;
|
||||
};
|
||||
|
||||
const CodeSnippetMessageContainer = styled.div<CodeSnippetMessageContainerProps>`
|
||||
border-color: var(--vscode-editor-snippetFinalTabstopHighlightBorder);
|
||||
border-width: 0.1em;
|
||||
border-style: solid;
|
||||
border-left-color: ${props => getSeverityColor(props.severity)};
|
||||
border-left-width: 0.3em;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
`;
|
||||
|
||||
const LocationLink = styled(VSCodeLink)`
|
||||
font-family: var(--vscode-editor-font-family)
|
||||
`;
|
||||
|
||||
type CodeSnippetMessageProps = {
|
||||
message: AnalysisMessage,
|
||||
severity: ResultSeverity,
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export const CodeSnippetMessage = ({
|
||||
message,
|
||||
severity,
|
||||
children
|
||||
}: CodeSnippetMessageProps) => {
|
||||
return (
|
||||
<CodeSnippetMessageContainer
|
||||
severity={severity}
|
||||
>
|
||||
<MessageText>
|
||||
{message.tokens.map((token, index) => {
|
||||
switch (token.t) {
|
||||
case 'text':
|
||||
return <span key={index}>{token.text}</span>;
|
||||
case 'location':
|
||||
return (
|
||||
<LocationLink
|
||||
key={index}
|
||||
href={
|
||||
createRemoteFileRef(
|
||||
token.location.fileLink,
|
||||
token.location.highlightedRegion?.startLine,
|
||||
token.location.highlightedRegion?.endLine
|
||||
)
|
||||
}
|
||||
>
|
||||
{token.text}
|
||||
</LocationLink>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
})}
|
||||
{
|
||||
children && (
|
||||
<>
|
||||
<VerticalSpace size={2} />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</MessageText>
|
||||
</CodeSnippetMessageContainer>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { VSCodeLink } from '@vscode/webview-ui-toolkit/react';
|
||||
|
||||
import {
|
||||
AnalysisMessage,
|
||||
CodeSnippet,
|
||||
@@ -9,24 +10,11 @@ import {
|
||||
ResultSeverity
|
||||
} from '../../../remote-queries/shared/analysis-result';
|
||||
import { createRemoteFileRef } from '../../../pure/location-link-utils';
|
||||
import { parseHighlightedLine, shouldHighlightLine } from '../../../pure/sarif-utils';
|
||||
import { VerticalSpace } from '../VerticalSpace';
|
||||
import { CodeSnippetMessage } from './CodeSnippetMessage';
|
||||
import { CodeSnippetLine } from './CodeSnippetLine';
|
||||
|
||||
const borderColor = 'var(--vscode-editor-snippetFinalTabstopHighlightBorder)';
|
||||
|
||||
const getSeverityColor = (severity: ResultSeverity) => {
|
||||
switch (severity) {
|
||||
case 'Recommendation':
|
||||
return 'var(--vscode-editorInfo-foreground)';
|
||||
case 'Warning':
|
||||
return 'var(--vscode-editorWarning-foreground)';
|
||||
case 'Error':
|
||||
return 'var(--vscode-editorError-foreground)';
|
||||
}
|
||||
};
|
||||
|
||||
const replaceSpaceAndTabChar = (text: string) => text.replaceAll(' ', '\u00a0').replaceAll('\t', '\u00a0\u00a0\u00a0\u00a0');
|
||||
|
||||
const Container = styled.div`
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-size: small;
|
||||
@@ -49,189 +37,6 @@ const CodeContainer = styled.div`
|
||||
padding-bottom: 1em;
|
||||
`;
|
||||
|
||||
const MessageText = styled.div`
|
||||
font-size: small;
|
||||
padding-left: 0.5em;
|
||||
`;
|
||||
|
||||
const MessageContainer = styled.div`
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
`;
|
||||
|
||||
const HighlightedSpan = styled.span`
|
||||
background-color: var(--vscode-editor-findMatchHighlightBackground);
|
||||
`;
|
||||
|
||||
const LineContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const LineNumberContainer = styled.div`
|
||||
border-style: none;
|
||||
padding: 0.01em 0.5em 0.2em;
|
||||
`;
|
||||
|
||||
const CodeSnippetLineCodeContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
border-style: none;
|
||||
padding: 0.01em 0.5em 0.2em 1.5em;
|
||||
word-break: break-word;
|
||||
`;
|
||||
|
||||
type CodeSnippetMessageContainerProps = {
|
||||
severity: ResultSeverity;
|
||||
};
|
||||
|
||||
const CodeSnippetMessageContainer = styled.div<CodeSnippetMessageContainerProps>`
|
||||
border-color: var(--vscode-editor-snippetFinalTabstopHighlightBorder);
|
||||
border-width: 0.1em;
|
||||
border-style: solid;
|
||||
border-left-color: ${props => getSeverityColor(props.severity)};
|
||||
border-left-width: 0.3em;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
`;
|
||||
|
||||
const LocationLink = styled(VSCodeLink)`
|
||||
font-family: var(--vscode-editor-font-family)
|
||||
`;
|
||||
|
||||
const PlainCode = ({ text }: { text: string }) => {
|
||||
return <span>{replaceSpaceAndTabChar(text)}</span>;
|
||||
};
|
||||
|
||||
const HighlightedCode = ({ text }: { text: string }) => {
|
||||
return <HighlightedSpan>{replaceSpaceAndTabChar(text)}</HighlightedSpan>;
|
||||
};
|
||||
|
||||
|
||||
type CodeSnippetMessageProps = {
|
||||
message: AnalysisMessage,
|
||||
severity: ResultSeverity,
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
const CodeSnippetMessage = ({
|
||||
message,
|
||||
severity,
|
||||
children
|
||||
}: CodeSnippetMessageProps) => {
|
||||
return (
|
||||
<CodeSnippetMessageContainer
|
||||
severity={severity}
|
||||
>
|
||||
<MessageText>
|
||||
{message.tokens.map((token, index) => {
|
||||
switch (token.t) {
|
||||
case 'text':
|
||||
return <span key={index}>{token.text}</span>;
|
||||
case 'location':
|
||||
return (
|
||||
<LocationLink
|
||||
key={index}
|
||||
href={
|
||||
createRemoteFileRef(
|
||||
token.location.fileLink,
|
||||
token.location.highlightedRegion?.startLine,
|
||||
token.location.highlightedRegion?.endLine
|
||||
)
|
||||
}
|
||||
>
|
||||
{token.text}
|
||||
</LocationLink>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
})}
|
||||
{
|
||||
children && (
|
||||
<>
|
||||
<VerticalSpace size={2} />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</MessageText>
|
||||
</CodeSnippetMessageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const CodeSnippetCode = ({
|
||||
line,
|
||||
lineNumber,
|
||||
highlightedRegion
|
||||
}: {
|
||||
line: string,
|
||||
lineNumber: number,
|
||||
highlightedRegion?: HighlightedRegion
|
||||
}) => {
|
||||
if (!highlightedRegion || !shouldHighlightLine(lineNumber, highlightedRegion)) {
|
||||
return <PlainCode text={line} />;
|
||||
}
|
||||
|
||||
const partiallyHighlightedLine = parseHighlightedLine(line, lineNumber, highlightedRegion);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PlainCode text={partiallyHighlightedLine.plainSection1} />
|
||||
<HighlightedCode text={partiallyHighlightedLine.highlightedSection} />
|
||||
<PlainCode text={partiallyHighlightedLine.plainSection2} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type CodeSnippetLineProps = {
|
||||
line: string,
|
||||
lineIndex: number,
|
||||
startingLineIndex: number,
|
||||
highlightedRegion?: HighlightedRegion,
|
||||
severity?: ResultSeverity,
|
||||
message?: AnalysisMessage,
|
||||
messageChildren?: React.ReactNode,
|
||||
};
|
||||
|
||||
const CodeSnippetLine = ({
|
||||
line,
|
||||
lineIndex,
|
||||
startingLineIndex,
|
||||
highlightedRegion,
|
||||
severity,
|
||||
message,
|
||||
messageChildren
|
||||
}: CodeSnippetLineProps) => {
|
||||
const shouldShowMessage = message &&
|
||||
severity &&
|
||||
highlightedRegion &&
|
||||
highlightedRegion.endLine == startingLineIndex + lineIndex;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LineContainer>
|
||||
<LineNumberContainer>{startingLineIndex + lineIndex}</LineNumberContainer>
|
||||
<CodeSnippetLineCodeContainer>
|
||||
<CodeSnippetCode
|
||||
line={line}
|
||||
lineNumber={startingLineIndex + lineIndex}
|
||||
highlightedRegion={highlightedRegion}
|
||||
/>
|
||||
</CodeSnippetLineCodeContainer>
|
||||
</LineContainer>
|
||||
{shouldShowMessage &&
|
||||
<MessageContainer>
|
||||
<CodeSnippetMessage
|
||||
message={message}
|
||||
severity={severity}
|
||||
>
|
||||
{messageChildren}
|
||||
</CodeSnippetMessage>
|
||||
</MessageContainer>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
fileLink: FileLink,
|
||||
codeSnippet?: CodeSnippet,
|
||||
|
||||
Reference in New Issue
Block a user