Skip to content

Commit ad2b224

Browse files
committed
Drop var declarations support
1 parent e238cc7 commit ad2b224

File tree

14 files changed

+170
-335
lines changed

14 files changed

+170
-335
lines changed

src/LuaAST.ts

Lines changed: 71 additions & 170 deletions
Large diffs are not rendered by default.

src/LuaPrinter.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -376,13 +376,10 @@ export class LuaPrinter {
376376
return this.createSourceNode(statement, chunks);
377377
}
378378

379-
public printIfStatement(statement: lua.IfStatement): SourceNode {
379+
public printIfStatement(statement: lua.IfStatement, isElseIf = false): SourceNode {
380380
const chunks: SourceChunk[] = [];
381381

382-
const isElseIf = statement.parent !== undefined && lua.isIfStatement(statement.parent);
383-
384382
const prefix = isElseIf ? "elseif" : "if";
385-
386383
chunks.push(this.indent(prefix + " "), this.printExpression(statement.condition), " then\n");
387384

388385
this.pushIndent();
@@ -391,7 +388,7 @@ export class LuaPrinter {
391388

392389
if (statement.elseBlock) {
393390
if (lua.isIfStatement(statement.elseBlock)) {
394-
chunks.push(this.printIfStatement(statement.elseBlock));
391+
chunks.push(this.printIfStatement(statement.elseBlock, true));
395392
} else {
396393
chunks.push(this.indent("else\n"));
397394
this.pushIndent();

src/transformation/context/context.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ export class TransformationContext {
2626
public readonly luaTarget = this.options.luaTarget || LuaTarget.LuaJIT;
2727
public readonly isModule = isFileModule(this.sourceFile);
2828
public readonly isStrict =
29-
this.options.alwaysStrict !== undefined ||
30-
(this.options.strict !== undefined && this.options.alwaysStrict !== false) ||
31-
(this.isModule && this.options.target !== undefined && this.options.target >= ts.ScriptTarget.ES2015);
29+
// TODO: ??
30+
this.options.alwaysStrict !== undefined
31+
? this.options.alwaysStrict
32+
: (this.options.strict !== undefined && this.options.alwaysStrict !== false) ||
33+
(this.isModule && this.options.target !== undefined && this.options.target >= ts.ScriptTarget.ES2015);
3234

3335
public constructor(public program: ts.Program, public sourceFile: ts.SourceFile, private visitorMap: VisitorMap) {
3436
// Use `getParseTreeNode` to get original SourceFile node, before it was substituted by custom transformers.

src/transformation/utils/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,6 @@ export const InvalidAmbientIdentifierName = (node: ts.Identifier) =>
151151

152152
export const InvalidForRangeCall = (node: ts.Node, message: string) =>
153153
new TranspileError(`Invalid @forRange call: ${message}`, node);
154+
155+
export const UnsupportedVarDeclaration = (node: ts.Node) =>
156+
new TranspileError("`var` declarations are not supported. Use `let` or `const` instead.", node);

src/transformation/utils/lua-ast.ts

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { LuaTarget } from "../../CompilerOptions";
33
import * as lua from "../../LuaAST";
44
import { TransformationContext } from "../context";
55
import { getCurrentNamespace } from "../visitors/namespace";
6-
import { UndefinedScope } from "./errors";
6+
import { UnsupportedVarDeclaration } from "./errors";
77
import { createExportedIdentifier, getIdentifierExportScope } from "./export";
88
import { findScope, peekScope, ScopeType } from "./scope";
9-
import { isFirstDeclaration, isFunctionType } from "./typescript";
9+
import { isFunctionType } from "./typescript";
1010

1111
export type OneToManyVisitorResult<T extends lua.Node> = T | T[] | undefined;
1212
export function unwrapVisitorResult<T extends lua.Node>(result: OneToManyVisitorResult<T>): T[] {
@@ -27,22 +27,6 @@ export function createExportsIdentifier(): lua.Identifier {
2727
return lua.createIdentifier("____exports");
2828
}
2929

30-
export function replaceStatementInParent(oldNode: lua.Statement, newNode?: lua.Statement): void {
31-
if (!oldNode.parent) {
32-
throw new Error("node has not yet been assigned a parent");
33-
}
34-
35-
if (lua.isBlock(oldNode.parent) || lua.isDoStatement(oldNode.parent)) {
36-
if (newNode) {
37-
oldNode.parent.statements.splice(oldNode.parent.statements.indexOf(oldNode), 1, newNode);
38-
} else {
39-
oldNode.parent.statements.splice(oldNode.parent.statements.indexOf(oldNode), 1);
40-
}
41-
} else {
42-
throw new Error("unexpected parent type");
43-
}
44-
}
45-
4630
export function createExpressionPlusOne(expression: lua.Expression): lua.Expression {
4731
if (lua.isNumericLiteral(expression)) {
4832
const newNode = lua.cloneNode(expression);
@@ -111,10 +95,9 @@ export function createHoistableVariableDeclarationStatement(
11195
context: TransformationContext,
11296
identifier: lua.Identifier,
11397
initializer?: lua.Expression,
114-
tsOriginal?: ts.Node,
115-
parent?: lua.Node
98+
tsOriginal?: ts.Node
11699
): lua.AssignmentStatement | lua.VariableDeclarationStatement {
117-
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal, parent);
100+
const declaration = lua.createVariableDeclarationStatement(identifier, initializer, tsOriginal);
118101
if (!context.options.noHoisting && identifier.symbolId) {
119102
const scope = peekScope(context);
120103
if (!scope.variableDeclarations) {
@@ -132,19 +115,26 @@ export function createLocalOrExportedOrGlobalDeclaration(
132115
lhs: lua.Identifier | lua.Identifier[],
133116
rhs?: lua.Expression | lua.Expression[],
134117
tsOriginal?: ts.Node,
135-
parent?: lua.Node,
136118
overrideExportScope?: ts.SourceFile | ts.ModuleDeclaration
137119
): lua.Statement[] {
138120
let declaration: lua.VariableDeclarationStatement | undefined;
139121
let assignment: lua.AssignmentStatement | undefined;
140122

141-
const functionDeclaration = tsOriginal && ts.isFunctionDeclaration(tsOriginal) ? tsOriginal : undefined;
123+
const isFunctionDeclaration = tsOriginal !== undefined && ts.isFunctionDeclaration(tsOriginal);
142124

143125
const identifiers = Array.isArray(lhs) ? lhs : [lhs];
144126
if (identifiers.length === 0) {
145127
return [];
146128
}
147129

130+
let isVariableDeclaration = false;
131+
if (tsOriginal && ts.isVariableDeclaration(tsOriginal)) {
132+
isVariableDeclaration = true;
133+
if (tsOriginal.parent && (tsOriginal.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0) {
134+
throw UnsupportedVarDeclaration(tsOriginal.parent);
135+
}
136+
}
137+
148138
const exportScope = overrideExportScope || getIdentifierExportScope(context, identifiers[0]);
149139
if (exportScope) {
150140
// exported
@@ -154,48 +144,31 @@ export function createLocalOrExportedOrGlobalDeclaration(
154144
assignment = lua.createAssignmentStatement(
155145
identifiers.map(identifier => createExportedIdentifier(context, identifier, exportScope)),
156146
rhs,
157-
tsOriginal,
158-
parent
147+
tsOriginal
159148
);
160149
}
161150
} else {
162151
const insideFunction = findScope(context, ScopeType.Function) !== undefined;
163-
let isLetOrConst = false;
164-
let isVariableFirstDeclaration = true; // var can have multiple declarations for the same variable :/
165-
if (tsOriginal && ts.isVariableDeclaration(tsOriginal) && tsOriginal.parent) {
166-
isLetOrConst = (tsOriginal.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0;
167-
isVariableFirstDeclaration = isLetOrConst || isFirstDeclaration(context, tsOriginal);
168-
}
169152

170-
if (
171-
(context.isModule || getCurrentNamespace(context) || insideFunction || isLetOrConst) &&
172-
isVariableFirstDeclaration
173-
) {
153+
if (context.isModule || getCurrentNamespace(context) || insideFunction || isVariableDeclaration) {
174154
// local
175155
const isPossibleWrappedFunction =
176-
!functionDeclaration &&
156+
!isFunctionDeclaration &&
177157
tsOriginal &&
178158
ts.isVariableDeclaration(tsOriginal) &&
179159
tsOriginal.initializer &&
180160
isFunctionType(context, context.checker.getTypeAtLocation(tsOriginal.initializer));
181161
if (isPossibleWrappedFunction) {
182162
// Split declaration and assignment for wrapped function types to allow recursion
183-
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal, parent);
184-
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal, parent);
163+
declaration = lua.createVariableDeclarationStatement(lhs, undefined, tsOriginal);
164+
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
185165
} else {
186-
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal, parent);
166+
declaration = lua.createVariableDeclarationStatement(lhs, rhs, tsOriginal);
187167
}
188168

189169
if (!context.options.noHoisting) {
190170
// Remember local variable declarations for hoisting later
191-
const scope =
192-
isLetOrConst || functionDeclaration
193-
? peekScope(context)
194-
: findScope(context, ScopeType.Function | ScopeType.File);
195-
196-
if (scope === undefined) {
197-
throw UndefinedScope();
198-
}
171+
const scope = peekScope(context);
199172

200173
if (!scope.variableDeclarations) {
201174
scope.variableDeclarations = [];
@@ -205,13 +178,13 @@ export function createLocalOrExportedOrGlobalDeclaration(
205178
}
206179
} else if (rhs) {
207180
// global
208-
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal, parent);
181+
assignment = lua.createAssignmentStatement(lhs, rhs, tsOriginal);
209182
} else {
210183
return [];
211184
}
212185
}
213186

214-
if (!context.options.noHoisting && functionDeclaration) {
187+
if (!context.options.noHoisting && isFunctionDeclaration) {
215188
// Remember function definitions for hoisting later
216189
const functionSymbolId = (lhs as lua.Identifier).symbolId;
217190
const scope = peekScope(context);

src/transformation/utils/scope.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import * as assert from "assert";
12
import * as ts from "typescript";
23
import * as lua from "../../LuaAST";
34
import { getOrUpdate, isNonNull } from "../../utils";
45
import { TransformationContext } from "../context";
56
import { UndefinedFunctionDefinition, UndefinedScope } from "./errors";
6-
import { replaceStatementInParent } from "./lua-ast";
77
import { getSymbolInfo } from "./symbols";
88
import { getFirstDeclarationInFile } from "./typescript";
99

@@ -168,15 +168,11 @@ function hoistVariableDeclarations(
168168
}
169169

170170
const index = result.indexOf(declaration);
171-
if (index >= 0) {
172-
if (assignment) {
173-
result.splice(index, 1, assignment);
174-
} else {
175-
result.splice(index, 1);
176-
}
171+
assert(index > -1);
172+
if (assignment) {
173+
result.splice(index, 1, assignment);
177174
} else {
178-
// Special case for 'var's declared in child scopes
179-
replaceStatementInParent(declaration, assignment);
175+
result.splice(index, 1);
180176
}
181177

182178
hoistedLocals.push(...declaration.left);

src/transformation/visitors/enum.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ export const transformEnumDeclaration: FunctionVisitor<ts.EnumDeclaration> = (no
7575
: lua.createIdentifier(member.name.getText(), member.name),
7676
valueExpression,
7777
node,
78-
undefined,
7978
exportScope
8079
)
8180
);

src/transformation/visitors/loops/for-of.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,7 @@ function transformForOfLuaIteratorStatement(
123123
// for ${initializer} in ${iterable} do
124124
const initializerVariable = statement.initializer.declarations[0].name;
125125
if (ts.isArrayBindingPattern(initializerVariable)) {
126-
const identifiers = castEach(
127-
initializerVariable.elements.map(e => transformArrayBindingElement(context, e)),
128-
lua.isIdentifier
129-
);
126+
const identifiers = initializerVariable.elements.map(e => transformArrayBindingElement(context, e));
130127
if (identifiers.length === 0) {
131128
identifiers.push(lua.createAnonymousIdentifier());
132129
}
@@ -234,7 +231,7 @@ function transformForOfIteratorStatement(
234231
// for ${initializer} in __TS__iterator(${iterator}) do
235232
return lua.createForInStatement(
236233
block,
237-
[transformIdentifier(context, statement.initializer.declarations[0].name as ts.Identifier)],
234+
[transformIdentifier(context, statement.initializer.declarations[0].name)],
238235
[transformLuaLibFunction(context, LuaLibFeature.Iterator, statement.expression, iterable)]
239236
);
240237
} else {

test/unit/assignments.spec.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
import * as util from "../util";
22

3-
test("Const assignment (%p)", () => {
3+
test("const declaration", () => {
44
const lua = util.transpileString(`const foo = true;`);
55
expect(lua).toBe(`local foo = true`);
66
});
77

8-
test("Let assignment (%p)", () => {
8+
test("let declaration", () => {
99
const lua = util.transpileString(`let foo = true;`);
1010
expect(lua).toBe(`local foo = true`);
1111
});
1212

13-
test("Var assignment (%p)", () => {
14-
const lua = util.transpileString(`var foo = true;`);
15-
expect(lua).toBe(`foo = true`);
13+
test("`var` declaration is disallowed", () => {
14+
util.testFunction`
15+
var foo = true;
16+
`.expectToHaveDiagnostics();
1617
});
1718

18-
test.each(["var myvar;", "let myvar;", "const myvar = null;", "const myvar = undefined;"])(
19-
"Null assignments (%p)",
20-
declaration => {
21-
const result = util.transpileAndExecute(declaration + " return myvar;");
22-
expect(result).toBe(undefined);
23-
}
24-
);
19+
// TODO:
20+
test.skip("`var` declaration in for...of loop is disallowed", () => {
21+
util.testFunction`
22+
for (var foo of []) {}
23+
`.expectToHaveDiagnostics();
24+
});
25+
26+
test.each(["let myvar;", "const myvar = null;", "const myvar = undefined;"])("Null assignments (%p)", declaration => {
27+
const result = util.transpileAndExecute(declaration + " return myvar;");
28+
expect(result).toBe(undefined);
29+
});
2530

2631
test.each(["x = y", "x += y"])("Assignment expressions (%p)", expression => {
2732
util.testFunction`

test/unit/builtins/map.spec.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ test("map entries", () => {
4343
const result = util.transpileAndExecute(
4444
`let mymap = new Map([[5, 2],[6, 3],[7, 4]]);
4545
let count = 0;
46-
for (var [key, value] of mymap.entries()) { count += key + value; }
46+
for (const [key, value] of mymap.entries()) { count += key + value; }
4747
return count;`
4848
);
4949
expect(result).toBe(27);
@@ -120,7 +120,7 @@ test("map keys", () => {
120120
const result = util.transpileAndExecute(
121121
`let mymap = new Map([[5, 2],[6, 3],[7, 4]]);
122122
let count = 0;
123-
for (var key of mymap.keys()) { count += key; }
123+
for (const key of mymap.keys()) { count += key; }
124124
return count;`
125125
);
126126

@@ -140,7 +140,7 @@ test("map values", () => {
140140
const result = util.transpileAndExecute(
141141
`let mymap = new Map([[5, 2],[6, 3],[7, 4]]);
142142
let count = 0;
143-
for (var value of mymap.values()) { count += value; }
143+
for (const value of mymap.values()) { count += value; }
144144
return count;`
145145
);
146146

@@ -160,14 +160,14 @@ describe.each(iterationMethods)("map.%s() preserves insertion order", iterationM
160160
test("basic", () => {
161161
util.testFunction`
162162
const mymap = new Map();
163-
163+
164164
mymap.set("x", 1);
165165
mymap.set("a", 2);
166166
mymap.set(4, 3);
167167
mymap.set("b", 6);
168168
mymap.set(1, 4);
169169
mymap.set("a", 5);
170-
170+
171171
mymap.delete("b");
172172
173173
return [...mymap.${iterationMethod}()];
@@ -177,11 +177,11 @@ describe.each(iterationMethods)("map.%s() preserves insertion order", iterationM
177177
test("after removing last", () => {
178178
util.testFunction`
179179
const mymap = new Map();
180-
180+
181181
mymap.set("x", 1);
182182
mymap.set("a", 2);
183183
mymap.set(4, 3);
184-
184+
185185
mymap.delete(4);
186186
187187
return [...mymap.${iterationMethod}()];
@@ -191,11 +191,11 @@ describe.each(iterationMethods)("map.%s() preserves insertion order", iterationM
191191
test("after removing first", () => {
192192
util.testFunction`
193193
const mymap = new Map();
194-
194+
195195
mymap.set("x", 1);
196196
mymap.set("a", 2);
197197
mymap.set(4, 3);
198-
198+
199199
mymap.delete("x");
200200
201201
return [...mymap.${iterationMethod}()];
@@ -205,10 +205,10 @@ describe.each(iterationMethods)("map.%s() preserves insertion order", iterationM
205205
test("after removing all", () => {
206206
util.testFunction`
207207
const mymap = new Map();
208-
208+
209209
mymap.set("x", 1);
210210
mymap.set("a", 2);
211-
211+
212212
mymap.delete("a");
213213
mymap.delete("x");
214214

0 commit comments

Comments
 (0)