Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epidemian
GitHub Repository: epidemian/eslint-plugin-import
Path: blob/main/src/rules/no-unused-modules.js
829 views
1
/**
2
* @fileOverview Ensures that modules contain exports and/or all
3
* modules are consumed within other modules.
4
* @author René Fermann
5
*/
6
7
import Exports, { recursivePatternCapture } from '../ExportMap';
8
import { getFileExtensions } from 'eslint-module-utils/ignore';
9
import resolve from 'eslint-module-utils/resolve';
10
import visit from 'eslint-module-utils/visit';
11
import docsUrl from '../docsUrl';
12
import { dirname, join } from 'path';
13
import readPkgUp from 'eslint-module-utils/readPkgUp';
14
import values from 'object.values';
15
import includes from 'array-includes';
16
17
let FileEnumerator;
18
let listFilesToProcess;
19
20
try {
21
({ FileEnumerator } = require('eslint/use-at-your-own-risk'));
22
} catch (e) {
23
try {
24
// has been moved to eslint/lib/cli-engine/file-enumerator in version 6
25
({ FileEnumerator } = require('eslint/lib/cli-engine/file-enumerator'));
26
} catch (e) {
27
try {
28
// eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.3
29
const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-utils');
30
31
// Prevent passing invalid options (extensions array) to old versions of the function.
32
// https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L280
33
// https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L269
34
listFilesToProcess = function (src, extensions) {
35
return originalListFilesToProcess(src, {
36
extensions,
37
});
38
};
39
} catch (e) {
40
const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-util');
41
42
listFilesToProcess = function (src, extensions) {
43
const patterns = src.reduce((carry, pattern) => {
44
return carry.concat(extensions.map((extension) => {
45
return /\*\*|\*\./.test(pattern) ? pattern : `${pattern}/**/*${extension}`;
46
}));
47
}, src.slice());
48
49
return originalListFilesToProcess(patterns);
50
};
51
}
52
}
53
}
54
55
if (FileEnumerator) {
56
listFilesToProcess = function (src, extensions) {
57
const e = new FileEnumerator({
58
extensions,
59
});
60
61
return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({
62
ignored,
63
filename: filePath,
64
}));
65
};
66
}
67
68
const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration';
69
const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration';
70
const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration';
71
const IMPORT_DECLARATION = 'ImportDeclaration';
72
const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier';
73
const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier';
74
const VARIABLE_DECLARATION = 'VariableDeclaration';
75
const FUNCTION_DECLARATION = 'FunctionDeclaration';
76
const CLASS_DECLARATION = 'ClassDeclaration';
77
const IDENTIFIER = 'Identifier';
78
const OBJECT_PATTERN = 'ObjectPattern';
79
const TS_INTERFACE_DECLARATION = 'TSInterfaceDeclaration';
80
const TS_TYPE_ALIAS_DECLARATION = 'TSTypeAliasDeclaration';
81
const TS_ENUM_DECLARATION = 'TSEnumDeclaration';
82
const DEFAULT = 'default';
83
84
function forEachDeclarationIdentifier(declaration, cb) {
85
if (declaration) {
86
if (
87
declaration.type === FUNCTION_DECLARATION ||
88
declaration.type === CLASS_DECLARATION ||
89
declaration.type === TS_INTERFACE_DECLARATION ||
90
declaration.type === TS_TYPE_ALIAS_DECLARATION ||
91
declaration.type === TS_ENUM_DECLARATION
92
) {
93
cb(declaration.id.name);
94
} else if (declaration.type === VARIABLE_DECLARATION) {
95
declaration.declarations.forEach(({ id }) => {
96
if (id.type === OBJECT_PATTERN) {
97
recursivePatternCapture(id, (pattern) => {
98
if (pattern.type === IDENTIFIER) {
99
cb(pattern.name);
100
}
101
});
102
} else {
103
cb(id.name);
104
}
105
});
106
}
107
}
108
}
109
110
/**
111
* List of imports per file.
112
*
113
* Represented by a two-level Map to a Set of identifiers. The upper-level Map
114
* keys are the paths to the modules containing the imports, while the
115
* lower-level Map keys are the paths to the files which are being imported
116
* from. Lastly, the Set of identifiers contains either names being imported
117
* or a special AST node name listed above (e.g ImportDefaultSpecifier).
118
*
119
* For example, if we have a file named foo.js containing:
120
*
121
* import { o2 } from './bar.js';
122
*
123
* Then we will have a structure that looks like:
124
*
125
* Map { 'foo.js' => Map { 'bar.js' => Set { 'o2' } } }
126
*
127
* @type {Map<string, Map<string, Set<string>>>}
128
*/
129
const importList = new Map();
130
131
/**
132
* List of exports per file.
133
*
134
* Represented by a two-level Map to an object of metadata. The upper-level Map
135
* keys are the paths to the modules containing the exports, while the
136
* lower-level Map keys are the specific identifiers or special AST node names
137
* being exported. The leaf-level metadata object at the moment only contains a
138
* `whereUsed` property, which contains a Set of paths to modules that import
139
* the name.
140
*
141
* For example, if we have a file named bar.js containing the following exports:
142
*
143
* const o2 = 'bar';
144
* export { o2 };
145
*
146
* And a file named foo.js containing the following import:
147
*
148
* import { o2 } from './bar.js';
149
*
150
* Then we will have a structure that looks like:
151
*
152
* Map { 'bar.js' => Map { 'o2' => { whereUsed: Set { 'foo.js' } } } }
153
*
154
* @type {Map<string, Map<string, object>>}
155
*/
156
const exportList = new Map();
157
158
const visitorKeyMap = new Map();
159
160
const ignoredFiles = new Set();
161
const filesOutsideSrc = new Set();
162
163
const isNodeModule = path => {
164
return /\/(node_modules)\//.test(path);
165
};
166
167
/**
168
* read all files matching the patterns in src and ignoreExports
169
*
170
* return all files matching src pattern, which are not matching the ignoreExports pattern
171
*/
172
const resolveFiles = (src, ignoreExports, context) => {
173
const extensions = Array.from(getFileExtensions(context.settings));
174
175
const srcFiles = new Set();
176
const srcFileList = listFilesToProcess(src, extensions);
177
178
// prepare list of ignored files
179
const ignoredFilesList = listFilesToProcess(ignoreExports, extensions);
180
ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename));
181
182
// prepare list of source files, don't consider files from node_modules
183
srcFileList.filter(({ filename }) => !isNodeModule(filename)).forEach(({ filename }) => {
184
srcFiles.add(filename);
185
});
186
return srcFiles;
187
};
188
189
/**
190
* parse all source files and build up 2 maps containing the existing imports and exports
191
*/
192
const prepareImportsAndExports = (srcFiles, context) => {
193
const exportAll = new Map();
194
srcFiles.forEach(file => {
195
const exports = new Map();
196
const imports = new Map();
197
const currentExports = Exports.get(file, context);
198
if (currentExports) {
199
const {
200
dependencies,
201
reexports,
202
imports: localImportList,
203
namespace,
204
visitorKeys,
205
} = currentExports;
206
207
visitorKeyMap.set(file, visitorKeys);
208
// dependencies === export * from
209
const currentExportAll = new Set();
210
dependencies.forEach(getDependency => {
211
const dependency = getDependency();
212
if (dependency === null) {
213
return;
214
}
215
216
currentExportAll.add(dependency.path);
217
});
218
exportAll.set(file, currentExportAll);
219
220
reexports.forEach((value, key) => {
221
if (key === DEFAULT) {
222
exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() });
223
} else {
224
exports.set(key, { whereUsed: new Set() });
225
}
226
const reexport = value.getImport();
227
if (!reexport) {
228
return;
229
}
230
let localImport = imports.get(reexport.path);
231
let currentValue;
232
if (value.local === DEFAULT) {
233
currentValue = IMPORT_DEFAULT_SPECIFIER;
234
} else {
235
currentValue = value.local;
236
}
237
if (typeof localImport !== 'undefined') {
238
localImport = new Set([...localImport, currentValue]);
239
} else {
240
localImport = new Set([currentValue]);
241
}
242
imports.set(reexport.path, localImport);
243
});
244
245
localImportList.forEach((value, key) => {
246
if (isNodeModule(key)) {
247
return;
248
}
249
const localImport = imports.get(key) || new Set();
250
value.declarations.forEach(({ importedSpecifiers }) =>
251
importedSpecifiers.forEach(specifier => localImport.add(specifier)),
252
);
253
imports.set(key, localImport);
254
});
255
importList.set(file, imports);
256
257
// build up export list only, if file is not ignored
258
if (ignoredFiles.has(file)) {
259
return;
260
}
261
namespace.forEach((value, key) => {
262
if (key === DEFAULT) {
263
exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() });
264
} else {
265
exports.set(key, { whereUsed: new Set() });
266
}
267
});
268
}
269
exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() });
270
exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() });
271
exportList.set(file, exports);
272
});
273
exportAll.forEach((value, key) => {
274
value.forEach(val => {
275
const currentExports = exportList.get(val);
276
const currentExport = currentExports.get(EXPORT_ALL_DECLARATION);
277
currentExport.whereUsed.add(key);
278
});
279
});
280
};
281
282
/**
283
* traverse through all imports and add the respective path to the whereUsed-list
284
* of the corresponding export
285
*/
286
const determineUsage = () => {
287
importList.forEach((listValue, listKey) => {
288
listValue.forEach((value, key) => {
289
const exports = exportList.get(key);
290
if (typeof exports !== 'undefined') {
291
value.forEach(currentImport => {
292
let specifier;
293
if (currentImport === IMPORT_NAMESPACE_SPECIFIER) {
294
specifier = IMPORT_NAMESPACE_SPECIFIER;
295
} else if (currentImport === IMPORT_DEFAULT_SPECIFIER) {
296
specifier = IMPORT_DEFAULT_SPECIFIER;
297
} else {
298
specifier = currentImport;
299
}
300
if (typeof specifier !== 'undefined') {
301
const exportStatement = exports.get(specifier);
302
if (typeof exportStatement !== 'undefined') {
303
const { whereUsed } = exportStatement;
304
whereUsed.add(listKey);
305
exports.set(specifier, { whereUsed });
306
}
307
}
308
});
309
}
310
});
311
});
312
};
313
314
const getSrc = src => {
315
if (src) {
316
return src;
317
}
318
return [process.cwd()];
319
};
320
321
/**
322
* prepare the lists of existing imports and exports - should only be executed once at
323
* the start of a new eslint run
324
*/
325
let srcFiles;
326
let lastPrepareKey;
327
const doPreparation = (src, ignoreExports, context) => {
328
const prepareKey = JSON.stringify({
329
src: (src || []).sort(),
330
ignoreExports: (ignoreExports || []).sort(),
331
extensions: Array.from(getFileExtensions(context.settings)).sort(),
332
});
333
if (prepareKey === lastPrepareKey) {
334
return;
335
}
336
337
importList.clear();
338
exportList.clear();
339
ignoredFiles.clear();
340
filesOutsideSrc.clear();
341
342
srcFiles = resolveFiles(getSrc(src), ignoreExports, context);
343
prepareImportsAndExports(srcFiles, context);
344
determineUsage();
345
lastPrepareKey = prepareKey;
346
};
347
348
const newNamespaceImportExists = specifiers =>
349
specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER);
350
351
const newDefaultImportExists = specifiers =>
352
specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER);
353
354
const fileIsInPkg = file => {
355
const { path, pkg } = readPkgUp({ cwd: file });
356
const basePath = dirname(path);
357
358
const checkPkgFieldString = pkgField => {
359
if (join(basePath, pkgField) === file) {
360
return true;
361
}
362
};
363
364
const checkPkgFieldObject = pkgField => {
365
const pkgFieldFiles = values(pkgField).map(value => join(basePath, value));
366
if (includes(pkgFieldFiles, file)) {
367
return true;
368
}
369
};
370
371
const checkPkgField = pkgField => {
372
if (typeof pkgField === 'string') {
373
return checkPkgFieldString(pkgField);
374
}
375
376
if (typeof pkgField === 'object') {
377
return checkPkgFieldObject(pkgField);
378
}
379
};
380
381
if (pkg.private === true) {
382
return false;
383
}
384
385
if (pkg.bin) {
386
if (checkPkgField(pkg.bin)) {
387
return true;
388
}
389
}
390
391
if (pkg.browser) {
392
if (checkPkgField(pkg.browser)) {
393
return true;
394
}
395
}
396
397
if (pkg.main) {
398
if (checkPkgFieldString(pkg.main)) {
399
return true;
400
}
401
}
402
403
return false;
404
};
405
406
module.exports = {
407
meta: {
408
type: 'suggestion',
409
docs: { url: docsUrl('no-unused-modules') },
410
schema: [{
411
properties: {
412
src: {
413
description: 'files/paths to be analyzed (only for unused exports)',
414
type: 'array',
415
minItems: 1,
416
items: {
417
type: 'string',
418
minLength: 1,
419
},
420
},
421
ignoreExports: {
422
description:
423
'files/paths for which unused exports will not be reported (e.g module entry points)',
424
type: 'array',
425
minItems: 1,
426
items: {
427
type: 'string',
428
minLength: 1,
429
},
430
},
431
missingExports: {
432
description: 'report modules without any exports',
433
type: 'boolean',
434
},
435
unusedExports: {
436
description: 'report exports without any usage',
437
type: 'boolean',
438
},
439
},
440
not: {
441
properties: {
442
unusedExports: { enum: [false] },
443
missingExports: { enum: [false] },
444
},
445
},
446
anyOf:[{
447
not: {
448
properties: {
449
unusedExports: { enum: [true] },
450
},
451
},
452
required: ['missingExports'],
453
}, {
454
not: {
455
properties: {
456
missingExports: { enum: [true] },
457
},
458
},
459
required: ['unusedExports'],
460
}, {
461
properties: {
462
unusedExports: { enum: [true] },
463
},
464
required: ['unusedExports'],
465
}, {
466
properties: {
467
missingExports: { enum: [true] },
468
},
469
required: ['missingExports'],
470
}],
471
}],
472
},
473
474
create: context => {
475
const {
476
src,
477
ignoreExports = [],
478
missingExports,
479
unusedExports,
480
} = context.options[0] || {};
481
482
if (unusedExports) {
483
doPreparation(src, ignoreExports, context);
484
}
485
486
const file = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
487
488
const checkExportPresence = node => {
489
if (!missingExports) {
490
return;
491
}
492
493
if (ignoredFiles.has(file)) {
494
return;
495
}
496
497
const exportCount = exportList.get(file);
498
const exportAll = exportCount.get(EXPORT_ALL_DECLARATION);
499
const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER);
500
501
exportCount.delete(EXPORT_ALL_DECLARATION);
502
exportCount.delete(IMPORT_NAMESPACE_SPECIFIER);
503
if (exportCount.size < 1) {
504
// node.body[0] === 'undefined' only happens, if everything is commented out in the file
505
// being linted
506
context.report(node.body[0] ? node.body[0] : node, 'No exports found');
507
}
508
exportCount.set(EXPORT_ALL_DECLARATION, exportAll);
509
exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);
510
};
511
512
const checkUsage = (node, exportedValue) => {
513
if (!unusedExports) {
514
return;
515
}
516
517
if (ignoredFiles.has(file)) {
518
return;
519
}
520
521
if (fileIsInPkg(file)) {
522
return;
523
}
524
525
if (filesOutsideSrc.has(file)) {
526
return;
527
}
528
529
// make sure file to be linted is included in source files
530
if (!srcFiles.has(file)) {
531
srcFiles = resolveFiles(getSrc(src), ignoreExports, context);
532
if (!srcFiles.has(file)) {
533
filesOutsideSrc.add(file);
534
return;
535
}
536
}
537
538
exports = exportList.get(file);
539
540
// special case: export * from
541
const exportAll = exports.get(EXPORT_ALL_DECLARATION);
542
if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) {
543
if (exportAll.whereUsed.size > 0) {
544
return;
545
}
546
}
547
548
// special case: namespace import
549
const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER);
550
if (typeof namespaceImports !== 'undefined') {
551
if (namespaceImports.whereUsed.size > 0) {
552
return;
553
}
554
}
555
556
// exportsList will always map any imported value of 'default' to 'ImportDefaultSpecifier'
557
const exportsKey = exportedValue === DEFAULT ? IMPORT_DEFAULT_SPECIFIER : exportedValue;
558
559
const exportStatement = exports.get(exportsKey);
560
561
const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey;
562
563
if (typeof exportStatement !== 'undefined') {
564
if (exportStatement.whereUsed.size < 1) {
565
context.report(
566
node,
567
`exported declaration '${value}' not used within other modules`,
568
);
569
}
570
} else {
571
context.report(
572
node,
573
`exported declaration '${value}' not used within other modules`,
574
);
575
}
576
};
577
578
/**
579
* only useful for tools like vscode-eslint
580
*
581
* update lists of existing exports during runtime
582
*/
583
const updateExportUsage = node => {
584
if (ignoredFiles.has(file)) {
585
return;
586
}
587
588
let exports = exportList.get(file);
589
590
// new module has been created during runtime
591
// include it in further processing
592
if (typeof exports === 'undefined') {
593
exports = new Map();
594
}
595
596
const newExports = new Map();
597
const newExportIdentifiers = new Set();
598
599
node.body.forEach(({ type, declaration, specifiers }) => {
600
if (type === EXPORT_DEFAULT_DECLARATION) {
601
newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER);
602
}
603
if (type === EXPORT_NAMED_DECLARATION) {
604
if (specifiers.length > 0) {
605
specifiers.forEach(specifier => {
606
if (specifier.exported) {
607
newExportIdentifiers.add(specifier.exported.name);
608
}
609
});
610
}
611
forEachDeclarationIdentifier(declaration, (name) => {
612
newExportIdentifiers.add(name);
613
});
614
}
615
});
616
617
// old exports exist within list of new exports identifiers: add to map of new exports
618
exports.forEach((value, key) => {
619
if (newExportIdentifiers.has(key)) {
620
newExports.set(key, value);
621
}
622
});
623
624
// new export identifiers added: add to map of new exports
625
newExportIdentifiers.forEach(key => {
626
if (!exports.has(key)) {
627
newExports.set(key, { whereUsed: new Set() });
628
}
629
});
630
631
// preserve information about namespace imports
632
const exportAll = exports.get(EXPORT_ALL_DECLARATION);
633
let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER);
634
635
if (typeof namespaceImports === 'undefined') {
636
namespaceImports = { whereUsed: new Set() };
637
}
638
639
newExports.set(EXPORT_ALL_DECLARATION, exportAll);
640
newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);
641
exportList.set(file, newExports);
642
};
643
644
/**
645
* only useful for tools like vscode-eslint
646
*
647
* update lists of existing imports during runtime
648
*/
649
const updateImportUsage = node => {
650
if (!unusedExports) {
651
return;
652
}
653
654
let oldImportPaths = importList.get(file);
655
if (typeof oldImportPaths === 'undefined') {
656
oldImportPaths = new Map();
657
}
658
659
const oldNamespaceImports = new Set();
660
const newNamespaceImports = new Set();
661
662
const oldExportAll = new Set();
663
const newExportAll = new Set();
664
665
const oldDefaultImports = new Set();
666
const newDefaultImports = new Set();
667
668
const oldImports = new Map();
669
const newImports = new Map();
670
oldImportPaths.forEach((value, key) => {
671
if (value.has(EXPORT_ALL_DECLARATION)) {
672
oldExportAll.add(key);
673
}
674
if (value.has(IMPORT_NAMESPACE_SPECIFIER)) {
675
oldNamespaceImports.add(key);
676
}
677
if (value.has(IMPORT_DEFAULT_SPECIFIER)) {
678
oldDefaultImports.add(key);
679
}
680
value.forEach(val => {
681
if (val !== IMPORT_NAMESPACE_SPECIFIER &&
682
val !== IMPORT_DEFAULT_SPECIFIER) {
683
oldImports.set(val, key);
684
}
685
});
686
});
687
688
function processDynamicImport(source) {
689
if (source.type !== 'Literal') {
690
return null;
691
}
692
const p = resolve(source.value, context);
693
if (p == null) {
694
return null;
695
}
696
newNamespaceImports.add(p);
697
}
698
699
visit(node, visitorKeyMap.get(file), {
700
ImportExpression(child) {
701
processDynamicImport(child.source);
702
},
703
CallExpression(child) {
704
if (child.callee.type === 'Import') {
705
processDynamicImport(child.arguments[0]);
706
}
707
},
708
});
709
710
node.body.forEach(astNode => {
711
let resolvedPath;
712
713
// support for export { value } from 'module'
714
if (astNode.type === EXPORT_NAMED_DECLARATION) {
715
if (astNode.source) {
716
resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);
717
astNode.specifiers.forEach(specifier => {
718
const name = specifier.local.name;
719
if (specifier.local.name === DEFAULT) {
720
newDefaultImports.add(resolvedPath);
721
} else {
722
newImports.set(name, resolvedPath);
723
}
724
});
725
}
726
}
727
728
if (astNode.type === EXPORT_ALL_DECLARATION) {
729
resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);
730
newExportAll.add(resolvedPath);
731
}
732
733
if (astNode.type === IMPORT_DECLARATION) {
734
resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);
735
if (!resolvedPath) {
736
return;
737
}
738
739
if (isNodeModule(resolvedPath)) {
740
return;
741
}
742
743
if (newNamespaceImportExists(astNode.specifiers)) {
744
newNamespaceImports.add(resolvedPath);
745
}
746
747
if (newDefaultImportExists(astNode.specifiers)) {
748
newDefaultImports.add(resolvedPath);
749
}
750
751
astNode.specifiers.forEach(specifier => {
752
if (specifier.type === IMPORT_DEFAULT_SPECIFIER ||
753
specifier.type === IMPORT_NAMESPACE_SPECIFIER) {
754
return;
755
}
756
newImports.set(specifier.imported.name, resolvedPath);
757
});
758
}
759
});
760
761
newExportAll.forEach(value => {
762
if (!oldExportAll.has(value)) {
763
let imports = oldImportPaths.get(value);
764
if (typeof imports === 'undefined') {
765
imports = new Set();
766
}
767
imports.add(EXPORT_ALL_DECLARATION);
768
oldImportPaths.set(value, imports);
769
770
let exports = exportList.get(value);
771
let currentExport;
772
if (typeof exports !== 'undefined') {
773
currentExport = exports.get(EXPORT_ALL_DECLARATION);
774
} else {
775
exports = new Map();
776
exportList.set(value, exports);
777
}
778
779
if (typeof currentExport !== 'undefined') {
780
currentExport.whereUsed.add(file);
781
} else {
782
const whereUsed = new Set();
783
whereUsed.add(file);
784
exports.set(EXPORT_ALL_DECLARATION, { whereUsed });
785
}
786
}
787
});
788
789
oldExportAll.forEach(value => {
790
if (!newExportAll.has(value)) {
791
const imports = oldImportPaths.get(value);
792
imports.delete(EXPORT_ALL_DECLARATION);
793
794
const exports = exportList.get(value);
795
if (typeof exports !== 'undefined') {
796
const currentExport = exports.get(EXPORT_ALL_DECLARATION);
797
if (typeof currentExport !== 'undefined') {
798
currentExport.whereUsed.delete(file);
799
}
800
}
801
}
802
});
803
804
newDefaultImports.forEach(value => {
805
if (!oldDefaultImports.has(value)) {
806
let imports = oldImportPaths.get(value);
807
if (typeof imports === 'undefined') {
808
imports = new Set();
809
}
810
imports.add(IMPORT_DEFAULT_SPECIFIER);
811
oldImportPaths.set(value, imports);
812
813
let exports = exportList.get(value);
814
let currentExport;
815
if (typeof exports !== 'undefined') {
816
currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER);
817
} else {
818
exports = new Map();
819
exportList.set(value, exports);
820
}
821
822
if (typeof currentExport !== 'undefined') {
823
currentExport.whereUsed.add(file);
824
} else {
825
const whereUsed = new Set();
826
whereUsed.add(file);
827
exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed });
828
}
829
}
830
});
831
832
oldDefaultImports.forEach(value => {
833
if (!newDefaultImports.has(value)) {
834
const imports = oldImportPaths.get(value);
835
imports.delete(IMPORT_DEFAULT_SPECIFIER);
836
837
const exports = exportList.get(value);
838
if (typeof exports !== 'undefined') {
839
const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER);
840
if (typeof currentExport !== 'undefined') {
841
currentExport.whereUsed.delete(file);
842
}
843
}
844
}
845
});
846
847
newNamespaceImports.forEach(value => {
848
if (!oldNamespaceImports.has(value)) {
849
let imports = oldImportPaths.get(value);
850
if (typeof imports === 'undefined') {
851
imports = new Set();
852
}
853
imports.add(IMPORT_NAMESPACE_SPECIFIER);
854
oldImportPaths.set(value, imports);
855
856
let exports = exportList.get(value);
857
let currentExport;
858
if (typeof exports !== 'undefined') {
859
currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER);
860
} else {
861
exports = new Map();
862
exportList.set(value, exports);
863
}
864
865
if (typeof currentExport !== 'undefined') {
866
currentExport.whereUsed.add(file);
867
} else {
868
const whereUsed = new Set();
869
whereUsed.add(file);
870
exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed });
871
}
872
}
873
});
874
875
oldNamespaceImports.forEach(value => {
876
if (!newNamespaceImports.has(value)) {
877
const imports = oldImportPaths.get(value);
878
imports.delete(IMPORT_NAMESPACE_SPECIFIER);
879
880
const exports = exportList.get(value);
881
if (typeof exports !== 'undefined') {
882
const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER);
883
if (typeof currentExport !== 'undefined') {
884
currentExport.whereUsed.delete(file);
885
}
886
}
887
}
888
});
889
890
newImports.forEach((value, key) => {
891
if (!oldImports.has(key)) {
892
let imports = oldImportPaths.get(value);
893
if (typeof imports === 'undefined') {
894
imports = new Set();
895
}
896
imports.add(key);
897
oldImportPaths.set(value, imports);
898
899
let exports = exportList.get(value);
900
let currentExport;
901
if (typeof exports !== 'undefined') {
902
currentExport = exports.get(key);
903
} else {
904
exports = new Map();
905
exportList.set(value, exports);
906
}
907
908
if (typeof currentExport !== 'undefined') {
909
currentExport.whereUsed.add(file);
910
} else {
911
const whereUsed = new Set();
912
whereUsed.add(file);
913
exports.set(key, { whereUsed });
914
}
915
}
916
});
917
918
oldImports.forEach((value, key) => {
919
if (!newImports.has(key)) {
920
const imports = oldImportPaths.get(value);
921
imports.delete(key);
922
923
const exports = exportList.get(value);
924
if (typeof exports !== 'undefined') {
925
const currentExport = exports.get(key);
926
if (typeof currentExport !== 'undefined') {
927
currentExport.whereUsed.delete(file);
928
}
929
}
930
}
931
});
932
};
933
934
return {
935
'Program:exit': node => {
936
updateExportUsage(node);
937
updateImportUsage(node);
938
checkExportPresence(node);
939
},
940
'ExportDefaultDeclaration': node => {
941
checkUsage(node, IMPORT_DEFAULT_SPECIFIER);
942
},
943
'ExportNamedDeclaration': node => {
944
node.specifiers.forEach(specifier => {
945
checkUsage(node, specifier.exported.name);
946
});
947
forEachDeclarationIdentifier(node.declaration, (name) => {
948
checkUsage(node, name);
949
});
950
},
951
};
952
},
953
};
954
955