| 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 |