diff --git a/fixtures/re-exports-deep/1-entry.ts b/fixtures/re-exports-deep/1-entry.ts new file mode 100644 index 000000000..b8957602d --- /dev/null +++ b/fixtures/re-exports-deep/1-entry.ts @@ -0,0 +1,2 @@ +import { something } from './2-re-export-star'; +something; diff --git a/fixtures/re-exports-deep/2-re-export-star.ts b/fixtures/re-exports-deep/2-re-export-star.ts new file mode 100644 index 000000000..b2b944f62 --- /dev/null +++ b/fixtures/re-exports-deep/2-re-export-star.ts @@ -0,0 +1 @@ +export * from './3-re-export-named'; diff --git a/fixtures/re-exports-deep/3-re-export-named.ts b/fixtures/re-exports-deep/3-re-export-named.ts new file mode 100644 index 000000000..25a11d2df --- /dev/null +++ b/fixtures/re-exports-deep/3-re-export-named.ts @@ -0,0 +1 @@ +export { something } from './4-re-export-star.js'; diff --git a/fixtures/re-exports-deep/4-re-export-star.ts b/fixtures/re-exports-deep/4-re-export-star.ts new file mode 100644 index 000000000..7584c72f2 --- /dev/null +++ b/fixtures/re-exports-deep/4-re-export-star.ts @@ -0,0 +1 @@ +export * from './5-re-export-named'; diff --git a/fixtures/re-exports-deep/5-re-export-named.ts b/fixtures/re-exports-deep/5-re-export-named.ts new file mode 100644 index 000000000..c3a4997a5 --- /dev/null +++ b/fixtures/re-exports-deep/5-re-export-named.ts @@ -0,0 +1 @@ +export { something } from './6-re-export-star'; diff --git a/fixtures/re-exports-deep/6-re-export-star.ts b/fixtures/re-exports-deep/6-re-export-star.ts new file mode 100644 index 000000000..9c0f22254 --- /dev/null +++ b/fixtures/re-exports-deep/6-re-export-star.ts @@ -0,0 +1 @@ +export * from './7-my-module'; diff --git a/fixtures/re-exports-deep/7-my-module.ts b/fixtures/re-exports-deep/7-my-module.ts new file mode 100644 index 000000000..1954434e2 --- /dev/null +++ b/fixtures/re-exports-deep/7-my-module.ts @@ -0,0 +1 @@ +export const something = {}; diff --git a/fixtures/re-exports-deep/package.json b/fixtures/re-exports-deep/package.json new file mode 100644 index 000000000..75d6007be --- /dev/null +++ b/fixtures/re-exports-deep/package.json @@ -0,0 +1,11 @@ +{ + "name": "@fixtures/re-exports-deep", + "knip": { + "entry": [ + "1-entry.ts" + ], + "project": [ + "*.ts" + ] + } +} diff --git a/fixtures/re-exports/1-entry.ts b/fixtures/re-exports/1-entry.ts new file mode 100644 index 000000000..e7d5c5a7f --- /dev/null +++ b/fixtures/re-exports/1-entry.ts @@ -0,0 +1,2 @@ +import { something } from './2-re-export-star.js'; +something; diff --git a/fixtures/re-exports/2-re-export-star.ts b/fixtures/re-exports/2-re-export-star.ts new file mode 100644 index 000000000..3aed1919c --- /dev/null +++ b/fixtures/re-exports/2-re-export-star.ts @@ -0,0 +1 @@ +export * from './3-re-export-named.js'; diff --git a/fixtures/re-exports/3-re-export-named.ts b/fixtures/re-exports/3-re-export-named.ts new file mode 100644 index 000000000..e16770ea7 --- /dev/null +++ b/fixtures/re-exports/3-re-export-named.ts @@ -0,0 +1 @@ +export { something } from './4-my-module'; diff --git a/fixtures/re-exports/4-my-module.ts b/fixtures/re-exports/4-my-module.ts new file mode 100644 index 000000000..1954434e2 --- /dev/null +++ b/fixtures/re-exports/4-my-module.ts @@ -0,0 +1 @@ +export const something = {}; diff --git a/fixtures/re-exports/package.json b/fixtures/re-exports/package.json new file mode 100644 index 000000000..f9066d3ff --- /dev/null +++ b/fixtures/re-exports/package.json @@ -0,0 +1,11 @@ +{ + "name": "@fixtures/re-exports", + "knip": { + "entry": [ + "1-entry.ts" + ], + "project": [ + "*.ts" + ] + } +} diff --git a/src/index.ts b/src/index.ts index b916b062b..94ca38b16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -328,6 +328,14 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => { collector.addFileCounts({ processed: analyzedFiles.size, unused: unusedFiles.length }); + const isSymbolImported = (symbol: string, importingModule?: ImportedModule): boolean => { + if (!importingModule) return false; + if (importingModule.symbols.has(symbol)) return true; + const { isReExport, isReExportedBy } = importingModule; + const hasSymbol = (file: string) => isSymbolImported(symbol, importedSymbols.get(file)); + return isReExport ? Array.from(isReExportedBy).some(hasSymbol) : false; + }; + const isExportedInEntryFile = (importedModule?: ImportedModule): boolean => { if (!importedModule) return false; const { isReExport, isReExportedBy } = importedModule; @@ -354,7 +362,7 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => { // Bail out when in entry file (unless --include-entry-exports) if (!isIncludeEntryExports && principal.entryPaths.has(filePath)) continue; - const importedModule = importedSymbols.get(filePath); + const importingModule = importedSymbols.get(filePath); for (const [symbol, exportedItem] of exportItems.entries()) { const jsDocTags = principal.getJSDocTags(exportedItem); @@ -365,9 +373,9 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => { // Skip exports tagged `@internal` in --production --ignore-internal mode if (isIgnoreInternal && jsDocTags.includes('@internal')) continue; - if (importedModule?.symbols.has(symbol)) { + if (importingModule && isSymbolImported(symbol, importingModule)) { // Skip members of classes/enums that are eventually exported by entry files - if (importedModule.isReExport && isExportedInEntryFile(importedModule)) continue; + if (importingModule.isReExport && isExportedInEntryFile(importingModule)) continue; if (report.enumMembers && exportedItem.type === 'enum' && exportedItem.members) { if (isProduction) continue; @@ -386,8 +394,8 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => { continue; } - const isStar = Boolean(importedModule?.isStar); - const isReExportedByEntryFile = !isIncludeEntryExports && isStar && isExportedInEntryFile(importedModule); + const isStar = Boolean(importingModule?.isStar); + const isReExportedByEntryFile = !isIncludeEntryExports && isStar && isExportedInEntryFile(importingModule); if (!isReExportedByEntryFile && !isExportedItemReferenced(exportedItem, filePath)) { if (['enum', 'type', 'interface'].includes(exportedItem.type)) {