diff options
Diffstat (limited to 'tools/node_modules/eslint/lib/linter')
-rw-r--r-- | tools/node_modules/eslint/lib/linter/apply-disable-directives.js | 79 | ||||
-rw-r--r-- | tools/node_modules/eslint/lib/linter/linter.js | 107 |
2 files changed, 119 insertions, 67 deletions
diff --git a/tools/node_modules/eslint/lib/linter/apply-disable-directives.js b/tools/node_modules/eslint/lib/linter/apply-disable-directives.js index f6b432399cf..f8e4aeedb29 100644 --- a/tools/node_modules/eslint/lib/linter/apply-disable-directives.js +++ b/tools/node_modules/eslint/lib/linter/apply-disable-directives.js @@ -197,62 +197,52 @@ function processUnusedDisableDirectives(allDirectives) { * for the exported function, except that `reportUnusedDisableDirectives` is not supported * (this function always reports unused disable directives). * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list - * of filtered problems and unused eslint-disable directives + * of problems (including suppressed ones) and unused eslint-disable directives */ function applyDirectives(options) { const problems = []; - let nextDirectiveIndex = 0; - let currentGlobalDisableDirective = null; - const disabledRuleMap = new Map(); - - // enabledRules is only used when there is a current global disable directive. - const enabledRules = new Set(); const usedDisableDirectives = new Set(); for (const problem of options.problems) { + let disableDirectivesForProblem = []; + let nextDirectiveIndex = 0; + while ( nextDirectiveIndex < options.directives.length && compareLocations(options.directives[nextDirectiveIndex], problem) <= 0 ) { const directive = options.directives[nextDirectiveIndex++]; - switch (directive.type) { - case "disable": - if (directive.ruleId === null) { - currentGlobalDisableDirective = directive; - disabledRuleMap.clear(); - enabledRules.clear(); - } else if (currentGlobalDisableDirective) { - enabledRules.delete(directive.ruleId); - disabledRuleMap.set(directive.ruleId, directive); - } else { - disabledRuleMap.set(directive.ruleId, directive); - } - break; - - case "enable": - if (directive.ruleId === null) { - currentGlobalDisableDirective = null; - disabledRuleMap.clear(); - } else if (currentGlobalDisableDirective) { - enabledRules.add(directive.ruleId); - disabledRuleMap.delete(directive.ruleId); - } else { - disabledRuleMap.delete(directive.ruleId); - } - break; - - // no default + if (directive.ruleId === null || directive.ruleId === problem.ruleId) { + switch (directive.type) { + case "disable": + disableDirectivesForProblem.push(directive); + break; + + case "enable": + disableDirectivesForProblem = []; + break; + + // no default + } } } - if (disabledRuleMap.has(problem.ruleId)) { - usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId)); - } else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) { - usedDisableDirectives.add(currentGlobalDisableDirective); - } else { - problems.push(problem); + if (disableDirectivesForProblem.length > 0) { + const suppressions = disableDirectivesForProblem.map(directive => ({ + kind: "directive", + justification: directive.unprocessedDirective.justification + })); + + if (problem.suppressions) { + problem.suppressions = problem.suppressions.concat(suppressions); + } else { + problem.suppressions = suppressions; + usedDisableDirectives.add(disableDirectivesForProblem[disableDirectivesForProblem.length - 1]); + } } + + problems.push(problem); } const unusedDisableDirectivesToReport = options.directives @@ -282,13 +272,14 @@ function applyDirectives(options) { /** * 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. + * of reported problems, adds the suppression information to the problems. * @param {Object} options Information about directives and problems * @param {{ * type: ("disable"|"enable"|"disable-line"|"disable-next-line"), * ruleId: (string|null), * line: number, - * column: number + * column: number, + * justification: string * }} 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). @@ -296,8 +287,8 @@ function applyDirectives(options) { * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns. * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives * @param {boolean} options.disableFixes If true, it doesn't make `fix` properties. - * @returns {{ruleId: (string|null), line: number, column: number}[]} - * A list of reported problems that were not disabled by the directive comments. + * @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]} + * An object with a list of reported problems, the suppressed of which contain the suppression information. */ module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => { const blockDirectives = directives diff --git a/tools/node_modules/eslint/lib/linter/linter.js b/tools/node_modules/eslint/lib/linter/linter.js index 056f9cec98e..9b9f4919207 100644 --- a/tools/node_modules/eslint/lib/linter/linter.js +++ b/tools/node_modules/eslint/lib/linter/linter.js @@ -59,6 +59,7 @@ const globals = require("../../conf/globals"); /** @typedef {import("../shared/types").Environment} Environment */ /** @typedef {import("../shared/types").GlobalConf} GlobalConf */ /** @typedef {import("../shared/types").LintMessage} LintMessage */ +/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */ /** @typedef {import("../shared/types").ParserOptions} ParserOptions */ /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ /** @typedef {import("../shared/types").Processor} Processor */ @@ -77,6 +78,7 @@ const globals = require("../../conf/globals"); * @property {number} line The line number * @property {number} column The column number * @property {(string|null)} ruleId The rule ID + * @property {string} justification The justification of directive */ /** @@ -84,6 +86,7 @@ const globals = require("../../conf/globals"); * @typedef {Object} LinterInternalSlots * @property {ConfigArray|null} lastConfigArray The `ConfigArray` instance that the last `verify()` call used. * @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used. + * @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced. * @property {Map<string, Parser>} parserMap The loaded parsers. * @property {Rules} ruleMap The loaded rules. */ @@ -287,11 +290,12 @@ function createLintingProblem(options) { * @param {token} options.commentToken The Comment token * @param {string} options.value The value after the directive in the comment * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`) + * @param {string} options.justification The justification of the directive * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules * @returns {Object} Directives and problems from the comment */ function createDisableDirectives(options) { - const { commentToken, type, value, ruleMapper } = options; + const { commentToken, type, value, justification, ruleMapper } = options; const ruleIds = Object.keys(commentParser.parseListConfig(value)); const directiveRules = ruleIds.length ? ruleIds : [null]; const result = { @@ -306,9 +310,23 @@ function createDisableDirectives(options) { // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/) if (ruleId === null || !!ruleMapper(ruleId)) { if (type === "disable-next-line") { - result.directives.push({ parentComment, type, line: commentToken.loc.end.line, column: commentToken.loc.end.column + 1, ruleId }); + result.directives.push({ + parentComment, + type, + line: commentToken.loc.end.line, + column: commentToken.loc.end.column + 1, + ruleId, + justification + }); } else { - result.directives.push({ parentComment, type, line: commentToken.loc.start.line, column: commentToken.loc.start.column + 1, ruleId }); + result.directives.push({ + parentComment, + type, + line: commentToken.loc.start.line, + column: commentToken.loc.start.column + 1, + ruleId, + justification + }); } } else { result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc })); @@ -318,12 +336,21 @@ function createDisableDirectives(options) { } /** - * Remove the ignored part from a given directive comment and trim it. - * @param {string} value The comment text to strip. - * @returns {string} The stripped text. + * Extract the directive and the justification from a given directive comment and trim them. + * @param {string} value The comment text to extract. + * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification. */ -function stripDirectiveComment(value) { - return value.split(/\s-{2,}\s/u)[0].trim(); +function extractDirectiveComment(value) { + const match = /\s-{2,}\s/u.exec(value); + + if (!match) { + return { directivePart: value.trim(), justificationPart: "" }; + } + + const directive = value.slice(0, match.index).trim(); + const justification = value.slice(match.index + match[0].length).trim(); + + return { directivePart: directive, justificationPart: justification }; } /** @@ -347,8 +374,9 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { }); ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { - const trimmedCommentText = stripDirectiveComment(comment.value); - const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText); + const { directivePart, justificationPart } = extractDirectiveComment(comment.value); + + const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(directivePart); if (!match) { return; @@ -383,7 +411,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { return; } - const directiveValue = trimmedCommentText.slice(match.index + directiveText.length); + const directiveValue = directivePart.slice(match.index + directiveText.length); switch (directiveText) { case "eslint-disable": @@ -391,7 +419,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) { case "eslint-disable-next-line": case "eslint-disable-line": { const directiveType = directiveText.slice("eslint-".length); - const options = { commentToken: comment, type: directiveType, value: directiveValue, ruleMapper }; + const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper }; const { directives, directiveProblems } = createDisableDirectives(options); disableDirectives.push(...directives); @@ -548,7 +576,7 @@ function findEslintEnv(text) { if (match[0].endsWith("*/")) { retv = Object.assign( retv || {}, - commentParser.parseListConfig(stripDirectiveComment(match[1])) + commentParser.parseListConfig(extractDirectiveComment(match[1]).directivePart) ); } } @@ -1223,6 +1251,7 @@ class Linter { cwd: normalizeCwd(cwd), lastConfigArray: null, lastSourceCode: null, + lastSuppressedMessages: [], configType, // TODO: Remove after flat config conversion parserMap: new Map([["espree", espree]]), ruleMap: new Rules() @@ -1246,7 +1275,7 @@ class Linter { * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. * @throws {Error} If during rule execution. - * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. */ _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { const slots = internalSlotsMap.get(this); @@ -1428,11 +1457,11 @@ class Linter { configArray.normalizeSync(); } - return this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true); + return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true)); } if (typeof config.extractConfig === "function") { - return this._verifyWithConfigArray(textOrSourceCode, config, options); + return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, config, options)); } } @@ -1446,9 +1475,9 @@ class Linter { * So we cannot apply multiple processors. */ if (options.preprocess || options.postprocess) { - return this._verifyWithProcessor(textOrSourceCode, config, options); + return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, config, options)); } - return this._verifyWithoutProcessors(textOrSourceCode, config, options); + return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, config, options)); } /** @@ -1457,7 +1486,7 @@ class Linter { * @param {FlatConfig} config The config array. * @param {VerifyOptions&ProcessorOptions} options The options. * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. - * @returns {LintMessage[]} The found problems. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. */ _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) { const filename = options.filename || "<input>"; @@ -1514,7 +1543,7 @@ class Linter { * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. * @throws {Error} If during rule execution. - * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. */ _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { const slots = internalSlotsMap.get(this); @@ -1663,7 +1692,7 @@ class Linter { * @param {string|SourceCode} textOrSourceCode The source code. * @param {ConfigArray} configArray The config array. * @param {VerifyOptions&ProcessorOptions} options The options. - * @returns {LintMessage[]} The found problems. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. */ _verifyWithConfigArray(textOrSourceCode, configArray, options) { debug("With ConfigArray: %s", options.filename); @@ -1700,7 +1729,7 @@ class Linter { * @param {VerifyOptions&ProcessorOptions} options The options. * @param {boolean} [firstCall=false] Indicates if this is being called directly * from verify(). (TODO: Remove once eslintrc is removed.) - * @returns {LintMessage[]} The found problems. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. */ _verifyWithFlatConfigArray(textOrSourceCode, configArray, options, firstCall = false) { debug("With flat config: %s", options.filename); @@ -1740,7 +1769,7 @@ class Linter { * @param {ConfigData|ExtractedConfig} config The config array. * @param {VerifyOptions&ProcessorOptions} options The options. * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. - * @returns {LintMessage[]} The found problems. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. */ _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) { const filename = options.filename || "<input>"; @@ -1793,6 +1822,30 @@ class Linter { } /** + * Given a list of reported problems, distinguish problems between normal messages and suppressed messages. + * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages. + * @param {Problem[]} problems A list of reported problems. + * @returns {LintMessage[]} A list of LintMessage. + */ + _distinguishSuppressedMessages(problems) { + const messages = []; + const suppressedMessages = []; + const slots = internalSlotsMap.get(this); + + for (const problem of problems) { + if (problem.suppressions) { + suppressedMessages.push(problem); + } else { + messages.push(problem); + } + } + + slots.lastSuppressedMessages = suppressedMessages; + + return messages; + } + + /** * Gets the SourceCode object representing the parsed source. * @returns {SourceCode} The SourceCode object. */ @@ -1801,6 +1854,14 @@ class Linter { } /** + * Gets the list of SuppressedLintMessage produced in the last running. + * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage + */ + getSuppressedMessages() { + return internalSlotsMap.get(this).lastSuppressedMessages; + } + + /** * Defines a new linting rule. * @param {string} ruleId A unique rule identifier * @param {Function | Rule} ruleModule Function from context to object mapping AST node types to event handlers |