Skip to content

Commit 1a99048

Browse files
committed
Don't track typeof value refs at top of exported type alias (resolve #1697)
1 parent 2b1b2d4 commit 1a99048

5 files changed

Lines changed: 68 additions & 9 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { DatePreset, Day, Wrap } from './schema.ts';
2+
3+
export const a: DatePreset = 'today';
4+
5+
export const b: Day = ['mon', 'tue', 'wed'] as const;
6+
7+
export const c: Wrap = { size: { width: 2, height: 2 } };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "@fixtures/typeof-in-type-alias",
3+
"type": "module"
4+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const PRESETS = ['today', 'last7Days'] as const;
2+
3+
export const DAYS = ['mon', 'tue', 'wed'] as const;
4+
5+
export const SHAPE = { width: 1, height: 1 };
6+
7+
export type DatePreset = (typeof PRESETS)[number];
8+
9+
export type Day = typeof DAYS;
10+
11+
export type Wrap = { size: typeof SHAPE };

packages/knip/src/typescript/visitors/walk.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export interface WalkState extends WalkContext {
9090
) => void;
9191
getFix: (start: number, end: number, flags?: number) => Fix;
9292
getTypeFix: (start: number, end: number) => Fix;
93-
collectRefsInType: (node: any, exportName: string, signatureOnly: boolean) => void;
93+
collectRefsInType: (node: any, exportName: string, signatureOnly: boolean, inMember?: boolean) => void;
9494
addRefInExport: (name: string, exportName: string) => void;
9595
isInNamespace: (node: Span) => boolean;
9696
}
@@ -142,14 +142,24 @@ const _addExport = (
142142
}
143143
};
144144

145-
const _collectRefsInType = (node: any, exportName: string, signatureOnly: boolean): void => {
145+
const MEMBER_CONTAINERS = new Set([
146+
'TSPropertySignature',
147+
'TSMethodSignature',
148+
'TSIndexSignature',
149+
'TSCallSignatureDeclaration',
150+
'TSConstructSignatureDeclaration',
151+
]);
152+
153+
const _collectRefsInType = (node: any, exportName: string, signatureOnly: boolean, inMember = false): void => {
146154
if (!node || typeof node !== 'object') return;
147155
if (node.type === 'TSTypeQuery') {
148-
const name = node.exprName.type === 'Identifier' ? node.exprName.name : undefined;
149-
if (name) {
150-
const refs = state.referencedInExport.get(name);
151-
if (refs) refs.add(exportName);
152-
else state.referencedInExport.set(name, new Set([exportName]));
156+
if (inMember) {
157+
const name = node.exprName.type === 'Identifier' ? node.exprName.name : undefined;
158+
if (name) {
159+
const refs = state.referencedInExport.get(name);
160+
if (refs) refs.add(exportName);
161+
else state.referencedInExport.set(name, new Set([exportName]));
162+
}
153163
}
154164
return;
155165
}
@@ -160,15 +170,17 @@ const _collectRefsInType = (node: any, exportName: string, signatureOnly: boolea
160170
if (refs) refs.add(exportName);
161171
else state.referencedInExport.set(name, new Set([exportName]));
162172
}
173+
const nextInMember = inMember || MEMBER_CONTAINERS.has(node.type);
163174
for (const key in node) {
164175
if (key === 'type' || key === 'parent') continue;
165176
const val = node[key];
166177
if (Array.isArray(val)) {
167178
for (const item of val) {
168-
if (item && typeof item === 'object' && item.type) _collectRefsInType(item, exportName, signatureOnly);
179+
if (item && typeof item === 'object' && item.type)
180+
_collectRefsInType(item, exportName, signatureOnly, nextInMember);
169181
}
170182
} else if (val && typeof val === 'object' && val.type) {
171-
_collectRefsInType(val, exportName, signatureOnly);
183+
_collectRefsInType(val, exportName, signatureOnly, nextInMember);
172184
}
173185
}
174186
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import assert from 'node:assert/strict';
2+
import test from 'node:test';
3+
import { main } from '../src/index.ts';
4+
import baseCounters from './helpers/baseCounters.ts';
5+
import { createOptions } from './helpers/create-options.ts';
6+
import { resolve } from './helpers/resolve.ts';
7+
8+
const cwd = resolve('fixtures/typeof-in-type-alias');
9+
10+
test('Flag values referenced only via typeof at the top of an exported type alias', async () => {
11+
const options = await createOptions({ cwd });
12+
const { issues, counters } = await main(options);
13+
14+
assert(issues.exports['schema.ts']['PRESETS']);
15+
assert(issues.exports['schema.ts']['DAYS']);
16+
17+
assert(!issues.exports['schema.ts']?.['SHAPE']);
18+
19+
assert.deepEqual(counters, {
20+
...baseCounters,
21+
exports: 2,
22+
processed: 2,
23+
total: 2,
24+
});
25+
});

0 commit comments

Comments
 (0)