@@ -3,9 +3,9 @@ import * as React from 'react';
33import * as Sarif from 'sarif' ;
44import * as Keys from '../../pure/result-keys' ;
55import * as octicons from './octicons' ;
6- import { className , renderLocation , ResultTableProps , zebraStripe , selectableZebraStripe , jumpToLocation , nextSortDirection , emptyQueryResultsMessage } from './result-table-utils' ;
7- import { onNavigation , NavigationEvent } from './results' ;
8- import { InterpretedResultSet , NavigateAlertMsg , NavigatePathMsg , SarifInterpretationData } from '../../pure/interface-types' ;
6+ import { className , renderLocation , ResultTableProps , selectableZebraStripe , jumpToLocation , nextSortDirection , emptyQueryResultsMessage } from './result-table-utils' ;
7+ import { onNavigation } from './results' ;
8+ import { InterpretedResultSet , NavigateMsg , NavigationDirection , SarifInterpretationData } from '../../pure/interface-types' ;
99import {
1010 parseSarifPlainTextMessage ,
1111 parseSarifLocation ,
@@ -18,7 +18,7 @@ import { isWholeFileLoc, isLineColumnLoc } from '../../pure/bqrs-utils';
1818export type PathTableProps = ResultTableProps & { resultSet : InterpretedResultSet < SarifInterpretationData > } ;
1919export interface PathTableState {
2020 expanded : Set < string > ;
21- selectedItem : undefined | Keys . PathNode | Keys . Result ;
21+ selectedItem : undefined | Keys . ResultKey ;
2222}
2323
2424export class PathTable extends React . Component < PathTableProps , PathTableState > {
@@ -246,8 +246,9 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
246246 const currentPathExpanded = this . state . expanded . has ( Keys . keyToString ( pathKey ) ) ;
247247 if ( currentResultExpanded ) {
248248 const indicator = currentPathExpanded ? octicons . chevronDown : octicons . chevronRight ;
249+ const isPathSpecificallySelected = Keys . equalsNotUndefined ( pathKey , selectedItem ) ;
249250 rows . push (
250- < tr { ...zebraStripe ( resultIndex ) } key = { `${ resultIndex } -${ pathIndex } ` } >
251+ < tr { ...selectableZebraStripe ( isPathSpecificallySelected , resultIndex ) } key = { `${ resultIndex } -${ pathIndex } ` } >
251252 < td className = "vscode-codeql__icon-cell" > < span className = "vscode-codeql__vertical-rule" > </ span > </ td >
252253 < td className = "vscode-codeql__icon-cell vscode-codeql__dropdown-cell" onMouseDown = { toggler ( [ pathKey ] ) } > { indicator } </ td >
253254 < td className = "vscode-codeql__text-center" colSpan = { 3 } >
@@ -302,82 +303,89 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
302303 </ table > ;
303304 }
304305
305- private handleNavigationEvent ( event : NavigationEvent ) {
306- switch ( event . t ) {
307- case 'navigatePath' : {
308- this . handleNavigatePathStepEvent ( event ) ;
309- break ;
310- }
311- case 'navigateAlert' : {
312- this . handleNavigateAlertEvent ( event ) ;
313- break ;
314- }
315- }
316- }
317-
318- private handleNavigatePathStepEvent ( event : NavigatePathMsg ) {
306+ private handleNavigationEvent ( event : NavigateMsg ) {
319307 this . setState ( prevState => {
320- const { selectedItem } = prevState ;
321- if ( selectedItem === undefined ) return prevState ;
322-
323- const selectedPath = selectedItem . pathIndex !== undefined
324- ? selectedItem
325- : { ...selectedItem , pathIndex : 0 } ;
326-
327- const path = Keys . getPath ( this . props . resultSet . interpretation . data , selectedPath ) ;
328- if ( path === undefined ) return prevState ;
329-
330- const nextIndex = selectedItem . pathNodeIndex !== undefined
331- ? ( selectedItem . pathNodeIndex + event . direction )
332- : 0 ;
333- if ( nextIndex < 0 || nextIndex >= path . locations . length ) return prevState ;
334-
335- const sarifLoc = path . locations [ nextIndex ] . location ;
336- if ( sarifLoc === undefined ) {
337- return prevState ;
308+ const key = this . getNewSelection ( prevState . selectedItem , event . direction ) ;
309+ const data = this . props . resultSet . interpretation . data ;
310+
311+ // Check if the selected node actually exists (bounds check) and get its location if relevant
312+ let jumpLocation : Sarif . Location | undefined ;
313+ if ( key . pathNodeIndex != null ) {
314+ jumpLocation = Keys . getPathNode ( data , key ) ;
315+ if ( jumpLocation == null ) {
316+ return prevState ; // Result does not exist
317+ }
318+ } else if ( key . pathIndex != null ) {
319+ if ( Keys . getPath ( data , key ) == null ) {
320+ return prevState ; // Path does not exist
321+ }
322+ jumpLocation = undefined ; // When selecting a 'path', don't jump anywhere.
323+ } else {
324+ jumpLocation = Keys . getResult ( data , key ) ?. locations ?. [ 0 ] ;
325+ if ( jumpLocation == null ) {
326+ return prevState ; // Path step does not exist.
327+ }
338328 }
339-
340- const loc = parseSarifLocation ( sarifLoc , this . props . resultSet . interpretation . sourceLocationPrefix ) ;
341- if ( isNoLocation ( loc ) ) {
342- return prevState ;
329+ if ( jumpLocation !== undefined ) {
330+ const parsedLocation = parseSarifLocation ( jumpLocation , this . props . resultSet . interpretation . sourceLocationPrefix ) ;
331+ if ( ! isNoLocation ( parsedLocation ) ) {
332+ jumpToLocation ( parsedLocation , this . props . databaseUri ) ;
333+ }
343334 }
344335
345- jumpToLocation ( loc , this . props . databaseUri ) ;
346- const newSelection = { ...selectedPath , pathNodeIndex : nextIndex } ;
347- const newExpanded = new Set ( prevState . expanded ) ;
348- // In case we're jumping from the main alert row to its first path step, expand the enclosing nodes so the selected
349- // node is actually visible.
350- newExpanded . add ( Keys . keyToString ( { resultIndex : newSelection . resultIndex } ) ) ;
351- newExpanded . add ( Keys . keyToString ( { resultIndex : newSelection . resultIndex , pathIndex : newSelection . pathIndex } ) ) ;
352- return { ...prevState , selectedItem : newSelection , expanded : newExpanded } ;
336+ const expanded = new Set ( prevState . expanded ) ;
337+ if ( event . direction === NavigationDirection . right ) {
338+ // When stepping right, expand to ensure the selected node is visible
339+ expanded . add ( Keys . keyToString ( { resultIndex : key . resultIndex } ) ) ;
340+ if ( key . pathIndex != null ) {
341+ expanded . add ( Keys . keyToString ( { resultIndex : key . resultIndex , pathIndex : key . pathIndex } ) ) ;
342+ }
343+ } else if ( event . direction === NavigationDirection . left ) {
344+ // When stepping left, collapse immediately
345+ expanded . delete ( Keys . keyToString ( key ) ) ;
346+ }
347+ return {
348+ ...prevState ,
349+ expanded,
350+ selectedItem : key
351+ } ;
353352 } ) ;
354353 }
355354
356- private handleNavigateAlertEvent ( event : NavigateAlertMsg ) {
357- this . setState ( prevState => {
358- const { selectedItem } = prevState ;
359- if ( selectedItem === undefined ) return prevState ;
360-
361- const nextIndex = selectedItem . resultIndex + event . direction ;
362- const nextSelection = { resultIndex : nextIndex } ;
363- const result = Keys . getResult ( this . props . resultSet . interpretation . data , nextSelection ) ;
364- if ( result === undefined ) {
365- return prevState ;
366- }
367-
368- const sarifLoc = result . locations ?. [ 0 ] ;
369- if ( sarifLoc === undefined ) {
370- return prevState ;
371- }
372-
373- const loc = parseSarifLocation ( sarifLoc , this . props . resultSet . interpretation . sourceLocationPrefix ) ;
374- if ( isNoLocation ( loc ) ) {
375- return prevState ;
355+ private getNewSelection ( key : Keys . ResultKey | undefined , direction : NavigationDirection ) : Keys . ResultKey {
356+ if ( key === undefined ) {
357+ return { resultIndex : 0 } ;
358+ }
359+ const { resultIndex, pathIndex, pathNodeIndex } = key ;
360+ switch ( direction ) {
361+ case NavigationDirection . up :
362+ case NavigationDirection . down : {
363+ const delta = direction === NavigationDirection . up ? - 1 : 1 ;
364+ if ( key . pathNodeIndex !== undefined ) {
365+ return { resultIndex, pathIndex : key . pathIndex , pathNodeIndex : key . pathNodeIndex + delta } ;
366+ } else if ( pathIndex !== undefined ) {
367+ return { resultIndex, pathIndex : pathIndex + delta } ;
368+ } else {
369+ return { resultIndex : resultIndex + delta } ;
370+ }
376371 }
377-
378- jumpToLocation ( loc , this . props . databaseUri ) ;
379- return { ...prevState , selectedItem : nextSelection } ;
380- } ) ;
372+ case NavigationDirection . left :
373+ if ( key . pathNodeIndex !== undefined ) {
374+ return { resultIndex, pathIndex : key . pathIndex } ;
375+ } else if ( pathIndex !== undefined ) {
376+ return { resultIndex } ;
377+ } else {
378+ return key ;
379+ }
380+ case NavigationDirection . right :
381+ if ( pathIndex === undefined ) {
382+ return { resultIndex, pathIndex : 0 } ;
383+ } else if ( pathNodeIndex === undefined ) {
384+ return { resultIndex, pathIndex, pathNodeIndex : 0 } ;
385+ } else {
386+ return key ;
387+ }
388+ }
381389 }
382390
383391 componentDidMount ( ) {
0 commit comments