Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/knip/fixtures/import-star-iteration/fruit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class Orange {
public message = "I am an orange";
}

export class Apple {
public message = "I am an apple";
}
18 changes: 18 additions & 0 deletions packages/knip/fixtures/import-star-iteration/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as fruitClasses from "./fruit";
import * as veggieClasses from "./vegetables";

// Outputs:
// I am an orange
// I am an apple
// I am broccoli
// I am spinach

for (const className in fruitClasses) {
const classInstance = new fruitClasses[className]();
console.log(`${classInstance.message}`);
}

for (const myClass of veggieClasses) {
const classInstance = new myClass();
console.log(classInstance.message);
}
17 changes: 17 additions & 0 deletions packages/knip/fixtures/import-star-iteration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@fixtures/import-star-iteration",
"devDependencies": {
"ts-node": "^10.8.2"
},
"scripts": {
"execute-test-code": "ts-node index.ts"
},
"knip": {
"ignoreBinaries": [
"ts-node"
],
"ignoreDependencies": [
"ts-node"
]
}
}
3 changes: 3 additions & 0 deletions packages/knip/fixtures/import-star-iteration/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"compilerOptions": {}
}
11 changes: 11 additions & 0 deletions packages/knip/fixtures/import-star-iteration/vegetables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Broccoli {
public message = "I am broccoli";
}

class Spinach {
public message = "I am spinach";
}

// This is contrived, but this leads to us being able to use for (...of) in index.ts
const veggieClasses = [Broccoli, Spinach];
export = veggieClasses; // Makes the file a module
5 changes: 5 additions & 0 deletions packages/knip/src/typescript/ast-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ export const isDestructuring = (node: ts.Node) =>
ts.isVariableDeclarationList(node.parent.parent) &&
ts.isObjectBindingPattern(node.parent.name);

// Pattern: for (const x in NS) { }
// Pattern: for (const x of NS) { }
export const isIteratingObject = (node: ts.Node) =>
node.parent && (ts.isForInStatement(node.parent) || ts.isForOfStatement(node.parent));

export const getDestructuredIds = (name: ts.ObjectBindingPattern) =>
name.elements.map(element => element.name.getText());

Expand Down
5 changes: 5 additions & 0 deletions packages/knip/src/typescript/get-imports-and-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
isConsiderReferencedNS,
isDestructuring,
isImportSpecifier,
isIteratingObject,
isObjectEnumerationCallExpressionArgument,
isReferencedInExport,
} from './ast-helpers.js';
Expand Down Expand Up @@ -353,6 +354,10 @@ const getImportsAndExports = (
} else if (isObjectEnumerationCallExpressionArgument(node)) {
// Pattern: Object.keys(NS)
imports.refs.add(id);
} else if (isIteratingObject(node)) {
// Pattern: for (const x in NS) { }
// Pattern: for (const x of NS) { }
imports.refs.add(id);
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions packages/knip/test/import-star-iteration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { test } from 'bun:test';
import assert from 'node:assert/strict';
import { main as knip } 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/import-star-iteration');

test('Handle usage of members of a namespace when imported using * and iterating', async () => {
const { counters } = await knip({
...baseArguments,
cwd,
});

// Classes Orange and Apple are both used using a for (...in) loop
// Classes Broccoli and Spinach are both used using a for (...of) loop
assert.deepEqual(counters, {
...baseCounters,
processed: 3,
total: 3,
});
});
Loading