Skip to content

Commit 4c1ea0d

Browse files
committed
Improve multi diagnostics
1 parent 1a44b55 commit 4c1ea0d

6 files changed

Lines changed: 111 additions & 49 deletions

File tree

src/transformation/utils/diagnostics.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,27 @@ export const unsupportedVarDeclaration = createDiagnosticFactory(
127127

128128
export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`);
129129

130-
export const invalidMultiHelperFunctionUse = createDiagnosticFactory("Invalid $multi() function use.");
130+
export const unsupportedMultiHelperFunctionPosition = createDiagnosticFactory(
131+
"The $multi() function cannot be used here."
132+
);
133+
134+
export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory(
135+
"Expected an array destructuring pattern."
136+
);
137+
138+
export const invalidMultiReturnToNonArrayLiteral = createDiagnosticFactory("Expected an array literal.");
139+
140+
export const invalidMultiReturnToEmptyPatternOrArrayLiteral = createDiagnosticFactory(
141+
"There must be one or more elements specified here."
142+
);
143+
144+
export const invalidMultiReturnArrayBindingPatternElementInitializer = createDiagnosticFactory(
145+
"This array binding pattern cannot have initializers."
146+
);
147+
148+
export const invalidMultiReturnArrayLiteralElementInitializer = createDiagnosticFactory(
149+
"This array literal pattern cannot have initializers."
150+
);
131151

132152
export const unsupportedMultiFunctionAssignment = createDiagnosticFactory(
133153
"Omitted expressions and BindingElements are expected here."

src/transformation/visitors/helpers/multi.ts

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import { transformArguments } from "../call";
99
import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export";
1010
import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast";
1111
import {
12-
invalidMultiHelperFunctionUse,
12+
invalidMultiReturnArrayBindingPatternElementInitializer,
13+
invalidMultiReturnArrayLiteralElementInitializer,
14+
invalidMultiReturnToEmptyPatternOrArrayLiteral,
15+
invalidMultiReturnToNonArrayBindingPattern,
16+
invalidMultiReturnToNonArrayLiteral,
1317
unsupportedMultiFunctionAssignment,
18+
unsupportedMultiHelperFunctionPosition,
1419
} from "../../../transformation/utils/diagnostics";
1520

1621
const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => {
@@ -52,11 +57,15 @@ function transformMultiHelperCallArguments(
5257
context: TransformationContext,
5358
expression: ts.CallExpression
5459
): lua.Expression[] | lua.Expression {
55-
return isMultiHelperCallSignature(context, expression)
56-
? expression.arguments.length > 0
57-
? expression.arguments.map(e => context.transformExpression(e))
58-
: lua.createNilLiteral(expression)
59-
: context.transformExpression(expression);
60+
if (!isMultiHelperCallSignature(context, expression)) {
61+
return context.transformExpression(expression);
62+
}
63+
64+
if (expression.arguments.length < 1) {
65+
return lua.createNilLiteral(expression);
66+
}
67+
68+
return expression.arguments.map(e => context.transformExpression(e));
6069
}
6170

6271
export function transformMultiHelperVariableDeclaration(
@@ -66,24 +75,41 @@ export function transformMultiHelperVariableDeclaration(
6675
if (!declaration.initializer) return;
6776
if (!isMultiReturnCall(context, declaration.initializer)) return;
6877

69-
if (!ts.isArrayBindingPattern(declaration.name) || declaration.name.elements.length < 1) {
70-
context.diagnostics.push(invalidMultiHelperFunctionUse(declaration.name));
78+
if (!ts.isArrayBindingPattern(declaration.name)) {
79+
context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name));
7180
return [];
7281
}
7382

74-
const leftIdentifiers = declaration.name.elements
75-
.map(element => {
76-
if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) {
77-
return transformIdentifier(context, element.name);
78-
}
83+
if (declaration.name.elements.length < 1) {
84+
context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(declaration.name));
85+
return [];
86+
}
87+
88+
const leftIdentifiers: lua.Identifier[] = [];
7989

80-
if (ts.isOmittedExpression(element)) {
81-
return lua.createAnonymousIdentifier(element);
90+
for (const element of declaration.name.elements) {
91+
let expression: lua.Identifier | undefined;
92+
93+
if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) {
94+
if (element.initializer) {
95+
context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element));
96+
continue;
97+
} else {
98+
expression = transformIdentifier(context, element.name);
8299
}
100+
}
101+
102+
if (ts.isOmittedExpression(element)) {
103+
expression = lua.createAnonymousIdentifier(element);
104+
}
83105

106+
if (!expression) {
84107
context.diagnostics.push(unsupportedMultiFunctionAssignment(element));
85-
})
86-
.filter(isNonNull);
108+
continue;
109+
}
110+
111+
leftIdentifiers.push(expression);
112+
}
87113

88114
const rightExpressions = transformMultiHelperCallArguments(context, declaration.initializer);
89115
return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration);
@@ -97,12 +123,18 @@ export function transformMultiHelperDestructuringAssignmentStatement(
97123
if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return;
98124
if (!isMultiReturnCall(context, statement.expression.right)) return;
99125

100-
if (
101-
!ts.isArrayLiteralExpression(statement.expression.left) ||
102-
statement.expression.left.elements.length < 1 ||
103-
statement.expression.left.elements.some(ts.isBinaryExpression)
104-
) {
105-
context.diagnostics.push(invalidMultiHelperFunctionUse(statement.expression.left));
126+
if (!ts.isArrayLiteralExpression(statement.expression.left)) {
127+
context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left));
128+
return [];
129+
}
130+
131+
if (statement.expression.left.elements.some(ts.isBinaryExpression)) {
132+
context.diagnostics.push(invalidMultiReturnArrayLiteralElementInitializer(statement.expression.left));
133+
return [];
134+
}
135+
136+
if (statement.expression.left.elements.length < 1) {
137+
context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(statement.expression.left));
106138
return [];
107139
}
108140

@@ -141,7 +173,7 @@ export function findMultiHelperAssignmentViolations(
141173
if (valueSymbol) {
142174
const declaration = valueSymbol.valueDeclaration;
143175
if (declaration && isMultiHelperDeclaration(context)(declaration)) {
144-
context.diagnostics.push(invalidMultiHelperFunctionUse(element));
176+
context.diagnostics.push(unsupportedMultiHelperFunctionPosition(element));
145177
return element;
146178
}
147179
}

src/transformation/visitors/identifier.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as lua from "../../LuaAST";
33
import { transformBuiltinIdentifierExpression } from "../builtins";
44
import { FunctionVisitor, TransformationContext } from "../context";
55
import { isForRangeType } from "../utils/annotations";
6-
import { invalidForRangeCall, invalidMultiHelperFunctionUse } from "../utils/diagnostics";
6+
import { invalidForRangeCall, unsupportedMultiHelperFunctionPosition } from "../utils/diagnostics";
77
import { createExportedIdentifier, getSymbolExportScope } from "../utils/export";
88
import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names";
99
import { getIdentifierSymbolId } from "../utils/symbols";
@@ -12,7 +12,7 @@ import { isMultiHelperNode } from "./helpers/multi";
1212

1313
export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier {
1414
if (isMultiHelperNode(context, identifier)) {
15-
context.diagnostics.push(invalidMultiHelperFunctionUse(identifier));
15+
context.diagnostics.push(unsupportedMultiHelperFunctionPosition(identifier));
1616
return lua.createAnonymousIdentifier(identifier);
1717
}
1818

src/transformation/visitors/literal.ts

Lines changed: 2 additions & 2 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 { assertNever } from "../../utils";
44
import { FunctionVisitor, TransformationContext, Visitors } from "../context";
5-
import { unsupportedAccessorInObjectLiteral, invalidMultiHelperFunctionUse } from "../utils/diagnostics";
5+
import { unsupportedAccessorInObjectLiteral, unsupportedMultiHelperFunctionPosition } from "../utils/diagnostics";
66
import { createExportedIdentifier, getSymbolExportScope } from "../utils/export";
77
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
88
import { createSafeName, hasUnsafeIdentifierName, hasUnsafeSymbolName } from "../utils/safe-names";
@@ -66,7 +66,7 @@ const transformObjectLiteralExpression: FunctionVisitor<ts.ObjectLiteralExpressi
6666
const violations = findMultiHelperAssignmentViolations(context, expression);
6767
if (violations.length > 0) {
6868
violations.forEach(element => {
69-
context.diagnostics.push(invalidMultiHelperFunctionUse(element));
69+
context.diagnostics.push(unsupportedMultiHelperFunctionPosition(element));
7070
});
7171
return lua.createNilLiteral(expression);
7272
}

test/unit/helpers/__snapshots__/multi.spec.ts.snap

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`;
44

5-
exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`;
5+
exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`;
66

77
exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`;
88

9-
exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`;
9+
exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`;
1010

1111
exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = `
1212
"local function ____(____, ____bindingPattern0)
@@ -17,24 +17,28 @@ exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = `
1717
end"
1818
`;
1919

20-
exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: Invalid $multi() function use."`;
20+
exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function cannot be used here."`;
2121

2222
exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`;
2323

24-
exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: Invalid $multi() function use."`;
24+
exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function cannot be used here."`;
2525

2626
exports[`invalid $multi call ([] = $multi()): code 1`] = `""`;
2727

28-
exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`;
28+
exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: There must be one or more elements specified here."`;
2929

3030
exports[`invalid $multi call (const [] = $multi();): code 1`] = `""`;
3131

32-
exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`;
32+
exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: There must be one or more elements specified here."`;
33+
34+
exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`;
35+
36+
exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,16): error TSTL: This array binding pattern cannot have initializers."`;
3337

3438
exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`;
3539

36-
exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`;
40+
exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`;
3741

3842
exports[`invalid $multi call (const a = $multi();): code 1`] = `""`;
3943

40-
exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`;
44+
exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`;

test/unit/helpers/multi.spec.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import * as path from "path";
22
import * as util from "../../util";
33
import * as tstl from "../../../src";
4-
import { invalidMultiHelperFunctionUse } from "../../../src/transformation/utils/diagnostics";
4+
import {
5+
unsupportedMultiHelperFunctionPosition,
6+
invalidMultiReturnToNonArrayBindingPattern,
7+
invalidMultiReturnArrayBindingPatternElementInitializer,
8+
invalidMultiReturnToEmptyPatternOrArrayLiteral,
9+
} from "../../../src/transformation/utils/diagnostics";
510

611
const multiProjectOptions: tstl.CompilerOptions = {
712
types: [path.resolve(__dirname, "../../../helpers")],
@@ -25,21 +30,22 @@ test.each<[string, any]>([
2530
.expectToEqual(result);
2631
});
2732

28-
test.each([
29-
"$multi",
30-
"$multi()",
31-
"({ $multi });",
32-
"[] = $multi()",
33-
"const [] = $multi();",
34-
"const a = $multi();",
35-
"const {} = $multi();",
36-
"([a] = $multi(1)) => {}",
37-
])("invalid $multi call (%s)", statement => {
33+
test.each<[string, number[]]>([
34+
["$multi", [unsupportedMultiHelperFunctionPosition.code]],
35+
["$multi()", [unsupportedMultiHelperFunctionPosition.code]],
36+
["({ $multi });", [unsupportedMultiHelperFunctionPosition.code]],
37+
["[] = $multi()", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]],
38+
["const [] = $multi();", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]],
39+
["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]],
40+
["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]],
41+
["([a] = $multi(1)) => {}", [unsupportedMultiHelperFunctionPosition.code]],
42+
["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]],
43+
])("invalid $multi call (%s)", (statement, diagnostics) => {
3844
util.testModule`
3945
${statement}
4046
`
4147
.setOptions(multiProjectOptions)
42-
.expectDiagnosticsToMatchSnapshot([invalidMultiHelperFunctionUse.code]);
48+
.expectDiagnosticsToMatchSnapshot(diagnostics);
4349
});
4450

4551
test.each<[string, any]>([

0 commit comments

Comments
 (0)