1 | 'use strict';
|
2 |
|
3 | const { findVariable } = require('eslint-utils');
|
4 | const { getPropertyName } = require('eslint-utils');
|
5 | const { getStringIfConstant, ReferenceTracker } = require('eslint-utils');
|
6 | const { getTestCaseNames, getSuiteNames } = require('../util/names');
|
7 | const { getAddtionalNames } = require('../util/settings');
|
8 |
|
9 | const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
|
10 | const has = Function.call.bind(Object.hasOwnProperty);
|
11 |
|
12 | const READ = Symbol('read');
|
13 | const CALL = Symbol('call');
|
14 | const CONSTRUCT = Symbol('construct');
|
15 | const ESM = Symbol('esm');
|
16 | const dynamic = Symbol('dynamic');
|
17 |
|
18 | const requireCall = { require: { [CALL]: true } };
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function isModifiedGlobal(variable) {
|
26 | return (
|
27 | variable == null ||
|
28 | variable.defs.length !== 0 ||
|
29 | variable.references.some((r) => r.isWrite())
|
30 | );
|
31 | }
|
32 |
|
33 | function getPropertyName2(memberExpresssionNode) {
|
34 | if (memberExpresssionNode.computed) {
|
35 | if (memberExpresssionNode.property.type === 'Literal') {
|
36 | return memberExpresssionNode.property.value;
|
37 | }
|
38 | return dynamic;
|
39 | }
|
40 |
|
41 | return memberExpresssionNode.property.name;
|
42 | }
|
43 |
|
44 | function fooObjectNamePath(node) {
|
45 | if (node.type === 'MemberExpression') {
|
46 | if (node.object.type === 'MemberExpression') {
|
47 | return [ ...fooObjectNamePath(node.object), getPropertyName2(node) ];
|
48 | }
|
49 |
|
50 | return [ node.object.name, getPropertyName2(node) ];
|
51 | }
|
52 |
|
53 | return [ node.name ];
|
54 | }
|
55 |
|
56 | function getParentNodeForIdentifier(node) {
|
57 | if (node.parent.type === 'MemberExpression') {
|
58 | return getParentNodeForIdentifier(node.parent);
|
59 | }
|
60 |
|
61 | return { node: node.parent, path: fooObjectNamePath(node) };
|
62 | }
|
63 |
|
64 | function findGlobalVariableReferences(globalScope, name) {
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | const tracker = new utils.ReferenceTracker(globalScope, options);
|
76 |
|
77 | const it = tracker.iterateGlobalReferences(traceMap);
|
78 |
|
79 | }
|
80 |
|
81 | function xxx(patternNode, path, traceMap) {
|
82 | if (patternNode.type === 'Identifier') {
|
83 | const variable = findVariable(this.globalScope, patternNode);
|
84 | if (variable != null) {
|
85 | this._iterateVariableReferences(
|
86 | variable,
|
87 | path,
|
88 | traceMap,
|
89 | false
|
90 | );
|
91 | }
|
92 | return;
|
93 | }
|
94 | if (patternNode.type === 'ObjectPattern') {
|
95 | for (const property of patternNode.properties) {
|
96 | const key = getPropertyName(property);
|
97 |
|
98 | if (key == null || !has(traceMap, key)) {
|
99 | continue;
|
100 | }
|
101 |
|
102 | const nextPath = path.concat(key);
|
103 | const nextTraceMap = traceMap[key];
|
104 | if (nextTraceMap[READ]) {
|
105 | return {
|
106 | node: property,
|
107 | path: nextPath,
|
108 | type: READ,
|
109 | info: nextTraceMap[READ]
|
110 | };
|
111 | }
|
112 | this._iterateLhsReferences(
|
113 | property.value,
|
114 | nextPath,
|
115 | nextTraceMap
|
116 | );
|
117 | }
|
118 | return;
|
119 | }
|
120 | if (patternNode.type === 'AssignmentPattern') {
|
121 | this._iterateLhsReferences(patternNode.left, path, traceMap);
|
122 | }
|
123 | }
|
124 |
|
125 | function lol(rootNode, path, names) {
|
126 | let node = rootNode;
|
127 | while (isPassThrough(node)) {
|
128 | node = node.parent;
|
129 | }
|
130 |
|
131 | const parent = node.parent;
|
132 | if (parent.type === 'MemberExpression') {
|
133 | if (parent.object === node) {
|
134 | const key = getPropertyName(parent);
|
135 | if (!names.include(key)) {
|
136 | return;
|
137 | }
|
138 |
|
139 | path = path.concat(key);
|
140 | const nextTraceMap = traceMap[key];
|
141 | lol(parent, path, nextTraceMap);
|
142 | }
|
143 | return;
|
144 | }
|
145 | if (parent.type === 'AssignmentExpression') {
|
146 | if (parent.right === node) {
|
147 | xxx(parent.left, path, traceMap);
|
148 | lol(parent, path, traceMap);
|
149 | }
|
150 | return;
|
151 | }
|
152 | if (parent.type === 'AssignmentPattern') {
|
153 | if (parent.right === node) {
|
154 | xxx(parent.left, path, traceMap);
|
155 | }
|
156 | return;
|
157 | }
|
158 | if (parent.type === 'VariableDeclarator') {
|
159 | if (parent.init === node) {
|
160 | xxx(parent.id, path, traceMap);
|
161 | }
|
162 | }
|
163 | }
|
164 |
|
165 | function findCommonJsMochaReferences(globalScope, name) {
|
166 | return findGlobalVariableReferences(globalScope, 'require').reduce((memo, requireCall) => {
|
167 | const key = getStringIfConstant(requireCall.node.arguments[0]);
|
168 | if (key === 'mocha') {
|
169 | return [ ...memo, ...lol() ];
|
170 | }
|
171 |
|
172 | return memo;
|
173 | }, []);
|
174 | }
|
175 |
|
176 | function nameToPath(name) {
|
177 | return name.split('.');
|
178 | }
|
179 |
|
180 | function pathEncodedNameToPlainName(name) {
|
181 | const path = nameToPath(name);
|
182 | return path[0];
|
183 | }
|
184 |
|
185 | function normalizePathsInNameList(names) {
|
186 | return names.map(pathEncodedNameToPlainName);
|
187 | }
|
188 |
|
189 | function startsWithPath(fullPath, pathToMatch) {
|
190 | return pathToMatch.every((segment, index) => segment === fullPath[index]);
|
191 | }
|
192 |
|
193 | function findMatchingNameOrPath(names, path) {
|
194 | return names.map(nameToPath).find((pathToMatch) => {
|
195 | console.log(pathToMatch, path);
|
196 | return startsWithPath(path, pathToMatch);
|
197 | });
|
198 | }
|
199 |
|
200 | function findGlobalVariableCalls(globalScope, { names }) {
|
201 | const references = normalizePathsInNameList(names).flatMap((name) => findGlobalVariableReferences(globalScope, name));
|
202 |
|
203 | return references.reduce((results, reference) => {
|
204 | console.log('ref', reference);
|
205 | const { node: parent, path } = getParentNodeForIdentifier(reference.identifier, []);
|
206 | const matchingPath = findMatchingNameOrPath(names, path);
|
207 |
|
208 | if (parent.type === 'CallExpression' && matchingPath !== undefined) {
|
209 | return [ ...results, { reference, node: parent, path, matchingPath } ];
|
210 | }
|
211 |
|
212 | return results;
|
213 | }, []);
|
214 | }
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | function isPassThrough(node) {
|
223 | const parent = node.parent;
|
224 |
|
225 | switch (parent && parent.type) {
|
226 | case 'ConditionalExpression':
|
227 | return parent.consequent === node || parent.alternate === node;
|
228 | case 'LogicalExpression':
|
229 | return true;
|
230 | case 'SequenceExpression':
|
231 | return parent.expressions[parent.expressions.length - 1] === node;
|
232 | case 'ChainExpression':
|
233 | return true;
|
234 |
|
235 | default:
|
236 | return false;
|
237 | }
|
238 | }
|
239 |
|
240 |
|
241 |
|
242 |
|
243 | class ReferenceTracker2 {
|
244 | |
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 | constructor(
|
252 | globalScope,
|
253 | {
|
254 | mode = 'strict',
|
255 | globalObjectNames = [ 'global', 'globalThis', 'self', 'window' ]
|
256 | } = {}
|
257 | ) {
|
258 | this.variableStack = [];
|
259 | this.globalScope = globalScope;
|
260 | this.mode = mode;
|
261 | this.globalObjectNames = globalObjectNames.slice(0);
|
262 | }
|
263 |
|
264 | |
265 |
|
266 |
|
267 |
|
268 |
|
269 | *iterateGlobalReferences(traceMap) {
|
270 | for (const key of Object.keys(traceMap)) {
|
271 | const nextTraceMap = traceMap[key];
|
272 | const path = [ key ];
|
273 | let variable = this.globalScope.set.get(key);
|
274 | if (!variable) {
|
275 | variable = this.globalScope.through.find((reference) => {
|
276 | return reference.identifier.name === key;
|
277 | });
|
278 | }
|
279 |
|
280 | console.log('xxx', variable);
|
281 | if (isModifiedGlobal(variable)) {
|
282 | continue;
|
283 | }
|
284 |
|
285 | yield* this._iterateVariableReferences(
|
286 | variable,
|
287 | path,
|
288 | nextTraceMap,
|
289 | true
|
290 | );
|
291 | }
|
292 |
|
293 | for (const key of this.globalObjectNames) {
|
294 | const path = [];
|
295 | const variable = this.globalScope.set.get(key);
|
296 |
|
297 | if (isModifiedGlobal(variable)) {
|
298 | continue;
|
299 | }
|
300 |
|
301 | yield* this._iterateVariableReferences(
|
302 | variable,
|
303 | path,
|
304 | traceMap,
|
305 | false
|
306 | );
|
307 | }
|
308 | }
|
309 |
|
310 | |
311 |
|
312 |
|
313 |
|
314 |
|
315 | *iterateCjsReferences(traceMap) {
|
316 | for (const { node } of this.iterateGlobalReferences(requireCall)) {
|
317 | const key = getStringIfConstant(node.arguments[0]);
|
318 | if (key == null || !has(traceMap, key)) {
|
319 | continue;
|
320 | }
|
321 |
|
322 | const nextTraceMap = traceMap[key];
|
323 | const path = [ key ];
|
324 |
|
325 | if (nextTraceMap[READ]) {
|
326 | yield {
|
327 | node,
|
328 | path,
|
329 | type: READ,
|
330 | info: nextTraceMap[READ]
|
331 | };
|
332 | }
|
333 | yield* this.lol(node, path, nextTraceMap);
|
334 | }
|
335 | }
|
336 |
|
337 | |
338 |
|
339 |
|
340 |
|
341 |
|
342 | *iterateEsmReferences(traceMap) {
|
343 | const programNode = this.globalScope.block;
|
344 |
|
345 | for (const node of programNode.body) {
|
346 | if (!IMPORT_TYPE.test(node.type) || node.source == null) {
|
347 | continue;
|
348 | }
|
349 | const moduleId = node.source.value;
|
350 |
|
351 | if (!has(traceMap, moduleId)) {
|
352 | continue;
|
353 | }
|
354 | const nextTraceMap = traceMap[moduleId];
|
355 | const path = [ moduleId ];
|
356 |
|
357 | if (nextTraceMap[READ]) {
|
358 | yield { node, path, type: READ, info: nextTraceMap[READ] };
|
359 | }
|
360 |
|
361 | if (node.type === 'ExportAllDeclaration') {
|
362 | for (const key of Object.keys(nextTraceMap)) {
|
363 | const exportTraceMap = nextTraceMap[key];
|
364 | if (exportTraceMap[READ]) {
|
365 | yield {
|
366 | node,
|
367 | path: path.concat(key),
|
368 | type: READ,
|
369 | info: exportTraceMap[READ]
|
370 | };
|
371 | }
|
372 | }
|
373 | } else {
|
374 | for (const specifier of node.specifiers) {
|
375 | const esm = has(nextTraceMap, ESM);
|
376 | const it = this._iterateImportReferences(
|
377 | specifier,
|
378 | path,
|
379 | esm ?
|
380 | nextTraceMap :
|
381 | this.mode === 'legacy' ?
|
382 | Object.assign(
|
383 | { default: nextTraceMap },
|
384 | nextTraceMap
|
385 | ) :
|
386 | { default: nextTraceMap }
|
387 | );
|
388 |
|
389 | if (esm) {
|
390 | yield* it;
|
391 | } else {
|
392 | for (const report of it) {
|
393 | report.path = report.path.filter(exceptDefault);
|
394 | if (
|
395 | report.path.length >= 2 ||
|
396 | report.type !== READ
|
397 | ) {
|
398 | yield report;
|
399 | }
|
400 | }
|
401 | }
|
402 | }
|
403 | }
|
404 | }
|
405 | }
|
406 |
|
407 | |
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 | *_iterateVariableReferences(variable, path, traceMap, shouldReport) {
|
416 | console.log('foo');
|
417 | if (this.variableStack.includes(variable)) {
|
418 | return;
|
419 | }
|
420 | console.log('foo');
|
421 | this.variableStack.push(variable);
|
422 | try {
|
423 | for (const reference of variable.references) {
|
424 | if (!reference.isRead()) {
|
425 | continue;
|
426 | }
|
427 | const node = reference.identifier;
|
428 | console.log(reference.identifier);
|
429 |
|
430 | if (shouldReport && traceMap[READ]) {
|
431 | yield { node, path, type: READ, info: traceMap[READ] };
|
432 | }
|
433 | yield* this._iteratePropertyReferences(node, path, traceMap);
|
434 | }
|
435 | } finally {
|
436 | this.variableStack.pop();
|
437 | }
|
438 | }
|
439 |
|
440 | |
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 | *_iteratePropertyReferences(rootNode, path, traceMap) {
|
449 | let node = rootNode;
|
450 | while (isPassThrough(node)) {
|
451 | node = node.parent;
|
452 | }
|
453 |
|
454 | const parent = node.parent;
|
455 | if (parent.type === 'MemberExpression') {
|
456 | if (parent.object === node) {
|
457 | const key = getPropertyName(parent);
|
458 | if (key == null || !has(traceMap, key)) {
|
459 | return;
|
460 | }
|
461 |
|
462 | path = path.concat(key);
|
463 | const nextTraceMap = traceMap[key];
|
464 | if (nextTraceMap[READ]) {
|
465 | yield {
|
466 | node: parent,
|
467 | path,
|
468 | type: READ,
|
469 | info: nextTraceMap[READ]
|
470 | };
|
471 | }
|
472 | yield* this._iteratePropertyReferences(
|
473 | parent,
|
474 | path,
|
475 | nextTraceMap
|
476 | );
|
477 | }
|
478 | return;
|
479 | }
|
480 | if (parent.type === 'CallExpression') {
|
481 | if (parent.callee === node && traceMap[CALL]) {
|
482 | yield { node: parent, path, type: CALL, info: traceMap[CALL] };
|
483 | }
|
484 | return;
|
485 | }
|
486 | if (parent.type === 'NewExpression') {
|
487 | if (parent.callee === node && traceMap[CONSTRUCT]) {
|
488 | yield {
|
489 | node: parent,
|
490 | path,
|
491 | type: CONSTRUCT,
|
492 | info: traceMap[CONSTRUCT]
|
493 | };
|
494 | }
|
495 | return;
|
496 | }
|
497 | if (parent.type === 'AssignmentExpression') {
|
498 | if (parent.right === node) {
|
499 | yield* this._iterateLhsReferences(parent.left, path, traceMap);
|
500 | yield* this._iteratePropertyReferences(parent, path, traceMap);
|
501 | }
|
502 | return;
|
503 | }
|
504 | if (parent.type === 'AssignmentPattern') {
|
505 | if (parent.right === node) {
|
506 | yield* this._iterateLhsReferences(parent.left, path, traceMap);
|
507 | }
|
508 | return;
|
509 | }
|
510 | if (parent.type === 'VariableDeclarator') {
|
511 | if (parent.init === node) {
|
512 | yield* this._iterateLhsReferences(parent.id, path, traceMap);
|
513 | }
|
514 | }
|
515 | }
|
516 |
|
517 | |
518 |
|
519 |
|
520 |
|
521 |
|
522 |
|
523 |
|
524 | *_iterateLhsReferences(patternNode, path, traceMap) {
|
525 | if (patternNode.type === 'Identifier') {
|
526 | const variable = findVariable(this.globalScope, patternNode);
|
527 | if (variable != null) {
|
528 | yield* this._iterateVariableReferences(
|
529 | variable,
|
530 | path,
|
531 | traceMap,
|
532 | false
|
533 | );
|
534 | }
|
535 | return;
|
536 | }
|
537 | if (patternNode.type === 'ObjectPattern') {
|
538 | for (const property of patternNode.properties) {
|
539 | const key = getPropertyName(property);
|
540 |
|
541 | if (key == null || !has(traceMap, key)) {
|
542 | continue;
|
543 | }
|
544 |
|
545 | const nextPath = path.concat(key);
|
546 | const nextTraceMap = traceMap[key];
|
547 | if (nextTraceMap[READ]) {
|
548 | yield {
|
549 | node: property,
|
550 | path: nextPath,
|
551 | type: READ,
|
552 | info: nextTraceMap[READ]
|
553 | };
|
554 | }
|
555 | yield* this._iterateLhsReferences(
|
556 | property.value,
|
557 | nextPath,
|
558 | nextTraceMap
|
559 | );
|
560 | }
|
561 | return;
|
562 | }
|
563 | if (patternNode.type === 'AssignmentPattern') {
|
564 | yield* this._iterateLhsReferences(patternNode.left, path, traceMap);
|
565 | }
|
566 | }
|
567 |
|
568 | |
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 | *_iterateImportReferences(specifierNode, path, traceMap) {
|
576 | const type = specifierNode.type;
|
577 |
|
578 | if (type === 'ImportSpecifier' || type === 'ImportDefaultSpecifier') {
|
579 | const key =
|
580 | type === 'ImportDefaultSpecifier' ?
|
581 | 'default' :
|
582 | specifierNode.imported.name;
|
583 | if (!has(traceMap, key)) {
|
584 | return;
|
585 | }
|
586 |
|
587 | path = path.concat(key);
|
588 | const nextTraceMap = traceMap[key];
|
589 | if (nextTraceMap[READ]) {
|
590 | yield {
|
591 | node: specifierNode,
|
592 | path,
|
593 | type: READ,
|
594 | info: nextTraceMap[READ]
|
595 | };
|
596 | }
|
597 | yield* this._iterateVariableReferences(
|
598 | findVariable(this.globalScope, specifierNode.local),
|
599 | path,
|
600 | nextTraceMap,
|
601 | false
|
602 | );
|
603 |
|
604 | return;
|
605 | }
|
606 |
|
607 | if (type === 'ImportNamespaceSpecifier') {
|
608 | yield* this._iterateVariableReferences(
|
609 | findVariable(this.globalScope, specifierNode.local),
|
610 | path,
|
611 | traceMap,
|
612 | false
|
613 | );
|
614 | return;
|
615 | }
|
616 |
|
617 | if (type === 'ExportSpecifier') {
|
618 | const key = specifierNode.local.name;
|
619 | if (!has(traceMap, key)) {
|
620 | return;
|
621 | }
|
622 |
|
623 | path = path.concat(key);
|
624 | const nextTraceMap = traceMap[key];
|
625 | if (nextTraceMap[READ]) {
|
626 | yield {
|
627 | node: specifierNode,
|
628 | path,
|
629 | type: READ,
|
630 | info: nextTraceMap[READ]
|
631 | };
|
632 | }
|
633 | }
|
634 | }
|
635 | }
|
636 |
|
637 | ReferenceTracker.READ = READ;
|
638 | ReferenceTracker.CALL = CALL;
|
639 | ReferenceTracker.CONSTRUCT = CONSTRUCT;
|
640 | ReferenceTracker.ESM = ESM;
|
641 |
|
642 | function extractModifier(callee) {
|
643 | if (callee.type === 'MemberExpression') {
|
644 | if (callee.property.type === 'Identifier') {
|
645 | return callee.property.name;
|
646 | }
|
647 | return callee.property.value;
|
648 | }
|
649 |
|
650 | return null;
|
651 | }
|
652 |
|
653 | function createMochaVisitors(context, visitors) {
|
654 | const globalScope = context.getScope();
|
655 | const additionalCustomNames = getAddtionalNames(context.settings);
|
656 | const testCaseNames = getTestCaseNames({ modifiersOnly: false, modifiers: [], additionalCustomNames });
|
657 | const suiteNames = getSuiteNames({ modifiersOnly: false, modifiers: [], additionalCustomNames });
|
658 | const names = [ ...testCaseNames, ...suiteNames ];
|
659 | console.log(names);
|
660 |
|
661 | const calls = findGlobalVariableCalls(globalScope, { names });
|
662 |
|
663 | if (typeof visitors.testCase === 'function') {
|
664 | console.log('found it', calls);
|
665 | calls.forEach((call) => {
|
666 | const modifiers = call.path.slice(call.matchingPath.length);
|
667 | console.log(modifiers);
|
668 | const testCaseContext = {
|
669 | name: call.matchingPath.join('.'),
|
670 | modifiers,
|
671 | isExclusive: modifiers.includes('only'),
|
672 | node: call.node
|
673 | };
|
674 |
|
675 | visitors.testCase(testCaseContext);
|
676 | });
|
677 | }
|
678 | }
|
679 |
|
680 | module.exports = { createMochaVisitors, findGlobalVariableCalls };
|