-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
115 lines (93 loc) · 4.19 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import ts, { CompilerHost, CompilerOptions, Program, SyntaxKind, TransformationContext, SourceFile, Node } from 'typescript';
import { PluginConfig, ProgramTransformerExtras } from "ts-patch";
import {} from 'ts-expose-internals'
import ex = CSS.ex;
// From: https://github.com/nonara/ts-patch/discussions/29#discussioncomment-325979
/* ****************************************************************************************************************** */
// region: Helpers
/* ****************************************************************************************************************** */
/**
* Patches existing Compiler Host (or creates new one) to allow feeding updated file content from cache
*/
function getPatchedHost(
maybeHost: CompilerHost | undefined,
tsInstance: typeof ts,
compilerOptions: CompilerOptions
): CompilerHost & { fileCache: Map<string, SourceFile> }
{
const fileCache = new Map();
const compilerHost = maybeHost ?? tsInstance.createCompilerHost(compilerOptions, true);
const originalGetSourceFile = compilerHost.getSourceFile;
return Object.assign(compilerHost, {
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget) {
fileName = tsInstance.normalizePath(fileName);
if (fileCache.has(fileName)) return fileCache.get(fileName);
const sourceFile = originalGetSourceFile.apply(void 0, Array.from(arguments) as any);
fileCache.set(fileName, sourceFile);
return sourceFile;
},
fileCache
});
}
// endregion
/* ****************************************************************************************************************** */
// region: Program Transformer
/* ****************************************************************************************************************** */
export default function transformProgram(
program: Program,
host: CompilerHost | undefined,
config: PluginConfig,
extras?: ProgramTransformerExtras,
): Program {
if(!extras) {
throw new Error(`Please add the flag "transformProgram": true to the transformer inside tsconfig.json`);
}
const { ts: tsInstance } = extras;
const compilerOptions = program.getCompilerOptions();
const compilerHost = getPatchedHost(host, tsInstance, compilerOptions);
const rootFileNames = program.getRootFileNames().map(tsInstance.normalizePath);
/* Transform AST */
const transformedSource = tsInstance.transform(
/* sourceFiles */ program.getSourceFiles().filter(sourceFile => rootFileNames.includes(sourceFile.fileName)),
/* transformers */ [ transformAst.bind(tsInstance) ],
compilerOptions
).transformed;
/* Render modified files and create new SourceFiles for them to use in host's cache */
const { printFile } = tsInstance.createPrinter();
for (const sourceFile of transformedSource) {
const { fileName, languageVersion } = sourceFile;
const updatedSourceFile = tsInstance.createSourceFile(fileName, printFile(sourceFile), languageVersion);
compilerHost.fileCache.set(fileName, updatedSourceFile);
}
/* Re-create Program instance */
return tsInstance.createProgram(rootFileNames, compilerOptions, compilerHost);
}
// endregion
/* ****************************************************************************************************************** */
// region: AST Transformer
/* ****************************************************************************************************************** */
/**
* Change all 'number' keywords to 'string'
*
* @example
* // before
* type A = number
*
* // after
* type A = string
*/
function transformAst(this: typeof ts, context: TransformationContext) {
const tsInstance = this;
/* Transformer Function */
return (sourceFile: SourceFile) => {
return tsInstance.visitEachChild(sourceFile, visit, context);
/* Visitor Function */
function visit(node: Node): Node {
if (node.kind === SyntaxKind.NumberKeyword)
return context.factory.createKeywordTypeNode(tsInstance.SyntaxKind.StringKeyword);
else
return tsInstance.visitEachChild(node, visit, context);
}
}
}
// endregion