Path: blob/main/src/rules/no-unused-modules.js
829 views
/**1* @fileOverview Ensures that modules contain exports and/or all2* modules are consumed within other modules.3* @author René Fermann4*/56import Exports, { recursivePatternCapture } from '../ExportMap';7import { getFileExtensions } from 'eslint-module-utils/ignore';8import resolve from 'eslint-module-utils/resolve';9import visit from 'eslint-module-utils/visit';10import docsUrl from '../docsUrl';11import { dirname, join } from 'path';12import readPkgUp from 'eslint-module-utils/readPkgUp';13import values from 'object.values';14import includes from 'array-includes';1516let FileEnumerator;17let listFilesToProcess;1819try {20({ FileEnumerator } = require('eslint/use-at-your-own-risk'));21} catch (e) {22try {23// has been moved to eslint/lib/cli-engine/file-enumerator in version 624({ FileEnumerator } = require('eslint/lib/cli-engine/file-enumerator'));25} catch (e) {26try {27// eslint/lib/util/glob-util has been moved to eslint/lib/util/glob-utils with version 5.328const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-utils');2930// Prevent passing invalid options (extensions array) to old versions of the function.31// https://github.com/eslint/eslint/blob/v5.16.0/lib/util/glob-utils.js#L178-L28032// https://github.com/eslint/eslint/blob/v5.2.0/lib/util/glob-util.js#L174-L26933listFilesToProcess = function (src, extensions) {34return originalListFilesToProcess(src, {35extensions,36});37};38} catch (e) {39const { listFilesToProcess: originalListFilesToProcess } = require('eslint/lib/util/glob-util');4041listFilesToProcess = function (src, extensions) {42const patterns = src.reduce((carry, pattern) => {43return carry.concat(extensions.map((extension) => {44return /\*\*|\*\./.test(pattern) ? pattern : `${pattern}/**/*${extension}`;45}));46}, src.slice());4748return originalListFilesToProcess(patterns);49};50}51}52}5354if (FileEnumerator) {55listFilesToProcess = function (src, extensions) {56const e = new FileEnumerator({57extensions,58});5960return Array.from(e.iterateFiles(src), ({ filePath, ignored }) => ({61ignored,62filename: filePath,63}));64};65}6667const EXPORT_DEFAULT_DECLARATION = 'ExportDefaultDeclaration';68const EXPORT_NAMED_DECLARATION = 'ExportNamedDeclaration';69const EXPORT_ALL_DECLARATION = 'ExportAllDeclaration';70const IMPORT_DECLARATION = 'ImportDeclaration';71const IMPORT_NAMESPACE_SPECIFIER = 'ImportNamespaceSpecifier';72const IMPORT_DEFAULT_SPECIFIER = 'ImportDefaultSpecifier';73const VARIABLE_DECLARATION = 'VariableDeclaration';74const FUNCTION_DECLARATION = 'FunctionDeclaration';75const CLASS_DECLARATION = 'ClassDeclaration';76const IDENTIFIER = 'Identifier';77const OBJECT_PATTERN = 'ObjectPattern';78const TS_INTERFACE_DECLARATION = 'TSInterfaceDeclaration';79const TS_TYPE_ALIAS_DECLARATION = 'TSTypeAliasDeclaration';80const TS_ENUM_DECLARATION = 'TSEnumDeclaration';81const DEFAULT = 'default';8283function forEachDeclarationIdentifier(declaration, cb) {84if (declaration) {85if (86declaration.type === FUNCTION_DECLARATION ||87declaration.type === CLASS_DECLARATION ||88declaration.type === TS_INTERFACE_DECLARATION ||89declaration.type === TS_TYPE_ALIAS_DECLARATION ||90declaration.type === TS_ENUM_DECLARATION91) {92cb(declaration.id.name);93} else if (declaration.type === VARIABLE_DECLARATION) {94declaration.declarations.forEach(({ id }) => {95if (id.type === OBJECT_PATTERN) {96recursivePatternCapture(id, (pattern) => {97if (pattern.type === IDENTIFIER) {98cb(pattern.name);99}100});101} else {102cb(id.name);103}104});105}106}107}108109/**110* List of imports per file.111*112* Represented by a two-level Map to a Set of identifiers. The upper-level Map113* keys are the paths to the modules containing the imports, while the114* lower-level Map keys are the paths to the files which are being imported115* from. Lastly, the Set of identifiers contains either names being imported116* or a special AST node name listed above (e.g ImportDefaultSpecifier).117*118* For example, if we have a file named foo.js containing:119*120* import { o2 } from './bar.js';121*122* Then we will have a structure that looks like:123*124* Map { 'foo.js' => Map { 'bar.js' => Set { 'o2' } } }125*126* @type {Map<string, Map<string, Set<string>>>}127*/128const importList = new Map();129130/**131* List of exports per file.132*133* Represented by a two-level Map to an object of metadata. The upper-level Map134* keys are the paths to the modules containing the exports, while the135* lower-level Map keys are the specific identifiers or special AST node names136* being exported. The leaf-level metadata object at the moment only contains a137* `whereUsed` property, which contains a Set of paths to modules that import138* the name.139*140* For example, if we have a file named bar.js containing the following exports:141*142* const o2 = 'bar';143* export { o2 };144*145* And a file named foo.js containing the following import:146*147* import { o2 } from './bar.js';148*149* Then we will have a structure that looks like:150*151* Map { 'bar.js' => Map { 'o2' => { whereUsed: Set { 'foo.js' } } } }152*153* @type {Map<string, Map<string, object>>}154*/155const exportList = new Map();156157const visitorKeyMap = new Map();158159const ignoredFiles = new Set();160const filesOutsideSrc = new Set();161162const isNodeModule = path => {163return /\/(node_modules)\//.test(path);164};165166/**167* read all files matching the patterns in src and ignoreExports168*169* return all files matching src pattern, which are not matching the ignoreExports pattern170*/171const resolveFiles = (src, ignoreExports, context) => {172const extensions = Array.from(getFileExtensions(context.settings));173174const srcFiles = new Set();175const srcFileList = listFilesToProcess(src, extensions);176177// prepare list of ignored files178const ignoredFilesList = listFilesToProcess(ignoreExports, extensions);179ignoredFilesList.forEach(({ filename }) => ignoredFiles.add(filename));180181// prepare list of source files, don't consider files from node_modules182srcFileList.filter(({ filename }) => !isNodeModule(filename)).forEach(({ filename }) => {183srcFiles.add(filename);184});185return srcFiles;186};187188/**189* parse all source files and build up 2 maps containing the existing imports and exports190*/191const prepareImportsAndExports = (srcFiles, context) => {192const exportAll = new Map();193srcFiles.forEach(file => {194const exports = new Map();195const imports = new Map();196const currentExports = Exports.get(file, context);197if (currentExports) {198const {199dependencies,200reexports,201imports: localImportList,202namespace,203visitorKeys,204} = currentExports;205206visitorKeyMap.set(file, visitorKeys);207// dependencies === export * from208const currentExportAll = new Set();209dependencies.forEach(getDependency => {210const dependency = getDependency();211if (dependency === null) {212return;213}214215currentExportAll.add(dependency.path);216});217exportAll.set(file, currentExportAll);218219reexports.forEach((value, key) => {220if (key === DEFAULT) {221exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() });222} else {223exports.set(key, { whereUsed: new Set() });224}225const reexport = value.getImport();226if (!reexport) {227return;228}229let localImport = imports.get(reexport.path);230let currentValue;231if (value.local === DEFAULT) {232currentValue = IMPORT_DEFAULT_SPECIFIER;233} else {234currentValue = value.local;235}236if (typeof localImport !== 'undefined') {237localImport = new Set([...localImport, currentValue]);238} else {239localImport = new Set([currentValue]);240}241imports.set(reexport.path, localImport);242});243244localImportList.forEach((value, key) => {245if (isNodeModule(key)) {246return;247}248const localImport = imports.get(key) || new Set();249value.declarations.forEach(({ importedSpecifiers }) =>250importedSpecifiers.forEach(specifier => localImport.add(specifier)),251);252imports.set(key, localImport);253});254importList.set(file, imports);255256// build up export list only, if file is not ignored257if (ignoredFiles.has(file)) {258return;259}260namespace.forEach((value, key) => {261if (key === DEFAULT) {262exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed: new Set() });263} else {264exports.set(key, { whereUsed: new Set() });265}266});267}268exports.set(EXPORT_ALL_DECLARATION, { whereUsed: new Set() });269exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed: new Set() });270exportList.set(file, exports);271});272exportAll.forEach((value, key) => {273value.forEach(val => {274const currentExports = exportList.get(val);275const currentExport = currentExports.get(EXPORT_ALL_DECLARATION);276currentExport.whereUsed.add(key);277});278});279};280281/**282* traverse through all imports and add the respective path to the whereUsed-list283* of the corresponding export284*/285const determineUsage = () => {286importList.forEach((listValue, listKey) => {287listValue.forEach((value, key) => {288const exports = exportList.get(key);289if (typeof exports !== 'undefined') {290value.forEach(currentImport => {291let specifier;292if (currentImport === IMPORT_NAMESPACE_SPECIFIER) {293specifier = IMPORT_NAMESPACE_SPECIFIER;294} else if (currentImport === IMPORT_DEFAULT_SPECIFIER) {295specifier = IMPORT_DEFAULT_SPECIFIER;296} else {297specifier = currentImport;298}299if (typeof specifier !== 'undefined') {300const exportStatement = exports.get(specifier);301if (typeof exportStatement !== 'undefined') {302const { whereUsed } = exportStatement;303whereUsed.add(listKey);304exports.set(specifier, { whereUsed });305}306}307});308}309});310});311};312313const getSrc = src => {314if (src) {315return src;316}317return [process.cwd()];318};319320/**321* prepare the lists of existing imports and exports - should only be executed once at322* the start of a new eslint run323*/324let srcFiles;325let lastPrepareKey;326const doPreparation = (src, ignoreExports, context) => {327const prepareKey = JSON.stringify({328src: (src || []).sort(),329ignoreExports: (ignoreExports || []).sort(),330extensions: Array.from(getFileExtensions(context.settings)).sort(),331});332if (prepareKey === lastPrepareKey) {333return;334}335336importList.clear();337exportList.clear();338ignoredFiles.clear();339filesOutsideSrc.clear();340341srcFiles = resolveFiles(getSrc(src), ignoreExports, context);342prepareImportsAndExports(srcFiles, context);343determineUsage();344lastPrepareKey = prepareKey;345};346347const newNamespaceImportExists = specifiers =>348specifiers.some(({ type }) => type === IMPORT_NAMESPACE_SPECIFIER);349350const newDefaultImportExists = specifiers =>351specifiers.some(({ type }) => type === IMPORT_DEFAULT_SPECIFIER);352353const fileIsInPkg = file => {354const { path, pkg } = readPkgUp({ cwd: file });355const basePath = dirname(path);356357const checkPkgFieldString = pkgField => {358if (join(basePath, pkgField) === file) {359return true;360}361};362363const checkPkgFieldObject = pkgField => {364const pkgFieldFiles = values(pkgField).map(value => join(basePath, value));365if (includes(pkgFieldFiles, file)) {366return true;367}368};369370const checkPkgField = pkgField => {371if (typeof pkgField === 'string') {372return checkPkgFieldString(pkgField);373}374375if (typeof pkgField === 'object') {376return checkPkgFieldObject(pkgField);377}378};379380if (pkg.private === true) {381return false;382}383384if (pkg.bin) {385if (checkPkgField(pkg.bin)) {386return true;387}388}389390if (pkg.browser) {391if (checkPkgField(pkg.browser)) {392return true;393}394}395396if (pkg.main) {397if (checkPkgFieldString(pkg.main)) {398return true;399}400}401402return false;403};404405module.exports = {406meta: {407type: 'suggestion',408docs: { url: docsUrl('no-unused-modules') },409schema: [{410properties: {411src: {412description: 'files/paths to be analyzed (only for unused exports)',413type: 'array',414minItems: 1,415items: {416type: 'string',417minLength: 1,418},419},420ignoreExports: {421description:422'files/paths for which unused exports will not be reported (e.g module entry points)',423type: 'array',424minItems: 1,425items: {426type: 'string',427minLength: 1,428},429},430missingExports: {431description: 'report modules without any exports',432type: 'boolean',433},434unusedExports: {435description: 'report exports without any usage',436type: 'boolean',437},438},439not: {440properties: {441unusedExports: { enum: [false] },442missingExports: { enum: [false] },443},444},445anyOf:[{446not: {447properties: {448unusedExports: { enum: [true] },449},450},451required: ['missingExports'],452}, {453not: {454properties: {455missingExports: { enum: [true] },456},457},458required: ['unusedExports'],459}, {460properties: {461unusedExports: { enum: [true] },462},463required: ['unusedExports'],464}, {465properties: {466missingExports: { enum: [true] },467},468required: ['missingExports'],469}],470}],471},472473create: context => {474const {475src,476ignoreExports = [],477missingExports,478unusedExports,479} = context.options[0] || {};480481if (unusedExports) {482doPreparation(src, ignoreExports, context);483}484485const file = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();486487const checkExportPresence = node => {488if (!missingExports) {489return;490}491492if (ignoredFiles.has(file)) {493return;494}495496const exportCount = exportList.get(file);497const exportAll = exportCount.get(EXPORT_ALL_DECLARATION);498const namespaceImports = exportCount.get(IMPORT_NAMESPACE_SPECIFIER);499500exportCount.delete(EXPORT_ALL_DECLARATION);501exportCount.delete(IMPORT_NAMESPACE_SPECIFIER);502if (exportCount.size < 1) {503// node.body[0] === 'undefined' only happens, if everything is commented out in the file504// being linted505context.report(node.body[0] ? node.body[0] : node, 'No exports found');506}507exportCount.set(EXPORT_ALL_DECLARATION, exportAll);508exportCount.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);509};510511const checkUsage = (node, exportedValue) => {512if (!unusedExports) {513return;514}515516if (ignoredFiles.has(file)) {517return;518}519520if (fileIsInPkg(file)) {521return;522}523524if (filesOutsideSrc.has(file)) {525return;526}527528// make sure file to be linted is included in source files529if (!srcFiles.has(file)) {530srcFiles = resolveFiles(getSrc(src), ignoreExports, context);531if (!srcFiles.has(file)) {532filesOutsideSrc.add(file);533return;534}535}536537exports = exportList.get(file);538539// special case: export * from540const exportAll = exports.get(EXPORT_ALL_DECLARATION);541if (typeof exportAll !== 'undefined' && exportedValue !== IMPORT_DEFAULT_SPECIFIER) {542if (exportAll.whereUsed.size > 0) {543return;544}545}546547// special case: namespace import548const namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER);549if (typeof namespaceImports !== 'undefined') {550if (namespaceImports.whereUsed.size > 0) {551return;552}553}554555// exportsList will always map any imported value of 'default' to 'ImportDefaultSpecifier'556const exportsKey = exportedValue === DEFAULT ? IMPORT_DEFAULT_SPECIFIER : exportedValue;557558const exportStatement = exports.get(exportsKey);559560const value = exportsKey === IMPORT_DEFAULT_SPECIFIER ? DEFAULT : exportsKey;561562if (typeof exportStatement !== 'undefined') {563if (exportStatement.whereUsed.size < 1) {564context.report(565node,566`exported declaration '${value}' not used within other modules`,567);568}569} else {570context.report(571node,572`exported declaration '${value}' not used within other modules`,573);574}575};576577/**578* only useful for tools like vscode-eslint579*580* update lists of existing exports during runtime581*/582const updateExportUsage = node => {583if (ignoredFiles.has(file)) {584return;585}586587let exports = exportList.get(file);588589// new module has been created during runtime590// include it in further processing591if (typeof exports === 'undefined') {592exports = new Map();593}594595const newExports = new Map();596const newExportIdentifiers = new Set();597598node.body.forEach(({ type, declaration, specifiers }) => {599if (type === EXPORT_DEFAULT_DECLARATION) {600newExportIdentifiers.add(IMPORT_DEFAULT_SPECIFIER);601}602if (type === EXPORT_NAMED_DECLARATION) {603if (specifiers.length > 0) {604specifiers.forEach(specifier => {605if (specifier.exported) {606newExportIdentifiers.add(specifier.exported.name);607}608});609}610forEachDeclarationIdentifier(declaration, (name) => {611newExportIdentifiers.add(name);612});613}614});615616// old exports exist within list of new exports identifiers: add to map of new exports617exports.forEach((value, key) => {618if (newExportIdentifiers.has(key)) {619newExports.set(key, value);620}621});622623// new export identifiers added: add to map of new exports624newExportIdentifiers.forEach(key => {625if (!exports.has(key)) {626newExports.set(key, { whereUsed: new Set() });627}628});629630// preserve information about namespace imports631const exportAll = exports.get(EXPORT_ALL_DECLARATION);632let namespaceImports = exports.get(IMPORT_NAMESPACE_SPECIFIER);633634if (typeof namespaceImports === 'undefined') {635namespaceImports = { whereUsed: new Set() };636}637638newExports.set(EXPORT_ALL_DECLARATION, exportAll);639newExports.set(IMPORT_NAMESPACE_SPECIFIER, namespaceImports);640exportList.set(file, newExports);641};642643/**644* only useful for tools like vscode-eslint645*646* update lists of existing imports during runtime647*/648const updateImportUsage = node => {649if (!unusedExports) {650return;651}652653let oldImportPaths = importList.get(file);654if (typeof oldImportPaths === 'undefined') {655oldImportPaths = new Map();656}657658const oldNamespaceImports = new Set();659const newNamespaceImports = new Set();660661const oldExportAll = new Set();662const newExportAll = new Set();663664const oldDefaultImports = new Set();665const newDefaultImports = new Set();666667const oldImports = new Map();668const newImports = new Map();669oldImportPaths.forEach((value, key) => {670if (value.has(EXPORT_ALL_DECLARATION)) {671oldExportAll.add(key);672}673if (value.has(IMPORT_NAMESPACE_SPECIFIER)) {674oldNamespaceImports.add(key);675}676if (value.has(IMPORT_DEFAULT_SPECIFIER)) {677oldDefaultImports.add(key);678}679value.forEach(val => {680if (val !== IMPORT_NAMESPACE_SPECIFIER &&681val !== IMPORT_DEFAULT_SPECIFIER) {682oldImports.set(val, key);683}684});685});686687function processDynamicImport(source) {688if (source.type !== 'Literal') {689return null;690}691const p = resolve(source.value, context);692if (p == null) {693return null;694}695newNamespaceImports.add(p);696}697698visit(node, visitorKeyMap.get(file), {699ImportExpression(child) {700processDynamicImport(child.source);701},702CallExpression(child) {703if (child.callee.type === 'Import') {704processDynamicImport(child.arguments[0]);705}706},707});708709node.body.forEach(astNode => {710let resolvedPath;711712// support for export { value } from 'module'713if (astNode.type === EXPORT_NAMED_DECLARATION) {714if (astNode.source) {715resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);716astNode.specifiers.forEach(specifier => {717const name = specifier.local.name;718if (specifier.local.name === DEFAULT) {719newDefaultImports.add(resolvedPath);720} else {721newImports.set(name, resolvedPath);722}723});724}725}726727if (astNode.type === EXPORT_ALL_DECLARATION) {728resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);729newExportAll.add(resolvedPath);730}731732if (astNode.type === IMPORT_DECLARATION) {733resolvedPath = resolve(astNode.source.raw.replace(/('|")/g, ''), context);734if (!resolvedPath) {735return;736}737738if (isNodeModule(resolvedPath)) {739return;740}741742if (newNamespaceImportExists(astNode.specifiers)) {743newNamespaceImports.add(resolvedPath);744}745746if (newDefaultImportExists(astNode.specifiers)) {747newDefaultImports.add(resolvedPath);748}749750astNode.specifiers.forEach(specifier => {751if (specifier.type === IMPORT_DEFAULT_SPECIFIER ||752specifier.type === IMPORT_NAMESPACE_SPECIFIER) {753return;754}755newImports.set(specifier.imported.name, resolvedPath);756});757}758});759760newExportAll.forEach(value => {761if (!oldExportAll.has(value)) {762let imports = oldImportPaths.get(value);763if (typeof imports === 'undefined') {764imports = new Set();765}766imports.add(EXPORT_ALL_DECLARATION);767oldImportPaths.set(value, imports);768769let exports = exportList.get(value);770let currentExport;771if (typeof exports !== 'undefined') {772currentExport = exports.get(EXPORT_ALL_DECLARATION);773} else {774exports = new Map();775exportList.set(value, exports);776}777778if (typeof currentExport !== 'undefined') {779currentExport.whereUsed.add(file);780} else {781const whereUsed = new Set();782whereUsed.add(file);783exports.set(EXPORT_ALL_DECLARATION, { whereUsed });784}785}786});787788oldExportAll.forEach(value => {789if (!newExportAll.has(value)) {790const imports = oldImportPaths.get(value);791imports.delete(EXPORT_ALL_DECLARATION);792793const exports = exportList.get(value);794if (typeof exports !== 'undefined') {795const currentExport = exports.get(EXPORT_ALL_DECLARATION);796if (typeof currentExport !== 'undefined') {797currentExport.whereUsed.delete(file);798}799}800}801});802803newDefaultImports.forEach(value => {804if (!oldDefaultImports.has(value)) {805let imports = oldImportPaths.get(value);806if (typeof imports === 'undefined') {807imports = new Set();808}809imports.add(IMPORT_DEFAULT_SPECIFIER);810oldImportPaths.set(value, imports);811812let exports = exportList.get(value);813let currentExport;814if (typeof exports !== 'undefined') {815currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER);816} else {817exports = new Map();818exportList.set(value, exports);819}820821if (typeof currentExport !== 'undefined') {822currentExport.whereUsed.add(file);823} else {824const whereUsed = new Set();825whereUsed.add(file);826exports.set(IMPORT_DEFAULT_SPECIFIER, { whereUsed });827}828}829});830831oldDefaultImports.forEach(value => {832if (!newDefaultImports.has(value)) {833const imports = oldImportPaths.get(value);834imports.delete(IMPORT_DEFAULT_SPECIFIER);835836const exports = exportList.get(value);837if (typeof exports !== 'undefined') {838const currentExport = exports.get(IMPORT_DEFAULT_SPECIFIER);839if (typeof currentExport !== 'undefined') {840currentExport.whereUsed.delete(file);841}842}843}844});845846newNamespaceImports.forEach(value => {847if (!oldNamespaceImports.has(value)) {848let imports = oldImportPaths.get(value);849if (typeof imports === 'undefined') {850imports = new Set();851}852imports.add(IMPORT_NAMESPACE_SPECIFIER);853oldImportPaths.set(value, imports);854855let exports = exportList.get(value);856let currentExport;857if (typeof exports !== 'undefined') {858currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER);859} else {860exports = new Map();861exportList.set(value, exports);862}863864if (typeof currentExport !== 'undefined') {865currentExport.whereUsed.add(file);866} else {867const whereUsed = new Set();868whereUsed.add(file);869exports.set(IMPORT_NAMESPACE_SPECIFIER, { whereUsed });870}871}872});873874oldNamespaceImports.forEach(value => {875if (!newNamespaceImports.has(value)) {876const imports = oldImportPaths.get(value);877imports.delete(IMPORT_NAMESPACE_SPECIFIER);878879const exports = exportList.get(value);880if (typeof exports !== 'undefined') {881const currentExport = exports.get(IMPORT_NAMESPACE_SPECIFIER);882if (typeof currentExport !== 'undefined') {883currentExport.whereUsed.delete(file);884}885}886}887});888889newImports.forEach((value, key) => {890if (!oldImports.has(key)) {891let imports = oldImportPaths.get(value);892if (typeof imports === 'undefined') {893imports = new Set();894}895imports.add(key);896oldImportPaths.set(value, imports);897898let exports = exportList.get(value);899let currentExport;900if (typeof exports !== 'undefined') {901currentExport = exports.get(key);902} else {903exports = new Map();904exportList.set(value, exports);905}906907if (typeof currentExport !== 'undefined') {908currentExport.whereUsed.add(file);909} else {910const whereUsed = new Set();911whereUsed.add(file);912exports.set(key, { whereUsed });913}914}915});916917oldImports.forEach((value, key) => {918if (!newImports.has(key)) {919const imports = oldImportPaths.get(value);920imports.delete(key);921922const exports = exportList.get(value);923if (typeof exports !== 'undefined') {924const currentExport = exports.get(key);925if (typeof currentExport !== 'undefined') {926currentExport.whereUsed.delete(file);927}928}929}930});931};932933return {934'Program:exit': node => {935updateExportUsage(node);936updateImportUsage(node);937checkExportPresence(node);938},939'ExportDefaultDeclaration': node => {940checkUsage(node, IMPORT_DEFAULT_SPECIFIER);941},942'ExportNamedDeclaration': node => {943node.specifiers.forEach(specifier => {944checkUsage(node, specifier.exported.name);945});946forEachDeclarationIdentifier(node.declaration, (name) => {947checkUsage(node, name);948});949},950};951},952};953954955