From 004ae3bcd6a1a9671134dbc03032473cd02c291f Mon Sep 17 00:00:00 2001 From: Lars Kappert Date: Wed, 8 May 2024 21:46:49 +0200 Subject: [PATCH] Refactor dep graph (#626) --- biome.json | 20 +++- packages/knip/fixtures/class-members/index.ts | 5 + packages/knip/fixtures/commonjs/index.js | 11 ++ packages/knip/fixtures/compilers/index.ts | 1 + .../fixtures/custom-paths-workspaces/index.ts | 1 + .../custom-paths-workspaces/my-module.ts | 2 + .../custom-paths-workspaces/ws/index.ts | 3 + .../fixtures/duplicate-exports-alias/index.ts | 1 + .../knip/fixtures/enum-members/members.ts | 2 + packages/knip/fixtures/exports/index.ts | 14 +++ packages/knip/fixtures/exports/my-module.ts | 9 ++ packages/knip/fixtures/exports/odd.ts | 1 + packages/knip/fixtures/import-equals/index.ts | 3 + packages/knip/fixtures/imports/index.ts | 22 ++++ .../fixtures/include-entry-exports/index.ts | 3 +- .../fixtures/pathless/src/dir/module-a.ts | 1 + packages/knip/fixtures/pathless/src/index.ts | 1 + packages/knip/fixtures/paths/index.ts | 4 + .../fixtures/plugins/eleventy3/.eleventy.js | 2 + .../knip/fixtures/plugins/webpack/src/app.ts | 1 + .../fixtures/re-exports-aliased-ns/1-first.ts | 1 + .../re-exports-aliased-ns/2-second.ts | 1 + .../re-exports-aliased-ns/3-barrel.ts | 2 + .../re-exports-aliased-ns/4-collect.ts | 2 + .../fixtures/re-exports-aliased-ns/index.ts | 3 + .../re-exports-aliased-ns/package.json | 3 + .../re-exports-ns-member/member-ab.ts | 1 + .../re-exports-ns-member/member-cd.ts | 1 + .../knip/fixtures/re-exports-renamed/fileA.ts | 2 +- .../knip/fixtures/re-exports-renamed/fileB.ts | 2 +- .../knip/fixtures/re-exports-renamed/index.ts | 5 +- .../knip/fixtures/subpath-import/entry.ts | 1 - .../fixtures/subpath-patterns/src/entry.ts | 1 - .../packages/lib-a/src/index.ts | 1 + .../packages/lib-e/src/index.ts | 1 + packages/knip/src/index.ts | 3 +- packages/knip/src/types/imports.ts | 2 + packages/knip/src/types/serializable-map.ts | 13 ++- packages/knip/src/typescript/ast-helpers.ts | 5 +- .../src/typescript/getImportsAndExports.ts | 103 +++++++++++------- .../visitors/dynamic-imports/importCall.ts | 54 ++++++++- .../visitors/dynamic-imports/requireCall.ts | 17 ++- .../visitors/imports/importDeclaration.ts | 13 ++- .../imports/importEqualsDeclaration.ts | 4 +- .../visitors/imports/reExportDeclaration.ts | 6 +- .../src/util/get-reexporting-entry-file.ts | 2 +- .../knip/src/util/is-identifier-referenced.ts | 21 +++- packages/knip/src/util/type.ts | 4 +- packages/knip/test/cli-reporter-json.test.ts | 12 +- packages/knip/test/exports.test.ts | 2 +- .../imports-namespace-with-nsexports.test.ts | 2 +- .../knip/test/re-exports-aliased-ns.test.ts | 24 ++++ .../knip/test/re-exports-ns-member.test.ts | 6 +- .../util/has-strictly-ns-references.test.ts | 14 +-- 54 files changed, 346 insertions(+), 95 deletions(-) create mode 100644 packages/knip/fixtures/re-exports-aliased-ns/1-first.ts create mode 100644 packages/knip/fixtures/re-exports-aliased-ns/2-second.ts create mode 100644 packages/knip/fixtures/re-exports-aliased-ns/3-barrel.ts create mode 100644 packages/knip/fixtures/re-exports-aliased-ns/4-collect.ts create mode 100644 packages/knip/fixtures/re-exports-aliased-ns/index.ts create mode 100644 packages/knip/fixtures/re-exports-aliased-ns/package.json create mode 100644 packages/knip/test/re-exports-aliased-ns.test.ts diff --git a/biome.json b/biome.json index 0c1a08fef..47b801322 100644 --- a/biome.json +++ b/biome.json @@ -21,8 +21,7 @@ "noUnusedImports": "error" }, "complexity": { - "useLiteralKeys": "off", - "useSimplifiedLogicExpression": "error" + "useLiteralKeys": "off" }, "nursery": { "noRestrictedImports": { @@ -90,6 +89,23 @@ } } } + }, + { + "include": ["packages/knip/fixtures"], + "organizeImports": { + "enabled": false + }, + "linter": { + "rules": { + "correctness": { + "noUnusedVariables": "off", + "noUnusedImports": "off" + }, + "style": { + "useImportType": "off" + } + } + } } ] } diff --git a/packages/knip/fixtures/class-members/index.ts b/packages/knip/fixtures/class-members/index.ts index a3a838e20..89ea23bdf 100644 --- a/packages/knip/fixtures/class-members/index.ts +++ b/packages/knip/fixtures/class-members/index.ts @@ -2,6 +2,11 @@ import { MyClass } from './members'; import { AbstractClassGen, ExtendedClassGen } from './iterator-generator'; import { AbstractClass, ExtendedClass } from './iterator'; +AbstractClassGen; +ExtendedClassGen; +AbstractClass; +ExtendedClass; + const instance = new MyClass(); export class Parent { diff --git a/packages/knip/fixtures/commonjs/index.js b/packages/knip/fixtures/commonjs/index.js index cc597e925..a5129dc5b 100644 --- a/packages/knip/fixtures/commonjs/index.js +++ b/packages/knip/fixtures/commonjs/index.js @@ -19,6 +19,9 @@ const templateStringExternal = value => { const templateStringInternal = value => { const baz = require(`./dir/mod1`); const { identifier } = require(`./dir/mod1`); + + baz; + identifier; }; const requireResolve = value => { @@ -27,6 +30,7 @@ const requireResolve = value => { const requireExportedShorthandsHeuristic = value => { const { identifier9, identifier10 } = require('./dir/mod3'); + [identifier9, identifier10]; }; const staticResolve = () => { @@ -36,3 +40,10 @@ const staticResolve = () => { const dynamicResolve = () => { return require.resolve(path.join(process.cwd(), 'package.json')); }; + +renamed; +defaultName; +named; +all; +staticResolve; +add; diff --git a/packages/knip/fixtures/compilers/index.ts b/packages/knip/fixtures/compilers/index.ts index e43dbb2d3..ed638b459 100644 --- a/packages/knip/fixtures/compilers/index.ts +++ b/packages/knip/fixtures/compilers/index.ts @@ -1 +1,2 @@ import identifier from './module.mdx'; +identifier; diff --git a/packages/knip/fixtures/custom-paths-workspaces/index.ts b/packages/knip/fixtures/custom-paths-workspaces/index.ts index 87471ddaf..41442c166 100644 --- a/packages/knip/fixtures/custom-paths-workspaces/index.ts +++ b/packages/knip/fixtures/custom-paths-workspaces/index.ts @@ -1 +1,2 @@ import anything from '~/my-module'; +anything; diff --git a/packages/knip/fixtures/custom-paths-workspaces/my-module.ts b/packages/knip/fixtures/custom-paths-workspaces/my-module.ts index 567b6a2f9..0fa1c5e38 100644 --- a/packages/knip/fixtures/custom-paths-workspaces/my-module.ts +++ b/packages/knip/fixtures/custom-paths-workspaces/my-module.ts @@ -1,2 +1,4 @@ import index from '@lib'; import fn from '@lib/fn'; +index; +fn; diff --git a/packages/knip/fixtures/custom-paths-workspaces/ws/index.ts b/packages/knip/fixtures/custom-paths-workspaces/ws/index.ts index f3153416c..af1400b7c 100644 --- a/packages/knip/fixtures/custom-paths-workspaces/ws/index.ts +++ b/packages/knip/fixtures/custom-paths-workspaces/ws/index.ts @@ -1,3 +1,6 @@ import main from 'lib/main'; import lang from '#util-foo/lang'; import svg from '~images/logo.svg'; +main; +lang; +svg; diff --git a/packages/knip/fixtures/duplicate-exports-alias/index.ts b/packages/knip/fixtures/duplicate-exports-alias/index.ts index bb3861b68..12d3820ae 100644 --- a/packages/knip/fixtures/duplicate-exports-alias/index.ts +++ b/packages/knip/fixtures/duplicate-exports-alias/index.ts @@ -1 +1,2 @@ import { isAlias } from './helpers'; +isAlias; diff --git a/packages/knip/fixtures/enum-members/members.ts b/packages/knip/fixtures/enum-members/members.ts index f5e6acba8..38075d752 100644 --- a/packages/knip/fixtures/enum-members/members.ts +++ b/packages/knip/fixtures/enum-members/members.ts @@ -12,3 +12,5 @@ export enum MyEnum { } const myNumber: MyEnum.C_UsedInternal = 1; + +type Used = EntryEnum; diff --git a/packages/knip/fixtures/exports/index.ts b/packages/knip/fixtures/exports/index.ts index 59e8cfcbc..235d9afbe 100644 --- a/packages/knip/fixtures/exports/index.ts +++ b/packages/knip/fixtures/exports/index.ts @@ -13,10 +13,24 @@ import { } from './named-exports'; import type { MyNum, MyString, MyInterface } from './types'; +num; +str; +functionName; +className; +generatorFunctionName; +name1; +name4; +exportedA; +exportedB; + +type Used = MyNum | MyString | MyInterface; + const dynamic = await import('./dynamic-import'); +dynamic; async function main() { const { used } = await import('./dynamic-import'); + used; } export const entryFileExport = exportedResult; diff --git a/packages/knip/fixtures/exports/my-module.ts b/packages/knip/fixtures/exports/my-module.ts index fbef39482..fc8da13c1 100644 --- a/packages/knip/fixtures/exports/my-module.ts +++ b/packages/knip/fixtures/exports/my-module.ts @@ -8,6 +8,15 @@ import defaultNamedGenFn from './default-named-generator-function'; import _default from './default.js'; import * as MyNamespace from './my-namespace.js'; +defaultArrowFn; +defaultClass; +defaultFn; +defaultGenFn; +defaultNamedClass; +defaultNamedFn; +defaultNamedGenFn; +_default; + const nsNumber = MyNamespace.nsNumber; const nsFunction = MyNamespace.nsFunction; diff --git a/packages/knip/fixtures/exports/odd.ts b/packages/knip/fixtures/exports/odd.ts index 46d76b846..e2fb79742 100644 --- a/packages/knip/fixtures/exports/odd.ts +++ b/packages/knip/fixtures/exports/odd.ts @@ -1 +1,2 @@ import one = require('./export-is'); +one; diff --git a/packages/knip/fixtures/import-equals/index.ts b/packages/knip/fixtures/import-equals/index.ts index 20bb7b27b..595dbd607 100644 --- a/packages/knip/fixtures/import-equals/index.ts +++ b/packages/knip/fixtures/import-equals/index.ts @@ -2,3 +2,6 @@ import * as NS from './my-module.js'; import local = require('./local.js'); import external = require('external'); import something = NS.something; +local; +external; +something; diff --git a/packages/knip/fixtures/imports/index.ts b/packages/knip/fixtures/imports/index.ts index 38457b0c1..bacc19421 100644 --- a/packages/knip/fixtures/imports/index.ts +++ b/packages/knip/fixtures/imports/index.ts @@ -17,6 +17,8 @@ async function main() { import('./side-effects-call'); await import('./await-import-call'); const { default: defaultName, identifier11: renamedIdentifier, identifier12 } = await import('./object-bindings'); + + [defaultName, renamedIdentifier, identifier12]; } const dynamicImport = (value: string) => { @@ -42,6 +44,8 @@ function promiseAll() { import('./import-a'), import('./dir/import-b'), ]); + + [identifierA, identifierB]; }, }; } @@ -72,3 +76,21 @@ export default fn({ child3: import('./import-e'), }, }); + +[ + topLevel, + top, + dynamic, + defaultName1, + defaultName2, + defaultName3, + defaultName4, + defaultName5, + renamed, + renamed2, + renamed3, + renamedIdentifier, + renamedIdentifier2, + namedC, + identifier14, +]; diff --git a/packages/knip/fixtures/include-entry-exports/index.ts b/packages/knip/fixtures/include-entry-exports/index.ts index f57821d5b..309306c0a 100644 --- a/packages/knip/fixtures/include-entry-exports/index.ts +++ b/packages/knip/fixtures/include-entry-exports/index.ts @@ -1,7 +1,8 @@ import { y } from './mod'; - +y; export default 1; export type EntryType = {}; +// biome-ignore lint/suspicious/noEmptyInterface: export interface EntryInterface {} export enum EntryEnum {} diff --git a/packages/knip/fixtures/pathless/src/dir/module-a.ts b/packages/knip/fixtures/pathless/src/dir/module-a.ts index 24dcdee17..42853dfdd 100644 --- a/packages/knip/fixtures/pathless/src/dir/module-a.ts +++ b/packages/knip/fixtures/pathless/src/dir/module-a.ts @@ -1,3 +1,4 @@ import same from 'same'; +same; export default 1; diff --git a/packages/knip/fixtures/pathless/src/index.ts b/packages/knip/fixtures/pathless/src/index.ts index b6bc30fbf..cc714eb40 100644 --- a/packages/knip/fixtures/pathless/src/index.ts +++ b/packages/knip/fixtures/pathless/src/index.ts @@ -1,4 +1,5 @@ import one from 'dir/module-a'; import same from 'same'; +same; export default one; diff --git a/packages/knip/fixtures/paths/index.ts b/packages/knip/fixtures/paths/index.ts index 09c9a9ff4..277dc540a 100644 --- a/packages/knip/fixtures/paths/index.ts +++ b/packages/knip/fixtures/paths/index.ts @@ -2,3 +2,7 @@ import index from '@lib'; import fn from '@lib/fn'; import js from 'xyz/main.js'; import anything from '~/my-module'; +index; +fn; +js; +anything; diff --git a/packages/knip/fixtures/plugins/eleventy3/.eleventy.js b/packages/knip/fixtures/plugins/eleventy3/.eleventy.js index 0f27237b2..b77a4d443 100644 --- a/packages/knip/fixtures/plugins/eleventy3/.eleventy.js +++ b/packages/knip/fixtures/plugins/eleventy3/.eleventy.js @@ -4,6 +4,8 @@ const eleventyNavigationPlugin = require('@11ty/eleventy-navigation'); const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight'); const path = require('path'); const highlighter = require('./src/_plugins/syntax-highlighter'); +path; +highlighter; module.exports = function (eleventyConfig) { eleventyConfig.addGlobalData('site_name', 'example'); diff --git a/packages/knip/fixtures/plugins/webpack/src/app.ts b/packages/knip/fixtures/plugins/webpack/src/app.ts index e070f286f..73753e35c 100644 --- a/packages/knip/fixtures/plugins/webpack/src/app.ts +++ b/packages/knip/fixtures/plugins/webpack/src/app.ts @@ -1 +1,2 @@ import dep from './app-dep'; +dep; diff --git a/packages/knip/fixtures/re-exports-aliased-ns/1-first.ts b/packages/knip/fixtures/re-exports-aliased-ns/1-first.ts new file mode 100644 index 000000000..8a856e1c5 --- /dev/null +++ b/packages/knip/fixtures/re-exports-aliased-ns/1-first.ts @@ -0,0 +1 @@ +export const first = 1; diff --git a/packages/knip/fixtures/re-exports-aliased-ns/2-second.ts b/packages/knip/fixtures/re-exports-aliased-ns/2-second.ts new file mode 100644 index 000000000..2646bb3a8 --- /dev/null +++ b/packages/knip/fixtures/re-exports-aliased-ns/2-second.ts @@ -0,0 +1 @@ +export const second = 2; diff --git a/packages/knip/fixtures/re-exports-aliased-ns/3-barrel.ts b/packages/knip/fixtures/re-exports-aliased-ns/3-barrel.ts new file mode 100644 index 000000000..54685ff79 --- /dev/null +++ b/packages/knip/fixtures/re-exports-aliased-ns/3-barrel.ts @@ -0,0 +1,2 @@ +export * from './1-first'; +export * from './2-second'; diff --git a/packages/knip/fixtures/re-exports-aliased-ns/4-collect.ts b/packages/knip/fixtures/re-exports-aliased-ns/4-collect.ts new file mode 100644 index 000000000..508586fdc --- /dev/null +++ b/packages/knip/fixtures/re-exports-aliased-ns/4-collect.ts @@ -0,0 +1,2 @@ +import * as NS from './3-barrel'; +export { NS as aliased }; diff --git a/packages/knip/fixtures/re-exports-aliased-ns/index.ts b/packages/knip/fixtures/re-exports-aliased-ns/index.ts new file mode 100644 index 000000000..d2743e204 --- /dev/null +++ b/packages/knip/fixtures/re-exports-aliased-ns/index.ts @@ -0,0 +1,3 @@ +import { aliased } from './4-collect'; + +aliased.first; diff --git a/packages/knip/fixtures/re-exports-aliased-ns/package.json b/packages/knip/fixtures/re-exports-aliased-ns/package.json new file mode 100644 index 000000000..9b2969bff --- /dev/null +++ b/packages/knip/fixtures/re-exports-aliased-ns/package.json @@ -0,0 +1,3 @@ +{ + "name": "@fixtures/re-exports-aliased-ns" +} diff --git a/packages/knip/fixtures/re-exports-ns-member/member-ab.ts b/packages/knip/fixtures/re-exports-ns-member/member-ab.ts index bd33e7033..7295796e9 100644 --- a/packages/knip/fixtures/re-exports-ns-member/member-ab.ts +++ b/packages/knip/fixtures/re-exports-ns-member/member-ab.ts @@ -1,2 +1,3 @@ export const memberA = 1; export const memberB = 1; +export const unusedMemberA = 1; diff --git a/packages/knip/fixtures/re-exports-ns-member/member-cd.ts b/packages/knip/fixtures/re-exports-ns-member/member-cd.ts index 9b786137a..5991874b1 100644 --- a/packages/knip/fixtures/re-exports-ns-member/member-cd.ts +++ b/packages/knip/fixtures/re-exports-ns-member/member-cd.ts @@ -1,2 +1,3 @@ export const memberC = 1; export const memberD = 1; +export const unusedMemberC = 1; diff --git a/packages/knip/fixtures/re-exports-renamed/fileA.ts b/packages/knip/fixtures/re-exports-renamed/fileA.ts index 146723242..1d0e80bfe 100644 --- a/packages/knip/fixtures/re-exports-renamed/fileA.ts +++ b/packages/knip/fixtures/re-exports-renamed/fileA.ts @@ -1 +1 @@ -export const afoo = 'foo'; +export const A = 'A'; diff --git a/packages/knip/fixtures/re-exports-renamed/fileB.ts b/packages/knip/fixtures/re-exports-renamed/fileB.ts index ac1783169..297ca07b8 100644 --- a/packages/knip/fixtures/re-exports-renamed/fileB.ts +++ b/packages/knip/fixtures/re-exports-renamed/fileB.ts @@ -1 +1 @@ -export * as A from './fileA.js'; +export * as B from './fileA.js'; diff --git a/packages/knip/fixtures/re-exports-renamed/index.ts b/packages/knip/fixtures/re-exports-renamed/index.ts index 0730b846f..56b55d76b 100644 --- a/packages/knip/fixtures/re-exports-renamed/index.ts +++ b/packages/knip/fixtures/re-exports-renamed/index.ts @@ -1,2 +1,3 @@ -import { A as x } from './fileB.js'; -console.log(x.afoo); +import { B as C } from './fileB.js'; + +C.A; diff --git a/packages/knip/fixtures/subpath-import/entry.ts b/packages/knip/fixtures/subpath-import/entry.ts index e9ff6cfb8..80fb1b088 100644 --- a/packages/knip/fixtures/subpath-import/entry.ts +++ b/packages/knip/fixtures/subpath-import/entry.ts @@ -1,3 +1,2 @@ import dep from '#dep'; - dep; diff --git a/packages/knip/fixtures/subpath-patterns/src/entry.ts b/packages/knip/fixtures/subpath-patterns/src/entry.ts index 06b20aead..59e56df9c 100644 --- a/packages/knip/fixtures/subpath-patterns/src/entry.ts +++ b/packages/knip/fixtures/subpath-patterns/src/entry.ts @@ -1,3 +1,2 @@ import used from '#internals/used'; - used; diff --git a/packages/knip/fixtures/workspaces-paths/packages/lib-a/src/index.ts b/packages/knip/fixtures/workspaces-paths/packages/lib-a/src/index.ts index ce0061a08..a407476b8 100644 --- a/packages/knip/fixtures/workspaces-paths/packages/lib-a/src/index.ts +++ b/packages/knip/fixtures/workspaces-paths/packages/lib-a/src/index.ts @@ -1,4 +1,5 @@ import same from 'src/same'; import one from '@/dir/module-a'; +same; export default one; diff --git a/packages/knip/fixtures/workspaces-paths/packages/lib-e/src/index.ts b/packages/knip/fixtures/workspaces-paths/packages/lib-e/src/index.ts index 29f3d2abe..b01e54efd 100644 --- a/packages/knip/fixtures/workspaces-paths/packages/lib-e/src/index.ts +++ b/packages/knip/fixtures/workspaces-paths/packages/lib-e/src/index.ts @@ -1,4 +1,5 @@ import unresolved from 'not/found'; import five from '@/dir/module-e'; +unresolved; export default five; diff --git a/packages/knip/src/index.ts b/packages/knip/src/index.ts index 89e3f9d3c..31bd0d3a6 100644 --- a/packages/knip/src/index.ts +++ b/packages/knip/src/index.ts @@ -250,7 +250,8 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => { const handleReferencedDependency = getHandler(collector, deputy, chief); const updateImports = (importedModule: SerializableImports, importItems: SerializableImports) => { - for (const id of importItems.identifiers) importedModule.identifiers.add(id); + for (const id of importItems.refs) importedModule.refs.add(id); + for (const id of importItems.importedAs) importedModule.importedAs.add(id); for (const id of importItems.importedNs) importedModule.importedNs.add(id); for (const id of importItems.isReExportedBy) importedModule.isReExportedBy.add(id); for (const id of importItems.isReExportedNs) importedModule.isReExportedNs.add(id); diff --git a/packages/knip/src/types/imports.ts b/packages/knip/src/types/imports.ts index de2898e51..8ba7dc784 100644 --- a/packages/knip/src/types/imports.ts +++ b/packages/knip/src/types/imports.ts @@ -3,6 +3,8 @@ import type ts from 'typescript'; export interface ImportNode { specifier: string; identifier: string | undefined; + alias?: string | undefined; + namespace?: string | undefined; pos: number | undefined; symbol?: ts.Symbol; isTypeOnly?: boolean; diff --git a/packages/knip/src/types/serializable-map.ts b/packages/knip/src/types/serializable-map.ts index baa7eed73..3d2114e8e 100644 --- a/packages/knip/src/types/serializable-map.ts +++ b/packages/knip/src/types/serializable-map.ts @@ -4,14 +4,15 @@ import type { IssueSymbol, SymbolType } from './issues.js'; type FilePath = string; type Specifier = string; -type Identifier = string; -type Identifiers = Set; +type Reference = string; +type References = Set; type Tags = Set; export type SerializableImports = { specifier: Specifier; - identifiers: Identifiers; + refs: References; hasStar: boolean; + importedAs: Set<[string, string]>; importedNs: Set; isReExport: boolean; isReExportedBy: Set; @@ -24,7 +25,7 @@ export type SerializableImportMap = Record; export type UnresolvedImport = { specifier: string; pos?: number; line?: number; col?: number }; export interface SerializableExport { - identifier: Identifier; + identifier: Reference; pos: number; line: number; col: number; @@ -37,7 +38,7 @@ export interface SerializableExport { } export type SerializableExportMember = { - identifier: Identifier; + identifier: Reference; pos: number; line: number; col: number; @@ -48,7 +49,7 @@ export type SerializableExportMember = { jsDocTags: Tags; }; -export type SerializableExports = Record; +export type SerializableExports = Record; export type SerializableFile = { internalImportCache?: SerializableImportMap; diff --git a/packages/knip/src/typescript/ast-helpers.ts b/packages/knip/src/typescript/ast-helpers.ts index 1518ff057..0f942e265 100644 --- a/packages/knip/src/typescript/ast-helpers.ts +++ b/packages/knip/src/typescript/ast-helpers.ts @@ -168,9 +168,12 @@ export const isDestructuring = (node: ts.Node) => export const getDestructuredIds = (name: ts.ObjectBindingPattern) => name.elements.map(element => element.name.getText()); -export const isConsiderReferencedNS = (node: ts.Identifier) => +export const isConsiderReferenced = (node: ts.Identifier) => ts.isShorthandPropertyAssignment(node.parent) || (ts.isCallExpression(node.parent) && node.parent.arguments.includes(node)) || ts.isSpreadAssignment(node.parent) || ts.isExportAssignment(node.parent) || (ts.isVariableDeclaration(node.parent) && node.parent.initializer === node); + +export const isTopLevel = (node: ts.Node) => + ts.isSourceFile(node.parent) || (node.parent && ts.isSourceFile(node.parent.parent)); diff --git a/packages/knip/src/typescript/getImportsAndExports.ts b/packages/knip/src/typescript/getImportsAndExports.ts index 020c50f0f..9fc1ba348 100644 --- a/packages/knip/src/typescript/getImportsAndExports.ts +++ b/packages/knip/src/typescript/getImportsAndExports.ts @@ -1,5 +1,6 @@ import { isBuiltin } from 'node:module'; import ts from 'typescript'; +import { DEFAULT_EXTENSIONS } from '../constants.js'; import type { Tags } from '../types/cli.js'; import type { ExportNode, ExportNodeMember } from '../types/exports.js'; import type { ImportNode } from '../types/imports.js'; @@ -13,7 +14,7 @@ import type { } from '../types/serializable-map.js'; import { timerify } from '../util/Performance.js'; import { isStartsLikePackageName, sanitizeSpecifier } from '../util/modules.js'; -import { isInNodeModules } from '../util/path.js'; +import { extname, isInNodeModules } from '../util/path.js'; import { shouldIgnore } from '../util/tag.js'; import type { BoundSourceFile, GetResolvedModule } from './SourceFile.js'; import { @@ -22,7 +23,7 @@ import { getJSDocTags, getLineAndCharacterOfPosition, isAccessExpression, - isConsiderReferencedNS, + isConsiderReferenced, isDestructuring, } from './ast-helpers.js'; import getDynamicImportVisitors from './visitors/dynamic-imports/index.js'; @@ -102,25 +103,38 @@ const getImportsAndExports = ( isReExportedBy: new Set(), isReExportedAs: new Set(), isReExportedNs: new Set(), + importedAs: new Set(), importedNs: new Set(), - identifiers: new Set(), + refs: new Set(), }); + if (isStar) internalImport.hasStar = true; + if (isReExport) { internalImport.isReExport = true; - if (namespace && isStar) internalImport.isReExportedAs.add([sourceFile.fileName, namespace]); - else if (namespace) internalImport.isReExportedNs.add([sourceFile.fileName, namespace]); - else internalImport.isReExportedBy.add(sourceFile.fileName); + if (namespace && isStar) { + internalImport.isReExportedNs.add([sourceFile.fileName, namespace]); + } else if (namespace) { + internalImport.isReExportedAs.add([sourceFile.fileName, namespace]); + } else { + internalImport.isReExportedBy.add(sourceFile.fileName); + } } - if (isStar) { - internalImport.hasStar = true; - if (symbol) internalImport.importedNs.add(String(symbol.escapedName)); - } else { - internalImport.identifiers.add(namespace ?? identifier); + const alias = symbol ? String(symbol.escapedName) : options.alias; + if (alias && alias !== identifier) { + if (isStar) { + internalImport.importedNs.add(alias); + } else { + internalImport.importedAs.add([identifier, alias]); + } } - if (symbol) importedInternalSymbols.set(symbol, filePath); + if (symbol && DEFAULT_EXTENSIONS.includes(extname(sourceFile.fileName))) { + importedInternalSymbols.set(symbol, filePath); + } else { + internalImport.refs.add(namespace ?? identifier); + } }; const addImport = (options: ImportNode, node: ts.Node) => { @@ -171,8 +185,8 @@ const getImportsAndExports = ( const importedSymbolFilePath = importedInternalSymbols.get(symbol); if (importedSymbolFilePath) { const internalImport = internalImports[importedSymbolFilePath]; - if (typeof member === 'string') internalImport.identifiers.add(`${namespace}.${member}`); - else for (const m of member) internalImport.identifiers.add(`${namespace}.${m}`); + if (typeof member === 'string') internalImport.refs.add(`${namespace}.${member}`); + else for (const m of member) internalImport.refs.add(`${namespace}.${m}`); } } }; @@ -195,7 +209,11 @@ const getImportsAndExports = ( if (importedSymbolFilePath) { const internalImport = internalImports[importedSymbolFilePath]; internalImport.isReExport = true; - internalImport.isReExportedAs.add([sourceFile.fileName, node.name.getText()]); + if (symbol.declarations && ts.isNamespaceImport(symbol.declarations[0])) { + internalImport.isReExportedNs.add([sourceFile.fileName, node.name.getText()]); + } else { + internalImport.isReExportedAs.add([sourceFile.fileName, node.name.getText()]); + } } } } else if (symbol) { @@ -296,34 +314,43 @@ const getImportsAndExports = ( if (ts.isIdentifier(node)) { const symbol = sourceFile.locals?.get(String(node.escapedText)); if (symbol) { - // TODO Ideally we store imported symbols and check directly against those, but can't get symbols to match const importedSymbolFilePath = importedInternalSymbols.get(symbol); if (importedSymbolFilePath) { - if (isAccessExpression(node.parent)) { - if (isDestructuring(node.parent)) { - if (ts.isPropertyAccessExpression(node.parent)) { - // Pattern: const { a, b } = NS.sub; - const ns = String(symbol.escapedName); - const key = String(node.parent.name.escapedText); - // @ts-expect-error safe after isDestructuring - const members = getDestructuredIds(node.parent.parent.name).map(n => `${key}.${n}`); - maybeAddAccessExpressionAsNsImport(ns, key); - maybeAddAccessExpressionAsNsImport(ns, members); + if ( + !ts.isImportSpecifier(node.parent) && + !ts.isImportEqualsDeclaration(node.parent) && + !ts.isImportClause(node.parent) && + !ts.isNamespaceImport(node.parent) + ) { + if ( + !internalImports[importedSymbolFilePath].importedNs.has(String(node.escapedText)) || + isConsiderReferenced(node) + ) { + internalImports[importedSymbolFilePath].refs.add(String(node.escapedText)); + } + + if (isAccessExpression(node.parent)) { + if (isDestructuring(node.parent)) { + if (ts.isPropertyAccessExpression(node.parent)) { + // Pattern: const { a, b } = NS.sub; + const ns = String(symbol.escapedName); + const key = String(node.parent.name.escapedText); + // @ts-expect-error safe after isDestructuring + const members = getDestructuredIds(node.parent.parent.name).map(n => `${key}.${n}`); + maybeAddAccessExpressionAsNsImport(ns, key); + maybeAddAccessExpressionAsNsImport(ns, members); + } + } else { + // Patterns: NS.id, NS['id'], NS.sub.id, NS[type], etc. + const members = getAccessMembers(typeChecker, node); + maybeAddAccessExpressionAsNsImport(String(node.escapedText), members); } - } else { - // Patterns: NS.id, NS['id'], NS.sub.id, NS[type], etc. - const members = getAccessMembers(typeChecker, node); + } else if (isDestructuring(node)) { + // Pattern: const { a, b } = NS; + // @ts-expect-error safe after isDestructuring + const members = getDestructuredIds(node.parent.name); maybeAddAccessExpressionAsNsImport(String(node.escapedText), members); } - } else if (isDestructuring(node)) { - // Pattern: const { a, b } = NS; - // @ts-expect-error safe after isDestructuring - const members = getDestructuredIds(node.parent.name); - maybeAddAccessExpressionAsNsImport(String(node.escapedText), members); - } else if (isConsiderReferencedNS(node)) { - // Patterns: const a = { NS }; fn(NS); const a = { ...NS }; export = NS; ; const ns = NS; - // Heuristic indicating imported symbol itself is consumed, which results in its members not being reported - internalImports[importedSymbolFilePath].identifiers.add(String(node.escapedText)); } } } diff --git a/packages/knip/src/typescript/visitors/dynamic-imports/importCall.ts b/packages/knip/src/typescript/visitors/dynamic-imports/importCall.ts index b55ec84ef..ea0fb8aae 100644 --- a/packages/knip/src/typescript/visitors/dynamic-imports/importCall.ts +++ b/packages/knip/src/typescript/visitors/dynamic-imports/importCall.ts @@ -1,5 +1,12 @@ import ts from 'typescript'; -import { findAncestor, findDescendants, isAccessExpression, isImportCall, stripQuotes } from '../../ast-helpers.js'; +import { + findAncestor, + findDescendants, + isAccessExpression, + isImportCall, + isTopLevel, + stripQuotes, +} from '../../ast-helpers.js'; import { importVisitor as visit } from '../index.js'; export default visit( @@ -41,6 +48,19 @@ export default visit( return { identifier: 'default', specifier, pos }; } + const variableDeclaration = findAncestor(accessExpression, _node => { + if (ts.isCallExpression(_node) || ts.isSourceFile(_node)) return 'STOP'; + return ts.isVariableDeclaration(_node); + }); + + if (variableDeclaration) { + const isTLA = isTopLevel(variableDeclaration.parent); + // @ts-expect-error TODO FIXME Property 'name' does not exist on type 'ElementAccessExpression'. + const alias = String(variableDeclaration.name.escapedText); + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'PropertyAccessExpression'. + return { identifier, alias, symbol: isTLA ? variableDeclaration.symbol : undefined, specifier, pos }; + } + // Pattern: import('side-effects') return { identifier, specifier, pos }; } @@ -61,20 +81,30 @@ export default visit( ts.isVariableDeclaration(variableDeclaration) && ts.isVariableDeclarationList(variableDeclaration.parent) ) { + const isTLA = isTopLevel(variableDeclaration.parent); if (ts.isIdentifier(variableDeclaration.name)) { // Pattern: const identifier = await import('specifier'); - return { identifier: 'default', specifier, pos: node.arguments[0].pos }; + return { + identifier: 'default', + alias: String(variableDeclaration.name.escapedText), + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'VariableDeclaration'. + symbol: isTLA ? variableDeclaration.symbol : undefined, + specifier, + pos: node.arguments[0].pos, + }; } const bindings = findDescendants(variableDeclaration, ts.isBindingElement); if (bindings.length > 0) { // Pattern: const { identifier } = await import('specifier'); return bindings.map(element => { const identifier = (element.propertyName ?? element.name).getText(); - return { identifier, specifier, pos: element.pos }; + const alias = element.propertyName ? element.name.getText() : undefined; + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'BindingElement'. + return { identifier, alias, symbol: isTLA ? element.symbol : undefined, specifier, pos: element.pos }; }); } // Pattern: import('specifier') - return { identifier: 'default', specifier, pos: node.arguments[0].pos }; + return { identifier: '__anonymous', specifier, pos: node.arguments[0].pos }; } const arrayLiteralExpression = node.parent; const variableDeclarationParent = node.parent.parent?.parent?.parent; @@ -87,16 +117,28 @@ export default visit( ) { const index = arrayLiteralExpression.elements.indexOf(node); // ts.indexOfNode is internal const element = variableDeclarationParent.name.elements[index]; + const isTLA = isTopLevel(variableDeclarationParent.parent); if (ts.isBindingElement(element) && ts.isObjectBindingPattern(element.name) && element.name.elements) { // Pattern: const [{ a }, { default: b, c }] = await Promise.all([import('A'), import('B')]); return element.name.elements.map(element => { const identifier = (element.propertyName ?? element.name).getText(); - return { identifier, specifier, pos: element.pos }; + const alias = element.propertyName ? element.name.getText() : undefined; + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'BindingElement'. + return { identifier, alias, symbol: isTLA ? element.symbol : undefined, specifier, pos: element.pos }; }); } // Pattern: const [a, b] = await Promise.all([import('A'), import('B')]); - return { identifier: 'default', specifier, pos: element.pos }; + // @ts-expect-error TODO FIXME Property 'name' does not exist on type 'OmittedExpression'. + const alias = element.name.escapedText; + return { + identifier: 'default', + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'BindingElement'. + symbol: isTLA ? element.symbol : undefined, + alias, + specifier, + pos: element.pos, + }; } // Pattern: import('side-effects') diff --git a/packages/knip/src/typescript/visitors/dynamic-imports/requireCall.ts b/packages/knip/src/typescript/visitors/dynamic-imports/requireCall.ts index a0510f47a..f644e6bbc 100644 --- a/packages/knip/src/typescript/visitors/dynamic-imports/requireCall.ts +++ b/packages/knip/src/typescript/visitors/dynamic-imports/requireCall.ts @@ -1,5 +1,5 @@ import ts from 'typescript'; -import { findAncestor, findDescendants, isRequireCall } from '../../ast-helpers.js'; +import { findAncestor, findDescendants, isRequireCall, isTopLevel } from '../../ast-helpers.js'; import { importVisitor as visit } from '../index.js'; export default visit( @@ -25,16 +25,27 @@ export default visit( ts.isVariableDeclaration(variableDeclaration) && ts.isVariableDeclarationList(variableDeclaration.parent) ) { + const isTLA = isTopLevel(variableDeclaration.parent); if (ts.isIdentifier(variableDeclaration.name)) { // Pattern: identifier = require('specifier') - return { identifier: 'default', specifier, pos: node.arguments[0].pos }; + const alias = String(variableDeclaration.name.escapedText); + return { + identifier: 'default', + alias, + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'VariableDeclaration'. + symbol: isTLA ? variableDeclaration.symbol : undefined, + specifier, + pos: node.arguments[0].pos, + }; } const bindings = findDescendants(variableDeclaration, ts.isBindingElement); if (bindings.length > 0) { // Pattern: { identifier } = require('specifier') return bindings.map(element => { const identifier = (element.propertyName ?? element.name).getText(); - return { identifier, specifier, pos: element.pos }; + const alias = element.propertyName ? element.name.getText() : undefined; + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'BindingElement'. + return { identifier, specifier, alias, symbol: isTLA ? element.symbol : undefined, pos: element.pos }; }); } // Pattern: require('specifier') diff --git a/packages/knip/src/typescript/visitors/imports/importDeclaration.ts b/packages/knip/src/typescript/visitors/imports/importDeclaration.ts index 8a41c27d0..9758c3c89 100644 --- a/packages/knip/src/typescript/visitors/imports/importDeclaration.ts +++ b/packages/knip/src/typescript/visitors/imports/importDeclaration.ts @@ -15,7 +15,14 @@ export default visit( if (isDefaultImport(node)) { // Pattern: import identifier from 'specifier' - imports.push({ specifier, identifier: 'default', pos: node.moduleSpecifier.pos }); + imports.push({ + identifier: 'default', + alias: String(node.importClause.name?.escapedText), + specifier, + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'ImportClause'. + symbol: node.importClause.symbol, + pos: node.moduleSpecifier.pos, + }); } if (node.importClause?.namedBindings) { @@ -30,11 +37,11 @@ export default visit( for (const element of node.importClause.namedBindings.elements) { const identifier = (element.propertyName ?? element.name).getText(); imports.push({ + identifier, + specifier, // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'ImportSpecifier'. symbol: element.symbol, isTypeOnly: node.importClause?.isTypeOnly, - specifier, - identifier, pos: element.pos, }); } diff --git a/packages/knip/src/typescript/visitors/imports/importEqualsDeclaration.ts b/packages/knip/src/typescript/visitors/imports/importEqualsDeclaration.ts index 9ca047e5a..74344c2ac 100644 --- a/packages/knip/src/typescript/visitors/imports/importEqualsDeclaration.ts +++ b/packages/knip/src/typescript/visitors/imports/importEqualsDeclaration.ts @@ -10,6 +10,8 @@ export default visit(isNotJS, node => { ) { // Pattern: import identifier = require('specifier') const specifier = node.moduleReference.expression.text; - return { specifier, identifier: 'default', pos: node.moduleReference.expression.pos }; + const alias = String(node.name.escapedText); + // @ts-expect-error TODO FIXME Property 'symbol' does not exist on type 'ImportEqualsDeclaration'. + return { specifier, alias, identifier: 'default', symbol: node.symbol, pos: node.moduleReference.expression.pos }; } }); diff --git a/packages/knip/src/typescript/visitors/imports/reExportDeclaration.ts b/packages/knip/src/typescript/visitors/imports/reExportDeclaration.ts index 76af5c052..6e298f631 100644 --- a/packages/knip/src/typescript/visitors/imports/reExportDeclaration.ts +++ b/packages/knip/src/typescript/visitors/imports/reExportDeclaration.ts @@ -15,7 +15,7 @@ export default visit( // Pattern: export * as namespace from 'specifier'; return { identifier: '*', - namespace: node.exportClause.name.escapedText, + namespace: String(node.exportClause.name.escapedText), specifier: node.moduleSpecifier.text, isReExport: true, pos: node.exportClause.name.pos, @@ -26,8 +26,8 @@ export default visit( if (element.propertyName && element.name) { // Pattern: export { identifier as otherIdentifier } from 'specifier'; return { - identifier: String(element.name.escapedText), - namespace: element.propertyName.escapedText, + identifier: String(element.propertyName.escapedText), + alias: String(element.name.escapedText), specifier: specifier.text, isReExport: true, pos: element.pos, diff --git a/packages/knip/src/util/get-reexporting-entry-file.ts b/packages/knip/src/util/get-reexporting-entry-file.ts index 2bed6994e..7985980e6 100644 --- a/packages/knip/src/util/get-reexporting-entry-file.ts +++ b/packages/knip/src/util/get-reexporting-entry-file.ts @@ -8,7 +8,7 @@ export const getReExportingEntryFileHandler = (entryPaths: Set, serializ depth = 0, filePath?: string ): string | undefined => { - if (depth === 0 && filePath) exportLookupLog(-1, `Looking up re-exporting file for ${id} from`, filePath); + if (depth === 0 && filePath) exportLookupLog(-1, `Looking up re-exporting file for "${id}" from`, filePath); if (!importedModule) return undefined; diff --git a/packages/knip/src/util/is-identifier-referenced.ts b/packages/knip/src/util/is-identifier-referenced.ts index efbfe8d25..a5e514327 100644 --- a/packages/knip/src/util/is-identifier-referenced.ts +++ b/packages/knip/src/util/is-identifier-referenced.ts @@ -21,14 +21,25 @@ export const getIsIdentifierReferencedHandler = (importedSymbols: SerializableMa return false; } - if (importsForExport.identifiers.has(id)) { - exportLookupLog(depth, 'imported from', filePath); + if (importsForExport.refs.has(id)) { + exportLookupLog(depth, `referenced ${id} from`, filePath); return true; } + for (const [id, alias] of importsForExport.importedAs) { + const [name, ...rest] = ids; + if (name === id) { + const id = [alias, ...rest].join('.'); + if (isIdentifierReferenced(filePath, id, importsForExport, depth)) { + exportLookupLog(depth, `imported ${name} as ${alias} by`, filePath); + return true; + } + } + } + for (const ns of importsForExport.importedNs) { - if (importsForExport.identifiers.has(`${ns}.${id}`)) { - exportLookupLog(depth, `imported on ${ns} from`, filePath); + if (importsForExport.refs.has(`${ns}.${id}`)) { + exportLookupLog(depth, `imported ${id} on ${ns} from`, filePath); return true; } } @@ -37,7 +48,7 @@ export const getIsIdentifierReferencedHandler = (importedSymbols: SerializableMa for (const filePath of importsForExport.isReExportedBy) { const file = importedSymbols[filePath]; if (file && isIdentifierReferenced(filePath, id, file.imported, depth + 1)) { - exportLookupLog(depth, 're-exported by', filePath); + exportLookupLog(depth, `re-exported ${id} by`, filePath); return true; } } diff --git a/packages/knip/src/util/type.ts b/packages/knip/src/util/type.ts index 4682a5fed..b4b9b5c28 100644 --- a/packages/knip/src/util/type.ts +++ b/packages/knip/src/util/type.ts @@ -4,9 +4,9 @@ export const getHasStrictlyNsReferences = (importsForExport?: SerializableImport if (!importsForExport?.hasStar || importsForExport.importedNs.size === 0) return [false]; let namespace: string | undefined; for (const ns of importsForExport.importedNs) { - const hasNs = importsForExport.identifiers.has(ns); + const hasNs = importsForExport.refs.has(ns); if (!hasNs) return [false, ns]; - for (const id of importsForExport.identifiers) if (id.startsWith(`${ns}.`)) return [false, ns]; + for (const id of importsForExport.refs) if (id.startsWith(`${ns}.`)) return [false, ns]; namespace = ns; } return [true, namespace]; diff --git a/packages/knip/test/cli-reporter-json.test.ts b/packages/knip/test/cli-reporter-json.test.ts index 58cb69efe..169d65607 100644 --- a/packages/knip/test/cli-reporter-json.test.ts +++ b/packages/knip/test/cli-reporter-json.test.ts @@ -21,16 +21,16 @@ test('knip --reporter json (exports & types)', () => { binaries: [], unresolved: [], exports: [ - { name: 'unusedNumber', line: 14, col: 14, pos: 562 }, - { name: 'unusedFunction', line: 15, col: 14, pos: 593 }, - { name: 'default', line: 21, col: 8, pos: 727 }, + { name: 'unusedNumber', line: 23, col: 14, pos: 682 }, + { name: 'unusedFunction', line: 24, col: 14, pos: 713 }, + { name: 'default', line: 30, col: 8, pos: 847 }, ], - types: [{ name: 'MyAnyType', line: 19, col: 13, pos: 702 }], + types: [{ name: 'MyAnyType', line: 28, col: 13, pos: 822 }], enumMembers: {}, duplicates: [ [ - { name: 'exportedResult', line: 17, col: 13, pos: 649 }, - { name: 'default', line: 21, col: 15, pos: 734 }, + { name: 'exportedResult', line: 26, col: 13, pos: 769 }, + { name: 'default', line: 30, col: 15, pos: 854 }, ], ], }, diff --git a/packages/knip/test/exports.test.ts b/packages/knip/test/exports.test.ts index d20b1a21c..49793146b 100644 --- a/packages/knip/test/exports.test.ts +++ b/packages/knip/test/exports.test.ts @@ -40,7 +40,7 @@ test('Find unused exports', async () => { assert.equal(issues.exports['default.ts']['NamedExport'].line, 1); assert.equal(issues.exports['default.ts']['NamedExport'].col, 14); - assert.equal(issues.types['my-module.ts']['MyAnyType'].line, 19); + assert.equal(issues.types['my-module.ts']['MyAnyType'].line, 28); assert.equal(issues.types['my-module.ts']['MyAnyType'].col, 13); assert.equal(issues.types['my-namespace.ts']['MyNamespace'].line, 5); assert.equal(issues.types['my-namespace.ts']['MyNamespace'].col, 18); diff --git a/packages/knip/test/imports-namespace-with-nsexports.test.ts b/packages/knip/test/imports-namespace-with-nsexports.test.ts index e2aae1563..eacd9a3f3 100644 --- a/packages/knip/test/imports-namespace-with-nsexports.test.ts +++ b/packages/knip/test/imports-namespace-with-nsexports.test.ts @@ -7,7 +7,7 @@ import baseCounters from './helpers/baseCounters.js'; const cwd = resolve('fixtures/imports-namespace-with-nsexports'); -test('Ignore namespace re-export by entry file', async () => { +test('Ignore namespace re-export by entry file (nsExports', async () => { const { counters } = await main({ ...baseArguments, cwd, diff --git a/packages/knip/test/re-exports-aliased-ns.test.ts b/packages/knip/test/re-exports-aliased-ns.test.ts new file mode 100644 index 000000000..3791ae78d --- /dev/null +++ b/packages/knip/test/re-exports-aliased-ns.test.ts @@ -0,0 +1,24 @@ +import { test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { main } from '../src/index.js'; +import { resolve } from '../src/util/path.js'; +import baseArguments from './helpers/baseArguments.js'; +import baseCounters from './helpers/baseCounters.js'; + +const cwd = resolve('fixtures/re-exports-aliased-ns'); + +test('Find exports through re-exported aliased namespace', async () => { + const { issues, counters } = await main({ + ...baseArguments, + cwd, + }); + + assert(issues.exports['2-second.ts']['second']); + + assert.deepEqual(counters, { + ...baseCounters, + exports: 1, + processed: 5, + total: 5, + }); +}); diff --git a/packages/knip/test/re-exports-ns-member.test.ts b/packages/knip/test/re-exports-ns-member.test.ts index 4e6fca1a8..b133441c3 100644 --- a/packages/knip/test/re-exports-ns-member.test.ts +++ b/packages/knip/test/re-exports-ns-member.test.ts @@ -8,13 +8,17 @@ import baseCounters from './helpers/baseCounters.js'; const cwd = resolve('fixtures/re-exports-ns-member'); test('Find destructured props of member-accessed imported symbol', async () => { - const { counters } = await main({ + const { issues, counters } = await main({ ...baseArguments, cwd, }); + assert(issues.exports['member-ab.ts']['unusedMemberA']); + assert(issues.exports['member-cd.ts']['unusedMemberC']); + assert.deepEqual(counters, { ...baseCounters, + exports: 2, processed: 6, total: 6, }); diff --git a/packages/knip/test/util/has-strictly-ns-references.test.ts b/packages/knip/test/util/has-strictly-ns-references.test.ts index 72062da89..ecfa32790 100644 --- a/packages/knip/test/util/has-strictly-ns-references.test.ts +++ b/packages/knip/test/util/has-strictly-ns-references.test.ts @@ -11,7 +11,7 @@ const base: SerializableImports = { isReExportedNs: new Set(), hasStar: false, importedNs: new Set(), - identifiers: new Set(), + refs: new Set(), }; test('Strictly namespace refs (no namespaces)', () => { @@ -24,7 +24,7 @@ test('Strictly namespace refs (single ns)', () => { ...base, hasStar: true, importedNs: new Set(['ns']), - identifiers: new Set(['ns']), + refs: new Set(['ns']), }), [true, 'ns'] ); @@ -36,7 +36,7 @@ test('Strictly namespace refs (no id)', () => { ...base, hasStar: true, importedNs: new Set(['ns']), - identifiers: new Set([]), + refs: new Set([]), }), [false, 'ns'] ); @@ -48,7 +48,7 @@ test('Strictly namespace refs (single ns, no id)', () => { ...base, hasStar: true, importedNs: new Set([]), - identifiers: new Set(['ns']), + refs: new Set(['ns']), }), [false] ); @@ -60,7 +60,7 @@ test('Strictly namespace refs (multiple ns, no id)', () => { ...base, hasStar: true, importedNs: new Set(['ns', 'ns2']), - identifiers: new Set(['ns']), + refs: new Set(['ns']), }), [false, 'ns2'] ); @@ -72,7 +72,7 @@ test('Strictly namespace refs (member access)', () => { ...base, hasStar: true, importedNs: new Set(['ns']), - identifiers: new Set(['ns', 'ns.prop']), + refs: new Set(['ns', 'ns.prop']), }), [false, 'ns'] ); @@ -84,7 +84,7 @@ test('Strictly namespace refs (no star)', () => { ...base, hasStar: false, importedNs: new Set(['ns']), - identifiers: new Set(['ns']), + refs: new Set(['ns']), }), [false] );