OLD | NEW |
1 require('source-map-support').install(); | |
2 import {SourceMapGenerator} from 'source-map'; | |
3 import * as fs from 'fs'; | 1 import * as fs from 'fs'; |
4 import * as path from 'path'; | 2 import * as path from 'path'; |
5 import * as ts from 'typescript'; | 3 import * as ts from 'typescript'; |
6 | 4 |
7 import {TranspilerBase} from './base'; | 5 import {Set, TranspilerBase} from './base'; |
| 6 |
8 import mkdirP from './mkdirp'; | 7 import mkdirP from './mkdirp'; |
9 import CallTranspiler from './call'; | |
10 import DeclarationTranspiler from './declaration'; | 8 import DeclarationTranspiler from './declaration'; |
11 import ExpressionTranspiler from './expression'; | 9 import * as merge from './merge'; |
12 import ModuleTranspiler from './module'; | 10 import ModuleTranspiler from './module'; |
13 import StatementTranspiler from './statement'; | |
14 import TypeTranspiler from './type'; | 11 import TypeTranspiler from './type'; |
15 import LiteralTranspiler from './literal'; | 12 import {FacadeConverter, NameRewriter} from './facade_converter'; |
16 import {FacadeConverter} from './facade_converter'; | |
17 import * as dartStyle from 'dart-style'; | 13 import * as dartStyle from 'dart-style'; |
18 | 14 |
19 export interface TranspilerOptions { | 15 export interface TranspilerOptions { |
20 /** | 16 /** |
21 * Fail on the first error, do not collect multiple. Allows easier debugging a
s stack traces lead | 17 * Fail on the first error, do not collect multiple. Allows easier debugging a
s stack traces lead |
22 * directly to the offending line. | 18 * directly to the offending line. |
23 */ | 19 */ |
24 failFast?: boolean; | 20 failFast?: boolean; |
25 /** Whether to generate 'library a.b.c;' names from relative file paths. */ | 21 /** Whether to generate 'library a.b.c;' names from relative file paths. */ |
26 generateLibraryName?: boolean; | 22 generateLibraryName?: boolean; |
27 /** Whether to generate source maps. */ | |
28 generateSourceMap?: boolean; | |
29 /** | 23 /** |
30 * A base path to relativize absolute file paths against. This is useful for l
ibrary name | 24 * A base path to relativize absolute file paths against. This is useful for l
ibrary name |
31 * generation (see above) and nicer file names in error messages. | 25 * generation (see above) and nicer file names in error messages. |
32 */ | 26 */ |
33 basePath?: string; | 27 basePath?: string; |
34 /** | 28 /** |
35 * Translate calls to builtins, i.e. seemlessly convert from `Array` to `List`
, and convert the | 29 * Use dart:html instead of the raw JavaScript DOM when generated Dart code. |
36 * corresponding methods. Requires type checking. | |
37 */ | 30 */ |
38 translateBuiltins?: boolean; | 31 useHtml?: boolean; |
39 /** | 32 /** |
40 * Enforce conventions of public/private keyword and underscore prefix | 33 * Enforce conventions of public/private keyword and underscore prefix |
41 */ | 34 */ |
42 enforceUnderscoreConventions?: boolean; | 35 enforceUnderscoreConventions?: boolean; |
43 /** | 36 /** |
44 * Sets a root path to look for typings used by the facade converter. | 37 * Sets a root path to look for typings used by the facade converter. |
45 */ | 38 */ |
46 typingsRoot?: string; | 39 typingsRoot?: string; |
| 40 |
| 41 /** |
| 42 * Experimental JS Interop specific option to promote properties with function |
| 43 * types to methods instead of properties with a function type. This the makes |
| 44 * the Dart code more readable at the cost of disallowing setting the value of |
| 45 * the property. |
| 46 * Example JS library that benifits from this option: |
| 47 * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/chartjs/char
t.d.ts |
| 48 */ |
| 49 promoteFunctionLikeMembers?: boolean; |
47 } | 50 } |
48 | 51 |
49 export const COMPILER_OPTIONS: ts.CompilerOptions = { | 52 export const COMPILER_OPTIONS: ts.CompilerOptions = { |
50 allowNonTsExtensions: true, | 53 allowNonTsExtensions: true, |
51 experimentalDecorators: true, | 54 experimentalDecorators: true, |
52 module: ts.ModuleKind.CommonJS, | 55 module: ts.ModuleKind.CommonJS, |
53 target: ts.ScriptTarget.ES5, | 56 target: ts.ScriptTarget.ES5, |
54 }; | 57 }; |
55 | 58 |
| 59 /** |
| 60 * Context to ouput code into. |
| 61 */ |
| 62 export enum OutputContext { |
| 63 Import = 0, |
| 64 Header = 1, |
| 65 Default = 2, |
| 66 } |
| 67 |
| 68 const NUM_OUTPUT_CONTEXTS = 3; |
| 69 |
56 export class Transpiler { | 70 export class Transpiler { |
57 private output: Output; | 71 private outputs: Output[]; |
| 72 private outputStack: Output[]; |
58 private currentFile: ts.SourceFile; | 73 private currentFile: ts.SourceFile; |
59 | 74 importsEmitted: Set; |
60 // Comments attach to all following AST nodes before the next 'physical' token
. Track the earliest | 75 // Comments attach to all following AST nodes before the next 'physical' token
. Track the earliest |
61 // offset to avoid printing comments multiple times. | 76 // offset to avoid printing comments multiple times. |
62 private lastCommentIdx: number = -1; | 77 private lastCommentIdx: number = -1; |
63 private errors: string[] = []; | 78 private errors: string[] = []; |
64 | 79 |
65 private transpilers: TranspilerBase[]; | 80 private transpilers: TranspilerBase[]; |
| 81 private declarationTranspiler: DeclarationTranspiler; |
66 private fc: FacadeConverter; | 82 private fc: FacadeConverter; |
| 83 private nameRewriter: NameRewriter; |
67 | 84 |
68 constructor(private options: TranspilerOptions = {}) { | 85 constructor(private options: TranspilerOptions = {}) { |
69 // TODO: Remove the angular2 default when angular uses typingsRoot. | 86 this.nameRewriter = new NameRewriter(); |
70 this.fc = new FacadeConverter(this, options.typingsRoot || 'angular2/typings
/'); | 87 this.fc = new FacadeConverter(this, options.typingsRoot, this.nameRewriter,
options.useHtml); |
| 88 this.declarationTranspiler = new DeclarationTranspiler( |
| 89 this, this.fc, options.enforceUnderscoreConventions, options.promoteFunc
tionLikeMembers); |
71 this.transpilers = [ | 90 this.transpilers = [ |
72 new CallTranspiler(this, this.fc), // Has to come before StatementTranspi
ler! | 91 this.declarationTranspiler, |
73 new DeclarationTranspiler(this, this.fc, options.enforceUnderscoreConventi
ons), | |
74 new ExpressionTranspiler(this, this.fc), | |
75 new LiteralTranspiler(this, this.fc), | |
76 new ModuleTranspiler(this, this.fc, options.generateLibraryName), | 92 new ModuleTranspiler(this, this.fc, options.generateLibraryName), |
77 new StatementTranspiler(this), | |
78 new TypeTranspiler(this, this.fc), | 93 new TypeTranspiler(this, this.fc), |
79 ]; | 94 ]; |
80 } | 95 } |
81 | 96 |
82 /** | 97 /** |
83 * Transpiles the given files to Dart. | 98 * Transpiles the given files to Dart. |
84 * @param fileNames The input files. | 99 * @param fileNames The input files. |
85 * @param destination Location to write files to. Creates files next to their
sources if absent. | 100 * @param destination Location to write files to. Creates files next to their
sources if absent. |
86 */ | 101 */ |
87 transpile(fileNames: string[], destination?: string): void { | 102 transpile(fileNames: string[], destination?: string): void { |
88 if (this.options.basePath) { | 103 if (this.options.basePath) { |
89 this.options.basePath = this.normalizeSlashes(path.resolve(this.options.ba
sePath)); | 104 this.options.basePath = this.normalizeSlashes(path.resolve(this.options.ba
sePath)); |
90 } | 105 } |
91 fileNames = fileNames.map((f) => this.normalizeSlashes(f)); | 106 fileNames = fileNames.map((f) => this.normalizeSlashes(f)); |
92 let host = this.createCompilerHost(); | 107 let host = this.createCompilerHost(); |
93 if (this.options.basePath && destination === undefined) { | 108 if (this.options.basePath && destination === undefined) { |
94 throw new Error( | 109 throw new Error( |
95 'Must have a destination path when a basePath is specified ' + this.op
tions.basePath); | 110 'Must have a destination path when a basePath is specified ' + this.op
tions.basePath); |
96 } | 111 } |
97 let destinationRoot = destination || this.options.basePath || ''; | 112 let destinationRoot = destination || this.options.basePath || ''; |
98 let program = ts.createProgram(fileNames, this.getCompilerOptions(), host); | 113 let program = ts.createProgram(fileNames, this.getCompilerOptions(), host); |
99 if (this.options.translateBuiltins) { | 114 this.fc.setTypeChecker(program.getTypeChecker()); |
100 this.fc.setTypeChecker(program.getTypeChecker()); | 115 this.declarationTranspiler.setTypeChecker(program.getTypeChecker()); |
101 } | |
102 | 116 |
103 // Only write files that were explicitly passed in. | 117 // Only write files that were explicitly passed in. |
104 let fileSet: {[s: string]: boolean} = {}; | 118 let fileSet: {[s: string]: boolean} = {}; |
105 fileNames.forEach((f) => fileSet[f] = true); | 119 fileNames.forEach((f) => fileSet[f] = true); |
| 120 let sourceFiles = program.getSourceFiles().filter((sourceFile) => fileSet[so
urceFile.fileName]); |
106 | 121 |
107 this.errors = []; | 122 this.errors = []; |
108 program.getSourceFiles() | 123 |
109 .filter((sourceFile) => fileSet[sourceFile.fileName]) | 124 sourceFiles.forEach((f: ts.SourceFile) => { |
110 // Do not generate output for .d.ts files. | 125 let dartCode = this.translate(f); |
111 .filter((sourceFile: ts.SourceFile) => !sourceFile.fileName.match(/\.d\.
ts$/)) | 126 let outputFile = this.getOutputPath(path.resolve(f.fileName), destinationR
oot); |
112 .forEach((f: ts.SourceFile) => { | 127 mkdirP(path.dirname(outputFile)); |
113 let dartCode = this.translate(f); | 128 fs.writeFileSync(outputFile, dartCode); |
114 let outputFile = this.getOutputPath(path.resolve(f.fileName), destinat
ionRoot); | 129 }); |
115 mkdirP(path.dirname(outputFile)); | |
116 fs.writeFileSync(outputFile, dartCode); | |
117 }); | |
118 this.checkForErrors(program); | 130 this.checkForErrors(program); |
119 } | 131 } |
120 | 132 |
121 translateProgram(program: ts.Program): {[path: string]: string} { | 133 translateProgram(program: ts.Program): {[path: string]: string} { |
122 if (this.options.translateBuiltins) { | 134 this.fc.setTypeChecker(program.getTypeChecker()); |
123 this.fc.setTypeChecker(program.getTypeChecker()); | 135 this.declarationTranspiler.setTypeChecker(program.getTypeChecker()); |
124 } | 136 |
125 let paths: {[path: string]: string} = {}; | 137 let paths: {[path: string]: string} = {}; |
126 this.errors = []; | 138 this.errors = []; |
127 program.getSourceFiles() | 139 program.getSourceFiles() |
128 .filter( | 140 .filter( |
129 (sourceFile: ts.SourceFile) => | 141 (sourceFile: ts.SourceFile) => |
130 (!sourceFile.fileName.match(/\.d\.ts$/) && !!sourceFile.fileName
.match(/\.[jt]s$/))) | 142 (!sourceFile.fileName.match(/\.d\.ts$/) && !!sourceFile.fileName
.match(/\.[jt]s$/))) |
131 .forEach((f) => paths[f.fileName] = this.translate(f)); | 143 .forEach((f) => paths[f.fileName] = this.translate(f)); |
132 this.checkForErrors(program); | 144 this.checkForErrors(program); |
133 return paths; | 145 return paths; |
134 } | 146 } |
(...skipping 27 matching lines...) Expand all Loading... |
162 getCurrentDirectory: () => '', | 174 getCurrentDirectory: () => '', |
163 getNewLine: () => '\n', | 175 getNewLine: () => '\n', |
164 }; | 176 }; |
165 compilerHost.resolveModuleNames = getModuleResolver(compilerHost); | 177 compilerHost.resolveModuleNames = getModuleResolver(compilerHost); |
166 return compilerHost; | 178 return compilerHost; |
167 } | 179 } |
168 | 180 |
169 // Visible for testing. | 181 // Visible for testing. |
170 getOutputPath(filePath: string, destinationRoot: string): string { | 182 getOutputPath(filePath: string, destinationRoot: string): string { |
171 let relative = this.getRelativeFileName(filePath); | 183 let relative = this.getRelativeFileName(filePath); |
172 let dartFile = relative.replace(/.(js|es6|ts)$/, '.dart'); | 184 let dartFile = relative.replace(/.(js|es6|d\.ts|ts)$/, '.dart'); |
173 return this.normalizeSlashes(path.join(destinationRoot, dartFile)); | 185 return this.normalizeSlashes(path.join(destinationRoot, dartFile)); |
174 } | 186 } |
175 | 187 |
| 188 public pushContext(context: OutputContext) { this.outputStack.push(this.output
s[context]); } |
| 189 |
| 190 public popContext() { |
| 191 if (this.outputStack.length === 0) { |
| 192 this.reportError(null, 'Attempting to pop output stack when already empty'
); |
| 193 } |
| 194 this.outputStack.pop(); |
| 195 } |
| 196 |
176 private translate(sourceFile: ts.SourceFile): string { | 197 private translate(sourceFile: ts.SourceFile): string { |
177 this.currentFile = sourceFile; | 198 this.currentFile = sourceFile; |
178 this.output = | 199 this.outputs = []; |
179 new Output(sourceFile, this.getRelativeFileName(), this.options.generate
SourceMap); | 200 this.outputStack = []; |
| 201 this.importsEmitted = {}; |
| 202 for (let i = 0; i < NUM_OUTPUT_CONTEXTS; ++i) { |
| 203 this.outputs.push(new Output()); |
| 204 } |
| 205 |
180 this.lastCommentIdx = -1; | 206 this.lastCommentIdx = -1; |
| 207 merge.normalizeSourceFile(sourceFile); |
| 208 this.pushContext(OutputContext.Default); |
181 this.visit(sourceFile); | 209 this.visit(sourceFile); |
182 let result = this.output.getResult(); | 210 this.popContext(); |
| 211 if (this.outputStack.length > 0) { |
| 212 this.reportError( |
| 213 sourceFile, 'Internal error managing output contexts. ' + |
| 214 'Inconsistent push and pop context calls.'); |
| 215 } |
| 216 let result = ''; |
| 217 for (let output of this.outputs) { |
| 218 result += output.getResult(); |
| 219 } |
183 return this.formatCode(result, sourceFile); | 220 return this.formatCode(result, sourceFile); |
184 } | 221 } |
185 | 222 |
186 private formatCode(code: string, context: ts.Node) { | 223 private formatCode(code: string, context: ts.Node) { |
187 let result = dartStyle.formatCode(code); | 224 let result = dartStyle.formatCode(code); |
188 if (result.error) { | 225 if (result.error) { |
189 this.reportError(context, result.error); | 226 this.reportError(context, result.error); |
190 } | 227 } |
191 return result.code; | 228 return result.code; |
192 } | 229 } |
193 | 230 |
194 private checkForErrors(program: ts.Program) { | 231 private checkForErrors(program: ts.Program) { |
195 let errors = this.errors; | 232 let errors = this.errors; |
196 | 233 |
197 let diagnostics = program.getGlobalDiagnostics().concat(program.getSyntactic
Diagnostics()); | 234 let diagnostics = program.getGlobalDiagnostics().concat(program.getSyntactic
Diagnostics()); |
198 | 235 |
199 if ((errors.length || diagnostics.length) && this.options.translateBuiltins)
{ | 236 if ((errors.length || diagnostics.length)) { |
200 // Only report semantic diagnostics if ts2dart failed; this code is not a
generic compiler, so | 237 // Only report semantic diagnostics if facade generation failed; this |
201 // only yields TS errors if they could be the cause of ts2dart issues. | 238 // code is not a generic compiler, so only yields TS errors if they could |
| 239 // be the cause of facade generation issues. |
202 // This greatly speeds up tests and execution. | 240 // This greatly speeds up tests and execution. |
203 diagnostics = diagnostics.concat(program.getSemanticDiagnostics()); | 241 diagnostics = diagnostics.concat(program.getSemanticDiagnostics()); |
204 } | 242 } |
205 | 243 |
206 let diagnosticErrs = diagnostics.map((d) => { | 244 let diagnosticErrs = diagnostics.map((d) => { |
207 let msg = ''; | 245 let msg = ''; |
208 if (d.file) { | 246 if (d.file) { |
209 let pos = d.file.getLineAndCharacterOfPosition(d.start); | 247 let pos = d.file.getLineAndCharacterOfPosition(d.start); |
210 let fn = this.getRelativeFileName(d.file.fileName); | 248 let fn = this.getRelativeFileName(d.file.fileName); |
211 msg += ` ${fn}:${pos.line + 1}:${pos.character + 1}`; | 249 msg += ` ${fn}:${pos.line + 1}:${pos.character + 1}`; |
212 } | 250 } |
213 msg += ': '; | 251 msg += ': '; |
214 msg += ts.flattenDiagnosticMessageText(d.messageText, '\n'); | 252 msg += ts.flattenDiagnosticMessageText(d.messageText, '\n'); |
215 return msg; | 253 return msg; |
216 }); | 254 }); |
217 if (diagnosticErrs.length) errors = errors.concat(diagnosticErrs); | 255 if (diagnosticErrs.length) errors = errors.concat(diagnosticErrs); |
218 | 256 |
219 if (errors.length) { | 257 if (errors.length) { |
220 let e = new Error(errors.join('\n')); | 258 let e = new Error(errors.join('\n')); |
221 e.name = 'TS2DartError'; | 259 e.name = 'DartFacadeError'; |
222 throw e; | 260 throw e; |
223 } | 261 } |
224 } | 262 } |
225 | 263 |
226 /** | 264 /** |
227 * Returns `filePath`, relativized to the program's `basePath`. | 265 * Returns `filePath`, relativized to the program's `basePath`. |
228 * @param filePath Optional path to relativize, defaults to the current file's
path. | 266 * @param filePath Optional path to relativize, defaults to the current file's
path. |
229 */ | 267 */ |
230 getRelativeFileName(filePath?: string) { | 268 getRelativeFileName(filePath?: string) { |
231 if (filePath === undefined) filePath = path.resolve(this.currentFile.fileNam
e); | 269 if (filePath === undefined) filePath = path.resolve(this.currentFile.fileNam
e); |
232 // TODO(martinprobst): Use path.isAbsolute on node v0.12. | 270 // TODO(martinprobst): Use path.isAbsolute on node v0.12. |
233 if (this.normalizeSlashes(path.resolve('/x/', filePath)) !== filePath) { | 271 if (this.normalizeSlashes(path.resolve('/x/', filePath)) !== filePath) { |
234 return filePath; // already relative. | 272 return filePath; // already relative. |
235 } | 273 } |
236 let base = this.options.basePath || ''; | 274 let base = this.options.basePath || ''; |
237 if (filePath.indexOf(base) !== 0 && !filePath.match(/\.d\.ts$/)) { | 275 if (filePath.indexOf(base) !== 0 && !filePath.match(/\.d\.ts$/)) { |
238 throw new Error(`Files must be located under base, got ${filePath} vs ${ba
se}`); | 276 throw new Error(`Files must be located under base, got ${filePath} vs ${ba
se}`); |
239 } | 277 } |
240 return this.normalizeSlashes(path.relative(base, filePath)); | 278 return this.normalizeSlashes(path.relative(base, filePath)); |
241 } | 279 } |
242 | 280 |
243 emit(s: string) { this.output.emit(s); } | 281 private get currentOutput(): Output { return this.outputStack[this.outputStack
.length - 1]; } |
244 emitNoSpace(s: string) { this.output.emitNoSpace(s); } | 282 |
| 283 emit(s: string) { this.currentOutput.emit(s); } |
| 284 emitNoSpace(s: string) { this.currentOutput.emitNoSpace(s); } |
| 285 enterCodeComment() { return this.currentOutput.enterCodeComment(); } |
| 286 exitCodeComment() { return this.currentOutput.exitCodeComment(); } |
245 | 287 |
246 reportError(n: ts.Node, message: string) { | 288 reportError(n: ts.Node, message: string) { |
247 let file = n.getSourceFile() || this.currentFile; | 289 let file = n.getSourceFile() || this.currentFile; |
248 let fileName = this.getRelativeFileName(file.fileName); | 290 let fileName = this.getRelativeFileName(file.fileName); |
249 let start = n.getStart(file); | 291 let start = n.getStart(file); |
250 let pos = file.getLineAndCharacterOfPosition(start); | 292 let pos = file.getLineAndCharacterOfPosition(start); |
251 // Line and character are 0-based. | 293 // Line and character are 0-based. |
252 let fullMessage = `${fileName}:${pos.line + 1}:${pos.character + 1}: ${messa
ge}`; | 294 let fullMessage = `${fileName}:${pos.line + 1}:${pos.character + 1}: ${messa
ge}`; |
253 if (this.options.failFast) throw new Error(fullMessage); | 295 if (this.options.failFast) throw new Error(fullMessage); |
254 this.errors.push(fullMessage); | 296 this.errors.push(fullMessage); |
255 } | 297 } |
256 | 298 |
257 visit(node: ts.Node) { | 299 visit(node: ts.Node) { |
258 this.output.addSourceMapping(node); | 300 if (!node) return; |
259 let comments = ts.getLeadingCommentRanges(this.currentFile.text, node.getFul
lStart()); | 301 let comments = ts.getLeadingCommentRanges(this.currentFile.text, node.getFul
lStart()); |
260 if (comments) { | 302 if (comments) { |
261 comments.forEach((c) => { | 303 comments.forEach((c) => { |
| 304 // Warning: the following check means that comments will only be |
| 305 // emitted correctly if Dart code is emitted in the same order it |
| 306 // appeared in the JavaScript AST. |
262 if (c.pos <= this.lastCommentIdx) return; | 307 if (c.pos <= this.lastCommentIdx) return; |
263 this.lastCommentIdx = c.pos; | 308 this.lastCommentIdx = c.pos; |
264 let text = this.currentFile.text.substring(c.pos, c.end); | 309 let text = this.currentFile.text.substring(c.pos, c.end); |
265 this.emitNoSpace('\n'); | 310 this.currentOutput.maybeLineBreak(); |
266 this.emit(this.translateComment(text)); | 311 this.emit(this.translateComment(text)); |
267 if (c.hasTrailingNewLine) this.emitNoSpace('\n'); | 312 if (c.hasTrailingNewLine) this.emitNoSpace('\n'); |
268 }); | 313 }); |
269 } | 314 } |
270 | 315 |
271 for (let i = 0; i < this.transpilers.length; i++) { | 316 for (let i = 0; i < this.transpilers.length; i++) { |
272 if (this.transpilers[i].visitNode(node)) return; | 317 if (this.transpilers[i].visitNode(node)) return; |
273 } | 318 } |
274 | 319 |
275 this.reportError( | 320 this.reportError( |
276 node, | 321 node, |
277 'Unsupported node type ' + (<any>ts).SyntaxKind[node.kind] + ': ' + node
.getFullText()); | 322 'Unsupported node type ' + (<any>ts).SyntaxKind[node.kind] + ': ' + node
.getFullText()); |
278 } | 323 } |
279 | 324 |
280 private normalizeSlashes(path: string) { return path.replace(/\\/g, '/'); } | 325 private normalizeSlashes(path: string) { return path.replace(/\\/g, '/'); } |
281 | 326 |
282 private translateComment(comment: string): string { | 327 private translateComment(comment: string): string { |
283 comment = comment.replace(/\{@link ([^\}]+)\}/g, '[$1]'); | 328 comment = comment.replace(/\{@link ([^\}]+)\}/g, '[$1]'); |
284 | 329 |
285 // Remove the following tags and following comments till end of line. | 330 // Remove the following tags and following comments till end of line. |
286 comment = comment.replace(/@param.*$/gm, ''); | 331 comment = comment.replace(/@param.*$/gm, ''); |
287 comment = comment.replace(/@throws.*$/gm, ''); | 332 comment = comment.replace(/@throws.*$/gm, ''); |
288 comment = comment.replace(/@return.*$/gm, ''); | 333 comment = comment.replace(/@return.*$/gm, ''); |
289 | 334 |
290 // Remove the following tags. | 335 // Remove the following tags. |
291 comment = comment.replace(/@module/g, ''); | 336 comment = comment.replace(/@module/g, ''); |
292 comment = comment.replace(/@description/g, ''); | 337 comment = comment.replace(/@description/g, ''); |
293 comment = comment.replace(/@deprecated/g, ''); | 338 comment = comment.replace(/@deprecated/g, ''); |
294 | 339 |
| 340 // Hack to make comment indentation does not look as bad when dart format is |
| 341 // run on files with multiple nested modules. |
| 342 comment = comment.replace(/\n ( +[*])/g, '\n$1'); |
295 return comment; | 343 return comment; |
296 } | 344 } |
297 } | 345 } |
298 | 346 |
299 export function getModuleResolver(compilerHost: ts.CompilerHost) { | 347 export function getModuleResolver(compilerHost: ts.CompilerHost) { |
300 return (moduleNames: string[], containingFile: string): ts.ResolvedModule[] =>
{ | 348 return (moduleNames: string[], containingFile: string): ts.ResolvedModule[] =>
{ |
301 let res: ts.ResolvedModule[] = []; | 349 let res: ts.ResolvedModule[] = []; |
302 for (let mod of moduleNames) { | 350 for (let mod of moduleNames) { |
303 let lookupRes = | 351 let lookupRes = |
304 ts.nodeModuleNameResolver(mod, containingFile, COMPILER_OPTIONS, compi
lerHost); | 352 ts.nodeModuleNameResolver(mod, containingFile, COMPILER_OPTIONS, compi
lerHost); |
305 if (lookupRes.resolvedModule) { | 353 if (lookupRes.resolvedModule) { |
306 res.push(lookupRes.resolvedModule); | 354 res.push(lookupRes.resolvedModule); |
307 continue; | 355 continue; |
308 } | 356 } |
309 lookupRes = ts.classicNameResolver(mod, containingFile, COMPILER_OPTIONS,
compilerHost); | 357 lookupRes = ts.classicNameResolver(mod, containingFile, COMPILER_OPTIONS,
compilerHost); |
310 if (lookupRes.resolvedModule) { | 358 if (lookupRes.resolvedModule) { |
311 res.push(lookupRes.resolvedModule); | 359 res.push(lookupRes.resolvedModule); |
312 continue; | 360 continue; |
313 } | 361 } |
314 res.push(undefined); | 362 res.push(undefined); |
315 } | 363 } |
316 return res; | 364 return res; |
317 }; | 365 }; |
318 } | 366 } |
319 | 367 |
320 class Output { | 368 class Output { |
321 private result: string = ''; | 369 private result: string = ''; |
322 private column: number = 1; | 370 private firstColumn: boolean = true; |
323 private line: number = 1; | |
324 | 371 |
325 // Position information. | 372 private insideCodeComment: boolean = false; |
326 private generateSourceMap: boolean; | 373 private codeCommentResult: string = ''; |
327 private sourceMap: SourceMapGenerator; | |
328 | 374 |
329 constructor( | 375 /** |
330 private currentFile: ts.SourceFile, private relativeFileName: string, | 376 * Line break if the current line is not empty. |
331 generateSourceMap: boolean) { | 377 */ |
332 if (generateSourceMap) { | 378 maybeLineBreak() { |
333 this.sourceMap = new SourceMapGenerator({file: relativeFileName + '.dart'}
); | 379 if (this.insideCodeComment) { |
334 this.sourceMap.setSourceContent(relativeFileName, this.currentFile.text); | 380 // Avoid line breaks inside code comments. |
| 381 return; |
| 382 } |
| 383 |
| 384 if (!this.firstColumn) { |
| 385 this.emitNoSpace('\n'); |
335 } | 386 } |
336 } | 387 } |
337 | 388 |
338 emit(str: string) { | 389 emit(str: string) { |
339 this.emitNoSpace(' '); | 390 this.emitNoSpace(' '); |
340 this.emitNoSpace(str); | 391 this.emitNoSpace(str); |
341 } | 392 } |
342 | 393 |
343 emitNoSpace(str: string) { | 394 emitNoSpace(str: string) { |
| 395 if (str.length === 0) return; |
| 396 if (this.insideCodeComment) { |
| 397 this.codeCommentResult += str; |
| 398 return; |
| 399 } |
344 this.result += str; | 400 this.result += str; |
345 for (let i = 0; i < str.length; i++) { | 401 this.firstColumn = str[str.length - 1] === '\n'; |
346 if (str[i] === '\n') { | |
347 this.line++; | |
348 this.column = 0; | |
349 } else { | |
350 this.column++; | |
351 } | |
352 } | |
353 } | 402 } |
354 | 403 |
355 getResult(): string { return this.result + this.generateSourceMapComment(); } | 404 enterCodeComment() { |
356 | 405 if (this.insideCodeComment) { |
357 addSourceMapping(n: ts.Node) { | 406 throw 'Cannot nest code comments'; |
358 if (!this.generateSourceMap) return; // source maps disabled. | 407 } |
359 let file = n.getSourceFile() || this.currentFile; | 408 this.insideCodeComment = true; |
360 let start = n.getStart(file); | 409 this.codeCommentResult = ''; |
361 let pos = file.getLineAndCharacterOfPosition(start); | |
362 | |
363 let mapping: SourceMap.Mapping = { | |
364 original: {line: pos.line + 1, column: pos.character}, | |
365 generated: {line: this.line, column: this.column}, | |
366 source: this.relativeFileName, | |
367 }; | |
368 | |
369 this.sourceMap.addMapping(mapping); | |
370 } | 410 } |
371 | 411 |
372 private generateSourceMapComment() { | 412 exitCodeComment() { |
373 if (!this.sourceMap) return ''; | 413 if (!this.insideCodeComment) { |
374 let base64map = new Buffer(JSON.stringify(this.sourceMap)).toString('base64'
); | 414 throw 'Exit code comment called while not within a code comment.'; |
375 return '\n\n//# sourceMappingURL=data:application/json;base64,' + base64map; | 415 } |
| 416 this.insideCodeComment = false; |
| 417 this.emit('/*'); |
| 418 let result = dartStyle.formatCode(this.codeCommentResult); |
| 419 if (result.error) { |
| 420 // Fall back to the raw expression if the formatter returns an error. |
| 421 // Ideally the formatter would handle a wider range of dart expressions. |
| 422 this.emit(this.codeCommentResult.trim()); |
| 423 } else { |
| 424 this.emit(result.code.trim()); |
| 425 } |
| 426 this.emit('*/'); |
| 427 // Don't really need an exact column, just need to track |
| 428 // that we aren't on the first column. |
| 429 this.firstColumn = false; |
| 430 this.codeCommentResult = ''; |
| 431 } |
| 432 |
| 433 getResult(): string { |
| 434 if (this.insideCodeComment) { |
| 435 throw 'Code comment not property terminated.'; |
| 436 } |
| 437 return this.result; |
376 } | 438 } |
377 } | 439 } |
378 | 440 |
379 // CLI entry point | 441 // CLI entry point |
380 if (require.main === module) { | 442 if (require.main === module) { |
381 let args = require('minimist')(process.argv.slice(2), {base: 'string'}); | 443 let args = require('minimist')(process.argv.slice(2), {base: 'string'}); |
382 try { | 444 try { |
383 let transpiler = new Transpiler(args); | 445 let transpiler = new Transpiler(args); |
384 console.error('Transpiling', args._, 'to', args.destination); | 446 console.error('Transpiling', args._, 'to', args.destination); |
385 transpiler.transpile(args._, args.destination); | 447 transpiler.transpile(args._, args.destination); |
386 } catch (e) { | 448 } catch (e) { |
387 if (e.name !== 'TS2DartError') throw e; | 449 if (e.name !== 'DartFacadeError') throw e; |
388 console.error(e.message); | 450 console.error(e.message); |
389 process.exit(1); | 451 process.exit(1); |
390 } | 452 } |
391 } | 453 } |
OLD | NEW |