Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 283 additions & 50 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"dependencies": {
"resolve": "^1.15.1",
"source-map": "^0.7.3",
"typescript": "^3.9.2"
"typescript": ">=4.0.2"
},
"devDependencies": {
"@types/fs-extra": "^8.1.0",
Expand All @@ -47,7 +47,7 @@
"@types/node": "^13.7.7",
"@types/resolve": "1.14.0",
"@typescript-eslint/eslint-plugin": "^2.31.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin version should match the parser, otherwise some rules might handle AST incorrectly. Though if it works okay with current codebase, and there would be some rules with changed configuration format, I think it's fine to keep it that way for now. I could update it to match my base config later

"@typescript-eslint/parser": "^2.31.0",
"@typescript-eslint/parser": "^4.1.0",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jest": "^23.8.2",
Expand All @@ -58,7 +58,7 @@
"jest-circus": "^25.1.0",
"lua-types": "^2.8.0",
"prettier": "^2.0.5",
"ts-jest": "^26.0.0",
"ts-jest": "^26.3.0",
"ts-node": "^8.6.2"
}
}
4 changes: 2 additions & 2 deletions src/transformation/utils/assignment-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export function validateAssignment(

validateFunctionAssignment(context, node, fromType, toType, toName);

const fromTypeNode = context.checker.typeToTypeNode(fromType);
const toTypeNode = context.checker.typeToTypeNode(toType);
const fromTypeNode = context.checker.typeToTypeNode(fromType, undefined, undefined);
const toTypeNode = context.checker.typeToTypeNode(toType, undefined, undefined);
if (!fromTypeNode || !toTypeNode) {
return;
}
Expand Down
7 changes: 4 additions & 3 deletions src/transformation/visitors/binary-expression/compound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type CompoundAssignmentToken =
| ts.SyntaxKind.GreaterThanGreaterThanToken
| ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken;

const compoundToAssignmentTokens: Record<ts.CompoundAssignmentOperator, CompoundAssignmentToken> = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which CompoundAssignmentOperator do we not support now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??=, &&=, ||=

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this PR we should either create an issue to support these compound operators later, or just revert the Partial/ | undefined changes and add them here, but then we also need some tests for them. I'm leaving it up to @hazzard993 to decide.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #916 to complete this later

const compoundToAssignmentTokens: Partial<Record<ts.CompoundAssignmentOperator, CompoundAssignmentToken>> = {
[ts.SyntaxKind.BarEqualsToken]: ts.SyntaxKind.BarToken,
[ts.SyntaxKind.PlusEqualsToken]: ts.SyntaxKind.PlusToken,
[ts.SyntaxKind.CaretEqualsToken]: ts.SyntaxKind.CaretToken,
Expand All @@ -66,8 +66,9 @@ const compoundToAssignmentTokens: Record<ts.CompoundAssignmentOperator, Compound
export const isCompoundAssignmentToken = (token: ts.BinaryOperator): token is ts.CompoundAssignmentOperator =>
token in compoundToAssignmentTokens;

export const unwrapCompoundAssignmentToken = (token: ts.CompoundAssignmentOperator): CompoundAssignmentToken =>
compoundToAssignmentTokens[token];
export const unwrapCompoundAssignmentToken = (
token: ts.CompoundAssignmentOperator
): CompoundAssignmentToken | undefined => compoundToAssignmentTokens[token];

export function transformCompoundAssignmentExpression(
context: TransformationContext,
Expand Down
31 changes: 15 additions & 16 deletions src/transformation/visitors/binary-expression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as ts from "typescript";
import * as lua from "../../../LuaAST";
import { FunctionVisitor, TransformationContext } from "../../context";
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
import { extensionInvalidInstanceOf, luaTableInvalidInstanceOf } from "../../utils/diagnostics";
import { extensionInvalidInstanceOf, luaTableInvalidInstanceOf, unsupportedNodeKind } from "../../utils/diagnostics";
import { createImmediatelyInvokedFunctionExpression, wrapInToStringForConcat } from "../../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
import { isStandardLibraryType, isStringType, typeCanSatisfy } from "../../utils/typescript";
Expand Down Expand Up @@ -77,14 +77,13 @@ export const transformBinaryExpression: FunctionVisitor<ts.BinaryExpression> = (
}

if (isCompoundAssignmentToken(operator)) {
return transformCompoundAssignmentExpression(
context,
node,
node.left,
node.right,
unwrapCompoundAssignmentToken(operator),
false
);
const token = unwrapCompoundAssignmentToken(operator);
if (!token) {
context.diagnostics.push(unsupportedNodeKind(node, operator));
return lua.createNilLiteral(node);
}

return transformCompoundAssignmentExpression(context, node, node.left, node.right, token, false);
}

switch (operator) {
Expand Down Expand Up @@ -157,13 +156,13 @@ export function transformBinaryExpressionStatement(

if (isCompoundAssignmentToken(operator)) {
// +=, -=, etc...
return transformCompoundAssignmentStatement(
context,
expression,
expression.left,
expression.right,
unwrapCompoundAssignmentToken(operator)
);
const token = unwrapCompoundAssignmentToken(operator);
if (!token) {
context.diagnostics.push(unsupportedNodeKind(node, operator));
return;
}

return transformCompoundAssignmentStatement(context, expression, expression.left, expression.right, token);
} else if (operator === ts.SyntaxKind.EqualsToken) {
return transformAssignmentStatement(context, expression as ts.AssignmentExpression<ts.EqualsToken>);
} else if (operator === ts.SyntaxKind.CommaToken) {
Expand Down
4 changes: 2 additions & 2 deletions src/transformation/visitors/class/members/constructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as lua from "../../../../LuaAST";
import { TransformationContext } from "../../../context";
import { createSelfIdentifier } from "../../../utils/lua-ast";
import { popScope, pushScope, ScopeType } from "../../../utils/scope";
import { transformFunctionBodyHeader, transformFunctionBodyStatements, transformParameters } from "../../function";
import { transformFunctionBodyHeader, transformFunctionBodyContent, transformParameters } from "../../function";
import { transformIdentifier } from "../../identifier";
import { transformClassInstanceFields } from "./fields";

Expand All @@ -28,7 +28,7 @@ export function transformConstructorDeclaration(

// Transform body
const scope = pushScope(context, ScopeType.Function);
const body = transformFunctionBodyStatements(context, statement.body);
const body = transformFunctionBodyContent(context, statement.body);

const [params, dotsLiteral, restParamName] = transformParameters(
context,
Expand Down
29 changes: 15 additions & 14 deletions src/transformation/visitors/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { peekScope, performHoisting, popScope, pushScope, Scope, ScopeType } from "../utils/scope";
import { transformIdentifier } from "./identifier";
import { transformExpressionBodyToReturnStatement } from "./return";
import { transformBindingPattern } from "./variable-declaration";

function transformParameterDefaultValueDeclaration(
Expand Down Expand Up @@ -52,7 +53,12 @@ function isRestParameterReferenced(context: TransformationContext, identifier: l
return references.some(r => !r.parent || !ts.isSpreadElement(r.parent) || !isVarargType(context, r));
}

export function transformFunctionBodyStatements(context: TransformationContext, body: ts.Block): lua.Statement[] {
export function transformFunctionBodyContent(context: TransformationContext, body: ts.ConciseBody): lua.Statement[] {
if (!ts.isBlock(body)) {
const returnStatement = transformExpressionBodyToReturnStatement(context, body);
return [returnStatement];
}

const bodyStatements = performHoisting(context, context.transformStatements(body.statements));
return bodyStatements;
}
Expand Down Expand Up @@ -107,11 +113,11 @@ export function transformFunctionBodyHeader(
export function transformFunctionBody(
context: TransformationContext,
parameters: ts.NodeArray<ts.ParameterDeclaration>,
body: ts.Block,
body: ts.ConciseBody,
spreadIdentifier?: lua.Identifier
): [lua.Statement[], Scope] {
const scope = pushScope(context, ScopeType.Function);
const bodyStatements = transformFunctionBodyStatements(context, body);
const bodyStatements = transformFunctionBodyContent(context, body);
const headerStatements = transformFunctionBodyHeader(context, scope, parameters, spreadIdentifier);
popScope(context);
return [[...headerStatements, ...bodyStatements], scope];
Expand Down Expand Up @@ -184,18 +190,13 @@ export function transformFunctionToExpression(
flags |= lua.FunctionExpressionFlags.Declaration;
}

let body: ts.Block;
if (ts.isBlock(node.body)) {
body = node.body;
} else {
const returnExpression = ts.createReturn(node.body);
body = ts.createBlock([returnExpression]);
returnExpression.parent = body;
if (node.body) body.parent = node.body.parent;
}

const [paramNames, dotsLiteral, spreadIdentifier] = transformParameters(context, node.parameters, functionContext);
const [transformedBody, functionScope] = transformFunctionBody(context, node.parameters, body, spreadIdentifier);
const [transformedBody, functionScope] = transformFunctionBody(
context,
node.parameters,
node.body,
spreadIdentifier
);
const functionExpression = lua.createFunctionExpression(
lua.createBlock(transformedBody),
paramNames,
Expand Down
63 changes: 41 additions & 22 deletions src/transformation/visitors/return.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
import * as ts from "typescript";
import * as lua from "../../LuaAST";
import { FunctionVisitor } from "../context";
import { FunctionVisitor, TransformationContext } from "../context";
import { isInTupleReturnFunction, isTupleReturnCall } from "../utils/annotations";
import { validateAssignment } from "../utils/assignment-validation";
import { createUnpackCall, wrapInTable } from "../utils/lua-ast";
import { ScopeType, walkScopesUp } from "../utils/scope";
import { isArrayType } from "../utils/typescript";

function transformExpressionsInReturn(
context: TransformationContext,
node: ts.Expression,
insideTryCatch: boolean
): lua.Expression[] {
if (!isInTupleReturnFunction(context, node)) {
return [context.transformExpression(node)];
}

let results: lua.Expression[];
const expressionType = context.checker.getTypeAtLocation(node);

// Parent function is a TupleReturn function
if (ts.isArrayLiteralExpression(node)) {
// If return expression is an array literal, leave out brackets.
results = node.elements.map(e => context.transformExpression(e));
} else if (!isTupleReturnCall(context, node) && isArrayType(context, expressionType)) {
// If return expression is an array-type and not another TupleReturn call, unpack it
results = [createUnpackCall(context, context.transformExpression(node), node)];
} else {
results = [context.transformExpression(node)];
}

// Wrap tupleReturn results when returning inside try/catch
if (insideTryCatch) {
results = [wrapInTable(...results)];
}

return results;
}

export function transformExpressionBodyToReturnStatement(
context: TransformationContext,
node: ts.Expression
): lua.Statement {
const expressions = transformExpressionsInReturn(context, node, false);
return lua.createReturnStatement(expressions, node);
}

export const transformReturnStatement: FunctionVisitor<ts.ReturnStatement> = (statement, context) => {
// Bubble up explicit return flag and check if we're inside a try/catch block
let insideTryCatch = false;
Expand All @@ -29,27 +68,7 @@ export const transformReturnStatement: FunctionVisitor<ts.ReturnStatement> = (st
validateAssignment(context, statement, expressionType, returnType);
}

if (isInTupleReturnFunction(context, statement)) {
// Parent function is a TupleReturn function
if (ts.isArrayLiteralExpression(statement.expression)) {
// If return expression is an array literal, leave out brackets.
results = statement.expression.elements.map(e => context.transformExpression(e));
} else if (!isTupleReturnCall(context, statement.expression) && isArrayType(context, expressionType)) {
// If return expression is an array-type and not another TupleReturn call, unpack it
results = [
createUnpackCall(context, context.transformExpression(statement.expression), statement.expression),
];
} else {
results = [context.transformExpression(statement.expression)];
}

// Wrap tupleReturn results when returning inside try/catch
if (insideTryCatch) {
results = [wrapInTable(...results)];
}
} else {
results = [context.transformExpression(statement.expression)];
}
results = transformExpressionsInReturn(context, statement.expression, insideTryCatch);
} else {
// Empty return
results = [];
Expand Down
72 changes: 0 additions & 72 deletions test/unit/classes/accessors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,6 @@ test("get accessor in base class", () => {
`.expectToMatchJsResult();
});

test.skip("get accessor override", () => {
util.testFunction`
class Foo {
_foo = "foo";
foo = "foo";
}
class Bar extends Foo {
get foo() { return this._foo + "bar"; }
}
const b = new Bar();
return b.foo;
`.expectToMatchJsResult();
});

test.skip("get accessor overridden", () => {
util.testFunction`
class Foo {
_foo = "foo";
get foo() { return this._foo; }
}
class Bar extends Foo {
foo = "bar";
}
const b = new Bar();
return b.foo;
`.expectToMatchJsResult();
});

test("get accessor override accessor", () => {
util.testFunction`
class Foo {
Expand Down Expand Up @@ -125,37 +97,6 @@ test("set accessor in base class", () => {
`.expectToMatchJsResult();
});

test("set accessor override", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check skipped tests tests, I think they shouldn't be relevant now too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep they were too, removed them as well

util.testFunction`
class Foo {
_foo = "foo";
foo = "foo";
}
class Bar extends Foo {
set foo(val: string) { this._foo = val; }
}
const b = new Bar();
b.foo = "bar"
return b._foo;
`.expectToMatchJsResult();
});

test("set accessor overridden", () => {
util.testFunction`
class Foo {
_foo = "baz";
set foo(val: string) { this._foo = val; }
}
class Bar extends Foo {
foo = "foo"; // triggers base class setter
}
const b = new Bar();
const fooOriginal = b._foo;
b.foo = "bar"
return fooOriginal + b._foo;
`.expectToMatchJsResult();
});

test("set accessor override accessor", () => {
util.testFunction`
class Foo {
Expand Down Expand Up @@ -250,19 +191,6 @@ test("static get accessor override", () => {
`.expectToMatchJsResult();
});

test.skip("static get accessor overridden", () => {
util.testFunction`
class Foo {
static _foo = "foo";
static get foo() { return this._foo; }
}
class Bar extends Foo {
static foo = "bar";
}
return Bar.foo;
`.expectToMatchJsResult();
});

test("static get accessor override accessor", () => {
util.testFunction`
class Foo {
Expand Down