Skip to content

Commit beae106

Browse files
committed
Include members for default-exported enums/classes
1 parent 1ff6db9 commit beae106

File tree

5 files changed

+62
-33
lines changed

5 files changed

+62
-33
lines changed

packages/knip/fixtures/exports-default-interface/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import Default_MyInterface from './interface';
44

55
const i: Default_MyInterface = { id: 0 };
66
const e: Default_MyEnum = 0;
7+
const m: Default_MyEnum.id = 0;
78
const t: Default_MyType = {};
89

910
i;
1011
e;
12+
m;
1113
t;

packages/knip/fixtures/re-exports-with-decorator/entry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import { NS } from './barrel.js';
22
import { MyDecorated } from './barrel.js';
33

44
NS;
5+
NS.KEY;
56
MyDecorated;

packages/knip/src/typescript/ast-helpers.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import ts from 'typescript';
2+
import { FIX_FLAGS } from '../constants.js';
3+
import type { Fix } from '../types/exports.js';
24
import { SymbolType } from '../types/issues.js';
35

4-
export function isGetOrSetAccessorDeclaration(node: ts.Node): node is ts.AccessorDeclaration {
6+
function isGetOrSetAccessorDeclaration(node: ts.Node): node is ts.AccessorDeclaration {
57
return node.kind === ts.SyntaxKind.SetAccessor || node.kind === ts.SyntaxKind.GetAccessor;
68
}
79

8-
export function isPrivateMember(
10+
function isPrivateMember(
911
node: ts.MethodDeclaration | ts.PropertyDeclaration | ts.SetAccessorDeclaration | ts.GetAccessorDeclaration
1012
): boolean {
1113
return node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.PrivateKeyword) ?? false;
@@ -60,6 +62,31 @@ export const getNodeType = (node: ts.Node): SymbolType => {
6062
return SymbolType.UNKNOWN;
6163
};
6264

65+
export const isNonPrivatePropertyOrMethodDeclaration = (
66+
member: ts.ClassElement
67+
): member is ts.MethodDeclaration | ts.PropertyDeclaration =>
68+
(ts.isPropertyDeclaration(member) || ts.isMethodDeclaration(member) || isGetOrSetAccessorDeclaration(member)) &&
69+
!isPrivateMember(member);
70+
71+
export const getClassMember = (member: ts.MethodDeclaration | ts.PropertyDeclaration, isFixTypes: boolean) => ({
72+
node: member,
73+
identifier: member.name.getText(),
74+
// Naive, but [does.the.job()]
75+
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
76+
type: SymbolType.MEMBER,
77+
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
78+
});
79+
80+
export const getEnumMember = (member: ts.EnumMember, isFixTypes: boolean) => ({
81+
node: member,
82+
identifier: stripQuotes(member.name.getText()),
83+
pos: member.name.getStart(),
84+
type: SymbolType.MEMBER,
85+
fix: isFixTypes
86+
? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] as Fix)
87+
: undefined,
88+
});
89+
6390
export function stripQuotes(name: string) {
6491
const length = name.length;
6592
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) {
Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import ts from 'typescript';
22
import { FIX_FLAGS } from '../../../constants.js';
33
import type { Fix } from '../../../types/exports.js';
4-
import { getNodeType } from '../../ast-helpers.js';
4+
import {
5+
getClassMember,
6+
getEnumMember,
7+
getNodeType,
8+
isNonPrivatePropertyOrMethodDeclaration,
9+
} from '../../ast-helpers.js';
510
import { isModule } from '../helpers.js';
611
import { exportVisitor as visit } from '../index.js';
712

8-
export default visit(isModule, (node, { isFixExports }) => {
13+
export default visit(isModule, (node, { isFixExports, isReportClassMembers, isFixTypes }) => {
914
if (ts.isExportAssignment(node)) {
1015
// Patterns:
1116
// export default 1;
@@ -15,6 +20,24 @@ export default visit(isModule, (node, { isFixExports }) => {
1520
// @ts-expect-error We need the symbol in `addExport`
1621
const symbol = node.getSourceFile().locals?.get(node.expression.escapedText);
1722
const type = getNodeType(symbol?.valueDeclaration);
23+
24+
if (symbol?.valueDeclaration) {
25+
const decl = symbol.valueDeclaration;
26+
if (ts.isEnumDeclaration(decl)) {
27+
const members = decl.members.map(member => getEnumMember(member, isFixExports));
28+
return { node, symbol, identifier: 'default', type, pos, fix, members };
29+
}
30+
31+
if (ts.isClassDeclaration(decl)) {
32+
const members = isReportClassMembers
33+
? decl.members
34+
.filter(isNonPrivatePropertyOrMethodDeclaration)
35+
.map(member => getClassMember(member, isFixTypes))
36+
: [];
37+
return { node, symbol, identifier: 'default', type, pos, fix, members };
38+
}
39+
}
40+
1841
return { node, symbol, identifier: 'default', type, pos, fix };
1942
}
2043
});

packages/knip/src/typescript/visitors/exports/exportKeyword.ts

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import type { Fix } from '../../../types/exports.js';
44
import { SymbolType } from '../../../types/issues.js';
55
import { compact } from '../../../util/array.js';
66
import {
7+
getClassMember,
78
getDefaultKeywordNode,
9+
getEnumMember,
810
getExportKeywordNode,
9-
isGetOrSetAccessorDeclaration,
10-
isPrivateMember,
11-
stripQuotes,
11+
isNonPrivatePropertyOrMethodDeclaration,
1212
} from '../../ast-helpers.js';
1313
import { isModule } from '../helpers.js';
1414
import { exportVisitor as visit } from '../index.js';
@@ -87,22 +87,7 @@ export default visit(isModule, (node, { isFixExports, isFixTypes, isReportClassM
8787
const pos = (node.name ?? node).getStart();
8888
const fix = getFix(exportKeyword, defaultKeyword);
8989
const members = isReportClassMembers
90-
? node.members
91-
.filter(
92-
(member): member is ts.MethodDeclaration | ts.PropertyDeclaration =>
93-
(ts.isPropertyDeclaration(member) ||
94-
ts.isMethodDeclaration(member) ||
95-
isGetOrSetAccessorDeclaration(member)) &&
96-
!isPrivateMember(member)
97-
)
98-
.map(member => ({
99-
node: member,
100-
identifier: member.name.getText(),
101-
// Naive, but [does.the.job()]
102-
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
103-
type: SymbolType.MEMBER,
104-
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
105-
}))
90+
? node.members.filter(isNonPrivatePropertyOrMethodDeclaration).map(member => getClassMember(member, isFixTypes))
10691
: [];
10792

10893
return { node, identifier, type: SymbolType.CLASS, pos, members, fix };
@@ -126,16 +111,7 @@ export default visit(isModule, (node, { isFixExports, isFixTypes, isReportClassM
126111
const identifier = node.name.getText();
127112
const pos = node.name.getStart();
128113
const fix = getTypeFix(exportKeyword);
129-
const members = node.members.map(member => ({
130-
node: member,
131-
identifier: stripQuotes(member.name.getText()),
132-
pos: member.name.getStart(),
133-
type: SymbolType.MEMBER,
134-
fix: isFixTypes
135-
? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] as Fix)
136-
: undefined,
137-
}));
138-
114+
const members = node.members.map(member => getEnumMember(member, isFixExports));
139115
return { node, identifier, type: SymbolType.ENUM, pos, members, fix };
140116
}
141117
}

0 commit comments

Comments
 (0)