Skip to content

Commit ccc28e3

Browse files
committed
Move transformer loading code to a separate file
1 parent c6edc48 commit ccc28e3

File tree

2 files changed

+207
-203
lines changed

2 files changed

+207
-203
lines changed

src/TSTransformers.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import * as path from "path";
2+
import * as resolve from "resolve";
3+
import * as ts from "typescript";
4+
import { CompilerOptions, TransformerImport } from "./CompilerOptions";
5+
import * as diagnosticFactories from "./diagnostics";
6+
7+
export function getCustomTransformers(
8+
program: ts.Program,
9+
diagnostics: ts.Diagnostic[],
10+
customTransformers: ts.CustomTransformers,
11+
onSourceFile: (sourceFile: ts.SourceFile) => void
12+
): ts.CustomTransformers {
13+
// TODO: https://github.com/Microsoft/TypeScript/issues/28310
14+
const forEachSourceFile = (node: ts.SourceFile, callback: (sourceFile: ts.SourceFile) => ts.SourceFile) =>
15+
ts.isBundle(node)
16+
? ((ts.updateBundle(node, node.sourceFiles.map(callback)) as unknown) as ts.SourceFile)
17+
: callback(node);
18+
19+
const luaTransformer: ts.TransformerFactory<ts.SourceFile> = () => node =>
20+
forEachSourceFile(node, sourceFile => {
21+
onSourceFile(sourceFile);
22+
return ts.createSourceFile(sourceFile.fileName, "", ts.ScriptTarget.ESNext);
23+
});
24+
25+
const transformersFromOptions = loadTransformersFromOptions(program, diagnostics);
26+
return {
27+
afterDeclarations: [
28+
...(transformersFromOptions.afterDeclarations || []),
29+
...(customTransformers.afterDeclarations || []),
30+
],
31+
before: [
32+
...(customTransformers.before || []),
33+
...(transformersFromOptions.before || []),
34+
35+
...(transformersFromOptions.after || []),
36+
...(customTransformers.after || []),
37+
luaTransformer,
38+
],
39+
};
40+
}
41+
42+
function loadTransformersFromOptions(program: ts.Program, allDiagnostics: ts.Diagnostic[]): ts.CustomTransformers {
43+
const customTransformers: Required<ts.CustomTransformers> = {
44+
before: [],
45+
after: [],
46+
afterDeclarations: [],
47+
};
48+
49+
const options = program.getCompilerOptions() as CompilerOptions;
50+
if (!options.plugins) return customTransformers;
51+
52+
const configFileName = options.configFilePath as string | undefined;
53+
const basedir = configFileName ? path.dirname(configFileName) : process.cwd();
54+
55+
for (const [index, transformerImport] of options.plugins.entries()) {
56+
if ("name" in transformerImport) continue;
57+
const optionName = `compilerOptions.plugins[${index}]`;
58+
59+
const { error: resolveError, factory } = resolveTransformerFactory(basedir, optionName, transformerImport);
60+
if (resolveError) allDiagnostics.push(resolveError);
61+
if (factory === undefined) continue;
62+
63+
const { error: loadError, transformer } = loadTransformer(optionName, program, factory, transformerImport);
64+
if (loadError) allDiagnostics.push(loadError);
65+
if (transformer === undefined) continue;
66+
67+
if (transformer.before) {
68+
customTransformers.before.push(transformer.before);
69+
}
70+
71+
if (transformer.after) {
72+
customTransformers.after.push(transformer.after);
73+
}
74+
75+
if (transformer.afterDeclarations) {
76+
customTransformers.afterDeclarations.push(transformer.afterDeclarations);
77+
}
78+
}
79+
80+
return customTransformers;
81+
}
82+
83+
type ProgramTransformerFactory = (program: ts.Program, options: Record<string, any>) => Transformer;
84+
type ConfigTransformerFactory = (options: Record<string, any>) => Transformer;
85+
type CompilerOptionsTransformerFactory = (
86+
compilerOptions: CompilerOptions,
87+
options: Record<string, any>
88+
) => Transformer;
89+
type TypeCheckerTransformerFactory = (typeChecker: ts.TypeChecker, options: Record<string, any>) => Transformer;
90+
type RawTransformerFactory = Transformer;
91+
type TransformerFactory =
92+
| ProgramTransformerFactory
93+
| ConfigTransformerFactory
94+
| CompilerOptionsTransformerFactory
95+
| TypeCheckerTransformerFactory
96+
| RawTransformerFactory;
97+
98+
type Transformer = GroupTransformer | ts.TransformerFactory<ts.SourceFile>;
99+
interface GroupTransformer {
100+
before?: ts.TransformerFactory<ts.SourceFile>;
101+
after?: ts.TransformerFactory<ts.SourceFile>;
102+
afterDeclarations?: ts.TransformerFactory<ts.SourceFile | ts.Bundle>;
103+
}
104+
105+
function resolveTransformerFactory(
106+
basedir: string,
107+
transformerOptionPath: string,
108+
{ transform, import: importName = "default" }: TransformerImport
109+
): { error?: ts.Diagnostic; factory?: TransformerFactory } {
110+
if (typeof transform !== "string") {
111+
const optionName = `${transformerOptionPath}.transform`;
112+
return { error: diagnosticFactories.compilerOptionRequiresAValueOfType(optionName, "string") };
113+
}
114+
115+
let resolved: string;
116+
try {
117+
resolved = resolve.sync(transform, { basedir, extensions: [".js", ".ts", ".tsx"] });
118+
} catch (err) {
119+
if (err.code !== "MODULE_NOT_FOUND") throw err;
120+
return { error: diagnosticFactories.couldNotResolveTransformerFrom(transform, basedir) };
121+
}
122+
123+
// tslint:disable-next-line: deprecation
124+
const hasNoRequireHook = require.extensions[".ts"] === undefined;
125+
if (hasNoRequireHook && (resolved.endsWith(".ts") || resolved.endsWith(".tsx"))) {
126+
try {
127+
const tsNode: typeof import("ts-node") = require("ts-node");
128+
tsNode.register({ transpileOnly: true });
129+
} catch (err) {
130+
if (err.code !== "MODULE_NOT_FOUND") throw err;
131+
return { error: diagnosticFactories.toLoadTransformerItShouldBeTranspiled(transform) };
132+
}
133+
}
134+
135+
const factory: TransformerFactory = require(resolved)[importName];
136+
if (factory === undefined) {
137+
return { error: diagnosticFactories.transformerShouldHaveAExport(transform, importName) };
138+
}
139+
140+
return { factory };
141+
}
142+
143+
function loadTransformer(
144+
transformerOptionPath: string,
145+
program: ts.Program,
146+
factory: TransformerFactory,
147+
{ transform, after = false, afterDeclarations = false, type = "program", ...extraOptions }: TransformerImport
148+
): { error?: ts.Diagnostic; transformer?: GroupTransformer } {
149+
let transformer: Transformer;
150+
switch (type) {
151+
case "program":
152+
transformer = (factory as ProgramTransformerFactory)(program, extraOptions);
153+
break;
154+
case "config":
155+
transformer = (factory as ConfigTransformerFactory)(extraOptions);
156+
break;
157+
case "checker":
158+
transformer = (factory as TypeCheckerTransformerFactory)(program.getTypeChecker(), extraOptions);
159+
break;
160+
case "raw":
161+
transformer = factory as RawTransformerFactory;
162+
break;
163+
case "compilerOptions":
164+
transformer = (factory as CompilerOptionsTransformerFactory)(program.getCompilerOptions(), extraOptions);
165+
break;
166+
default: {
167+
const optionName = `--${transformerOptionPath}.type`;
168+
return { error: diagnosticFactories.argumentForOptionMustBe(optionName, "program") };
169+
}
170+
}
171+
172+
if (typeof after !== "boolean") {
173+
const optionName = `${transformerOptionPath}.after`;
174+
return { error: diagnosticFactories.compilerOptionRequiresAValueOfType(optionName, "boolean") };
175+
}
176+
177+
if (typeof afterDeclarations !== "boolean") {
178+
const optionName = `${transformerOptionPath}.afterDeclarations`;
179+
return { error: diagnosticFactories.compilerOptionRequiresAValueOfType(optionName, "boolean") };
180+
}
181+
182+
if (typeof transformer === "function") {
183+
let wrappedTransformer: GroupTransformer;
184+
185+
if (after) {
186+
wrappedTransformer = { after: transformer };
187+
} else if (afterDeclarations) {
188+
wrappedTransformer = { afterDeclarations: transformer as ts.TransformerFactory<ts.SourceFile | ts.Bundle> };
189+
} else {
190+
wrappedTransformer = { before: transformer };
191+
}
192+
193+
return { transformer: wrappedTransformer };
194+
} else {
195+
const isValidGroupTransformer =
196+
typeof transformer === "object" &&
197+
(transformer.before || transformer.after || transformer.afterDeclarations);
198+
199+
if (!isValidGroupTransformer) {
200+
return { error: diagnosticFactories.transformerShouldBeATsTransformerFactory(transform) };
201+
}
202+
}
203+
204+
return { transformer };
205+
}

0 commit comments

Comments
 (0)