Skip to content

Commit d30f7ed

Browse files
ark120202Perryvw
authored andcommitted
Load custom transformers from tsconfig (#552)
* Load custom transformers from tsconfig * Rename diagnostics imported namespace to diagnosticFactories * Tests * Add diagnostic when transformer could not be resolved * Rename "transform" to "name" * Validate name and when transformer options * Resolve ts-node from module location instead of base dir * Make .js files have higher priority during transformer resolution * Rename variable * Add ts-node as an optional peer dependency * Add compatibility with ttypescript and support more transformer types * Remove old option tests * Use original source file node in getEmitResolver * Move transformer loading code to a separate file * Check for "transform" property existence instead of "name" non-existence * Remove ts-node optional peer dependency
1 parent cc5c0a0 commit d30f7ed

File tree

15 files changed

+492
-61
lines changed

15 files changed

+492
-61
lines changed

package-lock.json

Lines changed: 16 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@
3939
"node": ">=8.5.0"
4040
},
4141
"dependencies": {
42+
"resolve": "^1.10.1",
4243
"source-map": "^0.7.3",
4344
"typescript": "^3.4.5"
4445
},
4546
"devDependencies": {
4647
"@types/glob": "^7.1.1",
4748
"@types/jest": "^24.0.12",
4849
"@types/node": "^11.13.0",
50+
"@types/resolve": "0.0.8",
4951
"fengari": "^0.1.4",
5052
"jest": "^24.8.0",
5153
"jest-circus": "^24.8.0",

src/CommandLineParser.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from "path";
22
import * as ts from "typescript";
33
import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions";
4-
import * as diagnostics from "./diagnostics";
4+
import * as diagnosticFactories from "./diagnostics";
55

66
export interface ParsedCommandLine extends ts.ParsedCommandLine {
77
options: CompilerOptions;
@@ -10,7 +10,7 @@ export interface ParsedCommandLine extends ts.ParsedCommandLine {
1010
interface CommandLineOptionBase {
1111
name: string;
1212
aliases?: string[];
13-
describe: string;
13+
description: string;
1414
}
1515

1616
interface CommandLineOptionOfEnum extends CommandLineOptionBase {
@@ -23,33 +23,34 @@ interface CommandLineOptionOfBoolean extends CommandLineOptionBase {
2323
}
2424

2525
type CommandLineOption = CommandLineOptionOfEnum | CommandLineOptionOfBoolean;
26+
2627
const optionDeclarations: CommandLineOption[] = [
2728
{
2829
name: "luaLibImport",
29-
describe: "Specifies how js standard features missing in lua are imported.",
30+
description: "Specifies how js standard features missing in lua are imported.",
3031
type: "enum",
3132
choices: Object.values(LuaLibImportKind),
3233
},
3334
{
3435
name: "luaTarget",
3536
aliases: ["lt"],
36-
describe: "Specify Lua target version.",
37+
description: "Specify Lua target version.",
3738
type: "enum",
3839
choices: Object.values(LuaTarget),
3940
},
4041
{
4142
name: "noHeader",
42-
describe: "Specify if a header will be added to compiled files.",
43+
description: "Specify if a header will be added to compiled files.",
4344
type: "boolean",
4445
},
4546
{
4647
name: "noHoisting",
47-
describe: "Disables hoisting.",
48+
description: "Disables hoisting.",
4849
type: "boolean",
4950
},
5051
{
5152
name: "sourceMapTraceback",
52-
describe: "Applies the source map to show source TS files and lines in error tracebacks.",
53+
description: "Applies the source map to show source TS files and lines in error tracebacks.",
5354
type: "boolean",
5455
},
5556
];
@@ -78,7 +79,7 @@ export function getHelpString(): string {
7879
const valuesHint = option.type === "enum" ? option.choices.join("|") : option.type;
7980
const spacing = " ".repeat(Math.max(1, 45 - optionString.length - valuesHint.length));
8081

81-
result += `\n ${optionString} <${valuesHint}>${spacing}${option.describe}\n`;
82+
result += `\n ${optionString} <${valuesHint}>${spacing}${option.description}\n`;
8283
}
8384

8485
return result;
@@ -97,13 +98,15 @@ export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine):
9798

9899
if (parsedConfigFile.raw.tstl) {
99100
if (hasRootLevelOptions) {
100-
parsedConfigFile.errors.push(diagnostics.tstlOptionsAreMovingToTheTstlObject(parsedConfigFile.raw.tstl));
101+
parsedConfigFile.errors.push(
102+
diagnosticFactories.tstlOptionsAreMovingToTheTstlObject(parsedConfigFile.raw.tstl)
103+
);
101104
}
102105

103106
for (const key in parsedConfigFile.raw.tstl) {
104107
const option = optionDeclarations.find(option => option.name === key);
105108
if (!option) {
106-
parsedConfigFile.errors.push(diagnostics.unknownCompilerOption(key));
109+
parsedConfigFile.errors.push(diagnosticFactories.unknownCompilerOption(key));
107110
continue;
108111
}
109112

@@ -167,9 +170,11 @@ function readCommandLineArgument(option: CommandLineOption, value: any): Command
167170
// Set boolean arguments without supplied value to true
168171
return { value: true, increment: 0 };
169172
}
170-
} else if (value === undefined) {
173+
}
174+
175+
if (value === undefined) {
171176
return {
172-
error: diagnostics.compilerOptionExpectsAnArgument(option.name),
177+
error: diagnosticFactories.compilerOptionExpectsAnArgument(option.name),
173178
value: undefined,
174179
increment: 0,
175180
};
@@ -191,7 +196,7 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult {
191196
if (typeof value !== "boolean") {
192197
return {
193198
value: undefined,
194-
error: diagnostics.compilerOptionRequiresAValueOfType(option.name, "boolean"),
199+
error: diagnosticFactories.compilerOptionRequiresAValueOfType(option.name, "boolean"),
195200
};
196201
}
197202

@@ -202,7 +207,7 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult {
202207
if (typeof value !== "string") {
203208
return {
204209
value: undefined,
205-
error: diagnostics.compilerOptionRequiresAValueOfType(option.name, "string"),
210+
error: diagnosticFactories.compilerOptionRequiresAValueOfType(option.name, "string"),
206211
};
207212
}
208213

@@ -211,7 +216,7 @@ function readValue(option: CommandLineOption, value: unknown): ReadValueResult {
211216
const optionChoices = option.choices.join(", ");
212217
return {
213218
value: undefined,
214-
error: diagnostics.argumentForOptionMustBe(`--${option.name}`, optionChoices),
219+
error: diagnosticFactories.argumentForOptionMustBe(`--${option.name}`, optionChoices),
215220
};
216221
}
217222

src/CompilerOptions.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import * as ts from "typescript";
22

3-
export interface CompilerOptions extends ts.CompilerOptions {
3+
type KnownKeys<T> = { [K in keyof T]: string extends K ? never : number extends K ? never : K } extends {
4+
[_ in keyof T]: infer U
5+
}
6+
? U
7+
: never;
8+
9+
type OmitIndexSignature<T extends Record<any, any>> = Pick<T, KnownKeys<T>>;
10+
11+
export interface TransformerImport {
12+
transform: string;
13+
import?: string;
14+
after?: boolean;
15+
afterDeclarations?: boolean;
16+
type?: "program" | "config" | "checker" | "raw" | "compilerOptions";
17+
[option: string]: any;
18+
}
19+
20+
export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> & {
421
noHeader?: boolean;
522
luaTarget?: LuaTarget;
623
luaLibImport?: LuaLibImportKind;
724
noHoisting?: boolean;
825
sourceMapTraceback?: boolean;
9-
}
26+
plugins?: Array<ts.PluginImport | TransformerImport>;
27+
[option: string]: ts.CompilerOptions[string] | Array<ts.PluginImport | TransformerImport>;
28+
};
1029

1130
export enum LuaLibImportKind {
1231
None = "none",

src/LuaTransformer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ export class LuaTransformer {
108108
this.setupState();
109109

110110
this.currentSourceFile = node;
111-
this.resolver = this.checker.getEmitResolver(node);
111+
112+
// Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers.
113+
// It's required because otherwise `getEmitResolver` won't use cached diagnostics, produced in `emitWorker`
114+
// and would try to re-analyze the file, which would fail because of replaced nodes.
115+
const originalSourceFile = ts.getParseTreeNode(node, ts.isSourceFile) || node;
116+
this.resolver = this.checker.getEmitResolver(originalSourceFile);
112117

113118
let statements: tstl.Statement[] = [];
114119
if (node.flags & ts.NodeFlags.JsonFile) {

0 commit comments

Comments
 (0)