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 * as React from 'react';
|
||||||
import { ChangeEvent, SetStateAction, useCallback, useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
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 { Overlay } from '@primer/react';
|
||||||
|
|
||||||
import { CodeFlow, AnalysisMessage, ResultSeverity, ThreadFlow } from '../../../remote-queries/shared/analysis-result';
|
import { AnalysisMessage, CodeFlow, ResultSeverity } from '../../../remote-queries/shared/analysis-result';
|
||||||
import { SectionTitle } from '../SectionTitle';
|
import { CodePathsOverlay } from './CodePathsOverlay';
|
||||||
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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ShowPathsLink = styled(VSCodeLink)`
|
const ShowPathsLink = styled(VSCodeLink)`
|
||||||
cursor: pointer;
|
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 = {
|
type Props = {
|
||||||
codeFlows: CodeFlow[],
|
codeFlows: CodeFlow[],
|
||||||
ruleDescription: string,
|
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 * as React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { VSCodeLink } from '@vscode/webview-ui-toolkit/react';
|
import { VSCodeLink } from '@vscode/webview-ui-toolkit/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalysisMessage,
|
AnalysisMessage,
|
||||||
CodeSnippet,
|
CodeSnippet,
|
||||||
@@ -9,24 +10,11 @@ import {
|
|||||||
ResultSeverity
|
ResultSeverity
|
||||||
} from '../../../remote-queries/shared/analysis-result';
|
} from '../../../remote-queries/shared/analysis-result';
|
||||||
import { createRemoteFileRef } from '../../../pure/location-link-utils';
|
import { createRemoteFileRef } from '../../../pure/location-link-utils';
|
||||||
import { parseHighlightedLine, shouldHighlightLine } from '../../../pure/sarif-utils';
|
import { CodeSnippetMessage } from './CodeSnippetMessage';
|
||||||
import { VerticalSpace } from '../VerticalSpace';
|
import { CodeSnippetLine } from './CodeSnippetLine';
|
||||||
|
|
||||||
const borderColor = 'var(--vscode-editor-snippetFinalTabstopHighlightBorder)';
|
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`
|
const Container = styled.div`
|
||||||
font-family: var(--vscode-editor-font-family);
|
font-family: var(--vscode-editor-font-family);
|
||||||
font-size: small;
|
font-size: small;
|
||||||
@@ -49,189 +37,6 @@ const CodeContainer = styled.div`
|
|||||||
padding-bottom: 1em;
|
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 = {
|
type Props = {
|
||||||
fileLink: FileLink,
|
fileLink: FileLink,
|
||||||
codeSnippet?: CodeSnippet,
|
codeSnippet?: CodeSnippet,
|
||||||
|
|||||||
Reference in New Issue
Block a user