From a32ec3620bf9aa8fae0c7ae94163f3e4a732b3ce Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Wed, 6 Sep 2017 16:22:10 -0400 Subject: [PATCH] Update: refactor eslint-disable comment processing (#9216) (fixes #6592, fixes #9215) --- lib/linter.js | 138 +++---- lib/util/apply-disable-directives.js | 115 ++++++ tests/lib/linter.js | 23 +- tests/lib/util/apply-disable-directives.js | 395 +++++++++++++++++++++ 4 files changed, 564 insertions(+), 107 deletions(-) create mode 100644 lib/util/apply-disable-directives.js create mode 100644 tests/lib/util/apply-disable-directives.js diff --git a/lib/linter.js b/lib/linter.js index 76487c53ec2..0b713364351 100755 --- a/lib/linter.js +++ b/lib/linter.js @@ -18,6 +18,7 @@ const EventEmitter = require("events").EventEmitter, ConfigOps = require("./config/config-ops"), validator = require("./config/config-validator"), Environments = require("./config/environments"), + applyDisableDirectives = require("./util/apply-disable-directives"), NodeEventGenerator = require("./util/node-event-generator"), SourceCode = require("./util/source-code"), Traverser = require("./util/traverser"), @@ -249,68 +250,23 @@ function addDeclaredGlobals(program, globalScope, config, envContext) { } /** - * Add data to reporting configuration to disable reporting for list of rules - * starting from start location - * @param {Object[]} reportingConfig Current reporting configuration - * @param {Object} start Position to start - * @param {string[]} rulesToDisable List of rules - * @returns {void} - */ -function disableReporting(reportingConfig, start, rulesToDisable) { - - if (rulesToDisable.length) { - rulesToDisable.forEach(rule => { - reportingConfig.push({ - start, - end: null, - rule - }); - }); - } else { - reportingConfig.push({ - start, - end: null, - rule: null - }); - } -} - -/** - * Add data to reporting configuration to enable reporting for list of rules - * starting from start location - * @param {Object[]} reportingConfig Current reporting configuration - * @param {Object} start Position to start - * @param {string[]} rulesToEnable List of rules - * @returns {void} + * Creates a collection of disable directives from a comment + * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} type The type of directive comment + * @param {{line: number, column: number}} loc The 0-based location of the comment token + * @param {string} value The value after the directive in the comment + * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`) + * @returns {{ + * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), + * line: number, + * column: number, + * ruleId: (string|null) + * }[]} Directives from the comment */ -function enableReporting(reportingConfig, start, rulesToEnable) { - let i; - - if (rulesToEnable.length) { - rulesToEnable.forEach(rule => { - for (i = reportingConfig.length - 1; i >= 0; i--) { - if (!reportingConfig[i].end && reportingConfig[i].rule === rule) { - reportingConfig[i].end = start; - break; - } - } - }); - } else { - - // find all previous disabled locations if they was started as list of rules - let prevStart; - - for (i = reportingConfig.length - 1; i >= 0; i--) { - if (prevStart && prevStart !== reportingConfig[i].start) { - break; - } +function createDisableDirectives(type, loc, value) { + const ruleIds = Object.keys(parseListConfig(value)); + const directiveRules = ruleIds.length ? ruleIds : [null]; - if (!reportingConfig[i].end) { - reportingConfig[i].end = start; - prevStart = reportingConfig[i].start; - } - } - } + return directiveRules.map(ruleId => ({ type, line: loc.line, column: loc.column + 1, ruleId })); } /** @@ -321,7 +277,16 @@ function enableReporting(reportingConfig, start, rulesToEnable) { * @param {ASTNode} ast The top node of the AST. * @param {Object} config The existing configuration data. * @param {Linter} linterContext Linter context object - * @returns {{config: Object, problems: Problem[]}} Modified config object, along with any problems encountered + * @returns {{ + * config: Object, + * problems: Problem[], + * disableDirectives: { + * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), + * line: number, + * column: number, + * ruleId: (string|null) + * }[] + * }} Modified config object, along with any problems encountered * while parsing config comments */ function modifyConfigsFromComments(filename, ast, config, linterContext) { @@ -334,7 +299,7 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) { }; const commentRules = {}; const problems = []; - const reportingConfig = linterContext.reportingConfig; + const disableDirectives = []; ast.comments.forEach(comment => { @@ -360,11 +325,11 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) { break; case "eslint-disable": - disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); + [].push.apply(disableDirectives, createDisableDirectives("disable", comment.loc.start, value)); break; case "eslint-enable": - enableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); + [].push.apply(disableDirectives, createDisableDirectives("enable", comment.loc.start, value)); break; case "eslint": { @@ -388,11 +353,9 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) { } } else { // comment.type === "Line" if (match[1] === "eslint-disable-line") { - disableReporting(reportingConfig, { line: comment.loc.start.line, column: 0 }, Object.keys(parseListConfig(value))); - enableReporting(reportingConfig, comment.loc.end, Object.keys(parseListConfig(value))); + [].push.apply(disableDirectives, createDisableDirectives("disable-line", comment.loc.start, value)); } else if (match[1] === "eslint-disable-next-line") { - disableReporting(reportingConfig, comment.loc.start, Object.keys(parseListConfig(value))); - enableReporting(reportingConfig, { line: comment.loc.start.line + 2 }, Object.keys(parseListConfig(value))); + [].push.apply(disableDirectives, createDisableDirectives("disable-next-line", comment.loc.start, value)); } } } @@ -410,33 +373,11 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) { return { config: ConfigOps.merge(config, commentConfig), - problems + problems, + disableDirectives }; } -/** - * Check if message of rule with ruleId should be ignored in location - * @param {Object[]} reportingConfig Collection of ignore records - * @param {string} ruleId Id of rule - * @param {Object} location 1-indexed location of message - * @returns {boolean} True if message should be ignored, false otherwise - */ -function isDisabledByReportingConfig(reportingConfig, ruleId, location) { - - for (let i = 0, c = reportingConfig.length; i < c; i++) { - - const ignore = reportingConfig[i]; - - if ((!ignore.rule || ignore.rule === ruleId) && - (location.line > ignore.start.line || (location.line === ignore.start.line && location.column > ignore.start.column)) && - (!ignore.end || (location.line < ignore.end.line || (location.line === ignore.end.line && location.column - 1 <= ignore.end.column)))) { - return true; - } - } - - return false; -} - /** * Normalize ECMAScript version from the initial config * @param {number} ecmaVersion ECMAScript version from the initial config @@ -705,7 +646,6 @@ class Linter { this.currentConfig = null; this.scopeManager = null; this.traverser = null; - this.reportingConfig = []; this.sourceCode = null; this.version = pkg.version; @@ -721,7 +661,6 @@ class Linter { this.currentConfig = null; this.scopeManager = null; this.traverser = null; - this.reportingConfig = []; this.sourceCode = null; } @@ -822,6 +761,7 @@ class Linter { const problems = []; const sourceCode = this.sourceCode; + let disableDirectives; // parse global comments and modify config if (allowInlineConfig !== false) { @@ -829,6 +769,9 @@ class Linter { config = modifyConfigResult.config; modifyConfigResult.problems.forEach(problem => problems.push(problem)); + disableDirectives = modifyConfigResult.disableDirectives; + } else { + disableDirectives = []; } const emitter = new EventEmitter().setMaxListeners(Infinity); @@ -985,9 +928,10 @@ class Linter { } }); - return problems - .filter(problem => !isDisabledByReportingConfig(this.reportingConfig, problem.ruleId, problem)) - .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column); + return applyDisableDirectives({ + directives: disableDirectives, + problems: problems.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column) + }); } /** diff --git a/lib/util/apply-disable-directives.js b/lib/util/apply-disable-directives.js new file mode 100644 index 00000000000..d9d6374e6df --- /dev/null +++ b/lib/util/apply-disable-directives.js @@ -0,0 +1,115 @@ +/** + * @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments + * @author Teddy Katz + */ + +"use strict"; + +const lodash = require("lodash"); + +/** + * Compares the locations of two objects in a source file + * @param {{line: number, column: number}} itemA The first object + * @param {{line: number, column: number}} itemB The second object + * @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if + * itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location. + */ +function compareLocations(itemA, itemB) { + return itemA.line - itemB.line || itemA.column - itemB.column; +} + +/** + * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list + * of reported problems, determines which problems should be reported. + * @param {Object} options Information about directives and problems + * @param {{ + * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), + * ruleId: (string|null), + * line: number, + * column: number + * }} options.directives Directive comments found in the file, with one-based columns. + * Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable + * comment for two different rules is represented as two directives). + * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems + * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns. + * @returns {{ruleId: (string|null), line: number, column: number}[]} + * A list of reported problems that were not disabled by the directive comments. + */ +module.exports = options => { + const processedDirectives = lodash.flatMap(options.directives, directive => { + switch (directive.type) { + case "disable": + case "enable": + return [directive]; + + case "disable-line": + return [ + { type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId }, + { type: "enable", line: directive.line + 1, column: 1, ruleId: directive.ruleId } + ]; + + case "disable-next-line": + return [ + { type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId }, + { type: "enable", line: directive.line + 2, column: 1, ruleId: directive.ruleId } + ]; + + default: + throw new TypeError(`Unrecognized directive type '${directive.type}'`); + } + }).sort(compareLocations); + + const problems = []; + let nextDirectiveIndex = 0; + let globalDisableActive = false; + + // disabledRules is only used when there is no active global /* eslint-disable */ comment. + const disabledRules = new Set(); + + // enabledRules is only used when there is an active global /* eslint-disable */ comment. + const enabledRules = new Set(); + + for (const problem of options.problems) { + while ( + nextDirectiveIndex < processedDirectives.length && + compareLocations(processedDirectives[nextDirectiveIndex], problem) <= 0 + ) { + const directive = processedDirectives[nextDirectiveIndex++]; + + switch (directive.type) { + case "disable": + if (directive.ruleId === null) { + globalDisableActive = true; + enabledRules.clear(); + } else if (globalDisableActive) { + enabledRules.delete(directive.ruleId); + } else { + disabledRules.add(directive.ruleId); + } + break; + + case "enable": + if (directive.ruleId === null) { + globalDisableActive = false; + disabledRules.clear(); + } else if (globalDisableActive) { + enabledRules.add(directive.ruleId); + } else { + disabledRules.delete(directive.ruleId); + } + break; + + // no default + } + } + + if ( + globalDisableActive && enabledRules.has(problem.ruleId) || + !globalDisableActive && !disabledRules.has(problem.ruleId) + ) { + problems.push(problem); + } + } + + return problems; +}; diff --git a/tests/lib/linter.js b/tests/lib/linter.js index 92d7fa8c715..70b668a0a00 100644 --- a/tests/lib/linter.js +++ b/tests/lib/linter.js @@ -1671,7 +1671,7 @@ describe("Linter", () => { assert.equal(messages[1].column, 19); }); - it("should not report a violation", () => { + it("should report a violation", () => { const code = [ "/*eslint-disable */", @@ -1687,7 +1687,7 @@ describe("Linter", () => { const messages = linter.verify(code, config, filename); - assert.equal(messages.length, 0); + assert.equal(messages.length, 1); }); @@ -2024,7 +2024,7 @@ describe("Linter", () => { "console.log('test');", "/*eslint-enable */", - "alert('test');", + "alert('test');", // here "console.log('test');", // here "/*eslint-enable */", @@ -2038,16 +2038,19 @@ describe("Linter", () => { const messages = linter.verify(code, config, filename); - assert.equal(messages.length, 3); + assert.equal(messages.length, 4); - assert.equal(messages[0].ruleId, "no-console"); - assert.equal(messages[0].line, 7); + assert.equal(messages[0].ruleId, "no-alert"); + assert.equal(messages[0].line, 6); - assert.equal(messages[1].ruleId, "no-alert"); - assert.equal(messages[1].line, 9); + assert.equal(messages[1].ruleId, "no-console"); + assert.equal(messages[1].line, 7); - assert.equal(messages[2].ruleId, "no-console"); - assert.equal(messages[2].line, 10); + assert.equal(messages[2].ruleId, "no-alert"); + assert.equal(messages[2].line, 9); + + assert.equal(messages[3].ruleId, "no-console"); + assert.equal(messages[3].line, 10); }); diff --git a/tests/lib/util/apply-disable-directives.js b/tests/lib/util/apply-disable-directives.js new file mode 100644 index 00000000000..9b8d2692617 --- /dev/null +++ b/tests/lib/util/apply-disable-directives.js @@ -0,0 +1,395 @@ +/** + * @fileoverview Tests for filter-by-disable-comments + * @author Teddy Katz + */ + +"use strict"; + +const assert = require("chai").assert; +const applyDisableDirectives = require("../../../lib/util/apply-disable-directives"); + +describe("comment-reporting-config", () => { + describe("/* eslint-disable */ comments without rules", () => { + it("keeps problems before the comment on the same line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: null }], + problems: [{ line: 1, column: 7, ruleId: "foo" }] + }), + [{ ruleId: "foo", line: 1, column: 7 }] + ); + }); + + it("keeps problems on a previous line before the comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 2, column: 8, ruleId: null }], + problems: [{ line: 1, column: 10, ruleId: "foo" }] + }), + [{ ruleId: "foo", line: 1, column: 10 }] + ); + }); + + it("filters problems at the same location as the comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: null }], + problems: [{ line: 1, column: 8, ruleId: null }] + }), + [] + ); + }); + + it("filters out problems after the comment on the same line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: null }], + problems: [{ line: 1, column: 10, ruleId: "foo" }] + }), + [] + ); + }); + + it("filters out problems on a later line than the comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: null }], + problems: [{ line: 2, column: 3, ruleId: "foo" }] + }), + [] + ); + }); + }); + + describe("/* eslint-disable */ comments with rules", () => { + it("filters problems after the comment that have the same ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }], + problems: [{ line: 2, column: 3, ruleId: "foo" }] + }), + [] + ); + }); + + it("filters problems in the same location as the comment that have the same ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }], + problems: [{ line: 1, column: 8, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems after the comment that have a different ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }], + problems: [{ line: 2, column: 3, ruleId: "not-foo" }] + }), + [{ line: 2, column: 3, ruleId: "not-foo" }] + ); + }); + + it("keeps problems before the comment that have the same ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }], + problems: [{ line: 1, column: 7, ruleId: "foo" }] + }), + [{ line: 1, column: 7, ruleId: "foo" }] + ); + }); + }); + + describe("eslint-enable comments without rules", () => { + it("keeps problems after the eslint-enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: null }, + { type: "enable", line: 1, column: 5, ruleId: null } + ], + problems: [{ line: 1, column: 7, ruleId: "foo" }] + }), + [{ line: 1, column: 7, ruleId: "foo" }] + ); + }); + + it("keeps problems in the same location as the eslint-enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: null }, + { type: "enable", line: 1, column: 5, ruleId: null } + ], + problems: [{ line: 1, column: 5, ruleId: "foo" }] + }), + [{ line: 1, column: 5, ruleId: "foo" }] + ); + }); + + it("filters out problems before the eslint-enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: null }, + { type: "enable", line: 1, column: 5, ruleId: null } + ], + problems: [{ line: 1, column: 3, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems before the eslint-enable comment if there is no corresponding disable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: "foo" }, + { type: "enable", line: 1, column: 5, ruleId: null } + ], + problems: [{ line: 1, column: 3, ruleId: "not-foo" }] + }), + [{ line: 1, column: 3, ruleId: "not-foo" }] + ); + }); + }); + + describe("eslint-enable comments with rules", () => { + it("keeps problems after the comment that have the same ruleId as the eslint-enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 4, ruleId: null }, + { type: "enable", line: 2, column: 1, ruleId: "foo" } + ], + problems: [{ line: 2, column: 4, ruleId: "foo" }] + }), + [{ line: 2, column: 4, ruleId: "foo" }] + ); + }); + + it("keeps problems in the same location as the comment that have the same ruleId as the eslint-enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 4, ruleId: null }, + { type: "enable", line: 2, column: 1, ruleId: "foo" } + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }] + }), + [{ line: 2, column: 1, ruleId: "foo" }] + ); + }); + + it("filters problems after the comment that have a different ruleId as the eslint-enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 4, ruleId: null }, + { type: "enable", line: 2, column: 1, ruleId: "foo" } + ], + problems: [{ line: 2, column: 4, ruleId: "not-foo" }] + }), + [] + ); + }); + + it("reenables reporting correctly even when followed by another enable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: null }, + { type: "enable", line: 1, column: 3, ruleId: "foo" }, + { type: "enable", line: 1, column: 5, ruleId: "bar" } + ], + problems: [ + { line: 1, column: 2, ruleId: "foo" }, + { line: 1, column: 2, ruleId: "bar" }, + { line: 1, column: 4, ruleId: "foo" }, + { line: 1, column: 4, ruleId: "bar" }, + { line: 1, column: 6, ruleId: "foo" }, + { line: 1, column: 6, ruleId: "bar" } + ] + }), + [ + { line: 1, column: 4, ruleId: "foo" }, + { line: 1, column: 6, ruleId: "foo" }, + { line: 1, column: 6, ruleId: "bar" } + ] + ); + }); + }); + + describe("eslint-disable-line comments without rules", () => { + it("keeps problems on a previous line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 2, column: 1, ruleId: null }], + problems: [{ line: 1, column: 5, ruleId: "foo" }] + }), + [{ line: 1, column: 5, ruleId: "foo" }] + ); + }); + + it("filters problems before the comment on the same line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }], + problems: [{ line: 1, column: 1, ruleId: "foo" }] + }), + [] + ); + }); + + it("filters problems after the comment on the same line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }], + problems: [{ line: 1, column: 10, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems on a following line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 1, column: 4 }], + problems: [{ line: 2, column: 1, ruleId: "foo" }] + }), + [{ line: 2, column: 1, ruleId: "foo" }] + ); + }); + }); + + describe("eslint-disable-line comments with rules", () => { + it("filters problems on the current line that match the ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 1, column: 4, ruleId: "foo" }], + problems: [{ line: 1, column: 2, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems on the current line that do not match the ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-line", line: 1, column: 4, ruleId: "foo" }], + problems: [{ line: 1, column: 2, ruleId: "not-foo" }] + }), + [{ line: 1, column: 2, ruleId: "not-foo" }] + ); + }); + + it("filters problems on the current line that do not match the ruleId if preceded by a disable comment", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable", line: 1, column: 1, ruleId: null }, + { type: "disable-line", line: 1, column: 3, ruleId: "foo" } + ], + problems: [{ line: 1, column: 5, ruleId: "not-foo" }] + }), + [] + ); + }); + }); + + describe("eslint-disable-next-line comments without rules", () => { + it("filters problems on the next line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], + problems: [{ line: 2, column: 3, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems on the same line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], + problems: [{ line: 1, column: 3, ruleId: "foo" }] + }), + [{ line: 1, column: 3, ruleId: "foo" }] + ); + }); + + it("keeps problems after the next line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], + problems: [{ line: 3, column: 3, ruleId: "foo" }] + }), + [{ line: 3, column: 3, ruleId: "foo" }] + ); + }); + + it("filters problems on the next line even if there is an eslint-enable comment on the same line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable-next-line", line: 1, column: 1, ruleId: null }, + { type: "enable", line: 1, column: 5, ruleId: null } + ], + problems: [{ line: 2, column: 2, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems on the next line if there is an eslint-enable comment before the problem on the next line", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [ + { type: "disable-next-line", line: 1, column: 1, ruleId: null }, + { type: "enable", line: 2, column: 1, ruleId: null } + ], + problems: [{ line: 2, column: 2, ruleId: "foo" }] + }), + [{ line: 2, column: 2, ruleId: "foo" }] + ); + }); + }); + + describe("eslint-disable-next-line comments with rules", () => { + it("filters problems on the next line that match the ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: "foo" }], + problems: [{ line: 2, column: 1, ruleId: "foo" }] + }), + [] + ); + }); + + it("keeps problems on the next line that do not match the ruleId", () => { + assert.deepEqual( + applyDisableDirectives({ + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: "foo" }], + problems: [{ line: 2, column: 1, ruleId: "not-foo" }] + }), + [{ line: 2, column: 1, ruleId: "not-foo" }] + ); + }); + }); + + describe("unrecognized directive types", () => { + it("throws a TypeError when it encounters an unrecognized directive", () => { + assert.throws( + () => + applyDisableDirectives({ + directives: [{ type: "foo", line: 1, column: 4, ruleId: "foo" }], + problems: [] + }), + "Unrecognized directive type 'foo'" + ); + }); + }); +});