Skip to content

Commit c3e3390

Browse files
committed
Extract BQRS locations from string results
1 parent 010ae64 commit c3e3390

File tree

2 files changed

+59
-30
lines changed

2 files changed

+59
-30
lines changed

extensions/ql-vscode/test/pure-tests/location.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
11
import { expect } from 'chai';
22
import 'mocha';
3-
import { LocationStyle, StringLocation, tryGetWholeFileLocation } from 'semmle-bqrs';
3+
import { LocationStyle, StringLocation, tryGetResolvableLocation } from 'semmle-bqrs';
44

55
describe('processing string locations', function () {
66
it('should detect Windows whole-file locations', function () {
77
const loc: StringLocation = {
88
t: LocationStyle.String,
99
loc: 'file://C:/path/to/file.ext:0:0:0:0'
1010
};
11-
const wholeFileLoc = tryGetWholeFileLocation(loc);
11+
const wholeFileLoc = tryGetResolvableLocation(loc);
1212
expect(wholeFileLoc).to.eql({t: LocationStyle.WholeFile, file: 'C:/path/to/file.ext'});
1313
});
1414
it('should detect Unix whole-file locations', function () {
1515
const loc: StringLocation = {
1616
t: LocationStyle.String,
1717
loc: 'file:///path/to/file.ext:0:0:0:0'
1818
};
19-
const wholeFileLoc = tryGetWholeFileLocation(loc);
19+
const wholeFileLoc = tryGetResolvableLocation(loc);
2020
expect(wholeFileLoc).to.eql({t: LocationStyle.WholeFile, file: '/path/to/file.ext'});
2121
});
22+
it('should detect Unix 5-part locations', function () {
23+
const loc: StringLocation = {
24+
t: LocationStyle.String,
25+
loc: 'file:///path/to/file.ext:1:2:3:4'
26+
};
27+
const wholeFileLoc = tryGetResolvableLocation(loc);
28+
expect(wholeFileLoc).to.eql({
29+
t: LocationStyle.FivePart,
30+
file: '/path/to/file.ext',
31+
lineStart: 1,
32+
colStart: 2,
33+
lineEnd: 3,
34+
colEnd: 4
35+
});
36+
});
2237
it('should ignore other string locations', function () {
2338
for (const loc of ['file:///path/to/file.ext', 'I am not a location']) {
24-
const wholeFileLoc = tryGetWholeFileLocation({
39+
const wholeFileLoc = tryGetResolvableLocation({
2540
t: LocationStyle.String,
2641
loc: loc
2742
});

lib/semmle-bqrs/src/bqrs-results.ts

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LocationStyle } from './bqrs-schema';
1+
import { LocationStyle } from "./bqrs-schema";
22

33
// See https://help.semmle.com/QL/learn-ql/ql/locations.html for how these are used.
44
export interface FivePartLocation {
@@ -31,54 +31,69 @@ export type LocationValue = RawLocationValue | WholeFileLocation;
3131
/** A location that may be resolved to a source code element. */
3232
export type ResolvableLocationValue = FivePartLocation | WholeFileLocation;
3333

34-
3534
/**
3635
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates
3736
* to describe the location of an entire filesystem resource.
3837
* Such locations appear as `StringLocation`s instead of `FivePartLocation`s.
39-
*
38+
*
4039
* Folder resources also get similar URLs, but with the `folder` scheme.
4140
* They are deliberately ignored here, since there is no suitable location to show the user.
4241
*/
43-
const WHOLE_FILE_LOCATION_REGEX = /file:\/\/(.+):0:0:0:0/;
44-
42+
const FILE_LOCATION_REGEX = /file:\/\/(.+):([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
4543
/**
4644
* Gets a resolvable source file location for the specified `LocationValue`, if possible.
4745
* @param loc The location to test.
4846
*/
49-
export function tryGetResolvableLocation(loc: LocationValue | undefined): ResolvableLocationValue | undefined {
47+
export function tryGetResolvableLocation(
48+
loc: LocationValue | undefined
49+
): ResolvableLocationValue | undefined {
5050
if (loc === undefined) {
5151
return undefined;
52-
}
53-
else if ((loc.t === LocationStyle.FivePart) && loc.file) {
52+
} else if (loc.t === LocationStyle.FivePart && loc.file) {
5453
return loc;
55-
}
56-
else if ((loc.t === LocationStyle.WholeFile) && loc.file) {
54+
} else if (loc.t === LocationStyle.WholeFile && loc.file) {
5755
return loc;
58-
}
59-
else if ((loc.t === LocationStyle.String) && loc.loc) {
60-
return tryGetWholeFileLocation(loc);
61-
}
62-
else {
56+
} else if (loc.t === LocationStyle.String && loc.loc) {
57+
return tryGetLocationFromString(loc);
58+
} else {
6359
return undefined;
6460
}
6561
}
6662

67-
export function tryGetWholeFileLocation(loc: StringLocation): WholeFileLocation | undefined {
68-
const matches = WHOLE_FILE_LOCATION_REGEX.exec(loc.loc);
63+
export function tryGetLocationFromString(
64+
loc: StringLocation
65+
): ResolvableLocationValue | undefined {
66+
const matches = FILE_LOCATION_REGEX.exec(loc.loc);
6967
if (matches && matches.length > 1 && matches[1]) {
70-
// Whole-file location.
71-
// We could represent this as a FivePartLocation with all numeric fields set to zero,
72-
// but that would be a deliberate misuse as those fields are intended to be 1-based.
73-
return {
74-
t: LocationStyle.WholeFile,
75-
file: matches[1]
76-
};
68+
if (isWholeFileMatch(matches)) {
69+
return {
70+
t: LocationStyle.WholeFile,
71+
file: matches[1],
72+
};
73+
} else {
74+
return {
75+
t: LocationStyle.FivePart,
76+
file: matches[1],
77+
lineStart: Number(matches[2]),
78+
colStart: Number(matches[3]),
79+
lineEnd: Number(matches[4]),
80+
colEnd: Number(matches[5]),
81+
}
82+
}
7783
} else {
7884
return undefined;
7985
}
8086
}
8187

88+
function isWholeFileMatch(matches: RegExpExecArray): boolean {
89+
return (
90+
matches[2] === "0" &&
91+
matches[3] === "0" &&
92+
matches[4] === "0" &&
93+
matches[5] === "0"
94+
);
95+
}
96+
8297
export interface ElementBase {
8398
id: PrimitiveColumnValue;
8499
label?: string;
@@ -93,8 +108,7 @@ export interface ElementWithLocation extends ElementBase {
93108
location: LocationValue;
94109
}
95110

96-
export interface Element extends Required<ElementBase> {
97-
}
111+
export interface Element extends Required<ElementBase> {}
98112

99113
export type PrimitiveColumnValue = string | boolean | number | Date;
100114
export type ColumnValue = PrimitiveColumnValue | ElementBase;

0 commit comments

Comments
 (0)