Skip to content

Commit bf0b59c

Browse files
committed
refactor: consolidate declaration type guards into a single module
1 parent 15fb67a commit bf0b59c

26 files changed

+347
-362
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import { dedent } from "ts-dedent";
2+
import { ModuleKind, ModuleResolutionKind, Project, ScriptTarget } from "ts-morph";
3+
import { expect, test } from "vitest";
4+
import {
5+
isClass,
6+
isEnum,
7+
isExpression,
8+
isFileModule,
9+
isFunction,
10+
isFunctionExpression,
11+
isInterface,
12+
isNamespace,
13+
isTypeAlias,
14+
isVariable,
15+
isVariableAssignmentExpression,
16+
} from "./declaration-type-guards.ts";
17+
18+
test("isVariable", () => {
19+
const project = new Project({
20+
useInMemoryFileSystem: true,
21+
compilerOptions: {
22+
lib: ["lib.esnext.full.d.ts"],
23+
target: ScriptTarget.ESNext,
24+
module: ModuleKind.ESNext,
25+
moduleResolution: ModuleResolutionKind.Bundler,
26+
},
27+
});
28+
const indexFile = project.createSourceFile(
29+
"index.ts",
30+
dedent`
31+
export function foo() {}
32+
33+
// Not a variable because it represents function.
34+
export const bar: () => void;
35+
36+
export const baz: string;
37+
`,
38+
);
39+
expect(isVariable(indexFile.getFunctionOrThrow("foo"))).toBe(false);
40+
expect(isVariable(indexFile.getVariableDeclarationOrThrow("bar"))).toBe(false);
41+
expect(isVariable(indexFile.getVariableDeclarationOrThrow("baz"))).toBe(true);
42+
});
43+
44+
test("isVariableAssignmentExpression", () => {
45+
const project = new Project({
46+
useInMemoryFileSystem: true,
47+
compilerOptions: {
48+
lib: ["lib.esnext.full.d.ts"],
49+
target: ScriptTarget.ESNext,
50+
module: ModuleKind.ESNext,
51+
moduleResolution: ModuleResolutionKind.Bundler,
52+
},
53+
});
54+
const indexFile = project.createSourceFile(
55+
"index.ts",
56+
dedent`
57+
export function foo() {}
58+
59+
let var1;
60+
export default var1 = "var1";
61+
`,
62+
);
63+
expect(isVariableAssignmentExpression(indexFile.getFunctionOrThrow("foo"))).toBe(false);
64+
expect(
65+
isVariableAssignmentExpression(indexFile.getExportedDeclarations().get("default")?.at(0)!),
66+
).toBe(true);
67+
});
68+
69+
test("isExpression", () => {
70+
const project = new Project({
71+
useInMemoryFileSystem: true,
72+
compilerOptions: {
73+
lib: ["lib.esnext.full.d.ts"],
74+
target: ScriptTarget.ESNext,
75+
module: ModuleKind.ESNext,
76+
moduleResolution: ModuleResolutionKind.Bundler,
77+
},
78+
});
79+
const indexFile = project.createSourceFile(
80+
"index.ts",
81+
dedent`
82+
export function foo() {}
83+
84+
export default 42;
85+
`,
86+
);
87+
expect(isExpression(indexFile.getFunctionOrThrow("foo"))).toBe(false);
88+
expect(isExpression(indexFile.getExportedDeclarations().get("default")?.at(0)!)).toBe(true);
89+
});
90+
91+
test("isFunction", () => {
92+
const project = new Project({
93+
useInMemoryFileSystem: true,
94+
compilerOptions: {
95+
lib: ["lib.esnext.full.d.ts"],
96+
target: ScriptTarget.ESNext,
97+
module: ModuleKind.ESNext,
98+
moduleResolution: ModuleResolutionKind.Bundler,
99+
},
100+
});
101+
const indexFile = project.createSourceFile(
102+
"index.ts",
103+
dedent`
104+
export const foo: string;
105+
106+
export function bar() {}
107+
`,
108+
);
109+
expect(isFunction(indexFile.getVariableDeclarationOrThrow("foo"))).toBe(false);
110+
expect(isFunction(indexFile.getFunctionOrThrow("bar"))).toBe(true);
111+
});
112+
113+
test("isFunctionExpression", () => {
114+
const project = new Project({
115+
useInMemoryFileSystem: true,
116+
compilerOptions: {
117+
lib: ["lib.esnext.full.d.ts"],
118+
target: ScriptTarget.ESNext,
119+
module: ModuleKind.ESNext,
120+
moduleResolution: ModuleResolutionKind.Bundler,
121+
},
122+
});
123+
const indexFile = project.createSourceFile(
124+
"index.ts",
125+
dedent`
126+
export function foo() {}
127+
128+
export const bar = () => {};
129+
export const baz = function() {};
130+
export const qux: () => void;
131+
`,
132+
);
133+
expect(isFunctionExpression(indexFile.getFunctionOrThrow("foo"))).toBe(false);
134+
expect(isFunctionExpression(indexFile.getVariableDeclarationOrThrow("bar"))).toBe(true);
135+
expect(isFunctionExpression(indexFile.getVariableDeclarationOrThrow("baz"))).toBe(true);
136+
expect(isFunctionExpression(indexFile.getVariableDeclarationOrThrow("qux"))).toBe(true);
137+
});
138+
139+
test("isClass", () => {
140+
const project = new Project({
141+
useInMemoryFileSystem: true,
142+
compilerOptions: {
143+
lib: ["lib.esnext.full.d.ts"],
144+
target: ScriptTarget.ESNext,
145+
module: ModuleKind.ESNext,
146+
moduleResolution: ModuleResolutionKind.Bundler,
147+
},
148+
});
149+
const indexFile = project.createSourceFile(
150+
"index.ts",
151+
dedent`
152+
export function foo() {}
153+
154+
export class Foo {}
155+
`,
156+
);
157+
expect(isClass(indexFile.getFunctionOrThrow("foo"))).toBe(false);
158+
expect(isClass(indexFile.getClassOrThrow("Foo"))).toBe(true);
159+
});
160+
161+
test("isInterface", () => {
162+
const project = new Project({
163+
useInMemoryFileSystem: true,
164+
compilerOptions: {
165+
lib: ["lib.esnext.full.d.ts"],
166+
target: ScriptTarget.ESNext,
167+
module: ModuleKind.ESNext,
168+
moduleResolution: ModuleResolutionKind.Bundler,
169+
},
170+
});
171+
const indexFile = project.createSourceFile(
172+
"index.ts",
173+
dedent`
174+
export function foo() {}
175+
176+
export interface Foo {}
177+
`,
178+
);
179+
expect(isInterface(indexFile.getFunctionOrThrow("foo"))).toBe(false);
180+
expect(isInterface(indexFile.getInterfaceOrThrow("Foo"))).toBe(true);
181+
});
182+
183+
test("isEnum", () => {
184+
const project = new Project({
185+
useInMemoryFileSystem: true,
186+
compilerOptions: {
187+
lib: ["lib.esnext.full.d.ts"],
188+
target: ScriptTarget.ESNext,
189+
module: ModuleKind.ESNext,
190+
moduleResolution: ModuleResolutionKind.Bundler,
191+
},
192+
});
193+
const indexFile = project.createSourceFile(
194+
"index.ts",
195+
dedent`
196+
export function foo() {}
197+
198+
export enum Foo {}
199+
`,
200+
);
201+
expect(isEnum(indexFile.getFunctionOrThrow("foo"))).toBe(false);
202+
expect(isEnum(indexFile.getEnumOrThrow("Foo"))).toBe(true);
203+
});
204+
205+
test("isTypeAlias", () => {
206+
const project = new Project({
207+
useInMemoryFileSystem: true,
208+
compilerOptions: {
209+
lib: ["lib.esnext.full.d.ts"],
210+
target: ScriptTarget.ESNext,
211+
module: ModuleKind.ESNext,
212+
moduleResolution: ModuleResolutionKind.Bundler,
213+
},
214+
});
215+
const indexFile = project.createSourceFile(
216+
"index.ts",
217+
dedent`
218+
export function foo() {}
219+
220+
export type Foo = {};
221+
`,
222+
);
223+
expect(isTypeAlias(indexFile.getFunctionOrThrow("foo"))).toBe(false);
224+
expect(isTypeAlias(indexFile.getTypeAliasOrThrow("Foo"))).toBe(true);
225+
});
226+
227+
test("isNamespace", () => {
228+
const project = new Project({
229+
useInMemoryFileSystem: true,
230+
compilerOptions: {
231+
lib: ["lib.esnext.full.d.ts"],
232+
target: ScriptTarget.ESNext,
233+
module: ModuleKind.ESNext,
234+
moduleResolution: ModuleResolutionKind.Bundler,
235+
},
236+
});
237+
const indexFile = project.createSourceFile(
238+
"index.ts",
239+
dedent`
240+
export function foo() {}
241+
242+
export namespace Foo {}
243+
`,
244+
);
245+
expect(isNamespace(indexFile.getFunctionOrThrow("foo"))).toBe(false);
246+
expect(isNamespace(indexFile.getModuleOrThrow("Foo"))).toBe(true);
247+
});
248+
249+
test("isFileModule", () => {
250+
const project = new Project({
251+
useInMemoryFileSystem: true,
252+
compilerOptions: {
253+
lib: ["lib.esnext.full.d.ts"],
254+
target: ScriptTarget.ESNext,
255+
module: ModuleKind.ESNext,
256+
moduleResolution: ModuleResolutionKind.Bundler,
257+
},
258+
});
259+
const indexFile = project.createSourceFile(
260+
"index.ts",
261+
dedent`
262+
export function foo() {}
263+
`,
264+
);
265+
expect(isFileModule(indexFile.getFunctionOrThrow("foo"))).toBe(false);
266+
expect(isFileModule(indexFile)).toBe(true);
267+
});

src/declaration-type-guards.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
type ArrowFunction,
3+
type BinaryExpression,
4+
type ClassDeclaration,
5+
type EnumDeclaration,
6+
type Expression,
7+
type FunctionDeclaration,
8+
type InterfaceDeclaration,
9+
type ModuleDeclaration,
10+
Node,
11+
type SourceFile,
12+
SyntaxKind,
13+
type TypeAliasDeclaration,
14+
type VariableDeclaration,
15+
} from "ts-morph";
16+
17+
export function isVariable(node: Node): node is VariableDeclaration {
18+
return Node.isVariableDeclaration(node) && !isFunctionExpression(node);
19+
}
20+
21+
export function isVariableAssignmentExpression(node: Node): node is BinaryExpression {
22+
return Node.isBinaryExpression(node) && Node.isIdentifier(node.getLeft());
23+
}
24+
25+
export function isExpression(node: Node): node is Expression {
26+
return Node.isExpression(node) && !Node.isArrowFunction(node);
27+
}
28+
29+
export function isFunction(node: Node): node is FunctionDeclaration | ArrowFunction {
30+
return Node.isFunctionDeclaration(node) || Node.isArrowFunction(node);
31+
}
32+
33+
export function isFunctionExpression(node: Node): node is VariableDeclaration {
34+
if (!Node.isVariableDeclaration(node)) return false;
35+
36+
// Check type signature after `:` (e.g., `const foo: () => void;`).
37+
if (node.getTypeNode()?.getKind() === SyntaxKind.FunctionType) return true;
38+
39+
const initializer = node.getInitializer();
40+
if (!initializer) return false;
41+
return Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer);
42+
}
43+
44+
export function isClass(node: Node): node is ClassDeclaration {
45+
return Node.isClassDeclaration(node);
46+
}
47+
48+
export function isInterface(node: Node): node is InterfaceDeclaration {
49+
return Node.isInterfaceDeclaration(node);
50+
}
51+
52+
export function isEnum(node: Node): node is EnumDeclaration {
53+
return Node.isEnumDeclaration(node);
54+
}
55+
56+
export function isTypeAlias(node: Node): node is TypeAliasDeclaration {
57+
return Node.isTypeAliasDeclaration(node);
58+
}
59+
60+
export function isNamespace(node: Node): node is ModuleDeclaration {
61+
return Node.isModuleDeclaration(node);
62+
}
63+
64+
export function isFileModule(node: Node): node is SourceFile {
65+
return Node.isSourceFile(node);
66+
}

src/export-equals-declarations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ModuleDeclaration, SourceFile, SyntaxKind, type ExportedDeclarations } from "ts-morph";
2+
import { isNamespace } from "./declaration-type-guards.ts";
23
import { isExportedDeclarations } from "./is-exported-declarations.ts";
34
import { isHidden } from "./is-hidden.ts";
4-
import { isNamespace } from "./is-namespace.ts";
55
import { isShorthandAmbientModule } from "./is-shorthand-ambient-module.ts";
66

77
export type ExportEqualsDeclarationsReturn = {

src/extract-declarations.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import { orderBy } from "natural-orderby";
22
import { Node, type ExportedDeclarations } from "ts-morph";
33
import { ambientModulesDeclarations } from "./ambient-modules-declarations.ts";
4+
import {
5+
isClass,
6+
isEnum,
7+
isExpression,
8+
isFileModule,
9+
isFunction,
10+
isFunctionExpression,
11+
isInterface,
12+
isNamespace,
13+
isTypeAlias,
14+
isVariable,
15+
isVariableAssignmentExpression,
16+
} from "./declaration-type-guards.ts";
417
import { exportEqualsDeclarations } from "./export-equals-declarations.ts";
518
import { exportedDeclarations } from "./exported-declarations.ts";
619
import { extractClass } from "./extract-class.ts";
@@ -16,17 +29,6 @@ import { extractVariableAssignmentExpression } from "./extract-variable-assignme
1629
import { extractVariable } from "./extract-variable.ts";
1730
import { globalAmbientDeclarations } from "./global-ambient-declarations.ts";
1831
import { id } from "./id.ts";
19-
import { isClass } from "./is-class.ts";
20-
import { isEnum } from "./is-enum.ts";
21-
import { isExpression } from "./is-expression.ts";
22-
import { isFileModule } from "./is-file-module.ts";
23-
import { isFunctionExpression } from "./is-function-expression.ts";
24-
import { isFunction } from "./is-function.ts";
25-
import { isInterface } from "./is-interface.ts";
26-
import { isNamespace } from "./is-namespace.ts";
27-
import { isTypeAlias } from "./is-type-alias.ts";
28-
import { isVariableAssignmentExpression } from "./is-variable-assignment-expression.ts";
29-
import { isVariable } from "./is-variable.ts";
3032
import type { ExtractDeclarationsOptions, ExtractedDeclaration } from "./types.ts";
3133

3234
/**

0 commit comments

Comments
 (0)