]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/tools/code-sample-minimizer.js
512b692fe74ca56444cc4841192fb3046e46ed18
3 const evk
= require("eslint-visitor-keys");
4 const recast
= require("recast");
5 const espree
= require("espree");
6 const assert
= require("assert");
9 * Determines whether an AST node could be an expression, based on the type
10 * @param {ASTNode} node The node
11 * @returns {boolean} `true` if the node could be an expression
13 function isMaybeExpression(node
) {
14 return node
.type
.endsWith("Expression") ||
15 node
.type
=== "Identifier" ||
16 node
.type
=== "MetaProperty" ||
17 node
.type
.endsWith("Literal");
21 * Determines whether an AST node is a statement
22 * @param {ASTNode} node The node
23 * @returns {boolean} `true` if the node is a statement
25 function isStatement(node
) {
26 return node
.type
.endsWith("Statement") || node
.type
.endsWith("Declaration");
30 * Given "bad" source text (e.g. an code sample that causes a rule to crash), tries to return a smaller
31 * piece of source text which is also "bad", to make it easier for a human to figure out where the
33 * @param {Object} options Options to process
34 * @param {string} options.sourceText Initial piece of "bad" source text
35 * @param {function(string): boolean} options.predicate A predicate that returns `true` for bad source text and `false` for good source text
36 * @param {Parser} [options.parser] The parser used to parse the source text. Defaults to a modified
37 * version of espree that uses recent parser options.
38 * @param {Object} [options.visitorKeys] The visitor keys of the AST. Defaults to eslint-visitor-keys.
39 * @returns {string} Another piece of "bad" source text, which may or may not be smaller than the original source text.
41 function reduceBadExampleSize({
45 parse
: (code
, options
) =>
53 eslintVisitorKeys
: true,
54 eslintScopeManager
: true,
59 visitorKeys
= evk
.KEYS
64 * Returns a new unique identifier
65 * @returns {string} A name for a new identifier
67 function generateNewIdentifierName() {
68 return `$${(counter++)}`;
72 * Determines whether a source text sample is "bad"
73 * @param {string} updatedSourceText The sample
74 * @returns {boolean} `true` if the sample is "bad"
76 function reproducesBadCase(updatedSourceText
) {
78 parser
.parse(updatedSourceText
);
83 return predicate(updatedSourceText
);
86 assert(reproducesBadCase(sourceText
), "Original source text should reproduce issue");
87 const parseResult
= recast
.parse(sourceText
, { parser
});
90 * Recursively removes descendant subtrees of the given AST node and replaces
91 * them with simplified variants to produce a simplified AST which is still considered "bad".
92 * @param {ASTNode} node An AST node to prune. May be mutated by this call, but the
93 * resulting AST will still produce "bad" source code.
96 function pruneIrrelevantSubtrees(node
) {
97 for (const key
of visitorKeys
[node
.type
]) {
98 if (Array
.isArray(node
[key
])) {
99 for (let index
= node
[key
].length
- 1; index
>= 0; index
--) {
100 const [childNode
] = node
[key
].splice(index
, 1);
102 if (!reproducesBadCase(recast
.print(parseResult
).code
)) {
103 node
[key
].splice(index
, 0, childNode
);
105 pruneIrrelevantSubtrees(childNode
);
109 } else if (typeof node
[key
] === "object" && node
[key
] !== null) {
111 const childNode
= node
[key
];
113 if (isMaybeExpression(childNode
)) {
114 node
[key
] = { type
: "Identifier", name
: generateNewIdentifierName(), range
: childNode
.range
};
115 if (!reproducesBadCase(recast
.print(parseResult
).code
)) {
116 node
[key
] = childNode
;
117 pruneIrrelevantSubtrees(childNode
);
119 } else if (isStatement(childNode
)) {
120 node
[key
] = { type
: "EmptyStatement", range
: childNode
.range
};
121 if (!reproducesBadCase(recast
.print(parseResult
).code
)) {
122 node
[key
] = childNode
;
123 pruneIrrelevantSubtrees(childNode
);
131 * Recursively tries to extract a descendant node from the AST that is "bad" on its own
132 * @param {ASTNode} node A node which produces "bad" source code
133 * @returns {ASTNode} A descendent of `node` which is also bad
135 function extractRelevantChild(node
) {
136 const childNodes
= [].concat(
137 ...visitorKeys
[node
.type
]
138 .map(key
=> (Array
.isArray(node
[key
]) ? node
[key
] : [node
[key
]]))
141 for (const childNode
of childNodes
) {
146 if (isMaybeExpression(childNode
)) {
147 if (reproducesBadCase(recast
.print(childNode
).code
)) {
148 return extractRelevantChild(childNode
);
151 } else if (isStatement(childNode
)) {
152 if (reproducesBadCase(recast
.print(childNode
).code
)) {
153 return extractRelevantChild(childNode
);
156 const childResult
= extractRelevantChild(childNode
);
158 if (reproducesBadCase(recast
.print(childResult
).code
)) {
167 * Removes and simplifies comments from the source text
168 * @param {string} text A piece of "bad" source text
169 * @returns {string} A piece of "bad" source text with fewer and/or simpler comments.
171 function removeIrrelevantComments(text
) {
172 const ast
= parser
.parse(text
);
175 for (const comment
of ast
.comments
) {
176 for (const potentialSimplification
of [
178 // Try deleting the comment
179 `${text.slice(0, comment.range[0])}${text.slice(comment.range[1])}`,
181 // Try replacing the comment with a space
182 `${text.slice(0, comment.range[0])} ${text.slice(comment.range[1])}`,
184 // Try deleting the contents of the comment
185 text
.slice(0, comment
.range
[0] + 2) + text
.slice(comment
.type
=== "Block" ? comment
.range
[1] - 2 : comment
.range
[1])
187 if (reproducesBadCase(potentialSimplification
)) {
188 return removeIrrelevantComments(potentialSimplification
);
197 pruneIrrelevantSubtrees(parseResult
.program
);
198 const relevantChild
= recast
.print(extractRelevantChild(parseResult
.program
)).code
;
200 assert(reproducesBadCase(relevantChild
), "Extracted relevant source text should reproduce issue");
201 const result
= removeIrrelevantComments(relevantChild
);
203 assert(reproducesBadCase(result
), "Source text with irrelevant comments removed should reproduce issue");
207 module
.exports
= reduceBadExampleSize
;