update socials section
This commit is contained in:
		
							
								
								
									
										465
									
								
								node_modules/eslint/lib/linter/apply-disable-directives.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								node_modules/eslint/lib/linter/apply-disable-directives.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,465 @@ | ||||
| /** | ||||
|  * @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments | ||||
|  * @author Teddy Katz | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Typedefs | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Module Definition | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const escapeRegExp = require("escape-string-regexp"); | ||||
|  | ||||
| /** | ||||
|  * 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; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Groups a set of directives into sub-arrays by their parent comment. | ||||
|  * @param {Iterable<Directive>} directives Unused directives to be removed. | ||||
|  * @returns {Directive[][]} Directives grouped by their parent comment. | ||||
|  */ | ||||
| function groupByParentComment(directives) { | ||||
|     const groups = new Map(); | ||||
|  | ||||
|     for (const directive of directives) { | ||||
|         const { unprocessedDirective: { parentComment } } = directive; | ||||
|  | ||||
|         if (groups.has(parentComment)) { | ||||
|             groups.get(parentComment).push(directive); | ||||
|         } else { | ||||
|             groups.set(parentComment, [directive]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return [...groups.values()]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates removal details for a set of directives within the same comment. | ||||
|  * @param {Directive[]} directives Unused directives to be removed. | ||||
|  * @param {Token} commentToken The backing Comment token. | ||||
|  * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. | ||||
|  */ | ||||
| function createIndividualDirectivesRemoval(directives, commentToken) { | ||||
|  | ||||
|     /* | ||||
|      * `commentToken.value` starts right after `//` or `/*`. | ||||
|      * All calculated offsets will be relative to this index. | ||||
|      */ | ||||
|     const commentValueStart = commentToken.range[0] + "//".length; | ||||
|  | ||||
|     // Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`) | ||||
|     const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length; | ||||
|  | ||||
|     /* | ||||
|      * Get the list text without any surrounding whitespace. In order to preserve the original | ||||
|      * formatting, we don't want to change that whitespace. | ||||
|      * | ||||
|      *     // eslint-disable-line rule-one , rule-two , rule-three -- comment | ||||
|      *                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|      */ | ||||
|     const listText = commentToken.value | ||||
|         .slice(listStartOffset) // remove directive name and all whitespace before the list | ||||
|         .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists | ||||
|         .trimEnd(); // remove all whitespace after the list | ||||
|  | ||||
|     /* | ||||
|      * We can assume that `listText` contains multiple elements. | ||||
|      * Otherwise, this function wouldn't be called - if there is | ||||
|      * only one rule in the list, then the whole comment must be removed. | ||||
|      */ | ||||
|  | ||||
|     return directives.map(directive => { | ||||
|         const { ruleId } = directive; | ||||
|  | ||||
|         const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u"); | ||||
|         const match = regex.exec(listText); | ||||
|         const matchedText = match[0]; | ||||
|         const matchStartOffset = listStartOffset + match.index; | ||||
|         const matchEndOffset = matchStartOffset + matchedText.length; | ||||
|  | ||||
|         const firstIndexOfComma = matchedText.indexOf(","); | ||||
|         const lastIndexOfComma = matchedText.lastIndexOf(","); | ||||
|  | ||||
|         let removalStartOffset, removalEndOffset; | ||||
|  | ||||
|         if (firstIndexOfComma !== lastIndexOfComma) { | ||||
|  | ||||
|             /* | ||||
|              * Since there are two commas, this must one of the elements in the middle of the list. | ||||
|              * Matched range starts where the previous rule name ends, and ends where the next rule name starts. | ||||
|              * | ||||
|              *     // eslint-disable-line rule-one , rule-two , rule-three -- comment | ||||
|              *                                    ^^^^^^^^^^^^^^ | ||||
|              * | ||||
|              * We want to remove only the content between the two commas, and also one of the commas. | ||||
|              * | ||||
|              *     // eslint-disable-line rule-one , rule-two , rule-three -- comment | ||||
|              *                                     ^^^^^^^^^^^ | ||||
|              */ | ||||
|             removalStartOffset = matchStartOffset + firstIndexOfComma; | ||||
|             removalEndOffset = matchStartOffset + lastIndexOfComma; | ||||
|  | ||||
|         } else { | ||||
|  | ||||
|             /* | ||||
|              * This is either the first element or the last element. | ||||
|              * | ||||
|              * If this is the first element, matched range starts where the first rule name starts | ||||
|              * and ends where the second rule name starts. This is exactly the range we want | ||||
|              * to remove so that the second rule name will start where the first one was starting | ||||
|              * and thus preserve the original formatting. | ||||
|              * | ||||
|              *     // eslint-disable-line rule-one , rule-two , rule-three -- comment | ||||
|              *                            ^^^^^^^^^^^ | ||||
|              * | ||||
|              * Similarly, if this is the last element, we've already matched the range we want to | ||||
|              * remove. The previous rule name will end where the last one was ending, relative | ||||
|              * to the content on the right side. | ||||
|              * | ||||
|              *     // eslint-disable-line rule-one , rule-two , rule-three -- comment | ||||
|              *                                               ^^^^^^^^^^^^^ | ||||
|              */ | ||||
|             removalStartOffset = matchStartOffset; | ||||
|             removalEndOffset = matchEndOffset; | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             description: `'${ruleId}'`, | ||||
|             fix: { | ||||
|                 range: [ | ||||
|                     commentValueStart + removalStartOffset, | ||||
|                     commentValueStart + removalEndOffset | ||||
|                 ], | ||||
|                 text: "" | ||||
|             }, | ||||
|             unprocessedDirective: directive.unprocessedDirective | ||||
|         }; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates a description of deleting an entire unused disable comment. | ||||
|  * @param {Directive[]} directives Unused directives to be removed. | ||||
|  * @param {Token} commentToken The backing Comment token. | ||||
|  * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output Problem. | ||||
|  */ | ||||
| function createCommentRemoval(directives, commentToken) { | ||||
|     const { range } = commentToken; | ||||
|     const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`); | ||||
|  | ||||
|     return { | ||||
|         description: ruleIds.length <= 2 | ||||
|             ? ruleIds.join(" or ") | ||||
|             : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds[ruleIds.length - 1]}`, | ||||
|         fix: { | ||||
|             range, | ||||
|             text: " " | ||||
|         }, | ||||
|         unprocessedDirective: directives[0].unprocessedDirective | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Parses details from directives to create output Problems. | ||||
|  * @param {Iterable<Directive>} allDirectives Unused directives to be removed. | ||||
|  * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. | ||||
|  */ | ||||
| function processUnusedDirectives(allDirectives) { | ||||
|     const directiveGroups = groupByParentComment(allDirectives); | ||||
|  | ||||
|     return directiveGroups.flatMap( | ||||
|         directives => { | ||||
|             const { parentComment } = directives[0].unprocessedDirective; | ||||
|             const remainingRuleIds = new Set(parentComment.ruleIds); | ||||
|  | ||||
|             for (const directive of directives) { | ||||
|                 remainingRuleIds.delete(directive.ruleId); | ||||
|             } | ||||
|  | ||||
|             return remainingRuleIds.size | ||||
|                 ? createIndividualDirectivesRemoval(directives, parentComment.commentToken) | ||||
|                 : [createCommentRemoval(directives, parentComment.commentToken)]; | ||||
|         } | ||||
|     ); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Collect eslint-enable comments that are removing suppressions by eslint-disable comments. | ||||
|  * @param {Directive[]} directives The directives to check. | ||||
|  * @returns {Set<Directive>} The used eslint-enable comments | ||||
|  */ | ||||
| function collectUsedEnableDirectives(directives) { | ||||
|  | ||||
|     /** | ||||
|      * A Map of `eslint-enable` keyed by ruleIds that may be marked as used. | ||||
|      * If `eslint-enable` does not have a ruleId, the key will be `null`. | ||||
|      * @type {Map<string|null, Directive>} | ||||
|      */ | ||||
|     const enabledRules = new Map(); | ||||
|  | ||||
|     /** | ||||
|      * A Set of `eslint-enable` marked as used. | ||||
|      * It is also the return value of `collectUsedEnableDirectives` function. | ||||
|      * @type {Set<Directive>} | ||||
|      */ | ||||
|     const usedEnableDirectives = new Set(); | ||||
|  | ||||
|     /* | ||||
|      * Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`, | ||||
|      * and if so, stores the `eslint-enable` in `usedEnableDirectives`. | ||||
|      */ | ||||
|     for (let index = directives.length - 1; index >= 0; index--) { | ||||
|         const directive = directives[index]; | ||||
|  | ||||
|         if (directive.type === "disable") { | ||||
|             if (enabledRules.size === 0) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (directive.ruleId === null) { | ||||
|  | ||||
|                 // If encounter `eslint-disable` without ruleId, | ||||
|                 // mark all `eslint-enable` currently held in enabledRules as used. | ||||
|                 // e.g. | ||||
|                 //    /* eslint-disable */ <- current directive | ||||
|                 //    /* eslint-enable rule-id1 */ <- used | ||||
|                 //    /* eslint-enable rule-id2 */ <- used | ||||
|                 //    /* eslint-enable */ <- used | ||||
|                 for (const enableDirective of enabledRules.values()) { | ||||
|                     usedEnableDirectives.add(enableDirective); | ||||
|                 } | ||||
|                 enabledRules.clear(); | ||||
|             } else { | ||||
|                 const enableDirective = enabledRules.get(directive.ruleId); | ||||
|  | ||||
|                 if (enableDirective) { | ||||
|  | ||||
|                     // If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules, | ||||
|                     // mark `eslint-enable` with ruleId as used. | ||||
|                     // e.g. | ||||
|                     //    /* eslint-disable rule-id */ <- current directive | ||||
|                     //    /* eslint-enable rule-id */ <- used | ||||
|                     usedEnableDirectives.add(enableDirective); | ||||
|                 } else { | ||||
|                     const enabledDirectiveWithoutRuleId = enabledRules.get(null); | ||||
|  | ||||
|                     if (enabledDirectiveWithoutRuleId) { | ||||
|  | ||||
|                         // If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules, | ||||
|                         // mark `eslint-enable` without ruleId as used. | ||||
|                         // e.g. | ||||
|                         //    /* eslint-disable rule-id */ <- current directive | ||||
|                         //    /* eslint-enable */ <- used | ||||
|                         usedEnableDirectives.add(enabledDirectiveWithoutRuleId); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else if (directive.type === "enable") { | ||||
|             if (directive.ruleId === null) { | ||||
|  | ||||
|                 // If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused. | ||||
|                 // So clear enabledRules. | ||||
|                 // e.g. | ||||
|                 //    /* eslint-enable */ <- current directive | ||||
|                 //    /* eslint-enable rule-id *// <- unused | ||||
|                 //    /* eslint-enable */ <- unused | ||||
|                 enabledRules.clear(); | ||||
|                 enabledRules.set(null, directive); | ||||
|             } else { | ||||
|                 enabledRules.set(directive.ruleId, directive); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return usedEnableDirectives; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * This is the same as the exported function, except that it | ||||
|  * doesn't handle disable-line and disable-next-line directives, and it always reports unused | ||||
|  * disable directives. | ||||
|  * @param {Object} options options for applying directives. This is the same as the options | ||||
|  * for the exported function, except that `reportUnusedDisableDirectives` is not supported | ||||
|  * (this function always reports unused disable directives). | ||||
|  * @returns {{problems: LintMessage[], unusedDirectives: LintMessage[]}} An object with a list | ||||
|  * of problems (including suppressed ones) and unused eslint-disable directives | ||||
|  */ | ||||
| function applyDirectives(options) { | ||||
|     const problems = []; | ||||
|     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++]; | ||||
|  | ||||
|             if (directive.ruleId === null || directive.ruleId === problem.ruleId) { | ||||
|                 switch (directive.type) { | ||||
|                     case "disable": | ||||
|                         disableDirectivesForProblem.push(directive); | ||||
|                         break; | ||||
|  | ||||
|                     case "enable": | ||||
|                         disableDirectivesForProblem = []; | ||||
|                         break; | ||||
|  | ||||
|                     // no default | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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 | ||||
|         .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive)); | ||||
|  | ||||
|  | ||||
|     const unusedEnableDirectivesToReport = new Set( | ||||
|         options.directives.filter(directive => directive.unprocessedDirective.type === "enable") | ||||
|     ); | ||||
|  | ||||
|     /* | ||||
|      * If directives has the eslint-enable directive, | ||||
|      * check whether the eslint-enable comment is used. | ||||
|      */ | ||||
|     if (unusedEnableDirectivesToReport.size > 0) { | ||||
|         for (const directive of collectUsedEnableDirectives(options.directives)) { | ||||
|             unusedEnableDirectivesToReport.delete(directive); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const processed = processUnusedDirectives(unusedDisableDirectivesToReport) | ||||
|         .concat(processUnusedDirectives(unusedEnableDirectivesToReport)); | ||||
|  | ||||
|     const unusedDirectives = processed | ||||
|         .map(({ description, fix, unprocessedDirective }) => { | ||||
|             const { parentComment, type, line, column } = unprocessedDirective; | ||||
|  | ||||
|             let message; | ||||
|  | ||||
|             if (type === "enable") { | ||||
|                 message = description | ||||
|                     ? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).` | ||||
|                     : "Unused eslint-enable directive (no matching eslint-disable directives were found)."; | ||||
|             } else { | ||||
|                 message = description | ||||
|                     ? `Unused eslint-disable directive (no problems were reported from ${description}).` | ||||
|                     : "Unused eslint-disable directive (no problems were reported)."; | ||||
|             } | ||||
|             return { | ||||
|                 ruleId: null, | ||||
|                 message, | ||||
|                 line: type === "disable-next-line" ? parentComment.commentToken.loc.start.line : line, | ||||
|                 column: type === "disable-next-line" ? parentComment.commentToken.loc.start.column + 1 : column, | ||||
|                 severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2, | ||||
|                 nodeType: null, | ||||
|                 ...options.disableFixes ? {} : { fix } | ||||
|             }; | ||||
|         }); | ||||
|  | ||||
|     return { problems, unusedDirectives }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list | ||||
|  * 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, | ||||
|  *      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). | ||||
|  * @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. | ||||
|  * @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, 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 | ||||
|         .filter(directive => directive.type === "disable" || directive.type === "enable") | ||||
|         .map(directive => Object.assign({}, directive, { unprocessedDirective: directive })) | ||||
|         .sort(compareLocations); | ||||
|  | ||||
|     const lineDirectives = directives.flatMap(directive => { | ||||
|         switch (directive.type) { | ||||
|             case "disable": | ||||
|             case "enable": | ||||
|                 return []; | ||||
|  | ||||
|             case "disable-line": | ||||
|                 return [ | ||||
|                     { type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive }, | ||||
|                     { type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive } | ||||
|                 ]; | ||||
|  | ||||
|             case "disable-next-line": | ||||
|                 return [ | ||||
|                     { type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive }, | ||||
|                     { type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive } | ||||
|                 ]; | ||||
|  | ||||
|             default: | ||||
|                 throw new TypeError(`Unrecognized directive type '${directive.type}'`); | ||||
|         } | ||||
|     }).sort(compareLocations); | ||||
|  | ||||
|     const blockDirectivesResult = applyDirectives({ | ||||
|         problems, | ||||
|         directives: blockDirectives, | ||||
|         disableFixes, | ||||
|         reportUnusedDisableDirectives | ||||
|     }); | ||||
|     const lineDirectivesResult = applyDirectives({ | ||||
|         problems: blockDirectivesResult.problems, | ||||
|         directives: lineDirectives, | ||||
|         disableFixes, | ||||
|         reportUnusedDisableDirectives | ||||
|     }); | ||||
|  | ||||
|     return reportUnusedDisableDirectives !== "off" | ||||
|         ? lineDirectivesResult.problems | ||||
|             .concat(blockDirectivesResult.unusedDirectives) | ||||
|             .concat(lineDirectivesResult.unusedDirectives) | ||||
|             .sort(compareLocations) | ||||
|         : lineDirectivesResult.problems; | ||||
| }; | ||||
							
								
								
									
										852
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										852
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,852 @@ | ||||
| /** | ||||
|  * @fileoverview A class of the code path analyzer. | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const assert = require("assert"), | ||||
|     { breakableTypePattern } = require("../../shared/ast-utils"), | ||||
|     CodePath = require("./code-path"), | ||||
|     CodePathSegment = require("./code-path-segment"), | ||||
|     IdGenerator = require("./id-generator"), | ||||
|     debug = require("./debug-helpers"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Checks whether or not a given node is a `case` node (not `default` node). | ||||
|  * @param {ASTNode} node A `SwitchCase` node to check. | ||||
|  * @returns {boolean} `true` if the node is a `case` node (not `default` node). | ||||
|  */ | ||||
| function isCaseNode(node) { | ||||
|     return Boolean(node.test); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks if a given node appears as the value of a PropertyDefinition node. | ||||
|  * @param {ASTNode} node THe node to check. | ||||
|  * @returns {boolean} `true` if the node is a PropertyDefinition value, | ||||
|  *      false if not. | ||||
|  */ | ||||
| function isPropertyDefinitionValue(node) { | ||||
|     const parent = node.parent; | ||||
|  | ||||
|     return parent && parent.type === "PropertyDefinition" && parent.value === node; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks whether the given logical operator is taken into account for the code | ||||
|  * path analysis. | ||||
|  * @param {string} operator The operator found in the LogicalExpression node | ||||
|  * @returns {boolean} `true` if the operator is "&&" or "||" or "??" | ||||
|  */ | ||||
| function isHandledLogicalOperator(operator) { | ||||
|     return operator === "&&" || operator === "||" || operator === "??"; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks whether the given assignment operator is a logical assignment operator. | ||||
|  * Logical assignments are taken into account for the code path analysis | ||||
|  * because of their short-circuiting semantics. | ||||
|  * @param {string} operator The operator found in the AssignmentExpression node | ||||
|  * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??=" | ||||
|  */ | ||||
| function isLogicalAssignmentOperator(operator) { | ||||
|     return operator === "&&=" || operator === "||=" || operator === "??="; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Gets the label if the parent node of a given node is a LabeledStatement. | ||||
|  * @param {ASTNode} node A node to get. | ||||
|  * @returns {string|null} The label or `null`. | ||||
|  */ | ||||
| function getLabel(node) { | ||||
|     if (node.parent.type === "LabeledStatement") { | ||||
|         return node.parent.label.name; | ||||
|     } | ||||
|     return null; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks whether or not a given logical expression node goes different path | ||||
|  * between the `true` case and the `false` case. | ||||
|  * @param {ASTNode} node A node to check. | ||||
|  * @returns {boolean} `true` if the node is a test of a choice statement. | ||||
|  */ | ||||
| function isForkingByTrueOrFalse(node) { | ||||
|     const parent = node.parent; | ||||
|  | ||||
|     switch (parent.type) { | ||||
|         case "ConditionalExpression": | ||||
|         case "IfStatement": | ||||
|         case "WhileStatement": | ||||
|         case "DoWhileStatement": | ||||
|         case "ForStatement": | ||||
|             return parent.test === node; | ||||
|  | ||||
|         case "LogicalExpression": | ||||
|             return isHandledLogicalOperator(parent.operator); | ||||
|  | ||||
|         case "AssignmentExpression": | ||||
|             return isLogicalAssignmentOperator(parent.operator); | ||||
|  | ||||
|         default: | ||||
|             return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Gets the boolean value of a given literal node. | ||||
|  * | ||||
|  * This is used to detect infinity loops (e.g. `while (true) {}`). | ||||
|  * Statements preceded by an infinity loop are unreachable if the loop didn't | ||||
|  * have any `break` statement. | ||||
|  * @param {ASTNode} node A node to get. | ||||
|  * @returns {boolean|undefined} a boolean value if the node is a Literal node, | ||||
|  *   otherwise `undefined`. | ||||
|  */ | ||||
| function getBooleanValueIfSimpleConstant(node) { | ||||
|     if (node.type === "Literal") { | ||||
|         return Boolean(node.value); | ||||
|     } | ||||
|     return void 0; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks that a given identifier node is a reference or not. | ||||
|  * | ||||
|  * This is used to detect the first throwable node in a `try` block. | ||||
|  * @param {ASTNode} node An Identifier node to check. | ||||
|  * @returns {boolean} `true` if the node is a reference. | ||||
|  */ | ||||
| function isIdentifierReference(node) { | ||||
|     const parent = node.parent; | ||||
|  | ||||
|     switch (parent.type) { | ||||
|         case "LabeledStatement": | ||||
|         case "BreakStatement": | ||||
|         case "ContinueStatement": | ||||
|         case "ArrayPattern": | ||||
|         case "RestElement": | ||||
|         case "ImportSpecifier": | ||||
|         case "ImportDefaultSpecifier": | ||||
|         case "ImportNamespaceSpecifier": | ||||
|         case "CatchClause": | ||||
|             return false; | ||||
|  | ||||
|         case "FunctionDeclaration": | ||||
|         case "FunctionExpression": | ||||
|         case "ArrowFunctionExpression": | ||||
|         case "ClassDeclaration": | ||||
|         case "ClassExpression": | ||||
|         case "VariableDeclarator": | ||||
|             return parent.id !== node; | ||||
|  | ||||
|         case "Property": | ||||
|         case "PropertyDefinition": | ||||
|         case "MethodDefinition": | ||||
|             return ( | ||||
|                 parent.key !== node || | ||||
|                 parent.computed || | ||||
|                 parent.shorthand | ||||
|             ); | ||||
|  | ||||
|         case "AssignmentPattern": | ||||
|             return parent.key !== node; | ||||
|  | ||||
|         default: | ||||
|             return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Updates the current segment with the head segment. | ||||
|  * This is similar to local branches and tracking branches of git. | ||||
|  * | ||||
|  * To separate the current and the head is in order to not make useless segments. | ||||
|  * | ||||
|  * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd" | ||||
|  * events are fired. | ||||
|  * @param {CodePathAnalyzer} analyzer The instance. | ||||
|  * @param {ASTNode} node The current AST node. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function forwardCurrentToHead(analyzer, node) { | ||||
|     const codePath = analyzer.codePath; | ||||
|     const state = CodePath.getState(codePath); | ||||
|     const currentSegments = state.currentSegments; | ||||
|     const headSegments = state.headSegments; | ||||
|     const end = Math.max(currentSegments.length, headSegments.length); | ||||
|     let i, currentSegment, headSegment; | ||||
|  | ||||
|     // Fires leaving events. | ||||
|     for (i = 0; i < end; ++i) { | ||||
|         currentSegment = currentSegments[i]; | ||||
|         headSegment = headSegments[i]; | ||||
|  | ||||
|         if (currentSegment !== headSegment && currentSegment) { | ||||
|  | ||||
|             const eventName = currentSegment.reachable | ||||
|                 ? "onCodePathSegmentEnd" | ||||
|                 : "onUnreachableCodePathSegmentEnd"; | ||||
|  | ||||
|             debug.dump(`${eventName} ${currentSegment.id}`); | ||||
|  | ||||
|             analyzer.emitter.emit( | ||||
|                 eventName, | ||||
|                 currentSegment, | ||||
|                 node | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Update state. | ||||
|     state.currentSegments = headSegments; | ||||
|  | ||||
|     // Fires entering events. | ||||
|     for (i = 0; i < end; ++i) { | ||||
|         currentSegment = currentSegments[i]; | ||||
|         headSegment = headSegments[i]; | ||||
|  | ||||
|         if (currentSegment !== headSegment && headSegment) { | ||||
|  | ||||
|             const eventName = headSegment.reachable | ||||
|                 ? "onCodePathSegmentStart" | ||||
|                 : "onUnreachableCodePathSegmentStart"; | ||||
|  | ||||
|             debug.dump(`${eventName} ${headSegment.id}`); | ||||
|  | ||||
|             CodePathSegment.markUsed(headSegment); | ||||
|             analyzer.emitter.emit( | ||||
|                 eventName, | ||||
|                 headSegment, | ||||
|                 node | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Updates the current segment with empty. | ||||
|  * This is called at the last of functions or the program. | ||||
|  * @param {CodePathAnalyzer} analyzer The instance. | ||||
|  * @param {ASTNode} node The current AST node. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function leaveFromCurrentSegment(analyzer, node) { | ||||
|     const state = CodePath.getState(analyzer.codePath); | ||||
|     const currentSegments = state.currentSegments; | ||||
|  | ||||
|     for (let i = 0; i < currentSegments.length; ++i) { | ||||
|         const currentSegment = currentSegments[i]; | ||||
|         const eventName = currentSegment.reachable | ||||
|             ? "onCodePathSegmentEnd" | ||||
|             : "onUnreachableCodePathSegmentEnd"; | ||||
|  | ||||
|         debug.dump(`${eventName} ${currentSegment.id}`); | ||||
|  | ||||
|         analyzer.emitter.emit( | ||||
|             eventName, | ||||
|             currentSegment, | ||||
|             node | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     state.currentSegments = []; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Updates the code path due to the position of a given node in the parent node | ||||
|  * thereof. | ||||
|  * | ||||
|  * For example, if the node is `parent.consequent`, this creates a fork from the | ||||
|  * current path. | ||||
|  * @param {CodePathAnalyzer} analyzer The instance. | ||||
|  * @param {ASTNode} node The current AST node. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function preprocess(analyzer, node) { | ||||
|     const codePath = analyzer.codePath; | ||||
|     const state = CodePath.getState(codePath); | ||||
|     const parent = node.parent; | ||||
|  | ||||
|     switch (parent.type) { | ||||
|  | ||||
|         // The `arguments.length == 0` case is in `postprocess` function. | ||||
|         case "CallExpression": | ||||
|             if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) { | ||||
|                 state.makeOptionalRight(); | ||||
|             } | ||||
|             break; | ||||
|         case "MemberExpression": | ||||
|             if (parent.optional === true && parent.property === node) { | ||||
|                 state.makeOptionalRight(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "LogicalExpression": | ||||
|             if ( | ||||
|                 parent.right === node && | ||||
|                 isHandledLogicalOperator(parent.operator) | ||||
|             ) { | ||||
|                 state.makeLogicalRight(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "AssignmentExpression": | ||||
|             if ( | ||||
|                 parent.right === node && | ||||
|                 isLogicalAssignmentOperator(parent.operator) | ||||
|             ) { | ||||
|                 state.makeLogicalRight(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "ConditionalExpression": | ||||
|         case "IfStatement": | ||||
|  | ||||
|             /* | ||||
|              * Fork if this node is at `consequent`/`alternate`. | ||||
|              * `popForkContext()` exists at `IfStatement:exit` and | ||||
|              * `ConditionalExpression:exit`. | ||||
|              */ | ||||
|             if (parent.consequent === node) { | ||||
|                 state.makeIfConsequent(); | ||||
|             } else if (parent.alternate === node) { | ||||
|                 state.makeIfAlternate(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "SwitchCase": | ||||
|             if (parent.consequent[0] === node) { | ||||
|                 state.makeSwitchCaseBody(false, !parent.test); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "TryStatement": | ||||
|             if (parent.handler === node) { | ||||
|                 state.makeCatchBlock(); | ||||
|             } else if (parent.finalizer === node) { | ||||
|                 state.makeFinallyBlock(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "WhileStatement": | ||||
|             if (parent.test === node) { | ||||
|                 state.makeWhileTest(getBooleanValueIfSimpleConstant(node)); | ||||
|             } else { | ||||
|                 assert(parent.body === node); | ||||
|                 state.makeWhileBody(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "DoWhileStatement": | ||||
|             if (parent.body === node) { | ||||
|                 state.makeDoWhileBody(); | ||||
|             } else { | ||||
|                 assert(parent.test === node); | ||||
|                 state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node)); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "ForStatement": | ||||
|             if (parent.test === node) { | ||||
|                 state.makeForTest(getBooleanValueIfSimpleConstant(node)); | ||||
|             } else if (parent.update === node) { | ||||
|                 state.makeForUpdate(); | ||||
|             } else if (parent.body === node) { | ||||
|                 state.makeForBody(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "ForInStatement": | ||||
|         case "ForOfStatement": | ||||
|             if (parent.left === node) { | ||||
|                 state.makeForInOfLeft(); | ||||
|             } else if (parent.right === node) { | ||||
|                 state.makeForInOfRight(); | ||||
|             } else { | ||||
|                 assert(parent.body === node); | ||||
|                 state.makeForInOfBody(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "AssignmentPattern": | ||||
|  | ||||
|             /* | ||||
|              * Fork if this node is at `right`. | ||||
|              * `left` is executed always, so it uses the current path. | ||||
|              * `popForkContext()` exists at `AssignmentPattern:exit`. | ||||
|              */ | ||||
|             if (parent.right === node) { | ||||
|                 state.pushForkContext(); | ||||
|                 state.forkBypassPath(); | ||||
|                 state.forkPath(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Updates the code path due to the type of a given node in entering. | ||||
|  * @param {CodePathAnalyzer} analyzer The instance. | ||||
|  * @param {ASTNode} node The current AST node. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function processCodePathToEnter(analyzer, node) { | ||||
|     let codePath = analyzer.codePath; | ||||
|     let state = codePath && CodePath.getState(codePath); | ||||
|     const parent = node.parent; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new code path and trigger the onCodePathStart event | ||||
|      * based on the currently selected node. | ||||
|      * @param {string} origin The reason the code path was started. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     function startCodePath(origin) { | ||||
|         if (codePath) { | ||||
|  | ||||
|             // Emits onCodePathSegmentStart events if updated. | ||||
|             forwardCurrentToHead(analyzer, node); | ||||
|             debug.dumpState(node, state, false); | ||||
|         } | ||||
|  | ||||
|         // Create the code path of this scope. | ||||
|         codePath = analyzer.codePath = new CodePath({ | ||||
|             id: analyzer.idGenerator.next(), | ||||
|             origin, | ||||
|             upper: codePath, | ||||
|             onLooped: analyzer.onLooped | ||||
|         }); | ||||
|         state = CodePath.getState(codePath); | ||||
|  | ||||
|         // Emits onCodePathStart events. | ||||
|         debug.dump(`onCodePathStart ${codePath.id}`); | ||||
|         analyzer.emitter.emit("onCodePathStart", codePath, node); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Special case: The right side of class field initializer is considered | ||||
|      * to be its own function, so we need to start a new code path in this | ||||
|      * case. | ||||
|      */ | ||||
|     if (isPropertyDefinitionValue(node)) { | ||||
|         startCodePath("class-field-initializer"); | ||||
|  | ||||
|         /* | ||||
|          * Intentional fall through because `node` needs to also be | ||||
|          * processed by the code below. For example, if we have: | ||||
|          * | ||||
|          * class Foo { | ||||
|          *     a = () => {} | ||||
|          * } | ||||
|          * | ||||
|          * In this case, we also need start a second code path. | ||||
|          */ | ||||
|  | ||||
|     } | ||||
|  | ||||
|     switch (node.type) { | ||||
|         case "Program": | ||||
|             startCodePath("program"); | ||||
|             break; | ||||
|  | ||||
|         case "FunctionDeclaration": | ||||
|         case "FunctionExpression": | ||||
|         case "ArrowFunctionExpression": | ||||
|             startCodePath("function"); | ||||
|             break; | ||||
|  | ||||
|         case "StaticBlock": | ||||
|             startCodePath("class-static-block"); | ||||
|             break; | ||||
|  | ||||
|         case "ChainExpression": | ||||
|             state.pushChainContext(); | ||||
|             break; | ||||
|         case "CallExpression": | ||||
|             if (node.optional === true) { | ||||
|                 state.makeOptionalNode(); | ||||
|             } | ||||
|             break; | ||||
|         case "MemberExpression": | ||||
|             if (node.optional === true) { | ||||
|                 state.makeOptionalNode(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "LogicalExpression": | ||||
|             if (isHandledLogicalOperator(node.operator)) { | ||||
|                 state.pushChoiceContext( | ||||
|                     node.operator, | ||||
|                     isForkingByTrueOrFalse(node) | ||||
|                 ); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "AssignmentExpression": | ||||
|             if (isLogicalAssignmentOperator(node.operator)) { | ||||
|                 state.pushChoiceContext( | ||||
|                     node.operator.slice(0, -1), // removes `=` from the end | ||||
|                     isForkingByTrueOrFalse(node) | ||||
|                 ); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "ConditionalExpression": | ||||
|         case "IfStatement": | ||||
|             state.pushChoiceContext("test", false); | ||||
|             break; | ||||
|  | ||||
|         case "SwitchStatement": | ||||
|             state.pushSwitchContext( | ||||
|                 node.cases.some(isCaseNode), | ||||
|                 getLabel(node) | ||||
|             ); | ||||
|             break; | ||||
|  | ||||
|         case "TryStatement": | ||||
|             state.pushTryContext(Boolean(node.finalizer)); | ||||
|             break; | ||||
|  | ||||
|         case "SwitchCase": | ||||
|  | ||||
|             /* | ||||
|              * Fork if this node is after the 2st node in `cases`. | ||||
|              * It's similar to `else` blocks. | ||||
|              * The next `test` node is processed in this path. | ||||
|              */ | ||||
|             if (parent.discriminant !== node && parent.cases[0] !== node) { | ||||
|                 state.forkPath(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "WhileStatement": | ||||
|         case "DoWhileStatement": | ||||
|         case "ForStatement": | ||||
|         case "ForInStatement": | ||||
|         case "ForOfStatement": | ||||
|             state.pushLoopContext(node.type, getLabel(node)); | ||||
|             break; | ||||
|  | ||||
|         case "LabeledStatement": | ||||
|             if (!breakableTypePattern.test(node.body.type)) { | ||||
|                 state.pushBreakContext(false, node.label.name); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     // Emits onCodePathSegmentStart events if updated. | ||||
|     forwardCurrentToHead(analyzer, node); | ||||
|     debug.dumpState(node, state, false); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Updates the code path due to the type of a given node in leaving. | ||||
|  * @param {CodePathAnalyzer} analyzer The instance. | ||||
|  * @param {ASTNode} node The current AST node. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function processCodePathToExit(analyzer, node) { | ||||
|  | ||||
|     const codePath = analyzer.codePath; | ||||
|     const state = CodePath.getState(codePath); | ||||
|     let dontForward = false; | ||||
|  | ||||
|     switch (node.type) { | ||||
|         case "ChainExpression": | ||||
|             state.popChainContext(); | ||||
|             break; | ||||
|  | ||||
|         case "IfStatement": | ||||
|         case "ConditionalExpression": | ||||
|             state.popChoiceContext(); | ||||
|             break; | ||||
|  | ||||
|         case "LogicalExpression": | ||||
|             if (isHandledLogicalOperator(node.operator)) { | ||||
|                 state.popChoiceContext(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "AssignmentExpression": | ||||
|             if (isLogicalAssignmentOperator(node.operator)) { | ||||
|                 state.popChoiceContext(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "SwitchStatement": | ||||
|             state.popSwitchContext(); | ||||
|             break; | ||||
|  | ||||
|         case "SwitchCase": | ||||
|  | ||||
|             /* | ||||
|              * This is the same as the process at the 1st `consequent` node in | ||||
|              * `preprocess` function. | ||||
|              * Must do if this `consequent` is empty. | ||||
|              */ | ||||
|             if (node.consequent.length === 0) { | ||||
|                 state.makeSwitchCaseBody(true, !node.test); | ||||
|             } | ||||
|             if (state.forkContext.reachable) { | ||||
|                 dontForward = true; | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "TryStatement": | ||||
|             state.popTryContext(); | ||||
|             break; | ||||
|  | ||||
|         case "BreakStatement": | ||||
|             forwardCurrentToHead(analyzer, node); | ||||
|             state.makeBreak(node.label && node.label.name); | ||||
|             dontForward = true; | ||||
|             break; | ||||
|  | ||||
|         case "ContinueStatement": | ||||
|             forwardCurrentToHead(analyzer, node); | ||||
|             state.makeContinue(node.label && node.label.name); | ||||
|             dontForward = true; | ||||
|             break; | ||||
|  | ||||
|         case "ReturnStatement": | ||||
|             forwardCurrentToHead(analyzer, node); | ||||
|             state.makeReturn(); | ||||
|             dontForward = true; | ||||
|             break; | ||||
|  | ||||
|         case "ThrowStatement": | ||||
|             forwardCurrentToHead(analyzer, node); | ||||
|             state.makeThrow(); | ||||
|             dontForward = true; | ||||
|             break; | ||||
|  | ||||
|         case "Identifier": | ||||
|             if (isIdentifierReference(node)) { | ||||
|                 state.makeFirstThrowablePathInTryBlock(); | ||||
|                 dontForward = true; | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         case "CallExpression": | ||||
|         case "ImportExpression": | ||||
|         case "MemberExpression": | ||||
|         case "NewExpression": | ||||
|         case "YieldExpression": | ||||
|             state.makeFirstThrowablePathInTryBlock(); | ||||
|             break; | ||||
|  | ||||
|         case "WhileStatement": | ||||
|         case "DoWhileStatement": | ||||
|         case "ForStatement": | ||||
|         case "ForInStatement": | ||||
|         case "ForOfStatement": | ||||
|             state.popLoopContext(); | ||||
|             break; | ||||
|  | ||||
|         case "AssignmentPattern": | ||||
|             state.popForkContext(); | ||||
|             break; | ||||
|  | ||||
|         case "LabeledStatement": | ||||
|             if (!breakableTypePattern.test(node.body.type)) { | ||||
|                 state.popBreakContext(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     // Emits onCodePathSegmentStart events if updated. | ||||
|     if (!dontForward) { | ||||
|         forwardCurrentToHead(analyzer, node); | ||||
|     } | ||||
|     debug.dumpState(node, state, true); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Updates the code path to finalize the current code path. | ||||
|  * @param {CodePathAnalyzer} analyzer The instance. | ||||
|  * @param {ASTNode} node The current AST node. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function postprocess(analyzer, node) { | ||||
|  | ||||
|     /** | ||||
|      * Ends the code path for the current node. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     function endCodePath() { | ||||
|         let codePath = analyzer.codePath; | ||||
|  | ||||
|         // Mark the current path as the final node. | ||||
|         CodePath.getState(codePath).makeFinal(); | ||||
|  | ||||
|         // Emits onCodePathSegmentEnd event of the current segments. | ||||
|         leaveFromCurrentSegment(analyzer, node); | ||||
|  | ||||
|         // Emits onCodePathEnd event of this code path. | ||||
|         debug.dump(`onCodePathEnd ${codePath.id}`); | ||||
|         analyzer.emitter.emit("onCodePathEnd", codePath, node); | ||||
|         debug.dumpDot(codePath); | ||||
|  | ||||
|         codePath = analyzer.codePath = analyzer.codePath.upper; | ||||
|         if (codePath) { | ||||
|             debug.dumpState(node, CodePath.getState(codePath), true); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     switch (node.type) { | ||||
|         case "Program": | ||||
|         case "FunctionDeclaration": | ||||
|         case "FunctionExpression": | ||||
|         case "ArrowFunctionExpression": | ||||
|         case "StaticBlock": { | ||||
|             endCodePath(); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         // The `arguments.length >= 1` case is in `preprocess` function. | ||||
|         case "CallExpression": | ||||
|             if (node.optional === true && node.arguments.length === 0) { | ||||
|                 CodePath.getState(analyzer.codePath).makeOptionalRight(); | ||||
|             } | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Special case: The right side of class field initializer is considered | ||||
|      * to be its own function, so we need to end a code path in this | ||||
|      * case. | ||||
|      * | ||||
|      * We need to check after the other checks in order to close the | ||||
|      * code paths in the correct order for code like this: | ||||
|      * | ||||
|      * | ||||
|      * class Foo { | ||||
|      *     a = () => {} | ||||
|      * } | ||||
|      * | ||||
|      * In this case, The ArrowFunctionExpression code path is closed first | ||||
|      * and then we need to close the code path for the PropertyDefinition | ||||
|      * value. | ||||
|      */ | ||||
|     if (isPropertyDefinitionValue(node)) { | ||||
|         endCodePath(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * The class to analyze code paths. | ||||
|  * This class implements the EventGenerator interface. | ||||
|  */ | ||||
| class CodePathAnalyzer { | ||||
|  | ||||
|     /** | ||||
|      * @param {EventGenerator} eventGenerator An event generator to wrap. | ||||
|      */ | ||||
|     constructor(eventGenerator) { | ||||
|         this.original = eventGenerator; | ||||
|         this.emitter = eventGenerator.emitter; | ||||
|         this.codePath = null; | ||||
|         this.idGenerator = new IdGenerator("s"); | ||||
|         this.currentNode = null; | ||||
|         this.onLooped = this.onLooped.bind(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Does the process to enter a given AST node. | ||||
|      * This updates state of analysis and calls `enterNode` of the wrapped. | ||||
|      * @param {ASTNode} node A node which is entering. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     enterNode(node) { | ||||
|         this.currentNode = node; | ||||
|  | ||||
|         // Updates the code path due to node's position in its parent node. | ||||
|         if (node.parent) { | ||||
|             preprocess(this, node); | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * Updates the code path. | ||||
|          * And emits onCodePathStart/onCodePathSegmentStart events. | ||||
|          */ | ||||
|         processCodePathToEnter(this, node); | ||||
|  | ||||
|         // Emits node events. | ||||
|         this.original.enterNode(node); | ||||
|  | ||||
|         this.currentNode = null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Does the process to leave a given AST node. | ||||
|      * This updates state of analysis and calls `leaveNode` of the wrapped. | ||||
|      * @param {ASTNode} node A node which is leaving. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     leaveNode(node) { | ||||
|         this.currentNode = node; | ||||
|  | ||||
|         /* | ||||
|          * Updates the code path. | ||||
|          * And emits onCodePathStart/onCodePathSegmentStart events. | ||||
|          */ | ||||
|         processCodePathToExit(this, node); | ||||
|  | ||||
|         // Emits node events. | ||||
|         this.original.leaveNode(node); | ||||
|  | ||||
|         // Emits the last onCodePathStart/onCodePathSegmentStart events. | ||||
|         postprocess(this, node); | ||||
|  | ||||
|         this.currentNode = null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This is called on a code path looped. | ||||
|      * Then this raises a looped event. | ||||
|      * @param {CodePathSegment} fromSegment A segment of prev. | ||||
|      * @param {CodePathSegment} toSegment A segment of next. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     onLooped(fromSegment, toSegment) { | ||||
|         if (fromSegment.reachable && toSegment.reachable) { | ||||
|             debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`); | ||||
|             this.emitter.emit( | ||||
|                 "onCodePathSegmentLoop", | ||||
|                 fromSegment, | ||||
|                 toSegment, | ||||
|                 this.currentNode | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = CodePathAnalyzer; | ||||
							
								
								
									
										263
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,263 @@ | ||||
| /** | ||||
|  * @fileoverview The CodePathSegment class. | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const debug = require("./debug-helpers"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Checks whether or not a given segment is reachable. | ||||
|  * @param {CodePathSegment} segment A segment to check. | ||||
|  * @returns {boolean} `true` if the segment is reachable. | ||||
|  */ | ||||
| function isReachable(segment) { | ||||
|     return segment.reachable; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * A code path segment. | ||||
|  * | ||||
|  * Each segment is arranged in a series of linked lists (implemented by arrays) | ||||
|  * that keep track of the previous and next segments in a code path. In this way, | ||||
|  * you can navigate between all segments in any code path so long as you have a | ||||
|  * reference to any segment in that code path. | ||||
|  * | ||||
|  * When first created, the segment is in a detached state, meaning that it knows the | ||||
|  * segments that came before it but those segments don't know that this new segment | ||||
|  * follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it | ||||
|  * officially become part of the code path by updating the previous segments to know | ||||
|  * that this new segment follows. | ||||
|  */ | ||||
| class CodePathSegment { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance. | ||||
|      * @param {string} id An identifier. | ||||
|      * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. | ||||
|      *   This array includes unreachable segments. | ||||
|      * @param {boolean} reachable A flag which shows this is reachable. | ||||
|      */ | ||||
|     constructor(id, allPrevSegments, reachable) { | ||||
|  | ||||
|         /** | ||||
|          * The identifier of this code path. | ||||
|          * Rules use it to store additional information of each rule. | ||||
|          * @type {string} | ||||
|          */ | ||||
|         this.id = id; | ||||
|  | ||||
|         /** | ||||
|          * An array of the next reachable segments. | ||||
|          * @type {CodePathSegment[]} | ||||
|          */ | ||||
|         this.nextSegments = []; | ||||
|  | ||||
|         /** | ||||
|          * An array of the previous reachable segments. | ||||
|          * @type {CodePathSegment[]} | ||||
|          */ | ||||
|         this.prevSegments = allPrevSegments.filter(isReachable); | ||||
|  | ||||
|         /** | ||||
|          * An array of all next segments including reachable and unreachable. | ||||
|          * @type {CodePathSegment[]} | ||||
|          */ | ||||
|         this.allNextSegments = []; | ||||
|  | ||||
|         /** | ||||
|          * An array of all previous segments including reachable and unreachable. | ||||
|          * @type {CodePathSegment[]} | ||||
|          */ | ||||
|         this.allPrevSegments = allPrevSegments; | ||||
|  | ||||
|         /** | ||||
|          * A flag which shows this is reachable. | ||||
|          * @type {boolean} | ||||
|          */ | ||||
|         this.reachable = reachable; | ||||
|  | ||||
|         // Internal data. | ||||
|         Object.defineProperty(this, "internal", { | ||||
|             value: { | ||||
|  | ||||
|                 // determines if the segment has been attached to the code path | ||||
|                 used: false, | ||||
|  | ||||
|                 // array of previous segments coming from the end of a loop | ||||
|                 loopedPrevSegments: [] | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         /* c8 ignore start */ | ||||
|         if (debug.enabled) { | ||||
|             this.internal.nodes = []; | ||||
|         }/* c8 ignore stop */ | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks a given previous segment is coming from the end of a loop. | ||||
|      * @param {CodePathSegment} segment A previous segment to check. | ||||
|      * @returns {boolean} `true` if the segment is coming from the end of a loop. | ||||
|      */ | ||||
|     isLoopedPrevSegment(segment) { | ||||
|         return this.internal.loopedPrevSegments.includes(segment); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates the root segment. | ||||
|      * @param {string} id An identifier. | ||||
|      * @returns {CodePathSegment} The created segment. | ||||
|      */ | ||||
|     static newRoot(id) { | ||||
|         return new CodePathSegment(id, [], true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new segment and appends it after the given segments. | ||||
|      * @param {string} id An identifier. | ||||
|      * @param {CodePathSegment[]} allPrevSegments An array of the previous segments | ||||
|      *      to append to. | ||||
|      * @returns {CodePathSegment} The created segment. | ||||
|      */ | ||||
|     static newNext(id, allPrevSegments) { | ||||
|         return new CodePathSegment( | ||||
|             id, | ||||
|             CodePathSegment.flattenUnusedSegments(allPrevSegments), | ||||
|             allPrevSegments.some(isReachable) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates an unreachable segment and appends it after the given segments. | ||||
|      * @param {string} id An identifier. | ||||
|      * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. | ||||
|      * @returns {CodePathSegment} The created segment. | ||||
|      */ | ||||
|     static newUnreachable(id, allPrevSegments) { | ||||
|         const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false); | ||||
|  | ||||
|         /* | ||||
|          * In `if (a) return a; foo();` case, the unreachable segment preceded by | ||||
|          * the return statement is not used but must not be removed. | ||||
|          */ | ||||
|         CodePathSegment.markUsed(segment); | ||||
|  | ||||
|         return segment; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a segment that follows given segments. | ||||
|      * This factory method does not connect with `allPrevSegments`. | ||||
|      * But this inherits `reachable` flag. | ||||
|      * @param {string} id An identifier. | ||||
|      * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. | ||||
|      * @returns {CodePathSegment} The created segment. | ||||
|      */ | ||||
|     static newDisconnected(id, allPrevSegments) { | ||||
|         return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Marks a given segment as used. | ||||
|      * | ||||
|      * And this function registers the segment into the previous segments as a next. | ||||
|      * @param {CodePathSegment} segment A segment to mark. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     static markUsed(segment) { | ||||
|         if (segment.internal.used) { | ||||
|             return; | ||||
|         } | ||||
|         segment.internal.used = true; | ||||
|  | ||||
|         let i; | ||||
|  | ||||
|         if (segment.reachable) { | ||||
|  | ||||
|             /* | ||||
|              * If the segment is reachable, then it's officially part of the | ||||
|              * code path. This loops through all previous segments to update | ||||
|              * their list of next segments. Because the segment is reachable, | ||||
|              * it's added to both `nextSegments` and `allNextSegments`. | ||||
|              */ | ||||
|             for (i = 0; i < segment.allPrevSegments.length; ++i) { | ||||
|                 const prevSegment = segment.allPrevSegments[i]; | ||||
|  | ||||
|                 prevSegment.allNextSegments.push(segment); | ||||
|                 prevSegment.nextSegments.push(segment); | ||||
|             } | ||||
|         } else { | ||||
|  | ||||
|             /* | ||||
|              * If the segment is not reachable, then it's not officially part of the | ||||
|              * code path. This loops through all previous segments to update | ||||
|              * their list of next segments. Because the segment is not reachable, | ||||
|              * it's added only to `allNextSegments`. | ||||
|              */ | ||||
|             for (i = 0; i < segment.allPrevSegments.length; ++i) { | ||||
|                 segment.allPrevSegments[i].allNextSegments.push(segment); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Marks a previous segment as looped. | ||||
|      * @param {CodePathSegment} segment A segment. | ||||
|      * @param {CodePathSegment} prevSegment A previous segment to mark. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     static markPrevSegmentAsLooped(segment, prevSegment) { | ||||
|         segment.internal.loopedPrevSegments.push(prevSegment); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new array based on an array of segments. If any segment in the | ||||
|      * array is unused, then it is replaced by all of its previous segments. | ||||
|      * All used segments are returned as-is without replacement. | ||||
|      * @param {CodePathSegment[]} segments The array of segments to flatten. | ||||
|      * @returns {CodePathSegment[]} The flattened array. | ||||
|      */ | ||||
|     static flattenUnusedSegments(segments) { | ||||
|         const done = new Set(); | ||||
|  | ||||
|         for (let i = 0; i < segments.length; ++i) { | ||||
|             const segment = segments[i]; | ||||
|  | ||||
|             // Ignores duplicated. | ||||
|             if (done.has(segment)) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Use previous segments if unused. | ||||
|             if (!segment.internal.used) { | ||||
|                 for (let j = 0; j < segment.allPrevSegments.length; ++j) { | ||||
|                     const prevSegment = segment.allPrevSegments[j]; | ||||
|  | ||||
|                     if (!done.has(prevSegment)) { | ||||
|                         done.add(prevSegment); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 done.add(segment); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return [...done]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = CodePathSegment; | ||||
							
								
								
									
										2348
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2348
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										342
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								node_modules/eslint/lib/linter/code-path-analysis/code-path.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | ||||
| /** | ||||
|  * @fileoverview A class of the code path. | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const CodePathState = require("./code-path-state"); | ||||
| const IdGenerator = require("./id-generator"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * A code path. | ||||
|  */ | ||||
| class CodePath { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance. | ||||
|      * @param {Object} options Options for the function (see below). | ||||
|      * @param {string} options.id An identifier. | ||||
|      * @param {string} options.origin The type of code path origin. | ||||
|      * @param {CodePath|null} options.upper The code path of the upper function scope. | ||||
|      * @param {Function} options.onLooped A callback function to notify looping. | ||||
|      */ | ||||
|     constructor({ id, origin, upper, onLooped }) { | ||||
|  | ||||
|         /** | ||||
|          * The identifier of this code path. | ||||
|          * Rules use it to store additional information of each rule. | ||||
|          * @type {string} | ||||
|          */ | ||||
|         this.id = id; | ||||
|  | ||||
|         /** | ||||
|          * The reason that this code path was started. May be "program", | ||||
|          * "function", "class-field-initializer", or "class-static-block". | ||||
|          * @type {string} | ||||
|          */ | ||||
|         this.origin = origin; | ||||
|  | ||||
|         /** | ||||
|          * The code path of the upper function scope. | ||||
|          * @type {CodePath|null} | ||||
|          */ | ||||
|         this.upper = upper; | ||||
|  | ||||
|         /** | ||||
|          * The code paths of nested function scopes. | ||||
|          * @type {CodePath[]} | ||||
|          */ | ||||
|         this.childCodePaths = []; | ||||
|  | ||||
|         // Initializes internal state. | ||||
|         Object.defineProperty( | ||||
|             this, | ||||
|             "internal", | ||||
|             { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) } | ||||
|         ); | ||||
|  | ||||
|         // Adds this into `childCodePaths` of `upper`. | ||||
|         if (upper) { | ||||
|             upper.childCodePaths.push(this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the state of a given code path. | ||||
|      * @param {CodePath} codePath A code path to get. | ||||
|      * @returns {CodePathState} The state of the code path. | ||||
|      */ | ||||
|     static getState(codePath) { | ||||
|         return codePath.internal; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The initial code path segment. This is the segment that is at the head | ||||
|      * of the code path. | ||||
|      * This is a passthrough to the underlying `CodePathState`. | ||||
|      * @type {CodePathSegment} | ||||
|      */ | ||||
|     get initialSegment() { | ||||
|         return this.internal.initialSegment; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Final code path segments. These are the terminal (tail) segments in the | ||||
|      * code path, which is the combination of `returnedSegments` and `thrownSegments`. | ||||
|      * All segments in this array are reachable. | ||||
|      * This is a passthrough to the underlying `CodePathState`. | ||||
|      * @type {CodePathSegment[]} | ||||
|      */ | ||||
|     get finalSegments() { | ||||
|         return this.internal.finalSegments; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Final code path segments that represent normal completion of the code path. | ||||
|      * For functions, this means both explicit `return` statements and implicit returns, | ||||
|      * such as the last reachable segment in a function that does not have an | ||||
|      * explicit `return` as this implicitly returns `undefined`. For scripts, | ||||
|      * modules, class field initializers, and class static blocks, this means | ||||
|      * all lines of code have been executed. | ||||
|      * These segments are also present in `finalSegments`. | ||||
|      * This is a passthrough to the underlying `CodePathState`. | ||||
|      * @type {CodePathSegment[]} | ||||
|      */ | ||||
|     get returnedSegments() { | ||||
|         return this.internal.returnedForkContext; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Final code path segments that represent `throw` statements. | ||||
|      * This is a passthrough to the underlying `CodePathState`. | ||||
|      * These segments are also present in `finalSegments`. | ||||
|      * @type {CodePathSegment[]} | ||||
|      */ | ||||
|     get thrownSegments() { | ||||
|         return this.internal.thrownForkContext; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Tracks the traversal of the code path through each segment. This array | ||||
|      * starts empty and segments are added or removed as the code path is | ||||
|      * traversed. This array always ends up empty at the end of a code path | ||||
|      * traversal. The `CodePathState` uses this to track its progress through | ||||
|      * the code path. | ||||
|      * This is a passthrough to the underlying `CodePathState`. | ||||
|      * @type {CodePathSegment[]} | ||||
|      * @deprecated | ||||
|      */ | ||||
|     get currentSegments() { | ||||
|         return this.internal.currentSegments; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Traverses all segments in this code path. | ||||
|      * | ||||
|      *     codePath.traverseSegments((segment, controller) => { | ||||
|      *         // do something. | ||||
|      *     }); | ||||
|      * | ||||
|      * This method enumerates segments in order from the head. | ||||
|      * | ||||
|      * The `controller` argument has two methods: | ||||
|      * | ||||
|      * - `skip()` - skips the following segments in this branch | ||||
|      * - `break()` - skips all following segments in the traversal | ||||
|      * | ||||
|      * A note on the parameters: the `options` argument is optional. This means | ||||
|      * the first argument might be an options object or the callback function. | ||||
|      * @param {Object} [optionsOrCallback] Optional first and last segments to traverse. | ||||
|      * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse. | ||||
|      * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse. | ||||
|      * @param {Function} callback A callback function. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     traverseSegments(optionsOrCallback, callback) { | ||||
|  | ||||
|         // normalize the arguments into a callback and options | ||||
|         let resolvedOptions; | ||||
|         let resolvedCallback; | ||||
|  | ||||
|         if (typeof optionsOrCallback === "function") { | ||||
|             resolvedCallback = optionsOrCallback; | ||||
|             resolvedOptions = {}; | ||||
|         } else { | ||||
|             resolvedOptions = optionsOrCallback || {}; | ||||
|             resolvedCallback = callback; | ||||
|         } | ||||
|  | ||||
|         // determine where to start traversing from based on the options | ||||
|         const startSegment = resolvedOptions.first || this.internal.initialSegment; | ||||
|         const lastSegment = resolvedOptions.last; | ||||
|  | ||||
|         // set up initial location information | ||||
|         let record = null; | ||||
|         let index = 0; | ||||
|         let end = 0; | ||||
|         let segment = null; | ||||
|  | ||||
|         // segments that have already been visited during traversal | ||||
|         const visited = new Set(); | ||||
|  | ||||
|         // tracks the traversal steps | ||||
|         const stack = [[startSegment, 0]]; | ||||
|  | ||||
|         // tracks the last skipped segment during traversal | ||||
|         let skippedSegment = null; | ||||
|  | ||||
|         // indicates if we exited early from the traversal | ||||
|         let broken = false; | ||||
|  | ||||
|         /** | ||||
|          * Maintains traversal state. | ||||
|          */ | ||||
|         const controller = { | ||||
|  | ||||
|             /** | ||||
|              * Skip the following segments in this branch. | ||||
|              * @returns {void} | ||||
|              */ | ||||
|             skip() { | ||||
|                 if (stack.length <= 1) { | ||||
|                     broken = true; | ||||
|                 } else { | ||||
|                     skippedSegment = stack[stack.length - 2][0]; | ||||
|                 } | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * Stop traversal completely - do not traverse to any | ||||
|              * other segments. | ||||
|              * @returns {void} | ||||
|              */ | ||||
|             break() { | ||||
|                 broken = true; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks if a given previous segment has been visited. | ||||
|          * @param {CodePathSegment} prevSegment A previous segment to check. | ||||
|          * @returns {boolean} `true` if the segment has been visited. | ||||
|          */ | ||||
|         function isVisited(prevSegment) { | ||||
|             return ( | ||||
|                 visited.has(prevSegment) || | ||||
|                 segment.isLoopedPrevSegment(prevSegment) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // the traversal | ||||
|         while (stack.length > 0) { | ||||
|  | ||||
|             /* | ||||
|              * This isn't a pure stack. We use the top record all the time | ||||
|              * but don't always pop it off. The record is popped only if | ||||
|              * one of the following is true: | ||||
|              * | ||||
|              * 1) We have already visited the segment. | ||||
|              * 2) We have not visited *all* of the previous segments. | ||||
|              * 3) We have traversed past the available next segments. | ||||
|              * | ||||
|              * Otherwise, we just read the value and sometimes modify the | ||||
|              * record as we traverse. | ||||
|              */ | ||||
|             record = stack[stack.length - 1]; | ||||
|             segment = record[0]; | ||||
|             index = record[1]; | ||||
|  | ||||
|             if (index === 0) { | ||||
|  | ||||
|                 // Skip if this segment has been visited already. | ||||
|                 if (visited.has(segment)) { | ||||
|                     stack.pop(); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // Skip if all previous segments have not been visited. | ||||
|                 if (segment !== startSegment && | ||||
|                     segment.prevSegments.length > 0 && | ||||
|                     !segment.prevSegments.every(isVisited) | ||||
|                 ) { | ||||
|                     stack.pop(); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 // Reset the skipping flag if all branches have been skipped. | ||||
|                 if (skippedSegment && segment.prevSegments.includes(skippedSegment)) { | ||||
|                     skippedSegment = null; | ||||
|                 } | ||||
|                 visited.add(segment); | ||||
|  | ||||
|                 /* | ||||
|                  * If the most recent segment hasn't been skipped, then we call | ||||
|                  * the callback, passing in the segment and the controller. | ||||
|                  */ | ||||
|                 if (!skippedSegment) { | ||||
|                     resolvedCallback.call(this, segment, controller); | ||||
|  | ||||
|                     // exit if we're at the last segment | ||||
|                     if (segment === lastSegment) { | ||||
|                         controller.skip(); | ||||
|                     } | ||||
|  | ||||
|                     /* | ||||
|                      * If the previous statement was executed, or if the callback | ||||
|                      * called a method on the controller, we might need to exit the | ||||
|                      * loop, so check for that and break accordingly. | ||||
|                      */ | ||||
|                     if (broken) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Update the stack. | ||||
|             end = segment.nextSegments.length - 1; | ||||
|             if (index < end) { | ||||
|  | ||||
|                 /* | ||||
|                  * If we haven't yet visited all of the next segments, update | ||||
|                  * the current top record on the stack to the next index to visit | ||||
|                  * and then push a record for the current segment on top. | ||||
|                  * | ||||
|                  * Setting the current top record's index lets us know how many | ||||
|                  * times we've been here and ensures that the segment won't be | ||||
|                  * reprocessed (because we only process segments with an index | ||||
|                  * of 0). | ||||
|                  */ | ||||
|                 record[1] += 1; | ||||
|                 stack.push([segment.nextSegments[index], 0]); | ||||
|             } else if (index === end) { | ||||
|  | ||||
|                 /* | ||||
|                  * If we are at the last next segment, then reset the top record | ||||
|                  * in the stack to next segment and set its index to 0 so it will | ||||
|                  * be processed next. | ||||
|                  */ | ||||
|                 record[0] = segment.nextSegments[index]; | ||||
|                 record[1] = 0; | ||||
|             } else { | ||||
|  | ||||
|                 /* | ||||
|                  * If index > end, that means we have no more segments that need | ||||
|                  * processing. So, we pop that record off of the stack in order to | ||||
|                  * continue traversing at the next level up. | ||||
|                  */ | ||||
|                 stack.pop(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = CodePath; | ||||
							
								
								
									
										203
									
								
								node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| /** | ||||
|  * @fileoverview Helpers to debug for code path analysis. | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const debug = require("debug")("eslint:code-path"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Gets id of a given segment. | ||||
|  * @param {CodePathSegment} segment A segment to get. | ||||
|  * @returns {string} Id of the segment. | ||||
|  */ | ||||
| /* c8 ignore next */ | ||||
| function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring | ||||
|     return segment.id + (segment.reachable ? "" : "!"); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get string for the given node and operation. | ||||
|  * @param {ASTNode} node The node to convert. | ||||
|  * @param {"enter" | "exit" | undefined} label The operation label. | ||||
|  * @returns {string} The string representation. | ||||
|  */ | ||||
| function nodeToString(node, label) { | ||||
|     const suffix = label ? `:${label}` : ""; | ||||
|  | ||||
|     switch (node.type) { | ||||
|         case "Identifier": return `${node.type}${suffix} (${node.name})`; | ||||
|         case "Literal": return `${node.type}${suffix} (${node.value})`; | ||||
|         default: return `${node.type}${suffix}`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| module.exports = { | ||||
|  | ||||
|     /** | ||||
|      * A flag that debug dumping is enabled or not. | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     enabled: debug.enabled, | ||||
|  | ||||
|     /** | ||||
|      * Dumps given objects. | ||||
|      * @param {...any} args objects to dump. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     dump: debug, | ||||
|  | ||||
|     /** | ||||
|      * Dumps the current analyzing state. | ||||
|      * @param {ASTNode} node A node to dump. | ||||
|      * @param {CodePathState} state A state to dump. | ||||
|      * @param {boolean} leaving A flag whether or not it's leaving | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) { | ||||
|         for (let i = 0; i < state.currentSegments.length; ++i) { | ||||
|             const segInternal = state.currentSegments[i].internal; | ||||
|  | ||||
|             if (leaving) { | ||||
|                 const last = segInternal.nodes.length - 1; | ||||
|  | ||||
|                 if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) { | ||||
|                     segInternal.nodes[last] = nodeToString(node, void 0); | ||||
|                 } else { | ||||
|                     segInternal.nodes.push(nodeToString(node, "exit")); | ||||
|                 } | ||||
|             } else { | ||||
|                 segInternal.nodes.push(nodeToString(node, "enter")); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         debug([ | ||||
|             `${state.currentSegments.map(getId).join(",")})`, | ||||
|             `${node.type}${leaving ? ":exit" : ""}` | ||||
|         ].join(" ")); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Dumps a DOT code of a given code path. | ||||
|      * The DOT code can be visualized with Graphvis. | ||||
|      * @param {CodePath} codePath A code path to dump. | ||||
|      * @returns {void} | ||||
|      * @see http://www.graphviz.org | ||||
|      * @see http://www.webgraphviz.com | ||||
|      */ | ||||
|     dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) { | ||||
|         let text = | ||||
|             "\n" + | ||||
|             "digraph {\n" + | ||||
|             "node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" + | ||||
|             "initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; | ||||
|  | ||||
|         if (codePath.returnedSegments.length > 0) { | ||||
|             text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; | ||||
|         } | ||||
|         if (codePath.thrownSegments.length > 0) { | ||||
|             text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n"; | ||||
|         } | ||||
|  | ||||
|         const traceMap = Object.create(null); | ||||
|         const arrows = this.makeDotArrows(codePath, traceMap); | ||||
|  | ||||
|         for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype | ||||
|             const segment = traceMap[id]; | ||||
|  | ||||
|             text += `${id}[`; | ||||
|  | ||||
|             if (segment.reachable) { | ||||
|                 text += "label=\""; | ||||
|             } else { | ||||
|                 text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n"; | ||||
|             } | ||||
|  | ||||
|             if (segment.internal.nodes.length > 0) { | ||||
|                 text += segment.internal.nodes.join("\\n"); | ||||
|             } else { | ||||
|                 text += "????"; | ||||
|             } | ||||
|  | ||||
|             text += "\"];\n"; | ||||
|         } | ||||
|  | ||||
|         text += `${arrows}\n`; | ||||
|         text += "}"; | ||||
|         debug("DOT", text); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Makes a DOT code of a given code path. | ||||
|      * The DOT code can be visualized with Graphvis. | ||||
|      * @param {CodePath} codePath A code path to make DOT. | ||||
|      * @param {Object} traceMap Optional. A map to check whether or not segments had been done. | ||||
|      * @returns {string} A DOT code of the code path. | ||||
|      */ | ||||
|     makeDotArrows(codePath, traceMap) { | ||||
|         const stack = [[codePath.initialSegment, 0]]; | ||||
|         const done = traceMap || Object.create(null); | ||||
|         let lastId = codePath.initialSegment.id; | ||||
|         let text = `initial->${codePath.initialSegment.id}`; | ||||
|  | ||||
|         while (stack.length > 0) { | ||||
|             const item = stack.pop(); | ||||
|             const segment = item[0]; | ||||
|             const index = item[1]; | ||||
|  | ||||
|             if (done[segment.id] && index === 0) { | ||||
|                 continue; | ||||
|             } | ||||
|             done[segment.id] = segment; | ||||
|  | ||||
|             const nextSegment = segment.allNextSegments[index]; | ||||
|  | ||||
|             if (!nextSegment) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (lastId === segment.id) { | ||||
|                 text += `->${nextSegment.id}`; | ||||
|             } else { | ||||
|                 text += `;\n${segment.id}->${nextSegment.id}`; | ||||
|             } | ||||
|             lastId = nextSegment.id; | ||||
|  | ||||
|             stack.unshift([segment, 1 + index]); | ||||
|             stack.push([nextSegment, 0]); | ||||
|         } | ||||
|  | ||||
|         codePath.returnedSegments.forEach(finalSegment => { | ||||
|             if (lastId === finalSegment.id) { | ||||
|                 text += "->final"; | ||||
|             } else { | ||||
|                 text += `;\n${finalSegment.id}->final`; | ||||
|             } | ||||
|             lastId = null; | ||||
|         }); | ||||
|  | ||||
|         codePath.thrownSegments.forEach(finalSegment => { | ||||
|             if (lastId === finalSegment.id) { | ||||
|                 text += "->thrown"; | ||||
|             } else { | ||||
|                 text += `;\n${finalSegment.id}->thrown`; | ||||
|             } | ||||
|             lastId = null; | ||||
|         }); | ||||
|  | ||||
|         return `${text};`; | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										349
									
								
								node_modules/eslint/lib/linter/code-path-analysis/fork-context.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								node_modules/eslint/lib/linter/code-path-analysis/fork-context.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,349 @@ | ||||
| /** | ||||
|  * @fileoverview A class to operate forking. | ||||
|  * | ||||
|  * This is state of forking. | ||||
|  * This has a fork list and manages it. | ||||
|  * | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const assert = require("assert"), | ||||
|     CodePathSegment = require("./code-path-segment"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Determines whether or not a given segment is reachable. | ||||
|  * @param {CodePathSegment} segment The segment to check. | ||||
|  * @returns {boolean} `true` if the segment is reachable. | ||||
|  */ | ||||
| function isReachable(segment) { | ||||
|     return segment.reachable; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates a new segment for each fork in the given context and appends it | ||||
|  * to the end of the specified range of segments. Ultimately, this ends up calling | ||||
|  * `new CodePathSegment()` for each of the forks using the `create` argument | ||||
|  * as a wrapper around special behavior. | ||||
|  * | ||||
|  * The `startIndex` and `endIndex` arguments specify a range of segments in | ||||
|  * `context` that should become `allPrevSegments` for the newly created | ||||
|  * `CodePathSegment` objects. | ||||
|  * | ||||
|  * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and | ||||
|  * `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to | ||||
|  * the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of | ||||
|  * `b`, `d`, and `f`. | ||||
|  * @param {ForkContext} context An instance from which the previous segments | ||||
|  *      will be obtained. | ||||
|  * @param {number} startIndex The index of the first segment in the context | ||||
|  *      that should be specified as previous segments for the newly created segments. | ||||
|  * @param {number} endIndex The index of the last segment in the context | ||||
|  *      that should be specified as previous segments for the newly created segments. | ||||
|  * @param {Function} create A function that creates new `CodePathSegment` | ||||
|  *      instances in a particular way. See the `CodePathSegment.new*` methods. | ||||
|  * @returns {Array<CodePathSegment>} An array of the newly-created segments. | ||||
|  */ | ||||
| function createSegments(context, startIndex, endIndex, create) { | ||||
|  | ||||
|     /** @type {Array<Array<CodePathSegment>>} */ | ||||
|     const list = context.segmentsList; | ||||
|  | ||||
|     /* | ||||
|      * Both `startIndex` and `endIndex` work the same way: if the number is zero | ||||
|      * or more, then the number is used as-is. If the number is negative, | ||||
|      * then that number is added to the length of the segments list to | ||||
|      * determine the index to use. That means -1 for either argument | ||||
|      * is the last element, -2 is the second to last, and so on. | ||||
|      * | ||||
|      * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the | ||||
|      * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function | ||||
|      * will include items at indices 0, 1, and 2. | ||||
|      * | ||||
|      * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only | ||||
|      * be using the last segment in `list`. | ||||
|      */ | ||||
|     const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex; | ||||
|     const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex; | ||||
|  | ||||
|     /** @type {Array<CodePathSegment>} */ | ||||
|     const segments = []; | ||||
|  | ||||
|     for (let i = 0; i < context.count; ++i) { | ||||
|  | ||||
|         // this is passed into `new CodePathSegment` to add to code path. | ||||
|         const allPrevSegments = []; | ||||
|  | ||||
|         for (let j = normalizedBegin; j <= normalizedEnd; ++j) { | ||||
|             allPrevSegments.push(list[j][i]); | ||||
|         } | ||||
|  | ||||
|         // note: `create` is just a wrapper that augments `new CodePathSegment`. | ||||
|         segments.push(create(context.idGenerator.next(), allPrevSegments)); | ||||
|     } | ||||
|  | ||||
|     return segments; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Inside of a `finally` block we end up with two parallel paths. If the code path | ||||
|  * exits by a control statement (such as `break` or `continue`) from the `finally` | ||||
|  * block, then we need to merge the remaining parallel paths back into one. | ||||
|  * @param {ForkContext} context The fork context to work on. | ||||
|  * @param {Array<CodePathSegment>} segments Segments to merge. | ||||
|  * @returns {Array<CodePathSegment>} The merged segments. | ||||
|  */ | ||||
| function mergeExtraSegments(context, segments) { | ||||
|     let currentSegments = segments; | ||||
|  | ||||
|     /* | ||||
|      * We need to ensure that the array returned from this function contains no more | ||||
|      * than the number of segments that the context allows. `context.count` indicates | ||||
|      * how many items should be in the returned array to ensure that the new segment | ||||
|      * entries will line up with the already existing segment entries. | ||||
|      */ | ||||
|     while (currentSegments.length > context.count) { | ||||
|         const merged = []; | ||||
|  | ||||
|         /* | ||||
|          * Because `context.count` is a factor of 2 inside of a `finally` block, | ||||
|          * we can divide the segment count by 2 to merge the paths together. | ||||
|          * This loops through each segment in the list and creates a new `CodePathSegment` | ||||
|          * that has the segment and the segment two slots away as previous segments. | ||||
|          * | ||||
|          * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such | ||||
|          * that: | ||||
|          * | ||||
|          * When `i` is 0: | ||||
|          * a->e | ||||
|          * c->e | ||||
|          * | ||||
|          * When `i` is 1: | ||||
|          * b->f | ||||
|          * d->f | ||||
|          */ | ||||
|         for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) { | ||||
|             merged.push(CodePathSegment.newNext( | ||||
|                 context.idGenerator.next(), | ||||
|                 [currentSegments[i], currentSegments[i + length]] | ||||
|             )); | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * Go through the loop condition one more time to see if we have the | ||||
|          * number of segments for the context. If not, we'll keep merging paths | ||||
|          * of the merged segments until we get there. | ||||
|          */ | ||||
|         currentSegments = merged; | ||||
|     } | ||||
|  | ||||
|     return currentSegments; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Manages the forking of code paths. | ||||
|  */ | ||||
| class ForkContext { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance. | ||||
|      * @param {IdGenerator} idGenerator An identifier generator for segments. | ||||
|      * @param {ForkContext|null} upper The preceding fork context. | ||||
|      * @param {number} count The number of parallel segments in each element | ||||
|      *      of `segmentsList`. | ||||
|      */ | ||||
|     constructor(idGenerator, upper, count) { | ||||
|  | ||||
|         /** | ||||
|          * The ID generator that will generate segment IDs for any new | ||||
|          * segments that are created. | ||||
|          * @type {IdGenerator} | ||||
|          */ | ||||
|         this.idGenerator = idGenerator; | ||||
|  | ||||
|         /** | ||||
|          * The preceding fork context. | ||||
|          * @type {ForkContext|null} | ||||
|          */ | ||||
|         this.upper = upper; | ||||
|  | ||||
|         /** | ||||
|          * The number of elements in each element of `segmentsList`. In most | ||||
|          * cases, this is 1 but can be 2 when there is a `finally` present, | ||||
|          * which forks the code path outside of normal flow. In the case of nested | ||||
|          * `finally` blocks, this can be a multiple of 2. | ||||
|          * @type {number} | ||||
|          */ | ||||
|         this.count = count; | ||||
|  | ||||
|         /** | ||||
|          * The segments within this context. Each element in this array has | ||||
|          * `count` elements that represent one step in each fork. For example, | ||||
|          * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path | ||||
|          * a->c->e and one path b->d->f, and `count` is 2 because each element | ||||
|          * is an array with two elements. | ||||
|          * @type {Array<Array<CodePathSegment>>} | ||||
|          */ | ||||
|         this.segmentsList = []; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The segments that begin this fork context. | ||||
|      * @type {Array<CodePathSegment>} | ||||
|      */ | ||||
|     get head() { | ||||
|         const list = this.segmentsList; | ||||
|  | ||||
|         return list.length === 0 ? [] : list[list.length - 1]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Indicates if the context contains no segments. | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     get empty() { | ||||
|         return this.segmentsList.length === 0; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Indicates if there are any segments that are reachable. | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     get reachable() { | ||||
|         const segments = this.head; | ||||
|  | ||||
|         return segments.length > 0 && segments.some(isReachable); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates new segments in this context and appends them to the end of the | ||||
|      * already existing `CodePathSegment`s specified by `startIndex` and | ||||
|      * `endIndex`. | ||||
|      * @param {number} startIndex The index of the first segment in the context | ||||
|      *      that should be specified as previous segments for the newly created segments. | ||||
|      * @param {number} endIndex The index of the last segment in the context | ||||
|      *      that should be specified as previous segments for the newly created segments. | ||||
|      * @returns {Array<CodePathSegment>} An array of the newly created segments. | ||||
|      */ | ||||
|     makeNext(startIndex, endIndex) { | ||||
|         return createSegments(this, startIndex, endIndex, CodePathSegment.newNext); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates new unreachable segments in this context and appends them to the end of the | ||||
|      * already existing `CodePathSegment`s specified by `startIndex` and | ||||
|      * `endIndex`. | ||||
|      * @param {number} startIndex The index of the first segment in the context | ||||
|      *      that should be specified as previous segments for the newly created segments. | ||||
|      * @param {number} endIndex The index of the last segment in the context | ||||
|      *      that should be specified as previous segments for the newly created segments. | ||||
|      * @returns {Array<CodePathSegment>} An array of the newly created segments. | ||||
|      */ | ||||
|     makeUnreachable(startIndex, endIndex) { | ||||
|         return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates new segments in this context and does not append them to the end | ||||
|      *  of the already existing `CodePathSegment`s specified by `startIndex` and | ||||
|      * `endIndex`. The `startIndex` and `endIndex` are only used to determine if | ||||
|      * the new segments should be reachable. If any of the segments in this range | ||||
|      * are reachable then the new segments are also reachable; otherwise, the new | ||||
|      * segments are unreachable. | ||||
|      * @param {number} startIndex The index of the first segment in the context | ||||
|      *      that should be considered for reachability. | ||||
|      * @param {number} endIndex The index of the last segment in the context | ||||
|      *      that should be considered for reachability. | ||||
|      * @returns {Array<CodePathSegment>} An array of the newly created segments. | ||||
|      */ | ||||
|     makeDisconnected(startIndex, endIndex) { | ||||
|         return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds segments to the head of this context. | ||||
|      * @param {Array<CodePathSegment>} segments The segments to add. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     add(segments) { | ||||
|         assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); | ||||
|         this.segmentsList.push(mergeExtraSegments(this, segments)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Replaces the head segments with the given segments. | ||||
|      * The current head segments are removed. | ||||
|      * @param {Array<CodePathSegment>} replacementHeadSegments The new head segments. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     replaceHead(replacementHeadSegments) { | ||||
|         assert( | ||||
|             replacementHeadSegments.length >= this.count, | ||||
|             `${replacementHeadSegments.length} >= ${this.count}` | ||||
|         ); | ||||
|         this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds all segments of a given fork context into this context. | ||||
|      * @param {ForkContext} otherForkContext The fork context to add from. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     addAll(otherForkContext) { | ||||
|         assert(otherForkContext.count === this.count); | ||||
|         this.segmentsList.push(...otherForkContext.segmentsList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Clears all segments in this context. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     clear() { | ||||
|         this.segmentsList = []; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new root context, meaning that there are no parent | ||||
|      * fork contexts. | ||||
|      * @param {IdGenerator} idGenerator An identifier generator for segments. | ||||
|      * @returns {ForkContext} New fork context. | ||||
|      */ | ||||
|     static newRoot(idGenerator) { | ||||
|         const context = new ForkContext(idGenerator, null, 1); | ||||
|  | ||||
|         context.add([CodePathSegment.newRoot(idGenerator.next())]); | ||||
|  | ||||
|         return context; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates an empty fork context preceded by a given context. | ||||
|      * @param {ForkContext} parentContext The parent fork context. | ||||
|      * @param {boolean} shouldForkLeavingPath Indicates that we are inside of | ||||
|      *      a `finally` block and should therefore fork the path that leaves | ||||
|      *      `finally`. | ||||
|      * @returns {ForkContext} New fork context. | ||||
|      */ | ||||
|     static newEmpty(parentContext, shouldForkLeavingPath) { | ||||
|         return new ForkContext( | ||||
|             parentContext.idGenerator, | ||||
|             parentContext, | ||||
|             (shouldForkLeavingPath ? 2 : 1) * parentContext.count | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = ForkContext; | ||||
							
								
								
									
										45
									
								
								node_modules/eslint/lib/linter/code-path-analysis/id-generator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								node_modules/eslint/lib/linter/code-path-analysis/id-generator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| /** | ||||
|  * @fileoverview A class of identifiers generator for code path segments. | ||||
|  * | ||||
|  * Each rule uses the identifier of code path segments to store additional | ||||
|  * information of the code path. | ||||
|  * | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * A generator for unique ids. | ||||
|  */ | ||||
| class IdGenerator { | ||||
|  | ||||
|     /** | ||||
|      * @param {string} prefix Optional. A prefix of generated ids. | ||||
|      */ | ||||
|     constructor(prefix) { | ||||
|         this.prefix = String(prefix); | ||||
|         this.n = 0; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Generates id. | ||||
|      * @returns {string} A generated id. | ||||
|      */ | ||||
|     next() { | ||||
|         this.n = 1 + this.n | 0; | ||||
|  | ||||
|         /* c8 ignore start */ | ||||
|         if (this.n < 0) { | ||||
|             this.n = 1; | ||||
|         }/* c8 ignore stop */ | ||||
|  | ||||
|         return this.prefix + this.n; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = IdGenerator; | ||||
							
								
								
									
										185
									
								
								node_modules/eslint/lib/linter/config-comment-parser.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								node_modules/eslint/lib/linter/config-comment-parser.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,185 @@ | ||||
| /** | ||||
|  * @fileoverview Config Comment Parser | ||||
|  * @author Nicholas C. Zakas | ||||
|  */ | ||||
|  | ||||
| /* eslint class-methods-use-this: off -- Methods desired on instance */ | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const levn = require("levn"), | ||||
|     { | ||||
|         Legacy: { | ||||
|             ConfigOps | ||||
|         } | ||||
|     } = require("@eslint/eslintrc/universal"), | ||||
|     { | ||||
|         directivesPattern | ||||
|     } = require("../shared/directives"); | ||||
|  | ||||
| const debug = require("debug")("eslint:config-comment-parser"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Typedefs | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Object to parse ESLint configuration comments inside JavaScript files. | ||||
|  * @name ConfigCommentParser | ||||
|  */ | ||||
| module.exports = class ConfigCommentParser { | ||||
|  | ||||
|     /** | ||||
|      * Parses a list of "name:string_value" or/and "name" options divided by comma or | ||||
|      * whitespace. Used for "global" and "exported" comments. | ||||
|      * @param {string} string The string to parse. | ||||
|      * @param {Comment} comment The comment node which has the string. | ||||
|      * @returns {Object} Result map object of names and string values, or null values if no value was provided | ||||
|      */ | ||||
|     parseStringConfig(string, comment) { | ||||
|         debug("Parsing String config"); | ||||
|  | ||||
|         const items = {}; | ||||
|  | ||||
|         // Collapse whitespace around `:` and `,` to make parsing easier | ||||
|         const trimmedString = string.replace(/\s*([:,])\s*/gu, "$1"); | ||||
|  | ||||
|         trimmedString.split(/\s|,+/u).forEach(name => { | ||||
|             if (!name) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // value defaults to null (if not provided), e.g: "foo" => ["foo", null] | ||||
|             const [key, value = null] = name.split(":"); | ||||
|  | ||||
|             items[key] = { value, comment }; | ||||
|         }); | ||||
|         return items; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses a JSON-like config. | ||||
|      * @param {string} string The string to parse. | ||||
|      * @param {Object} location Start line and column of comments for potential error message. | ||||
|      * @returns {({success: true, config: Object}|{success: false, error: LintMessage})} Result map object | ||||
|      */ | ||||
|     parseJsonConfig(string, location) { | ||||
|         debug("Parsing JSON config"); | ||||
|  | ||||
|         let items = {}; | ||||
|  | ||||
|         // Parses a JSON-like comment by the same way as parsing CLI option. | ||||
|         try { | ||||
|             items = levn.parse("Object", string) || {}; | ||||
|  | ||||
|             // Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`. | ||||
|             // Also, commaless notations have invalid severity: | ||||
|             //     "no-alert: 2 no-console: 2" --> {"no-alert": "2 no-console: 2"} | ||||
|             // Should ignore that case as well. | ||||
|             if (ConfigOps.isEverySeverityValid(items)) { | ||||
|                 return { | ||||
|                     success: true, | ||||
|                     config: items | ||||
|                 }; | ||||
|             } | ||||
|         } catch { | ||||
|  | ||||
|             debug("Levn parsing failed; falling back to manual parsing."); | ||||
|  | ||||
|             // ignore to parse the string by a fallback. | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * Optionator cannot parse commaless notations. | ||||
|          * But we are supporting that. So this is a fallback for that. | ||||
|          */ | ||||
|         items = {}; | ||||
|         const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,"); | ||||
|  | ||||
|         try { | ||||
|             items = JSON.parse(`{${normalizedString}}`); | ||||
|         } catch (ex) { | ||||
|             debug("Manual parsing failed."); | ||||
|  | ||||
|             return { | ||||
|                 success: false, | ||||
|                 error: { | ||||
|                     ruleId: null, | ||||
|                     fatal: true, | ||||
|                     severity: 2, | ||||
|                     message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`, | ||||
|                     line: location.start.line, | ||||
|                     column: location.start.column + 1, | ||||
|                     nodeType: null | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             success: true, | ||||
|             config: items | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses a config of values separated by comma. | ||||
|      * @param {string} string The string to parse. | ||||
|      * @returns {Object} Result map of values and true values | ||||
|      */ | ||||
|     parseListConfig(string) { | ||||
|         debug("Parsing list config"); | ||||
|  | ||||
|         const items = {}; | ||||
|  | ||||
|         string.split(",").forEach(name => { | ||||
|             const trimmedName = name.trim().replace(/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/us, "$<ruleId>"); | ||||
|  | ||||
|             if (trimmedName) { | ||||
|                 items[trimmedName] = true; | ||||
|             } | ||||
|         }); | ||||
|         return items; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     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 }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses a directive comment into directive text and value. | ||||
|      * @param {Comment} comment The comment node with the directive to be parsed. | ||||
|      * @returns {{directiveText: string, directiveValue: string}} The directive text and value. | ||||
|      */ | ||||
|     parseDirective(comment) { | ||||
|         const { directivePart } = this.extractDirectiveComment(comment.value); | ||||
|         const match = directivesPattern.exec(directivePart); | ||||
|         const directiveText = match[1]; | ||||
|         const directiveValue = directivePart.slice(match.index + directiveText.length); | ||||
|  | ||||
|         return { directiveText, directiveValue }; | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										13
									
								
								node_modules/eslint/lib/linter/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								node_modules/eslint/lib/linter/index.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const { Linter } = require("./linter"); | ||||
| const interpolate = require("./interpolate"); | ||||
| const SourceCodeFixer = require("./source-code-fixer"); | ||||
|  | ||||
| module.exports = { | ||||
|     Linter, | ||||
|  | ||||
|     // For testers. | ||||
|     SourceCodeFixer, | ||||
|     interpolate | ||||
| }; | ||||
							
								
								
									
										28
									
								
								node_modules/eslint/lib/linter/interpolate.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								node_modules/eslint/lib/linter/interpolate.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| /** | ||||
|  * @fileoverview Interpolate keys from an object into a string with {{ }} markers. | ||||
|  * @author Jed Fox | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| module.exports = (text, data) => { | ||||
|     if (!data) { | ||||
|         return text; | ||||
|     } | ||||
|  | ||||
|     // Substitution content for any {{ }} markers. | ||||
|     return text.replace(/\{\{([^{}]+?)\}\}/gu, (fullMatch, termWithWhitespace) => { | ||||
|         const term = termWithWhitespace.trim(); | ||||
|  | ||||
|         if (term in data) { | ||||
|             return data[term]; | ||||
|         } | ||||
|  | ||||
|         // Preserve old behavior: If parameter name not provided, don't replace it. | ||||
|         return fullMatch; | ||||
|     }); | ||||
| }; | ||||
							
								
								
									
										2119
									
								
								node_modules/eslint/lib/linter/linter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2119
									
								
								node_modules/eslint/lib/linter/linter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										354
									
								
								node_modules/eslint/lib/linter/node-event-generator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								node_modules/eslint/lib/linter/node-event-generator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,354 @@ | ||||
| /** | ||||
|  * @fileoverview The event generator for AST nodes. | ||||
|  * @author Toru Nagashima | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const esquery = require("esquery"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Typedefs | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * An object describing an AST selector | ||||
|  * @typedef {Object} ASTSelector | ||||
|  * @property {string} rawSelector The string that was parsed into this selector | ||||
|  * @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering | ||||
|  * @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector | ||||
|  * @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match, | ||||
|  * or `null` if all node types could cause a match | ||||
|  * @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector | ||||
|  * @property {number} identifierCount The total number of identifier queries in this selector | ||||
|  */ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Computes the union of one or more arrays | ||||
|  * @param {...any[]} arrays One or more arrays to union | ||||
|  * @returns {any[]} The union of the input arrays | ||||
|  */ | ||||
| function union(...arrays) { | ||||
|     return [...new Set(arrays.flat())]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Computes the intersection of one or more arrays | ||||
|  * @param {...any[]} arrays One or more arrays to intersect | ||||
|  * @returns {any[]} The intersection of the input arrays | ||||
|  */ | ||||
| function intersection(...arrays) { | ||||
|     if (arrays.length === 0) { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     let result = [...new Set(arrays[0])]; | ||||
|  | ||||
|     for (const array of arrays.slice(1)) { | ||||
|         result = result.filter(x => array.includes(x)); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Gets the possible types of a selector | ||||
|  * @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector | ||||
|  * @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it | ||||
|  */ | ||||
| function getPossibleTypes(parsedSelector) { | ||||
|     switch (parsedSelector.type) { | ||||
|         case "identifier": | ||||
|             return [parsedSelector.value]; | ||||
|  | ||||
|         case "matches": { | ||||
|             const typesForComponents = parsedSelector.selectors.map(getPossibleTypes); | ||||
|  | ||||
|             if (typesForComponents.every(Boolean)) { | ||||
|                 return union(...typesForComponents); | ||||
|             } | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         case "compound": { | ||||
|             const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent); | ||||
|  | ||||
|             // If all of the components could match any type, then the compound could also match any type. | ||||
|             if (!typesForComponents.length) { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             /* | ||||
|              * If at least one of the components could only match a particular type, the compound could only match | ||||
|              * the intersection of those types. | ||||
|              */ | ||||
|             return intersection(...typesForComponents); | ||||
|         } | ||||
|  | ||||
|         case "child": | ||||
|         case "descendant": | ||||
|         case "sibling": | ||||
|         case "adjacent": | ||||
|             return getPossibleTypes(parsedSelector.right); | ||||
|  | ||||
|         case "class": | ||||
|             if (parsedSelector.name === "function") { | ||||
|                 return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]; | ||||
|             } | ||||
|  | ||||
|             return null; | ||||
|  | ||||
|         default: | ||||
|             return null; | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Counts the number of class, pseudo-class, and attribute queries in this selector | ||||
|  * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior | ||||
|  * @returns {number} The number of class, pseudo-class, and attribute queries in this selector | ||||
|  */ | ||||
| function countClassAttributes(parsedSelector) { | ||||
|     switch (parsedSelector.type) { | ||||
|         case "child": | ||||
|         case "descendant": | ||||
|         case "sibling": | ||||
|         case "adjacent": | ||||
|             return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right); | ||||
|  | ||||
|         case "compound": | ||||
|         case "not": | ||||
|         case "matches": | ||||
|             return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0); | ||||
|  | ||||
|         case "attribute": | ||||
|         case "field": | ||||
|         case "nth-child": | ||||
|         case "nth-last-child": | ||||
|             return 1; | ||||
|  | ||||
|         default: | ||||
|             return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Counts the number of identifier queries in this selector | ||||
|  * @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior | ||||
|  * @returns {number} The number of identifier queries | ||||
|  */ | ||||
| function countIdentifiers(parsedSelector) { | ||||
|     switch (parsedSelector.type) { | ||||
|         case "child": | ||||
|         case "descendant": | ||||
|         case "sibling": | ||||
|         case "adjacent": | ||||
|             return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right); | ||||
|  | ||||
|         case "compound": | ||||
|         case "not": | ||||
|         case "matches": | ||||
|             return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0); | ||||
|  | ||||
|         case "identifier": | ||||
|             return 1; | ||||
|  | ||||
|         default: | ||||
|             return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Compares the specificity of two selector objects, with CSS-like rules. | ||||
|  * @param {ASTSelector} selectorA An AST selector descriptor | ||||
|  * @param {ASTSelector} selectorB Another AST selector descriptor | ||||
|  * @returns {number} | ||||
|  * a value less than 0 if selectorA is less specific than selectorB | ||||
|  * a value greater than 0 if selectorA is more specific than selectorB | ||||
|  * a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically | ||||
|  * a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically | ||||
|  */ | ||||
| function compareSpecificity(selectorA, selectorB) { | ||||
|     return selectorA.attributeCount - selectorB.attributeCount || | ||||
|         selectorA.identifierCount - selectorB.identifierCount || | ||||
|         (selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Parses a raw selector string, and throws a useful error if parsing fails. | ||||
|  * @param {string} rawSelector A raw AST selector | ||||
|  * @returns {Object} An object (from esquery) describing the matching behavior of this selector | ||||
|  * @throws {Error} An error if the selector is invalid | ||||
|  */ | ||||
| function tryParseSelector(rawSelector) { | ||||
|     try { | ||||
|         return esquery.parse(rawSelector.replace(/:exit$/u, "")); | ||||
|     } catch (err) { | ||||
|         if (err.location && err.location.start && typeof err.location.start.offset === "number") { | ||||
|             throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`); | ||||
|         } | ||||
|         throw err; | ||||
|     } | ||||
| } | ||||
|  | ||||
| const selectorCache = new Map(); | ||||
|  | ||||
| /** | ||||
|  * Parses a raw selector string, and returns the parsed selector along with specificity and type information. | ||||
|  * @param {string} rawSelector A raw AST selector | ||||
|  * @returns {ASTSelector} A selector descriptor | ||||
|  */ | ||||
| function parseSelector(rawSelector) { | ||||
|     if (selectorCache.has(rawSelector)) { | ||||
|         return selectorCache.get(rawSelector); | ||||
|     } | ||||
|  | ||||
|     const parsedSelector = tryParseSelector(rawSelector); | ||||
|  | ||||
|     const result = { | ||||
|         rawSelector, | ||||
|         isExit: rawSelector.endsWith(":exit"), | ||||
|         parsedSelector, | ||||
|         listenerTypes: getPossibleTypes(parsedSelector), | ||||
|         attributeCount: countClassAttributes(parsedSelector), | ||||
|         identifierCount: countIdentifiers(parsedSelector) | ||||
|     }; | ||||
|  | ||||
|     selectorCache.set(rawSelector, result); | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * The event generator for AST nodes. | ||||
|  * This implements below interface. | ||||
|  * | ||||
|  * ```ts | ||||
|  * interface EventGenerator { | ||||
|  *     emitter: SafeEmitter; | ||||
|  *     enterNode(node: ASTNode): void; | ||||
|  *     leaveNode(node: ASTNode): void; | ||||
|  * } | ||||
|  * ``` | ||||
|  */ | ||||
| class NodeEventGenerator { | ||||
|  | ||||
|     /** | ||||
|      * @param {SafeEmitter} emitter | ||||
|      * An SafeEmitter which is the destination of events. This emitter must already | ||||
|      * have registered listeners for all of the events that it needs to listen for. | ||||
|      * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.) | ||||
|      * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes. | ||||
|      * @returns {NodeEventGenerator} new instance | ||||
|      */ | ||||
|     constructor(emitter, esqueryOptions) { | ||||
|         this.emitter = emitter; | ||||
|         this.esqueryOptions = esqueryOptions; | ||||
|         this.currentAncestry = []; | ||||
|         this.enterSelectorsByNodeType = new Map(); | ||||
|         this.exitSelectorsByNodeType = new Map(); | ||||
|         this.anyTypeEnterSelectors = []; | ||||
|         this.anyTypeExitSelectors = []; | ||||
|  | ||||
|         emitter.eventNames().forEach(rawSelector => { | ||||
|             const selector = parseSelector(rawSelector); | ||||
|  | ||||
|             if (selector.listenerTypes) { | ||||
|                 const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType; | ||||
|  | ||||
|                 selector.listenerTypes.forEach(nodeType => { | ||||
|                     if (!typeMap.has(nodeType)) { | ||||
|                         typeMap.set(nodeType, []); | ||||
|                     } | ||||
|                     typeMap.get(nodeType).push(selector); | ||||
|                 }); | ||||
|                 return; | ||||
|             } | ||||
|             const selectors = selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors; | ||||
|  | ||||
|             selectors.push(selector); | ||||
|         }); | ||||
|  | ||||
|         this.anyTypeEnterSelectors.sort(compareSpecificity); | ||||
|         this.anyTypeExitSelectors.sort(compareSpecificity); | ||||
|         this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity)); | ||||
|         this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks a selector against a node, and emits it if it matches | ||||
|      * @param {ASTNode} node The node to check | ||||
|      * @param {ASTSelector} selector An AST selector descriptor | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     applySelector(node, selector) { | ||||
|         if (esquery.matches(node, selector.parsedSelector, this.currentAncestry, this.esqueryOptions)) { | ||||
|             this.emitter.emit(selector.rawSelector, node); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Applies all appropriate selectors to a node, in specificity order | ||||
|      * @param {ASTNode} node The node to check | ||||
|      * @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     applySelectors(node, isExit) { | ||||
|         const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || []; | ||||
|         const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors; | ||||
|  | ||||
|         /* | ||||
|          * selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor. | ||||
|          * Iterate through each of them, applying selectors in the right order. | ||||
|          */ | ||||
|         let selectorsByTypeIndex = 0; | ||||
|         let anyTypeSelectorsIndex = 0; | ||||
|  | ||||
|         while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) { | ||||
|             if ( | ||||
|                 selectorsByTypeIndex >= selectorsByNodeType.length || | ||||
|                 anyTypeSelectorsIndex < anyTypeSelectors.length && | ||||
|                 compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0 | ||||
|             ) { | ||||
|                 this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]); | ||||
|             } else { | ||||
|                 this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Emits an event of entering AST node. | ||||
|      * @param {ASTNode} node A node which was entered. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     enterNode(node) { | ||||
|         if (node.parent) { | ||||
|             this.currentAncestry.unshift(node.parent); | ||||
|         } | ||||
|         this.applySelectors(node, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Emits an event of leaving AST node. | ||||
|      * @param {ASTNode} node A node which was left. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     leaveNode(node) { | ||||
|         this.applySelectors(node, true); | ||||
|         this.currentAncestry.shift(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = NodeEventGenerator; | ||||
							
								
								
									
										369
									
								
								node_modules/eslint/lib/linter/report-translator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								node_modules/eslint/lib/linter/report-translator.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,369 @@ | ||||
| /** | ||||
|  * @fileoverview A helper that translates context.report() calls from the rule API into generic problem objects | ||||
|  * @author Teddy Katz | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const assert = require("assert"); | ||||
| const ruleFixer = require("./rule-fixer"); | ||||
| const interpolate = require("./interpolate"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Typedefs | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** @typedef {import("../shared/types").LintMessage} LintMessage */ | ||||
|  | ||||
| /** | ||||
|  * An error message description | ||||
|  * @typedef {Object} MessageDescriptor | ||||
|  * @property {ASTNode} [node] The reported node | ||||
|  * @property {Location} loc The location of the problem. | ||||
|  * @property {string} message The problem message. | ||||
|  * @property {Object} [data] Optional data to use to fill in placeholders in the | ||||
|  *      message. | ||||
|  * @property {Function} [fix] The function to call that creates a fix command. | ||||
|  * @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes. | ||||
|  */ | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Module Definition | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Translates a multi-argument context.report() call into a single object argument call | ||||
|  * @param {...*} args A list of arguments passed to `context.report` | ||||
|  * @returns {MessageDescriptor} A normalized object containing report information | ||||
|  */ | ||||
| function normalizeMultiArgReportCall(...args) { | ||||
|  | ||||
|     // If there is one argument, it is considered to be a new-style call already. | ||||
|     if (args.length === 1) { | ||||
|  | ||||
|         // Shallow clone the object to avoid surprises if reusing the descriptor | ||||
|         return Object.assign({}, args[0]); | ||||
|     } | ||||
|  | ||||
|     // If the second argument is a string, the arguments are interpreted as [node, message, data, fix]. | ||||
|     if (typeof args[1] === "string") { | ||||
|         return { | ||||
|             node: args[0], | ||||
|             message: args[1], | ||||
|             data: args[2], | ||||
|             fix: args[3] | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     // Otherwise, the arguments are interpreted as [node, loc, message, data, fix]. | ||||
|     return { | ||||
|         node: args[0], | ||||
|         loc: args[1], | ||||
|         message: args[2], | ||||
|         data: args[3], | ||||
|         fix: args[4] | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Asserts that either a loc or a node was provided, and the node is valid if it was provided. | ||||
|  * @param {MessageDescriptor} descriptor A descriptor to validate | ||||
|  * @returns {void} | ||||
|  * @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object | ||||
|  */ | ||||
| function assertValidNodeInfo(descriptor) { | ||||
|     if (descriptor.node) { | ||||
|         assert(typeof descriptor.node === "object", "Node must be an object"); | ||||
|     } else { | ||||
|         assert(descriptor.loc, "Node must be provided when reporting error if location is not provided"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties | ||||
|  * @param {MessageDescriptor} descriptor A descriptor for the report from a rule. | ||||
|  * @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties | ||||
|  * from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor. | ||||
|  */ | ||||
| function normalizeReportLoc(descriptor) { | ||||
|     if (descriptor.loc) { | ||||
|         if (descriptor.loc.start) { | ||||
|             return descriptor.loc; | ||||
|         } | ||||
|         return { start: descriptor.loc, end: null }; | ||||
|     } | ||||
|     return descriptor.node.loc; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Clones the given fix object. | ||||
|  * @param {Fix|null} fix The fix to clone. | ||||
|  * @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in. | ||||
|  */ | ||||
| function cloneFix(fix) { | ||||
|     if (!fix) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         range: [fix.range[0], fix.range[1]], | ||||
|         text: fix.text | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Check that a fix has a valid range. | ||||
|  * @param {Fix|null} fix The fix to validate. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function assertValidFix(fix) { | ||||
|     if (fix) { | ||||
|         assert(fix.range && typeof fix.range[0] === "number" && typeof fix.range[1] === "number", `Fix has invalid range: ${JSON.stringify(fix, null, 2)}`); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Compares items in a fixes array by range. | ||||
|  * @param {Fix} a The first message. | ||||
|  * @param {Fix} b The second message. | ||||
|  * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. | ||||
|  * @private | ||||
|  */ | ||||
| function compareFixesByRange(a, b) { | ||||
|     return a.range[0] - b.range[0] || a.range[1] - b.range[1]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Merges the given fixes array into one. | ||||
|  * @param {Fix[]} fixes The fixes to merge. | ||||
|  * @param {SourceCode} sourceCode The source code object to get the text between fixes. | ||||
|  * @returns {{text: string, range: number[]}} The merged fixes | ||||
|  */ | ||||
| function mergeFixes(fixes, sourceCode) { | ||||
|     for (const fix of fixes) { | ||||
|         assertValidFix(fix); | ||||
|     } | ||||
|  | ||||
|     if (fixes.length === 0) { | ||||
|         return null; | ||||
|     } | ||||
|     if (fixes.length === 1) { | ||||
|         return cloneFix(fixes[0]); | ||||
|     } | ||||
|  | ||||
|     fixes.sort(compareFixesByRange); | ||||
|  | ||||
|     const originalText = sourceCode.text; | ||||
|     const start = fixes[0].range[0]; | ||||
|     const end = fixes[fixes.length - 1].range[1]; | ||||
|     let text = ""; | ||||
|     let lastPos = Number.MIN_SAFE_INTEGER; | ||||
|  | ||||
|     for (const fix of fixes) { | ||||
|         assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report."); | ||||
|  | ||||
|         if (fix.range[0] >= 0) { | ||||
|             text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]); | ||||
|         } | ||||
|         text += fix.text; | ||||
|         lastPos = fix.range[1]; | ||||
|     } | ||||
|     text += originalText.slice(Math.max(0, start, lastPos), end); | ||||
|  | ||||
|     return { range: [start, end], text }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Gets one fix object from the given descriptor. | ||||
|  * If the descriptor retrieves multiple fixes, this merges those to one. | ||||
|  * @param {MessageDescriptor} descriptor The report descriptor. | ||||
|  * @param {SourceCode} sourceCode The source code object to get text between fixes. | ||||
|  * @returns {({text: string, range: number[]}|null)} The fix for the descriptor | ||||
|  */ | ||||
| function normalizeFixes(descriptor, sourceCode) { | ||||
|     if (typeof descriptor.fix !== "function") { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     // @type {null | Fix | Fix[] | IterableIterator<Fix>} | ||||
|     const fix = descriptor.fix(ruleFixer); | ||||
|  | ||||
|     // Merge to one. | ||||
|     if (fix && Symbol.iterator in fix) { | ||||
|         return mergeFixes(Array.from(fix), sourceCode); | ||||
|     } | ||||
|  | ||||
|     assertValidFix(fix); | ||||
|     return cloneFix(fix); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Gets an array of suggestion objects from the given descriptor. | ||||
|  * @param {MessageDescriptor} descriptor The report descriptor. | ||||
|  * @param {SourceCode} sourceCode The source code object to get text between fixes. | ||||
|  * @param {Object} messages Object of meta messages for the rule. | ||||
|  * @returns {Array<SuggestionResult>} The suggestions for the descriptor | ||||
|  */ | ||||
| function mapSuggestions(descriptor, sourceCode, messages) { | ||||
|     if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     return descriptor.suggest | ||||
|         .map(suggestInfo => { | ||||
|             const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId]; | ||||
|  | ||||
|             return { | ||||
|                 ...suggestInfo, | ||||
|                 desc: interpolate(computedDesc, suggestInfo.data), | ||||
|                 fix: normalizeFixes(suggestInfo, sourceCode) | ||||
|             }; | ||||
|         }) | ||||
|  | ||||
|         // Remove suggestions that didn't provide a fix | ||||
|         .filter(({ fix }) => fix); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Creates information about the report from a descriptor | ||||
|  * @param {Object} options Information about the problem | ||||
|  * @param {string} options.ruleId Rule ID | ||||
|  * @param {(0|1|2)} options.severity Rule severity | ||||
|  * @param {(ASTNode|null)} options.node Node | ||||
|  * @param {string} options.message Error message | ||||
|  * @param {string} [options.messageId] The error message ID. | ||||
|  * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location | ||||
|  * @param {{text: string, range: (number[]|null)}} options.fix The fix object | ||||
|  * @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects | ||||
|  * @returns {LintMessage} Information about the report | ||||
|  */ | ||||
| function createProblem(options) { | ||||
|     const problem = { | ||||
|         ruleId: options.ruleId, | ||||
|         severity: options.severity, | ||||
|         message: options.message, | ||||
|         line: options.loc.start.line, | ||||
|         column: options.loc.start.column + 1, | ||||
|         nodeType: options.node && options.node.type || null | ||||
|     }; | ||||
|  | ||||
|     /* | ||||
|      * If this isn’t in the conditional, some of the tests fail | ||||
|      * because `messageId` is present in the problem object | ||||
|      */ | ||||
|     if (options.messageId) { | ||||
|         problem.messageId = options.messageId; | ||||
|     } | ||||
|  | ||||
|     if (options.loc.end) { | ||||
|         problem.endLine = options.loc.end.line; | ||||
|         problem.endColumn = options.loc.end.column + 1; | ||||
|     } | ||||
|  | ||||
|     if (options.fix) { | ||||
|         problem.fix = options.fix; | ||||
|     } | ||||
|  | ||||
|     if (options.suggestions && options.suggestions.length > 0) { | ||||
|         problem.suggestions = options.suggestions; | ||||
|     } | ||||
|  | ||||
|     return problem; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Validates that suggestions are properly defined. Throws if an error is detected. | ||||
|  * @param {Array<{ desc?: string, messageId?: string }>} suggest The incoming suggest data. | ||||
|  * @param {Object} messages Object of meta messages for the rule. | ||||
|  * @returns {void} | ||||
|  */ | ||||
| function validateSuggestions(suggest, messages) { | ||||
|     if (suggest && Array.isArray(suggest)) { | ||||
|         suggest.forEach(suggestion => { | ||||
|             if (suggestion.messageId) { | ||||
|                 const { messageId } = suggestion; | ||||
|  | ||||
|                 if (!messages) { | ||||
|                     throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`); | ||||
|                 } | ||||
|  | ||||
|                 if (!messages[messageId]) { | ||||
|                     throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); | ||||
|                 } | ||||
|  | ||||
|                 if (suggestion.desc) { | ||||
|                     throw new TypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one."); | ||||
|                 } | ||||
|             } else if (!suggestion.desc) { | ||||
|                 throw new TypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`"); | ||||
|             } | ||||
|  | ||||
|             if (typeof suggestion.fix !== "function") { | ||||
|                 throw new TypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Returns a function that converts the arguments of a `context.report` call from a rule into a reported | ||||
|  * problem for the Node.js API. | ||||
|  * @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean}} metadata Metadata for the reported problem | ||||
|  * @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted | ||||
|  * @returns {function(...args): LintMessage} Function that returns information about the report | ||||
|  */ | ||||
|  | ||||
| module.exports = function createReportTranslator(metadata) { | ||||
|  | ||||
|     /* | ||||
|      * `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant. | ||||
|      * The report translator itself (i.e. the function that `createReportTranslator` returns) gets | ||||
|      * called every time a rule reports a problem, which happens much less frequently (usually, the vast | ||||
|      * majority of rules don't report any problems for a given file). | ||||
|      */ | ||||
|     return (...args) => { | ||||
|         const descriptor = normalizeMultiArgReportCall(...args); | ||||
|         const messages = metadata.messageIds; | ||||
|  | ||||
|         assertValidNodeInfo(descriptor); | ||||
|  | ||||
|         let computedMessage; | ||||
|  | ||||
|         if (descriptor.messageId) { | ||||
|             if (!messages) { | ||||
|                 throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata."); | ||||
|             } | ||||
|             const id = descriptor.messageId; | ||||
|  | ||||
|             if (descriptor.message) { | ||||
|                 throw new TypeError("context.report() called with a message and a messageId. Please only pass one."); | ||||
|             } | ||||
|             if (!messages || !Object.prototype.hasOwnProperty.call(messages, id)) { | ||||
|                 throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); | ||||
|             } | ||||
|             computedMessage = messages[id]; | ||||
|         } else if (descriptor.message) { | ||||
|             computedMessage = descriptor.message; | ||||
|         } else { | ||||
|             throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem."); | ||||
|         } | ||||
|  | ||||
|         validateSuggestions(descriptor.suggest, messages); | ||||
|  | ||||
|         return createProblem({ | ||||
|             ruleId: metadata.ruleId, | ||||
|             severity: metadata.severity, | ||||
|             node: descriptor.node, | ||||
|             message: interpolate(computedMessage, descriptor.data), | ||||
|             messageId: descriptor.messageId, | ||||
|             loc: normalizeReportLoc(descriptor), | ||||
|             fix: metadata.disableFixes ? null : normalizeFixes(descriptor, metadata.sourceCode), | ||||
|             suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, metadata.sourceCode, messages) | ||||
|         }); | ||||
|     }; | ||||
| }; | ||||
							
								
								
									
										140
									
								
								node_modules/eslint/lib/linter/rule-fixer.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								node_modules/eslint/lib/linter/rule-fixer.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| /** | ||||
|  * @fileoverview An object that creates fix commands for rules. | ||||
|  * @author Nicholas C. Zakas | ||||
|  */ | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| // none! | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Creates a fix command that inserts text at the specified index in the source text. | ||||
|  * @param {int} index The 0-based index at which to insert the new text. | ||||
|  * @param {string} text The text to insert. | ||||
|  * @returns {Object} The fix command. | ||||
|  * @private | ||||
|  */ | ||||
| function insertTextAt(index, text) { | ||||
|     return { | ||||
|         range: [index, index], | ||||
|         text | ||||
|     }; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Creates code fixing commands for rules. | ||||
|  */ | ||||
|  | ||||
| const ruleFixer = Object.freeze({ | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that inserts text after the given node or token. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {ASTNode|Token} nodeOrToken The node or token to insert after. | ||||
|      * @param {string} text The text to insert. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     insertTextAfter(nodeOrToken, text) { | ||||
|         return this.insertTextAfterRange(nodeOrToken.range, text); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that inserts text after the specified range in the source text. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {int[]} range The range to replace, first item is start of range, second | ||||
|      *      is end of range. | ||||
|      * @param {string} text The text to insert. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     insertTextAfterRange(range, text) { | ||||
|         return insertTextAt(range[1], text); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that inserts text before the given node or token. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {ASTNode|Token} nodeOrToken The node or token to insert before. | ||||
|      * @param {string} text The text to insert. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     insertTextBefore(nodeOrToken, text) { | ||||
|         return this.insertTextBeforeRange(nodeOrToken.range, text); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that inserts text before the specified range in the source text. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {int[]} range The range to replace, first item is start of range, second | ||||
|      *      is end of range. | ||||
|      * @param {string} text The text to insert. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     insertTextBeforeRange(range, text) { | ||||
|         return insertTextAt(range[0], text); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that replaces text at the node or token. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {ASTNode|Token} nodeOrToken The node or token to remove. | ||||
|      * @param {string} text The text to insert. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     replaceText(nodeOrToken, text) { | ||||
|         return this.replaceTextRange(nodeOrToken.range, text); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that replaces text at the specified range in the source text. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {int[]} range The range to replace, first item is start of range, second | ||||
|      *      is end of range. | ||||
|      * @param {string} text The text to insert. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     replaceTextRange(range, text) { | ||||
|         return { | ||||
|             range, | ||||
|             text | ||||
|         }; | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that removes the node or token from the source. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {ASTNode|Token} nodeOrToken The node or token to remove. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     remove(nodeOrToken) { | ||||
|         return this.removeRange(nodeOrToken.range); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Creates a fix command that removes the specified range of text from the source. | ||||
|      * The fix is not applied until applyFixes() is called. | ||||
|      * @param {int[]} range The range to remove, first item is start of range, second | ||||
|      *      is end of range. | ||||
|      * @returns {Object} The fix command. | ||||
|      */ | ||||
|     removeRange(range) { | ||||
|         return { | ||||
|             range, | ||||
|             text: "" | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| }); | ||||
|  | ||||
|  | ||||
| module.exports = ruleFixer; | ||||
							
								
								
									
										80
									
								
								node_modules/eslint/lib/linter/rules.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								node_modules/eslint/lib/linter/rules.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| /** | ||||
|  * @fileoverview Defines a storage for rules. | ||||
|  * @author Nicholas C. Zakas | ||||
|  * @author aladdin-add | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const builtInRules = require("../rules"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Normalizes a rule module to the new-style API | ||||
|  * @param {(Function|{create: Function})} rule A rule object, which can either be a function | ||||
|  * ("old-style") or an object with a `create` method ("new-style") | ||||
|  * @returns {{create: Function}} A new-style rule. | ||||
|  */ | ||||
| function normalizeRule(rule) { | ||||
|     return typeof rule === "function" ? Object.assign({ create: rule }, rule) : rule; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * A storage for rules. | ||||
|  */ | ||||
| class Rules { | ||||
|     constructor() { | ||||
|         this._rules = Object.create(null); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Registers a rule module for rule id in storage. | ||||
|      * @param {string} ruleId Rule id (file name). | ||||
|      * @param {Function} ruleModule Rule handler. | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     define(ruleId, ruleModule) { | ||||
|         this._rules[ruleId] = normalizeRule(ruleModule); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Access rule handler by id (file name). | ||||
|      * @param {string} ruleId Rule id (file name). | ||||
|      * @returns {{create: Function, schema: JsonSchema[]}} | ||||
|      * A rule. This is normalized to always have the new-style shape with a `create` method. | ||||
|      */ | ||||
|     get(ruleId) { | ||||
|         if (typeof this._rules[ruleId] === "string") { | ||||
|             this.define(ruleId, require(this._rules[ruleId])); | ||||
|         } | ||||
|         if (this._rules[ruleId]) { | ||||
|             return this._rules[ruleId]; | ||||
|         } | ||||
|         if (builtInRules.has(ruleId)) { | ||||
|             return builtInRules.get(ruleId); | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     *[Symbol.iterator]() { | ||||
|         yield* builtInRules; | ||||
|  | ||||
|         for (const ruleId of Object.keys(this._rules)) { | ||||
|             yield [ruleId, this.get(ruleId)]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Rules; | ||||
							
								
								
									
										52
									
								
								node_modules/eslint/lib/linter/safe-emitter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								node_modules/eslint/lib/linter/safe-emitter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| /** | ||||
|  * @fileoverview A variant of EventEmitter which does not give listeners information about each other | ||||
|  * @author Teddy Katz | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Typedefs | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * An event emitter | ||||
|  * @typedef {Object} SafeEmitter | ||||
|  * @property {(eventName: string, listenerFunc: Function) => void} on Adds a listener for a given event name | ||||
|  * @property {(eventName: string, arg1?: any, arg2?: any, arg3?: any) => void} emit Emits an event with a given name. | ||||
|  * This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments. | ||||
|  * @property {function(): string[]} eventNames Gets the list of event names that have registered listeners. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Creates an object which can listen for and emit events. | ||||
|  * This is similar to the EventEmitter API in Node's standard library, but it has a few differences. | ||||
|  * The goal is to allow multiple modules to attach arbitrary listeners to the same emitter, without | ||||
|  * letting the modules know about each other at all. | ||||
|  * 1. It has no special keys like `error` and `newListener`, which would allow modules to detect when | ||||
|  * another module throws an error or registers a listener. | ||||
|  * 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a | ||||
|  * `this` value of the emitter instance, which would give listeners access to other listeners.) | ||||
|  * @returns {SafeEmitter} An emitter | ||||
|  */ | ||||
| module.exports = () => { | ||||
|     const listeners = Object.create(null); | ||||
|  | ||||
|     return Object.freeze({ | ||||
|         on(eventName, listener) { | ||||
|             if (eventName in listeners) { | ||||
|                 listeners[eventName].push(listener); | ||||
|             } else { | ||||
|                 listeners[eventName] = [listener]; | ||||
|             } | ||||
|         }, | ||||
|         emit(eventName, ...args) { | ||||
|             if (eventName in listeners) { | ||||
|                 listeners[eventName].forEach(listener => listener(...args)); | ||||
|             } | ||||
|         }, | ||||
|         eventNames() { | ||||
|             return Object.keys(listeners); | ||||
|         } | ||||
|     }); | ||||
| }; | ||||
							
								
								
									
										152
									
								
								node_modules/eslint/lib/linter/source-code-fixer.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								node_modules/eslint/lib/linter/source-code-fixer.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| /** | ||||
|  * @fileoverview An object that caches and applies source code fixes. | ||||
|  * @author Nicholas C. Zakas | ||||
|  */ | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Requirements | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const debug = require("debug")("eslint:source-code-fixer"); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const BOM = "\uFEFF"; | ||||
|  | ||||
| /** | ||||
|  * Compares items in a messages array by range. | ||||
|  * @param {Message} a The first message. | ||||
|  * @param {Message} b The second message. | ||||
|  * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. | ||||
|  * @private | ||||
|  */ | ||||
| function compareMessagesByFixRange(a, b) { | ||||
|     return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Compares items in a messages array by line and column. | ||||
|  * @param {Message} a The first message. | ||||
|  * @param {Message} b The second message. | ||||
|  * @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal. | ||||
|  * @private | ||||
|  */ | ||||
| function compareMessagesByLocation(a, b) { | ||||
|     return a.line - b.line || a.column - b.column; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Public Interface | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /** | ||||
|  * Utility for apply fixes to source code. | ||||
|  * @constructor | ||||
|  */ | ||||
| function SourceCodeFixer() { | ||||
|     Object.freeze(this); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Applies the fixes specified by the messages to the given text. Tries to be | ||||
|  * smart about the fixes and won't apply fixes over the same area in the text. | ||||
|  * @param {string} sourceText The text to apply the changes to. | ||||
|  * @param {Message[]} messages The array of messages reported by ESLint. | ||||
|  * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed | ||||
|  * @returns {Object} An object containing the fixed text and any unfixed messages. | ||||
|  */ | ||||
| SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) { | ||||
|     debug("Applying fixes"); | ||||
|  | ||||
|     if (shouldFix === false) { | ||||
|         debug("shouldFix parameter was false, not attempting fixes"); | ||||
|         return { | ||||
|             fixed: false, | ||||
|             messages, | ||||
|             output: sourceText | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     // clone the array | ||||
|     const remainingMessages = [], | ||||
|         fixes = [], | ||||
|         bom = sourceText.startsWith(BOM) ? BOM : "", | ||||
|         text = bom ? sourceText.slice(1) : sourceText; | ||||
|     let lastPos = Number.NEGATIVE_INFINITY, | ||||
|         output = bom; | ||||
|  | ||||
|     /** | ||||
|      * Try to use the 'fix' from a problem. | ||||
|      * @param {Message} problem The message object to apply fixes from | ||||
|      * @returns {boolean} Whether fix was successfully applied | ||||
|      */ | ||||
|     function attemptFix(problem) { | ||||
|         const fix = problem.fix; | ||||
|         const start = fix.range[0]; | ||||
|         const end = fix.range[1]; | ||||
|  | ||||
|         // Remain it as a problem if it's overlapped or it's a negative range | ||||
|         if (lastPos >= start || start > end) { | ||||
|             remainingMessages.push(problem); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Remove BOM. | ||||
|         if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) { | ||||
|             output = ""; | ||||
|         } | ||||
|  | ||||
|         // Make output to this fix. | ||||
|         output += text.slice(Math.max(0, lastPos), Math.max(0, start)); | ||||
|         output += fix.text; | ||||
|         lastPos = end; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     messages.forEach(problem => { | ||||
|         if (Object.prototype.hasOwnProperty.call(problem, "fix")) { | ||||
|             fixes.push(problem); | ||||
|         } else { | ||||
|             remainingMessages.push(problem); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     if (fixes.length) { | ||||
|         debug("Found fixes to apply"); | ||||
|         let fixesWereApplied = false; | ||||
|  | ||||
|         for (const problem of fixes.sort(compareMessagesByFixRange)) { | ||||
|             if (typeof shouldFix !== "function" || shouldFix(problem)) { | ||||
|                 attemptFix(problem); | ||||
|  | ||||
|                 /* | ||||
|                  * The only time attemptFix will fail is if a previous fix was | ||||
|                  * applied which conflicts with it.  So we can mark this as true. | ||||
|                  */ | ||||
|                 fixesWereApplied = true; | ||||
|             } else { | ||||
|                 remainingMessages.push(problem); | ||||
|             } | ||||
|         } | ||||
|         output += text.slice(Math.max(0, lastPos)); | ||||
|  | ||||
|         return { | ||||
|             fixed: fixesWereApplied, | ||||
|             messages: remainingMessages.sort(compareMessagesByLocation), | ||||
|             output | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     debug("No fixes to apply"); | ||||
|     return { | ||||
|         fixed: false, | ||||
|         messages, | ||||
|         output: bom + text | ||||
|     }; | ||||
|  | ||||
| }; | ||||
|  | ||||
| module.exports = SourceCodeFixer; | ||||
							
								
								
									
										161
									
								
								node_modules/eslint/lib/linter/timing.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								node_modules/eslint/lib/linter/timing.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| /** | ||||
|  * @fileoverview Tracks performance of individual rules. | ||||
|  * @author Brandon Mills | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Helpers | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| /* c8 ignore next */ | ||||
| /** | ||||
|  * Align the string to left | ||||
|  * @param {string} str string to evaluate | ||||
|  * @param {int} len length of the string | ||||
|  * @param {string} ch delimiter character | ||||
|  * @returns {string} modified string | ||||
|  * @private | ||||
|  */ | ||||
| function alignLeft(str, len, ch) { | ||||
|     return str + new Array(len - str.length + 1).join(ch || " "); | ||||
| } | ||||
|  | ||||
| /* c8 ignore next */ | ||||
| /** | ||||
|  * Align the string to right | ||||
|  * @param {string} str string to evaluate | ||||
|  * @param {int} len length of the string | ||||
|  * @param {string} ch delimiter character | ||||
|  * @returns {string} modified string | ||||
|  * @private | ||||
|  */ | ||||
| function alignRight(str, len, ch) { | ||||
|     return new Array(len - str.length + 1).join(ch || " ") + str; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Module definition | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| const enabled = !!process.env.TIMING; | ||||
|  | ||||
| const HEADERS = ["Rule", "Time (ms)", "Relative"]; | ||||
| const ALIGN = [alignLeft, alignRight, alignRight]; | ||||
|  | ||||
| /** | ||||
|  * Decide how many rules to show in the output list. | ||||
|  * @returns {number} the number of rules to show | ||||
|  */ | ||||
| function getListSize() { | ||||
|     const MINIMUM_SIZE = 10; | ||||
|  | ||||
|     if (typeof process.env.TIMING !== "string") { | ||||
|         return MINIMUM_SIZE; | ||||
|     } | ||||
|  | ||||
|     if (process.env.TIMING.toLowerCase() === "all") { | ||||
|         return Number.POSITIVE_INFINITY; | ||||
|     } | ||||
|  | ||||
|     const TIMING_ENV_VAR_AS_INTEGER = Number.parseInt(process.env.TIMING, 10); | ||||
|  | ||||
|     return TIMING_ENV_VAR_AS_INTEGER > 10 ? TIMING_ENV_VAR_AS_INTEGER : MINIMUM_SIZE; | ||||
| } | ||||
|  | ||||
| /* c8 ignore next */ | ||||
| /** | ||||
|  * display the data | ||||
|  * @param {Object} data Data object to be displayed | ||||
|  * @returns {void} prints modified string with console.log | ||||
|  * @private | ||||
|  */ | ||||
| function display(data) { | ||||
|     let total = 0; | ||||
|     const rows = Object.keys(data) | ||||
|         .map(key => { | ||||
|             const time = data[key]; | ||||
|  | ||||
|             total += time; | ||||
|             return [key, time]; | ||||
|         }) | ||||
|         .sort((a, b) => b[1] - a[1]) | ||||
|         .slice(0, getListSize()); | ||||
|  | ||||
|     rows.forEach(row => { | ||||
|         row.push(`${(row[1] * 100 / total).toFixed(1)}%`); | ||||
|         row[1] = row[1].toFixed(3); | ||||
|     }); | ||||
|  | ||||
|     rows.unshift(HEADERS); | ||||
|  | ||||
|     const widths = []; | ||||
|  | ||||
|     rows.forEach(row => { | ||||
|         const len = row.length; | ||||
|  | ||||
|         for (let i = 0; i < len; i++) { | ||||
|             const n = row[i].length; | ||||
|  | ||||
|             if (!widths[i] || n > widths[i]) { | ||||
|                 widths[i] = n; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     const table = rows.map(row => ( | ||||
|         row | ||||
|             .map((cell, index) => ALIGN[index](cell, widths[index])) | ||||
|             .join(" | ") | ||||
|     )); | ||||
|  | ||||
|     table.splice(1, 0, widths.map((width, index) => { | ||||
|         const extraAlignment = index !== 0 && index !== widths.length - 1 ? 2 : 1; | ||||
|  | ||||
|         return ALIGN[index](":", width + extraAlignment, "-"); | ||||
|     }).join("|")); | ||||
|  | ||||
|     console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function | ||||
| } | ||||
|  | ||||
| /* c8 ignore next */ | ||||
| module.exports = (function() { | ||||
|  | ||||
|     const data = Object.create(null); | ||||
|  | ||||
|     /** | ||||
|      * Time the run | ||||
|      * @param {any} key key from the data object | ||||
|      * @param {Function} fn function to be called | ||||
|      * @returns {Function} function to be executed | ||||
|      * @private | ||||
|      */ | ||||
|     function time(key, fn) { | ||||
|         if (typeof data[key] === "undefined") { | ||||
|             data[key] = 0; | ||||
|         } | ||||
|  | ||||
|         return function(...args) { | ||||
|             let t = process.hrtime(); | ||||
|             const result = fn(...args); | ||||
|  | ||||
|             t = process.hrtime(t); | ||||
|             data[key] += t[0] * 1e3 + t[1] / 1e6; | ||||
|             return result; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     if (enabled) { | ||||
|         process.on("exit", () => { | ||||
|             display(data); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         time, | ||||
|         enabled, | ||||
|         getListSize | ||||
|     }; | ||||
|  | ||||
| }()); | ||||
		Reference in New Issue
	
	Block a user