diff --git a/package-lock.json b/package-lock.json index 917dc78de..af6ed4063 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,9 +52,9 @@ } }, "alsatian": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/alsatian/-/alsatian-2.2.1.tgz", - "integrity": "sha1-B+qeiU7bnqmX7VcaZOFMyYsj0/g=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/alsatian/-/alsatian-2.3.0.tgz", + "integrity": "sha512-e83K7JpH9Hj1+TUYyZialNKDln5gW56C82CVcd0AMt2whLFtKzYiddE5Dz2biUWjT8KzF11lkwonzf9wOCQyAA==", "dev": true, "requires": { "@types/node": ">=4.0.0", @@ -341,7 +341,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -3511,9 +3511,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==", + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -3521,7 +3521,7 @@ "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, @@ -3689,9 +3689,9 @@ } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -3718,7 +3718,7 @@ }, "tap-bark": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tap-bark/-/tap-bark-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/tap-bark/-/tap-bark-1.0.0.tgz", "integrity": "sha1-bAPcUWh/7Xh3+COtSx3dHvXrVnQ=", "dev": true, "requires": { @@ -3751,12 +3751,12 @@ } }, "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "^2.1.5", + "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, diff --git a/package.json b/package.json index 1f083c6e4..a71fd0cb5 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/glob": "^5.0.35", "@types/node": "^9.6.23", "@types/yargs": "^11.1.1", - "alsatian": "^2.2.1", + "alsatian": "^2.3.0", "circular-json": "^0.5.5", "codecov": "3.0.2", "deep-equal": "^1.0.1", diff --git a/src/LuaLibFeature.ts b/src/LuaLibFeature.ts new file mode 100644 index 000000000..f33236ff1 --- /dev/null +++ b/src/LuaLibFeature.ts @@ -0,0 +1,22 @@ +export enum LuaLibFeature { + ArrayConcat = "ArrayConcat", + ArrayEvery = "ArrayEvery", + ArrayFilter = "ArrayFilter", + ArrayForEach = "ArrayForEach", + ArrayIndexOf = "ArrayIndexOf", + ArrayMap = "ArrayMap", + ArrayPush = "ArrayPush", + ArrayReverse = "ArrayReverse", + ArrayShift = "ArrayShift", + ArrayUnshift = "ArrayUnshift", + ArraySort = "ArraySort", + ArraySlice = "ArraySlice", + ArraySome = "ArraySome", + ArraySplice = "ArraySplice", + InstanceOf = "InstanceOf", + Map = "Map", + Set = "Set", + StringReplace = "StringReplace", + StringSplit = "StringSplit", + Ternary = "Ternary", +} diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 203181681..286f4b016 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -56,6 +56,16 @@ export class TSHelper { return false; } + public static isIdentifierExported( + identifier: ts.Identifier, scope: ts.ModuleDeclaration | ts.SourceFile, checker: ts.TypeChecker): boolean { + const identifierSymbol = checker.getTypeAtLocation(scope).getSymbol(); + if (identifierSymbol.exports) { + return identifierSymbol.exports.has(identifier.escapedText); + } + + return false; + } + public static isInDestructingAssignment(node: ts.Node): boolean { return node.parent && ((ts.isVariableDeclaration(node.parent) && ts.isArrayBindingPattern(node.parent.name)) || (ts.isBinaryExpression(node.parent) && ts.isArrayLiteralExpression(node.parent.left))); diff --git a/src/TransformHelper.ts b/src/TransformHelper.ts index 34f32dc33..959b63fd6 100644 --- a/src/TransformHelper.ts +++ b/src/TransformHelper.ts @@ -2,13 +2,12 @@ import * as ts from "typescript"; export class TransformHelper { // Helper to create simple lua variable statement; - public static createLuaVariableStatement( - identifier: ts.Identifier, - expression: ts.Expression, - typeNode?: ts.TypeNode - ): ts.VariableStatement { + public static createLuaVariableStatement(identifier: ts.Identifier, + expression?: ts.Expression, + typeNode?: ts.TypeNode, + modifiers: ReadonlyArray = []): ts.VariableStatement { const declaration = ts.createVariableDeclaration(identifier, typeNode, expression); - const statement = ts.createVariableStatement([], ts.createVariableDeclarationList([declaration])); + const statement = ts.createVariableStatement(modifiers, ts.createVariableDeclarationList([declaration])); return statement; } @@ -18,4 +17,9 @@ export class TransformHelper { ts.createCall(requireIdentifier, [ts.createLiteralTypeNode(moduleSpecifier)], [moduleSpecifier]); return this.createLuaVariableStatement(identifier, requireCall); } + + public static flatten(arr: T[]): T[] { + const flat = [].concat(...arr); + return flat.some(Array.isArray) ? this.flatten(flat) : flat; + } } diff --git a/src/Transformer.ts b/src/Transformer.ts index 1dda3859b..d9f59173d 100644 --- a/src/Transformer.ts +++ b/src/Transformer.ts @@ -5,6 +5,7 @@ import {CompilerOptions} from "./CompilerOptions"; import {DecoratorKind} from "./Decorator"; import {TSTLErrors} from "./Errors"; import {TransformHelper as transformHelper} from "./TransformHelper"; +import {LuaTarget} from "./Transpiler"; import {TSHelper as tsHelper} from "./TSHelper"; export class LuaTransformer { @@ -12,6 +13,9 @@ export class LuaTransformer { private options: CompilerOptions; private context: ts.TransformationContext; private sourceFile: ts.SourceFile; + private isModule: boolean; + + private currentNamespace: ts.ModuleDeclaration; constructor(checker: ts.TypeChecker, options: CompilerOptions) { this.checker = checker; @@ -20,6 +24,7 @@ export class LuaTransformer { public transform(node: ts.SourceFile): ts.SourceFile { this.sourceFile = node; + this.isModule = tsHelper.isFileModule(node); return ts .transform(node, [(ctx: ts.TransformationContext) => { @@ -63,6 +68,79 @@ export class LuaTransformer { case ts.SyntaxKind.InterfaceDeclaration: return this.visitInterfaceDeclaration(node as ts.InterfaceDeclaration); // Statements + case ts.SyntaxKind.VariableStatement: + case ts.SyntaxKind.ExpressionStatement: + case ts.SyntaxKind.ReturnStatement: + case ts.SyntaxKind.IfStatement: + case ts.SyntaxKind.WhileStatement: + case ts.SyntaxKind.DoStatement: + case ts.SyntaxKind.ForStatement: + case ts.SyntaxKind.ForOfStatement: + case ts.SyntaxKind.ForInStatement: + case ts.SyntaxKind.SwitchStatement: + case ts.SyntaxKind.BreakStatement: + case ts.SyntaxKind.TryStatement: + case ts.SyntaxKind.ThrowStatement: + case ts.SyntaxKind.ContinueStatement: + case ts.SyntaxKind.EmptyStatement: + return this.visitStatement(node as ts.Statement); + // Expressions + case ts.SyntaxKind.BinaryExpression: + case ts.SyntaxKind.ConditionalExpression: + case ts.SyntaxKind.CallExpression: + case ts.SyntaxKind.PropertyAccessExpression: + case ts.SyntaxKind.ElementAccessExpression: + case ts.SyntaxKind.ParenthesizedExpression: + case ts.SyntaxKind.TypeAssertionExpression: + case ts.SyntaxKind.AsExpression: + case ts.SyntaxKind.TypeOfExpression: + case ts.SyntaxKind.SpreadElement: + case ts.SyntaxKind.NonNullExpression: + case ts.SyntaxKind.ClassExpression: + case ts.SyntaxKind.TemplateExpression: + case ts.SyntaxKind.PostfixUnaryExpression: + case ts.SyntaxKind.PrefixUnaryExpression: + case ts.SyntaxKind.ArrayLiteralExpression: + case ts.SyntaxKind.ObjectLiteralExpression: + case ts.SyntaxKind.DeleteExpression: + case ts.SyntaxKind.FunctionExpression: + case ts.SyntaxKind.ArrowFunction: + case ts.SyntaxKind.NewExpression: + // Identifier + case ts.SyntaxKind.Identifier: + // Literals + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: + case ts.SyntaxKind.NumericLiteral: + // Keywords + case ts.SyntaxKind.TrueKeyword: + case ts.SyntaxKind.FalseKeyword: + case ts.SyntaxKind.NullKeyword: + case ts.SyntaxKind.UndefinedKeyword: + case ts.SyntaxKind.ThisKeyword: + case ts.SyntaxKind.SuperKeyword: + return this.visitExpression(node as ts.Expression); + // ComputedPropertyName + case ts.SyntaxKind.ComputedPropertyName: + return this.visitComputedPropertyName(node as ts.ComputedPropertyName); + // Blocks + case ts.SyntaxKind.Block: + return this.visitBlock(node as ts.Block); + case ts.SyntaxKind.ModuleBlock: + return this.visitModuleBlock(node as ts.ModuleBlock); + // EOF TOKEN + case ts.SyntaxKind.EndOfFileToken: + return this.visitEndOfFileToken(node as ts.EndOfFileToken); + default: + throw TSTLErrors.UnsupportedKind("Node", node.kind, node); + } + } + + public visitStatement(node: ts.Statement): ts.Statement { + if (ts.isBlock(node)) { + return this.visitBlock(node); + } + switch (node.kind) { case ts.SyntaxKind.VariableStatement: return this.visitVariableStatement(node as ts.VariableStatement); case ts.SyntaxKind.ExpressionStatement: @@ -93,7 +171,13 @@ export class LuaTransformer { return this.visitContinueStatement(node as ts.ContinueStatement); case ts.SyntaxKind.EmptyStatement: return this.visitEmptyStatement(node as ts.EmptyStatement); - // Expressions + default: + throw TSTLErrors.UnsupportedKind("Statement", node.kind, node); + } + } + + public visitExpression(node: ts.Expression): ts.Expression { + switch (node.kind) { case ts.SyntaxKind.BinaryExpression: return this.visitBinaryExpression(node as ts.BinaryExpression); case ts.SyntaxKind.ConditionalExpression: @@ -132,48 +216,35 @@ export class LuaTransformer { return this.visitDeleteExpression(node as ts.DeleteExpression); case ts.SyntaxKind.FunctionExpression: return this.visitFunctionExpression(node as ts.FunctionExpression); - case ts.SyntaxKind.NewExpression: - return this.visitNewExpression(node as ts.NewExpression); case ts.SyntaxKind.ArrowFunction: return this.visitArrowFunction(node as ts.ArrowFunction); - // Identifier + case ts.SyntaxKind.NewExpression: + return this.visitNewExpression(node as ts.NewExpression); case ts.SyntaxKind.Identifier: return this.visitIdentifier(node as ts.Identifier); - // Literal case ts.SyntaxKind.StringLiteral: return this.visitStringLiteral(node as ts.StringLiteral); case ts.SyntaxKind.NoSubstitutionTemplateLiteral: return this.visitNoSubstitutionTemplateLiteral(node as ts.NoSubstitutionTemplateLiteral); case ts.SyntaxKind.NumericLiteral: return this.visitNumericLiteral(node as ts.NumericLiteral); - // Keywords case ts.SyntaxKind.TrueKeyword: return this.visitTrueKeyword(node as ts.BooleanLiteral); case ts.SyntaxKind.FalseKeyword: return this.visitFalseKeyword(node as ts.BooleanLiteral); case ts.SyntaxKind.NullKeyword: - return this.visitNullKeyword(node as ts.KeywordTypeNode); + return this.visitNullKeyword(node as ts.NullLiteral); case ts.SyntaxKind.UndefinedKeyword: - return this.visitUndefinedKeyword(node as ts.KeywordTypeNode); + return this.visitUndefinedKeyword(node as ts.LiteralExpression); case ts.SyntaxKind.ThisKeyword: - return this.visitThisKeyword(node as ts.KeywordTypeNode); + return this.visitThisKeyword(node as ts.ThisExpression); case ts.SyntaxKind.SuperKeyword: - return this.visitSuperKeyword(node as ts.KeywordTypeNode); - // ComputedPropertyName - case ts.SyntaxKind.ComputedPropertyName: - return this.visitComputedPropertyName(node as ts.ComputedPropertyName); - // Blocks - case ts.SyntaxKind.Block: - return this.visitBlock(node as ts.Block); - case ts.SyntaxKind.ModuleBlock: - return this.visitModuleBlock(node as ts.ModuleBlock); - // EOF TOKEN - case ts.SyntaxKind.EndOfFileToken: - return this.visitEndOfFileToken(node as ts.EndOfFileToken); + return this.visitSuperKeyword(node as ts.SuperExpression); default: - throw TSTLErrors.UnsupportedKind("Node", node.kind, node); + throw TSTLErrors.UnsupportedKind("Expression", node.kind, node); } } + public visitImportDeclaration(node: ts.ImportDeclaration): ts.VisitResult { if (!node.importClause || !node.importClause.namedBindings) { throw TSTLErrors.DefaultImportsNotSupported(node); @@ -222,8 +293,59 @@ export class LuaTransformer { throw TSTLErrors.UnsupportedImportType(imports); } } - public visitClassDeclaration(node: ts.ClassDeclaration): ts.VisitResult { - return node; + + public visitClassDeclaration(node: ts.ClassDeclaration): ts.ClassDeclaration { + // TODO this should actually be converted to lua nodes + return ts.updateClassDeclaration(node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + node.members.map(elem => this.visitClassElement(elem) as ts.ClassElement)); + // TODO make member visitor more specific + } + + public visitClassElement(node: ts.ClassElement): ts.ClassElement { + switch (node.kind) { + case ts.SyntaxKind.PropertyDeclaration: + return this.visitPropertyDeclaration(node as ts.PropertyDeclaration); + case ts.SyntaxKind.MethodDeclaration: + return this.visitMethodDeclaration(node as ts.MethodDeclaration); + case ts.SyntaxKind.Constructor: + return this.visitConstructorDeclaration(node as ts.ConstructorDeclaration); + } + } + + public visitPropertyDeclaration(node: ts.PropertyDeclaration): ts.PropertyDeclaration { + let updatedInitializer: ts.Expression; + if (node.initializer) { + updatedInitializer = this.visitExpression(node.initializer); + } + return ts.updateProperty(node, + node.decorators, + node.modifiers, + node.name, + node.questionToken || node.exclamationToken, + node.type, + updatedInitializer); + } + + public visitMethodDeclaration(node: ts.MethodDeclaration): ts.MethodDeclaration { + return ts.updateMethod(node, + node.decorators, + node.modifiers, + node.asteriskToken, + node.name, + node.questionToken, + node.typeParameters, + node.parameters, + node.type, + this.visitBlock(node.body)); + } + + public visitConstructorDeclaration(node: ts.ConstructorDeclaration): ts.ConstructorDeclaration { + return ts.updateConstructor(node, node.decorators, node.modifiers, node.parameters, this.visitBlock(node.body)); } // previously transpileNamespace public visitModuleDeclaration(node: ts.ModuleDeclaration): ts.VisitResult { @@ -232,170 +354,403 @@ export class LuaTransformer { if (decorators.has(DecoratorKind.Phantom) && node.body) { return node.body; } - // TODO actual transpilation - return node; + + const result: ts.Node[] = []; + + if (this.currentNamespace) { + // outerNS.innerNS = outerNS.innerNS or {}; + // local innerNS = outerNS.innerNS + const declarationNameExpression = + ts.createPropertyAccess(this.currentNamespace.name, node.name as ts.Identifier); + const declarationAssignment = ts.createAssignment( + declarationNameExpression, ts.createLogicalOr(declarationNameExpression, ts.createObjectLiteral())); + + result.push(declarationAssignment); + + const localDeclaration = + transformHelper.createLuaVariableStatement(node.name as ts.Identifier, declarationNameExpression); + + result.push(localDeclaration); + } else if (this.isModule && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export)) { + // exports.NS = exports.NS or {} + // local NS = exports.NS + const declarationNameExpression = + ts.createPropertyAccess(ts.createIdentifier("exports"), node.name as ts.Identifier); + const declarationAssignment = ts.createAssignment( + declarationNameExpression, ts.createLogicalOr(declarationNameExpression, ts.createObjectLiteral())); + + result.push(declarationAssignment); + + const localDeclaration = + transformHelper.createLuaVariableStatement(node.name as ts.Identifier, declarationNameExpression); + + result.push(localDeclaration); + } else { + // local NS = NS or {} + const declarationNameExpression = node.name; + const declarationAssignment = ts.createAssignment( + declarationNameExpression, ts.createLogicalOr(declarationNameExpression, ts.createObjectLiteral())); + + result.push(declarationAssignment); + } + + // Set current namespace for nested NS + // Keep previous currentNS to reset after block transpilation + const previousNamespace = this.currentNamespace; + this.currentNamespace = node; + + // Transform moduleblock to block and visit it + if (node.body && ts.isModuleBlock(node.body)) { + const bodyBlock = this.visitModuleBlock(node.body) as ts.Block; + result.push(bodyBlock); + } + + this.currentNamespace = previousNamespace; + + return result; } + public visitEnumDeclaration(node: ts.EnumDeclaration): ts.VisitResult { return node; } + public visitFunctionDeclaration(node: ts.FunctionDeclaration): ts.VisitResult { - return node; + return ts.updateFunctionDeclaration(node, + node.decorators, + node.modifiers, + node.asteriskToken, + node.name, + node.typeParameters, + node.parameters, + node.type, + this.visitBlock(node.body)); } + public visitTypeAliasDeclaration(node: ts.TypeAliasDeclaration): ts.VisitResult { return undefined; } + public visitInterfaceDeclaration(node: ts.InterfaceDeclaration): ts.VisitResult { return undefined; } - public visitVariableStatement(node: ts.VariableStatement): ts.VisitResult { - return node; + + public visitVariableStatement(node: ts.VariableStatement): ts.VariableStatement { + return ts.updateVariableStatement( + node, node.modifiers, this.visitVariableDeclarationList(node.declarationList)); } - public visitExpressionStatement(node: ts.ExpressionStatement): ts.VisitResult { - return node; + + public visitVariableDeclarationList(node: ts.VariableDeclarationList): ts.VariableDeclarationList { + return ts.updateVariableDeclarationList(node, + node.declarations.map(decl => this.visitVariableDeclaration(decl))); } - public visitReturn(node: ts.ReturnStatement): ts.VisitResult { - return node; + + public visitVariableDeclaration(node: ts.VariableDeclaration): ts.VariableDeclaration { + let initializer: ts.Expression; + if (node.initializer) { + initializer = this.visitExpression(node.initializer); + } + return ts.updateVariableDeclaration(node, node.name, node.type, initializer); } - public visitIfStatement(node: ts.IfStatement): ts.VisitResult { - return node; + + public visitExpressionStatement(node: ts.ExpressionStatement): ts.ExpressionStatement { + return ts.updateStatement(node, this.visitExpression(node.expression)); } - public visitWhileStatement(node: ts.WhileStatement): ts.VisitResult { - return node; + + public visitReturn(node: ts.ReturnStatement): ts.ReturnStatement { + let updatedExpression: ts.Expression; + if (node.expression) { + updatedExpression = this.visitExpression(node.expression); + } + return ts.updateReturn(node, updatedExpression); } - public visitDoStatement(node: ts.DoStatement): ts.VisitResult { - return node; + + public visitIfStatement(node: ts.IfStatement): ts.IfStatement { + let elseStatement: ts.Statement; + if (node.elseStatement) { + elseStatement = this.visitStatement(node.elseStatement); + } + return ts.updateIf( + node, this.visitExpression(node.expression), this.visitStatement(node.thenStatement), elseStatement); } - public visitForStatement(node: ts.ForStatement): ts.VisitResult { - return node; + + public visitWhileStatement(node: ts.WhileStatement): ts.WhileStatement { + return ts.updateWhile(node, this.visitExpression(node.expression), this.visitStatement(node.statement)); } - public visitForOfStatement(node: ts.ForOfStatement): ts.VisitResult { - return node; + + public visitDoStatement(node: ts.DoStatement): ts.DoStatement { + return ts.updateDo(node, this.visitStatement(node.statement), this.visitExpression(node.expression)); } - public visitForInStatement(node: ts.ForInStatement): ts.VisitResult { - return node; + + public visitForStatement(node: ts.ForStatement): ts.ForStatement { + return ts.updateFor(node, + this.visitForInitializer(node.initializer), + this.visitExpression(node.condition), + this.visitExpression(node.incrementor), + this.visitStatement(node.statement)); + } + + public visitForOfStatement(node: ts.ForOfStatement): ts.ForOfStatement { + return ts.updateForOf(node, + node.awaitModifier, + this.visitForInitializer(node.initializer), + this.visitExpression(node.expression), + this.visitStatement(node.statement)); + } + + public visitForInStatement(node: ts.ForInStatement): ts.ForInStatement { + return ts.updateForIn(node, + this.visitForInitializer(node.initializer), + this.visitExpression(node.expression), + this.visitStatement(node.statement)); } - public visitSwitchStatement(node: ts.SwitchStatement): ts.VisitResult { + + public visitForInitializer(node: ts.ForInitializer): ts.ForInitializer { + let updatedInitializer: ts.ForInitializer; + if (ts.isVariableDeclarationList(node)) { + updatedInitializer = this.visitVariableDeclarationList(node); + } else { + updatedInitializer = this.visitExpression(node); + } + return updatedInitializer; + } + + public visitSwitchStatement(node: ts.SwitchStatement): ts.SwitchStatement { + // TODO return node; } - public visitBreakStatement(node: ts.BreakStatement): ts.VisitResult { + + public visitBreakStatement(node: ts.BreakStatement): ts.BreakStatement { + // TODO return node; } - public visitTryStatement(node: ts.TryStatement): ts.VisitResult { + + public visitTryStatement(node: ts.TryStatement): ts.TryStatement { + // TODO return node; } - public visitThrowStatement(node: ts.ThrowStatement): ts.VisitResult { + + public visitThrowStatement(node: ts.ThrowStatement): ts.ThrowStatement { + // TODO return node; } - public visitContinueStatement(node: ts.ContinueStatement): ts.VisitResult { + + public visitContinueStatement(node: ts.ContinueStatement): ts.ContinueStatement { + // TODO return node; } - public visitEmptyStatement(node: ts.EmptyStatement): ts.VisitResult { + + public visitEmptyStatement(node: ts.EmptyStatement): ts.EmptyStatement { return undefined; } - public visitBinaryExpression(node: ts.BinaryExpression): ts.VisitResult { - return node; + + public visitBinaryExpression(node: ts.BinaryExpression): ts.BinaryExpression { + let operatorToken = node.operatorToken; + if (node.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) { + operatorToken = ts.createToken(ts.SyntaxKind.EqualsEqualsToken); + } + return ts.updateBinary(node, this.visitExpression(node.left), this.visitExpression(node.right), operatorToken); } - public visitConditionalExpression(node: ts.ConditionalExpression): ts.VisitResult { - return node; + + public visitConditionalExpression(node: ts.ConditionalExpression): ts.ConditionalExpression { + return ts.updateConditional(node, + this.visitExpression(node.condition), + this.visitExpression(node.whenTrue), + this.visitExpression(node.whenFalse)); } - public visitCallExpression(node: ts.CallExpression): ts.VisitResult { - return node; + + public visitCallExpression(node: ts.CallExpression): ts.CallExpression { + return ts.updateCall(node, + this.visitExpression(node.expression), + node.typeArguments, + node.arguments.map(arg => this.visitExpression(arg))); } - public visitPropertyAccessExpression(node: ts.PropertyAccessExpression): - ts.VisitResult { - return node; + + public visitPropertyAccessExpression(node: ts.PropertyAccessExpression): ts.PropertyAccessExpression { + return ts.updatePropertyAccess(node, this.visitExpression(node.expression), node.name); } - public visitElementAccessExpression(node: ts.ElementAccessExpression): ts.VisitResult { - return node; + + public visitElementAccessExpression(node: ts.ElementAccessExpression): ts.ElementAccessExpression { + return ts.updateElementAccess( + node, this.visitExpression(node.expression), this.visitExpression(node.argumentExpression)); } - public visitParenthesizedExpression(node: ts.ParenthesizedExpression): ts.VisitResult { - throw node; + + public visitParenthesizedExpression(node: ts.ParenthesizedExpression): ts.ParenthesizedExpression { + return ts.updateParen(node, this.visitExpression(node.expression)); } - public visitTypeAssertionExpression(node: ts.TypeAssertion): ts.VisitResult { - return node.expression; + + public visitTypeAssertionExpression(node: ts.TypeAssertion): ts.Expression { + return this.visitExpression(node.expression); } - public visitAsExpression(node: ts.AsExpression): ts.VisitResult { - return node.expression; + + public visitAsExpression(node: ts.AsExpression): ts.Expression { + return this.visitExpression(node.expression); } - public visitTypeOfExpression(node: ts.TypeOfExpression): ts.VisitResult { - return node; + + public visitTypeOfExpression(node: ts.TypeOfExpression): ts.BinaryExpression { + // ((type(${expression}) == "table" and "object") or type(${expression})) + const expression = this.visitExpression(node.expression); + const typeCall = ts.createCall( + ts.createIdentifier("type"), [ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)], [expression]); + const comapareExpression = + ts.createBinary(typeCall, ts.SyntaxKind.EqualsEqualsToken, ts.createLiteral("table")); + const andExpression = ts.createLogicalAnd(comapareExpression, ts.createLiteral("object")); + const orExpression = ts.createLogicalOr(andExpression, typeCall); + return orExpression; } - public visitSpreadElement(node: ts.SpreadElement): ts.VisitResult { - return node; + + public visitSpreadElement(node: ts.SpreadElement): ts.CallExpression { + // TODO move this to differen targets + // table.unpack(expression) / unpack(expression) + let functionExpresion: ts.Expression; + switch (this.options.luaTarget) { + case LuaTarget.Lua51: + functionExpresion = ts.createIdentifier("unpack"); + break; + case LuaTarget.Lua52: + case LuaTarget.Lua53: + functionExpresion = + ts.createPropertyAccess(ts.createIdentifier("table"), ts.createIdentifier("unpack")); + case LuaTarget.LuaJIT: + functionExpresion = ts.createIdentifier("unpack"); + break; + } + return ts.createCall( + functionExpresion, [ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)], [node.expression]); } - public visitNonNullExpression(node: ts.NonNullExpression): ts.VisitResult { - return node.expression; + + public visitNonNullExpression(node: ts.NonNullExpression): ts.Expression { + return this.visitExpression(node.expression); } - public visitClassExpression(node: ts.ClassExpression): ts.VisitResult { + + public visitClassExpression(node: ts.ClassExpression): ts.ClassExpression { + // TODO return node; } - public visitTemplateExpression(node: ts.TemplateExpression): ts.VisitResult { - return node; + + public visitTemplateExpression(node: ts.TemplateExpression): ts.Expression { + let concatExpression: ts.Expression = ts.createLiteral(node.head.text); + node.templateSpans.forEach(span => { + const expr = ts.createCall(ts.createIdentifier("tostring"), + [ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)], + [this.visitExpression(span.expression)]); + const text = ts.createLiteral(span.literal.text); + + concatExpression = ts.createAdd(concatExpression, ts.createAdd(expr, ts.createLiteral(text))); + }); + return concatExpression; } - public visitPostfixUnaryExpression(node: ts.PostfixUnaryExpression): ts.VisitResult { + + public visitPostfixUnaryExpression(node: ts.PostfixUnaryExpression): ts.PostfixUnaryExpression { + // TODO return node; } - public visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ts.VisitResult { + + public visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ts.PrefixUnaryExpression { + // TODO return node; } - public visitArrayLiteralExpression(node: ts.ArrayLiteralExpression): ts.VisitResult { + + public visitArrayLiteralExpression(node: ts.ArrayLiteralExpression): ts.ArrayLiteralExpression { return node; } - public visitObjectLiteralExpression(node: ts.ObjectLiteralExpression): ts.VisitResult { + + public visitObjectLiteralExpression(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { return node; } - public visitDeleteExpression(node: ts.DeleteExpression): ts.VisitResult { - return node; + + public visitDeleteExpression(node: ts.DeleteExpression): ts.Expression { + return ts.createAssignment(this.visitExpression(node.expression), ts.createNull()); } - public visitFunctionExpression(node: ts.FunctionExpression): ts.VisitResult { - return node; + + public visitFunctionExpression(node: ts.FunctionExpression): ts.FunctionExpression { + return ts.updateFunctionExpression(node, + node.modifiers, + node.asteriskToken, + node.name, + node.typeParameters, + node.parameters, + node.type, + this.visitBlock(node.body)); } - public visitNewExpression(node: ts.NewExpression): ts.VisitResult { - return node; + + public visitArrowFunction(node: ts.ArrowFunction): ts.ArrowFunction { + let newBody: ts.ConciseBody; + if (ts.isBlock(node.body)) { + newBody = this.visitBlock(node.body); + } else { + newBody = this.visitExpression(node.body); + } + return ts.updateArrowFunction(node, node.modifiers, node.typeParameters, node.parameters, node.type, newBody); } - public visitArrowFunction(node: ts.ArrowFunction): ts.VisitResult { + + public visitNewExpression(node: ts.NewExpression): ts.NewExpression { return node; } - public visitIdentifier(node: ts.Identifier): ts.VisitResult { + + public visitIdentifier(node: ts.Identifier): ts.Identifier| ts.PropertyAccessExpression { + // If we are in a namespace or a sourcefile that is a module check if this identifier is exported + if (this.currentNamespace && tsHelper.isIdentifierExported(node, this.currentNamespace, this.checker)) { + return ts.createPropertyAccess(this.currentNamespace.name, node); + } else if (this.isModule && tsHelper.isIdentifierExported(node, this.sourceFile, this.checker)) { + return ts.createPropertyAccess(ts.createIdentifier("exports"), node); + } + return node; } - public visitStringLiteral(node: ts.StringLiteral): ts.VisitResult { + + public visitStringLiteral(node: ts.StringLiteral): ts.StringLiteral { return node; } + public visitNoSubstitutionTemplateLiteral(node: ts.NoSubstitutionTemplateLiteral): - ts.VisitResult { + ts.NoSubstitutionTemplateLiteral { return node; } - public visitNumericLiteral(node: ts.NumericLiteral): ts.VisitResult { + + public visitNumericLiteral(node: ts.NumericLiteral): ts.NumericLiteral { return node; } - public visitTrueKeyword(node: ts.BooleanLiteral): ts.VisitResult { + + public visitTrueKeyword(node: ts.BooleanLiteral): ts.BooleanLiteral { return node; } - public visitFalseKeyword(node: ts.BooleanLiteral): ts.VisitResult { + + public visitFalseKeyword(node: ts.BooleanLiteral): ts.BooleanLiteral { return node; } - public visitNullKeyword(node: ts.KeywordTypeNode): ts.VisitResult { + + public visitNullKeyword(node: ts.NullLiteral): ts.NullLiteral { return node; } - public visitUndefinedKeyword(node: ts.KeywordTypeNode): ts.VisitResult { + + public visitUndefinedKeyword(node: ts.LiteralExpression): ts.LiteralExpression { return node; } - public visitThisKeyword(node: ts.KeywordTypeNode): ts.VisitResult { + + public visitThisKeyword(node: ts.ThisExpression): ts.ThisExpression { return node; } - public visitSuperKeyword(node: ts.KeywordTypeNode): ts.VisitResult { + + public visitSuperKeyword(node: ts.SuperExpression): ts.SuperExpression { return node; } + public visitComputedPropertyName(node: ts.ComputedPropertyName): ts.VisitResult { return node; } - public visitBlock(node: ts.Block): ts.VisitResult { - return node; + + public visitBlock(node: ts.Block): ts.Block { + if (!node) { + return undefined; + } + return ts.updateBlock(node, + transformHelper.flatten(node.statements.map(s => this.visitor(s)) as ts.Statement[])); } - public visitModuleBlock(node: ts.ModuleBlock): ts.VisitResult { - return node; + + public visitModuleBlock(node: ts.ModuleBlock): ts.VisitResult { + return this.visitBlock(ts.createBlock(node.statements)); } + public visitEndOfFileToken(node: ts.EndOfFileToken): ts.VisitResult { return node; } @@ -424,5 +779,4 @@ export class LuaTransformer { private pathToLuaRequirePath(filePath: string): string { return filePath.replace(new RegExp("\\\\|\/", "g"), "."); } - } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index d2b0a3ac6..dead9fca5 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -5,6 +5,7 @@ import * as ts from "typescript"; import { CompilerOptions } from "./CompilerOptions"; import { DecoratorKind } from "./Decorator"; import { TSTLErrors } from "./Errors"; +import { LuaLibFeature } from "./LuaLibFeature"; import { TSHelper as tsHelper } from "./TSHelper"; import { LuaTransformer } from "./Transformer"; @@ -20,29 +21,6 @@ export enum LuaTarget { LuaJIT = "jit", } -export enum LuaLibFeature { - ArrayConcat = "ArrayConcat", - ArrayEvery = "ArrayEvery", - ArrayFilter = "ArrayFilter", - ArrayForEach = "ArrayForEach", - ArrayIndexOf = "ArrayIndexOf", - ArrayMap = "ArrayMap", - ArrayPush = "ArrayPush", - ArrayReverse = "ArrayReverse", - ArrayShift = "ArrayShift", - ArrayUnshift = "ArrayUnshift", - ArraySort = "ArraySort", - ArraySlice = "ArraySlice", - ArraySome = "ArraySome", - ArraySplice = "ArraySplice", - InstanceOf = "InstanceOf", - Map = "Map", - Set = "Set", - StringReplace = "StringReplace", - StringSplit = "StringSplit", - Ternary = "Ternary", -} - export enum LuaLibImportKind { None = "none", Always = "always", @@ -267,8 +245,6 @@ export abstract class LuaTranspiler { switch (node.kind) { case ts.SyntaxKind.ClassDeclaration: return this.transpileClass(node as ts.ClassDeclaration); - case ts.SyntaxKind.ModuleDeclaration: - return this.transpileNamespace(node as ts.ModuleDeclaration); case ts.SyntaxKind.ModuleBlock: return this.transpileBlock(node as ts.Block); case ts.SyntaxKind.EnumDeclaration: @@ -319,36 +295,6 @@ export abstract class LuaTranspiler { return `__TS__${func}(${params.join(", ")})`; } - public transpileNamespace(node: ts.ModuleDeclaration): string { - const decorators = tsHelper.getCustomDecorators(this.checker.getTypeAtLocation(node), this.checker); - // If phantom namespace just transpile the body as normal - if (decorators.has(DecoratorKind.Phantom) && node.body) { - return this.transpileNode(node.body); - } - - const defName = this.definitionName(node.name.text); - // Initialize to pre-existing export if one exists - const prefix = (this.namespace.length === 0 && this.isModule && - (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export)) ? `exports.${node.name.text} or ` : ""; - let result = - this.indent + - this.accessPrefix(node) + - `${node.name.text} = ${prefix}${node.name.text} or {}\n`; - - this.pushExport(node.name.text, node); - // Create closure - result += this.indent + "do\n"; - this.pushIndent(); - this.namespace.push(node.name.text); - if (node.body) { - result += this.transpileNode(node.body); - } - this.namespace.pop(); - this.popIndent(); - result += this.indent + "end\n"; - return result; - } - public transpileEnum(node: ts.EnumDeclaration): string { const type = this.checker.getTypeAtLocation(node); @@ -678,8 +624,6 @@ export abstract class LuaTranspiler { case ts.SyntaxKind.NoSubstitutionTemplateLiteral: const text = this.escapeString((node as ts.StringLiteral).text); return `"${text}"`; - case ts.SyntaxKind.TemplateExpression: - return this.transpileTemplateExpression(node as ts.TemplateExpression); case ts.SyntaxKind.NumericLiteral: return (node as ts.NumericLiteral).text; case ts.SyntaxKind.TrueKeyword: @@ -699,8 +643,6 @@ export abstract class LuaTranspiler { return this.transpileArrayLiteral(node as ts.ArrayLiteralExpression); case ts.SyntaxKind.ObjectLiteralExpression: return this.transpileObjectLiteral(node as ts.ObjectLiteralExpression); - case ts.SyntaxKind.DeleteExpression: - return this.transpileExpression((node as ts.DeleteExpression).expression) + "=nil"; case ts.SyntaxKind.FunctionExpression: case ts.SyntaxKind.ArrowFunction: return this.transpileFunctionExpression(node as ts.ArrowFunction); @@ -712,20 +654,8 @@ export abstract class LuaTranspiler { return "(" + this.transpileExpression((node as ts.ParenthesizedExpression).expression) + ")"; case ts.SyntaxKind.SuperKeyword: return "self.__base"; - case ts.SyntaxKind.TypeAssertionExpression: - // Simply ignore the type assertion - return this.transpileExpression((node as ts.TypeAssertion).expression); - case ts.SyntaxKind.AsExpression: - // Also ignore as casts - return this.transpileExpression((node as ts.AsExpression).expression); - case ts.SyntaxKind.TypeOfExpression: - return this.transpileTypeOfExpression(node as ts.TypeOfExpression); case ts.SyntaxKind.EmptyStatement: return ""; - case ts.SyntaxKind.SpreadElement: - return this.transpileSpreadElement(node as ts.SpreadElement); - case ts.SyntaxKind.NonNullExpression: - return this.transpileExpression((node as ts.NonNullExpression).expression); case ts.SyntaxKind.ClassExpression: this.namespace.push(""); const classDeclaration = this.transpileClass(node as ts.ClassExpression, "_"); @@ -893,21 +823,6 @@ export abstract class LuaTranspiler { throw TSTLErrors.UnsupportedForTarget("Bitwise operations", this.options.luaTarget, node); } - public transpileTemplateExpression(node: ts.TemplateExpression): string { - const parts = [`"${this.escapeString(node.head.text)}"`]; - node.templateSpans.forEach(span => { - const expr = this.transpileExpression(span.expression, true); - const text = this.escapeString(span.literal.text); - - if (ts.isTemplateTail(span.literal)) { - parts.push(`tostring(${expr}).."${text}"`); - } else { - parts.push(`tostring(${expr}).."${text}"`); - } - }); - return parts.join(".."); - } - public transpileConditionalExpression(node: ts.ConditionalExpression, brackets?: boolean): string { const condition = this.transpileExpression(node.condition); const val1 = this.transpileExpression(node.whenTrue); @@ -1459,10 +1374,6 @@ export abstract class LuaTranspiler { return escapedText; } - public transpileSpreadElement(node: ts.SpreadElement): string { - return "unpack(" + this.transpileExpression(node.expression) + ")"; - } - public transpileArrayBindingElement(name: ts.ArrayBindingElement): string { if (ts.isOmittedExpression(name)) { return "__"; @@ -1475,11 +1386,6 @@ export abstract class LuaTranspiler { } } - public transpileTypeOfExpression(node: ts.TypeOfExpression): string { - const expression = this.transpileExpression(node.expression); - return `(type(${expression}) == "table" and "object" or type(${expression}))`; - } - // Transpile a variable statement public transpileVariableStatement(node: ts.VariableStatement): string { let result = ""; diff --git a/src/targets/Transpiler.52.ts b/src/targets/Transpiler.52.ts index 0fcba461f..5e5787973 100644 --- a/src/targets/Transpiler.52.ts +++ b/src/targets/Transpiler.52.ts @@ -136,9 +136,4 @@ export class LuaTranspiler52 extends LuaTranspiler51 { public transpileDestructingAssignmentValue(node: ts.Expression): string { return `table.unpack(${this.transpileExpression(node)})`; } - - /** @override */ - public transpileSpreadElement(node: ts.SpreadElement): string { - return "table.unpack(" + this.transpileExpression(node.expression) + ")"; - } } diff --git a/src/targets/Transpiler.JIT.ts b/src/targets/Transpiler.JIT.ts index a23e3a8e2..76bf2fac2 100644 --- a/src/targets/Transpiler.JIT.ts +++ b/src/targets/Transpiler.JIT.ts @@ -39,9 +39,4 @@ export class LuaTranspilerJIT extends LuaTranspiler52 { public transpileDestructingAssignmentValue(node: ts.Expression): string { return `unpack(${this.transpileExpression(node)})`; } - - /** @override */ - public transpileSpreadElement(node: ts.SpreadElement): string { - return "unpack(" + this.transpileExpression(node.expression) + ")"; - } } diff --git a/src/tstl.ts b/src/tstl.ts index 31174588c..0594900f7 100644 --- a/src/tstl.ts +++ b/src/tstl.ts @@ -19,12 +19,15 @@ export {LuaTranspiler53} from "./targets/Transpiler.53"; export {LuaTranspilerJIT} from "./targets/Transpiler.JIT"; export { - LuaLibFeature, LuaLibImportKind, LuaTarget, LuaTranspiler, } from "./Transpiler"; +export { + LuaLibFeature, +} from "./LuaLibFeature"; + export { createTranspiler } from "./TranspilerFactory"; diff --git a/test/translation/builder.spec.ts b/test/translation/builder.spec.ts index 70676141c..f667a6fd0 100644 --- a/test/translation/builder.spec.ts +++ b/test/translation/builder.spec.ts @@ -5,28 +5,28 @@ import * as util from "../src/util"; import * as fs from "fs"; import * as path from "path"; -let files: string[][] = []; -let fileContents: {[key: string]: Buffer} = {}; +const files: string[][] = []; +const fileContents: {[key: string]: Buffer} = {}; -let tsPath = path.join(__dirname, "./ts/"); -let luaPath = path.join(__dirname, "./lua/") +const tsPath = path.join(__dirname, "./ts/"); +const luaPath = path.join(__dirname, "./lua/"); -let tsFiles = fs.readdirSync(tsPath); -let luaFiles = fs.readdirSync(luaPath); +const tsFiles = fs.readdirSync(tsPath); +const luaFiles = fs.readdirSync(luaPath); tsFiles.forEach( (tsFile, i) => { // ignore non ts files - if (path.extname(tsFile) !== '.ts') { + if (path.extname(tsFile) !== ".ts") { return; } - let luaPart = luaFiles.indexOf(tsFile.replace('.ts', '.lua')); + const luaPart = luaFiles.indexOf(tsFile.replace(".ts", ".lua")); if (luaPart === -1) { - throw new Error("Missing lua counter part for test file: " + tsFile) + throw new Error("Missing lua counter part for test file: " + tsFile); } - let luaFile = luaFiles[luaPart]; - let luaFileAbsolute = path.join(luaPath, luaFile); - let tsFileAbsolute = path.join(tsPath, tsFile); + const luaFile = luaFiles[luaPart]; + const luaFileAbsolute = path.join(luaPath, luaFile); + const tsFileAbsolute = path.join(tsPath, tsFile); files.push([tsFile, luaFile]); fileContents[tsFile] = fs.readFileSync(tsFileAbsolute); fileContents[luaFile] = fs.readFileSync(luaFileAbsolute); @@ -34,14 +34,14 @@ tsFiles.forEach( ); function BufferToTestString(b: Buffer): string { - return b.toString().trim().split("\r\n").join("\n") + return b.toString().trim().split("\r\n").join("\n"); } export class FileTests { @TestCases(files) @Test("Transformation Tests") - public transformationTests(tsFile: string, luaFile:string) { + public transformationTests(tsFile: string, luaFile: string) { Expect(util.transpileString(BufferToTestString(fileContents[tsFile]))) .toEqual(BufferToTestString(fileContents[luaFile])); }