UNPKG

22.4 kBJavaScriptView Raw
1'use strict';
2
3const { findVariable } = require('eslint-utils');
4const { getPropertyName } = require('eslint-utils');
5const { getStringIfConstant, ReferenceTracker } = require('eslint-utils');
6const { getTestCaseNames, getSuiteNames } = require('../util/names');
7const { getAddtionalNames } = require('../util/settings');
8
9const IMPORT_TYPE = /^(?:Import|Export(?:All|Default|Named))Declaration$/u;
10const has = Function.call.bind(Object.hasOwnProperty);
11
12const READ = Symbol('read');
13const CALL = Symbol('call');
14const CONSTRUCT = Symbol('construct');
15const ESM = Symbol('esm');
16const dynamic = Symbol('dynamic');
17
18const requireCall = { require: { [CALL]: true } };
19
20/**
21 * Check whether a given variable is modified or not.
22 * @param {Variable} variable The variable to check.
23 * @returns {boolean} `true` if the variable is modified.
24 */
25function isModifiedGlobal(variable) {
26 return (
27 variable == null ||
28 variable.defs.length !== 0 ||
29 variable.references.some((r) => r.isWrite())
30 );
31}
32
33function 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
44function 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
56function 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
64function findGlobalVariableReferences(globalScope, name) {
65/* const variable = globalScope.set.get(name);
66
67 if (variable && variable.defs.length === 0) {
68 return variable.references;
69 }
70
71 return globalScope.through.filter((reference) => {
72 return reference.identifier.name === name;
73 });*/
74
75 const tracker = new utils.ReferenceTracker(globalScope, options);
76
77 const it = tracker.iterateGlobalReferences(traceMap);
78 // ...
79}
80
81function 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
125function 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); // eslint-disable-line no-param-reassign
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
165function 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
176function nameToPath(name) {
177 return name.split('.');
178}
179
180function pathEncodedNameToPlainName(name) {
181 const path = nameToPath(name);
182 return path[0];
183}
184
185function normalizePathsInNameList(names) {
186 return names.map(pathEncodedNameToPlainName);
187}
188
189function startsWithPath(fullPath, pathToMatch) {
190 return pathToMatch.every((segment, index) => segment === fullPath[index]);
191}
192
193function findMatchingNameOrPath(names, path) {
194 return names.map(nameToPath).find((pathToMatch) => {
195 console.log(pathToMatch, path);
196 return startsWithPath(path, pathToMatch);
197 });
198}
199
200function 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 * Check if the value of a given node is passed through to the parent syntax as-is.
218 * For example, `a` and `b` in (`a || b` and `c ? a : b`) are passed through.
219 * @param {Node} node A node to check.
220 * @returns {boolean} `true` if the node is passed through.
221 */
222function 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 * The reference tracker.
242 */
243class ReferenceTracker2 {
244 /**
245 * Initialize this tracker.
246 * @param {Scope} globalScope The global scope.
247 * @param {object} [options] The options.
248 * @param {"legacy"|"strict"} [options.mode="strict"] The mode to determine the ImportDeclaration's behavior for CJS modules.
249 * @param {string[]} [options.globalObjectNames=["global","globalThis","self","window"]] The variable names for Global Object.
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 * Iterate the references of global variables.
266 * @param {object} traceMap The trace map.
267 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
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 * Iterate the references of CommonJS modules.
312 * @param {object} traceMap The trace map.
313 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
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 * Iterate the references of ES modules.
339 * @param {object} traceMap The trace map.
340 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
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 * Iterate the references for a given variable.
409 * @param {Variable} variable The variable to iterate that references.
410 * @param {string[]} path The current path.
411 * @param {object} traceMap The trace map.
412 * @param {boolean} shouldReport = The flag to report those references.
413 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
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 * Iterate the references for a given AST node.
442 * @param rootNode The AST node to iterate references.
443 * @param {string[]} path The current path.
444 * @param {object} traceMap The trace map.
445 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
446 */
447 // eslint-disable-next-line complexity
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); // eslint-disable-line no-param-reassign
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 * Iterate the references for a given Pattern node.
519 * @param {Node} patternNode The Pattern node to iterate references.
520 * @param {string[]} path The current path.
521 * @param {object} traceMap The trace map.
522 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
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 * Iterate the references for a given ModuleSpecifier node.
570 * @param {Node} specifierNode The ModuleSpecifier node to iterate references.
571 * @param {string[]} path The current path.
572 * @param {object} traceMap The trace map.
573 * @returns {IterableIterator<{node:Node,path:string[],type:symbol,info:any}>} The iterator to iterate references.
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); // eslint-disable-line no-param-reassign
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); // eslint-disable-line no-param-reassign
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
637ReferenceTracker.READ = READ;
638ReferenceTracker.CALL = CALL;
639ReferenceTracker.CONSTRUCT = CONSTRUCT;
640ReferenceTracker.ESM = ESM;
641
642function 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
653function 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 // const calls = findGlobalVariableCalls(globalScope, { names: [ 'it', 'suite', 'describe', 'test', 'context', 'specify', 'custom' ] });
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
680module.exports = { createMochaVisitors, findGlobalVariableCalls };