massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / linter / apply-disable-directives.js
1 /**
2  * @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments
3  * @author Teddy Katz
4  */
5
6 "use strict";
7
8 /**
9  * Compares the locations of two objects in a source file
10  * @param {{line: number, column: number}} itemA The first object
11  * @param {{line: number, column: number}} itemB The second object
12  * @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
13  * itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
14  */
15 function compareLocations(itemA, itemB) {
16     return itemA.line - itemB.line || itemA.column - itemB.column;
17 }
18
19 /**
20  * This is the same as the exported function, except that it
21  * doesn't handle disable-line and disable-next-line directives, and it always reports unused
22  * disable directives.
23  * @param {Object} options options for applying directives. This is the same as the options
24  * for the exported function, except that `reportUnusedDisableDirectives` is not supported
25  * (this function always reports unused disable directives).
26  * @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
27  * of filtered problems and unused eslint-disable directives
28  */
29 function applyDirectives(options) {
30     const problems = [];
31     let nextDirectiveIndex = 0;
32     let currentGlobalDisableDirective = null;
33     const disabledRuleMap = new Map();
34
35     // enabledRules is only used when there is a current global disable directive.
36     const enabledRules = new Set();
37     const usedDisableDirectives = new Set();
38
39     for (const problem of options.problems) {
40         while (
41             nextDirectiveIndex < options.directives.length &&
42             compareLocations(options.directives[nextDirectiveIndex], problem) <= 0
43         ) {
44             const directive = options.directives[nextDirectiveIndex++];
45
46             switch (directive.type) {
47                 case "disable":
48                     if (directive.ruleId === null) {
49                         currentGlobalDisableDirective = directive;
50                         disabledRuleMap.clear();
51                         enabledRules.clear();
52                     } else if (currentGlobalDisableDirective) {
53                         enabledRules.delete(directive.ruleId);
54                         disabledRuleMap.set(directive.ruleId, directive);
55                     } else {
56                         disabledRuleMap.set(directive.ruleId, directive);
57                     }
58                     break;
59
60                 case "enable":
61                     if (directive.ruleId === null) {
62                         currentGlobalDisableDirective = null;
63                         disabledRuleMap.clear();
64                     } else if (currentGlobalDisableDirective) {
65                         enabledRules.add(directive.ruleId);
66                         disabledRuleMap.delete(directive.ruleId);
67                     } else {
68                         disabledRuleMap.delete(directive.ruleId);
69                     }
70                     break;
71
72                 // no default
73             }
74         }
75
76         if (disabledRuleMap.has(problem.ruleId)) {
77             usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId));
78         } else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) {
79             usedDisableDirectives.add(currentGlobalDisableDirective);
80         } else {
81             problems.push(problem);
82         }
83     }
84
85     const unusedDisableDirectives = options.directives
86         .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive))
87         .map(directive => ({
88             ruleId: null,
89             message: directive.ruleId
90                 ? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').`
91                 : "Unused eslint-disable directive (no problems were reported).",
92             line: directive.unprocessedDirective.line,
93             column: directive.unprocessedDirective.column,
94             severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
95             nodeType: null
96         }));
97
98     return { problems, unusedDisableDirectives };
99 }
100
101 /**
102  * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
103  * of reported problems, determines which problems should be reported.
104  * @param {Object} options Information about directives and problems
105  * @param {{
106  *      type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
107  *      ruleId: (string|null),
108  *      line: number,
109  *      column: number
110  * }} options.directives Directive comments found in the file, with one-based columns.
111  * Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
112  * comment for two different rules is represented as two directives).
113  * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
114  * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
115  * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
116  * @returns {{ruleId: (string|null), line: number, column: number}[]}
117  * A list of reported problems that were not disabled by the directive comments.
118  */
119 module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off" }) => {
120     const blockDirectives = directives
121         .filter(directive => directive.type === "disable" || directive.type === "enable")
122         .map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
123         .sort(compareLocations);
124
125     /**
126      * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
127      * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
128      * @param {any[]} array The array to process
129      * @param {Function} fn The function to use
130      * @returns {any[]} The result array
131      */
132     function flatMap(array, fn) {
133         const mapped = array.map(fn);
134         const flattened = [].concat(...mapped);
135
136         return flattened;
137     }
138
139     const lineDirectives = flatMap(directives, directive => {
140         switch (directive.type) {
141             case "disable":
142             case "enable":
143                 return [];
144
145             case "disable-line":
146                 return [
147                     { type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
148                     { type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
149                 ];
150
151             case "disable-next-line":
152                 return [
153                     { type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
154                     { type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
155                 ];
156
157             default:
158                 throw new TypeError(`Unrecognized directive type '${directive.type}'`);
159         }
160     }).sort(compareLocations);
161
162     const blockDirectivesResult = applyDirectives({
163         problems,
164         directives: blockDirectives,
165         reportUnusedDisableDirectives
166     });
167     const lineDirectivesResult = applyDirectives({
168         problems: blockDirectivesResult.problems,
169         directives: lineDirectives,
170         reportUnusedDisableDirectives
171     });
172
173     return reportUnusedDisableDirectives !== "off"
174         ? lineDirectivesResult.problems
175             .concat(blockDirectivesResult.unusedDisableDirectives)
176             .concat(lineDirectivesResult.unusedDisableDirectives)
177             .sort(compareLocations)
178         : lineDirectivesResult.problems;
179 };