From 62c9a1ee22918336bd14e5b1ae82cadb4400244a Mon Sep 17 00:00:00 2001 From: Lolleko Date: Mon, 25 Feb 2019 23:10:04 +0100 Subject: [PATCH 01/11] Added initial support for sourcemaps --- package-lock.json | 15 +- package.json | 1 + src/LuaAST.ts | 37 ++-- src/LuaPrinter.ts | 471 +++++++++++++++++++++++++++++++----------- src/LuaTransformer.ts | 6 +- src/LuaTranspiler.ts | 22 +- 6 files changed, 409 insertions(+), 143 deletions(-) diff --git a/package-lock.json b/package-lock.json index 420d71821..ef520aec3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2016,10 +2016,9 @@ "dev": true }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" }, "source-map-support": { "version": "0.5.6", @@ -2029,6 +2028,14 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "sprintf-js": { diff --git a/package.json b/package.json index 200f4f278..fa1cf14f0 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "node": ">=8.5.0" }, "dependencies": { + "source-map": "^0.7.3", "typescript": "^3.3.1" }, "devDependencies": { diff --git a/src/LuaAST.ts b/src/LuaAST.ts index a973354ca..4d854ee5d 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -99,10 +99,9 @@ export type Operator = UnaryOperator | BinaryOperator; export type SymbolId = number; -// TODO For future sourcemap support? export interface TextRange { - pos: number; - end: number; + line?: number; + column?: number; } export interface Node extends TextRange { @@ -111,13 +110,16 @@ export interface Node extends TextRange { } export function createNode(kind: SyntaxKind, tsOriginal?: ts.Node, parent?: Node): Node { - let pos = -1; - let end = -1; - if (tsOriginal) { - pos = tsOriginal.pos; - end = tsOriginal.end; + let line: number | undefined; + let column: number | undefined; + // TODO figure out why tsOriginal.getSourceFile() + // can return udnefined in the first place instead of catching it here + if (tsOriginal && tsOriginal.getSourceFile()) { + const lineAndCharacter = ts.getLineAndCharacterOfPosition(tsOriginal.getSourceFile(), tsOriginal.pos); + line = lineAndCharacter.line; + column = lineAndCharacter.character; } - return {kind, parent, pos, end}; + return {kind, parent, line, column}; } export function cloneNode(node: T): T { @@ -125,8 +127,9 @@ export function cloneNode(node: T): T { } export function setNodeOriginal(node: T, tsOriginal: ts.Node): T { - node.pos = tsOriginal.pos; - node.end = tsOriginal.end; + const lineAndCharacter = ts.getLineAndCharacterOfPosition(tsOriginal.getSourceFile(), tsOriginal.pos); + node.line = lineAndCharacter.line; + node.column = lineAndCharacter.character; return node; } @@ -137,16 +140,16 @@ export function setParent(node: Node | Node[] | undefined, parent: Node): void if (Array.isArray(node)) { node.forEach(n => { n.parent = parent; - if (n.pos === -1 || n.end === -1) { - n.pos = parent.pos; - n.end = parent.end; + if (!n.line || !n.column) { + n.line = parent.line; + n.column = parent.column; } }); } else { node.parent = parent; - if (node.pos === -1 || node.end === -1) { - node.pos = parent.pos; - node.end = parent.end; + if (!node.line || !node.column) { + node.line = parent.line; + node.column = parent.column; } } } diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 0f509c593..16a040e52 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -1,5 +1,9 @@ import * as tstl from "./LuaAST"; +import * as path from "path"; + +import {SourceNode} from "source-map"; + import { TSHelper as tsHelper } from "./TSHelper"; import { LuaLibFeature, LuaLib } from "./LuaLib"; import { CompilerOptions } from "./CompilerOptions"; @@ -40,12 +44,34 @@ export class LuaPrinter { private options: CompilerOptions; private currentIndent: string; + private sourceFile: string; + public constructor(options: CompilerOptions) { this.options = options; this.currentIndent = ""; } - public print(block: tstl.Block, luaLibFeatures?: Set): string { + public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile?: string): string { + return this.printImplementation(block, luaLibFeatures, sourceFile).toString(); + } + + public printWithSourceMap( + block: tstl.Block, + luaLibFeatures?: Set, + sourceFile?: string): [string, string] { + + const codeWithMap = + this.printImplementation(block, luaLibFeatures, sourceFile) + // TODO is the file: part really required? and should this be handled in the printer? + .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); + return [codeWithMap.code, codeWithMap.map.toString()]; + } + + private printImplementation( + block: tstl.Block, + luaLibFeatures?: Set, + sourceFile?: string): SourceNode { + let header = ""; if (this.options.noHeader === undefined || this.options.noHeader === false) { @@ -67,7 +93,9 @@ export class LuaPrinter { } } - return header + this.printBlock(block); + this.sourceFile = sourceFile; + + return this.createSourceNode(undefined, undefined, this.sourceFile, [header, this.printBlock(block)]); } private pushIndent(): void { @@ -82,11 +110,27 @@ export class LuaPrinter { return this.currentIndent + input; } - private printBlock(block: tstl.Block): string { - return this.ignoreDeadStatements(block.statements).map(s => this.printStatement(s)).join(""); + private createSourceNode( + line: number | undefined, + column: number | undefined, + sourceFile: string, + chunks: Array<(string | SourceNode)> | SourceNode | string): SourceNode { + + line = line !== undefined ? line + 1 : line; + column = column !== undefined ? column + 1 : column; + + return new SourceNode(line, column, sourceFile, chunks); + } + + private printBlock(block: tstl.Block): SourceNode { + return this.createSourceNode( + undefined, + undefined, + this.sourceFile, + this.ignoreDeadStatements(block.statements).map(s => this.printStatement(s))); } - private printStatement(statement: tstl.Statement): string { + private printStatement(statement: tstl.Statement): SourceNode { switch (statement.kind) { case tstl.SyntaxKind.DoStatement: return this.printDoStatement(statement as tstl.DoStatement); @@ -117,132 +161,222 @@ export class LuaPrinter { } } - private printDoStatement(statement: tstl.DoStatement): string { - let result = this.indent("do\n"); + private printDoStatement(statement: tstl.DoStatement): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + chunks.push(this.indent("do\n")); this.pushIndent(); - result += this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s)).join(""); + chunks.push(...this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s))); this.popIndent(); - result += this.indent("end\n"); + chunks.push(this.indent("end\n")); - return result; + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printVariableDeclarationStatement(statement: tstl.VariableDeclarationStatement): string { - const left = this.indent(`local ${statement.left.map(e => this.printExpression(e)).join(", ")}`); + private printVariableDeclarationStatement(statement: tstl.VariableDeclarationStatement): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + chunks.push("local "); + statement.left.forEach((e, i) => { + if (i < statement.left.length - 1) { + chunks.push(this.printExpression(e), ", "); + } else { + chunks.push(this.printExpression(e)); + } + }); + if (statement.right) { - return left + ` = ${statement.right.map(e => this.printExpression(e)).join(", ")};\n`; - } else { - return left + ";\n"; + chunks.push(" = "); + statement.right.forEach((e, i) => { + if (i < statement.right.length - 1) { + chunks.push(this.printExpression(e), ", "); + } else { + chunks.push(this.printExpression(e)); + } + }); } + chunks.push(";\n"); + + + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printVariableAssignmentStatement(statement: tstl.AssignmentStatement): string { - return this.indent( - `${statement.left.map(e => this.printExpression(e)).join(", ")} = ` + - `${statement.right.map(e => this.printExpression(e)).join(", ")};\n`); + private printVariableAssignmentStatement(statement: tstl.AssignmentStatement): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + statement.left.forEach((e, i) => { + if (i < statement.left.length - 1) { + chunks.push(this.printExpression(e), ", "); + } else { + chunks.push(this.printExpression(e)); + } + }); + + chunks.push(" = "); + statement.right.forEach((e, i) => { + if (i < statement.right.length - 1) { + chunks.push(this.printExpression(e), ", "); + } else { + chunks.push(this.printExpression(e)); + } + }); + chunks.push(";\n"); + + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printIfStatement(statement: tstl.IfStatement, isElseIf?: boolean): string { + private printIfStatement(statement: tstl.IfStatement, isElseIf?: boolean): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + const prefix = isElseIf ? "elseif" : "if"; - let result = this.indent(`${prefix} ${this.printExpression(statement.condtion)} then\n`); + + chunks.push(this.indent(prefix + " "), this.printExpression(statement.condtion), " then\n"); + this.pushIndent(); - result += this.printBlock(statement.ifBlock); + chunks.push(this.printBlock(statement.ifBlock)); this.popIndent(); + if (statement.elseBlock) { if (tstl.isIfStatement(statement.elseBlock)) { - result += this.printIfStatement(statement.elseBlock, true); + chunks.push(this.printIfStatement(statement.elseBlock, true)); } else { - result += this.indent("else\n"); + chunks.push(this.indent("else\n")); this.pushIndent(); - result += this.printBlock(statement.elseBlock); + chunks.push(this.printBlock(statement.elseBlock)); this.popIndent(); - result += this.indent("end\n"); + chunks.push(this.indent("end\n")); } } else { - result += this.indent("end\n"); + chunks.push(this.indent("end\n")); } - return result; + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printWhileStatement(statement: tstl.WhileStatement): string { - let result = this.indent(`while ${this.printExpression(statement.condtion)} do\n`); + private printWhileStatement(statement: tstl.WhileStatement): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.indent("while "), this.printExpression(statement.condtion), " do\n"); + this.pushIndent(); - result += this.printBlock(statement.body); + chunks.push(this.printBlock(statement.body)); this.popIndent(); - result += this.indent("end\n"); - return result; + chunks.push(this.indent("end\n")); + + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printRepeatStatement(statement: tstl.RepeatStatement): string { - let result = this.indent(`repeat\n`); + private printRepeatStatement(statement: tstl.RepeatStatement): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.indent(`repeat\n`)); + this.pushIndent(); - result += this.printBlock(statement.body); + chunks.push(this.printBlock(statement.body)); this.popIndent(); - result += this.indent(`until ${this.printExpression(statement.condtion)};\n`); - return result; + chunks.push(this.indent("until "), this.printExpression(statement.condtion), ";\n"); + + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printForStatement(statement: tstl.ForStatement): string { + private printForStatement(statement: tstl.ForStatement): SourceNode { const ctrlVar = this.printExpression(statement.controlVariable); const ctrlVarInit = this.printExpression(statement.controlVariableInitializer); const limit = this.printExpression(statement.limitExpression); - let result = this.indent(`for ${ctrlVar} = ${ctrlVarInit}, ${limit}`); + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.indent("for "), ctrlVar, " = ", ctrlVarInit, ", ", limit); + if (statement.stepExpression) { - const step = this.printExpression(statement.stepExpression); - result += `, ${step}`; + chunks.push(", ", this.printExpression(statement.stepExpression)); } - result += ` do\n`; + chunks.push(" do\n"); this.pushIndent(); - result += this.printBlock(statement.body); + chunks.push(this.printBlock(statement.body)); this.popIndent(); - result += this.indent("end\n"); - return result; + chunks.push(this.indent("end\n")); + + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printForInStatement(statement: tstl.ForInStatement): string { + private printForInStatement(statement: tstl.ForInStatement): SourceNode { const names = statement.names.map(i => this.printIdentifier(i)).join(", "); const expressions = statement.expressions.map(e => this.printExpression(e)).join(", "); - let result = this.indent(`for ${names} in ${expressions} do\n`); + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.indent("for "), names, " in ", expressions, " do\n"); + this.pushIndent(); - result += this.printBlock(statement.body); + chunks.push(this.printBlock(statement.body)); this.popIndent(); - result += this.indent("end\n"); + chunks.push(this.indent("end\n")); - return result; + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printGotoStatement(statement: tstl.GotoStatement): string { - return this.indent(`goto ${statement.label};\n`); + private printGotoStatement(statement: tstl.GotoStatement): SourceNode { + return this.createSourceNode( + statement.line, + statement.column, + this.sourceFile, + [this.indent("goto "), statement.label, ";\n"]); } - private printLabelStatement(statement: tstl.LabelStatement): string { - return this.indent(`::${statement.name}::\n`); + private printLabelStatement(statement: tstl.LabelStatement): SourceNode { + return this.createSourceNode( + statement.line, + statement.column, + this.sourceFile, + [this.indent("::"), statement.name, "::\n"]); } - private printReturnStatement(statement: tstl.ReturnStatement): string { + private printReturnStatement(statement: tstl.ReturnStatement): SourceNode { if (!statement.expressions) { - return this.indent(`return;\n`); + return this.createSourceNode( + statement.line, + statement.column, + this.sourceFile, + this.indent("return;\n")); } - return this.indent(`return ${statement.expressions.map(e => this.printExpression(e)).join(", ")};\n`); + + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.indent("return ")); + + statement.expressions.forEach((e, i) => { + if (i < statement.expressions.length - 1) { + chunks.push(this.printExpression(e), ", "); + } else { + chunks.push(this.printExpression(e)); + } + }); + + chunks.push(";\n"); + + return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); } - private printBreakStatement(statement: tstl.BreakStatement): string { - return this.indent("break;\n"); + private printBreakStatement(statement: tstl.BreakStatement): SourceNode { + return this.createSourceNode( + statement.line, + statement.column, + this.sourceFile, + this.indent("break;\n")); } - private printExpressionStatement(statement: tstl.ExpressionStatement): string { - return this.indent(`${this.printExpression(statement.expression)};\n`); + private printExpressionStatement(statement: tstl.ExpressionStatement): SourceNode { + return this.createSourceNode( + statement.line, + statement.column, + this.sourceFile, + [this.printExpression(statement.expression), ";\n"]); } // Expressions - private printExpression(expression: tstl.Expression): string { + private printExpression(expression: tstl.Expression): SourceNode { switch (expression.kind) { case tstl.SyntaxKind.StringLiteral: return this.printStringLiteral(expression as tstl.StringLiteral); @@ -278,85 +412,149 @@ export class LuaPrinter { } } - private printStringLiteral(expression: tstl.StringLiteral): string { - return `"${expression.value}"`; + private printStringLiteral(expression: tstl.StringLiteral): SourceNode { + return this.createSourceNode( + expression.line, + expression.column, + this.sourceFile, + expression.value); } - private printNumericLiteral(expression: tstl.NumericLiteral): string { - return `${expression.value}`; + private printNumericLiteral(expression: tstl.NumericLiteral): SourceNode { + return this.createSourceNode( + expression.line, + expression.column, + this.sourceFile, + String(expression.value)); } - private printNilLiteral(expression: tstl.NilLiteral): string { - return "nil"; + private printNilLiteral(expression: tstl.NilLiteral): SourceNode { + return this.createSourceNode(expression.line, + expression.column, + this.sourceFile, + "nil"); } - private printDotsLiteral(expression: tstl.DotsLiteral): string { - return "..."; + private printDotsLiteral(expression: tstl.DotsLiteral): SourceNode { + return this.createSourceNode( + expression.line, + expression.column, + this.sourceFile, + "..."); } - private printBooleanLiteral(expression: tstl.BooleanLiteral): string { + private printBooleanLiteral(expression: tstl.BooleanLiteral): SourceNode { if (expression.kind === tstl.SyntaxKind.TrueKeyword) { - return "true"; + return this.createSourceNode( + expression.line, + expression.column, + this.sourceFile, + "true"); } else { - return "false"; + return this.createSourceNode( + expression.line, + expression.column, + this.sourceFile, + "false"); } } - private printFunctionExpression(expression: tstl.FunctionExpression): string { - const paramterArr: string[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; + private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { + const paramterArr: SourceNode[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; if (expression.dots) { paramterArr.push(this.printDotsLiteral(expression.dots)); } - let result = `function(${paramterArr.join(", ")})\n`; + const chunks: Array<(string | SourceNode)> = []; + + chunks.push("function("); + paramterArr.forEach((p, i) => { + if (i < paramterArr.length - 1) { + chunks.push(p, ", "); + } else { + chunks.push(p); + } + }); + chunks.push(")\n"); + this.pushIndent(); - result += this.printBlock(expression.body); + chunks.push(this.printBlock(expression.body)); this.popIndent(); - result += this.indent("end"); + chunks.push(this.indent("end")); - return result; + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } - private printTableFieldExpression(expression: tstl.TableFieldExpression): string { + private printTableFieldExpression(expression: tstl.TableFieldExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + const value = this.printExpression(expression.value); if (expression.key) { if (tstl.isStringLiteral(expression.key) && tsHelper.isValidLuaIdentifier(expression.key.value)) { - return `${expression.key.value} = ${value}`; + chunks.push(expression.key.value, " = ", value); } else { - return `[${this.printExpression(expression.key)}] = ${value}`; + chunks.push("[", this.printExpression(expression.key), "] = ", value); } } else { - return value; + chunks.push(value); } + + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } - private printTableExpression(expression: tstl.TableExpression): string { - let fields = ""; + private printTableExpression(expression: tstl.TableExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + chunks.push("{"); + if (expression.fields) { - fields = expression.fields.map(f => this.printTableFieldExpression(f)).join(", "); + expression.fields.forEach((f, i) => { + if (i < expression.fields.length - 1) { + chunks.push(this.printTableFieldExpression(f), ", "); + } else { + chunks.push(this.printTableFieldExpression(f)); + } + }); } - return `{${fields}}`; + + chunks.push("}"); + + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } - private printUnaryExpression(expression: tstl.UnaryExpression): string { - const operand = this.needsParentheses(expression.operand) - ? `(${this.printExpression(expression.operand)})` - : this.printExpression(expression.operand); - return `${this.printOperator(expression.operator)}${operand}`; + private printUnaryExpression(expression: tstl.UnaryExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.printOperator(expression.operator)); + + if (this.needsParentheses(expression.operand)) { + chunks.push("(", this.printExpression(expression.operand), ")"); + } else { + chunks.push(this.printExpression(expression.operand)); + } + + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } - private printBinaryExpression(expression: tstl.BinaryExpression): string { - const left = this.needsParentheses(expression.left) - ? `(${this.printExpression(expression.left)})` - : this.printExpression(expression.left); + private printBinaryExpression(expression: tstl.BinaryExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; - const right = this.needsParentheses(expression.right) - ? `(${this.printExpression(expression.right)})` - : this.printExpression(expression.right); + if (this.needsParentheses(expression.left)) { + chunks.push("(", this.printExpression(expression.left), ")"); + } else { + chunks.push(this.printExpression(expression.left)); + } - const operator = this.printOperator(expression.operator); - return `${left} ${operator} ${right}`; + chunks.push(" ", this.printOperator(expression.operator), " "); + + if (this.needsParentheses(expression.right)) { + chunks.push("(", this.printExpression(expression.right), ")"); + } else { + chunks.push(this.printExpression(expression.right)); + } + + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } private needsParentheses(expression: tstl.Expression): boolean { @@ -364,34 +562,71 @@ export class LuaPrinter { || tstl.isFunctionExpression(expression); } - private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): string { - return `(${this.printExpression(expression.innerEpxression)})`; + private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): SourceNode { + return this.createSourceNode( + expression.line, + expression.column, + this.sourceFile, + ["(", this.printExpression(expression.innerEpxression), ")"]); } - private printCallExpression(expression: tstl.CallExpression): string { - const params = expression.params ? expression.params.map(e => this.printExpression(e)).join(", ") : ""; - return this.needsParentheses(expression.expression) - ? `(${this.printExpression(expression.expression)})(${params})` - : `${this.printExpression(expression.expression)}(${params})`; + private printCallExpression(expression: tstl.CallExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + const params: Array<(string | SourceNode)> = []; + + expression.params.forEach((p, i) => { + if (i < expression.params.length - 1) { + params.push(this.printExpression(p), ", "); + } else { + params.push(this.printExpression(p)); + } + }); + + if (this.needsParentheses(expression.expression)) { + chunks.push("(", this.printExpression(expression.expression), ")(", ...params, ")"); + } else { + chunks.push(this.printExpression(expression.expression), "(", ...params, ")"); + } + + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } - private printMethodCallExpression(expression: tstl.MethodCallExpression): string { - const params = expression.params.map(e => this.printExpression(e)).join(", "); + private printMethodCallExpression(expression: tstl.MethodCallExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + const params: Array<(string | SourceNode)> = []; + + expression.params.forEach((p, i) => { + if (i < expression.params.length - 1) { + params.push(this.printExpression(p), ", "); + } else { + params.push(this.printExpression(p)); + } + }); + const prefix = this.printExpression(expression.prefixExpression); const name = this.printIdentifier(expression.name); - return `${prefix}:${name}(${params})`; + + chunks.push(prefix, ":", name, "(", ...params, ")"); + + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } - private printIdentifier(expression: tstl.Identifier): string { - return expression.text; + private printIdentifier(expression: tstl.Identifier): SourceNode { + return this.createSourceNode(expression.line, expression.column, this.sourceFile, expression.text); } - private printTableIndexExpression(expression: tstl.TableIndexExpression): string { - const table = this.printExpression(expression.table); + private printTableIndexExpression(expression: tstl.TableIndexExpression): SourceNode { + const chunks: Array<(string | SourceNode)> = []; + + chunks.push(this.printExpression(expression.table)); if (tstl.isStringLiteral(expression.index) && tsHelper.isValidLuaIdentifier(expression.index.value)) { - return `${table}.${expression.index.value}`; + chunks.push(".", expression.index.value); + } else { + chunks.push("[", this.printExpression(expression.index), "]"); } - return `${table}[${this.printExpression(expression.index)}]`; + return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); } private printOperator(kind: tstl.Operator): string { diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 1fd4e46ff..c3b01e7ca 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -4243,8 +4243,12 @@ export class LuaTransformer { if (scope.functionDefinitions) { for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { + const assignmentPos = ts.getPositionOfLineAndCharacter( + this.currentSourceFile, + functionDefinition.assignment.line, + functionDefinition.assignment.column); if (functionSymbolId !== symbolId // Don't recurse into self - && declaration.pos < functionDefinition.assignment.pos // Ignore functions before symbol declaration + && declaration.pos < assignmentPos // Ignore functions before symbol declaration && functionDefinition.referencedSymbols.has(symbolId) && this.shouldHoist(functionSymbolId, scope)) { diff --git a/src/LuaTranspiler.ts b/src/LuaTranspiler.ts index a4a4416a2..9e09ee08b 100644 --- a/src/LuaTranspiler.ts +++ b/src/LuaTranspiler.ts @@ -89,7 +89,13 @@ export class LuaTranspiler { try { const rootDir = this.options.rootDir; - const lua = this.transpileSourceFile(sourceFile); + let lua, sourceMap; + + if (this.options.sourceMap) { + [lua, sourceMap] = this.transpileSourceFileWithSourceMap(sourceFile); + } else { + lua = this.transpileSourceFile(sourceFile); + } let outPath = sourceFile.fileName; if (this.options.outDir !== this.options.rootDir) { @@ -112,6 +118,9 @@ export class LuaTranspiler { // Write output ts.sys.writeFile(outPath, lua); + if (this.options.sourceMap) { + ts.sys.writeFile(outPath + ".map", sourceMap); + } } catch (exception) { /* istanbul ignore else: Testing else part would require to add a bug/exception to our code */ if (exception.node) { @@ -130,14 +139,21 @@ export class LuaTranspiler { // Transform AST const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); // Print AST - return this.luaPrinter.print(luaAST, lualibFeatureSet); + return this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); + } + + public transpileSourceFileWithSourceMap(sourceFile: ts.SourceFile): [string, string] { + // Transform AST + const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); + // Print AST + return this.luaPrinter.printWithSourceMap(luaAST, lualibFeatureSet, sourceFile.fileName); } public transpileSourceFileKeepAST(sourceFile: ts.SourceFile): [tstl.Block, string] { // Transform AST const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); // Print AST - return [luaAST, this.luaPrinter.print(luaAST, lualibFeatureSet)]; + return [luaAST, this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName)]; } public reportDiagnostic(diagnostic: ts.Diagnostic): void { From 7e918f656ad76da90a3c621e891b6606c5923f15 Mon Sep 17 00:00:00 2001 From: Perry van Wesel Date: Wed, 6 Mar 2019 11:58:09 +0100 Subject: [PATCH 02/11] Made some adjustments to source maps (#466) * Made some adjustments to source maps * Fixed tests * Removed tsOriginal field from AST nodes --- src/LuaAST.ts | 55 +++--- src/LuaPrinter.ts | 286 +++++++++++--------------------- src/LuaTransformer.ts | 122 ++++++++------ test/compiler/watchmode.spec.ts | 6 +- 4 files changed, 209 insertions(+), 260 deletions(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index 4d854ee5d..679f88bd7 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -110,16 +110,12 @@ export interface Node extends TextRange { } export function createNode(kind: SyntaxKind, tsOriginal?: ts.Node, parent?: Node): Node { - let line: number | undefined; - let column: number | undefined; - // TODO figure out why tsOriginal.getSourceFile() - // can return udnefined in the first place instead of catching it here - if (tsOriginal && tsOriginal.getSourceFile()) { - const lineAndCharacter = ts.getLineAndCharacterOfPosition(tsOriginal.getSourceFile(), tsOriginal.pos); - line = lineAndCharacter.line; - column = lineAndCharacter.character; + const sourcePosition = getSourcePosition(tsOriginal); + if (sourcePosition) { + return {kind, parent, line: sourcePosition.line, column: sourcePosition.column}; + } else { + return {kind, parent}; } - return {kind, parent, line, column}; } export function cloneNode(node: T): T { @@ -127,9 +123,12 @@ export function cloneNode(node: T): T { } export function setNodeOriginal(node: T, tsOriginal: ts.Node): T { - const lineAndCharacter = ts.getLineAndCharacterOfPosition(tsOriginal.getSourceFile(), tsOriginal.pos); - node.line = lineAndCharacter.line; - node.column = lineAndCharacter.character; + const sourcePosition = getSourcePosition(tsOriginal); + if (sourcePosition) { + node.line = sourcePosition.line; + node.line = sourcePosition.line; + } + return node; } @@ -140,20 +139,32 @@ export function setParent(node: Node | Node[] | undefined, parent: Node): void if (Array.isArray(node)) { node.forEach(n => { n.parent = parent; - if (!n.line || !n.column) { - n.line = parent.line; - n.column = parent.column; - } }); } else { node.parent = parent; - if (!node.line || !node.column) { - node.line = parent.line; - node.column = parent.column; - } } } +function getSourcePosition(sourceNode: ts.Node): TextRange | undefined { + if (sourceNode !== undefined && sourceNode.getSourceFile() !== undefined && sourceNode.pos >= 0) { + + const { line, character } = ts.getLineAndCharacterOfPosition( + sourceNode.getSourceFile(), + sourceNode.pos + sourceNode.getLeadingTriviaWidth() + ); + + return { line, column: character }; + } +} + +export function getOriginalPos(node: Node): TextRange { + while (node.line === undefined && node.parent !== undefined) { + node = node.parent; + } + + return { line: node.line, column: node.column }; +} + export interface Block extends Node { kind: SyntaxKind.Block; statements?: Statement[]; @@ -816,8 +827,8 @@ export function createIdentifier( return expression; } -export function cloneIdentifier(identifier: Identifier): Identifier { - return createIdentifier(identifier.text, undefined, identifier.symbolId); +export function cloneIdentifier(identifier: Identifier, tsOriginal?: ts.Node): Identifier { + return createIdentifier(identifier.text, tsOriginal, identifier.symbolId); } export function createAnnonymousIdentifier(tsOriginal?: ts.Node, parent?: Node): Identifier { diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 16a040e52..d69c0a6d3 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -9,6 +9,8 @@ import { LuaLibFeature, LuaLib } from "./LuaLib"; import { CompilerOptions } from "./CompilerOptions"; import { LuaLibImportKind } from "./CompilerOptions"; +type SourceChunk = string | SourceNode; + export class LuaPrinter { /* tslint:disable:object-literal-sort-keys */ private static operatorMap: {[key in tstl.Operator]: string} = { @@ -60,7 +62,7 @@ export class LuaPrinter { luaLibFeatures?: Set, sourceFile?: string): [string, string] { - const codeWithMap = + const codeWithMap = this.printImplementation(block, luaLibFeatures, sourceFile) // TODO is the file: part really required? and should this be handled in the printer? .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); @@ -93,9 +95,11 @@ export class LuaPrinter { } } - this.sourceFile = sourceFile; + this.sourceFile = path.basename(sourceFile); + + const blockNode = this.createSourceNode(block, this.printBlock(block)); - return this.createSourceNode(undefined, undefined, this.sourceFile, [header, this.printBlock(block)]); + return this.concatNodes(header, blockNode); } private pushIndent(): void { @@ -106,28 +110,27 @@ export class LuaPrinter { this.currentIndent = this.currentIndent.slice(4); } - private indent(input: string): string { - return this.currentIndent + input; + private indent(input: SourceChunk = ""): SourceChunk { + return this.concatNodes(this.currentIndent, input); } - private createSourceNode( - line: number | undefined, - column: number | undefined, - sourceFile: string, - chunks: Array<(string | SourceNode)> | SourceNode | string): SourceNode { + private createSourceNode(node: tstl.Node, chunks: SourceChunk | SourceChunk[]): SourceNode { + const originalPos = tstl.getOriginalPos(node); - line = line !== undefined ? line + 1 : line; - column = column !== undefined ? column + 1 : column; + return originalPos !== undefined + ? new SourceNode(originalPos.line + 1, originalPos.column, this.sourceFile, chunks) + : new SourceNode(undefined, undefined, this.sourceFile, chunks); + } - return new SourceNode(line, column, sourceFile, chunks); + private concatNodes(...chunks: SourceChunk[]): SourceNode { + return new SourceNode(undefined, undefined, this.sourceFile, chunks); } private printBlock(block: tstl.Block): SourceNode { return this.createSourceNode( - undefined, - undefined, - this.sourceFile, - this.ignoreDeadStatements(block.statements).map(s => this.printStatement(s))); + block, + this.ignoreDeadStatements(block.statements).map(s => this.printStatement(s)) + ); } private printStatement(statement: tstl.Statement): SourceNode { @@ -158,72 +161,50 @@ export class LuaPrinter { return this.printBreakStatement(statement as tstl.BreakStatement); case tstl.SyntaxKind.ExpressionStatement: return this.printExpressionStatement(statement as tstl.ExpressionStatement); + default: + throw new Error(`Tried to print unknown statement kind: ${tstl.SyntaxKind[statement.kind]}`); } } private printDoStatement(statement: tstl.DoStatement): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.indent("do\n")); this.pushIndent(); chunks.push(...this.ignoreDeadStatements(statement.statements).map(s => this.printStatement(s))); this.popIndent(); chunks.push(this.indent("end\n")); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printVariableDeclarationStatement(statement: tstl.VariableDeclarationStatement): SourceNode { - const chunks: Array<(string | SourceNode)> = []; - chunks.push("local "); - statement.left.forEach((e, i) => { - if (i < statement.left.length - 1) { - chunks.push(this.printExpression(e), ", "); - } else { - chunks.push(this.printExpression(e)); - } - }); + const chunks: SourceChunk[] = []; + chunks.push(this.indent("local ")); + chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e)))); if (statement.right) { chunks.push(" = "); - statement.right.forEach((e, i) => { - if (i < statement.right.length - 1) { - chunks.push(this.printExpression(e), ", "); - } else { - chunks.push(this.printExpression(e)); - } - }); + chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e)))); } chunks.push(";\n"); - - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printVariableAssignmentStatement(statement: tstl.AssignmentStatement): SourceNode { - const chunks: Array<(string | SourceNode)> = []; - statement.left.forEach((e, i) => { - if (i < statement.left.length - 1) { - chunks.push(this.printExpression(e), ", "); - } else { - chunks.push(this.printExpression(e)); - } - }); + const chunks: SourceChunk[] = []; + chunks.push(this.indent()); + chunks.push(...this.joinChunks(", ", statement.left.map(e => this.printExpression(e)))); chunks.push(" = "); - statement.right.forEach((e, i) => { - if (i < statement.right.length - 1) { - chunks.push(this.printExpression(e), ", "); - } else { - chunks.push(this.printExpression(e)); - } - }); + chunks.push(...this.joinChunks(", ", statement.right.map(e => this.printExpression(e)))); chunks.push(";\n"); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.createSourceNode(statement, chunks); } private printIfStatement(statement: tstl.IfStatement, isElseIf?: boolean): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; const prefix = isElseIf ? "elseif" : "if"; @@ -247,11 +228,11 @@ export class LuaPrinter { chunks.push(this.indent("end\n")); } - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printWhileStatement(statement: tstl.WhileStatement): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.indent("while "), this.printExpression(statement.condtion), " do\n"); @@ -261,11 +242,11 @@ export class LuaPrinter { chunks.push(this.indent("end\n")); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printRepeatStatement(statement: tstl.RepeatStatement): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.indent(`repeat\n`)); @@ -275,7 +256,7 @@ export class LuaPrinter { chunks.push(this.indent("until "), this.printExpression(statement.condtion), ";\n"); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printForStatement(statement: tstl.ForStatement): SourceNode { @@ -283,7 +264,7 @@ export class LuaPrinter { const ctrlVarInit = this.printExpression(statement.controlVariableInitializer); const limit = this.printExpression(statement.limitExpression); - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.indent("for "), ctrlVar, " = ", ctrlVarInit, ", ", limit); @@ -298,14 +279,14 @@ export class LuaPrinter { chunks.push(this.indent("end\n")); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printForInStatement(statement: tstl.ForInStatement): SourceNode { const names = statement.names.map(i => this.printIdentifier(i)).join(", "); const expressions = statement.expressions.map(e => this.printExpression(e)).join(", "); - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.indent("for "), names, " in ", expressions, " do\n"); @@ -314,65 +295,37 @@ export class LuaPrinter { this.popIndent(); chunks.push(this.indent("end\n")); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.createSourceNode(statement, chunks); } private printGotoStatement(statement: tstl.GotoStatement): SourceNode { - return this.createSourceNode( - statement.line, - statement.column, - this.sourceFile, - [this.indent("goto "), statement.label, ";\n"]); + return this.createSourceNode(statement, [this.indent("goto "), statement.label, ";\n"]); } private printLabelStatement(statement: tstl.LabelStatement): SourceNode { - return this.createSourceNode( - statement.line, - statement.column, - this.sourceFile, - [this.indent("::"), statement.name, "::\n"]); + return this.createSourceNode(statement, [this.indent("::"), statement.name, "::\n"]); } private printReturnStatement(statement: tstl.ReturnStatement): SourceNode { - if (!statement.expressions) { - return this.createSourceNode( - statement.line, - statement.column, - this.sourceFile, - this.indent("return;\n")); + if (!statement.expressions || statement.expressions.length === 0) { + return this.createSourceNode(statement, this.indent("return;\n")); } - const chunks: Array<(string | SourceNode)> = []; - - chunks.push(this.indent("return ")); + const chunks: SourceChunk[] = []; - statement.expressions.forEach((e, i) => { - if (i < statement.expressions.length - 1) { - chunks.push(this.printExpression(e), ", "); - } else { - chunks.push(this.printExpression(e)); - } - }); + chunks.push(...this.joinChunks(", ", statement.expressions.map(e => this.printExpression(e)))); chunks.push(";\n"); - return this.createSourceNode(statement.line, statement.column, this.sourceFile, chunks); + return this.createSourceNode(statement, [this.indent(), "return ", ...chunks]); } private printBreakStatement(statement: tstl.BreakStatement): SourceNode { - return this.createSourceNode( - statement.line, - statement.column, - this.sourceFile, - this.indent("break;\n")); + return this.createSourceNode(statement, this.indent("break;\n")); } private printExpressionStatement(statement: tstl.ExpressionStatement): SourceNode { - return this.createSourceNode( - statement.line, - statement.column, - this.sourceFile, - [this.printExpression(statement.expression), ";\n"]); + return this.concatNodes(this.indent(), this.printExpression(statement.expression), ";\n"); } // Expressions @@ -409,72 +362,48 @@ export class LuaPrinter { return this.printIdentifier(expression as tstl.Identifier); case tstl.SyntaxKind.TableIndexExpression: return this.printTableIndexExpression(expression as tstl.TableIndexExpression); + default: + throw new Error(`Tried to print unknown statement kind: ${tstl.SyntaxKind[expression.kind]}`); } } private printStringLiteral(expression: tstl.StringLiteral): SourceNode { - return this.createSourceNode( - expression.line, - expression.column, - this.sourceFile, - expression.value); + return this.createSourceNode(expression, `"${expression.value}"`); } private printNumericLiteral(expression: tstl.NumericLiteral): SourceNode { - return this.createSourceNode( - expression.line, - expression.column, - this.sourceFile, - String(expression.value)); + return this.createSourceNode(expression, String(expression.value)); } private printNilLiteral(expression: tstl.NilLiteral): SourceNode { - return this.createSourceNode(expression.line, - expression.column, - this.sourceFile, - "nil"); + return this.createSourceNode(expression, "nil"); } private printDotsLiteral(expression: tstl.DotsLiteral): SourceNode { - return this.createSourceNode( - expression.line, - expression.column, - this.sourceFile, - "..."); + return this.createSourceNode(expression, "..."); } private printBooleanLiteral(expression: tstl.BooleanLiteral): SourceNode { if (expression.kind === tstl.SyntaxKind.TrueKeyword) { - return this.createSourceNode( - expression.line, - expression.column, - this.sourceFile, - "true"); + return this.createSourceNode(expression, "true"); } else { - return this.createSourceNode( - expression.line, - expression.column, - this.sourceFile, - "false"); + return this.createSourceNode(expression, "false"); } } private printFunctionExpression(expression: tstl.FunctionExpression): SourceNode { - const paramterArr: SourceNode[] = expression.params ? expression.params.map(i => this.printIdentifier(i)) : []; + const parameterChunks: SourceNode[] = expression.params + ? expression.params.map(i => this.printIdentifier(i)) + : []; + if (expression.dots) { - paramterArr.push(this.printDotsLiteral(expression.dots)); + parameterChunks.push(this.printDotsLiteral(expression.dots)); } - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push("function("); - paramterArr.forEach((p, i) => { - if (i < paramterArr.length - 1) { - chunks.push(p, ", "); - } else { - chunks.push(p); - } - }); + chunks.push(...this.joinChunks(", ", parameterChunks)); chunks.push(")\n"); this.pushIndent(); @@ -482,11 +411,11 @@ export class LuaPrinter { this.popIndent(); chunks.push(this.indent("end")); - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.createSourceNode(expression, chunks); } private printTableFieldExpression(expression: tstl.TableFieldExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; const value = this.printExpression(expression.value); @@ -500,11 +429,11 @@ export class LuaPrinter { chunks.push(value); } - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.createSourceNode(expression, chunks); } private printTableExpression(expression: tstl.TableExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push("{"); @@ -520,11 +449,11 @@ export class LuaPrinter { chunks.push("}"); - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.createSourceNode(expression, chunks); } private printUnaryExpression(expression: tstl.UnaryExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.printOperator(expression.operator)); @@ -534,11 +463,11 @@ export class LuaPrinter { chunks.push(this.printExpression(expression.operand)); } - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.createSourceNode(expression, chunks); } private printBinaryExpression(expression: tstl.BinaryExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; if (this.needsParentheses(expression.left)) { chunks.push("(", this.printExpression(expression.left), ")"); @@ -554,7 +483,7 @@ export class LuaPrinter { chunks.push(this.printExpression(expression.right)); } - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.createSourceNode(expression, chunks); } private needsParentheses(expression: tstl.Expression): boolean { @@ -563,70 +492,44 @@ export class LuaPrinter { } private printParenthesizedExpression(expression: tstl.ParenthesizedExpression): SourceNode { - return this.createSourceNode( - expression.line, - expression.column, - this.sourceFile, - ["(", this.printExpression(expression.innerEpxression), ")"]); + return this.createSourceNode(expression, ["(", this.printExpression(expression.innerEpxression), ")"]); } private printCallExpression(expression: tstl.CallExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; - - const params: Array<(string | SourceNode)> = []; - - expression.params.forEach((p, i) => { - if (i < expression.params.length - 1) { - params.push(this.printExpression(p), ", "); - } else { - params.push(this.printExpression(p)); - } - }); + const chunks = []; + const parameterChunks = this.joinChunks(", ", expression.params.map(e => this.printExpression(e))); if (this.needsParentheses(expression.expression)) { - chunks.push("(", this.printExpression(expression.expression), ")(", ...params, ")"); + chunks.push("(", this.printExpression(expression.expression), ")(", ...parameterChunks, ")"); } else { - chunks.push(this.printExpression(expression.expression), "(", ...params, ")"); + chunks.push(this.printExpression(expression.expression), "(", ...parameterChunks, ")"); } - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.concatNodes(...chunks); } private printMethodCallExpression(expression: tstl.MethodCallExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; - - const params: Array<(string | SourceNode)> = []; - - expression.params.forEach((p, i) => { - if (i < expression.params.length - 1) { - params.push(this.printExpression(p), ", "); - } else { - params.push(this.printExpression(p)); - } - }); - const prefix = this.printExpression(expression.prefixExpression); + const parameterChunks = this.joinChunks(", ", expression.params.map(e => this.printExpression(e))); const name = this.printIdentifier(expression.name); - chunks.push(prefix, ":", name, "(", ...params, ")"); - - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.concatNodes(prefix, ":", name, "(", ...parameterChunks, ")"); } private printIdentifier(expression: tstl.Identifier): SourceNode { - return this.createSourceNode(expression.line, expression.column, this.sourceFile, expression.text); + return this.createSourceNode(expression, expression.text); } private printTableIndexExpression(expression: tstl.TableIndexExpression): SourceNode { - const chunks: Array<(string | SourceNode)> = []; + const chunks: SourceChunk[] = []; chunks.push(this.printExpression(expression.table)); if (tstl.isStringLiteral(expression.index) && tsHelper.isValidLuaIdentifier(expression.index.value)) { - chunks.push(".", expression.index.value); + chunks.push(".", this.createSourceNode(expression.index, expression.index.value)); } else { chunks.push("[", this.printExpression(expression.index), "]"); } - return this.createSourceNode(expression.line, expression.column, this.sourceFile, chunks); + return this.createSourceNode(expression, chunks); } private printOperator(kind: tstl.Operator): string { @@ -643,4 +546,15 @@ export class LuaPrinter { } return aliveStatements; } + + private joinChunks(separator: string, chunks: SourceChunk[]): SourceChunk[] { + const result = []; + for (let i = 0; i < chunks.length; i++) { + result.push(chunks[i]); + if (i < chunks.length - 1) { + result.push(separator); + } + } + return result; + } } diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index c3b01e7ca..9bb5ebea5 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -503,7 +503,8 @@ export class LuaTransformer { const fieldAssign = tstl.createAssignmentStatement( classField, - value + value, + field ); result.push(fieldAssign); @@ -590,12 +591,13 @@ export class LuaTransformer { const result: tstl.Statement[] = []; // className = className or {} - let classTable: tstl.Expression = tstl.createTableExpression(); + let classTable: tstl.Expression = tstl.createTableExpression([], statement); if (!noClassOr) { classTable = tstl.createBinaryExpression( this.addExportToIdentifier(className), // Use original identifier node in declaration classTable, - tstl.SyntaxKind.OrOperator + tstl.SyntaxKind.OrOperator, + statement ); } @@ -608,7 +610,8 @@ export class LuaTransformer { if (statement.members.some(m => ts.isGetAccessor(m) && tsHelper.isStatic(m))) { const classGetters = tstl.createTableIndexExpression( createClassNameWithExport(), - tstl.createStringLiteral("____getters") + tstl.createStringLiteral("____getters"), + statement ); const assignClassGetters = tstl.createAssignmentStatement( classGetters, @@ -623,7 +626,8 @@ export class LuaTransformer { // className.__index = className const classIndex = tstl.createTableIndexExpression( createClassNameWithExport(), - tstl.createStringLiteral("__index") + tstl.createStringLiteral("__index"), + statement ); const assignClassIndex = tstl.createAssignmentStatement(classIndex, createClassNameWithExport(), statement); result.push(assignClassIndex); @@ -647,14 +651,16 @@ export class LuaTransformer { // className.prototype = className.prototype or {} const createClassPrototype = () => tstl.createTableIndexExpression( createClassNameWithExport(), - tstl.createStringLiteral("prototype") + tstl.createStringLiteral("prototype"), + statement ); const classPrototypeTable = noClassOr - ? tstl.createTableExpression() + ? tstl.createTableExpression([], statement) : tstl.createBinaryExpression( createClassPrototype(), tstl.createTableExpression(), - tstl.SyntaxKind.OrOperator + tstl.SyntaxKind.OrOperator, + statement ); const assignClassPrototype = tstl.createAssignmentStatement(createClassPrototype(), classPrototypeTable); result.push(assignClassPrototype); @@ -663,11 +669,13 @@ export class LuaTransformer { if (statement.members.some(m => ts.isGetAccessor(m) && !tsHelper.isStatic(m))) { const classPrototypeGetters = tstl.createTableIndexExpression( createClassPrototype(), - tstl.createStringLiteral("____getters") + tstl.createStringLiteral("____getters"), + statement ); const assignClassPrototypeGetters = tstl.createAssignmentStatement( classPrototypeGetters, - tstl.createTableExpression() + tstl.createTableExpression(), + statement ); result.push(assignClassPrototypeGetters); } @@ -680,7 +688,8 @@ export class LuaTransformer { // className.prototype.__index = __TS_Index(className.prototype) const assignClassPrototypeIndex = tstl.createAssignmentStatement( classPrototypeIndex, - this.transformLuaLibFunction(LuaLibFeature.Index, undefined, createClassPrototype()) + this.transformLuaLibFunction(LuaLibFeature.Index, undefined, createClassPrototype()), + statement ); result.push(assignClassPrototypeIndex); @@ -688,7 +697,8 @@ export class LuaTransformer { // className.prototype.__index = className.prototype const assignClassPrototypeIndex = tstl.createAssignmentStatement( classPrototypeIndex, - createClassPrototype() + createClassPrototype(), + statement ); result.push(assignClassPrototypeIndex); } @@ -697,11 +707,13 @@ export class LuaTransformer { // className.prototype.____setters = {} const classPrototypeSetters = tstl.createTableIndexExpression( createClassPrototype(), - tstl.createStringLiteral("____setters") + tstl.createStringLiteral("____setters"), + statement ); const assignClassPrototypeSetters = tstl.createAssignmentStatement( classPrototypeSetters, - tstl.createTableExpression() + tstl.createTableExpression(), + statement ); result.push(assignClassPrototypeSetters); } @@ -741,7 +753,8 @@ export class LuaTransformer { // className.____super = baseName const createClassBase = () => tstl.createTableIndexExpression( createClassNameWithExport(), - tstl.createStringLiteral("____super") + tstl.createStringLiteral("____super"), + statement ); const assignClassBase = tstl.createAssignmentStatement(createClassBase(), baseName, statement); result.push(assignClassBase); @@ -795,13 +808,15 @@ export class LuaTransformer { // setmetatable(className.prototype, className.____super.prototype) const basePrototype = tstl.createTableIndexExpression( createClassBase(), - tstl.createStringLiteral("prototype") + tstl.createStringLiteral("prototype"), + statement ); const setClassPrototypeMetatable = tstl.createExpressionStatement( tstl.createCallExpression( tstl.createIdentifier("setmetatable"), [createClassPrototype(), basePrototype] - ) + ), + statement ); result.push(setClassPrototypeMetatable); @@ -844,7 +859,8 @@ export class LuaTransformer { tstl.createCallExpression( tstl.createIdentifier("setmetatable"), [tstl.createTableExpression(), createClassPrototype()] - ) + ), + statement ); newFuncStatements.push(assignSelf); @@ -854,12 +870,13 @@ export class LuaTransformer { this.createSelfIdentifier(), tstl.createIdentifier("____constructor"), [tstl.createDotsLiteral()] - ) + ), + statement ); newFuncStatements.push(callConstructor); // return self - const returnSelf = tstl.createReturnStatement([this.createSelfIdentifier()]); + const returnSelf = tstl.createReturnStatement([this.createSelfIdentifier()], statement); newFuncStatements.push(returnSelf); // function className.new(construct, ...) ... end @@ -874,7 +891,8 @@ export class LuaTransformer { tstl.createDotsLiteral(), undefined, statement - ) + ), + statement ); result.push(newFunc); @@ -898,7 +916,7 @@ export class LuaTransformer { const selfIndex = tstl.createTableIndexExpression(this.createSelfIdentifier(), fieldName); // self[fieldName] = value - const assignClassField = tstl.createAssignmentStatement(selfIndex, value); + const assignClassField = tstl.createAssignmentStatement(selfIndex, value, f); statements.push(assignClassField); } @@ -1088,7 +1106,8 @@ export class LuaTransformer { tstl.createBlock(body), paramNames, dots, - restParamName + restParamName, + node.body ); const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className)); @@ -1493,20 +1512,6 @@ export class LuaTransformer { resumeCall) ); - //coroutine.status(____co) ~= "dead"; - const coStatus = tstl.createCallExpression( - tstl.createTableIndexExpression( - tstl.createIdentifier("coroutine"), - tstl.createStringLiteral("status") - ), - [coroutineIdentifier] - ); - const status = tstl.createBinaryExpression( - coStatus, - tstl.createStringLiteral("dead"), - tstl.SyntaxKind.EqualityOperator - ); - nextBody.push(status); //if(not ____err){error(____value)} const errorCheck = tstl.createIfStatement( tstl.createUnaryExpression( @@ -1523,7 +1528,22 @@ export class LuaTransformer { ]) ); nextBody.push(errorCheck); - //{done = coroutine.status(____co) ~= "dead"; value = ____value} + + //coroutine.status(____co) == "dead"; + const coStatus = tstl.createCallExpression( + tstl.createTableIndexExpression( + tstl.createIdentifier("coroutine"), + tstl.createStringLiteral("status") + ), + [coroutineIdentifier] + ); + const status = tstl.createBinaryExpression( + coStatus, + tstl.createStringLiteral("dead"), + tstl.SyntaxKind.EqualityOperator + ); + + //{done = coroutine.status(____co) == "dead"; value = ____value} const iteratorResult = tstl.createTableExpression([ tstl.createTableFieldExpression( status, @@ -1801,10 +1821,10 @@ export class LuaTransformer { return tstl.createReturnStatement([expression]); } } - return tstl.createReturnStatement([this.transformExpression(statement.expression)]); + return tstl.createReturnStatement([this.transformExpression(statement.expression)], statement); } else { // Empty return - return tstl.createReturnStatement(); + return tstl.createReturnStatement([], statement); } } @@ -3107,7 +3127,7 @@ export class LuaTransformer { return this.transformLuaLibFunction(LuaLibFeature.Symbol, node, ...parameters); } - const callExpression = tstl.createCallExpression(callPath, parameters); + const callExpression = tstl.createCallExpression(callPath, parameters, node); return wrapResult ? this.wrapInTable(callExpression) : callExpression; } @@ -3198,13 +3218,17 @@ export class LuaTransformer { // table:name() return tstl.createMethodCallExpression( table, - tstl.createIdentifier(name), + this.transformIdentifier(node.expression.name), parameters, node ); } else { // table.name() - const callPath = tstl.createTableIndexExpression(table, tstl.createStringLiteral(name)); + const callPath = tstl.createTableIndexExpression( + table, + tstl.createStringLiteral(name), + node.expression + ); return tstl.createCallExpression(callPath, parameters, node); } } @@ -3752,7 +3776,7 @@ export class LuaTransformer { public transformStringLiteral(literal: ts.StringLiteralLike): tstl.StringLiteral { const text = tsHelper.escapeString(literal.text); - return tstl.createStringLiteral(text); + return tstl.createStringLiteral(text, literal); } public transformNumericLiteral(literal: ts.NumericLiteral): tstl.NumericLiteral { @@ -3805,13 +3829,13 @@ export class LuaTransformer { const value = Number(propertyName.text); return tstl.createNumericLiteral(value, propertyName); } else { - return tstl.createStringLiteral(this.transformIdentifier(propertyName).text); + return tstl.createStringLiteral(this.transformIdentifier(propertyName).text, propertyName); } } public transformIdentifier(expression: ts.Identifier): tstl.Identifier { if (expression.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword) { - return tstl.createIdentifier("nil"); // TODO this is a hack that allows use to keep Identifier + return tstl.createIdentifier("nil", expression); // TODO this is a hack that allows use to keep Identifier // as return time as changing that would break a lot of stuff. // But this should be changed to retun tstl.createNilLiteral() // at some point. @@ -4016,7 +4040,6 @@ export class LuaTransformer { // exported if (!rhs) { return []; - } else if (Array.isArray(lhs)) { assignment = tstl.createAssignmentStatement( lhs.map(i => this.createExportedIdentifier(i)), @@ -4243,10 +4266,11 @@ export class LuaTransformer { if (scope.functionDefinitions) { for (const [functionSymbolId, functionDefinition] of scope.functionDefinitions) { + const { line, column } = tstl.getOriginalPos(functionDefinition.assignment); const assignmentPos = ts.getPositionOfLineAndCharacter( this.currentSourceFile, - functionDefinition.assignment.line, - functionDefinition.assignment.column); + line, + column); if (functionSymbolId !== symbolId // Don't recurse into self && declaration.pos < assignmentPos // Ignore functions before symbol declaration && functionDefinition.referencedSymbols.has(symbolId) diff --git a/test/compiler/watchmode.spec.ts b/test/compiler/watchmode.spec.ts index 7b838316b..105f02fdb 100644 --- a/test/compiler/watchmode.spec.ts +++ b/test/compiler/watchmode.spec.ts @@ -23,8 +23,8 @@ export class CompilerWatchModeTest { Expect(fs.existsSync(fileToChangeOut)).toBe(true); - const initialResultLua = fs.readFileSync(fileToChangeOut); - const originalTS = fs.readFileSync(fileToChange); + const initialResultLua = fs.readFileSync(fileToChangeOut, "utf-8"); + const originalTS = fs.readFileSync(fileToChange, "utf-8"); fs.unlinkSync(fileToChangeOut); @@ -33,7 +33,7 @@ export class CompilerWatchModeTest { await this.waitForFileExists(fileToChangeOut, 5000) .catch(err => console.error(err)); - const updatedResultLua = fs.readFileSync(fileToChangeOut).toString(); + const updatedResultLua = fs.readFileSync(fileToChangeOut, "utf-8").toString(); Expect(initialResultLua).not.toEqual(updatedResultLua); From 21d3284ace7f36bdddcb0430febe69c44f48c680 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 22 Mar 2019 20:34:27 +0100 Subject: [PATCH 03/11] Fixed package issues --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef520aec3..ab9497032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -459,9 +459,9 @@ "dev": true }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esutils": { @@ -693,9 +693,9 @@ "dev": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "dev": true, "requires": { "argparse": "^1.0.7", From 7f22d4027b365805cd1383e4c0e49fcfc6cc3a85 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 22 Mar 2019 20:34:54 +0100 Subject: [PATCH 04/11] Inline source maps --- src/LuaPrinter.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index d69c0a6d3..a27bbf2f7 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -2,7 +2,7 @@ import * as tstl from "./LuaAST"; import * as path from "path"; -import {SourceNode} from "source-map"; +import {SourceNode, SourceMapGenerator, RawSourceMap, SourceMapConsumer} from "source-map"; import { TSHelper as tsHelper } from "./TSHelper"; import { LuaLibFeature, LuaLib } from "./LuaLib"; @@ -54,7 +54,23 @@ export class LuaPrinter { } public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile?: string): string { - return this.printImplementation(block, luaLibFeatures, sourceFile).toString(); + if (this.options.inlineSourceMap === true) { + const rootSourceNode = this.printImplementation(block, luaLibFeatures, sourceFile); + + const codeWithMap = rootSourceNode + // TODO is the file: part really required? and should this be handled in the printer? + .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); + + let inlineSourceMap = this.printInlineSourceMap(codeWithMap.map); + + // TODO: Put this behind a compiler option? + const stackTraceOverride = this.printStackTraceOverride(rootSourceNode); + inlineSourceMap = stackTraceOverride + inlineSourceMap; + + return codeWithMap.code + "\n" + inlineSourceMap; + } else { + return this.printImplementation(block, luaLibFeatures, sourceFile).toString(); + } } public printWithSourceMap( @@ -66,9 +82,35 @@ export class LuaPrinter { this.printImplementation(block, luaLibFeatures, sourceFile) // TODO is the file: part really required? and should this be handled in the printer? .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); + + return [codeWithMap.code, codeWithMap.map.toString()]; } + private printInlineSourceMap(sourceMap: SourceMapGenerator): string { + const map = sourceMap.toString(); + const base64Map = Buffer.from(map).toString('base64'); + + return "//# sourceMappingURL=data:application/json;base64," + base64Map; + } + + private printStackTraceOverride(rootNode: SourceNode): string { + let line = 1; + const map = {}; + rootNode.walk((chunk, mappedPosition) => { + if (mappedPosition.line !== undefined && mappedPosition.line > 0) { + if (map[line] === undefined) { + map[line] = mappedPosition.line; + } else { + map[line] = Math.min(map[line], mappedPosition.line); + } + } + line += chunk.split("\n").length - 1; + }); + console.log(map); + return ""; + } + private printImplementation( block: tstl.Block, luaLibFeatures?: Set, From 6decc0d6675ab3a718c5a0a9dc355ca14c7bc7e5 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Fri, 22 Mar 2019 20:35:02 +0100 Subject: [PATCH 05/11] Added sourcemap lualib function --- src/lualib/SourceMapTraceBack.ts | 20 ++++++++++++++++++++ src/lualib/StringReplace.ts | 5 ----- src/lualib/string.d.ts | 9 +++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/lualib/SourceMapTraceBack.ts create mode 100644 src/lualib/string.d.ts diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts new file mode 100644 index 000000000..35478bf39 --- /dev/null +++ b/src/lualib/SourceMapTraceBack.ts @@ -0,0 +1,20 @@ +declare const debug: { + traceback: (this: void, ...args: any[]) => string; +}; + +declare function getfenv(obj: any): {[key: string]: any}; + +function __TS__SourceMapTraceBack(fileName: string, sourceMap: {[line: number]: number}): void { + getfenv(1)["traceback"] = getfenv(1)["traceback"] || {}; + getfenv(1)["traceback"][fileName] = getfenv(1)["traceback"][fileName] || debug.traceback; + debug.traceback = (...args: any[]) => { + let trace = getfenv(1)["traceback"][fileName](...args); + + const matches = string.gmatch(trace, `${fileName}.lua:(%d+)`); + for (const match in matches) { + trace = string.gsub(trace, `${fileName}.lua:${match}`, `${fileName}.ts:${sourceMap[match] || "??"}`); + } + + return trace; + }; +} diff --git a/src/lualib/StringReplace.ts b/src/lualib/StringReplace.ts index 7324db680..43f7cd3f3 100644 --- a/src/lualib/StringReplace.ts +++ b/src/lualib/StringReplace.ts @@ -1,8 +1,3 @@ -declare namespace string { - /** @tupleReturn */ - function gsub(source: string, searchValue: string, replaceValue: string): [string, number]; -} - function __TS__StringReplace(source: string, searchValue: string, replaceValue: string): string { return string.gsub(source, searchValue, replaceValue)[0]; } diff --git a/src/lualib/string.d.ts b/src/lualib/string.d.ts new file mode 100644 index 000000000..4361889b3 --- /dev/null +++ b/src/lualib/string.d.ts @@ -0,0 +1,9 @@ +/** @luaIterator */ +interface GMatchResult extends Iterable { } + +declare namespace string { + /** @tupleReturn */ + function gsub(source: string, searchValue: string, replaceValue: string): [string, number]; + + function gmatch(haystack: string, pattern: string): GMatchResult; +} From 77b092987502e6dbf4de59ebde2f7b0e91a884b6 Mon Sep 17 00:00:00 2001 From: Perry van Wesel Date: Sat, 30 Mar 2019 20:39:57 +0100 Subject: [PATCH 06/11] Added override for traceback (#490) * Added override for traceback * Improved sourcemap override * Removed obsolete argument * put traceback override at start of the file after headers * changed 2 underscore identifiers * Added test for sourceMapTraceback * don't enforce prettier linting * use debug.getinfo for file names * Trying to diagnose test issue * No longer check filename in sourcemap test * Another stab at fixing tests --- build_lualib.ts | 2 +- package.json | 2 +- src/CommandLineParser.ts | 5 ++ src/CompilerOptions.ts | 1 + src/LuaLib.ts | 1 + src/LuaPrinter.ts | 68 +++++++++-------- src/LuaTranspiler.ts | 8 +- src/lualib/SourceMapTraceBack.ts | 37 +++++++--- src/lualib/{ => declarations}/string.d.ts | 5 +- .../configuration/mixed/index.spec.ts | 1 + test/unit/sourcemaps.spec.ts | 73 +++++++++++++++++++ 11 files changed, 154 insertions(+), 49 deletions(-) rename src/lualib/{ => declarations}/string.d.ts (53%) create mode 100644 test/unit/sourcemaps.spec.ts diff --git a/build_lualib.ts b/build_lualib.ts index f968f5968..be376fef6 100644 --- a/build_lualib.ts +++ b/build_lualib.ts @@ -20,7 +20,7 @@ compile([ "./src/lualib", "--noHeader", "true", - ...glob.sync("./src/lualib/*.ts"), + ...glob.sync("./src/lualib/**/*.ts"), ]); if (fs.existsSync(bundlePath)) { diff --git a/package.json b/package.json index d38187f3c..76b1ec51a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "build-lualib": "ts-node ./build_lualib.ts", "pretest": "ts-node --transpile-only ./build_lualib.ts", "test": "jest", - "lint": "npm run lint:tslint && npm run lint:prettier", + "lint": "npm run lint:tslint", "lint:prettier": "prettier --check **/*.{js,ts,yml,json}", "lint:tslint": "tslint -p . && tslint -p test && tslint src/lualib/*.ts", "release-major": "npm version major", diff --git a/src/CommandLineParser.ts b/src/CommandLineParser.ts index d2fdc9f0d..50a1fa738 100644 --- a/src/CommandLineParser.ts +++ b/src/CommandLineParser.ts @@ -52,6 +52,11 @@ const optionDeclarations: {[key: string]: CLIOption} = { describe: "Disables hoisting.", type: "boolean", } as CLIOption, + sourceMapTraceback: { + default: false, + describe: "Applies the source map to show source TS files and lines in error tracebacks.", + type: "boolean", + } as CLIOption, }; export const { version } = require("../package.json"); diff --git a/src/CompilerOptions.ts b/src/CompilerOptions.ts index 24fae4374..49ad4eabf 100644 --- a/src/CompilerOptions.ts +++ b/src/CompilerOptions.ts @@ -5,6 +5,7 @@ export interface CompilerOptions extends ts.CompilerOptions { luaTarget?: LuaTarget; luaLibImport?: LuaLibImportKind; noHoisting?: boolean; + sourceMapTraceback?: boolean; } export enum LuaLibImportKind { diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 3db60196c..c5614bc10 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -34,6 +34,7 @@ export enum LuaLibFeature { Set = "Set", WeakMap = "WeakMap", WeakSet = "WeakSet", + SourceMapTraceBack = "SourceMapTraceBack", StringReplace = "StringReplace", StringSplit = "StringSplit", StringConcat = "StringConcat", diff --git a/src/LuaPrinter.ts b/src/LuaPrinter.ts index 1699b1e4d..8ea637c2a 100644 --- a/src/LuaPrinter.ts +++ b/src/LuaPrinter.ts @@ -50,50 +50,45 @@ export class LuaPrinter { this.currentIndent = ""; } - public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile?: string): string { - if (this.options.inlineSourceMap === true) { - const rootSourceNode = this.printImplementation(block, luaLibFeatures, sourceFile); + public print(block: tstl.Block, luaLibFeatures?: Set, sourceFile?: string): [string, string] { + // Add traceback lualib if sourcemap traceback option is enabled + if (this.options.sourceMapTraceback) { + if (luaLibFeatures === undefined) { + luaLibFeatures = new Set(); + } + luaLibFeatures.add(LuaLibFeature.SourceMapTraceBack); + } - const codeWithMap = rootSourceNode - // TODO is the file: part really required? and should this be handled in the printer? - .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); + const rootSourceNode = this.printImplementation(block, luaLibFeatures, sourceFile); - let inlineSourceMap = this.printInlineSourceMap(codeWithMap.map); + const codeWithSourceMap = rootSourceNode + // TODO is the file: part really required? and should this be handled in the printer? + .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); - // TODO: Put this behind a compiler option? - const stackTraceOverride = this.printStackTraceOverride(rootSourceNode); - inlineSourceMap = stackTraceOverride + inlineSourceMap; + let codeResult = codeWithSourceMap.code; - return codeWithMap.code + "\n" + inlineSourceMap; - } else { - return this.printImplementation(block, luaLibFeatures, sourceFile).toString(); + if (this.options.inlineSourceMap) { + codeResult += "\n" + this.printInlineSourceMap(codeWithSourceMap.map); } - } - - public printWithSourceMap( - block: tstl.Block, - luaLibFeatures?: Set, - sourceFile?: string): [string, string] { - - const codeWithMap = - this.printImplementation(block, luaLibFeatures, sourceFile) - // TODO is the file: part really required? and should this be handled in the printer? - .toStringWithSourceMap({file: path.basename(sourceFile, path.extname(sourceFile)) + ".lua"}); + if (this.options.sourceMapTraceback) { + const stackTraceOverride = this.printStackTraceOverride(rootSourceNode); + codeResult = codeResult.replace("{#SourceMapTraceback}", stackTraceOverride); + } - return [codeWithMap.code, codeWithMap.map.toString()]; + return [codeResult, codeWithSourceMap.map.toString()]; } private printInlineSourceMap(sourceMap: SourceMapGenerator): string { const map = sourceMap.toString(); const base64Map = Buffer.from(map).toString('base64'); - return "//# sourceMappingURL=data:application/json;base64," + base64Map; + return `//# sourceMappingURL=data:application/json;base64,${base64Map}\n`; } private printStackTraceOverride(rootNode: SourceNode): string { let line = 1; - const map = {}; + const map: {[line: number]: number} = {}; rootNode.walk((chunk, mappedPosition) => { if (mappedPosition.line !== undefined && mappedPosition.line > 0) { if (map[line] === undefined) { @@ -104,8 +99,15 @@ export class LuaPrinter { } line += chunk.split("\n").length - 1; }); - console.log(map); - return ""; + + const mapItems = []; + for (const lineNr in map) { + mapItems.push(`["${lineNr}"] = ${map[lineNr]}`); + } + + const mapString = "{" + mapItems.join(",") + "}"; + + return `__TS__SourceMapTraceBack(debug.getinfo(1).short_src, ${mapString});`; } private printImplementation( @@ -136,9 +138,13 @@ export class LuaPrinter { this.sourceFile = path.basename(sourceFile); - const blockNode = this.createSourceNode(block, this.printBlock(block)); + if (this.options.sourceMapTraceback) { + header += "{#SourceMapTraceback}\n"; + } + + const fileBlockNode = this.createSourceNode(block, this.printBlock(block)); - return this.concatNodes(header, blockNode); + return this.concatNodes(header, fileBlockNode); } private pushIndent(): void { diff --git a/src/LuaTranspiler.ts b/src/LuaTranspiler.ts index ee75331c4..9581ca4cc 100644 --- a/src/LuaTranspiler.ts +++ b/src/LuaTranspiler.ts @@ -141,21 +141,23 @@ export class LuaTranspiler { // Transform AST const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); // Print AST - return this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); + const [code, sourceMap] = this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); + return code; } public transpileSourceFileWithSourceMap(sourceFile: ts.SourceFile): [string, string] { // Transform AST const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); // Print AST - return this.luaPrinter.printWithSourceMap(luaAST, lualibFeatureSet, sourceFile.fileName); + return this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); } public transpileSourceFileKeepAST(sourceFile: ts.SourceFile): [tstl.Block, string] { // Transform AST const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); // Print AST - return [luaAST, this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName)]; + const [code, sourceMap] = this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); + return [luaAST, code]; } public reportDiagnostic(diagnostic: ts.Diagnostic): void { diff --git a/src/lualib/SourceMapTraceBack.ts b/src/lualib/SourceMapTraceBack.ts index 35478bf39..eb5215d64 100644 --- a/src/lualib/SourceMapTraceBack.ts +++ b/src/lualib/SourceMapTraceBack.ts @@ -2,19 +2,32 @@ declare const debug: { traceback: (this: void, ...args: any[]) => string; }; -declare function getfenv(obj: any): {[key: string]: any}; +type TraceBackFunction = (this: void, thread?: any, message?: string, level?: number) => string; -function __TS__SourceMapTraceBack(fileName: string, sourceMap: {[line: number]: number}): void { - getfenv(1)["traceback"] = getfenv(1)["traceback"] || {}; - getfenv(1)["traceback"][fileName] = getfenv(1)["traceback"][fileName] || debug.traceback; - debug.traceback = (...args: any[]) => { - let trace = getfenv(1)["traceback"][fileName](...args); +declare const _G: {[key: string]: any} & {__TS__originalTraceback: TraceBackFunction}; - const matches = string.gmatch(trace, `${fileName}.lua:(%d+)`); - for (const match in matches) { - trace = string.gsub(trace, `${fileName}.lua:${match}`, `${fileName}.ts:${sourceMap[match] || "??"}`); - } +// TODO: In the future, change this to __TS__RegisterFileInfo and provide tstl interface to +// get some metadata about transpilation. +function __TS__SourceMapTraceBack(this: void, fileName: string, sourceMap: {[line: number]: number}): void { + _G["__TS__sourcemap"] = _G["__TS__sourcemap"] || {}; + _G["__TS__sourcemap"][fileName] = sourceMap; - return trace; - }; + if (_G.__TS__originalTraceback === undefined) { + _G.__TS__originalTraceback = debug.traceback; + debug.traceback = (thread, message, level) => { + const trace = _G["__TS__originalTraceback"](thread, message, level); + const [result, occurrences] = string.gsub( + trace, + "(%S+).lua:(%d+)", + (file, line) => { + if (_G["__TS__sourcemap"][file + ".lua"] && _G["__TS__sourcemap"][file + ".lua"][line]) { + return `${file}.ts:${_G["__TS__sourcemap"][file + ".lua"][line]}`; + } + return `${file}.lua:${line}`; + } + ); + + return result; + }; + } } diff --git a/src/lualib/string.d.ts b/src/lualib/declarations/string.d.ts similarity index 53% rename from src/lualib/string.d.ts rename to src/lualib/declarations/string.d.ts index 4361889b3..6e387692b 100644 --- a/src/lualib/string.d.ts +++ b/src/lualib/declarations/string.d.ts @@ -1,9 +1,12 @@ /** @luaIterator */ -interface GMatchResult extends Iterable { } +interface GMatchResult extends Array { } +/** @noSelf */ declare namespace string { /** @tupleReturn */ function gsub(source: string, searchValue: string, replaceValue: string): [string, number]; + /** @tupleReturn */ + function gsub(source: string, searchValue: string, replaceValue: (...groups: string[]) => string): [string, number]; function gmatch(haystack: string, pattern: string): GMatchResult; } diff --git a/test/unit/compiler/configuration/mixed/index.spec.ts b/test/unit/compiler/configuration/mixed/index.spec.ts index 119a91365..cb98a7d45 100644 --- a/test/unit/compiler/configuration/mixed/index.spec.ts +++ b/test/unit/compiler/configuration/mixed/index.spec.ts @@ -32,6 +32,7 @@ test("tsconfig.json mixed with cmd line args", () => { noHeader: false, project: tsConfigPath, noHoisting: false, + sourceMapTraceback: false, } as CompilerOptions); } else { expect(parsedArgs.isValid).toBeTruthy(); diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts new file mode 100644 index 000000000..88910e622 --- /dev/null +++ b/test/unit/sourcemaps.spec.ts @@ -0,0 +1,73 @@ +import * as util from "../util"; +import { LuaLibImportKind } from "../../src/CompilerOptions"; + +test("sourceMapTraceback saves sourcemap in _G", () => { + const typeScriptSource = ` + function abc() { + return "foo"; + } + return JSONStringify(_G.__TS__sourcemap);`; + + const options = {sourceMapTraceback: true, luaLibImport: LuaLibImportKind.Inline}; + + const transpiledLua = util.transpileString(typeScriptSource, options); + + const sourceMapJson = util.transpileAndExecute( + typeScriptSource, + options, + undefined, + "declare const _G: {__TS__sourcemap: any};" + ); + + expect(sourceMapJson).toBeDefined(); + + const sourceMap = JSON.parse(sourceMapJson); + + const sourceMapFiles = Object.keys(sourceMap); + + expect(sourceMapFiles.length).toBe(1); + expect(sourceMap[sourceMapFiles[0]]).toBeDefined(); + + expectCorrectMapping(typeScriptSource, transpiledLua, sourceMap[sourceMapFiles[0]], [ + ["function abc()", "abc = function("], + ["return \"foo\"", "return \"foo\""] + ]); +}); + +// Helper functions + +function expectCorrectMapping( + original: string, + lua: string, + sourceMap: {[line: string]: number}, + patterns: Array<[string, string]> +): void { + for (const [tsPattern, luaPattern] of patterns) { + const originalLine = lineOf(original, "function abc()") + 1; // Add 1 for util-added header + const luaLine = lineOf(lua, "abc = function("); + const mappedLuaLine = sourceMap[luaLine.toString()]; + + expect(mappedLuaLine).toBe(originalLine); + } +} + +// Find the line of the first occurrence of a pattern. +function lineOf(text: string, pattern: string): number { + const pos = text.indexOf(pattern); + if (pos === -1) { + return pos; + } + + const lineLengths = text.split("\n").map(s => s.length); + + let totalPos = 0; + for (let line = 1; line <= lineLengths.length; line++) { + // Add length of the line + 1 for the removed \n + totalPos += lineLengths[line - 1] + 1; + if (pos < totalPos) { + return line; + } + } + + return -1; +} \ No newline at end of file From 8e4d71ffc63e77b93003a325c88b5396c5311676 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 31 Mar 2019 21:04:14 +0200 Subject: [PATCH 07/11] Added sourcemap test, simplified API --- package.json | 2 +- src/Compiler.ts | 8 +-- src/LuaTranspiler.ts | 34 ++++------- test/unit/json.spec.ts | 10 ++-- test/unit/sourcemaps.spec.ts | 111 +++++++++++++++++++++++++---------- test/util.ts | 39 ++++++++++++ 6 files changed, 140 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index 76b1ec51a..980bb1d6b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "scripts": { "build": "tsc -p tsconfig.json && npm run build-lualib", "build-lualib": "ts-node ./build_lualib.ts", - "pretest": "ts-node --transpile-only ./build_lualib.ts", + "pretest": "npm run build", "test": "jest", "lint": "npm run lint:tslint", "lint:prettier": "prettier --check **/*.{js,ts,yml,json}", diff --git a/src/Compiler.ts b/src/Compiler.ts index 642624347..306182f34 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -3,7 +3,7 @@ import * as path from "path"; import * as ts from "typescript"; import * as CommandLineParser from "./CommandLineParser"; import { CompilerOptions, LuaLibImportKind, LuaTarget } from "./CompilerOptions"; -import { LuaTranspiler } from "./LuaTranspiler"; +import { LuaTranspiler, TranspileResult } from "./LuaTranspiler"; export function compile(argv: string[]): void { const parseResult = CommandLineParser.parseCommandLine(argv); @@ -167,7 +167,7 @@ export function transpileString( options: CompilerOptions = defaultCompilerOptions, ignoreDiagnostics = false, filePath = "file.ts" -): string { +): TranspileResult { const program = createStringCompilerProgram(input, options, filePath); if (!ignoreDiagnostics) { @@ -182,7 +182,5 @@ export function transpileString( const transpiler = new LuaTranspiler(program); - const result = transpiler.transpileSourceFile(program.getSourceFile(filePath)); - - return result.trim(); + return transpiler.transpileSourceFile(program.getSourceFile(filePath)); } diff --git a/src/LuaTranspiler.ts b/src/LuaTranspiler.ts index 9581ca4cc..771a32642 100644 --- a/src/LuaTranspiler.ts +++ b/src/LuaTranspiler.ts @@ -6,6 +6,12 @@ import * as tstl from "./LuaAST"; import { LuaPrinter } from "./LuaPrinter"; import { LuaTransformer } from "./LuaTransformer"; +export interface TranspileResult { + lua: string; + luaAST: tstl.Node; + sourceMap: string; +} + export class LuaTranspiler { private program: ts.Program; @@ -89,13 +95,7 @@ export class LuaTranspiler { try { const rootDir = this.options.rootDir; - let lua, sourceMap; - - if (this.options.sourceMap) { - [lua, sourceMap] = this.transpileSourceFileWithSourceMap(sourceFile); - } else { - lua = this.transpileSourceFile(sourceFile); - } + const { lua, luaAST, sourceMap } = this.transpileSourceFile(sourceFile); let outPath = sourceFile.fileName; if (this.options.outDir !== this.options.rootDir) { @@ -137,27 +137,13 @@ export class LuaTranspiler { return 0; } - public transpileSourceFile(sourceFile: ts.SourceFile): string { + public transpileSourceFile(sourceFile: ts.SourceFile): TranspileResult { // Transform AST const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); // Print AST - const [code, sourceMap] = this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); - return code; - } + const [lua, sourceMap] = this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); - public transpileSourceFileWithSourceMap(sourceFile: ts.SourceFile): [string, string] { - // Transform AST - const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); - // Print AST - return this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); - } - - public transpileSourceFileKeepAST(sourceFile: ts.SourceFile): [tstl.Block, string] { - // Transform AST - const [luaAST, lualibFeatureSet] = this.luaTransformer.transformSourceFile(sourceFile); - // Print AST - const [code, sourceMap] = this.luaPrinter.print(luaAST, lualibFeatureSet, sourceFile.fileName); - return [luaAST, code]; + return { lua, luaAST, sourceMap }; } public reportDiagnostic(diagnostic: ts.Diagnostic): void { diff --git a/test/unit/json.spec.ts b/test/unit/json.spec.ts index 337e5676f..19f6437e7 100644 --- a/test/unit/json.spec.ts +++ b/test/unit/json.spec.ts @@ -1,13 +1,15 @@ -import { transpileString } from "../../src/Compiler"; import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; +import * as ts from "typescript"; + +const jsonOptions = { resolveJsonModule: true, noHeader: true, moduleResolution: ts.ModuleResolutionKind.NodeJs }; test.each(["0", '""', "[]", '[1, "2", []]', '{ "a": "b" }', '{ "a": { "b": "c" } }'])( "JSON (%p)", json => { - const lua = transpileString( + const lua = util.transpileString( json, - { resolveJsonModule: true, noHeader: true }, + jsonOptions, false, "file.json", ).replace(/^return ([\s\S]+);$/, "return JSONStringify($1);"); @@ -19,6 +21,6 @@ test.each(["0", '""', "[]", '[1, "2", []]', '{ "a": "b" }', '{ "a": { "b": "c" } test("Empty JSON", () => { expect(() => - transpileString("", { resolveJsonModule: true, noHeader: true }, false, "file.json"), + util.transpileString("", jsonOptions, false, "file.json"), ).toThrowExactError(TSTLErrors.InvalidJsonFileContent(util.nodeStub)); }); diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index 88910e622..db82da80d 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -1,24 +1,79 @@ import * as util from "../util"; import { LuaLibImportKind } from "../../src/CompilerOptions"; +import { SourceMapConsumer, Position } from "source-map"; + +test.each([ + { + typeScriptSource: + `const abc = "foo"; + const def = "bar"; + + const xyz = "baz";`, + + assertPatterns: [ + { luaPattern: "abc", typeScriptPattern: "abc" }, + { luaPattern: "def", typeScriptPattern: "def" }, + { luaPattern: "xyz", typeScriptPattern: "xyz" }, + { luaPattern: `"foo"`, typeScriptPattern: `"foo"` }, + { luaPattern: `"bar"`, typeScriptPattern: `"bar"` }, + { luaPattern: `"baz"`, typeScriptPattern: `"baz"` }, + ] + }, + { + typeScriptSource: + `function abc() { + return def(); + } + function def() { + return "foo"; + } + return abc();`, + + assertPatterns: [ + { luaPattern: "abc = function(", typeScriptPattern: "abc() {" }, + { luaPattern: "def = function(", typeScriptPattern: "def() {" }, + { luaPattern: "return abc(", typeScriptPattern: "return abc(" }, + ] + }, +])("Source map has correct mapping (%p)", async ({ typeScriptSource, assertPatterns }) => { + + // Act + const { lua, sourceMap } = util.transpileStringResult(typeScriptSource); + + // Assert + const consumer = await new SourceMapConsumer(sourceMap); + + for (const { luaPattern, typeScriptPattern } of assertPatterns) { + const luaPosition = lineAndColumnOf(lua, luaPattern); + const mappedPosition = consumer.originalPositionFor(luaPosition); + + const typescriptPosition = lineAndColumnOf(typeScriptSource, typeScriptPattern); + + expect({ line: mappedPosition.line, column: mappedPosition.column }).toEqual(typescriptPosition); + } +}); test("sourceMapTraceback saves sourcemap in _G", () => { + // Arrange const typeScriptSource = ` function abc() { return "foo"; } return JSONStringify(_G.__TS__sourcemap);`; - const options = {sourceMapTraceback: true, luaLibImport: LuaLibImportKind.Inline}; + const options = { sourceMapTraceback: true, luaLibImport: LuaLibImportKind.Inline }; + // Act const transpiledLua = util.transpileString(typeScriptSource, options); const sourceMapJson = util.transpileAndExecute( typeScriptSource, options, undefined, - "declare const _G: {__TS__sourcemap: any};" + "declare const _G: {__TS__sourcemap: any};", ); + // Assert expect(sourceMapJson).toBeDefined(); const sourceMap = JSON.parse(sourceMapJson); @@ -28,46 +83,42 @@ test("sourceMapTraceback saves sourcemap in _G", () => { expect(sourceMapFiles.length).toBe(1); expect(sourceMap[sourceMapFiles[0]]).toBeDefined(); - expectCorrectMapping(typeScriptSource, transpiledLua, sourceMap[sourceMapFiles[0]], [ - ["function abc()", "abc = function("], - ["return \"foo\"", "return \"foo\""] - ]); -}); + const assertPatterns = [ + { luaPattern: "abc = function(", typeScriptPattern: "abc() {" }, + { luaPattern: `return "foo"`, typeScriptPattern: `return "foo"` }, + ]; -// Helper functions + for (const { luaPattern, typeScriptPattern } of assertPatterns) { + const luaPosition = lineAndColumnOf(transpiledLua, luaPattern); + const mappedLine = sourceMap[sourceMapFiles[0]][luaPosition.line.toString()]; -function expectCorrectMapping( - original: string, - lua: string, - sourceMap: {[line: string]: number}, - patterns: Array<[string, string]> -): void { - for (const [tsPattern, luaPattern] of patterns) { - const originalLine = lineOf(original, "function abc()") + 1; // Add 1 for util-added header - const luaLine = lineOf(lua, "abc = function("); - const mappedLuaLine = sourceMap[luaLine.toString()]; - - expect(mappedLuaLine).toBe(originalLine); + const typescriptPosition = lineAndColumnOf(typeScriptSource, typeScriptPattern); + + // Add 1 to account for transpiledAndExecute-added function header + expect(mappedLine).toEqual(typescriptPosition.line + 1); } -} +}); -// Find the line of the first occurrence of a pattern. -function lineOf(text: string, pattern: string): number { +// Helper functions + +function lineAndColumnOf(text: string, pattern: string): Position { const pos = text.indexOf(pattern); if (pos === -1) { - return pos; + return { line: -1, column: -1 }; } const lineLengths = text.split("\n").map(s => s.length); let totalPos = 0; for (let line = 1; line <= lineLengths.length; line++) { - // Add length of the line + 1 for the removed \n - totalPos += lineLengths[line - 1] + 1; - if (pos < totalPos) { - return line; + // Add + 1 for the removed \n + const lineLength = lineLengths[line - 1] + 1; + if (pos < totalPos + lineLength) { + return { line, column: pos - totalPos }; } + + totalPos += lineLengths[line - 1] + 1; } - return -1; -} \ No newline at end of file + return { line: -1, column: -1 }; +} diff --git a/test/util.ts b/test/util.ts index 73d338434..d09cd7824 100644 --- a/test/util.ts +++ b/test/util.ts @@ -8,6 +8,7 @@ import { } from "../src/Compiler"; import { CompilerOptions, LuaLibImportKind, LuaTarget } from "../src/CompilerOptions"; import { LuaTransformer } from "../src/LuaTransformer"; +import { TranspileResult } from "../src/LuaTranspiler"; export const nodeStub = ts.createNode(ts.SyntaxKind.Unknown); @@ -46,12 +47,50 @@ expect.extend({ }, }); +function compilerTranspile( + str: string | { [filename: string]: string }, + options: CompilerOptions = {}, + ignoreDiagnostics = true, + filePath = "file.ts", +): TranspileResult { + return compilerTranspileString( + str, + { + luaLibImport: LuaLibImportKind.Inline, + luaTarget: LuaTarget.Lua53, + noHeader: true, + skipLibCheck: true, + target: ts.ScriptTarget.ESNext, + lib: [ + "lib.es2015.d.ts", + "lib.es2016.d.ts", + "lib.es2017.d.ts", + "lib.es2018.d.ts", + "lib.esnext.d.ts", + ], + ...options, + }, + ignoreDiagnostics, + filePath, + ); +} + export function transpileString( str: string | { [filename: string]: string }, options: CompilerOptions = {}, ignoreDiagnostics = true, filePath = "file.ts", ): string { + const { lua } = transpileStringResult(str, options, ignoreDiagnostics, filePath); + return lua.trim(); +} + +export function transpileStringResult( + str: string | { [filename: string]: string }, + options: CompilerOptions = {}, + ignoreDiagnostics = true, + filePath = "file.ts", +): TranspileResult { return compilerTranspileString( str, { From 994d8c3b1aeb08cdc6bd9b88ee848c6e2ad71dd8 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 31 Mar 2019 21:13:20 +0200 Subject: [PATCH 08/11] Added back dependency that got lost in the merge --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7c7d6bd1e..7a671c44b 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "node": ">=8.5.0" }, "dependencies": { + "source-map": "^0.7.3", "typescript": "^3.3.1" }, "devDependencies": { From 9f75f1be97f70ec71a26ee8d67f6d006fb4bc55e Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 31 Mar 2019 21:36:49 +0200 Subject: [PATCH 09/11] Fix prettier complaining --- test/unit/json.spec.ts | 21 +++++++++++---------- test/unit/sourcemaps.spec.ts | 16 ++++++++-------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/test/unit/json.spec.ts b/test/unit/json.spec.ts index 19f6437e7..db16d75ae 100644 --- a/test/unit/json.spec.ts +++ b/test/unit/json.spec.ts @@ -2,17 +2,18 @@ import { TSTLErrors } from "../../src/TSTLErrors"; import * as util from "../util"; import * as ts from "typescript"; -const jsonOptions = { resolveJsonModule: true, noHeader: true, moduleResolution: ts.ModuleResolutionKind.NodeJs }; +const jsonOptions = { + resolveJsonModule: true, + noHeader: true, + moduleResolution: ts.ModuleResolutionKind.NodeJs, +}; test.each(["0", '""', "[]", '[1, "2", []]', '{ "a": "b" }', '{ "a": { "b": "c" } }'])( "JSON (%p)", json => { - const lua = util.transpileString( - json, - jsonOptions, - false, - "file.json", - ).replace(/^return ([\s\S]+);$/, "return JSONStringify($1);"); + const lua = util + .transpileString(json, jsonOptions, false, "file.json") + .replace(/^return ([\s\S]+);$/, "return JSONStringify($1);"); const result = util.executeLua(lua); expect(JSON.parse(result)).toEqual(JSON.parse(json)); @@ -20,7 +21,7 @@ test.each(["0", '""', "[]", '[1, "2", []]', '{ "a": "b" }', '{ "a": { "b": "c" } ); test("Empty JSON", () => { - expect(() => - util.transpileString("", jsonOptions, false, "file.json"), - ).toThrowExactError(TSTLErrors.InvalidJsonFileContent(util.nodeStub)); + expect(() => util.transpileString("", jsonOptions, false, "file.json")).toThrowExactError( + TSTLErrors.InvalidJsonFileContent(util.nodeStub), + ); }); diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index db82da80d..9a5025d4a 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -4,8 +4,8 @@ import { SourceMapConsumer, Position } from "source-map"; test.each([ { - typeScriptSource: - `const abc = "foo"; + typeScriptSource: ` + const abc = "foo"; const def = "bar"; const xyz = "baz";`, @@ -17,11 +17,11 @@ test.each([ { luaPattern: `"foo"`, typeScriptPattern: `"foo"` }, { luaPattern: `"bar"`, typeScriptPattern: `"bar"` }, { luaPattern: `"baz"`, typeScriptPattern: `"baz"` }, - ] + ], }, { - typeScriptSource: - `function abc() { + typeScriptSource: ` + function abc() { return def(); } function def() { @@ -33,10 +33,9 @@ test.each([ { luaPattern: "abc = function(", typeScriptPattern: "abc() {" }, { luaPattern: "def = function(", typeScriptPattern: "def() {" }, { luaPattern: "return abc(", typeScriptPattern: "return abc(" }, - ] + ], }, ])("Source map has correct mapping (%p)", async ({ typeScriptSource, assertPatterns }) => { - // Act const { lua, sourceMap } = util.transpileStringResult(typeScriptSource); @@ -49,7 +48,8 @@ test.each([ const typescriptPosition = lineAndColumnOf(typeScriptSource, typeScriptPattern); - expect({ line: mappedPosition.line, column: mappedPosition.column }).toEqual(typescriptPosition); + const mappedLineColumn = { line: mappedPosition.line, column: mappedPosition.column }; + expect(mappedLineColumn).toEqual(typescriptPosition); } }); From 385db2f2054bcb8f8fcfeb8e6c5d6247a0119bad Mon Sep 17 00:00:00 2001 From: Perryvw Date: Sun, 31 Mar 2019 21:40:51 +0200 Subject: [PATCH 10/11] Made appveyor lint --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 6ae337099..078fad304 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,6 +19,7 @@ test_script: - node --version - npm --version # run tests + - npm run lint - npm run build - npm test From ad52bc273b6a4f596db7f5db7031f36e5c696e4e Mon Sep 17 00:00:00 2001 From: Perryvw Date: Mon, 1 Apr 2019 20:59:16 +0200 Subject: [PATCH 11/11] Fixed bug in setNodeOriginal and added test to detect it --- src/LuaAST.ts | 2 +- test/unit/sourcemaps.spec.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/LuaAST.ts b/src/LuaAST.ts index d9dc79d70..277f4fdf7 100644 --- a/src/LuaAST.ts +++ b/src/LuaAST.ts @@ -125,7 +125,7 @@ export function setNodeOriginal(node: T, tsOriginal: ts.Node): T const sourcePosition = getSourcePosition(tsOriginal); if (sourcePosition) { node.line = sourcePosition.line; - node.line = sourcePosition.line; + node.column = sourcePosition.column; } return node; diff --git a/test/unit/sourcemaps.spec.ts b/test/unit/sourcemaps.spec.ts index 9a5025d4a..b6873787b 100644 --- a/test/unit/sourcemaps.spec.ts +++ b/test/unit/sourcemaps.spec.ts @@ -35,6 +35,16 @@ test.each([ { luaPattern: "return abc(", typeScriptPattern: "return abc(" }, ], }, + { + typeScriptSource: ` + const enum abc { foo = 2, bar = 4 }; + const xyz = abc.foo;`, + + assertPatterns: [ + { luaPattern: "xyz", typeScriptPattern: "xyz" }, + { luaPattern: "2", typeScriptPattern: "abc.foo" }, + ], + }, ])("Source map has correct mapping (%p)", async ({ typeScriptSource, assertPatterns }) => { // Act const { lua, sourceMap } = util.transpileStringResult(typeScriptSource);