11import * as path from "path" ;
22
3- import { SourceNode , SourceMapGenerator } from "source-map" ;
3+ import { Mapping , SourceNode , SourceMapGenerator } from "source-map" ;
44
55import * as tstl from "./LuaAST" ;
66import { CompilerOptions , LuaLibImportKind } from "./CompilerOptions" ;
@@ -59,22 +59,23 @@ export class LuaPrinter {
5959
6060 const rootSourceNode = this . printImplementation ( block , luaLibFeatures , sourceFile ) ;
6161
62- const codeWithSourceMap = rootSourceNode
63- // TODO is the file: part really required? and should this be handled in the printer?
64- . toStringWithSourceMap ( { file : path . basename ( sourceFile , path . extname ( sourceFile ) ) + ".lua" } ) ;
62+ const sourceRoot = this . options . sourceRoot
63+ || ( this . options . outDir ? path . relative ( this . options . outDir , this . options . rootDir || process . cwd ( ) ) : "." ) ;
6564
66- let codeResult = codeWithSourceMap . code ;
65+ const sourceMap = this . buildSourceMap ( sourceFile , sourceRoot , rootSourceNode ) ;
66+
67+ let codeResult = rootSourceNode . toString ( ) ;
6768
6869 if ( this . options . inlineSourceMap ) {
69- codeResult += "\n" + this . printInlineSourceMap ( codeWithSourceMap . map ) ;
70+ codeResult += "\n" + this . printInlineSourceMap ( sourceMap ) ;
7071 }
7172
7273 if ( this . options . sourceMapTraceback ) {
7374 const stackTraceOverride = this . printStackTraceOverride ( rootSourceNode ) ;
7475 codeResult = codeResult . replace ( "{#SourceMapTraceback}" , stackTraceOverride ) ;
7576 }
7677
77- return [ codeResult , codeWithSourceMap . map . toString ( ) ] ;
78+ return [ codeResult , sourceMap . toString ( ) ] ;
7879 }
7980
8081 private printInlineSourceMap ( sourceMap : SourceMapGenerator ) : string {
@@ -140,7 +141,7 @@ export class LuaPrinter {
140141 header += "{#SourceMapTraceback}\n" ;
141142 }
142143
143- const fileBlockNode = this . createSourceNode ( block , this . printBlock ( block ) ) ;
144+ const fileBlockNode = this . printBlock ( block ) ;
144145
145146 return this . concatNodes ( header , fileBlockNode ) ;
146147 }
@@ -172,7 +173,7 @@ export class LuaPrinter {
172173 }
173174
174175 protected printBlock ( block : tstl . Block ) : SourceNode {
175- return this . createSourceNode ( block , this . printStatementArray ( block . statements ) ) ;
176+ return this . concatNodes ( ... this . printStatementArray ( block . statements ) ) ;
176177 }
177178
178179 private statementMayRequireSemiColon ( statement : tstl . Statement ) : boolean {
@@ -428,7 +429,7 @@ export class LuaPrinter {
428429 }
429430
430431 public printExpressionStatement ( statement : tstl . ExpressionStatement ) : SourceNode {
431- return this . concatNodes ( this . indent ( ) , this . printExpression ( statement . expression ) ) ;
432+ return this . createSourceNode ( statement , [ this . indent ( ) , this . printExpression ( statement . expression ) ] ) ;
432433 }
433434
434435 // Expressions
@@ -626,7 +627,7 @@ export class LuaPrinter {
626627
627628 chunks . push ( this . printExpression ( expression . expression ) , "(" , ...this . joinChunks ( ", " , parameterChunks ) , ")" ) ;
628629
629- return this . concatNodes ( ... chunks ) ;
630+ return this . createSourceNode ( expression , chunks ) ;
630631 }
631632
632633 public printMethodCallExpression ( expression : tstl . MethodCallExpression ) : SourceNode {
@@ -638,7 +639,10 @@ export class LuaPrinter {
638639
639640 const name = this . printIdentifier ( expression . name ) ;
640641
641- return this . concatNodes ( prefix , ":" , name , "(" , ...this . joinChunks ( ", " , parameterChunks ) , ")" ) ;
642+ return this . createSourceNode (
643+ expression ,
644+ [ prefix , ":" , name , "(" , ...this . joinChunks ( ", " , parameterChunks ) , ")" ]
645+ ) ;
642646 }
643647
644648 public printIdentifier ( expression : tstl . Identifier ) : SourceNode {
@@ -689,4 +693,62 @@ export class LuaPrinter {
689693 }
690694 return result ;
691695 }
696+
697+ // The key difference between this and SourceNode.toStringWithSourceMap() is that SourceNodes with null line/column
698+ // will not generate 'empty' mappings in the source map that point to nothing in the original TS.
699+ private buildSourceMap ( sourceFile : string , sourceRoot : string , rootSourceNode : SourceNode ) : SourceMapGenerator {
700+ const map = new SourceMapGenerator ( {
701+ file : path . basename ( sourceFile , path . extname ( sourceFile ) ) + ".lua" ,
702+ sourceRoot,
703+ } ) ;
704+
705+ let generatedLine = 1 ;
706+ let generatedColumn = 0 ;
707+ let currentMapping : Mapping | undefined ;
708+
709+ const isNewMapping = ( sourceNode : SourceNode ) => {
710+ if ( sourceNode . line === null ) {
711+ return false ;
712+ }
713+ if ( currentMapping === undefined ) {
714+ return true ;
715+ }
716+ if ( currentMapping . generated . line === generatedLine
717+ && currentMapping . generated . column === generatedColumn )
718+ {
719+ return false ;
720+ }
721+ return ( currentMapping . original . line !== sourceNode . line
722+ || currentMapping . original . column !== sourceNode . column ) ;
723+ } ;
724+
725+ const build = ( sourceNode : SourceNode ) => {
726+ if ( isNewMapping ( sourceNode ) ) {
727+ currentMapping = {
728+ source : sourceNode . source ,
729+ original : { line : sourceNode . line , column : sourceNode . column } ,
730+ generated : { line : generatedLine , column : generatedColumn } ,
731+ } ;
732+ map . addMapping ( currentMapping ) ;
733+ }
734+
735+ for ( const chunk of sourceNode . children ) {
736+ if ( typeof chunk === "string" ) {
737+ const lines = ( chunk as string ) . split ( "\n" ) ;
738+ if ( lines . length > 1 ) {
739+ generatedLine += lines . length - 1 ;
740+ generatedColumn = 0 ;
741+ currentMapping = undefined ; // Mappings end at newlines
742+ }
743+ generatedColumn += lines [ lines . length - 1 ] . length ;
744+
745+ } else {
746+ build ( chunk ) ;
747+ }
748+ }
749+ } ;
750+ build ( rootSourceNode ) ;
751+
752+ return map ;
753+ }
692754}
0 commit comments