Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit eb89105

Browse files
committed
Generate source maps during transpilation.
Also refactor the interface to ts2dart to pass an options object, for better readability of the call sites.
1 parent c92eb96 commit eb89105

File tree

4 files changed

+122
-32
lines changed

4 files changed

+122
-32
lines changed

lib/main.ts

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,45 @@
1-
/// <reference path="../typings/node/node.d.ts" />
1+
/// <reference path='../typings/node/node.d.ts' />
2+
/// <reference path='../typings/source-map/source-map.d.ts' />
23
// Use HEAD version of typescript, installed by npm
3-
/// <reference path="../node_modules/typescript/bin/typescript.d.ts" />
4+
/// <reference path='../node_modules/typescript/bin/typescript.d.ts' />
45
require('source-map-support').install();
5-
import ts = require("typescript");
6-
import fs = require("fs");
6+
import fs = require('fs');
7+
import SourceMap = require('source-map');
8+
import ts = require('typescript');
79

810
export type ClassLike = ts.ClassDeclaration | ts.InterfaceDeclaration;
911

12+
export interface TranspilerOptions {
13+
failFast?: boolean;
14+
generateLibraryName?: boolean;
15+
generateSourceMap?: boolean;
16+
}
17+
1018
export class Transpiler {
19+
// Position information.
20+
generateSourceMap: boolean;
21+
sourceMap: SourceMap.SourceMapGenerator;
22+
23+
generateLibraryName: boolean;
24+
relativeFileName: string;
25+
currentFile: ts.SourceFile;
26+
1127
result: string = '';
28+
column: number = 1;
29+
line: number = 1;
30+
1231
// Comments attach to all following AST nodes before the next 'physical' token. Track the earliest
1332
// offset to avoid printing comments multiple times.
1433
lastCommentIdx: number = -1;
15-
currentFile: ts.SourceFile;
16-
relativeFileName: string;
34+
35+
failFast: boolean;
1736
errors: string[] = [];
1837

19-
constructor(private failFast: boolean = false, private generateLibraryName: boolean = false) {}
38+
constructor(opts: TranspilerOptions = {}) {
39+
this.failFast = !!opts.failFast;
40+
this.generateLibraryName = !!opts.generateLibraryName;
41+
this.generateSourceMap = !!opts.generateSourceMap;
42+
}
2043

2144
static OPTIONS: ts.CompilerOptions = {
2245
target: ts.ScriptTarget.ES6,
@@ -26,11 +49,18 @@ export class Transpiler {
2649

2750
translateProgram(program: ts.Program, relativeFileName: string): string {
2851
this.relativeFileName = relativeFileName;
29-
return program.getSourceFiles()
30-
.filter((sourceFile: ts.SourceFile) => !sourceFile.fileName.match(/\.d\.ts$/) &&
31-
!!sourceFile.fileName.match(/\.[jt]s$/))
32-
.map((f) => this.translate(f))
33-
.join('\n');
52+
var src = program.getSourceFiles()
53+
.filter((sourceFile: ts.SourceFile) => !sourceFile.fileName.match(/\.d\.ts$/) &&
54+
!!sourceFile.fileName.match(/\.[jt]s$/))
55+
.map((f) => this.translate(f))
56+
.join('\n');
57+
if (this.sourceMap) src += this.generateSourceMapComment();
58+
return src;
59+
}
60+
61+
generateSourceMapComment() {
62+
var base64map = new Buffer(JSON.stringify(this.sourceMap)).toString('base64');
63+
return '\n\n//# sourceMappingURL=data:application/json;base64,' + base64map;
3464
}
3565

3666
createCompilerHost(files: string[]): ts.CompilerHost {
@@ -80,19 +110,51 @@ export class Transpiler {
80110
this.result = '';
81111
this.errors = [];
82112
this.lastCommentIdx = -1;
83-
this.currentFile = sourceFile.getSourceFile();
113+
this.currentFile = sourceFile;
114+
if (this.generateSourceMap) {
115+
this.sourceMap = new SourceMap.SourceMapGenerator({file: this.relativeFileName + '.dart'});
116+
this.sourceMap.setSourceContent(this.relativeFileName, this.currentFile.text);
117+
}
84118
this.visit(sourceFile);
85119
if (this.errors.length) {
86120
var e = new Error(this.errors.join('\n'));
87121
e.name = 'TS2DartError';
88122
throw e;
89123
}
124+
90125
return this.result;
91126
}
92127

128+
enterNode(n: ts.Node) {
129+
if (!this.sourceMap) return; // source maps disabled.
130+
var file = n.getSourceFile() || this.currentFile;
131+
var start = n.getStart(file);
132+
var pos = file.getLineAndCharacterOfPosition(start);
133+
134+
var mapping: SourceMap.Mapping = {
135+
original: {line: pos.line + 1, column: pos.character},
136+
generated: {line: this.line, column: this.column},
137+
source: this.relativeFileName,
138+
};
139+
140+
this.sourceMap.addMapping(mapping);
141+
}
142+
93143
emit(str: string) {
94-
this.result += ' ';
144+
this.emitNoSpace(' ');
145+
this.emitNoSpace(str);
146+
}
147+
148+
emitNoSpace(str: string) {
95149
this.result += str;
150+
for (var i = 0; i < str.length; i++) {
151+
if (str[i] === '\n') {
152+
this.line++;
153+
this.column = 0;
154+
} else {
155+
this.column++;
156+
}
157+
}
96158
}
97159

98160
visitEach(nodes: ts.Node[]) { nodes.forEach((n) => this.visit(n)); }
@@ -144,7 +206,7 @@ export class Transpiler {
144206
this.visitParameters(fn);
145207
} else {
146208
if (fn.parameters && fn.parameters.length > 0) {
147-
this.reportError(fn, "getter should not accept parameters");
209+
this.reportError(fn, 'getter should not accept parameters');
148210
}
149211
}
150212
if (fn.body) {
@@ -583,14 +645,15 @@ export class Transpiler {
583645
}
584646

585647
visit(node: ts.Node) {
648+
this.enterNode(node);
586649
var comments = ts.getLeadingCommentRanges(this.currentFile.text, node.getFullStart());
587650
if (comments) {
588651
comments.forEach((c) => {
589652
if (c.pos <= this.lastCommentIdx) return;
590653
this.lastCommentIdx = c.pos;
591654
var text = this.currentFile.text.substring(c.pos, c.end);
592655
this.emit(text);
593-
if (c.hasTrailingNewLine) this.result += '\n';
656+
if (c.hasTrailingNewLine) this.emitNoSpace('\n');
594657
});
595658
}
596659

@@ -773,7 +836,7 @@ export class Transpiler {
773836
this.emit(`'''${this.escapeTextForTemplateString(node)}'''`);
774837
break;
775838
case ts.SyntaxKind.TemplateMiddle:
776-
this.result += this.escapeTextForTemplateString(node);
839+
this.emitNoSpace(this.escapeTextForTemplateString(node));
777840
break;
778841
case ts.SyntaxKind.TemplateExpression:
779842
var tmpl = <ts.TemplateExpression>node;
@@ -784,16 +847,16 @@ export class Transpiler {
784847
this.emit(`'''${this.escapeTextForTemplateString(node)}`); //highlighting bug:'
785848
break;
786849
case ts.SyntaxKind.TemplateTail:
787-
this.result += this.escapeTextForTemplateString(node);
788-
this.result += `'''`;
850+
this.emitNoSpace(this.escapeTextForTemplateString(node));
851+
this.emitNoSpace(`'''`);
789852
break;
790853
case ts.SyntaxKind.TemplateSpan:
791854
var span = <ts.TemplateSpan>node;
792855
if (span.expression) {
793856
// Do not emit extra whitespace inside the string template
794-
this.result += '${';
857+
this.emitNoSpace('${');
795858
this.visit(span.expression);
796-
this.result += '}';
859+
this.emitNoSpace('}');
797860
}
798861
if (span.literal) this.visit(span.literal);
799862
break;
@@ -813,9 +876,9 @@ export class Transpiler {
813876
var propAssign = <ts.PropertyAssignment>node;
814877
if (propAssign.name.kind === ts.SyntaxKind.Identifier) {
815878
// Dart identifiers in Map literals need quoting.
816-
this.result += ' "';
817-
this.result += (<ts.Identifier>propAssign.name).text;
818-
this.result += '"';
879+
this.emitNoSpace(' "');
880+
this.emitNoSpace((<ts.Identifier>propAssign.name).text);
881+
this.emitNoSpace('"');
819882
} else {
820883
this.visit(propAssign.name);
821884
}
@@ -824,9 +887,9 @@ export class Transpiler {
824887
break;
825888
case ts.SyntaxKind.ShorthandPropertyAssignment:
826889
var shorthand = <ts.ShorthandPropertyAssignment>node;
827-
this.result += ' "';
828-
this.result += shorthand.name.text;
829-
this.result += '"';
890+
this.emitNoSpace(' "');
891+
this.emitNoSpace(shorthand.name.text);
892+
this.emitNoSpace('"');
830893
this.emit(':');
831894
this.visit(shorthand.name);
832895
break;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"test": "test"
88
},
99
"dependencies": {
10+
"source-map": "^0.4.2",
1011
"source-map-support": "^0.2.9",
1112
"typescript": "Microsoft/TypeScript#cebe42b81fbeeaf68c8e228dda5e602ab94e151b"
1213
},

test/DartTest.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/// <reference path="../typings/chai/chai.d.ts"/>
22
/// <reference path="../typings/mocha/mocha.d.ts"/>
3+
/// <reference path="../typings/source-map/source-map.d.ts"/>
34
/// <reference path="../typings/source-map-support/source-map-support.d.ts"/>
45

5-
import sms = require('source-map-support');
6-
sms.install();
6+
require('source-map-support').install();
77

88
import chai = require('chai');
99
import main = require('../lib/main');
10+
import SourceMap = require('source-map');
1011
import ts = require('typescript');
1112

1213
describe('transpile to dart', () => {
@@ -573,7 +574,7 @@ describe('transpile to dart', () => {
573574

574575
describe('library name', () => {
575576
var transpiler;
576-
beforeEach(() => transpiler = new main.Transpiler(true, /* generateLibraryName */ true));
577+
beforeEach(() => transpiler = new main.Transpiler({failFast: true, generateLibraryName: true}));
577578
it('adds a library name', () => {
578579
var program = parseProgram('var x;', '/a/b/c.ts');
579580
var res = transpiler.translateProgram(program, 'a/b/c.ts');
@@ -592,6 +593,28 @@ describe('transpile to dart', () => {
592593
it('handles non word characters',
593594
() => { chai.expect(transpiler.getLibraryName('a/%x.ts')).to.equal('a._x'); });
594595
});
596+
597+
describe('source maps', () => {
598+
var transpiler: main.Transpiler;
599+
beforeEach(() => transpiler = new main.Transpiler({failFast: true, generateSourceMap: true}));
600+
function expectTranslateMap(source) {
601+
var program = parseProgram(source, '/absolute/path/test.ts');
602+
var res = transpiler.translateProgram(program, 'path/test.ts');
603+
return chai.expect(res);
604+
}
605+
it('generates a source map', () => {
606+
expectTranslateMap('var x;').to.contain('//# sourceMappingURL=data:application/json;base64,');
607+
});
608+
it('maps locations', () => {
609+
expectTranslateMap('var xVar: number;\nvar yVar: string;')
610+
.to.contain(' num xVar ; String yVar ;');
611+
var consumer = new SourceMap.SourceMapConsumer(<any>transpiler.sourceMap.toString());
612+
var expectedColumn = ' num xVar ; String yVar ;'.indexOf('yVar') + 1;
613+
var pos = consumer.originalPositionFor({line: 1, column: expectedColumn});
614+
chai.expect(pos).to.include({line: 2, column: 4});
615+
chai.expect(consumer.sourceContentFor('path/test.ts')).to.contain('yVar: string');
616+
});
617+
});
595618
});
596619

597620
function parseProgram(contents: string, fileName = 'file.ts'): ts.Program {
@@ -629,6 +652,6 @@ function parseProgram(contents: string, fileName = 'file.ts'): ts.Program {
629652

630653
function translateSource(contents: string, failFast = true): string {
631654
var program = parseProgram(contents);
632-
var transpiler = new main.Transpiler(failFast, /* generateLibraryName */ false);
633-
return transpiler.translateProgram(program, null);
655+
var transpiler = new main.Transpiler({failFast});
656+
return transpiler.translateProgram(program, 'test.ts');
634657
}

tsd.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
},
1717
"source-map-support/source-map-support.d.ts": {
1818
"commit": "69bdfb0884020e41f17a1dd80ad4c77de2636874"
19+
},
20+
"source-map/source-map.d.ts": {
21+
"commit": "2520bce9a8a71b66e67487cbd5b33fec880b0c55"
1922
}
2023
}
2124
}

0 commit comments

Comments
 (0)