Skip to content

Commit 9f61a82

Browse files
authored
Return diagnostics instead of throwing errors (#769)
* Add diagnostic reporting and convert forbidden for...in array error * Include generated code in diagnostic snapshots * Remove some errors that already have TypeScript diagnostics * Replace unactionable errors with assertions * Replace empty json file diagnostic with runtime error * Refactor `@forRange` annotation tests * Move invalid `@forRange` call error to diagnostics * Make annotation errors diagnostics * Make unsupported `luaIterator` usage error a diagnostic * Make function assignment errors diagnostics * Replace UnsupportedKind errors with diagnostics or better types * Make `UnsupportedForTarget` error a diagnostic * Make `UnsupportedProperty` error a diagnostic * Make loop errors diagnostics * Simplift `isValidLuaIdentifier` usage * Make `InvalidAmbientIdentifierName` error a diagnostic * Remove `UndefinedScope` * Make class transform safer, fixes #771 * Make all other errors diagnostics * Make `bundle` tests check diagnostic snapshots * Remove remaining TranspileError handling code * Fix formatting * Update `loops.spec.ts.snap` * Temporary loosen `for...of` initializer variable declaration check * Refactor SimpleOperator handling * Fix typo * Remove unused missing import * Remove `test.only` from `conditionals.spec.ts` * Use inline snapshots for simple tests * Revert "Use inline snapshots for simple tests" This reverts commit 9312a0c. * Factorize transpilation diagnostics * Improve some diagnostic code spans * Allow to specify expected diagnostic codes in matchers * Add expected diagnostics to snapshot assertions * Add expected diagnostics to function assignability cast tests * Add expected diagnostics to bundle tests * Add changelog entry * Refactor for initializer variable transformation * Update tests * Refactor diagnostc factories * Fix `luaTable` interface declaration tests * Make condition more readable
1 parent 71a2aef commit 9f61a82

File tree

95 files changed

+4028
-1654
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+4028
-1654
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,35 @@
5252

5353
This change simplifies our codebase and opens a path to object accessors implementation
5454

55+
- Errors reported during transpilation now are created as TypeScript diagnostics, instead of being thrown as JavaScript errors. This makes TypeScriptToLua always try to generate valid code (even in presence of errors) and allows multiple errors to be reported in a single file:
56+
57+
<!-- prettier-ignore -->
58+
```ts
59+
for (var x in []) {}
60+
```
61+
62+
```shell
63+
# Before
64+
65+
$ tstl file.ts
66+
file.ts:1:1 - error TSTL: Iterating over arrays with 'for ... in' is not allowed.
67+
68+
$ cat file.lua
69+
error("Iterating over arrays with 'for ... in' is not allowed.")
70+
```
71+
72+
```shell
73+
# Now
74+
75+
$ tstl file.ts
76+
file.ts:1:1 - error TSTL: Iterating over arrays with 'for ... in' is not allowed.
77+
file.ts:1:6 - error TSTL: `var` declarations are not supported. Use `let` or `const` instead.
78+
79+
$ cat file.lua
80+
for x in pairs({}) do
81+
end
82+
```
83+
5584
## 0.31.0
5685
5786
- **Breaking:** The old annotation syntax (`/* !varArg */`) **no longer works**, the only currently supported syntax is:

src/CompilerOptions.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as ts from "typescript";
2+
import * as diagnosticFactories from "./transpilation/diagnostics";
23

34
type KnownKeys<T> = { [K in keyof T]: string extends K ? never : number extends K ? never : K } extends {
45
[_ in keyof T]: infer U;
@@ -47,37 +48,12 @@ export function validateOptions(options: CompilerOptions): ts.Diagnostic[] {
4748
const diagnostics: ts.Diagnostic[] = [];
4849

4950
if (options.luaBundle && !options.luaBundleEntry) {
50-
diagnostics.push(configErrorDiagnostic(`'luaBundleEntry' is required when 'luaBundle' is enabled.`));
51+
diagnostics.push(diagnosticFactories.luaBundleEntryIsRequired());
5152
}
5253

5354
if (options.luaBundle && options.luaLibImport === LuaLibImportKind.Inline) {
54-
diagnostics.push(
55-
configWarningDiagnostic(
56-
`Using 'luaBundle' with 'luaLibImport: "inline"' might generate duplicate code. ` +
57-
`It is recommended to use 'luaLibImport: "require"'`
58-
)
59-
);
55+
diagnostics.push(diagnosticFactories.usingLuaBundleWithInlineMightGenerateDuplicateCode());
6056
}
6157

6258
return diagnostics;
6359
}
64-
65-
const configErrorDiagnostic = (message: string): ts.Diagnostic => ({
66-
file: undefined,
67-
start: undefined,
68-
length: undefined,
69-
category: ts.DiagnosticCategory.Error,
70-
code: 0,
71-
source: "typescript-to-lua",
72-
messageText: message,
73-
});
74-
75-
const configWarningDiagnostic = (message: string): ts.Diagnostic => ({
76-
file: undefined,
77-
start: undefined,
78-
length: undefined,
79-
category: ts.DiagnosticCategory.Warning,
80-
code: 0,
81-
source: "typescript-to-lua",
82-
messageText: message,
83-
});

src/LuaPrinter.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as ts from "typescript";
44
import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions";
55
import * as lua from "./LuaAST";
66
import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib";
7-
import { isValidLuaIdentifier, luaKeywords } from "./transformation/utils/safe-names";
7+
import { isValidLuaIdentifier } from "./transformation/utils/safe-names";
88
import { EmitHost } from "./transpilation";
99
import { trimExtension } from "./utils";
1010

@@ -660,11 +660,7 @@ export class LuaPrinter {
660660
const value = this.printExpression(expression.value);
661661

662662
if (expression.key) {
663-
if (
664-
lua.isStringLiteral(expression.key) &&
665-
isValidLuaIdentifier(expression.key.value) &&
666-
!luaKeywords.has(expression.key.value)
667-
) {
663+
if (lua.isStringLiteral(expression.key) && isValidLuaIdentifier(expression.key.value)) {
668664
chunks.push(expression.key.value, " = ", value);
669665
} else {
670666
chunks.push("[", this.printExpression(expression.key), "] = ", value);
@@ -761,11 +757,7 @@ export class LuaPrinter {
761757
const chunks: SourceChunk[] = [];
762758

763759
chunks.push(this.printExpressionInParenthesesIfNeeded(expression.table));
764-
if (
765-
lua.isStringLiteral(expression.index) &&
766-
isValidLuaIdentifier(expression.index.value) &&
767-
!luaKeywords.has(expression.index.value)
768-
) {
760+
if (lua.isStringLiteral(expression.index) && isValidLuaIdentifier(expression.index.value)) {
769761
chunks.push(".", this.createSourceNode(expression.index, expression.index.value));
770762
} else {
771763
chunks.push("[", this.printExpression(expression.index), "]");

src/cli/diagnostics.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import * as ts from "typescript";
2+
import { createSerialDiagnosticFactory, createDiagnosticFactoryWithCode } from "../utils";
23

3-
export const tstlOptionsAreMovingToTheTstlObject = (tstl: Record<string, any>): ts.Diagnostic => ({
4-
file: undefined,
5-
start: undefined,
6-
length: undefined,
4+
export const tstlOptionsAreMovingToTheTstlObject = createSerialDiagnosticFactory((tstl: Record<string, any>) => ({
75
category: ts.DiagnosticCategory.Warning,
8-
code: 0,
9-
source: "typescript-to-lua",
106
messageText:
117
'TSTL options are moving to the "tstl" object. Adjust your tsconfig to look like\n' +
128
`"tstl": ${JSON.stringify(tstl, undefined, 4)}`,
13-
});
9+
}));
1410

1511
export const watchErrorSummary = (errorCount: number): ts.Diagnostic => ({
1612
file: undefined,
@@ -24,16 +20,8 @@ export const watchErrorSummary = (errorCount: number): ts.Diagnostic => ({
2420
: `Found ${errorCount} errors. Watching for file changes.`,
2521
});
2622

27-
const createCommandLineError = <Args extends any[]>(code: number, getMessage: (...args: Args) => string) => (
28-
...args: Args
29-
): ts.Diagnostic => ({
30-
file: undefined,
31-
start: undefined,
32-
length: undefined,
33-
category: ts.DiagnosticCategory.Error,
34-
code,
35-
messageText: getMessage(...args),
36-
});
23+
const createCommandLineError = <TArgs extends any[]>(code: number, getMessage: (...args: TArgs) => string) =>
24+
createDiagnosticFactoryWithCode(code, (...args: TArgs) => ({ messageText: getMessage(...args) }));
3725

3826
export const unknownCompilerOption = createCommandLineError(
3927
5023,

src/cli/report.ts

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

3+
export const prepareDiagnosticForFormatting = (diagnostic: ts.Diagnostic) =>
4+
diagnostic.source === "typescript-to-lua" ? { ...diagnostic, code: "TL" as any } : diagnostic;
5+
36
export function createDiagnosticReporter(pretty: boolean, system = ts.sys): ts.DiagnosticReporter {
47
const reporter = ts.createDiagnosticReporter(system, pretty);
5-
return diagnostic => {
6-
if (diagnostic.source === "typescript-to-lua") {
7-
diagnostic = { ...diagnostic, code: ("TL" + diagnostic.code) as any };
8-
}
9-
10-
reporter(diagnostic);
11-
};
8+
return diagnostic => reporter(prepareDiagnosticForFormatting(diagnostic));
129
}

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ export * from "./LuaAST";
66
export { LuaLibFeature } from "./LuaLib";
77
export * from "./LuaPrinter";
88
export * from "./transformation/context";
9-
export { TranspileError } from "./transformation/utils/errors";
109
export * from "./transpilation";

src/transformation/builtins/array.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { TransformationContext } from "../context";
4-
import { UnsupportedProperty } from "../utils/errors";
4+
import { unsupportedProperty } from "../utils/diagnostics";
55
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
66
import { PropertyCallExpression, transformArguments } from "../visitors/call";
77

88
export function transformArrayPrototypeCall(
99
context: TransformationContext,
1010
node: PropertyCallExpression
11-
): lua.CallExpression {
11+
): lua.CallExpression | undefined {
1212
const expression = node.expression;
1313
const signature = context.checker.getResolvedSignature(node);
1414
const params = transformArguments(context, node.arguments, signature);
@@ -79,7 +79,7 @@ export function transformArrayPrototypeCall(
7979
case "flatMap":
8080
return transformLuaLibFunction(context, LuaLibFeature.ArrayFlatMap, node, caller, ...params);
8181
default:
82-
throw UnsupportedProperty("array", expressionName, node);
82+
context.diagnostics.push(unsupportedProperty(expression.name, "array", expressionName));
8383
}
8484
}
8585

src/transformation/builtins/console.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as ts from "typescript";
22
import * as lua from "../../LuaAST";
33
import { TransformationContext } from "../context";
4-
import { UnsupportedProperty } from "../utils/errors";
4+
import { unsupportedProperty } from "../utils/diagnostics";
55
import { PropertyCallExpression, transformArguments } from "../visitors/call";
66

77
const isStringFormatTemplate = (node: ts.Expression) => ts.isStringLiteral(node) && node.text.includes("%");
88

99
export function transformConsoleCall(
1010
context: TransformationContext,
1111
expression: PropertyCallExpression
12-
): lua.Expression {
12+
): lua.Expression | undefined {
1313
const method = expression.expression;
1414
const methodName = method.name.text;
1515
const signature = context.checker.getResolvedSignature(expression);
@@ -61,6 +61,6 @@ export function transformConsoleCall(
6161
);
6262
return lua.createCallExpression(lua.createIdentifier("print"), [debugTracebackCall]);
6363
default:
64-
throw UnsupportedProperty("console", methodName, expression);
64+
context.diagnostics.push(unsupportedProperty(method.name, "console", methodName));
6565
}
6666
}

src/transformation/builtins/function.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import * as lua from "../../LuaAST";
22
import { TransformationContext } from "../context";
3-
import { UnsupportedProperty, UnsupportedSelfFunctionConversion } from "../utils/errors";
3+
import { unsupportedProperty, unsupportedSelfFunctionConversion } from "../utils/diagnostics";
44
import { ContextType, getFunctionContextType } from "../utils/function-context";
55
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
66
import { PropertyCallExpression, transformArguments } from "../visitors/call";
77

88
export function transformFunctionPrototypeCall(
99
context: TransformationContext,
1010
node: PropertyCallExpression
11-
): lua.CallExpression {
11+
): lua.CallExpression | undefined {
1212
const expression = node.expression;
1313
const callerType = context.checker.getTypeAtLocation(expression.expression);
1414
if (getFunctionContextType(context, callerType) === ContextType.Void) {
15-
throw UnsupportedSelfFunctionConversion(node);
15+
context.diagnostics.push(unsupportedSelfFunctionConversion(node));
1616
}
1717

1818
const signature = context.checker.getResolvedSignature(node);
@@ -27,6 +27,6 @@ export function transformFunctionPrototypeCall(
2727
case "call":
2828
return transformLuaLibFunction(context, LuaLibFeature.FunctionCall, node, caller, ...params);
2929
default:
30-
throw UnsupportedProperty("function", expressionName, node);
30+
context.diagnostics.push(unsupportedProperty(expression.name, "function", expressionName));
3131
}
3232
}

src/transformation/builtins/index.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,22 @@ export function transformBuiltinPropertyAccessExpression(
2828
context: TransformationContext,
2929
node: ts.PropertyAccessExpression
3030
): lua.Expression | undefined {
31-
const type = context.checker.getTypeAtLocation(node.expression);
32-
if (isStringType(context, type)) {
31+
const ownerType = context.checker.getTypeAtLocation(node.expression);
32+
33+
if (isStringType(context, ownerType)) {
3334
return transformStringProperty(context, node);
34-
} else if (isArrayType(context, type)) {
35-
const arrayPropertyAccess = transformArrayProperty(context, node);
36-
if (arrayPropertyAccess) {
37-
return arrayPropertyAccess;
38-
}
3935
}
4036

41-
if (ts.isIdentifier(node.expression)) {
42-
const ownerType = context.checker.getTypeAtLocation(node.expression);
37+
if (isArrayType(context, ownerType)) {
38+
return transformArrayProperty(context, node);
39+
}
4340

44-
if (isStandardLibraryType(context, ownerType, "Math")) {
45-
return transformMathProperty(node);
46-
} else if (isStandardLibraryType(context, ownerType, "Symbol")) {
47-
// Pull in Symbol lib
48-
importLuaLibFeature(context, LuaLibFeature.Symbol);
41+
if (ts.isIdentifier(node.expression) && isStandardLibraryType(context, ownerType, undefined)) {
42+
switch (node.expression.text) {
43+
case "Math":
44+
return transformMathProperty(context, node);
45+
case "Symbol":
46+
importLuaLibFeature(context, LuaLibFeature.Symbol);
4947
}
5048
}
5149
}

0 commit comments

Comments
 (0)