]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/linter/node-event-generator.js
2 * @fileoverview Tests for NodeEventGenerator.
3 * @author Toru Nagashima
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const assert
= require ( "assert" ),
12 sinon
= require ( "sinon" ),
13 espree
= require ( "espree" ),
14 vk
= require ( "eslint-visitor-keys" ),
15 Traverser
= require ( "../../../lib/shared/traverser" ),
16 EventGeneratorTester
= require ( "../../../tools/internal-testers/event-generator-tester" ),
17 createEmitter
= require ( "../../../lib/linter/safe-emitter" ),
18 NodeEventGenerator
= require ( "../../../lib/linter/node-event-generator" );
21 //------------------------------------------------------------------------------
23 //------------------------------------------------------------------------------
25 const ESPREE_CONFIG
= {
33 const STANDARD_ESQUERY_OPTION
= { visitorKeys
: vk
. KEYS
, fallback
: Traverser
. getKeys
};
35 //------------------------------------------------------------------------------
37 //------------------------------------------------------------------------------
39 describe ( "NodeEventGenerator" , () => {
40 EventGeneratorTester
. testEventGeneratorInterface (
41 new NodeEventGenerator ( createEmitter (), STANDARD_ESQUERY_OPTION
)
44 describe ( "entering a single AST node" , () => {
45 let emitter
, generator
;
48 emitter
= Object
. create ( createEmitter (), { emit
: { value
: sinon
. spy () } });
50 [ "Foo" , "Bar" , "Foo > Bar" , "Foo:exit" ]. forEach ( selector
=> emitter
. on ( selector
, () => {}));
51 generator
= new NodeEventGenerator ( emitter
, STANDARD_ESQUERY_OPTION
);
54 it ( "should generate events for entering AST node." , () => {
55 const dummyNode
= { type
: "Foo" , value
: 1 };
57 generator
. enterNode ( dummyNode
);
59 assert ( emitter
. emit
. calledOnce
);
60 assert ( emitter
. emit
. calledWith ( "Foo" , dummyNode
));
63 it ( "should generate events for exitting AST node." , () => {
64 const dummyNode
= { type
: "Foo" , value
: 1 };
66 generator
. leaveNode ( dummyNode
);
68 assert ( emitter
. emit
. calledOnce
);
69 assert ( emitter
. emit
. calledWith ( "Foo:exit" , dummyNode
));
72 it ( "should generate events for AST queries" , () => {
73 const dummyNode
= { type
: "Bar" , parent
: { type
: "Foo" } };
75 generator
. enterNode ( dummyNode
);
77 assert ( emitter
. emit
. calledTwice
);
78 assert ( emitter
. emit
. calledWith ( "Foo > Bar" , dummyNode
));
82 describe ( "traversing the entire AST" , () => {
85 * Gets a list of emitted types/selectors from the generator, in emission order
86 * @param {ASTNode} ast The AST to traverse
87 * @param {Array<string>|Set<string>} possibleQueries Selectors to detect
88 * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element
89 * array where the first element is a string, and the second element is the emitted AST node.
91 function getEmissions ( ast
, possibleQueries
) {
93 const emitter
= Object
. create ( createEmitter (), {
95 value
: ( selector
, node
) => emissions
. push ([ selector
, node
])
99 possibleQueries
. forEach ( query
=> emitter
. on ( query
, () => {}));
100 const generator
= new NodeEventGenerator ( emitter
, STANDARD_ESQUERY_OPTION
);
102 Traverser
. traverse ( ast
, {
103 enter ( node
, parent
) {
104 node
. parent
= parent
;
105 generator
. enterNode ( node
);
108 generator
. leaveNode ( node
);
116 * Creates a test case that asserts a particular sequence of generator emissions
117 * @param {string} sourceText The source text that should be parsed and traversed
118 * @param {string[]} possibleQueries A collection of selectors that rules are listening for
119 * @param {Array[]} expectedEmissions A function that accepts the AST and returns a list of the emissions that the
120 * generator is expected to produce, in order.
121 * Each element of this list is an array where the first element is a selector (string), and the second is an AST node
122 * This should only include emissions that appear in possibleQueries.
125 function assertEmissions ( sourceText
, possibleQueries
, expectedEmissions
) {
126 it ( possibleQueries
. join ( "; " ), () => {
127 const ast
= espree
. parse ( sourceText
, ESPREE_CONFIG
);
128 const emissions
= getEmissions ( ast
, possibleQueries
)
129 . filter ( emission
=> possibleQueries
. indexOf ( emission
[ 0 ]) !== - 1 );
131 assert
. deepStrictEqual ( emissions
, expectedEmissions ( ast
));
137 [ "Program" , "Program:exit" , "ExpressionStatement" , "ExpressionStatement:exit" , "BinaryExpression" , "BinaryExpression:exit" , "Identifier" , "Identifier:exit" ],
139 [ "Program" , ast
], // entering program
140 [ "ExpressionStatement" , ast
. body
[ 0 ]], // entering 'foo + bar;'
141 [ "BinaryExpression" , ast
. body
[ 0 ]. expression
], // entering 'foo + bar'
142 [ "Identifier" , ast
. body
[ 0 ]. expression
. left
], // entering 'foo'
143 [ "Identifier:exit" , ast
. body
[ 0 ]. expression
. left
], // exiting 'foo'
144 [ "Identifier" , ast
. body
[ 0 ]. expression
. right
], // entering 'bar'
145 [ "Identifier:exit" , ast
. body
[ 0 ]. expression
. right
], // exiting 'bar'
146 [ "BinaryExpression:exit" , ast
. body
[ 0 ]. expression
], // exiting 'foo + bar'
147 [ "ExpressionStatement:exit" , ast
. body
[ 0 ]], // exiting 'foo + bar;'
148 [ "Program:exit" , ast
] // exiting program
155 "BinaryExpression > Identifier" ,
157 "BinaryExpression Literal:exit" ,
158 "BinaryExpression > Identifier:exit" ,
159 "BinaryExpression:exit"
162 [ "BinaryExpression" , ast
. body
[ 0 ]. expression
], // foo + 5
163 [ "BinaryExpression > Identifier" , ast
. body
[ 0 ]. expression
. left
], // foo
164 [ "BinaryExpression > Identifier:exit" , ast
. body
[ 0 ]. expression
. left
], // exiting foo
165 [ "BinaryExpression Literal:exit" , ast
. body
[ 0 ]. expression
. right
], // exiting 5
166 [ "BinaryExpression:exit" , ast
. body
[ 0 ]. expression
] // exiting foo + 5
172 [ "BinaryExpression > *[name='foo']" ],
173 ast
=> [[ "BinaryExpression > *[name='foo']" , ast
. body
[ 0 ]. expression
. left
]] // entering foo
180 [ "*" , ast
], // Program
181 [ "*" , ast
. body
[ 0 ]], // ExpressionStatement
182 [ "*" , ast
. body
[ 0 ]. expression
] // Identifier
188 [ "*:not(ExpressionStatement)" ],
190 [ "*:not(ExpressionStatement)" , ast
], // Program
191 [ "*:not(ExpressionStatement)" , ast
. body
[ 0 ]. expression
] // Identifier
197 [ "CallExpression[callee.name='foo']" ],
198 ast
=> [[ "CallExpression[callee.name='foo']" , ast
. body
[ 0 ]. expression
]] // foo()
203 [ "CallExpression[callee.name='bar']" ],
204 () => [] // (nothing emitted)
210 () => [] // (nothing emitted)
215 [ ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])" ],
217 [ ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])" , ast
. body
[ 0 ]. expression
. left
. left
], // foo
218 [ ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])" , ast
. body
[ 0 ]. expression
. left
. right
], // bar
219 [ ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])" , ast
. body
[ 0 ]. expression
. right
] // baz
225 [ "Identifier, Literal[value=5]" ],
227 [ "Identifier, Literal[value=5]" , ast
. body
[ 0 ]. expression
. left
. left
], // foo
228 [ "Identifier, Literal[value=5]" , ast
. body
[ 0 ]. expression
. left
. right
] // 5
234 [ "Identifier + Literal" ],
235 ast
=> [[ "Identifier + Literal" , ast
. body
[ 0 ]. expression
. elements
[ 1 ]]] // 5
240 [ "Identifier + Literal" , "Identifier ~ Literal" ],
241 ast
=> [[ "Identifier ~ Literal" , ast
. body
[ 0 ]. expression
. elements
[ 2 ]]] // 5
245 "foo; bar + baz; qux()" ,
246 [ ":expression" , ":statement" ],
248 [ ":statement" , ast
. body
[ 0 ]],
249 [ ":expression" , ast
. body
[ 0 ]. expression
],
250 [ ":statement" , ast
. body
[ 1 ]],
251 [ ":expression" , ast
. body
[ 1 ]. expression
],
252 [ ":expression" , ast
. body
[ 1 ]. expression
. left
],
253 [ ":expression" , ast
. body
[ 1 ]. expression
. right
],
254 [ ":statement" , ast
. body
[ 2 ]],
255 [ ":expression" , ast
. body
[ 2 ]. expression
],
256 [ ":expression" , ast
. body
[ 2 ]. expression
. callee
]
261 "function foo(){} var x; (function (p){}); () => {};" ,
262 [ ":function" , "ExpressionStatement > :function" , "VariableDeclaration, :function[params.length=1]" ],
264 [ ":function" , ast
. body
[ 0 ]], // function foo(){}
265 [ "VariableDeclaration, :function[params.length=1]" , ast
. body
[ 1 ]], // var x;
266 [ ":function" , ast
. body
[ 2 ]. expression
], // function (p){}
267 [ "ExpressionStatement > :function" , ast
. body
[ 2 ]. expression
], // function (p){}
268 [ "VariableDeclaration, :function[params.length=1]" , ast
. body
[ 2 ]. expression
], // function (p){}
269 [ ":function" , ast
. body
[ 3 ]. expression
], // () => {}
270 [ "ExpressionStatement > :function" , ast
. body
[ 3 ]. expression
] // () => {}
280 "ExpressionStatement > *" ,
281 "ExpressionStatement > Identifier" ,
282 "ExpressionStatement > [name='foo']" ,
283 "Identifier, ReturnStatement" ,
288 "Identifier[name='foo']" ,
289 "[name='foo'][name.length=3]" ,
290 ":not(Program, ExpressionStatement)" ,
291 ":not(Program, Identifier) > [name.length=3]"
294 [ "*" , ast
], // Program
295 [ "*" , ast
. body
[ 0 ]], // ExpressionStatement
297 // selectors for the 'foo' identifier, in order of increasing specificity
298 [ "*" , ast
. body
[ 0 ]. expression
], // 0 identifiers, 0 pseudoclasses
299 [ "ExpressionStatement > *" , ast
. body
[ 0 ]. expression
], // 0 pseudoclasses, 1 identifier
300 [ "Identifier" , ast
. body
[ 0 ]. expression
], // 0 pseudoclasses, 1 identifier
301 [ ":not(Program, ExpressionStatement)" , ast
. body
[ 0 ]. expression
], // 0 pseudoclasses, 2 identifiers
302 [ "ExpressionStatement > Identifier" , ast
. body
[ 0 ]. expression
], // 0 pseudoclasses, 2 identifiers
303 [ "Identifier, ReturnStatement" , ast
. body
[ 0 ]. expression
], // 0 pseudoclasses, 2 identifiers
304 [ "[name = 'foo']" , ast
. body
[ 0 ]. expression
], // 1 pseudoclass, 0 identifiers
305 [ "[name ='foo']" , ast
. body
[ 0 ]. expression
], // 1 pseudoclass, 0 identifiers
306 [ "[name='foo']" , ast
. body
[ 0 ]. expression
], // 1 pseudoclass, 0 identifiers
307 [ "ExpressionStatement > [name='foo']" , ast
. body
[ 0 ]. expression
], // 1 attribute, 1 identifier
308 [ "Identifier[name='foo']" , ast
. body
[ 0 ]. expression
], // 1 attribute, 1 identifier
309 [ ":not(Program, Identifier) > [name.length=3]" , ast
. body
[ 0 ]. expression
], // 1 attribute, 2 identifiers
310 [ "[name='foo'][name.length=3]" , ast
. body
[ 0 ]. expression
] // 2 attributes, 0 identifiers
316 [ "CallExpression, [name='bar']" ],
318 [ "CallExpression, [name='bar']" , ast
. body
[ 0 ]. expression
],
319 [ "CallExpression, [name='bar']" , ast
. body
[ 1 ]. expression
]
325 [ "[name.length=3]:exit" ],
327 [ "[name.length=3]:exit" , ast
. body
[ 0 ]. expression
],
328 [ "[name.length=3]:exit" , ast
. body
[ 1 ]. expression
]
332 // https://github.com/eslint/eslint/issues/14799
334 "const {a = 1} = b;" ,
337 [ "Property > .key" , ast
. body
[ 0 ]. declarations
[ 0 ]. id
. properties
[ 0 ]. key
]
342 describe ( "traversing the entire non-standard AST" , () => {
345 * Gets a list of emitted types/selectors from the generator, in emission order
346 * @param {ASTNode} ast The AST to traverse
347 * @param {Record<string, string[]>} visitorKeys The custom visitor keys.
348 * @param {Array<string>|Set<string>} possibleQueries Selectors to detect
349 * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element
350 * array where the first element is a string, and the second element is the emitted AST node.
352 function getEmissions ( ast
, visitorKeys
, possibleQueries
) {
353 const emissions
= [];
354 const emitter
= Object
. create ( createEmitter (), {
356 value
: ( selector
, node
) => emissions
. push ([ selector
, node
])
360 possibleQueries
. forEach ( query
=> emitter
. on ( query
, () => {}));
361 const generator
= new NodeEventGenerator ( emitter
, { visitorKeys
, fallback
: Traverser
. getKeys
});
363 Traverser
. traverse ( ast
, {
365 enter ( node
, parent
) {
366 node
. parent
= parent
;
367 generator
. enterNode ( node
);
370 generator
. leaveNode ( node
);
378 * Creates a test case that asserts a particular sequence of generator emissions
379 * @param {ASTNode} ast The AST to traverse
380 * @param {Record<string, string[]>} visitorKeys The custom visitor keys.
381 * @param {string[]} possibleQueries A collection of selectors that rules are listening for
382 * @param {Array[]} expectedEmissions A function that accepts the AST and returns a list of the emissions that the
383 * generator is expected to produce, in order.
384 * Each element of this list is an array where the first element is a selector (string), and the second is an AST node
385 * This should only include emissions that appear in possibleQueries.
388 function assertEmissions ( ast
, visitorKeys
, possibleQueries
, expectedEmissions
) {
389 it ( possibleQueries
. join ( "; " ), () => {
390 const emissions
= getEmissions ( ast
, visitorKeys
, possibleQueries
)
391 . filter ( emission
=> possibleQueries
. indexOf ( emission
[ 0 ]) !== - 1 );
393 assert
. deepStrictEqual ( emissions
, expectedEmissions ( ast
));
398 espree
. parse ( "const foo = [<div/>, <div/>]" , { ... ESPREE_CONFIG
, ecmaFeatures
: { jsx
: true } }),
402 [ "* ~ *" , ast
. body
[ 0 ]. declarations
[ 0 ]. init
. elements
[ 1 ]] // entering second JSXElement
409 // Parse `class A implements B {}` with typescript-eslint.
413 sourceType
: "module" ,
416 type
: "ClassDeclaration" ,
424 type
: "ClassImplements" ,
441 // see https://github.com/typescript-eslint/typescript-eslint/blob/e4d737b47574ff2c53cabab22853035dfe48c1ed/packages/visitor-keys/src/visitor-keys.ts#L27
447 "superTypeParameters" ,
454 [ ":first-child" , ast
. body
[ 0 ]], // entering first ClassDeclaration
455 [ ":first-child" , ast
. body
[ 0 ]. implements [ 0 ]] // entering first ClassImplements
460 describe ( "parsing an invalid selector" , () => {
461 it ( "throws a useful error" , () => {
462 const emitter
= createEmitter ();
464 emitter
. on ( "Foo >" , () => {});
466 () => new NodeEventGenerator ( emitter
, STANDARD_ESQUERY_OPTION
),
467 /Syntax error in selector "Foo >" at position 5: Expected " ", "!", .*/u