Skip to content

Commit 5d66aa4

Browse files
authored
Error when using null or undefined in array literal (#1223)
* Error when using null or undefined in array literal * Fix prettier
1 parent 9954426 commit 5d66aa4

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

src/transformation/utils/diagnostics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,7 @@ export const unsupportedBuiltinOptionalCall = createErrorDiagnosticFactory(
155155
export const unsupportedOptionalCompileMembersOnly = createErrorDiagnosticFactory(
156156
"Optional calls are not supported on enums marked with @compileMembersOnly."
157157
);
158+
159+
export const undefinedInArrayLiteral = createErrorDiagnosticFactory(
160+
"Array literals may not contain undefined or null."
161+
);

src/transformation/visitors/literal.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ 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 { invalidMultiFunctionUse, unsupportedAccessorInObjectLiteral } from "../utils/diagnostics";
5+
import {
6+
invalidMultiFunctionUse,
7+
undefinedInArrayLiteral,
8+
unsupportedAccessorInObjectLiteral,
9+
} from "../utils/diagnostics";
610
import { createExportedIdentifier, getSymbolExportScope } from "../utils/export";
711
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
812
import { createSafeName, hasUnsafeIdentifierName, hasUnsafeSymbolName } from "../utils/safe-names";
@@ -196,6 +200,9 @@ const transformObjectLiteralExpression: FunctionVisitor<ts.ObjectLiteralExpressi
196200
};
197201

198202
const transformArrayLiteralExpression: FunctionVisitor<ts.ArrayLiteralExpression> = (expression, context) => {
203+
// Disallow using undefined/null in array literals
204+
checkForUndefinedOrNullInArrayLiteral(expression, context);
205+
199206
const filteredElements = expression.elements.map(e =>
200207
ts.isOmittedExpression(e) ? ts.factory.createIdentifier("undefined") : e
201208
);
@@ -204,6 +211,31 @@ const transformArrayLiteralExpression: FunctionVisitor<ts.ArrayLiteralExpression
204211
return lua.createTableExpression(values, expression);
205212
};
206213

214+
function checkForUndefinedOrNullInArrayLiteral(array: ts.ArrayLiteralExpression, context: TransformationContext) {
215+
// Look for last non-nil element in literal
216+
let lastNonUndefinedIndex = array.elements.length - 1;
217+
for (; lastNonUndefinedIndex >= 0; lastNonUndefinedIndex--) {
218+
if (!isUndefinedOrNull(array.elements[lastNonUndefinedIndex])) {
219+
break;
220+
}
221+
}
222+
223+
// Add diagnostics for non-trailing nil elements in array literal
224+
for (let i = 0; i < array.elements.length; i++) {
225+
if (i < lastNonUndefinedIndex && isUndefinedOrNull(array.elements[i])) {
226+
context.diagnostics.push(undefinedInArrayLiteral(array.elements[i]));
227+
}
228+
}
229+
}
230+
231+
function isUndefinedOrNull(node: ts.Node) {
232+
return (
233+
node.kind === ts.SyntaxKind.UndefinedKeyword ||
234+
node.kind === ts.SyntaxKind.NullKeyword ||
235+
(ts.isIdentifier(node) && node.text === "undefined")
236+
);
237+
}
238+
207239
export const literalVisitors: Visitors = {
208240
[ts.SyntaxKind.NullKeyword]: node => lua.createNilLiteral(node),
209241
[ts.SyntaxKind.TrueKeyword]: node => lua.createBooleanLiteral(true, node),

test/unit/builtins/array.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { undefinedInArrayLiteral } from "../../../src/transformation/utils/diagnostics";
12
import * as util from "../../util";
23

34
test("omitted expression", () => {
@@ -674,3 +675,28 @@ test("Array.isArray returns true for empty objects", () => {
674675
test("array.prototype.concat issue #738", () => {
675676
util.testExpression`([] as any[]).concat(13, 323, {x: 3}, [2, 3])`.expectToMatchJsResult();
676677
});
678+
679+
test.each(["[1, undefined, 3]", "[1, null, 3]", "[1, undefined, 2, null]"])(
680+
"not allowed to use null or undefined in array literals (%p)",
681+
literal => {
682+
util.testExpression(literal).expectToHaveDiagnostics([undefinedInArrayLiteral.code]);
683+
}
684+
);
685+
686+
test("not allowed to use null or undefined in array literals ([undefined, null, 1])", () => {
687+
util.testExpression`[undefined, null, 1]`.expectToHaveDiagnostics([
688+
undefinedInArrayLiteral.code,
689+
undefinedInArrayLiteral.code,
690+
]);
691+
});
692+
693+
test.each([
694+
"[]",
695+
"[1, 2, undefined]",
696+
"[1, 2, null]",
697+
"[1, undefined, undefined, null]",
698+
"[undefined]",
699+
"[undefined, null]",
700+
])("trailing undefined or null are allowed in array literal (%p)", literal => {
701+
util.testExpression(literal).expectToHaveNoDiagnostics();
702+
});

0 commit comments

Comments
 (0)