Skip to content

Commit c66fe07

Browse files
committed
Return undefined for finding file ranges on empty URI
Also, refactor resolveSourceFile to make it easier to read. And add unit tests for resolveSourceFile. This commit fixes a bug in resolveSourceFile where the `pathWithinSourceArchive` was being removed and appended to the `sourceArchiveZipPath`. In normal situations, we don't hit this bug because most database source archive uris have an empty path for the `pathWithinSourceArchive`.
1 parent fe219e0 commit c66fe07

File tree

5 files changed

+155
-33
lines changed

5 files changed

+155
-33
lines changed

extensions/ql-vscode/src/bqrs-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function isWholeFileMatch(matches: RegExpExecArray): boolean {
6868
*
6969
* @param uri A file uri
7070
*/
71-
function isEmptyPath(uriStr: string) {
71+
export function isEmptyPath(uriStr: string) {
7272
return !uriStr || uriStr === 'file:/';
7373
}
7474

extensions/ql-vscode/src/contextual/fileRangeFromURI.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as vscode from 'vscode';
22

33
import { UrlValue, LineColumnLocation } from '../bqrs-cli-types';
4+
import { isEmptyPath } from '../bqrs-utils';
45
import { DatabaseItem } from '../databases';
56

67

@@ -11,6 +12,9 @@ export default function fileRangeFromURI(uri: UrlValue | undefined, db: Database
1112
return undefined;
1213
} else {
1314
const loc = uri as LineColumnLocation;
15+
if (isEmptyPath(loc.uri)) {
16+
return undefined;
17+
}
1418
const range = new vscode.Range(Math.max(0, (loc.startLine || 0) - 1),
1519
Math.max(0, (loc.startColumn || 0) - 1),
1620
Math.max(0, (loc.endLine || 0) - 1),

extensions/ql-vscode/src/databases.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ export interface DatabaseChangedEvent {
267267
item: DatabaseItem | undefined;
268268
}
269269

270-
class DatabaseItemImpl implements DatabaseItem {
270+
// Exported for testing
271+
export class DatabaseItemImpl implements DatabaseItem {
271272
private _error: Error | undefined = undefined;
272273
private _contents: DatabaseContents | undefined;
273274
/** A cache of database info */
@@ -301,8 +302,7 @@ class DatabaseItemImpl implements DatabaseItem {
301302
public get sourceArchive(): vscode.Uri | undefined {
302303
if (this.options.ignoreSourceArchive || (this._contents === undefined)) {
303304
return undefined;
304-
}
305-
else {
305+
} else {
306306
return this._contents.sourceArchiveUri;
307307
}
308308
}
@@ -341,42 +341,42 @@ class DatabaseItemImpl implements DatabaseItem {
341341

342342
public resolveSourceFile(uri: string | undefined): vscode.Uri {
343343
const sourceArchive = this.sourceArchive;
344-
// FIXME: This is wrong. Should parse the uri properly first
344+
// Sometimes, we are passed a path, sometimes a file URI.
345+
// We need to convert this to a file path that is probably inside of a zip file.
345346
const file = uri?.replace(/file:/, '');
346-
if (sourceArchive === undefined) {
347-
if (file !== undefined) {
347+
if (!sourceArchive) {
348+
if (file) {
348349
// Treat it as an absolute path.
349350
return vscode.Uri.file(file);
350-
}
351-
else {
351+
} else {
352352
return this.databaseUri;
353353
}
354354
}
355-
else {
356-
if (file !== undefined) {
357-
const absoluteFilePath = file.replace(':', '_');
358-
// Strip any leading slashes from the file path, and replace `:` with `_`.
359-
const relativeFilePath = absoluteFilePath.replace(/^\/*/, '').replace(':', '_');
360-
if (sourceArchive.scheme == zipArchiveScheme) {
361-
return encodeSourceArchiveUri({
362-
pathWithinSourceArchive: absoluteFilePath,
363-
sourceArchiveZipPath: sourceArchive.fsPath,
364-
});
365-
}
366-
else {
367-
let newPath = sourceArchive.path;
368-
if (!newPath.endsWith('/')) {
369-
// Ensure a trailing slash.
370-
newPath += '/';
371-
}
372-
newPath += relativeFilePath;
373355

374-
return sourceArchive.with({ path: newPath });
356+
if (file) {
357+
const absoluteFilePath = file.replace(':', '_');
358+
// Strip any leading slashes from the file path, and replace `:` with `_`.
359+
const relativeFilePath = absoluteFilePath.replace(/^\/*/, '').replace(':', '_');
360+
if (sourceArchive.scheme === zipArchiveScheme) {
361+
const zipRef = decodeSourceArchiveUri(sourceArchive);
362+
return encodeSourceArchiveUri({
363+
pathWithinSourceArchive: zipRef.pathWithinSourceArchive + '/' + absoluteFilePath,
364+
sourceArchiveZipPath: zipRef.sourceArchiveZipPath,
365+
});
366+
367+
} else {
368+
let newPath = sourceArchive.path;
369+
if (!newPath.endsWith('/')) {
370+
// Ensure a trailing slash.
371+
newPath += '/';
375372
}
373+
newPath += relativeFilePath;
374+
375+
return sourceArchive.with({ path: newPath });
376376
}
377-
else {
378-
return sourceArchive;
379-
}
377+
378+
} else {
379+
return sourceArchive;
380380
}
381381
}
382382

extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ describe('fileRangeFromURI', () => {
1212
expect(fileRangeFromURI('hucairz', createMockDatabaseItem())).to.be.undefined;
1313
});
1414

15+
it('should return undefined when value is an empty uri', () => {
16+
expect(fileRangeFromURI({
17+
uri: 'file:/',
18+
startLine: 1,
19+
startColumn: 2,
20+
endLine: 3,
21+
endColumn: 4,
22+
} as LineColumnLocation, createMockDatabaseItem())).to.be.undefined;
23+
});
24+
1525
it('should return a range for a WholeFileLocation', () => {
1626
expect(fileRangeFromURI({
1727
uri: 'file:///hucairz',

extensions/ql-vscode/src/vscode-tests/no-workspace/databases.test.ts

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@ import 'vscode-test';
22
import 'mocha';
33
import * as sinon from 'sinon';
44
import { expect } from 'chai';
5-
import { ExtensionContext } from 'vscode';
5+
import { ExtensionContext, Uri } from 'vscode';
66

7-
import { DatabaseEventKind, DatabaseItem, DatabaseManager } from '../../databases';
7+
import {
8+
DatabaseEventKind,
9+
DatabaseItem,
10+
DatabaseManager,
11+
DatabaseItemImpl,
12+
DatabaseContents
13+
} from '../../databases';
814
import { QueryServerConfig } from '../../config';
915
import { Logger } from '../../logging';
16+
import { encodeSourceArchiveUri } from '../../archive-filesystem-provider';
1017

1118
describe('databases', () => {
1219
let databaseManager: DatabaseManager;
@@ -78,4 +85,105 @@ describe('databases', () => {
7885
kind: DatabaseEventKind.Rename
7986
});
8087
});
88+
89+
describe('resolveSourceFile', () => {
90+
describe('unzipped source archive', () => {
91+
it('should resolve a source file in an unzipped database', () => {
92+
const db = createMockDB();
93+
const resolved = db.resolveSourceFile('abc');
94+
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/abc');
95+
});
96+
97+
it('should resolve a source file in an unzipped database with trailing slash', () => {
98+
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
99+
const resolved = db.resolveSourceFile('abc');
100+
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/abc');
101+
});
102+
103+
it('should resolve a source uri in an unzipped database with trailing slash', () => {
104+
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
105+
const resolved = db.resolveSourceFile('file:/abc');
106+
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/abc');
107+
});
108+
});
109+
110+
describe('no source archive', () => {
111+
it('should resolve a file', () => {
112+
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
113+
(db as any)._contents.sourceArchiveUri = undefined;
114+
const resolved = db.resolveSourceFile('abc');
115+
expect(resolved.toString()).to.eq('file:///abc');
116+
});
117+
118+
it('should resolve an empty file', () => {
119+
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
120+
(db as any)._contents.sourceArchiveUri = undefined;
121+
const resolved = db.resolveSourceFile('file:');
122+
expect(resolved.toString()).to.eq('file:///database-uri');
123+
});
124+
});
125+
126+
describe('zipped source archive', () => {
127+
it('should encode a source archive url', () => {
128+
const db = createMockDB(encodeSourceArchiveUri({
129+
sourceArchiveZipPath: 'sourceArchive-uri',
130+
pathWithinSourceArchive: 'def'
131+
}));
132+
const resolved = db.resolveSourceFile(Uri.file('abc').toString());
133+
134+
// must recreate an encoded archive uri instead of typing out the
135+
// text since the uris will be different on windows and ubuntu.
136+
expect(resolved.toString()).to.eq(encodeSourceArchiveUri({
137+
sourceArchiveZipPath: 'sourceArchive-uri',
138+
pathWithinSourceArchive: 'def/abc'
139+
}).toString());
140+
});
141+
142+
it('should encode a source archive url with trailing slash', () => {
143+
const db = createMockDB(encodeSourceArchiveUri({
144+
sourceArchiveZipPath: 'sourceArchive-uri',
145+
pathWithinSourceArchive: 'def/'
146+
}));
147+
const resolved = db.resolveSourceFile(Uri.file('abc').toString());
148+
149+
// must recreate an encoded archive uri instead of typing out the
150+
// text since the uris will be different on windows and ubuntu.
151+
expect(resolved.toString()).to.eq(encodeSourceArchiveUri({
152+
sourceArchiveZipPath: 'sourceArchive-uri',
153+
pathWithinSourceArchive: 'def/abc'
154+
}).toString());
155+
});
156+
157+
it('should encode an empty source archive url', () => {
158+
const db = createMockDB(encodeSourceArchiveUri({
159+
sourceArchiveZipPath: 'sourceArchive-uri',
160+
pathWithinSourceArchive: 'def'
161+
}));
162+
const resolved = db.resolveSourceFile('file:');
163+
expect(resolved.toString()).to.eq('codeql-zip-archive://1-18/sourceArchive-uri/def');
164+
});
165+
});
166+
167+
it('should handle an empty file', () => {
168+
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
169+
const resolved = db.resolveSourceFile('');
170+
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/');
171+
});
172+
function createMockDB(
173+
sourceArchiveUri = Uri.parse('file:/sourceArchive-uri'),
174+
databaseUri = Uri.parse('file:/database-uri')
175+
) {
176+
return new DatabaseItemImpl(
177+
databaseUri,
178+
{
179+
sourceArchiveUri
180+
} as DatabaseContents,
181+
{
182+
dateAdded: 123,
183+
ignoreSourceArchive: false
184+
},
185+
() => { /**/ }
186+
);
187+
}
188+
});
81189
});

0 commit comments

Comments
 (0)