Skip to content

Commit 05f9f50

Browse files
committed
Make annotation errors diagnostics
1 parent 95471f3 commit 05f9f50

File tree

16 files changed

+449
-204
lines changed

16 files changed

+449
-204
lines changed

src/transformation/utils/assignment-validation.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
11
import * as ts from "typescript";
22
import { getOrUpdate } from "../../utils";
33
import { TransformationContext } from "../context";
4-
import { AnnotationKind, getTypeAnnotations } from "./annotations";
54
import {
6-
ForbiddenLuaTableUseException,
75
UnsupportedNoSelfFunctionConversion,
86
UnsupportedOverloadAssignment,
97
UnsupportedSelfFunctionConversion,
108
} from "./errors";
119
import { ContextType, getFunctionContextType } from "./function-context";
1210

13-
// TODO: Make validateAssignment check symbols?
14-
// TODO: Move to LuaTable plugin?
15-
export function validatePropertyAssignment(
16-
context: TransformationContext,
17-
node: ts.AssignmentExpression<ts.EqualsToken>
18-
): void {
19-
if (!ts.isPropertyAccessExpression(node.left)) return;
20-
21-
const leftType = context.checker.getTypeAtLocation(node.left.expression);
22-
const annotations = getTypeAnnotations(context, leftType);
23-
if (annotations.has(AnnotationKind.LuaTable) && node.left.name.text === "length") {
24-
throw ForbiddenLuaTableUseException(`A LuaTable object's length cannot be re-assigned.`, node);
25-
}
26-
}
27-
2811
// TODO: Clear if types are reused between compilations
2912
const typeValidationCache = new WeakMap<ts.Type, Set<ts.Type>>();
3013

src/transformation/utils/diagnostics.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as ts from "typescript";
2+
import { AnnotationKind } from "./annotations";
23

34
const createDiagnosticFactory = <TArgs extends any[]>(
45
message: string | ((...args: TArgs) => string),
@@ -15,4 +16,48 @@ const createDiagnosticFactory = <TArgs extends any[]>(
1516

1617
export const forbiddenForIn = createDiagnosticFactory(`Iterating over arrays with 'for ... in' is not allowed.`);
1718

19+
export const annotationInvalidArgumentCount = createDiagnosticFactory(
20+
(kind: AnnotationKind, got: number, expected: number) => `'@${kind}' expects ${expected} arguments, but got ${got}.`
21+
);
22+
23+
export const extensionCannotConstruct = createDiagnosticFactory(
24+
"Cannot construct classes with '@extension' or '@metaExtension' annotation."
25+
);
26+
27+
export const extensionCannotExtend = createDiagnosticFactory(
28+
`Cannot extend classes with '@extension' or '@metaExtension' annotation.`
29+
);
30+
31+
export const extensionCannotExport = createDiagnosticFactory(
32+
`Cannot export classes with '@extension' or '@metaExtension' annotation.`
33+
);
34+
35+
export const extensionInvalidInstanceOf = createDiagnosticFactory(
36+
`Cannot use instanceof on classes with '@extension' or '@metaExtension' annotation.`
37+
);
38+
39+
export const extensionAndMetaExtensionConflict = createDiagnosticFactory(
40+
`Cannot use both '@extension' and '@metaExtension' annotations on the same class.`
41+
);
42+
43+
export const metaExtensionMissingExtends = createDiagnosticFactory(
44+
`'@metaExtension' annotation requires the extension of the metatable class.`
45+
);
46+
1847
export const invalidForRangeCall = createDiagnosticFactory((message: string) => `Invalid @forRange call: ${message}.`);
48+
49+
export const luaTableMustBeAmbient = createDiagnosticFactory(
50+
"Classes with the '@luaTable' annotation must be ambient."
51+
);
52+
53+
export const luaTableCannotBeExtended = createDiagnosticFactory(
54+
"Cannot extend classes with the '@luaTable' annotation."
55+
);
56+
57+
export const luaTableInvalidInstanceOf = createDiagnosticFactory(
58+
"The instanceof operator cannot be used with a '@luaTable' class."
59+
);
60+
61+
export const luaTableForbiddenUsage = createDiagnosticFactory(
62+
(description: string) => `Invalid @luaTable usage: ${description}.`
63+
);

src/transformation/utils/errors.ts

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,12 @@ export class TranspileError extends Error {
1010

1111
const getLuaTargetName = (version: LuaTarget) => (version === LuaTarget.LuaJIT ? "LuaJIT" : `Lua ${version}`);
1212

13-
export const ForbiddenLuaTableNonDeclaration = (node: ts.Node) =>
14-
new TranspileError(`Classes with the '@luaTable' annotation must be declared.`, node);
15-
16-
export const InvalidExtendsLuaTable = (node: ts.Node) =>
17-
new TranspileError(`Cannot extend classes with the '@luaTable' annotation.`, node);
18-
19-
export const InvalidInstanceOfLuaTable = (node: ts.Node) =>
20-
new TranspileError(`The instanceof operator cannot be used with a '@luaTable' class.`, node);
21-
22-
export const ForbiddenLuaTableUseException = (description: string, node: ts.Node) =>
23-
new TranspileError(`Invalid @luaTable usage: ${description}`, node);
24-
25-
export const InvalidAnnotationArgumentNumber = (name: string, got: number, expected: number, node: ts.Node) =>
26-
new TranspileError(`'${name}' expects ${expected} argument(s) but got ${got}.`, node);
27-
2813
export const InvalidDecoratorContext = (node: ts.Node) =>
2914
new TranspileError(`Decorator function cannot have 'this: void'.`, node);
3015

31-
export const InvalidExtensionMetaExtension = (node: ts.Node) =>
32-
new TranspileError(`Cannot use both '@extension' and '@metaExtension' annotations on the same class.`, node);
33-
34-
export const InvalidNewExpressionOnExtension = (node: ts.Node) =>
35-
new TranspileError(`Cannot construct classes with '@extension' or '@metaExtension' annotation.`, node);
36-
37-
export const InvalidExtendsExtension = (node: ts.Node) =>
38-
new TranspileError(`Cannot extend classes with '@extension' or '@metaExtension' annotation.`, node);
39-
40-
export const InvalidExportsExtension = (node: ts.Node) =>
41-
new TranspileError(`Cannot export classes with '@extension' or '@metaExtension' annotation.`, node);
42-
43-
export const InvalidInstanceOfExtension = (node: ts.Node) =>
44-
new TranspileError(`Cannot use instanceof on classes with '@extension' or '@metaExtension' annotation.`, node);
45-
4616
export const MissingForOfVariables = (node: ts.Node) =>
4717
new TranspileError("Transpiled ForOf variable declaration list contains no declarations.", node);
4818

49-
export const MissingMetaExtension = (node: ts.Node) =>
50-
new TranspileError(`'@metaExtension' annotation requires the extension of the metatable class.`, node);
51-
5219
export const UnsupportedForInVariable = (node: ts.Node) =>
5320
new TranspileError(`Unsupported for-in variable kind.`, node);
5421

src/transformation/visitors/binary-expression/assignments.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import * as lua from "../../../LuaAST";
33
import { cast, castEach } from "../../../utils";
44
import { TransformationContext } from "../../context";
55
import { isTupleReturnCall } from "../../utils/annotations";
6-
import { validateAssignment, validatePropertyAssignment } from "../../utils/assignment-validation";
6+
import { validateAssignment } from "../../utils/assignment-validation";
77
import { createImmediatelyInvokedFunctionExpression, createUnpackCall, wrapInTable } from "../../utils/lua-ast";
88
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
99
import { isArrayType, isDestructuringAssignment } from "../../utils/typescript";
1010
import { transformElementAccessArgument } from "../access";
11+
import { transformLuaTablePropertyAccessInAssignment } from "../lua-table";
1112
import { isArrayLength, transformDestructuringAssignment } from "./destructuring-assignments";
1213

1314
export function transformAssignment(
@@ -29,17 +30,22 @@ export function transformAssignment(
2930
);
3031
}
3132

32-
return lua.createAssignmentStatement(
33-
cast(context.transformExpression(lhs), lua.isAssignmentLeftHandSideExpression),
34-
right,
35-
lhs.parent
36-
);
33+
let left: lua.AssignmentLeftHandSideExpression | undefined;
34+
if (ts.isPropertyAccessExpression(lhs)) {
35+
left = transformLuaTablePropertyAccessInAssignment(context, lhs);
36+
}
37+
38+
if (!left) {
39+
left = cast(context.transformExpression(lhs), lua.isAssignmentLeftHandSideExpression);
40+
}
41+
42+
return lua.createAssignmentStatement(left, right, parent);
3743
}
3844

3945
export function transformAssignmentExpression(
4046
context: TransformationContext,
4147
expression: ts.AssignmentExpression<ts.EqualsToken>
42-
): lua.CallExpression | lua.MethodCallExpression {
48+
): lua.Expression {
4349
// Validate assignment
4450
const rightType = context.checker.getTypeAtLocation(expression.right);
4551
const leftType = context.checker.getTypeAtLocation(expression.left);
@@ -91,6 +97,9 @@ export function transformAssignmentExpression(
9197
const objExpression = context.transformExpression(expression.left.expression);
9298
let indexExpression: lua.Expression;
9399
if (ts.isPropertyAccessExpression(expression.left)) {
100+
// Called only for validation
101+
transformLuaTablePropertyAccessInAssignment(context, expression.left);
102+
94103
// Property access
95104
indexExpression = lua.createStringLiteral(expression.left.name.text);
96105
} else {
@@ -121,7 +130,6 @@ export function transformAssignmentStatement(
121130
const rightType = context.checker.getTypeAtLocation(expression.right);
122131
const leftType = context.checker.getTypeAtLocation(expression.left);
123132
validateAssignment(context, expression.right, rightType, leftType);
124-
validatePropertyAssignment(context, expression);
125133

126134
if (isDestructuringAssignment(expression)) {
127135
if (

src/transformation/visitors/binary-expression/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
33
import { FunctionVisitor, TransformationContext } from "../../context";
44
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
5-
import { InvalidInstanceOfExtension, InvalidInstanceOfLuaTable, UnsupportedKind } from "../../utils/errors";
5+
import { extensionInvalidInstanceOf, luaTableInvalidInstanceOf } from "../../utils/diagnostics";
6+
import { UnsupportedKind } from "../../utils/errors";
67
import { createImmediatelyInvokedFunctionExpression, wrapInToStringForConcat } from "../../utils/lua-ast";
78
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
89
import { isStandardLibraryType, isStringType } from "../../utils/typescript";
@@ -181,12 +182,11 @@ export const transformBinaryExpression: FunctionVisitor<ts.BinaryExpression> = (
181182
const annotations = getTypeAnnotations(context, rhsType);
182183

183184
if (annotations.has(AnnotationKind.Extension) || annotations.has(AnnotationKind.MetaExtension)) {
184-
// Cannot use instanceof on extension classes
185-
throw InvalidInstanceOfExtension(node);
185+
context.diagnostics.push(extensionInvalidInstanceOf(node));
186186
}
187187

188188
if (annotations.has(AnnotationKind.LuaTable)) {
189-
throw InvalidInstanceOfLuaTable(node);
189+
context.diagnostics.push(luaTableInvalidInstanceOf(node));
190190
}
191191

192192
if (isStandardLibraryType(context, rhsType, "ObjectConstructor")) {

src/transformation/visitors/class/index.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { assert, getOrUpdate, isNonNull } from "../../../utils";
44
import { FunctionVisitor, TransformationContext } from "../../context";
55
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
66
import {
7-
ForbiddenLuaTableNonDeclaration,
8-
InvalidExportsExtension,
9-
InvalidExtendsExtension,
10-
InvalidExtendsLuaTable,
11-
InvalidExtensionMetaExtension,
12-
MissingMetaExtension,
13-
} from "../../utils/errors";
7+
extensionAndMetaExtensionConflict,
8+
extensionCannotExport,
9+
extensionCannotExtend,
10+
metaExtensionMissingExtends,
11+
luaTableMustBeAmbient,
12+
luaTableCannotBeExtended,
13+
} from "../../utils/diagnostics";
1414
import {
1515
createDefaultExportIdentifier,
1616
createExportedIdentifier,
@@ -96,15 +96,16 @@ export function transformClassDeclaration(
9696
const isMetaExtension = annotations.has(AnnotationKind.MetaExtension);
9797

9898
if (isExtension && isMetaExtension) {
99-
throw InvalidExtensionMetaExtension(classDeclaration);
99+
context.diagnostics.push(extensionAndMetaExtensionConflict(classDeclaration));
100100
}
101101

102102
if ((isExtension || isMetaExtension) && getIdentifierExportScope(context, className) !== undefined) {
103103
// Cannot export extension classes
104-
throw InvalidExportsExtension(classDeclaration);
104+
context.diagnostics.push(extensionCannotExport(classDeclaration));
105105
}
106106

107107
// Get type that is extended
108+
const extendsTypeNode = getExtendedTypeNode(context, classDeclaration);
108109
const extendsType = getExtendedType(context, classDeclaration);
109110

110111
if (extendsType) {
@@ -115,21 +116,20 @@ export function transformClassDeclaration(
115116
// Non-extensions cannot extend extension classes
116117
const extendsAnnotations = getTypeAnnotations(context, extendsType);
117118
if (extendsAnnotations.has(AnnotationKind.Extension) || extendsAnnotations.has(AnnotationKind.MetaExtension)) {
118-
throw InvalidExtendsExtension(classDeclaration);
119+
context.diagnostics.push(extensionCannotExtend(classDeclaration));
119120
}
120121
}
121122

122123
// You cannot extend LuaTable classes
123124
if (extendsType) {
124125
const annotations = getTypeAnnotations(context, extendsType);
125126
if (annotations.has(AnnotationKind.LuaTable)) {
126-
throw InvalidExtendsLuaTable(classDeclaration);
127+
context.diagnostics.push(luaTableCannotBeExtended(extendsTypeNode!));
127128
}
128129
}
129130

130-
// LuaTable classes must be ambient
131131
if (annotations.has(AnnotationKind.LuaTable) && !isAmbientNode(classDeclaration)) {
132-
throw ForbiddenLuaTableNonDeclaration(classDeclaration);
132+
context.diagnostics.push(luaTableMustBeAmbient(classDeclaration));
133133
}
134134

135135
// Get all properties with value
@@ -143,30 +143,30 @@ export function transformClassDeclaration(
143143

144144
// Overwrite the original className with the class we are overriding for extensions
145145
if (isMetaExtension) {
146-
if (!extendsType) {
147-
throw MissingMetaExtension(classDeclaration);
148-
}
149-
150-
const extendsName = lua.createStringLiteral(extendsType.symbol.name as string);
151-
className = lua.createIdentifier("__meta__" + extendsName.value);
152-
153-
// local className = debug.getregistry()["extendsName"]
154-
const assignDebugCallIndex = lua.createVariableDeclarationStatement(
155-
className,
156-
lua.createTableIndexExpression(
157-
lua.createCallExpression(
158-
lua.createTableIndexExpression(
159-
lua.createIdentifier("debug"),
160-
lua.createStringLiteral("getregistry")
146+
if (extendsType) {
147+
const extendsName = lua.createStringLiteral(extendsType.symbol.name);
148+
className = lua.createIdentifier("__meta__" + extendsName.value);
149+
150+
// local className = debug.getregistry()["extendsName"]
151+
const assignDebugCallIndex = lua.createVariableDeclarationStatement(
152+
className,
153+
lua.createTableIndexExpression(
154+
lua.createCallExpression(
155+
lua.createTableIndexExpression(
156+
lua.createIdentifier("debug"),
157+
lua.createStringLiteral("getregistry")
158+
),
159+
[]
161160
),
162-
[]
161+
extendsName
163162
),
164-
extendsName
165-
),
166-
classDeclaration
167-
);
163+
classDeclaration
164+
);
168165

169-
result.push(assignDebugCallIndex);
166+
result.push(assignDebugCallIndex);
167+
} else {
168+
context.diagnostics.push(metaExtensionMissingExtends(classDeclaration));
169+
}
170170
}
171171

172172
if (extensionDirective !== undefined) {

src/transformation/visitors/class/new.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ts from "typescript";
22
import * as lua from "../../../LuaAST";
33
import { FunctionVisitor, TransformationContext } from "../../context";
44
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
5-
import { InvalidAnnotationArgumentNumber, InvalidNewExpressionOnExtension } from "../../utils/errors";
5+
import { annotationInvalidArgumentCount, extensionCannotConstruct } from "../../utils/diagnostics";
66
import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
77
import { transformArguments } from "../call";
88
import { transformLuaTableNewExpression } from "../lua-table";
@@ -66,20 +66,27 @@ export const transformNewExpression: FunctionVisitor<ts.NewExpression> = (node,
6666
const annotations = getTypeAnnotations(context, type);
6767

6868
if (annotations.has(AnnotationKind.Extension) || annotations.has(AnnotationKind.MetaExtension)) {
69-
throw InvalidNewExpressionOnExtension(node);
69+
context.diagnostics.push(extensionCannotConstruct(node));
7070
}
7171

7272
const customConstructorAnnotation = annotations.get(AnnotationKind.CustomConstructor);
7373
if (customConstructorAnnotation) {
74-
if (customConstructorAnnotation.args[0] === undefined) {
75-
throw InvalidAnnotationArgumentNumber("@customConstructor", 0, 1, node);
74+
if (customConstructorAnnotation.args.length === 1) {
75+
return lua.createCallExpression(
76+
lua.createIdentifier(customConstructorAnnotation.args[0]),
77+
transformArguments(context, node.arguments ?? []),
78+
node
79+
);
80+
} else {
81+
context.diagnostics.push(
82+
annotationInvalidArgumentCount(
83+
node,
84+
AnnotationKind.CustomConstructor,
85+
customConstructorAnnotation.args.length,
86+
1
87+
)
88+
);
7689
}
77-
78-
return lua.createCallExpression(
79-
lua.createIdentifier(customConstructorAnnotation.args[0]),
80-
transformArguments(context, node.arguments ?? []),
81-
node
82-
);
8390
}
8491

8592
return transformLuaLibFunction(context, LuaLibFeature.New, node, name, ...params);

0 commit comments

Comments
 (0)