2 * @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments
8 const lodash
= require("lodash");
11 * Compares the locations of two objects in a source file
12 * @param {{line: number, column: number}} itemA The first object
13 * @param {{line: number, column: number}} itemB The second object
14 * @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
15 * itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
17 function compareLocations(itemA
, itemB
) {
18 return itemA
.line
- itemB
.line
|| itemA
.column
- itemB
.column
;
22 * This is the same as the exported function, except that it
23 * doesn't handle disable-line and disable-next-line directives, and it always reports unused
25 * @param {Object} options options for applying directives. This is the same as the options
26 * for the exported function, except that `reportUnusedDisableDirectives` is not supported
27 * (this function always reports unused disable directives).
28 * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
29 * of filtered problems and unused eslint-disable directives
31 function applyDirectives(options
) {
33 let nextDirectiveIndex
= 0;
34 let currentGlobalDisableDirective
= null;
35 const disabledRuleMap
= new Map();
37 // enabledRules is only used when there is a current global disable directive.
38 const enabledRules
= new Set();
39 const usedDisableDirectives
= new Set();
41 for (const problem
of options
.problems
) {
43 nextDirectiveIndex
< options
.directives
.length
&&
44 compareLocations(options
.directives
[nextDirectiveIndex
], problem
) <= 0
46 const directive
= options
.directives
[nextDirectiveIndex
++];
48 switch (directive
.type
) {
50 if (directive
.ruleId
=== null) {
51 currentGlobalDisableDirective
= directive
;
52 disabledRuleMap
.clear();
54 } else if (currentGlobalDisableDirective
) {
55 enabledRules
.delete(directive
.ruleId
);
56 disabledRuleMap
.set(directive
.ruleId
, directive
);
58 disabledRuleMap
.set(directive
.ruleId
, directive
);
63 if (directive
.ruleId
=== null) {
64 currentGlobalDisableDirective
= null;
65 disabledRuleMap
.clear();
66 } else if (currentGlobalDisableDirective
) {
67 enabledRules
.add(directive
.ruleId
);
68 disabledRuleMap
.delete(directive
.ruleId
);
70 disabledRuleMap
.delete(directive
.ruleId
);
78 if (disabledRuleMap
.has(problem
.ruleId
)) {
79 usedDisableDirectives
.add(disabledRuleMap
.get(problem
.ruleId
));
80 } else if (currentGlobalDisableDirective
&& !enabledRules
.has(problem
.ruleId
)) {
81 usedDisableDirectives
.add(currentGlobalDisableDirective
);
83 problems
.push(problem
);
87 const unusedDisableDirectives
= options
.directives
88 .filter(directive
=> directive
.type
=== "disable" && !usedDisableDirectives
.has(directive
))
91 message
: directive
.ruleId
92 ? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').`
93 : "Unused eslint-disable directive (no problems were reported).",
94 line
: directive
.unprocessedDirective
.line
,
95 column
: directive
.unprocessedDirective
.column
,
96 severity
: options
.reportUnusedDisableDirectives
=== "warn" ? 1 : 2,
100 return { problems
, unusedDisableDirectives
};
104 * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
105 * of reported problems, determines which problems should be reported.
106 * @param {Object} options Information about directives and problems
108 * type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
109 * ruleId: (string|null),
112 * }} options.directives Directive comments found in the file, with one-based columns.
113 * Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
114 * comment for two different rules is represented as two directives).
115 * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
116 * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
117 * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
118 * @returns {{ruleId: (string|null), line: number, column: number}[]}
119 * A list of reported problems that were not disabled by the directive comments.
121 module
.exports
= ({ directives
, problems
, reportUnusedDisableDirectives
= "off" }) => {
122 const blockDirectives
= directives
123 .filter(directive
=> directive
.type
=== "disable" || directive
.type
=== "enable")
124 .map(directive
=> Object
.assign({}, directive
, { unprocessedDirective
: directive
}))
125 .sort(compareLocations
);
127 const lineDirectives
= lodash
.flatMap(directives
, directive
=> {
128 switch (directive
.type
) {
135 { type
: "disable", line
: directive
.line
, column
: 1, ruleId
: directive
.ruleId
, unprocessedDirective
: directive
},
136 { type
: "enable", line
: directive
.line
+ 1, column
: 0, ruleId
: directive
.ruleId
, unprocessedDirective
: directive
}
139 case "disable-next-line":
141 { type
: "disable", line
: directive
.line
+ 1, column
: 1, ruleId
: directive
.ruleId
, unprocessedDirective
: directive
},
142 { type
: "enable", line
: directive
.line
+ 2, column
: 0, ruleId
: directive
.ruleId
, unprocessedDirective
: directive
}
146 throw new TypeError(`Unrecognized directive type '${directive.type}'`);
148 }).sort(compareLocations
);
150 const blockDirectivesResult
= applyDirectives({
152 directives
: blockDirectives
,
153 reportUnusedDisableDirectives
155 const lineDirectivesResult
= applyDirectives({
156 problems
: blockDirectivesResult
.problems
,
157 directives
: lineDirectives
,
158 reportUnusedDisableDirectives
161 return reportUnusedDisableDirectives
!== "off"
162 ? lineDirectivesResult
.problems
163 .concat(blockDirectivesResult
.unusedDisableDirectives
)
164 .concat(lineDirectivesResult
.unusedDisableDirectives
)
165 .sort(compareLocations
)
166 : lineDirectivesResult
.problems
;