massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / linter / node-event-generator.js
1 /**
2  * @fileoverview The event generator for AST nodes.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const esquery = require("esquery");
13
14 //------------------------------------------------------------------------------
15 // Typedefs
16 //------------------------------------------------------------------------------
17
18 /**
19  * An object describing an AST selector
20  * @typedef {Object} ASTSelector
21  * @property {string} rawSelector The string that was parsed into this selector
22  * @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering
23  * @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
24  * @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match,
25  * or `null` if all node types could cause a match
26  * @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector
27  * @property {number} identifierCount The total number of identifier queries in this selector
28  */
29
30 //------------------------------------------------------------------------------
31 // Helpers
32 //------------------------------------------------------------------------------
33
34 /**
35  * Computes the union of one or more arrays
36  * @param {...any[]} arrays One or more arrays to union
37  * @returns {any[]} The union of the input arrays
38  */
39 function union(...arrays) {
40
41     // TODO(stephenwade): Replace this with arrays.flat() when we drop support for Node v10
42     return [...new Set([].concat(...arrays))];
43 }
44
45 /**
46  * Computes the intersection of one or more arrays
47  * @param {...any[]} arrays One or more arrays to intersect
48  * @returns {any[]} The intersection of the input arrays
49  */
50 function intersection(...arrays) {
51     if (arrays.length === 0) {
52         return [];
53     }
54
55     let result = [...new Set(arrays[0])];
56
57     for (const array of arrays.slice(1)) {
58         result = result.filter(x => array.includes(x));
59     }
60     return result;
61 }
62
63 /**
64  * Gets the possible types of a selector
65  * @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
66  * @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it
67  */
68 function getPossibleTypes(parsedSelector) {
69     switch (parsedSelector.type) {
70         case "identifier":
71             return [parsedSelector.value];
72
73         case "matches": {
74             const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
75
76             if (typesForComponents.every(Boolean)) {
77                 return union(...typesForComponents);
78             }
79             return null;
80         }
81
82         case "compound": {
83             const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent);
84
85             // If all of the components could match any type, then the compound could also match any type.
86             if (!typesForComponents.length) {
87                 return null;
88             }
89
90             /*
91              * If at least one of the components could only match a particular type, the compound could only match
92              * the intersection of those types.
93              */
94             return intersection(...typesForComponents);
95         }
96
97         case "child":
98         case "descendant":
99         case "sibling":
100         case "adjacent":
101             return getPossibleTypes(parsedSelector.right);
102
103         default:
104             return null;
105
106     }
107 }
108
109 /**
110  * Counts the number of class, pseudo-class, and attribute queries in this selector
111  * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
112  * @returns {number} The number of class, pseudo-class, and attribute queries in this selector
113  */
114 function countClassAttributes(parsedSelector) {
115     switch (parsedSelector.type) {
116         case "child":
117         case "descendant":
118         case "sibling":
119         case "adjacent":
120             return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right);
121
122         case "compound":
123         case "not":
124         case "matches":
125             return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0);
126
127         case "attribute":
128         case "field":
129         case "nth-child":
130         case "nth-last-child":
131             return 1;
132
133         default:
134             return 0;
135     }
136 }
137
138 /**
139  * Counts the number of identifier queries in this selector
140  * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
141  * @returns {number} The number of identifier queries
142  */
143 function countIdentifiers(parsedSelector) {
144     switch (parsedSelector.type) {
145         case "child":
146         case "descendant":
147         case "sibling":
148         case "adjacent":
149             return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right);
150
151         case "compound":
152         case "not":
153         case "matches":
154             return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0);
155
156         case "identifier":
157             return 1;
158
159         default:
160             return 0;
161     }
162 }
163
164 /**
165  * Compares the specificity of two selector objects, with CSS-like rules.
166  * @param {ASTSelector} selectorA An AST selector descriptor
167  * @param {ASTSelector} selectorB Another AST selector descriptor
168  * @returns {number}
169  * a value less than 0 if selectorA is less specific than selectorB
170  * a value greater than 0 if selectorA is more specific than selectorB
171  * a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically
172  * a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically
173  */
174 function compareSpecificity(selectorA, selectorB) {
175     return selectorA.attributeCount - selectorB.attributeCount ||
176         selectorA.identifierCount - selectorB.identifierCount ||
177         (selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1);
178 }
179
180 /**
181  * Parses a raw selector string, and throws a useful error if parsing fails.
182  * @param {string} rawSelector A raw AST selector
183  * @returns {Object} An object (from esquery) describing the matching behavior of this selector
184  * @throws {Error} An error if the selector is invalid
185  */
186 function tryParseSelector(rawSelector) {
187     try {
188         return esquery.parse(rawSelector.replace(/:exit$/u, ""));
189     } catch (err) {
190         if (err.location && err.location.start && typeof err.location.start.offset === "number") {
191             throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`);
192         }
193         throw err;
194     }
195 }
196
197 const selectorCache = new Map();
198
199 /**
200  * Parses a raw selector string, and returns the parsed selector along with specificity and type information.
201  * @param {string} rawSelector A raw AST selector
202  * @returns {ASTSelector} A selector descriptor
203  */
204 function parseSelector(rawSelector) {
205     if (selectorCache.has(rawSelector)) {
206         return selectorCache.get(rawSelector);
207     }
208
209     const parsedSelector = tryParseSelector(rawSelector);
210
211     const result = {
212         rawSelector,
213         isExit: rawSelector.endsWith(":exit"),
214         parsedSelector,
215         listenerTypes: getPossibleTypes(parsedSelector),
216         attributeCount: countClassAttributes(parsedSelector),
217         identifierCount: countIdentifiers(parsedSelector)
218     };
219
220     selectorCache.set(rawSelector, result);
221     return result;
222 }
223
224 //------------------------------------------------------------------------------
225 // Public Interface
226 //------------------------------------------------------------------------------
227
228 /**
229  * The event generator for AST nodes.
230  * This implements below interface.
231  *
232  * ```ts
233  * interface EventGenerator {
234  *     emitter: SafeEmitter;
235  *     enterNode(node: ASTNode): void;
236  *     leaveNode(node: ASTNode): void;
237  * }
238  * ```
239  */
240 class NodeEventGenerator {
241
242     // eslint-disable-next-line jsdoc/require-description
243     /**
244      * @param {SafeEmitter} emitter
245      * An SafeEmitter which is the destination of events. This emitter must already
246      * have registered listeners for all of the events that it needs to listen for.
247      * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.)
248      * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes.
249      * @returns {NodeEventGenerator} new instance
250      */
251     constructor(emitter, esqueryOptions) {
252         this.emitter = emitter;
253         this.esqueryOptions = esqueryOptions;
254         this.currentAncestry = [];
255         this.enterSelectorsByNodeType = new Map();
256         this.exitSelectorsByNodeType = new Map();
257         this.anyTypeEnterSelectors = [];
258         this.anyTypeExitSelectors = [];
259
260         emitter.eventNames().forEach(rawSelector => {
261             const selector = parseSelector(rawSelector);
262
263             if (selector.listenerTypes) {
264                 const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType;
265
266                 selector.listenerTypes.forEach(nodeType => {
267                     if (!typeMap.has(nodeType)) {
268                         typeMap.set(nodeType, []);
269                     }
270                     typeMap.get(nodeType).push(selector);
271                 });
272                 return;
273             }
274             const selectors = selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
275
276             selectors.push(selector);
277         });
278
279         this.anyTypeEnterSelectors.sort(compareSpecificity);
280         this.anyTypeExitSelectors.sort(compareSpecificity);
281         this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
282         this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
283     }
284
285     /**
286      * Checks a selector against a node, and emits it if it matches
287      * @param {ASTNode} node The node to check
288      * @param {ASTSelector} selector An AST selector descriptor
289      * @returns {void}
290      */
291     applySelector(node, selector) {
292         if (esquery.matches(node, selector.parsedSelector, this.currentAncestry, this.esqueryOptions)) {
293             this.emitter.emit(selector.rawSelector, node);
294         }
295     }
296
297     /**
298      * Applies all appropriate selectors to a node, in specificity order
299      * @param {ASTNode} node The node to check
300      * @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited
301      * @returns {void}
302      */
303     applySelectors(node, isExit) {
304         const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || [];
305         const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
306
307         /*
308          * selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor.
309          * Iterate through each of them, applying selectors in the right order.
310          */
311         let selectorsByTypeIndex = 0;
312         let anyTypeSelectorsIndex = 0;
313
314         while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) {
315             if (
316                 selectorsByTypeIndex >= selectorsByNodeType.length ||
317                 anyTypeSelectorsIndex < anyTypeSelectors.length &&
318                 compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0
319             ) {
320                 this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]);
321             } else {
322                 this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]);
323             }
324         }
325     }
326
327     /**
328      * Emits an event of entering AST node.
329      * @param {ASTNode} node A node which was entered.
330      * @returns {void}
331      */
332     enterNode(node) {
333         if (node.parent) {
334             this.currentAncestry.unshift(node.parent);
335         }
336         this.applySelectors(node, false);
337     }
338
339     /**
340      * Emits an event of leaving AST node.
341      * @param {ASTNode} node A node which was left.
342      * @returns {void}
343      */
344     leaveNode(node) {
345         this.applySelectors(node, true);
346         this.currentAncestry.shift();
347     }
348 }
349
350 module.exports = NodeEventGenerator;