ts-unused-exports
Version:
ts-unused-exports finds unused exported symbols in your Typescript project
231 lines • 9.73 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var util_1 = require("./parser/util");
var isExportArray = function (e) {
return e.startsWith('[') && e.endsWith(']');
};
var parseExportArray = function (e) {
return e
.replace('[', '')
.replace(']', '')
.split(',')
.map(function (e) { return e.trim(); });
};
var getFileExports = function (file) {
var exports = {};
file.exports.forEach(function (e, index) {
var addExport = function (exportName) {
exports[exportName] = {
usageCount: 0,
location: file.exportLocations[index],
};
};
if (isExportArray(e)) {
var exportArray = parseExportArray(e);
exportArray.forEach(addExport);
}
else {
addExport(e);
}
});
return { exports: exports, path: file.fullPath, fileWasImported: false };
};
var getExportMap = function (files) {
var map = {};
files.forEach(function (file) {
map[file.path] = getFileExports(file);
});
return map;
};
var processImports = function (file, exportMap) {
Object.keys(file.imports).forEach(function (key) {
var _a, _b, _c;
var importedFileExports = exportMap[util_1.removeFileExtensionToAllowForJsTsJsxTsx(key)] || null;
var ex = (importedFileExports === null || importedFileExports === void 0 ? void 0 : importedFileExports.exports) || null;
// Handle imports from an index file
if (!ex) {
// Try matching import from /a/b/c -> /a/b/c/index.x
for (var c = 0; c < util_1.indexCandidates.length; c++) {
var indexKey = util_1.indexCandidates[c];
ex = ((_a = exportMap[indexKey]) === null || _a === void 0 ? void 0 : _a.exports) || undefined;
if (ex)
break;
}
if (!ex && key.endsWith('index')) {
// Try matching import from /a/b/c/index -> /a/b/c/index.x
for (var c = 0; c < util_1.indexCandidateExtensions.length; c++) {
var indexKey = key + util_1.indexCandidateExtensions[c];
ex = ((_b = exportMap[indexKey]) === null || _b === void 0 ? void 0 : _b.exports) || undefined;
if (ex)
break;
}
if (!ex) {
// Try matching import from /a/b/c/index -> /a/b/c
var indexKey = key.substring(0, key.length - '/index'.length);
ex = ((_c = exportMap[indexKey]) === null || _c === void 0 ? void 0 : _c.exports) || undefined;
}
}
}
if (!ex) {
// DEV DEBUG - console.warn(`Could not resolve ${key}`);
return;
}
var addUsage = function (imp) {
if (!ex[imp]) {
// The imported symbol we are checking was not found in the imported
// file. For example:
// `a.ts` import { b } from './b';
// `b.ts` does not export a `b` symbol
// In here `imp` is `b`, `imports` represents `a.ts` and `ex.exports`
// are the symbols exported by `b.ts`
ex[imp] = {
usageCount: 0,
location: {
line: 1,
character: 1,
},
};
}
// DEV DEBUG - console.log(`Marking as used: ${imp} in ${file.path}`);
ex[imp].usageCount++;
};
if (!!importedFileExports) {
importedFileExports.fileWasImported = true;
}
file.imports[key].forEach(function (imp) {
imp === '*' ? Object.keys(ex).forEach(addUsage) : addUsage(imp);
});
});
};
var expandExportFromStarOrStarAsForFile = function (file, exportMap, prefix, isWithAlias) {
var fileExports = exportMap[file.path];
file.exports
.filter(function (ex) { return ex.startsWith(prefix); })
.forEach(function (ex) {
var _a;
delete fileExports.exports[ex];
var exports = (_a = exportMap[util_1.removeExportStarPrefix(ex)]) === null || _a === void 0 ? void 0 : _a.exports;
if (exports) {
Object.keys(exports)
.filter(function (e) { return e != 'default'; })
.forEach(function (key) {
if (!isWithAlias) {
// Copy the exports from the imported file:
if (!fileExports.exports[key]) {
var export1 = exports[key];
fileExports.exports[key] = {
usageCount: 0,
location: export1.location,
};
}
fileExports.exports[key].usageCount = 0;
}
// else is export-as: so this file exports a new namespace.
// Mark the items as imported, for the imported file:
var importedFileExports = exportMap[util_1.removeExportStarPrefix(ex)];
if (importedFileExports) {
// DEV DEBUG
/* console.log(
`Marking as used: ${key} in ${removeExportStarPrefix(ex)}`,
);
console.dir(Object.keys(exportMap));*/
importedFileExports.fileWasImported = true;
importedFileExports.exports[key].usageCount++;
}
});
}
});
};
// export * from 'a' (no 'alias')
var expandExportFromStarForFile = function (file, exportMap) {
expandExportFromStarOrStarAsForFile(file, exportMap, '*:', false);
};
// export * as X from 'a' (has 'alias')
var expandExportFromStarAsForFile = function (file, exportMap) {
expandExportFromStarOrStarAsForFile(file, exportMap, '*as:', true);
};
var expandExportFromStar = function (files, exportMap) {
files.forEach(function (file) {
expandExportFromStarForFile(file, exportMap);
expandExportFromStarAsForFile(file, exportMap);
});
};
// Allow disabling of *results*, by path from command line (useful for large projects)
var shouldPathBeExcludedFromResults = function (path, extraOptions) {
if (!extraOptions || !extraOptions.pathsToExcludeFromReport) {
return false;
}
return extraOptions.pathsToExcludeFromReport.some(function (ignore) {
return new RegExp(ignore).test(path);
});
};
var filterFiles = function (files, extraOptions) {
var _a;
if (!(extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.ignoreFilesRegex)) {
return files;
}
var regexes = (_a = extraOptions.ignoreFilesRegex) === null || _a === void 0 ? void 0 : _a.map(function (rex) { return new RegExp(rex); });
var shouldIgnoreFile = function (fileName) {
return regexes.some(function (reg) {
return reg.test(fileName);
});
};
return files.filter(function (f) { return !shouldIgnoreFile(f.fullPath); });
};
var areEqual = function (files1, files2) {
if (files1.length !== files2.length)
return false;
return files1.every(function (f) { return files2.includes(f); });
};
var makeExportStarRelativeForPresentation = function (baseUrl, filePath) {
if (!filePath.startsWith('*')) {
return filePath;
}
var filePathNoStar = util_1.removeExportStarPrefix(filePath);
if (!!baseUrl && filePathNoStar.startsWith(baseUrl)) {
return "* -> " + filePathNoStar.substring(baseUrl.length);
}
return filePath;
};
exports.default = (function (files, extraOptions) {
var filteredFiles = filterFiles(files, extraOptions);
var exportMap = getExportMap(filteredFiles);
expandExportFromStar(filteredFiles, exportMap);
filteredFiles.forEach(function (file) { return processImports(file, exportMap); });
var analysis = { unusedExports: {} };
var unusedFiles = [];
Object.keys(exportMap).forEach(function (file) {
var expItem = exportMap[file];
var exports = expItem.exports, path = expItem.path;
if (shouldPathBeExcludedFromResults(path, extraOptions))
return;
var unusedExports = Object.keys(exports).filter(function (k) {
// If the file was imported at least once, then do NOT consider any of its 'export (import) *' as unused.
// This avoids false positives with transitive import/exports like a -> b -> c.
if (expItem.fileWasImported && k.startsWith('*')) {
return false;
}
return exports[k].usageCount === 0;
});
if (unusedExports.length === 0) {
return;
}
var realExportNames = Object.keys(exports);
analysis.unusedExports[path] = [];
unusedExports.forEach(function (e) {
analysis.unusedExports[path].push({
exportName: makeExportStarRelativeForPresentation(extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.baseUrl, e),
location: exports[e].location,
});
});
if ((extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.findCompletelyUnusedFiles) &&
areEqual(realExportNames, unusedExports)) {
unusedFiles.push(path);
}
});
if (extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.findCompletelyUnusedFiles) {
analysis.unusedFiles = unusedFiles;
}
return analysis;
});
//# sourceMappingURL=analyzer.js.map