1- import { XCircleIcon } from '@primer/octicons-react' ;
2- import { Overlay } from '@primer/react' ;
3- import { VSCodeDropdown , VSCodeLink , VSCodeOption , VSCodeTag } from '@vscode/webview-ui-toolkit/react' ;
41import * as React from 'react' ;
5- import { ChangeEvent , useRef , useState } from 'react' ;
2+ import { ChangeEvent , SetStateAction , useCallback , useRef , useState } from 'react' ;
63import styled from 'styled-components' ;
7- import { CodeFlow , AnalysisMessage , ResultSeverity } from '../../../remote-queries/shared/analysis-result' ;
4+ import { VSCodeDropdown , VSCodeLink , VSCodeOption , VSCodeTag } from '@vscode/webview-ui-toolkit/react' ;
5+
6+ import { Overlay } from '@primer/react' ;
7+
8+ import { CodeFlow , AnalysisMessage , ResultSeverity , ThreadFlow } from '../../../remote-queries/shared/analysis-result' ;
89import { SectionTitle } from '../SectionTitle' ;
910import { VerticalSpace } from '../VerticalSpace' ;
1011import { FileCodeSnippet } from '../FileCodeSnippet' ;
@@ -16,13 +17,14 @@ const StyledCloseButton = styled.button`
1617 background-color: var(--vscode-editor-background);
1718 color: var(--vscode-editor-foreground);
1819 border: none;
20+ cursor: pointer;
21+
1922 &:focus-visible {
2023 outline: none
2124 }
2225` ;
2326
2427const OverlayContainer = styled . div `
25- padding: 1em;
2628 height: 100%;
2729 width: 100%;
2830 padding: 2em;
@@ -35,142 +37,246 @@ const OverlayContainer = styled.div`
3537` ;
3638
3739const CloseButton = ( { onClick } : { onClick : ( ) => void } ) => (
38- < StyledCloseButton onClick = { onClick } tabIndex = { - 1 } >
39- < XCircleIcon size = { 24 } />
40+ < StyledCloseButton onClick = { onClick } tabIndex = { - 1 } >
41+ < span className = "codicon codicon-chrome-close" />
4042 </ StyledCloseButton >
4143) ;
4244
43- const CodePath = ( {
44- codeFlow,
45+ const PathsContainer = styled . div `
46+ display: flex;
47+ justify-content: center;
48+ align-items: center;
49+ ` ;
50+
51+ const PathDetailsContainer = styled . div `
52+ padding: 0;
53+ border: 0;
54+ ` ;
55+
56+ const PathDropdownContainer = styled . div `
57+ flex-grow: 1;
58+ padding: 0 0 0 0.2em;
59+ border: none;
60+ ` ;
61+
62+ const Container = styled . div `
63+ max-width: 55em;
64+ margin-bottom: 1.5em;
65+ ` ;
66+
67+ const HeaderContainer = styled . div `
68+ display: flex;
69+ justify-content: center;
70+ align-items: center;
71+ margin-bottom: 1em;
72+ ` ;
73+
74+ const TitleContainer = styled . div `
75+ flex-grow: 1;
76+ padding: 0;
77+ border: none;
78+ ` ;
79+
80+ const TagContainer = styled . div `
81+ padding: 0;
82+ border: none;
83+ ` ;
84+
85+ const ShowPathsLink = styled ( VSCodeLink ) `
86+ cursor: pointer;
87+ ` ;
88+
89+ type ThreadPathProps = {
90+ threadFlow : ThreadFlow ;
91+ step : number ;
92+ message : AnalysisMessage ;
93+ severity : ResultSeverity ;
94+ isSource ?: boolean ;
95+ isSink ?: boolean ;
96+ }
97+
98+ const ThreadPath = ( {
99+ threadFlow,
100+ step,
45101 message,
46- severity
47- } : {
102+ severity,
103+ isSource,
104+ isSink,
105+ } : ThreadPathProps ) => (
106+ < Container >
107+ < HeaderContainer >
108+ < TitleContainer >
109+ < SectionTitle > Step { step } </ SectionTitle >
110+ </ TitleContainer >
111+ { isSource &&
112+ < TagContainer >
113+ < VSCodeTag > Source</ VSCodeTag >
114+ </ TagContainer >
115+ }
116+ { isSink &&
117+ < TagContainer >
118+ < VSCodeTag > Sink</ VSCodeTag >
119+ </ TagContainer >
120+ }
121+ </ HeaderContainer >
122+
123+ < FileCodeSnippet
124+ fileLink = { threadFlow . fileLink }
125+ codeSnippet = { threadFlow . codeSnippet }
126+ highlightedRegion = { threadFlow . highlightedRegion }
127+ severity = { severity }
128+ message = { isSink ? message : threadFlow . message }
129+ />
130+ </ Container >
131+ ) ;
132+
133+ type CodePathProps = {
48134 codeFlow : CodeFlow ;
49135 message : AnalysisMessage ;
50136 severity : ResultSeverity ;
51- } ) => {
52- return < >
137+ }
138+
139+ const CodePath = ( {
140+ codeFlow,
141+ message,
142+ severity
143+ } : CodePathProps ) => (
144+ < >
53145 { codeFlow . threadFlows . map ( ( threadFlow , index ) =>
54- < div key = { `thread-flow-${ index } ` } style = { { maxWidth : '55em' } } >
55- { index !== 0 && < VerticalSpace size = { 3 } /> }
56-
57- < div style = { { display : 'flex' , justifyContent : 'center' , alignItems : 'center' } } >
58- < div style = { { flexGrow : 1 , padding : 0 , border : 'none' } } >
59- < SectionTitle > Step { index + 1 } </ SectionTitle >
60- </ div >
61- { index === 0 &&
62- < div style = { { padding : 0 , border : 'none' } } >
63- < VSCodeTag > Source</ VSCodeTag >
64- </ div >
65- }
66- { index === codeFlow . threadFlows . length - 1 &&
67- < div style = { { padding : 0 , border : 'none' } } >
68- < VSCodeTag > Sink</ VSCodeTag >
69- </ div >
70- }
71- </ div >
72-
73- < VerticalSpace size = { 2 } />
74- < FileCodeSnippet
75- fileLink = { threadFlow . fileLink }
76- codeSnippet = { threadFlow . codeSnippet }
77- highlightedRegion = { threadFlow . highlightedRegion }
78- severity = { severity }
79- message = { index === codeFlow . threadFlows . length - 1 ? message : threadFlow . message } />
80- </ div >
146+ < ThreadPath
147+ key = { index }
148+ threadFlow = { threadFlow }
149+ step = { index + 1 }
150+ message = { message }
151+ severity = { severity }
152+ isSource = { index === 0 }
153+ isSink = { index === codeFlow . threadFlows . length - 1 }
154+ />
81155 ) }
82- </ > ;
83- } ;
156+ </ >
157+ ) ;
84158
85159const getCodeFlowName = ( codeFlow : CodeFlow ) => {
86160 const filePath = codeFlow . threadFlows [ codeFlow . threadFlows . length - 1 ] . fileLink . filePath ;
87161 return filePath . substring ( filePath . lastIndexOf ( '/' ) + 1 ) ;
88162} ;
89163
90- const Menu = ( {
164+ type CodeFlowsDropdownProps = {
165+ codeFlows : CodeFlow [ ] ;
166+ setSelectedCodeFlow : ( value : SetStateAction < CodeFlow > ) => void ;
167+ }
168+
169+ const CodeFlowsDropdown = ( {
91170 codeFlows,
92171 setSelectedCodeFlow
93- } : {
94- codeFlows : CodeFlow [ ] ,
95- setSelectedCodeFlow : ( value : React . SetStateAction < CodeFlow > ) => void
96- } ) => {
97- return < VSCodeDropdown
98- onChange = { ( event : ChangeEvent < HTMLSelectElement > ) => {
99- const selectedOption = event . target ;
100- const selectedIndex = selectedOption . value as unknown as number ;
101- setSelectedCodeFlow ( codeFlows [ selectedIndex ] ) ;
102- } }
103- >
104- { codeFlows . map ( ( codeFlow , index ) =>
105- < VSCodeOption
106- key = { `codeflow-${ index } '` }
107- value = { index }
108- >
109- { getCodeFlowName ( codeFlow ) }
110- </ VSCodeOption >
111- ) }
112- </ VSCodeDropdown > ;
172+ } : CodeFlowsDropdownProps ) => {
173+ const handleChange = useCallback ( ( e : ChangeEvent < HTMLSelectElement > ) => {
174+ const selectedOption = e . target ;
175+ const selectedIndex = selectedOption . value as unknown as number ;
176+ setSelectedCodeFlow ( codeFlows [ selectedIndex ] ) ;
177+ } , [ setSelectedCodeFlow , codeFlows ] ) ;
178+
179+ return (
180+ < VSCodeDropdown onChange = { handleChange } >
181+ { codeFlows . map ( ( codeFlow , index ) =>
182+ < VSCodeOption
183+ key = { index }
184+ value = { index }
185+ >
186+ { getCodeFlowName ( codeFlow ) }
187+ </ VSCodeOption >
188+ ) }
189+ </ VSCodeDropdown >
190+ ) ;
113191} ;
114192
115- export const CodePaths = ( {
193+ type CodePathsOverlayProps = {
194+ codeFlows : CodeFlow [ ] ;
195+ ruleDescription : string ;
196+ message : AnalysisMessage ;
197+ severity : ResultSeverity ;
198+ onClose : ( ) => void ;
199+ }
200+
201+ const CodePathsOverlay = ( {
116202 codeFlows,
117203 ruleDescription,
118204 message,
119- severity
120- } : {
205+ severity,
206+ onClose,
207+ } : CodePathsOverlayProps ) => {
208+ const [ selectedCodeFlow , setSelectedCodeFlow ] = useState ( codeFlows [ 0 ] ) ;
209+
210+ return (
211+ < OverlayContainer >
212+ < CloseButton onClick = { onClose } />
213+
214+ < SectionTitle > { ruleDescription } </ SectionTitle >
215+ < VerticalSpace size = { 2 } />
216+
217+ < PathsContainer >
218+ < PathDetailsContainer >
219+ { codeFlows . length } paths available: { selectedCodeFlow . threadFlows . length } steps in
220+ </ PathDetailsContainer >
221+ < PathDropdownContainer >
222+ < CodeFlowsDropdown codeFlows = { codeFlows } setSelectedCodeFlow = { setSelectedCodeFlow } />
223+ </ PathDropdownContainer >
224+ </ PathsContainer >
225+
226+ < VerticalSpace size = { 2 } />
227+ < CodePath
228+ codeFlow = { selectedCodeFlow }
229+ severity = { severity }
230+ message = { message }
231+ />
232+ < VerticalSpace size = { 3 } />
233+ </ OverlayContainer >
234+ ) ;
235+ } ;
236+
237+ type Props = {
121238 codeFlows : CodeFlow [ ] ,
122239 ruleDescription : string ,
123240 message : AnalysisMessage ,
124241 severity : ResultSeverity
125- } ) => {
242+ } ;
243+
244+ export const CodePaths = ( {
245+ codeFlows,
246+ ruleDescription,
247+ message,
248+ severity
249+ } : Props ) => {
126250 const [ isOpen , setIsOpen ] = useState ( false ) ;
127- const [ selectedCodeFlow , setSelectedCodeFlow ] = useState ( codeFlows [ 0 ] ) ;
128251
129- const anchorRef = useRef < HTMLDivElement > ( null ) ;
130252 const linkRef = useRef < HTMLAnchorElement > ( null ) ;
131253
132254 const closeOverlay = ( ) => setIsOpen ( false ) ;
133255
134256 return (
135- < div ref = { anchorRef } >
136- < VSCodeLink
257+ < >
258+ < ShowPathsLink
137259 onClick = { ( ) => setIsOpen ( true ) }
138260 ref = { linkRef }
139- sx = { { cursor : 'pointer' } } >
261+ >
140262 Show paths
141- </ VSCodeLink >
263+ </ ShowPathsLink >
142264 { isOpen && (
143265 < Overlay
144266 returnFocusRef = { linkRef }
145267 onEscape = { closeOverlay }
146268 onClickOutside = { closeOverlay }
147- anchorSide = "outside-top" >
148- < OverlayContainer >
149- < CloseButton onClick = { closeOverlay } />
150-
151- < SectionTitle > { ruleDescription } </ SectionTitle >
152- < VerticalSpace size = { 2 } />
153-
154- < div style = { { display : 'flex' , justifyContent : 'center' , alignItems : 'center' } } >
155- < div style = { { padding : 0 , border : 0 } } >
156- { codeFlows . length } paths available: { selectedCodeFlow . threadFlows . length } steps in
157- </ div >
158- < div style = { { flexGrow : 1 , padding : 0 , paddingLeft : '0.2em' , border : 'none' } } >
159- < Menu codeFlows = { codeFlows } setSelectedCodeFlow = { setSelectedCodeFlow } />
160- </ div >
161- </ div >
162-
163- < VerticalSpace size = { 2 } />
164- < CodePath
165- codeFlow = { selectedCodeFlow }
166- severity = { severity }
167- message = { message } />
168-
169- < VerticalSpace size = { 3 } />
170-
171- </ OverlayContainer >
269+ anchorSide = "outside-top"
270+ >
271+ < CodePathsOverlay
272+ codeFlows = { codeFlows }
273+ ruleDescription = { ruleDescription }
274+ message = { message }
275+ severity = { severity }
276+ onClose = { closeOverlay }
277+ />
172278 </ Overlay >
173279 ) }
174- </ div >
280+ </ >
175281 ) ;
176282} ;
0 commit comments