Skip to content

Commit a4552c8

Browse files
Improve tsc type generation performance (#45)
1 parent 620be21 commit a4552c8

File tree

4 files changed

+68
-58
lines changed

4 files changed

+68
-58
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@joernio/astgen",
3-
"version": "3.33.0",
3+
"version": "3.34.0",
44
"description": "Generate JS/TS AST in json format with Babel",
55
"exports": "./index.js",
66
"keywords": [

src/Defaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export const DEFAULT_TSC_TYPE_OPTIONS: number = tsc.TypeFormatFlags.NoTruncation
9696

9797
export const ANY: string = "any"
9898
export const UNKNOWN: string = "unknown"
99+
export const UNRESOLVED: string = "/*unresolved*/"
99100

100101
export const STRING_REGEX: RegExp = /^["'`].*["'`]$/
101102
export const ARRAY_REGEX: RegExp = /.+\[]$/

src/TscUtils.ts

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,64 +4,73 @@ import tsc from "typescript";
44

55
export type TypeMap = Map<string, string>;
66

7-
function forEachNode(ast: tsc.Node, callback: (node: tsc.Node) => void): void {
8-
function visit(node: tsc.Node) {
9-
tsc.forEachChild(node, visit);
10-
callback(node);
11-
}
12-
13-
visit(ast);
14-
}
7+
export default class TscUtils {
8+
private readonly program: tsc.Program;
9+
private readonly typeChecker: tsc.TypeChecker;
1510

16-
function safeTypeToString(node: tsc.Type, typeChecker: tsc.TypeChecker): string {
17-
try {
18-
const tpe: string = typeChecker.typeToString(node, undefined, Defaults.DEFAULT_TSC_TYPE_OPTIONS);
19-
if (tpe.length === 0) return Defaults.ANY
20-
if (tpe == Defaults.UNKNOWN) return Defaults.ANY
21-
if (Defaults.STRING_REGEX.test(tpe)) return "string";
22-
if (Defaults.ARRAY_REGEX.test(tpe)) return "__ecma.Array";
23-
return tpe;
24-
} catch (err) {
25-
return Defaults.ANY;
11+
constructor(files: string[]) {
12+
this.program = tsc.createProgram(files, Defaults.DEFAULT_TSC_OPTIONS);
13+
this.typeChecker = this.program.getTypeChecker();
2614
}
27-
}
2815

29-
function isSignatureDeclaration(node: tsc.Node): node is tsc.SignatureDeclaration {
30-
return tsc.isSetAccessor(node) || tsc.isGetAccessor(node) ||
31-
tsc.isConstructSignatureDeclaration(node) || tsc.isMethodDeclaration(node) ||
32-
tsc.isFunctionDeclaration(node) || tsc.isConstructorDeclaration(node)
33-
}
34-
35-
export function typeMapForFile(file: string): TypeMap {
36-
function addType(node: tsc.Node): void {
37-
if (tsc.isSourceFile(node)) return;
38-
let typeStr;
39-
if (isSignatureDeclaration(node)) {
40-
const signature: tsc.Signature = typeChecker.getSignatureFromDeclaration(node)!;
41-
const returnType: tsc.Type = typeChecker.getReturnTypeOfSignature(signature);
42-
typeStr = safeTypeToString(returnType, typeChecker);
43-
} else if (tsc.isFunctionLike(node)) {
44-
const funcType: tsc.Type = typeChecker.getTypeAtLocation(node);
45-
const funcSignature: tsc.Signature = typeChecker.getSignaturesOfType(funcType, tsc.SignatureKind.Call)[0];
46-
if (funcSignature) {
47-
typeStr = safeTypeToString(funcSignature.getReturnType(), typeChecker);
16+
typeMapForFile(file: string): TypeMap {
17+
let addType: (node: tsc.Node) => void = (node: tsc.Node): void => {
18+
if (tsc.isSourceFile(node)) return;
19+
let typeStr;
20+
if (this.isSignatureDeclaration(node)) {
21+
const signature: tsc.Signature = this.typeChecker.getSignatureFromDeclaration(node)!;
22+
const returnType: tsc.Type = this.typeChecker.getReturnTypeOfSignature(signature);
23+
typeStr = this.safeTypeToString(returnType);
24+
} else if (tsc.isFunctionLike(node)) {
25+
const funcType: tsc.Type = this.typeChecker.getTypeAtLocation(node);
26+
const funcSignature: tsc.Signature = this.typeChecker.getSignaturesOfType(funcType, tsc.SignatureKind.Call)[0];
27+
if (funcSignature) {
28+
typeStr = this.safeTypeToString(funcSignature.getReturnType());
29+
} else {
30+
typeStr = this.safeTypeToString(this.typeChecker.getTypeAtLocation(node));
31+
}
4832
} else {
49-
typeStr = safeTypeToString(typeChecker.getTypeAtLocation(node), typeChecker);
33+
typeStr = this.safeTypeToString(this.typeChecker.getTypeAtLocation(node));
34+
}
35+
if (typeStr !== Defaults.ANY) {
36+
const pos = `${node.getStart()}:${node.getEnd()}`;
37+
seenTypes.set(pos, typeStr);
5038
}
51-
} else {
52-
typeStr = safeTypeToString(typeChecker.getTypeAtLocation(node), typeChecker);
5339
}
54-
if (typeStr !== Defaults.ANY) {
55-
const pos = `${node.getStart()}:${node.getEnd()}`;
56-
seenTypes.set(pos, typeStr);
40+
41+
const seenTypes = new Map<string, string>();
42+
this.forEachNode(this.program.getSourceFile(file)!, addType)
43+
return seenTypes
44+
}
45+
46+
private forEachNode(ast: tsc.Node, callback: (node: tsc.Node) => void): void {
47+
function visit(node: tsc.Node) {
48+
tsc.forEachChild(node, visit);
49+
callback(node);
5750
}
51+
52+
visit(ast);
5853
}
5954

60-
const program: tsc.Program = tsc.createProgram([file], Defaults.DEFAULT_TSC_OPTIONS);
61-
const typeChecker: tsc.TypeChecker = program.getTypeChecker();
62-
const seenTypes = new Map<string, string>();
55+
private safeTypeToString(node: tsc.Type): string {
56+
try {
57+
const tpe: string = this.typeChecker.typeToString(node, undefined, Defaults.DEFAULT_TSC_TYPE_OPTIONS);
58+
if (tpe.length === 0) return Defaults.ANY
59+
if (tpe == Defaults.UNKNOWN) return Defaults.ANY
60+
if (tpe.startsWith(Defaults.UNRESOLVED)) return Defaults.ANY
61+
if (Defaults.STRING_REGEX.test(tpe)) return "string";
62+
if (Defaults.ARRAY_REGEX.test(tpe)) return "__ecma.Array";
63+
return tpe;
64+
} catch (err) {
65+
return Defaults.ANY;
66+
}
67+
}
68+
69+
private isSignatureDeclaration(node: tsc.Node): node is tsc.SignatureDeclaration {
70+
return tsc.isSetAccessor(node) || tsc.isGetAccessor(node) ||
71+
tsc.isConstructSignatureDeclaration(node) || tsc.isMethodDeclaration(node) ||
72+
tsc.isFunctionDeclaration(node) || tsc.isConstructorDeclaration(node)
73+
}
6374

64-
forEachNode(program.getSourceFile(file)!, addType)
65-
return seenTypes
6675
}
6776

src/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import * as Defaults from "./Defaults"
33
import * as FileUtils from "./FileUtils"
44
import * as JsonUtils from "./JsonUtils"
55
import * as VueCodeCleaner from "./VueCodeCleaner"
6-
import * as TscUtils from "./TscUtils"
7-
import {TypeMap} from "./TscUtils"
6+
import TscUtils, {TypeMap} from "./TscUtils"
87

98
import * as babelParser from "@babel/parser"
109
import * as path from "node:path"
@@ -41,12 +40,12 @@ function toVueAst(file: string): babelParser.ParseResult {
4140
return codeToJsAst(cleanedCode);
4241
}
4342

44-
function typeMapForFile(file: string): TypeMap | undefined {
43+
function buildTscUtils(files: string[]): TscUtils | undefined {
4544
try {
46-
return TscUtils.typeMapForFile(file)
45+
return new TscUtils(files)
4746
} catch (err) {
4847
if (err instanceof Error) {
49-
console.warn("Retrieving types", file, ":", err.message);
48+
console.warn("Retrieving types:", err.message);
5049
}
5150
return undefined;
5251
}
@@ -58,14 +57,15 @@ function typeMapForFile(file: string): TypeMap | undefined {
5857
async function createJSAst(options: Options) {
5958
try {
6059
const srcFiles: string[] = await FileUtils.filesWithExtensions(options, Defaults.JS_EXTENSIONS);
60+
const tscUtils: TscUtils | undefined = (options.tsTypes && srcFiles.length > 0) ? buildTscUtils(srcFiles) : undefined;
6161
for (const file of srcFiles) {
6262
try {
6363
const ast: babelParser.ParseResult = fileToJsAst(file);
6464
writeAstFile(file, ast, options);
6565
try {
66-
let ts: TypeMap | undefined = options.tsTypes ? typeMapForFile(file) : undefined;
67-
if (ts && ts.size !== 0) {
68-
writeTypesFile(file, ts, options);
66+
let typeMap: TypeMap | undefined = tscUtils ? tscUtils.typeMapForFile(file) : undefined;
67+
if (typeMap && typeMap.size !== 0) {
68+
writeTypesFile(file, typeMap, options);
6969
}
7070
} catch (err) {
7171
if (err instanceof Error) {

0 commit comments

Comments
 (0)