Add unit tests for query history and remote queries
Adds some tests for reading in the history and manipulating. There are some more tests to come later. Maybe in another PR, maybe in this one. Note that this PR uses a new node 16 API String.prototype.replaceAll. I think this is ok since vscode ships with node 16. If this causes problems, I can separate to a different PR and we can discuss there.
This commit is contained in:
42
extensions/ql-vscode/package-lock.json
generated
42
extensions/ql-vscode/package-lock.json
generated
@@ -55,7 +55,7 @@
|
||||
"@types/jszip": "~3.1.6",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/nanoid": "^3.0.0",
|
||||
"@types/node": "^12.14.1",
|
||||
"@types/node": "^16.11.25",
|
||||
"@types/node-fetch": "~2.5.2",
|
||||
"@types/proxyquire": "~1.3.28",
|
||||
"@types/react": "^17.0.2",
|
||||
@@ -1002,11 +1002,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/glob/node_modules/@types/node": {
|
||||
"version": "14.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
|
||||
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw=="
|
||||
},
|
||||
"node_modules/@types/google-protobuf": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.2.tgz",
|
||||
@@ -1220,10 +1215,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "12.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz",
|
||||
"integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==",
|
||||
"dev": true
|
||||
"version": "16.11.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.25.tgz",
|
||||
"integrity": "sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ=="
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.5.7",
|
||||
@@ -1291,12 +1285,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver/node_modules/@types/node": {
|
||||
"version": "14.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
|
||||
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/sinon": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz",
|
||||
@@ -13912,13 +13900,6 @@
|
||||
"requires": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
|
||||
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/glob-stream": {
|
||||
@@ -14109,10 +14090,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "12.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz",
|
||||
"integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==",
|
||||
"dev": true
|
||||
"version": "16.11.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.25.tgz",
|
||||
"integrity": "sha512-NrTwfD7L1RTc2qrHQD4RTTy4p0CO2LatKBEKEds3CaVuhoM/+DJzmWZl5f+ikR8cm8F5mfJxK+9rQq07gRiSjQ=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.5.7",
|
||||
@@ -14178,14 +14158,6 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "14.0.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz",
|
||||
"integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/sinon": {
|
||||
|
||||
@@ -1064,7 +1064,7 @@
|
||||
"@types/jszip": "~3.1.6",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/nanoid": "^3.0.0",
|
||||
"@types/node": "^12.14.1",
|
||||
"@types/node": "^16.11.25",
|
||||
"@types/node-fetch": "~2.5.2",
|
||||
"@types/proxyquire": "~1.3.28",
|
||||
"@types/react": "^17.0.2",
|
||||
|
||||
11
extensions/ql-vscode/src/blob.d.ts
vendored
11
extensions/ql-vscode/src/blob.d.ts
vendored
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* The npm library jszip is designed to work in both the browser and
|
||||
* node. Consequently its typings @types/jszip refers to both node
|
||||
* types like `Buffer` (which don't exist in the browser), and browser
|
||||
* types like `Blob` (which don't exist in node). Instead of sticking
|
||||
* all of `dom` in `compilerOptions.lib`, it suffices just to put in a
|
||||
* stub definition of the type `Blob` here so that compilation
|
||||
* succeeds.
|
||||
*/
|
||||
|
||||
declare type Blob = string;
|
||||
@@ -403,7 +403,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
try {
|
||||
if (cancellationToken !== undefined) {
|
||||
cancellationRegistration = cancellationToken.onCancellationRequested(_e => {
|
||||
tk(child.pid);
|
||||
tk(child.pid || 0);
|
||||
});
|
||||
}
|
||||
if (logger !== undefined) {
|
||||
|
||||
@@ -83,3 +83,17 @@ export abstract class DisposableObject implements Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple disposable object that does nothing other than contain a list of disposable objects.
|
||||
* This is useful for implementing a `Disposable` that owns other disposable objects.
|
||||
*/
|
||||
export class DisposableBucket extends DisposableObject {
|
||||
/**
|
||||
* Add a disposable object to this bucket.
|
||||
* @param obj The object to add.
|
||||
*/
|
||||
public push<T extends Disposable>(obj: T): T {
|
||||
return super.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,7 +574,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
const current = this.treeDataProvider.getCurrent();
|
||||
if (current !== undefined) {
|
||||
await this.treeView.reveal(current, { select: true });
|
||||
await this._onWillOpenQueryItem.fire(current);
|
||||
this._onWillOpenQueryItem.fire(current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,7 +655,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
|
||||
async handleItemClicked(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
multiSelect: QueryHistoryInfo[] = []
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
|
||||
@@ -676,8 +676,10 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
// show original query file on double click
|
||||
await this.handleOpenQuery(finalSingleItem, [finalSingleItem]);
|
||||
} else {
|
||||
// show results on single click
|
||||
await this._onWillOpenQueryItem.fire(finalSingleItem);
|
||||
// show results on single click only if query is completed successfully.
|
||||
if (finalSingleItem.status === QueryStatus.Completed) {
|
||||
await this._onWillOpenQueryItem.fire(finalSingleItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,7 +720,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
|
||||
async handleShowQueryText(
|
||||
singleItem: QueryHistoryInfo,
|
||||
multiSelect: QueryHistoryInfo[]
|
||||
multiSelect: QueryHistoryInfo[] = []
|
||||
) {
|
||||
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ export class QueryServerClient extends DisposableObject {
|
||||
}
|
||||
|
||||
get serverProcessPid(): number {
|
||||
return this.serverProcess!.child.pid;
|
||||
return this.serverProcess!.child.pid || 0;
|
||||
}
|
||||
|
||||
async sendRequest<P, R, E, RO>(type: RequestType<WithProgressId<P>, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise<R> {
|
||||
|
||||
@@ -106,7 +106,8 @@ export class AnalysesResultsManager {
|
||||
const resultsForQuery = this.internalGetAnalysesResults(queryId);
|
||||
resultsForQuery.push(analysisResults);
|
||||
this.analysesResults.set(queryId, resultsForQuery);
|
||||
void publishResults(resultsForQuery);
|
||||
void publishResults([...resultsForQuery]);
|
||||
const pos = resultsForQuery.length - 1;
|
||||
|
||||
let artifactPath;
|
||||
try {
|
||||
@@ -116,16 +117,23 @@ export class AnalysesResultsManager {
|
||||
throw new Error(`Could not download the analysis results for ${analysis.nwo}: ${e.message}`);
|
||||
}
|
||||
|
||||
let newAnaysisResults: AnalysisResults;
|
||||
if (path.extname(artifactPath) === '.sarif') {
|
||||
const queryResults = await this.readResults(artifactPath);
|
||||
analysisResults.results = queryResults;
|
||||
analysisResults.status = 'Completed';
|
||||
newAnaysisResults = {
|
||||
...analysisResults,
|
||||
results: queryResults,
|
||||
status: 'Completed'
|
||||
};
|
||||
} else {
|
||||
void this.logger.log('Cannot download results. Only alert and path queries are fully supported.');
|
||||
analysisResults.status = 'Failed';
|
||||
newAnaysisResults = {
|
||||
...analysisResults,
|
||||
status: 'Failed'
|
||||
};
|
||||
}
|
||||
|
||||
void publishResults(resultsForQuery);
|
||||
resultsForQuery[pos] = newAnaysisResults;
|
||||
void publishResults([...resultsForQuery]);
|
||||
}
|
||||
|
||||
private async readResults(filePath: string): Promise<QueryResult[]> {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @name MRVA Integration test 1
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id javascript/integration-test-1
|
||||
*/
|
||||
import javascript
|
||||
|
||||
from MemberDeclaration md
|
||||
where md.getName() = "dispose"
|
||||
select md, "Dispose method"
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @name MRVA Integration test 2
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id javascript/integration-test-2
|
||||
*/
|
||||
import javascript
|
||||
|
||||
from MemberDeclaration md
|
||||
where md.getName() = "refresh"
|
||||
select md, "Refresh method"
|
||||
@@ -0,0 +1 @@
|
||||
other/hucairz
|
||||
@@ -0,0 +1 @@
|
||||
15
|
||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
"md","col1"
|
||||
"dispose ... ();\n }","Dispose method"
|
||||
"readonl ... > void;","Dispose method"
|
||||
"async d ... }\n }","Dispose method"
|
||||
"dispose(): any;","Dispose method"
|
||||
"public ... }\n }","Dispose method"
|
||||
"dispose: () => void;","Dispose method"
|
||||
"dispose ... ');\n }","Dispose method"
|
||||
"dispose ... ();\n }","Dispose method"
|
||||
"public ... ();\n }","Dispose method"
|
||||
"readonl ... > void;","Dispose method"
|
||||
"dispose(): unknown","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
|
@@ -0,0 +1,19 @@
|
||||
## github/vscode-codeql
|
||||
|
||||
| - | Message |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
|
||||
| [dispose ... ();\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/cli.ts#L211) | Dispose method |
|
||||
| [readonl ... > void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/extension.ts#L166) | Dispose method |
|
||||
| [async d ... }\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/logging.ts#L151) | Dispose method |
|
||||
| [dispose(): any;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L5) | Dispose method |
|
||||
| [public ... }\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L65) | Dispose method |
|
||||
| [dispose: () => void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/query-results.ts#L54) | Dispose method |
|
||||
| [dispose ... ');\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/queryserver-client.ts#L32) | Dispose method |
|
||||
| [dispose ... ();\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/telemetry.ts#L129) | Dispose method |
|
||||
| [public ... ();\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/test-ui.ts#L54) | Dispose method |
|
||||
| [readonl ... > void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/run-queries.ts#L327) | Dispose method |
|
||||
| [dispose(): unknown](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts#L150) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L12) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L13) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L14) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L15) | Dispose method |
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
github/vscode-codeql
|
||||
@@ -0,0 +1 @@
|
||||
15
|
||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
"md","col1"
|
||||
"dispose ... ();\n }","Dispose method"
|
||||
"readonl ... > void;","Dispose method"
|
||||
"async d ... }\n }","Dispose method"
|
||||
"dispose(): any;","Dispose method"
|
||||
"public ... }\n }","Dispose method"
|
||||
"dispose: () => void;","Dispose method"
|
||||
"dispose ... ');\n }","Dispose method"
|
||||
"dispose ... ();\n }","Dispose method"
|
||||
"public ... ();\n }","Dispose method"
|
||||
"readonl ... > void;","Dispose method"
|
||||
"dispose(): unknown","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
"dispose ... inonSpy","Dispose method"
|
||||
|
@@ -0,0 +1,19 @@
|
||||
## github/vscode-codeql
|
||||
|
||||
| - | Message |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
|
||||
| [dispose ... ();\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/cli.ts#L211) | Dispose method |
|
||||
| [readonl ... > void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/extension.ts#L166) | Dispose method |
|
||||
| [async d ... }\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/logging.ts#L151) | Dispose method |
|
||||
| [dispose(): any;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L5) | Dispose method |
|
||||
| [public ... }\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/pure/disposable-object.ts#L65) | Dispose method |
|
||||
| [dispose: () => void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/query-results.ts#L54) | Dispose method |
|
||||
| [dispose ... ');\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/queryserver-client.ts#L32) | Dispose method |
|
||||
| [dispose ... ();\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/telemetry.ts#L129) | Dispose method |
|
||||
| [public ... ();\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/test-ui.ts#L54) | Dispose method |
|
||||
| [readonl ... > void;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/run-queries.ts#L327) | Dispose method |
|
||||
| [dispose(): unknown](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts#L150) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L12) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L13) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L14) | Dispose method |
|
||||
| [dispose ... inonSpy](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts#L15) | Dispose method |
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"executionEndTime": 1645645080281,
|
||||
"analysisSummaries": [
|
||||
{
|
||||
"nwo": "github/vscode-codeql",
|
||||
"resultCount": 15,
|
||||
"fileSizeInBytes": 191025,
|
||||
"downloadLink": {
|
||||
"id": "171543249",
|
||||
"urlPath": "/repos/dsp-testing/qc-run2/actions/artifacts/171543249",
|
||||
"innerFilePath": "results.sarif",
|
||||
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"nwo": "other/hucairz",
|
||||
"resultCount": 15,
|
||||
"fileSizeInBytes": 191025,
|
||||
"downloadLink": {
|
||||
"id": "11111111",
|
||||
"urlPath": "/repos/dsp-testing/qc-run2/actions/artifacts/11111111",
|
||||
"innerFilePath": "results.sarif",
|
||||
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx"
|
||||
}
|
||||
}
|
||||
],
|
||||
"analysisFailures": [],
|
||||
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"queryName": "MRVA Integration test 1",
|
||||
"queryFilePath": "PLACEHOLDER/q0.ql",
|
||||
"queryText": "/**\n * @name MRVA Integration test 1\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-1\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"dispose\"\nselect md, \"Dispose method\"\n",
|
||||
"controllerRepository": {
|
||||
"owner": "dsp-testing",
|
||||
"name": "qc-run2"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"owner": "github",
|
||||
"name": "vscode-codeql"
|
||||
}
|
||||
],
|
||||
"executionStartTime": 1645644967533,
|
||||
"actionsWorkflowRunId": 1889315769
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1645644971870
|
||||
@@ -0,0 +1 @@
|
||||
github/vscode-codeql
|
||||
@@ -0,0 +1 @@
|
||||
5
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
"md","col1"
|
||||
"refresh ... d);\n }","Refresh method"
|
||||
"refresh ... <void>;","Refresh method"
|
||||
"public ... }\n }","Refresh method"
|
||||
"public ... }\n }","Refresh method"
|
||||
"refresh ... d);\n }","Refresh method"
|
||||
|
@@ -0,0 +1,9 @@
|
||||
## github/vscode-codeql
|
||||
|
||||
| - | Message |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
|
||||
| [refresh ... d);\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/astViewer.ts#L58) | Refresh method |
|
||||
| [refresh ... <void>;](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/databases.ts#L234) | Refresh method |
|
||||
| [public ... }\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/databases.ts#L354) | Refresh method |
|
||||
| [public ... }\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/discovery.ts#L21) | Refresh method |
|
||||
| [refresh ... d);\n }](https://github.com/github/vscode-codeql/blob/c943c89fc694a06e95845c0b7b7c4e71983dd8c4/extensions/ql-vscode/src/query-history.ts#L268) | Refresh method |
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"executionEndTime": 1645645150738,
|
||||
"analysisSummaries": [
|
||||
{
|
||||
"nwo": "github/vscode-codeql",
|
||||
"resultCount": 5,
|
||||
"fileSizeInBytes": 81237,
|
||||
"downloadLink": {
|
||||
"id": "171544171",
|
||||
"urlPath": "/repos/dsp-testing/qc-run2/actions/artifacts/171544171",
|
||||
"innerFilePath": "results.sarif",
|
||||
"queryId": "MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN"
|
||||
}
|
||||
}
|
||||
],
|
||||
"analysisFailures": [],
|
||||
"queryId": "MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"queryName": "MRVA Integration test 2",
|
||||
"queryFilePath": "PLACEHOLDER/q1.ql",
|
||||
"queryText": "/**\n * @name MRVA Integration test 2\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-2\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"refresh\"\nselect md, \"Refresh method\"\n",
|
||||
"controllerRepository": {
|
||||
"owner": "dsp-testing",
|
||||
"name": "qc-run2"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"owner": "github",
|
||||
"name": "vscode-codeql"
|
||||
}
|
||||
],
|
||||
"executionStartTime": 1645644973911,
|
||||
"actionsWorkflowRunId": 1889316048
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1645644974055
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"version": 1,
|
||||
"queries": [
|
||||
{
|
||||
"t": "remote",
|
||||
"status": "Completed",
|
||||
"completed": true,
|
||||
"queryId": "MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx",
|
||||
"label": "MRVA Integration test 1",
|
||||
"remoteQuery": {
|
||||
"queryName": "MRVA Integration test 1",
|
||||
"queryFilePath": "PLACEHOLDER/q0.ql",
|
||||
"queryText": "/**\n * @name MRVA Integration test 1\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-1\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"dispose\"\nselect md, \"Dispose method\"\n",
|
||||
"controllerRepository": {
|
||||
"owner": "dsp-testing",
|
||||
"name": "qc-run2"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"owner": "github",
|
||||
"name": "vscode-codeql"
|
||||
}
|
||||
],
|
||||
"executionStartTime": 1645644967533,
|
||||
"actionsWorkflowRunId": 1889315769
|
||||
}
|
||||
},
|
||||
{
|
||||
"t": "remote",
|
||||
"status": "Completed",
|
||||
"completed": true,
|
||||
"queryId": "MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN",
|
||||
"label": "MRVA Integration test 2",
|
||||
"remoteQuery": {
|
||||
"queryName": "MRVA Integration test 2",
|
||||
"queryFilePath": "PLACEHOLDER/q1.ql",
|
||||
"queryText": "/**\n * @name MRVA Integration test 2\n * @kind problem\n * @problem.severity warning\n * @id javascript/integration-test-2\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"refresh\"\nselect md, \"Refresh method\"\n",
|
||||
"controllerRepository": {
|
||||
"owner": "dsp-testing",
|
||||
"name": "qc-run2"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"owner": "github",
|
||||
"name": "vscode-codeql"
|
||||
}
|
||||
],
|
||||
"executionStartTime": 1645644973911,
|
||||
"actionsWorkflowRunId": 1889316048
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { slurpQueryHistory, splatQueryHistory } from '../../query-serialization'
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe.only('query-results', () => {
|
||||
describe('query-results', () => {
|
||||
let disposeSpy: sinon.SinonSpy;
|
||||
let onDidChangeQueryHistoryConfigurationSpy: sinon.SinonSpy;
|
||||
let mockConfig: QueryHistoryConfig;
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
import 'sinon-chai';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
|
||||
import { CancellationToken, ExtensionContext, Uri, window, workspace } from 'vscode';
|
||||
import { QueryHistoryConfig } from '../../config';
|
||||
import { DatabaseManager } from '../../databases';
|
||||
import { tmpDir } from '../../helpers';
|
||||
import { QueryHistoryManager } from '../../query-history';
|
||||
import { QueryServerClient } from '../../queryserver-client';
|
||||
import { DisposableBucket } from '../../pure/disposable-object';
|
||||
import { Credentials } from '../../authentication';
|
||||
import { AnalysesResultsManager } from '../../remote-queries/analyses-results-manager';
|
||||
import { RemoteQueryResult } from '../../remote-queries/shared/remote-query-result';
|
||||
import { walk } from '../test-helpers';
|
||||
import { testDisposeHandler } from '../test-dispose-handler';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
/**
|
||||
* Tests for remote queries and how they interact with the query history manager.
|
||||
*/
|
||||
|
||||
describe('Remote queries and query history manager', function() {
|
||||
|
||||
const EXTENSION_PATH = path.join(__dirname, '../../../');
|
||||
const STORAGE_DIR = Uri.file(path.join(tmpDir.name, 'remote-queries')).fsPath;
|
||||
const asyncNoop = async () => { /** noop */ };
|
||||
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let qhm: QueryHistoryManager;
|
||||
let rawQueryHistory: any;
|
||||
let remoteQueryResult0: RemoteQueryResult;
|
||||
let remoteQueryResult1: RemoteQueryResult;
|
||||
let disposables: DisposableBucket;
|
||||
let showTextDocumentSpy: sinon.SinonSpy;
|
||||
let openTextDocumentSpy: sinon.SinonSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
copyHistoryState();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
deleteHistoryState();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
disposables = new DisposableBucket();
|
||||
|
||||
rawQueryHistory = fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json')).queries;
|
||||
remoteQueryResult0 = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[0].queryId, 'query-result.json'));
|
||||
remoteQueryResult1 = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[1].queryId, 'query-result.json'));
|
||||
|
||||
qhm = new QueryHistoryManager(
|
||||
{} as QueryServerClient,
|
||||
{} as DatabaseManager,
|
||||
STORAGE_DIR,
|
||||
{
|
||||
globalStorageUri: Uri.file(STORAGE_DIR),
|
||||
extensionPath: EXTENSION_PATH
|
||||
} as ExtensionContext,
|
||||
{
|
||||
onDidChangeConfiguration: () => new DisposableBucket(),
|
||||
} as unknown as QueryHistoryConfig,
|
||||
asyncNoop
|
||||
);
|
||||
disposables.push(qhm);
|
||||
|
||||
showTextDocumentSpy = sandbox.spy(window, 'showTextDocument');
|
||||
openTextDocumentSpy = sandbox.spy(workspace, 'openTextDocument');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
disposables.dispose(testDisposeHandler);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should read query history', async () => {
|
||||
const spy = sandbox.spy();
|
||||
disposables.push(qhm.onDidAddQueryItem(spy));
|
||||
await qhm.readQueryHistory();
|
||||
|
||||
// Should have added the query history. Contents are directly from the file
|
||||
expect(spy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
|
||||
expect(spy.getCall(1).args[0]).to.deep.eq(rawQueryHistory[1]);
|
||||
expect(spy.callCount).to.eq(2);
|
||||
|
||||
expect(qhm.treeDataProvider.allHistory[0]).to.deep.eq(rawQueryHistory[0]);
|
||||
expect(qhm.treeDataProvider.allHistory[1]).to.deep.eq(rawQueryHistory[1]);
|
||||
expect(qhm.treeDataProvider.allHistory.length).to.eq(2);
|
||||
});
|
||||
|
||||
it('should remove and then a query from history', async () => {
|
||||
await qhm.readQueryHistory();
|
||||
const addSpy = sandbox.spy();
|
||||
disposables.push(qhm.onDidAddQueryItem(addSpy));
|
||||
const removeSpy = sandbox.spy();
|
||||
disposables.push(qhm.onDidRemoveQueryItem(removeSpy));
|
||||
|
||||
// Remove the first query
|
||||
await qhm.handleRemoveHistoryItem(qhm.treeDataProvider.allHistory[0]);
|
||||
expect(removeSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
|
||||
expect(removeSpy.callCount).to.eq(1);
|
||||
expect(addSpy.callCount).to.eq(0);
|
||||
expect(qhm.treeDataProvider.allHistory).to.deep.eq(rawQueryHistory.slice(1));
|
||||
|
||||
// Add it back
|
||||
qhm.addQuery(rawQueryHistory[0]);
|
||||
expect(removeSpy.callCount).to.eq(1);
|
||||
expect(addSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
|
||||
expect(addSpy.callCount).to.eq(1);
|
||||
expect(qhm.treeDataProvider.allHistory).to.deep.eq([rawQueryHistory[1], rawQueryHistory[0]]);
|
||||
});
|
||||
|
||||
it('should remove two queries from history', async () => {
|
||||
await qhm.readQueryHistory();
|
||||
const addSpy = sandbox.spy();
|
||||
disposables.push(qhm.onDidAddQueryItem(addSpy));
|
||||
const removeSpy = sandbox.spy();
|
||||
disposables.push(qhm.onDidRemoveQueryItem(removeSpy));
|
||||
|
||||
// Remove the both queries
|
||||
// Just for fun, let's do it in reverse order
|
||||
await qhm.handleRemoveHistoryItem(undefined!, [qhm.treeDataProvider.allHistory[1], qhm.treeDataProvider.allHistory[0]]);
|
||||
expect(removeSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[1]);
|
||||
expect(removeSpy.getCall(1).args[0]).to.deep.eq(rawQueryHistory[0]);
|
||||
expect(qhm.treeDataProvider.allHistory).to.deep.eq([]);
|
||||
expect(removeSpy.callCount).to.eq(2);
|
||||
|
||||
// also, both queries should be removed from on disk storage
|
||||
expect(fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json'))).to.deep.eq({
|
||||
version: 1,
|
||||
queries: []
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle a click', async () => {
|
||||
await qhm.readQueryHistory();
|
||||
const openSpy = sandbox.spy();
|
||||
disposables.push(qhm.onWillOpenQueryItem(openSpy));
|
||||
|
||||
await qhm.handleItemClicked(qhm.treeDataProvider.allHistory[0], []);
|
||||
expect(openSpy.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0]);
|
||||
});
|
||||
|
||||
it('should get the query text', async () => {
|
||||
await qhm.readQueryHistory();
|
||||
await qhm.handleShowQueryText(qhm.treeDataProvider.allHistory[0], []);
|
||||
|
||||
expect(showTextDocumentSpy).to.have.been.calledOnce;
|
||||
expect(openTextDocumentSpy).to.have.been.calledOnce;
|
||||
|
||||
const uri: Uri = openTextDocumentSpy.getCall(0).args[0];
|
||||
expect(uri.scheme).to.eq('codeql');
|
||||
const params = new URLSearchParams(uri.query);
|
||||
expect(params.get('isQuickEval')).to.eq('false');
|
||||
expect(params.get('queryText')).to.eq(rawQueryHistory[0].remoteQuery.queryText);
|
||||
});
|
||||
|
||||
describe('AnalysisResultsManager', () => {
|
||||
|
||||
let mockCredentials: any;
|
||||
let mockOctokit: any;
|
||||
let mockLogger: any;
|
||||
let arm: AnalysesResultsManager;
|
||||
|
||||
beforeEach(() => {
|
||||
mockOctokit = {
|
||||
request: sandbox.stub()
|
||||
};
|
||||
mockCredentials = {
|
||||
getOctokit: () => mockOctokit
|
||||
};
|
||||
mockLogger = {
|
||||
log: sandbox.spy()
|
||||
};
|
||||
sandbox.stub(Credentials, 'initialize').resolves(mockCredentials);
|
||||
|
||||
arm = new AnalysesResultsManager(
|
||||
{} as ExtensionContext,
|
||||
path.join(STORAGE_DIR, 'queries'),
|
||||
mockLogger
|
||||
);
|
||||
});
|
||||
|
||||
it('should avoid downloading an analysis result', async () => {
|
||||
// because the analysis result is already in on disk, it should not be downloaded
|
||||
const publisher = sandbox.spy();
|
||||
const analysisSummary = remoteQueryResult0.analysisSummaries[0];
|
||||
await arm.downloadAnalysisResults(analysisSummary, publisher);
|
||||
|
||||
// Should not have made the request since the analysis result is already on disk
|
||||
expect(mockOctokit.request).to.not.have.been.called;
|
||||
|
||||
// result should have been published twice
|
||||
// first time, it is in progress
|
||||
expect(publisher.getCall(0).args[0][0]).to.include({
|
||||
nwo: 'github/vscode-codeql',
|
||||
status: 'InProgress',
|
||||
// results: ... avoid checking the results object since it is complex
|
||||
});
|
||||
|
||||
// second time, it has the path to the sarif file.
|
||||
expect(publisher.getCall(1).args[0][0]).to.include({
|
||||
nwo: 'github/vscode-codeql',
|
||||
status: 'Completed',
|
||||
// results: ... avoid checking the results object since it is complex
|
||||
});
|
||||
expect(publisher).to.have.been.calledTwice;
|
||||
|
||||
// result should be stored in the manager
|
||||
expect(arm.getAnalysesResults(rawQueryHistory[0].queryId)[0]).to.include({
|
||||
nwo: 'github/vscode-codeql',
|
||||
status: 'Completed',
|
||||
// results: ... avoid checking the results object since it is complex
|
||||
});
|
||||
publisher.resetHistory();
|
||||
|
||||
// now, let's try to download it again. This time, since it's already in memory,
|
||||
// it should not even be re-published
|
||||
await arm.downloadAnalysisResults(analysisSummary, publisher);
|
||||
expect(publisher).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should download two artifacts at once', async () => {
|
||||
const publisher = sandbox.spy();
|
||||
const analysisSummaries = [...remoteQueryResult0.analysisSummaries];
|
||||
await arm.downloadAnalysesResults(analysisSummaries, undefined, publisher);
|
||||
|
||||
const trimmed = publisher.getCalls().map(call => call.args[0]).map(args => {
|
||||
args.forEach((analysisResult: any) => delete analysisResult.results);
|
||||
return args;
|
||||
});
|
||||
|
||||
// As before, but now both summaries should have been published
|
||||
expect(trimmed[0]).to.deep.eq([{
|
||||
nwo: 'github/vscode-codeql',
|
||||
status: 'InProgress',
|
||||
}]);
|
||||
|
||||
expect(trimmed[1]).to.deep.eq([{
|
||||
nwo: 'github/vscode-codeql',
|
||||
status: 'InProgress',
|
||||
}, {
|
||||
nwo: 'other/hucairz',
|
||||
status: 'InProgress',
|
||||
}]);
|
||||
|
||||
// there is a third call. It is non-deterministic if
|
||||
// github/vscode-codeql is completed first or other/hucairz is.
|
||||
// There is not much point in trying to test it if the other calls are correct.
|
||||
|
||||
expect(trimmed[3]).to.deep.eq([{
|
||||
nwo: 'github/vscode-codeql',
|
||||
status: 'Completed',
|
||||
}, {
|
||||
nwo: 'other/hucairz',
|
||||
status: 'Completed',
|
||||
}]);
|
||||
|
||||
expect(publisher).to.have.callCount(4);
|
||||
});
|
||||
|
||||
it('should avoid publishing when the request is cancelled', async () => {
|
||||
const publisher = sandbox.spy();
|
||||
const analysisSummaries = [...remoteQueryResult0.analysisSummaries];
|
||||
|
||||
try {
|
||||
await arm.downloadAnalysesResults(analysisSummaries, {
|
||||
isCancellationRequested: true
|
||||
} as CancellationToken, publisher);
|
||||
expect.fail('Should have thrown');
|
||||
} catch (e) {
|
||||
expect(e.message).to.contain('cancelled');
|
||||
}
|
||||
|
||||
expect(publisher).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should get the analysis results', async () => {
|
||||
const publisher = sandbox.spy();
|
||||
const analysisSummaries0 = [...remoteQueryResult0.analysisSummaries];
|
||||
const analysisSummaries1 = [...remoteQueryResult1.analysisSummaries];
|
||||
|
||||
await arm.downloadAnalysesResults(analysisSummaries0, undefined, publisher);
|
||||
await arm.downloadAnalysesResults(analysisSummaries1, undefined, publisher);
|
||||
|
||||
const result0 = arm.getAnalysesResults(rawQueryHistory[0].queryId);
|
||||
const result1 = arm.getAnalysesResults(rawQueryHistory[1].queryId);
|
||||
|
||||
// Shoule be equal, but not equivalent
|
||||
expect(result0).to.deep.eq((arm as any).analysesResults.get(rawQueryHistory[0].queryId));
|
||||
expect(result0).not.to.eq((arm as any).analysesResults.get(rawQueryHistory[0].queryId));
|
||||
|
||||
expect(result1).to.deep.eq((arm as any).analysesResults.get(rawQueryHistory[1].queryId));
|
||||
expect(result1).not.to.eq((arm as any).analysesResults.get(rawQueryHistory[1].queryId));
|
||||
});
|
||||
|
||||
// This test is failing on windows in CI.
|
||||
it.skip('should read sarif', async () => {
|
||||
const publisher = sandbox.spy();
|
||||
const analysisSummaries0 = [remoteQueryResult0.analysisSummaries[0]];
|
||||
await arm.downloadAnalysesResults(analysisSummaries0, undefined, publisher);
|
||||
|
||||
const sarif = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[0].queryId, '171543249', 'results.sarif'));
|
||||
const queryResults = sarif.runs
|
||||
.flatMap((run: any) => run.results)
|
||||
.map((result: any) => ({ message: result.message.text }));
|
||||
|
||||
expect(publisher.getCall(1).args[0][0].results).to.deep.eq(queryResults);
|
||||
});
|
||||
});
|
||||
|
||||
// Since this test changes the state of the query history manager, we need to copy the original
|
||||
// to a temporary folder where we can manipulate it for tests
|
||||
function copyHistoryState() {
|
||||
fs.ensureDirSync(STORAGE_DIR);
|
||||
fs.copySync(path.join(__dirname, 'data/remote-queries/'), path.join(tmpDir.name, 'remote-queries'));
|
||||
|
||||
// also, replace the files with "PLACEHOLDER" so that they have the correct directory
|
||||
for (const p of walk(STORAGE_DIR)) {
|
||||
replacePlaceholder(path.join(p));
|
||||
}
|
||||
}
|
||||
|
||||
function deleteHistoryState() {
|
||||
fs.removeSync(STORAGE_DIR);
|
||||
}
|
||||
|
||||
function replacePlaceholder(filePath: string) {
|
||||
if (filePath.endsWith('.json')) {
|
||||
const newContents = fs.readFileSync(filePath, 'utf8').replaceAll('PLACEHOLDER', STORAGE_DIR.replaceAll('\\', '/'));
|
||||
fs.writeFileSync(filePath, newContents, 'utf8');
|
||||
}
|
||||
}
|
||||
});
|
||||
21
extensions/ql-vscode/src/vscode-tests/test-helpers.ts
Normal file
21
extensions/ql-vscode/src/vscode-tests/test-helpers.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
/**
|
||||
* Recursively walk a directory and return the full path to all files found.
|
||||
* Note that this function uses synchronous fs calls, so it should only be used in tests.
|
||||
*
|
||||
* @param dir the directory to walk
|
||||
*/
|
||||
export function* walk(dir: string): IterableIterator<string> {
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
yield* walk(filePath);
|
||||
} else {
|
||||
yield filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"outDir": "out",
|
||||
"lib": ["ES2020"],
|
||||
"lib": ["ES2021"],
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
|
||||
Reference in New Issue
Block a user