diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 3db03dab9..188a3cbed 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -969,8 +969,8 @@ "react": "^16.8.6", "react-dom": "^16.8.6", "semver": "~7.3.2", - "stream-chain": "^2.2.4", - "stream-json": "^1.7.3", + "stream-chain": "~2.2.4", + "stream-json": "~1.7.3", "tmp": "^0.1.0", "tmp-promise": "~3.0.2", "tree-kill": "~1.2.2", @@ -1005,8 +1005,8 @@ "@types/semver": "~7.2.0", "@types/sinon": "~7.5.2", "@types/sinon-chai": "~3.2.3", - "@types/stream-chain": "^2.0.1", - "@types/stream-json": "^1.7.1", + "@types/stream-chain": "~2.0.1", + "@types/stream-json": "~1.7.1", "@types/through2": "^2.0.36", "@types/tmp": "^0.1.0", "@types/unzipper": "~0.10.1", diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index d5d445ad9..670519d80 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -39,6 +39,8 @@ const CSV_FORMAT = 'csv'; */ const LOGGING_FLAGS = ['-v', '--log-to-stderr']; +const DUMMY_TOOL : sarif.Tool = {driver: {name: ''}}; + /** * The expected output of `codeql resolve library-path`. */ @@ -581,6 +583,47 @@ export class CodeQLCliServer implements Disposable { } } + static async parseSarif(interpretedResultsPath: string) : Promise { + try { + // Parse the SARIF file into token streams, filtering out only the results array. + const p = parser(); + const pipeline = chain([ + fs.createReadStream(interpretedResultsPath), + p, + pick({filter: 'runs.0.results'}), + verifier() + ]); + + // Creates JavaScript objects from the token stream + const asm = Assembler.connectTo(pipeline); + + // Returns a constructed Log object with the results or an empty array if no results were found. + // If the parser fails for any reason, it will reject the promise. + return await new Promise((resolve, reject) => { + pipeline.on('error', (error) => { + reject(error); + }); + + asm.on('done', (asm) => { + + const log : sarif.Log = { + version: '2.1.0', + runs: [ + { + tool: DUMMY_TOOL, + results: asm.current ?? [] + } + ] + }; + + resolve(log); + }); + }); + } catch (err) { + throw new Error(`Parsing output of interpretation failed: ${err.stderr || err}`); + } + } + /** * Gets the metadata for a query. * @param queryPath The path to the query. @@ -687,59 +730,7 @@ export class CodeQLCliServer implements Disposable { async interpretBqrs(metadata: QueryMetadata, resultsPath: string, interpretedResultsPath: string, sourceInfo?: SourceInfo): Promise { await this.runInterpretCommand(SARIF_FORMAT, metadata, resultsPath, interpretedResultsPath, sourceInfo); - try { - // Parse the SARIF file into token streams, filtering out only the results array. - const p = parser(); - const pipeline = chain([ - fs.createReadStream(interpretedResultsPath), - p, - pick({filter: 'runs.0.results'}), - verifier() - ]); - - // Creates JavaScript objects from the token stream - const asm = Assembler.connectTo(pipeline); - - // Returns a constructed Log object with the results or an empty array if no results were found. - // If the parser fails for any reason, it will reject the promise. - return await new Promise((resolve, reject) => { - pipeline.on('error', (error) => { - reject(error); - }); - - asm.on('done', (asm) => { - const dummyTool : sarif.Tool = {driver: {name: ''}}; - if (asm.current) { - const log : sarif.Log = { - version: '2.1.0', - runs: [ - { - tool: dummyTool, - results: asm.current - } - ] - }; - - resolve(log); - } else { - const log : sarif.Log = { - version: '2.1.0', - runs: [ - { - tool: dummyTool, - results: [] - } - ] - }; - - resolve(log); - } - - }); - }); - } catch (err) { - throw new Error(`Parsing output of interpretation failed: ${err.stderr || err}`); - } + return await CodeQLCliServer.parseSarif(interpretedResultsPath); } async generateResultsCsv(metadata: QueryMetadata, resultsPath: string, csvPath: string, sourceInfo?: SourceInfo): Promise { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/cli.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/cli.test.ts new file mode 100644 index 000000000..b0a8445c7 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/cli.test.ts @@ -0,0 +1,24 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import { CodeQLCliServer } from '../../cli'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe.only('cliServerTests', () => { + it('should parse a valid SARIF file', async () => { + const result = await CodeQLCliServer.parseSarif(__dirname + '/data/sarif/validSarif.sarif'); + expect(result.runs.length).to.eq(1); + }); + + it('should return an empty array if there are no results', async () => { + const result = await CodeQLCliServer.parseSarif(__dirname + '/data/sarif/emptyResultsSarif.sarif'); + expect(result.runs[0].results?.length).to.eq(0); + }); + + it('should throw an error if the file fails to parse', async () => { + const result = await CodeQLCliServer.parseSarif(__dirname + '/data/sarif/invalidSarif.sarif'); + await expect(result).to.rejectedWith(/Error: Parsing output of interpretation failed: /); + }); +}); \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif new file mode 100644 index 000000000..3b4b4292d --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif @@ -0,0 +1,35 @@ +{ + "version": "2.1.0", + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.4", + "runs": [ + { + "tool": { + "driver": { + "name": "ESLint", + "informationUri": "https://eslint.org", + "rules": [ + { + "id": "no-unused-vars", + "shortDescription": { + "text": "disallow unused variables" + }, + "helpUri": "https://eslint.org/docs/rules/no-unused-vars", + "properties": { + "category": "Variables" + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js" + } + } + ], + "results": [], + ] + } + ] +} \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif new file mode 100644 index 000000000..aece31dc9 --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif @@ -0,0 +1,33 @@ +{ + "version": "2.1.0", + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.4", + "runs": [ + { + "tool": { + "driver": { + "name": "ESLint", + "informationUri": "https://eslint.org", + "rules": [ + { + "id": "no-unused-vars", + "shortDescription": { + "text": "disallow unused variables" + }, + "helpUri": "https://eslint.org/docs/rules/no-unused-vars", + "properties": { + "category": "Variables" + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js" + } + } + ], + } + ] +} \ No newline at end of file diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/validSarif.sarif b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/validSarif.sarif new file mode 100644 index 000000000..2d2fa5cac --- /dev/null +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/validSarif.sarif @@ -0,0 +1,57 @@ +{ + "version": "2.1.0", + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.4", + "runs": [ + { + "tool": { + "driver": { + "name": "ESLint", + "informationUri": "https://eslint.org", + "rules": [ + { + "id": "no-unused-vars", + "shortDescription": { + "text": "disallow unused variables" + }, + "helpUri": "https://eslint.org/docs/rules/no-unused-vars", + "properties": { + "category": "Variables" + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js" + } + } + ], + "results": [ + { + "level": "error", + "message": { + "text": "'x' is assigned a value but never used." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js", + "index": 0 + }, + "region": { + "startLine": 1, + "startColumn": 5 + } + } + } + ], + "ruleId": "no-unused-vars", + "ruleIndex": 0 + } + ] + } + ] +} \ No newline at end of file