754 lines
No EOL
31 KiB
JavaScript
754 lines
No EOL
31 KiB
JavaScript
import path from 'node:path';
|
|
import { TSESTree } from '@typescript-eslint/types';
|
|
import eslintUnsupportedApi from 'eslint/use-at-your-own-risk';
|
|
import { ExportMap, recursivePatternCapture, createRule, resolve, getFileExtensions, readPkgUp, visit, getValue, } from '../utils/index.js';
|
|
const { FileEnumerator, shouldUseFlatConfig } = eslintUnsupportedApi;
|
|
function listFilesUsingFileEnumerator(src, extensions) {
|
|
const { ESLINT_USE_FLAT_CONFIG } = process.env;
|
|
let isUsingFlatConfig;
|
|
try {
|
|
isUsingFlatConfig =
|
|
shouldUseFlatConfig && ESLINT_USE_FLAT_CONFIG !== 'false';
|
|
}
|
|
catch {
|
|
isUsingFlatConfig =
|
|
!!ESLINT_USE_FLAT_CONFIG && ESLINT_USE_FLAT_CONFIG !== 'false';
|
|
}
|
|
const enumerator = new FileEnumerator({ extensions });
|
|
try {
|
|
return Array.from(enumerator.iterateFiles(src), ({ filePath, ignored }) => ({ filename: filePath, ignored }));
|
|
}
|
|
catch (error) {
|
|
if (isUsingFlatConfig &&
|
|
error.message.includes('No ESLint configuration found')) {
|
|
throw new Error(`
|
|
Due to the exclusion of certain internal ESLint APIs when using flat config,
|
|
the import-x/no-unused-modules rule requires an .eslintrc file (even empty) to know which
|
|
files to ignore (even when using flat config).
|
|
The .eslintrc file only needs to contain "ignorePatterns", or can be empty if
|
|
you do not want to ignore any files.
|
|
|
|
See https://github.com/import-js/eslint-plugin-import/issues/3079
|
|
for additional context.
|
|
`);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
const DEFAULT = 'default';
|
|
const { AST_NODE_TYPES } = TSESTree;
|
|
function forEachDeclarationIdentifier(declaration, cb) {
|
|
if (declaration) {
|
|
const isTypeDeclaration = declaration.type === AST_NODE_TYPES.TSInterfaceDeclaration ||
|
|
declaration.type === AST_NODE_TYPES.TSTypeAliasDeclaration ||
|
|
declaration.type === AST_NODE_TYPES.TSEnumDeclaration;
|
|
if (declaration.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
declaration.type === AST_NODE_TYPES.ClassDeclaration ||
|
|
isTypeDeclaration) {
|
|
cb(declaration.id.name, isTypeDeclaration);
|
|
}
|
|
else if (declaration.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
for (const { id } of declaration.declarations) {
|
|
if (id.type === AST_NODE_TYPES.ObjectPattern) {
|
|
recursivePatternCapture(id, pattern => {
|
|
if (pattern.type === AST_NODE_TYPES.Identifier) {
|
|
cb(pattern.name, false);
|
|
}
|
|
});
|
|
}
|
|
else if (id.type === AST_NODE_TYPES.ArrayPattern) {
|
|
for (const el of id.elements) {
|
|
if (el?.type === AST_NODE_TYPES.Identifier) {
|
|
cb(el.name, false);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
cb(id.name, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const importList = new Map();
|
|
const exportList = new Map();
|
|
const visitorKeyMap = new Map();
|
|
const ignoredFiles = new Set();
|
|
const filesOutsideSrc = new Set();
|
|
const isNodeModule = (path) => /([/\\])(node_modules)\1/.test(path);
|
|
const resolveFiles = (src, ignoreExports, context) => {
|
|
const extensions = [...getFileExtensions(context.settings)];
|
|
const srcFileList = listFilesUsingFileEnumerator(src, extensions);
|
|
const ignoredFilesList = listFilesUsingFileEnumerator(ignoreExports, extensions);
|
|
for (const { filename } of ignoredFilesList)
|
|
ignoredFiles.add(filename);
|
|
return new Set(srcFileList.flatMap(({ filename }) => isNodeModule(filename) ? [] : filename));
|
|
};
|
|
const prepareImportsAndExports = (srcFiles, context) => {
|
|
const exportAll = new Map();
|
|
for (const file of srcFiles) {
|
|
const exports = new Map();
|
|
const imports = new Map();
|
|
const currentExports = ExportMap.get(file, context);
|
|
if (currentExports) {
|
|
const { dependencies, reexports, imports: localImportList, namespace, visitorKeys, } = currentExports;
|
|
visitorKeyMap.set(file, visitorKeys);
|
|
const currentExportAll = new Set();
|
|
for (const getDependency of dependencies) {
|
|
const dependency = getDependency();
|
|
if (dependency === null) {
|
|
continue;
|
|
}
|
|
currentExportAll.add(dependency.path);
|
|
}
|
|
exportAll.set(file, currentExportAll);
|
|
for (const [key, value] of reexports.entries()) {
|
|
if (key === DEFAULT) {
|
|
exports.set(AST_NODE_TYPES.ImportDefaultSpecifier, {
|
|
whereUsed: new Set(),
|
|
});
|
|
}
|
|
else {
|
|
exports.set(key, { whereUsed: new Set() });
|
|
}
|
|
const reexport = value.getImport();
|
|
if (!reexport) {
|
|
continue;
|
|
}
|
|
let localImport = imports.get(reexport.path);
|
|
const currentValue = value.local === DEFAULT
|
|
? AST_NODE_TYPES.ImportDefaultSpecifier
|
|
: value.local;
|
|
localImport =
|
|
localImport === undefined
|
|
? new Set([currentValue])
|
|
: new Set([...localImport, currentValue]);
|
|
imports.set(reexport.path, localImport);
|
|
}
|
|
for (const [key, value] of localImportList.entries()) {
|
|
if (isNodeModule(key)) {
|
|
continue;
|
|
}
|
|
const localImport = imports.get(key) || new Set();
|
|
for (const { importedSpecifiers } of value.declarations) {
|
|
for (const specifier of importedSpecifiers) {
|
|
localImport.add(specifier);
|
|
}
|
|
}
|
|
imports.set(key, localImport);
|
|
}
|
|
importList.set(file, imports);
|
|
if (ignoredFiles.has(file)) {
|
|
continue;
|
|
}
|
|
for (const [key, _value] of namespace.entries()) {
|
|
if (key === DEFAULT) {
|
|
exports.set(AST_NODE_TYPES.ImportDefaultSpecifier, {
|
|
whereUsed: new Set(),
|
|
});
|
|
}
|
|
else {
|
|
exports.set(key, { whereUsed: new Set() });
|
|
}
|
|
}
|
|
}
|
|
exports.set(AST_NODE_TYPES.ExportAllDeclaration, {
|
|
whereUsed: new Set(),
|
|
});
|
|
exports.set(AST_NODE_TYPES.ImportNamespaceSpecifier, {
|
|
whereUsed: new Set(),
|
|
});
|
|
exportList.set(file, exports);
|
|
}
|
|
for (const [key, value] of exportAll.entries()) {
|
|
for (const val of value) {
|
|
const currentExports = exportList.get(val);
|
|
if (currentExports) {
|
|
const currentExport = currentExports.get(AST_NODE_TYPES.ExportAllDeclaration);
|
|
currentExport.whereUsed.add(key);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const determineUsage = () => {
|
|
for (const [listKey, listValue] of importList.entries()) {
|
|
for (const [key, value] of listValue.entries()) {
|
|
const exports = exportList.get(key);
|
|
if (exports !== undefined) {
|
|
for (const currentImport of value) {
|
|
let specifier;
|
|
if (currentImport === AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
specifier = AST_NODE_TYPES.ImportNamespaceSpecifier;
|
|
}
|
|
else if (currentImport === AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
specifier = AST_NODE_TYPES.ImportDefaultSpecifier;
|
|
}
|
|
else {
|
|
specifier = currentImport;
|
|
}
|
|
if (specifier !== undefined) {
|
|
const exportStatement = exports.get(specifier);
|
|
if (exportStatement !== undefined) {
|
|
const { whereUsed } = exportStatement;
|
|
whereUsed.add(listKey);
|
|
exports.set(specifier, { whereUsed });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let srcFiles;
|
|
let lastPrepareKey;
|
|
const doPreparation = (src, ignoreExports, context) => {
|
|
const prepareKey = JSON.stringify({
|
|
src: src.sort(),
|
|
ignoreExports: (ignoreExports || []).sort(),
|
|
extensions: [...getFileExtensions(context.settings)].sort(),
|
|
});
|
|
if (prepareKey === lastPrepareKey) {
|
|
return;
|
|
}
|
|
importList.clear();
|
|
exportList.clear();
|
|
ignoredFiles.clear();
|
|
filesOutsideSrc.clear();
|
|
srcFiles = resolveFiles(src, ignoreExports, context);
|
|
prepareImportsAndExports(srcFiles, context);
|
|
determineUsage();
|
|
lastPrepareKey = prepareKey;
|
|
};
|
|
const newNamespaceImportExists = (specifiers) => specifiers.some(({ type }) => type === AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
const newDefaultImportExists = (specifiers) => specifiers.some(({ type }) => type === AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
const fileIsInPkg = (file) => {
|
|
const { pkg, path: pkgPath } = readPkgUp({ cwd: file });
|
|
const basePath = path.dirname(pkgPath);
|
|
const checkPkgFieldString = (pkgField) => {
|
|
if (path.join(basePath, pkgField) === file) {
|
|
return true;
|
|
}
|
|
};
|
|
const checkPkgFieldObject = (pkgField) => {
|
|
const pkgFieldFiles = Object.values(pkgField).flatMap(value => typeof value === 'boolean' ? [] : path.join(basePath, value));
|
|
if (pkgFieldFiles.includes(file)) {
|
|
return true;
|
|
}
|
|
};
|
|
const checkPkgField = (pkgField) => {
|
|
if (typeof pkgField === 'string') {
|
|
return checkPkgFieldString(pkgField);
|
|
}
|
|
if (typeof pkgField === 'object') {
|
|
return checkPkgFieldObject(pkgField);
|
|
}
|
|
};
|
|
if (!pkg) {
|
|
return false;
|
|
}
|
|
if (pkg.private === true) {
|
|
return false;
|
|
}
|
|
if (pkg.bin && checkPkgField(pkg.bin)) {
|
|
return true;
|
|
}
|
|
if (pkg.browser && checkPkgField(pkg.browser)) {
|
|
return true;
|
|
}
|
|
if (pkg.main && checkPkgFieldString(pkg.main)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
export default createRule({
|
|
name: 'no-unused-modules',
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
category: 'Helpful warnings',
|
|
description: 'Forbid modules without exports, or exports without matching import in another module.',
|
|
},
|
|
schema: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
src: {
|
|
description: 'files/paths to be analyzed (only for unused exports)',
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
items: {
|
|
type: 'string',
|
|
minLength: 1,
|
|
},
|
|
},
|
|
ignoreExports: {
|
|
description: 'files/paths for which unused exports will not be reported (e.g module entry points)',
|
|
type: 'array',
|
|
uniqueItems: true,
|
|
items: {
|
|
type: 'string',
|
|
minLength: 1,
|
|
},
|
|
},
|
|
missingExports: {
|
|
description: 'report modules without any exports',
|
|
type: 'boolean',
|
|
},
|
|
unusedExports: {
|
|
description: 'report exports without any usage',
|
|
type: 'boolean',
|
|
},
|
|
ignoreUnusedTypeExports: {
|
|
description: 'ignore type exports without any usage',
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
anyOf: [
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
unusedExports: {
|
|
type: 'boolean',
|
|
enum: [true],
|
|
},
|
|
src: {
|
|
type: 'array',
|
|
minItems: 1,
|
|
},
|
|
},
|
|
required: ['unusedExports'],
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
missingExports: {
|
|
type: 'boolean',
|
|
enum: [true],
|
|
},
|
|
},
|
|
required: ['missingExports'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
messages: {
|
|
notFound: 'No exports found',
|
|
unused: "exported declaration '{{value}}' not used within other modules",
|
|
},
|
|
},
|
|
defaultOptions: [],
|
|
create(context) {
|
|
const { src = [process.cwd()], ignoreExports = [], missingExports, unusedExports, ignoreUnusedTypeExports, } = context.options[0] || {};
|
|
if (unusedExports) {
|
|
doPreparation(src, ignoreExports, context);
|
|
}
|
|
const filename = context.physicalFilename;
|
|
const checkExportPresence = (node) => {
|
|
if (!missingExports) {
|
|
return;
|
|
}
|
|
if (ignoreUnusedTypeExports) {
|
|
return;
|
|
}
|
|
if (ignoredFiles.has(filename)) {
|
|
return;
|
|
}
|
|
const exportCount = exportList.get(filename);
|
|
const exportAll = exportCount.get(AST_NODE_TYPES.ExportAllDeclaration);
|
|
const namespaceImports = exportCount.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
exportCount.delete(AST_NODE_TYPES.ExportAllDeclaration);
|
|
exportCount.delete(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
if (exportCount.size === 0) {
|
|
context.report({
|
|
node: node.body[0] ?? node,
|
|
messageId: 'notFound',
|
|
});
|
|
}
|
|
exportCount.set(AST_NODE_TYPES.ExportAllDeclaration, exportAll);
|
|
exportCount.set(AST_NODE_TYPES.ImportNamespaceSpecifier, namespaceImports);
|
|
};
|
|
const checkUsage = (node, exportedValue, isTypeExport) => {
|
|
if (!unusedExports) {
|
|
return;
|
|
}
|
|
if (isTypeExport && ignoreUnusedTypeExports) {
|
|
return;
|
|
}
|
|
if (ignoredFiles.has(filename)) {
|
|
return;
|
|
}
|
|
if (fileIsInPkg(filename)) {
|
|
return;
|
|
}
|
|
if (filesOutsideSrc.has(filename)) {
|
|
return;
|
|
}
|
|
if (!srcFiles.has(filename)) {
|
|
srcFiles = resolveFiles(src, ignoreExports, context);
|
|
if (!srcFiles.has(filename)) {
|
|
filesOutsideSrc.add(filename);
|
|
return;
|
|
}
|
|
}
|
|
const exports = exportList.get(filename);
|
|
if (!exports) {
|
|
console.error(`file \`${filename}\` has no exports. Please update to the latest, and if it still happens, report this on https://github.com/import-js/eslint-plugin-import/issues/2866!`);
|
|
return;
|
|
}
|
|
const exportAll = exports.get(AST_NODE_TYPES.ExportAllDeclaration);
|
|
if (exportAll !== undefined &&
|
|
exportedValue !== AST_NODE_TYPES.ImportDefaultSpecifier &&
|
|
exportAll.whereUsed.size > 0) {
|
|
return;
|
|
}
|
|
const namespaceImports = exports.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
if (namespaceImports !== undefined &&
|
|
namespaceImports.whereUsed.size > 0) {
|
|
return;
|
|
}
|
|
const exportsKey = exportedValue === DEFAULT
|
|
? AST_NODE_TYPES.ImportDefaultSpecifier
|
|
: exportedValue;
|
|
const exportStatement = exports.get(exportsKey);
|
|
const value = exportsKey === AST_NODE_TYPES.ImportDefaultSpecifier
|
|
? DEFAULT
|
|
: exportsKey;
|
|
if (exportStatement === undefined) {
|
|
context.report({
|
|
node,
|
|
messageId: 'unused',
|
|
data: {
|
|
value,
|
|
},
|
|
});
|
|
}
|
|
else {
|
|
if (exportStatement.whereUsed.size === 0) {
|
|
context.report({
|
|
node,
|
|
messageId: 'unused',
|
|
data: {
|
|
value,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
};
|
|
const updateExportUsage = (node) => {
|
|
if (ignoredFiles.has(filename)) {
|
|
return;
|
|
}
|
|
const exports = exportList.get(filename) ??
|
|
new Map();
|
|
const newExports = new Map();
|
|
const newExportIdentifiers = new Set();
|
|
for (const s of node.body) {
|
|
if (s.type === AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
newExportIdentifiers.add(AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
}
|
|
if (s.type === AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
if (s.specifiers.length > 0) {
|
|
for (const specifier of s.specifiers) {
|
|
if (specifier.exported) {
|
|
newExportIdentifiers.add(getValue(specifier.exported));
|
|
}
|
|
}
|
|
}
|
|
forEachDeclarationIdentifier(s.declaration, name => {
|
|
newExportIdentifiers.add(name);
|
|
});
|
|
}
|
|
}
|
|
for (const [key, value] of exports.entries()) {
|
|
if (newExportIdentifiers.has(key)) {
|
|
newExports.set(key, value);
|
|
}
|
|
}
|
|
for (const key of newExportIdentifiers) {
|
|
if (!exports.has(key)) {
|
|
newExports.set(key, { whereUsed: new Set() });
|
|
}
|
|
}
|
|
const exportAll = exports.get(AST_NODE_TYPES.ExportAllDeclaration);
|
|
const namespaceImports = exports.get(AST_NODE_TYPES.ImportNamespaceSpecifier) ?? { whereUsed: new Set() };
|
|
newExports.set(AST_NODE_TYPES.ExportAllDeclaration, exportAll);
|
|
newExports.set(AST_NODE_TYPES.ImportNamespaceSpecifier, namespaceImports);
|
|
exportList.set(filename, newExports);
|
|
};
|
|
const updateImportUsage = (node) => {
|
|
if (!unusedExports) {
|
|
return;
|
|
}
|
|
const oldImportPaths = importList.get(filename) ?? new Map();
|
|
const oldNamespaceImports = new Set();
|
|
const newNamespaceImports = new Set();
|
|
const oldExportAll = new Set();
|
|
const newExportAll = new Set();
|
|
const oldDefaultImports = new Set();
|
|
const newDefaultImports = new Set();
|
|
const oldImports = new Map();
|
|
const newImports = new Map();
|
|
for (const [key, value] of oldImportPaths.entries()) {
|
|
if (value.has(AST_NODE_TYPES.ExportAllDeclaration)) {
|
|
oldExportAll.add(key);
|
|
}
|
|
if (value.has(AST_NODE_TYPES.ImportNamespaceSpecifier)) {
|
|
oldNamespaceImports.add(key);
|
|
}
|
|
if (value.has(AST_NODE_TYPES.ImportDefaultSpecifier)) {
|
|
oldDefaultImports.add(key);
|
|
}
|
|
for (const val of value) {
|
|
if (val !== AST_NODE_TYPES.ImportNamespaceSpecifier &&
|
|
val !== AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
oldImports.set(val, key);
|
|
}
|
|
}
|
|
}
|
|
function processDynamicImport(source) {
|
|
if (source.type !== 'Literal' || typeof source.value !== 'string') {
|
|
return null;
|
|
}
|
|
const p = resolve(source.value, context);
|
|
if (p == null) {
|
|
return null;
|
|
}
|
|
newNamespaceImports.add(p);
|
|
}
|
|
visit(node, visitorKeyMap.get(filename), {
|
|
ImportExpression(child) {
|
|
processDynamicImport(child.source);
|
|
},
|
|
CallExpression(child_) {
|
|
const child = child_;
|
|
if (child.callee.type === 'Import') {
|
|
processDynamicImport(child.arguments[0]);
|
|
}
|
|
},
|
|
});
|
|
for (const astNode of node.body) {
|
|
let resolvedPath;
|
|
if (astNode.type === AST_NODE_TYPES.ExportNamedDeclaration &&
|
|
astNode.source) {
|
|
resolvedPath = resolve(astNode.source.raw.replaceAll(/('|")/g, ''), context);
|
|
for (const specifier of astNode.specifiers) {
|
|
const name = getValue(specifier.local);
|
|
if (name === DEFAULT) {
|
|
newDefaultImports.add(resolvedPath);
|
|
}
|
|
else {
|
|
newImports.set(name, resolvedPath);
|
|
}
|
|
}
|
|
}
|
|
if (astNode.type === AST_NODE_TYPES.ExportAllDeclaration) {
|
|
resolvedPath = resolve(astNode.source.raw.replaceAll(/('|")/g, ''), context);
|
|
newExportAll.add(resolvedPath);
|
|
}
|
|
if (astNode.type === AST_NODE_TYPES.ImportDeclaration) {
|
|
resolvedPath = resolve(astNode.source.raw.replaceAll(/('|")/g, ''), context);
|
|
if (!resolvedPath) {
|
|
continue;
|
|
}
|
|
if (isNodeModule(resolvedPath)) {
|
|
continue;
|
|
}
|
|
if (newNamespaceImportExists(astNode.specifiers)) {
|
|
newNamespaceImports.add(resolvedPath);
|
|
}
|
|
if (newDefaultImportExists(astNode.specifiers)) {
|
|
newDefaultImports.add(resolvedPath);
|
|
}
|
|
for (const specifier of astNode.specifiers.filter(specifier => specifier.type !== AST_NODE_TYPES.ImportDefaultSpecifier &&
|
|
specifier.type !== AST_NODE_TYPES.ImportNamespaceSpecifier)) {
|
|
if ('imported' in specifier) {
|
|
newImports.set(getValue(specifier.imported), resolvedPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const value of newExportAll) {
|
|
if (!oldExportAll.has(value)) {
|
|
const imports = oldImportPaths.get(value) ?? new Set();
|
|
imports.add(AST_NODE_TYPES.ExportAllDeclaration);
|
|
oldImportPaths.set(value, imports);
|
|
let exports = exportList.get(value);
|
|
let currentExport;
|
|
if (exports === undefined) {
|
|
exports = new Map();
|
|
exportList.set(value, exports);
|
|
}
|
|
else {
|
|
currentExport = exports.get(AST_NODE_TYPES.ExportAllDeclaration);
|
|
}
|
|
if (currentExport === undefined) {
|
|
const whereUsed = new Set();
|
|
whereUsed.add(filename);
|
|
exports.set(AST_NODE_TYPES.ExportAllDeclaration, {
|
|
whereUsed,
|
|
});
|
|
}
|
|
else {
|
|
currentExport.whereUsed.add(filename);
|
|
}
|
|
}
|
|
}
|
|
for (const value of oldExportAll) {
|
|
if (!newExportAll.has(value)) {
|
|
const imports = oldImportPaths.get(value);
|
|
imports.delete(AST_NODE_TYPES.ExportAllDeclaration);
|
|
const exports = exportList.get(value);
|
|
if (exports !== undefined) {
|
|
const currentExport = exports.get(AST_NODE_TYPES.ExportAllDeclaration);
|
|
if (currentExport !== undefined) {
|
|
currentExport.whereUsed.delete(filename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const value of newDefaultImports) {
|
|
if (!oldDefaultImports.has(value)) {
|
|
let imports = oldImportPaths.get(value);
|
|
if (imports === undefined) {
|
|
imports = new Set();
|
|
}
|
|
imports.add(AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
oldImportPaths.set(value, imports);
|
|
let exports = exportList.get(value);
|
|
let currentExport;
|
|
if (exports === undefined) {
|
|
exports = new Map();
|
|
exportList.set(value, exports);
|
|
}
|
|
else {
|
|
currentExport = exports.get(AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
}
|
|
if (currentExport === undefined) {
|
|
const whereUsed = new Set();
|
|
whereUsed.add(filename);
|
|
exports.set(AST_NODE_TYPES.ImportDefaultSpecifier, {
|
|
whereUsed,
|
|
});
|
|
}
|
|
else {
|
|
currentExport.whereUsed.add(filename);
|
|
}
|
|
}
|
|
}
|
|
for (const value of oldDefaultImports) {
|
|
if (!newDefaultImports.has(value)) {
|
|
const imports = oldImportPaths.get(value);
|
|
imports.delete(AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
const exports = exportList.get(value);
|
|
if (exports !== undefined) {
|
|
const currentExport = exports.get(AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
if (currentExport !== undefined) {
|
|
currentExport.whereUsed.delete(filename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const value of newNamespaceImports) {
|
|
if (!oldNamespaceImports.has(value)) {
|
|
let imports = oldImportPaths.get(value);
|
|
if (imports === undefined) {
|
|
imports = new Set();
|
|
}
|
|
imports.add(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
oldImportPaths.set(value, imports);
|
|
let exports = exportList.get(value);
|
|
let currentExport;
|
|
if (exports === undefined) {
|
|
exports = new Map();
|
|
exportList.set(value, exports);
|
|
}
|
|
else {
|
|
currentExport = exports.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
}
|
|
if (currentExport === undefined) {
|
|
const whereUsed = new Set();
|
|
whereUsed.add(filename);
|
|
exports.set(AST_NODE_TYPES.ImportNamespaceSpecifier, {
|
|
whereUsed,
|
|
});
|
|
}
|
|
else {
|
|
currentExport.whereUsed.add(filename);
|
|
}
|
|
}
|
|
}
|
|
for (const value of oldNamespaceImports) {
|
|
if (!newNamespaceImports.has(value)) {
|
|
const imports = oldImportPaths.get(value);
|
|
imports.delete(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
const exports = exportList.get(value);
|
|
if (exports !== undefined) {
|
|
const currentExport = exports.get(AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
if (currentExport !== undefined) {
|
|
currentExport.whereUsed.delete(filename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const [key, value] of newImports.entries()) {
|
|
if (!oldImports.has(key)) {
|
|
let imports = oldImportPaths.get(value);
|
|
if (imports === undefined) {
|
|
imports = new Set();
|
|
}
|
|
imports.add(key);
|
|
oldImportPaths.set(value, imports);
|
|
let exports = exportList.get(value);
|
|
let currentExport;
|
|
if (exports === undefined) {
|
|
exports = new Map();
|
|
exportList.set(value, exports);
|
|
}
|
|
else {
|
|
currentExport = exports.get(key);
|
|
}
|
|
if (currentExport === undefined) {
|
|
const whereUsed = new Set();
|
|
whereUsed.add(filename);
|
|
exports.set(key, { whereUsed });
|
|
}
|
|
else {
|
|
currentExport.whereUsed.add(filename);
|
|
}
|
|
}
|
|
}
|
|
for (const [key, value] of oldImports.entries()) {
|
|
if (!newImports.has(key)) {
|
|
const imports = oldImportPaths.get(value);
|
|
imports.delete(key);
|
|
const exports = exportList.get(value);
|
|
if (exports !== undefined) {
|
|
const currentExport = exports.get(key);
|
|
if (currentExport !== undefined) {
|
|
currentExport.whereUsed.delete(filename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
return {
|
|
'Program:exit'(node) {
|
|
updateExportUsage(node);
|
|
updateImportUsage(node);
|
|
checkExportPresence(node);
|
|
},
|
|
ExportDefaultDeclaration(node) {
|
|
checkUsage(node, AST_NODE_TYPES.ImportDefaultSpecifier, false);
|
|
},
|
|
ExportNamedDeclaration(node) {
|
|
for (const specifier of node.specifiers) {
|
|
checkUsage(specifier, getValue(specifier.exported), false);
|
|
}
|
|
forEachDeclarationIdentifier(node.declaration, (name, isTypeExport) => {
|
|
checkUsage(node, name, isTypeExport);
|
|
});
|
|
},
|
|
};
|
|
},
|
|
});
|
|
//# sourceMappingURL=no-unused-modules.js.map
|