diff --git a/src/ESLint.Formatter/package-lock.json b/src/ESLint.Formatter/package-lock.json index d9fdd771a..34b1071e5 100644 --- a/src/ESLint.Formatter/package-lock.json +++ b/src/ESLint.Formatter/package-lock.json @@ -139,6 +139,11 @@ "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-2.1.0.tgz", "integrity": "sha512-Fuk25QlebgHnvCQvAm258+y2D8yVs1VFIFwiqXbvxG4n9vo5YZsBPZuMxnc7Gb9ElAeN8QfRDvgkkIhW4DPoPA==" }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", diff --git a/src/ESLint.Formatter/package.json b/src/ESLint.Formatter/package.json index 35e07f7fe..397f4bf2d 100644 --- a/src/ESLint.Formatter/package.json +++ b/src/ESLint.Formatter/package.json @@ -31,6 +31,7 @@ "dependencies": { "chai": "^4.2.0", "jschardet": "latest", + "lodash": "^4.17.11", "mocha": "^5.2.0", "utf8": "^3.0.0" } diff --git a/src/ESLint.Formatter/sarif-with-rules.js b/src/ESLint.Formatter/sarif-with-rules.js deleted file mode 100644 index 7c5430949..000000000 --- a/src/ESLint.Formatter/sarif-with-rules.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @fileoverview SARIF v2.0 formatter - * @author Microsoft - */ - -"use strict"; - -const lodash = require("lodash"); -const fs = require("fs"); -const utf8 = require("utf8"); -const jschardet = require("jschardet"); - -//------------------------------------------------------------------------------ -// Helper Functions -//------------------------------------------------------------------------------ - -/** - * Returns the severity of warning or error - * @param {Object} message message object to examine - * @returns {string} severity level - * @private - */ -function getResultLevel(message) { - if (message.fatal || message.severity === 2) { - return "error"; - } - return "warning"; -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function (results, data) { - const rulesMetdata = lodash.get(data, "rulesMetdata", null); - - const sarifLog = { - version: "2.0.0", - $schema: "http://json.schemastore.org/sarif-2.0.0", - runs: [ - { - files: [], - tool: { - name: "ESLint" - } - } - ] - }; - - const sarifFiles = {}; - const sarifRules = {}; - const sarifResults = []; - const embedFileContents = process.env.SARIF_ESLINT_EMBED === "true"; - - results.forEach(result => { - - if (typeof sarifFiles[result.filePath] === "undefined") { - - let contentsUtf8; - - // Create a new entry in the files dictionary. - sarifFiles[result.filePath] = { - fileLocation: { - uri: result.filePath - } - }; - - if (embedFileContents) { - try { - - // Try to get the file contents and encoding. - const contents = fs.readFileSync(result.filePath); - const encoding = jschardet.detect(contents); - - // Encoding will be null if it could not be determined. - if (encoding) { - - // Convert the content bytes to a UTF-8 string. - contentsUtf8 = utf8.encode(contents.toString(encoding.encoding)); - - sarifFiles[result.filePath].contents = { - text: contentsUtf8 - }; - sarifFiles[result.filePath].encoding = encoding.encoding; - } - } - catch (err) { - console.log(err); - } - } - } - - const messages = result.messages; - - messages.forEach(message => { - - const sarifResult = { - level: getResultLevel(message), - message: { - text: message.message - }, - locations: [ - { - physicalLocation: { - fileLocation: { - uri: result.filePath - } - } - } - ] - }; - - if (message.ruleId) { - sarifResult.ruleId = message.ruleId; - - if (rulesMetdata && typeof sarifRules[message.ruleId] === "undefined") { - const meta = rulesMetdata[message.ruleId]; - - // An unknown ruleId will return null. This check prevents unit test failure. - if (meta) { - - // Create a new entry in the rules dictionary. - sarifRules[message.ruleId] = { - id: message.ruleId, - shortDescription: { - text: meta.docs.description - }, - helpUri: meta.docs.url, - tags: { - category: meta.docs.category - } - }; - } - } - } - - if (message.line > 0 && message.column > 0) { - sarifResult.locations[0].physicalLocation.region = { - startLine: message.line, - startColumn: message.column - }; - } - - if (message.source) { - - // Create an empty region if we don't already have one from the line / column block above. - sarifResult.locations[0].physicalLocation.region = sarifResult.locations[0].physicalLocation.region || {}; - sarifResult.locations[0].physicalLocation.region.snippet = { - text: message.source - }; - } - - sarifResults.push(sarifResult); - - }); - - }); - - Object.keys(sarifFiles).forEach(function (path) { - sarifLog.runs[0].files.push(sarifFiles[path]); - }); - - if (sarifResults.length > 0) { - sarifLog.runs[0].results = sarifResults; - } - - if (Object.keys(sarifRules).length > 0) { - sarifLog.runs[0].resources = { - rules: sarifRules - }; - } - - return JSON.stringify(sarifLog, - null, // replacer function - 2 // # of spaces for indents - ); -}; diff --git a/src/ESLint.Formatter/sarif.js b/src/ESLint.Formatter/sarif.js index 3be21aa00..56a3a5d59 100644 --- a/src/ESLint.Formatter/sarif.js +++ b/src/ESLint.Formatter/sarif.js @@ -5,6 +5,7 @@ "use strict"; +const lodash = require("lodash"); const fs = require("fs"); const utf8 = require("utf8"); const jschardet = require("jschardet"); @@ -30,21 +31,28 @@ function getResultLevel(message) { // Public Interface //------------------------------------------------------------------------------ -module.exports = function(results) { +module.exports = function (results, data) { + + const rulesMetdata = lodash.get(data, "rulesMetdata", null); + const sarifLog = { version: "2.0.0", $schema: "http://json.schemastore.org/sarif-2.0.0", runs: [ { - files: [], + artifacts: [], tool: { - name: "ESLint" + driver: { + name: "ESLint", + downloadUri: "https://eslint.org" + } } } ] }; const sarifFiles = {}; + const sarifRules = {}; const sarifResults = []; const embedFileContents = process.env.SARIF_ESLINT_EMBED === "true"; @@ -56,7 +64,7 @@ module.exports = function(results) { // Create a new entry in the files dictionary. sarifFiles[result.filePath] = { - fileLocation: { + location: { uri: result.filePath } }; @@ -98,7 +106,7 @@ module.exports = function(results) { locations: [ { physicalLocation: { - fileLocation: { + artifactLocation: { uri: result.filePath } } @@ -108,6 +116,26 @@ module.exports = function(results) { if (message.ruleId) { sarifResult.ruleId = message.ruleId; + + if (rulesMetdata && typeof sarifRules[message.ruleId] === "undefined") { + const meta = rulesMetdata[message.ruleId]; + + // An unknown ruleId will return null. This check prevents unit test failure. + if (meta) { + + // Create a new entry in the rules dictionary. + sarifRules[message.ruleId] = { + id: message.ruleId, + shortDescription: { + text: meta.docs.description + }, + helpUri: meta.docs.url, + tags: { + category: meta.docs.category + } + }; + } + } } if (message.line > 0 && message.column > 0) { @@ -127,19 +155,30 @@ module.exports = function(results) { } sarifResults.push(sarifResult); - }); - }); Object.keys(sarifFiles).forEach(function (path) { - sarifLog.runs[0].files.push(sarifFiles[path]); + sarifLog.runs[0].artifacts.push(sarifFiles[path]); }); if (sarifResults.length > 0) { sarifLog.runs[0].results = sarifResults; } + if (Object.keys(sarifRules).length > 0) { + sarifLog.runs[0].tool.driver = { + rules: [] + }; + + var ruleIndex = 0; + Object.keys(sarifRules).forEach(function (ruleId) { + var rule = sarifRules[ruleId]; + rule.ruleIndex = ruleIndex++; + sarifLog.runs[0].tool.driver.rules.push(rule); + }); + } + return JSON.stringify(sarifLog, null, // replacer function 2 // # of spaces for indents diff --git a/src/ESLint.Formatter/test/sarif-with-rules.js b/src/ESLint.Formatter/test/sarif-with-rules.js deleted file mode 100644 index 7eff5a05d..000000000 --- a/src/ESLint.Formatter/test/sarif-with-rules.js +++ /dev/null @@ -1,317 +0,0 @@ -/** - * @fileoverview Tests for SARIF format. - * @author Microsoft - */ - -"use strict"; - - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../sarif-with-rules"); - - -//------------------------------------------------------------------------------ -// Global Test Content -//------------------------------------------------------------------------------ - -const rules = new Map(); - -rules.set("no-unused-vars", { - meta: { - type: "suggestion", - docs: { - description: "disallow unused variables", - category: "Variables", - recommended: true, - url: "https://eslint.org/docs/rules/no-unused-vars" - }, - fixable: "code" - } -}); -rules.set("no-extra-semi", { - meta: { - type: "suggestion", - - docs: { - description: "disallow unnecessary semicolons", - category: "Possible Errors", - recommended: true, - url: "https://eslint.org/docs/rules/no-extra-semi" - }, - - fixable: "code", - schema: [], - - messages: { - unexpected: "Unnecessary semicolon." - } - } -}); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:sarif", () => { - describe("when passed no messages", () => { - const sourceFilePath = "service.js"; - const code = [{ - filePath: sourceFilePath, - messages: [] - }]; - - it("should return a log with one file and no results", () => { - const result = JSON.parse(formatter(code, null)); - - assert.hasAllKeys(result.runs[0].files, sourceFilePath); - assert.strictEqual(result.runs[0].files[sourceFilePath].fileLocation.uri, sourceFilePath); - assert.isUndefined(result.runs[0].results); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed one message", () => { - const sourceFilePath = "service.js"; - const code = [{ - filePath: sourceFilePath, - messages: [{ - message: "Unexpected value.", - severity: 2 - }] - }]; - - it("should return a log with one file and one result", () => { - const result = JSON.parse(formatter(code)); - - assert.hasAllKeys(result.runs[0].files, sourceFilePath); - assert.strictEqual(result.runs[0].files[sourceFilePath].fileLocation.uri, sourceFilePath); - assert.isDefined(result.runs[0].results); - assert.lengthOf(result.runs[0].results, 1); - assert.strictEqual(result.runs[0].results[0].level, "error"); - assert.isDefined(result.runs[0].results[0].message); - assert.strictEqual(result.runs[0].results[0].message.text, code[0].messages[0].message); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.fileLocation.uri, sourceFilePath); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed one message with a rule id", () => { - const ruleid = "no-unused-vars"; - const code = [{ - filePath: "service.js", - messages: [{ - message: "Unexpected value.", - ruleId: ruleid, - source: "getValue()" - }] - }]; - - it("should return a log with one rule", () => { - const result = JSON.parse(formatter(code, rules)); - const rule = rules.get(ruleid); - - assert.hasAllKeys(result.runs[0].resources.rules, ruleid); - assert.strictEqual(result.runs[0].resources.rules[ruleid].id, ruleid); - assert.strictEqual(result.runs[0].resources.rules[ruleid].shortDescription.text, rule.meta.docs.description); - assert.strictEqual(result.runs[0].resources.rules[ruleid].helpUri, rule.meta.docs.url); - assert.strictEqual(result.runs[0].resources.rules[ruleid].tags.category, rule.meta.docs.category); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed one message with line but no column nor source string", () => { - const code = [{ - filePath: "service.js", - messages: [{ - message: "Unexpected value.", - line: 10 - }] - }]; - - it("should return a log with one result whose location does not contain a region", () => { - const result = JSON.parse(formatter(code)); - - assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed one message with line and column but no source string", () => { - const code = [{ - filePath: "service.js", - messages: [{ - message: "Unexpected value.", - line: 10, - column: 5 - }] - }]; - - it("should return a log with one result whose location contains a region with line and column #s", () => { - const result = JSON.parse(formatter(code)); - - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.region.startLine, code[0].messages[0].line); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.region.startColumn, code[0].messages[0].column); - assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region.snippet); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed one message with line, column, and source string", () => { - const code = [{ - filePath: "service.js", - messages: [{ - message: "Unexpected value.", - line: 10, - column: 5, - source: "getValue()" - }] - }]; - - it("should return a log with one result whose location contains a region with line and column #s", () => { - const result = JSON.parse(formatter(code)); - - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.region.startLine, code[0].messages[0].line); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.region.startColumn, code[0].messages[0].column); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.region.snippet.text, code[0].messages[0].source); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed one message with a source string but without line and column #s", () => { - const code = [{ - filePath: "service.js", - messages: [{ - message: "Unexpected value.", - severity: 2, - ruleId: "the-rule", - source: "getValue()" - }] - }]; - - it("should return a log with one result whose location contains a region with line and column #s", () => { - const result = JSON.parse(formatter(code, rules)); - - assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region.startLine); - assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region.startColumn); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.region.snippet.text, code[0].messages[0].source); - }); - }); -}); - -describe("formatter:sarif", () => { - describe("when passed two results with two messages each", () => { - const sourceFilePath1 = "service.js"; - const sourceFilePath2 = "utils.js"; - const ruleid1 = "no-unused-vars"; - const ruleid2 = "no-extra-semi"; - const ruleid3 = "custom-rule"; - - rules.set(ruleid3, { - meta: { - type: "suggestion", - docs: { - description: "custom description", - category: "Possible Errors" - } - } - }); - const code = [{ - filePath: sourceFilePath1, - messages: [{ - message: "Unexpected value.", - severity: 2, - ruleId: "the-rule" - }, - { - ruleId: ruleid1, - message: "Some warning.", - severity: 1, - line: 10, - column: 5, - source: "doSomething(thingId)" - }] - }, - { - filePath: sourceFilePath2, - messages: [{ - message: "Unexpected something.", - severity: 2, - ruleId: ruleid2, - line: 18 - }, - { - message: "Custom error.", - ruleId: ruleid3, - line: 42 - }] - }]; - - it("should return a log with two files, three rules, and four results", () => { - const result = JSON.parse(formatter(code, rules)); - const rule1 = rules.get(ruleid1); - const rule2 = rules.get(ruleid2); - const rule3 = rules.get(ruleid3); - - assert.lengthOf(result.runs[0].results, 4); - - assert.hasAllKeys(result.runs[0].resources.rules, [ruleid1, ruleid2, ruleid3]); - assert.hasAllKeys(result.runs[0].files, [sourceFilePath1, sourceFilePath2]); - - assert.strictEqual(result.runs[0].files[sourceFilePath1].fileLocation.uri, sourceFilePath1); - assert.strictEqual(result.runs[0].files[sourceFilePath2].fileLocation.uri, sourceFilePath2); - - assert.strictEqual(result.runs[0].resources.rules[ruleid1].id, ruleid1); - assert.strictEqual(result.runs[0].resources.rules[ruleid1].shortDescription.text, rule1.meta.docs.description); - assert.strictEqual(result.runs[0].resources.rules[ruleid1].helpUri, rule1.meta.docs.url); - assert.strictEqual(result.runs[0].resources.rules[ruleid1].tags.category, rule1.meta.docs.category); - - assert.strictEqual(result.runs[0].resources.rules[ruleid2].id, ruleid2); - assert.strictEqual(result.runs[0].resources.rules[ruleid2].shortDescription.text, rule2.meta.docs.description); - assert.strictEqual(result.runs[0].resources.rules[ruleid2].helpUri, rule2.meta.docs.url); - assert.strictEqual(result.runs[0].resources.rules[ruleid2].tags.category, rule2.meta.docs.category); - - assert.strictEqual(result.runs[0].resources.rules[ruleid3].id, ruleid3); - assert.strictEqual(result.runs[0].resources.rules[ruleid3].shortDescription.text, rule3.meta.docs.description); - assert.strictEqual(result.runs[0].resources.rules[ruleid3].helpUri, rule3.meta.docs.url); - assert.strictEqual(result.runs[0].resources.rules[ruleid3].tags.category, rule3.meta.docs.category); - - assert.strictEqual(result.runs[0].results[0].ruleId, "the-rule"); - assert.strictEqual(result.runs[0].results[1].ruleId, ruleid1); - assert.strictEqual(result.runs[0].results[2].ruleId, ruleid2); - assert.strictEqual(result.runs[0].results[3].ruleId, ruleid3); - - assert.strictEqual(result.runs[0].results[0].level, "error"); - assert.strictEqual(result.runs[0].results[1].level, "warning"); - assert.strictEqual(result.runs[0].results[2].level, "error"); - assert.strictEqual(result.runs[0].results[3].level, "warning"); - - assert.strictEqual(result.runs[0].results[0].message.text, "Unexpected value."); - assert.strictEqual(result.runs[0].results[1].message.text, "Some warning."); - assert.strictEqual(result.runs[0].results[2].message.text, "Unexpected something."); - assert.strictEqual(result.runs[0].results[3].message.text, "Custom error."); - - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.fileLocation.uri, sourceFilePath1); - assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.fileLocation.uri, sourceFilePath1); - assert.strictEqual(result.runs[0].results[2].locations[0].physicalLocation.fileLocation.uri, sourceFilePath2); - assert.strictEqual(result.runs[0].results[3].locations[0].physicalLocation.fileLocation.uri, sourceFilePath2); - - assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region); - - assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.region.startLine, 10); - assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.region.startColumn, 5); - assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.region.snippet.text, "doSomething(thingId)"); - - assert.isUndefined(result.runs[0].results[2].locations[0].physicalLocation.region); - }); - }); -}); diff --git a/src/ESLint.Formatter/test/sarif.js b/src/ESLint.Formatter/test/sarif.js index 5cb0cc8cc..ba6eecefa 100644 --- a/src/ESLint.Formatter/test/sarif.js +++ b/src/ESLint.Formatter/test/sarif.js @@ -5,14 +5,50 @@ "use strict"; - //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const assert = require("chai").assert, - formatter = require("../sarif"); +const assert = require("chai").assert; +const formatter = require("../sarif-with-rules"); + +//------------------------------------------------------------------------------ +// Global Test Content +//------------------------------------------------------------------------------ + +const rules = new Map(); +rules.set("no-unused-vars", { + meta: { + type: "suggestion", + docs: { + description: "disallow unused variables", + category: "Variables", + recommended: true, + url: "https://eslint.org/docs/rules/no-unused-vars" + }, + fixable: "code" + } +}); +rules.set("no-extra-semi", { + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary semicolons", + category: "Possible Errors", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi" + }, + + fixable: "code", + schema: [], + + messages: { + unexpected: "Unnecessary semicolon." + } + } +}); //------------------------------------------------------------------------------ // Tests @@ -27,10 +63,10 @@ describe("formatter:sarif", () => { }]; it("should return a log with one file and no results", () => { - const result = JSON.parse(formatter(code)); + const result = JSON.parse(formatter(code, null)); - assert.hasAllKeys(result.runs[0].files, sourceFilePath); - assert.strictEqual(result.runs[0].files[sourceFilePath].fileLocation.uri, sourceFilePath); + assert.hasAllKeys(result.runs[0].artifacts, sourceFilePath); + assert.strictEqual(result.runs[0].artifacts[sourceFilePath].artifactLocation.uri, sourceFilePath); assert.isUndefined(result.runs[0].results); }); }); @@ -50,14 +86,39 @@ describe("formatter:sarif", () => { it("should return a log with one file and one result", () => { const result = JSON.parse(formatter(code)); - assert.hasAllKeys(result.runs[0].files, sourceFilePath); - assert.strictEqual(result.runs[0].files[sourceFilePath].fileLocation.uri, sourceFilePath); + assert.hasAllKeys(result.runs[0].artifacts, sourceFilePath); + assert.strictEqual(result.runs[0].artifacts[sourceFilePath].artifactLocation.uri, sourceFilePath); assert.isDefined(result.runs[0].results); assert.lengthOf(result.runs[0].results, 1); assert.strictEqual(result.runs[0].results[0].level, "error"); assert.isDefined(result.runs[0].results[0].message); assert.strictEqual(result.runs[0].results[0].message.text, code[0].messages[0].message); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.fileLocation.uri, sourceFilePath); + assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri, sourceFilePath); + }); + }); +}); + +describe("formatter:sarif", () => { + describe("when passed one message with a rule id", () => { + const ruleid = "no-unused-vars"; + const code = [{ + filePath: "service.js", + messages: [{ + message: "Unexpected value.", + ruleId: ruleid, + source: "getValue()" + }] + }]; + + it("should return a log with one rule", () => { + const result = JSON.parse(formatter(code, rules)); + const rule = rules.get(ruleid); + + assert.hasAllKeys(result.runs[0].tool.driver.ruleDescriptors, ruleid); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid].id, ruleid); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid].shortDescription.text, rule.meta.docs.description); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid].helpUri, rule.meta.docs.url); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid].tags.category, rule.meta.docs.category); }); }); }); @@ -74,14 +135,14 @@ describe("formatter:sarif", () => { it("should return a log with one result whose location does not contain a region", () => { const result = JSON.parse(formatter(code)); - + assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region); }); }); }); describe("formatter:sarif", () => { - describe("when passed one message with line and column nor source string", () => { + describe("when passed one message with line and column but no source string", () => { const code = [{ filePath: "service.js", messages: [{ @@ -136,7 +197,7 @@ describe("formatter:sarif", () => { }]; it("should return a log with one result whose location contains a region with line and column #s", () => { - const result = JSON.parse(formatter(code)); + const result = JSON.parse(formatter(code, rules)); assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region.startLine); assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region.startColumn); @@ -146,11 +207,22 @@ describe("formatter:sarif", () => { }); describe("formatter:sarif", () => { - describe("when passed two results with two and one messages, respectively", () => { + describe("when passed two results with two messages each", () => { const sourceFilePath1 = "service.js"; const sourceFilePath2 = "utils.js"; const ruleid1 = "no-unused-vars"; const ruleid2 = "no-extra-semi"; + const ruleid3 = "custom-rule"; + + rules.set(ruleid3, { + meta: { + type: "suggestion", + docs: { + description: "custom description", + category: "Possible Errors" + } + } + }); const code = [{ filePath: sourceFilePath1, messages: [{ @@ -174,37 +246,69 @@ describe("formatter:sarif", () => { severity: 2, ruleId: ruleid2, line: 18 + }, + { + message: "Custom error.", + ruleId: ruleid3, + line: 42 }] }]; - it("should return a log with two files and three results", () => { - const result = JSON.parse(formatter(code)); + it("should return a log with two files, three rules, and four results", () => { + const result = JSON.parse(formatter(code, rules)); + const rule1 = rules.get(ruleid1); + const rule2 = rules.get(ruleid2); + const rule3 = rules.get(ruleid3); + + assert.lengthOf(result.runs[0].results, 4); + + assert.hasAllKeys(result.runs[0].tool.driver.ruleDescriptors, [ruleid1, ruleid2, ruleid3]); + assert.hasAllKeys(result.runs[0].artifacts, [sourceFilePath1, sourceFilePath2]); - assert.lengthOf(result.runs[0].results, 3); - - assert.hasAllKeys(result.runs[0].files, [sourceFilePath1, sourceFilePath2]); + assert.strictEqual(result.runs[0].files[sourceFilePath1].artifactLocation.uri, sourceFilePath1); + assert.strictEqual(result.runs[0].files[sourceFilePath2].artifactLocation.uri, sourceFilePath2); - assert.strictEqual(result.runs[0].files[sourceFilePath1].fileLocation.uri, sourceFilePath1); - assert.strictEqual(result.runs[0].files[sourceFilePath2].fileLocation.uri, sourceFilePath2); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid1].id, ruleid1); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid1].shortDescription.text, rule1.meta.docs.description); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid1].helpUri, rule1.meta.docs.url); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid1].tags.category, rule1.meta.docs.category); + + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid2].id, ruleid2); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid2].shortDescription.text, rule2.meta.docs.description); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid2].helpUri, rule2.meta.docs.url); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid2].tags.category, rule2.meta.docs.category); + + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid3].id, ruleid3); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid3].shortDescription.text, rule3.meta.docs.description); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid3].helpUri, rule3.meta.docs.url); + assert.strictEqual(result.runs[0].tool.driver.ruleDescriptors[ruleid3].tags.category, rule3.meta.docs.category); + + assert.strictEqual(result.runs[0].results[0].ruleId, "the-rule"); + assert.strictEqual(result.runs[0].results[1].ruleId, ruleid1); + assert.strictEqual(result.runs[0].results[2].ruleId, ruleid2); + assert.strictEqual(result.runs[0].results[3].ruleId, ruleid3); assert.strictEqual(result.runs[0].results[0].level, "error"); assert.strictEqual(result.runs[0].results[1].level, "warning"); assert.strictEqual(result.runs[0].results[2].level, "error"); + assert.strictEqual(result.runs[0].results[3].level, "warning"); assert.strictEqual(result.runs[0].results[0].message.text, "Unexpected value."); assert.strictEqual(result.runs[0].results[1].message.text, "Some warning."); assert.strictEqual(result.runs[0].results[2].message.text, "Unexpected something."); + assert.strictEqual(result.runs[0].results[3].message.text, "Custom error."); - assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.fileLocation.uri, sourceFilePath1); - assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.fileLocation.uri, sourceFilePath1); - assert.strictEqual(result.runs[0].results[2].locations[0].physicalLocation.fileLocation.uri, sourceFilePath2); + assert.strictEqual(result.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri, sourceFilePath1); + assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.artifactLocation.uri, sourceFilePath1); + assert.strictEqual(result.runs[0].results[2].locations[0].physicalLocation.artifactLocation.uri, sourceFilePath2); + assert.strictEqual(result.runs[0].results[3].locations[0].physicalLocation.artifactLocation.uri, sourceFilePath2); assert.isUndefined(result.runs[0].results[0].locations[0].physicalLocation.region); assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.region.startLine, 10); assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.region.startColumn, 5); assert.strictEqual(result.runs[0].results[1].locations[0].physicalLocation.region.snippet.text, "doSomething(thingId)"); - + assert.isUndefined(result.runs[0].results[2].locations[0].physicalLocation.region); }); });