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