Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(42)

Side by Side Diff: lib/main.ts

Issue 2394683003: JS Interop Facade generation polish.
Patch Set: more cleanup Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/facade_converter.ts ('k') | lib/merge.ts » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 import * as dartStyle from 'dart-style';
1 import * as fs from 'fs'; 2 import * as fs from 'fs';
2 import * as path from 'path'; 3 import * as path from 'path';
3 import * as ts from 'typescript'; 4 import * as ts from 'typescript';
4 5
5 import * as base from './base'; 6 import * as base from './base';
6 import {Set, TranspilerBase} from './base'; 7 import {Set, TranspilerBase} from './base';
7 8 import DeclarationTranspiler from './declaration';
9 import {FacadeConverter, NameRewriter} from './facade_converter';
10 import * as merge from './merge';
8 import mkdirP from './mkdirp'; 11 import mkdirP from './mkdirp';
9 import DeclarationTranspiler from './declaration';
10 import * as merge from './merge';
11 import ModuleTranspiler from './module'; 12 import ModuleTranspiler from './module';
12 import TypeTranspiler from './type'; 13 import TypeTranspiler from './type';
13 import {FacadeConverter, NameRewriter} from './facade_converter';
14 import * as dartStyle from 'dart-style';
15 14
16 export interface TranspilerOptions { 15 export interface TranspilerOptions {
17 /** 16 /**
18 * 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
19 * directly to the offending line. 18 * directly to the offending line.
20 */ 19 */
21 failFast?: boolean; 20 failFast?: boolean;
22 /** Whether to generate 'library a.b.c;' names from relative file paths. */ 21 /**
23 generateLibraryName?: boolean; 22 * Pass in a module name (e.g.) d3 instead of determining the module name fro the d.ts files.
23 * This is useful when using libraries that assume they will be loaded with a JS module loader
24 * without having to use a JS module loader.
25 */
26 moduleName?: string;
24 /** 27 /**
25 * A base path to relativize absolute file paths against. This is useful for l ibrary name 28 * A base path to relativize absolute file paths against. This is useful for l ibrary name
26 * generation (see above) and nicer file names in error messages. 29 * generation (see above) and nicer file names in error messages.
27 */ 30 */
28 basePath?: string; 31 basePath?: string;
29 /** 32 /**
30 * Use dart:html instead of the raw JavaScript DOM when generated Dart code.
31 */
32 useHtml?: boolean;
33 /**
34 * Enforce conventions of public/private keyword and underscore prefix 33 * Enforce conventions of public/private keyword and underscore prefix
35 */ 34 */
36 enforceUnderscoreConventions?: boolean; 35 enforceUnderscoreConventions?: boolean;
37 /** 36 /**
38 * 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.
39 */ 38 */
40 typingsRoot?: string; 39 typingsRoot?: string;
41 40
42 /** 41 /**
43 * Experimental JS Interop specific option to promote properties with function 42 * Experimental JS Interop specific option to promote properties with function
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
75 importsEmitted: Set; 74 importsEmitted: Set;
76 // 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
77 // offset to avoid printing comments multiple times. 76 // offset to avoid printing comments multiple times.
78 private lastCommentIdx: number = -1; 77 private lastCommentIdx: number = -1;
79 private errors: string[] = []; 78 private errors: string[] = [];
80 79
81 private transpilers: TranspilerBase[]; 80 private transpilers: TranspilerBase[];
82 private declarationTranspiler: DeclarationTranspiler; 81 private declarationTranspiler: DeclarationTranspiler;
83 private fc: FacadeConverter; 82 private fc: FacadeConverter;
84 private nameRewriter: NameRewriter; 83 private nameRewriter: NameRewriter;
84 private typeArgumentDepth = 0;
85 85
86 constructor(private options: TranspilerOptions = {}) { 86 constructor(private options: TranspilerOptions = {}) {
87 this.nameRewriter = new NameRewriter(); 87 this.nameRewriter = new NameRewriter();
88 this.fc = new FacadeConverter(this, options.typingsRoot, this.nameRewriter, options.useHtml); 88 this.fc = new FacadeConverter(this, options.typingsRoot, this.nameRewriter);
89 this.declarationTranspiler = new DeclarationTranspiler( 89 this.declarationTranspiler = new DeclarationTranspiler(
90 this, this.fc, options.enforceUnderscoreConventions, options.promoteFunc tionLikeMembers); 90 this, this.fc, options.enforceUnderscoreConventions, options.promoteFunc tionLikeMembers);
91 this.transpilers = [ 91 this.transpilers = [
92 new ModuleTranspiler(this, this.fc, options.moduleName),
92 this.declarationTranspiler, 93 this.declarationTranspiler,
93 new ModuleTranspiler(this, this.fc, options.generateLibraryName),
94 new TypeTranspiler(this, this.fc), 94 new TypeTranspiler(this, this.fc),
95 ]; 95 ];
96 } 96 }
97 97
98 /** 98 /**
99 * Transpiles the given files to Dart. 99 * Transpiles the given files to Dart.
100 * @param fileNames The input files. 100 * @param fileNames The input files.
101 * @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.
102 */ 102 */
103 transpile(fileNames: string[], destination?: string): void { 103 transpile(fileNames: string[], destination?: string): void {
104 if (this.options.basePath) { 104 if (this.options.basePath) {
105 this.options.basePath = this.normalizeSlashes(path.resolve(this.options.ba sePath)); 105 this.options.basePath = this.normalizeSlashes(path.resolve(this.options.ba sePath));
106 } 106 }
107 fileNames = fileNames.map((f) => this.normalizeSlashes(f)); 107 fileNames = fileNames.map((f) => this.normalizeSlashes(f));
108 let host = this.createCompilerHost(); 108 let host = this.createCompilerHost();
109 if (this.options.basePath && destination === undefined) {
110 throw new Error(
111 'Must have a destination path when a basePath is specified ' + this.op tions.basePath);
112 }
113 let destinationRoot = destination || this.options.basePath || '';
114 let program = ts.createProgram(fileNames, this.getCompilerOptions(), host); 109 let program = ts.createProgram(fileNames, this.getCompilerOptions(), host);
115 this.fc.setTypeChecker(program.getTypeChecker()); 110 this.fc.setTypeChecker(program.getTypeChecker());
116 this.declarationTranspiler.setTypeChecker(program.getTypeChecker()); 111 this.declarationTranspiler.setTypeChecker(program.getTypeChecker());
117 112
118 // Only write files that were explicitly passed in. 113 // Only write files that were explicitly passed in.
119 let fileSet: {[s: string]: boolean} = {}; 114 let fileSet: {[s: string]: boolean} = {};
120 fileNames.forEach((f) => fileSet[f] = true); 115 fileNames.forEach((f) => fileSet[f] = true);
121 let sourceFiles = program.getSourceFiles().filter((sourceFile) => fileSet[so urceFile.fileName]); 116 let sourceFiles = program.getSourceFiles().filter((sourceFile) => fileSet[so urceFile.fileName]);
122 117
123 this.errors = []; 118 this.errors = [];
124 119
120 let sourceFileMap: {[s: string]: ts.SourceFile} = {};
121 sourceFiles.forEach((f: ts.SourceFile) => { sourceFileMap[f.fileName] = f; } );
122
123 // Check for global module export declarations and propogate them to
124 // and propogate
125 sourceFiles.forEach((f: ts.SourceFile) => {
126 f.statements.forEach((n: ts.Node) => {
127 if (n.kind !== ts.SyntaxKind.GlobalModuleExportDeclaration) return;
128 // This is the name we are interested in for Dart purposes until Dart su pports AMD module
129 // loaders. This module name should all be reflected by all modules expo rted by this
130 // library as we need to specify a global module location for every Dart library.
131 let globalModuleName = base.ident((n as ts.GlobalModuleExportDeclaration ).name);
132 f.moduleName = globalModuleName;
133
134 f.statements.forEach((e: ts.Node) => {
135 if (e.kind !== ts.SyntaxKind.ExportDeclaration) return;
136 let exportDecl = e as ts.ExportDeclaration;
137 if (!exportDecl.moduleSpecifier) return;
138 let moduleLocation = <ts.StringLiteral>exportDecl.moduleSpecifier;
139 let location = moduleLocation.text;
140 let resolvedPath = host.resolveModuleNames([location], f.fileName);
141 resolvedPath.forEach((p) => {
142 if (p.isExternalLibraryImport) return;
143 let exportedFile = sourceFileMap[p.resolvedFileName];
144 exportedFile.moduleName = globalModuleName;
145 });
146 });
147 });
148 });
149
125 sourceFiles.forEach((f: ts.SourceFile) => { 150 sourceFiles.forEach((f: ts.SourceFile) => {
126 let dartCode = this.translate(f); 151 let dartCode = this.translate(f);
127 let outputFile = this.getOutputPath(path.resolve(f.fileName), destinationR oot); 152
128 mkdirP(path.dirname(outputFile)); 153 if (destination) {
129 fs.writeFileSync(outputFile, dartCode); 154 let outputFile = this.getOutputPath(path.resolve(f.fileName), destinatio n);
155 console.log('Output file:', outputFile);
156 mkdirP(path.dirname(outputFile));
157 fs.writeFileSync(outputFile, dartCode);
158 } else {
159 console.log(dartCode);
160 }
130 }); 161 });
131 this.checkForErrors(program); 162 this.checkForErrors(program);
132 } 163 }
133 164
134 translateProgram(program: ts.Program): {[path: string]: string} { 165 translateProgram(program: ts.Program): {[path: string]: string} {
135 this.fc.setTypeChecker(program.getTypeChecker()); 166 this.fc.setTypeChecker(program.getTypeChecker());
136 this.declarationTranspiler.setTypeChecker(program.getTypeChecker()); 167 this.declarationTranspiler.setTypeChecker(program.getTypeChecker());
137 168
138 let paths: {[path: string]: string} = {}; 169 let paths: {[path: string]: string} = {};
139 this.errors = []; 170 this.errors = [];
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
174 getCanonicalFileName: (filename) => filename, 205 getCanonicalFileName: (filename) => filename,
175 getCurrentDirectory: () => '', 206 getCurrentDirectory: () => '',
176 getNewLine: () => '\n', 207 getNewLine: () => '\n',
177 }; 208 };
178 compilerHost.resolveModuleNames = getModuleResolver(compilerHost); 209 compilerHost.resolveModuleNames = getModuleResolver(compilerHost);
179 return compilerHost; 210 return compilerHost;
180 } 211 }
181 212
182 // Visible for testing. 213 // Visible for testing.
183 getOutputPath(filePath: string, destinationRoot: string): string { 214 getOutputPath(filePath: string, destinationRoot: string): string {
184 let relative = this.getRelativeFileName(filePath); 215 let relative = this.getDartFileName(filePath);
185 let dartFile = relative.replace(/.(js|es6|d\.ts|ts)$/, '.dart'); 216 return this.normalizeSlashes(path.join(destinationRoot, relative));
186 return this.normalizeSlashes(path.join(destinationRoot, dartFile));
187 } 217 }
188 218
189 public pushContext(context: OutputContext) { this.outputStack.push(this.output s[context]); } 219 public pushContext(context: OutputContext) { this.outputStack.push(this.output s[context]); }
190 220
191 public popContext() { 221 public popContext() {
192 if (this.outputStack.length === 0) { 222 if (this.outputStack.length === 0) {
193 this.reportError(null, 'Attempting to pop output stack when already empty' ); 223 this.reportError(null, 'Attempting to pop output stack when already empty' );
194 } 224 }
195 this.outputStack.pop(); 225 this.outputStack.pop();
196 } 226 }
197 227
198 private translate(sourceFile: ts.SourceFile): string { 228 private translate(sourceFile: ts.SourceFile): string {
199 this.currentFile = sourceFile; 229 this.currentFile = sourceFile;
200 this.outputs = []; 230 this.outputs = [];
201 this.outputStack = []; 231 this.outputStack = [];
202 this.importsEmitted = {}; 232 this.importsEmitted = {};
203 for (let i = 0; i < NUM_OUTPUT_CONTEXTS; ++i) { 233 for (let i = 0; i < NUM_OUTPUT_CONTEXTS; ++i) {
204 this.outputs.push(new Output()); 234 this.outputs.push(new Output());
205 } 235 }
206 236
207 this.lastCommentIdx = -1; 237 this.lastCommentIdx = -1;
208 merge.normalizeSourceFile(sourceFile); 238 merge.normalizeSourceFile(sourceFile, this.fc);
209 this.pushContext(OutputContext.Default); 239 this.pushContext(OutputContext.Default);
210 this.visit(sourceFile); 240 this.visit(sourceFile);
211 this.popContext(); 241 this.popContext();
212 if (this.outputStack.length > 0) { 242 if (this.outputStack.length > 0) {
213 this.reportError( 243 this.reportError(
214 sourceFile, 'Internal error managing output contexts. ' + 244 sourceFile, 'Internal error managing output contexts. ' +
215 'Inconsistent push and pop context calls.'); 245 'Inconsistent push and pop context calls.');
216 } 246 }
217 let result = ''; 247 let result = '';
218 for (let output of this.outputs) { 248 for (let output of this.outputs) {
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 let e = new Error(errors.join('\n')); 290 let e = new Error(errors.join('\n'));
261 e.name = 'DartFacadeError'; 291 e.name = 'DartFacadeError';
262 throw e; 292 throw e;
263 } 293 }
264 } 294 }
265 295
266 /** 296 /**
267 * Returns `filePath`, relativized to the program's `basePath`. 297 * Returns `filePath`, relativized to the program's `basePath`.
268 * @param filePath Optional path to relativize, defaults to the current file's path. 298 * @param filePath Optional path to relativize, defaults to the current file's path.
269 */ 299 */
270 getRelativeFileName(filePath?: string) { 300 getRelativeFileName(filePath?: string): string {
271 if (filePath === undefined) filePath = path.resolve(this.currentFile.fileNam e); 301 if (filePath === undefined) filePath = path.resolve(this.currentFile.fileNam e);
272 // TODO(martinprobst): Use path.isAbsolute on node v0.12. 302 // TODO(jacobr): Use path.isAbsolute on node v0.12.
273 if (this.normalizeSlashes(path.resolve('/x/', filePath)) !== filePath) { 303 if (this.normalizeSlashes(path.resolve('/x/', filePath)) !== filePath) {
274 return filePath; // already relative. 304 return filePath; // already relative.
275 } 305 }
276 let base = this.options.basePath || ''; 306 let base = this.options.basePath || '';
277 if (filePath.indexOf(base) !== 0 && !filePath.match(/\.d\.ts$/)) { 307 if (filePath.indexOf(base) !== 0 && !filePath.match(/\.d\.ts$/)) {
278 throw new Error(`Files must be located under base, got ${filePath} vs ${ba se}`); 308 throw new Error(`Files must be located under base, got ${filePath} vs ${ba se}`);
279 } 309 }
280 return this.normalizeSlashes(path.relative(base, filePath)); 310 return this.normalizeSlashes(path.relative(base, filePath));
281 } 311 }
282 312
313 getDartFileName(filePath?: string): string {
314 if (filePath === undefined) filePath = path.resolve(this.currentFile.fileNam e);
315 filePath = this.normalizeSlashes(filePath);
316 filePath = filePath.replace(/\.(js|es6|d\.ts|ts)$/, '.dart');
317 // Normalize from node module file path pattern to
318 filePath = filePath.replace(/([^/]+)\/index.dart$/, '$1.dart');
319 return this.getRelativeFileName(filePath);
320 }
321
322 isJsModuleFile(): boolean {
323 // Treat files as being part of js modules if they match the node module fil e location
324 // convention of module_name/index.js.
325 return !('/' + this.currentFile.fileName).match(/\/index\.(js|es6|d\.ts|ts)$ /);
326 }
327
283 private get currentOutput(): Output { return this.outputStack[this.outputStack .length - 1]; } 328 private get currentOutput(): Output { return this.outputStack[this.outputStack .length - 1]; }
284 329
285 emit(s: string) { this.currentOutput.emit(s); } 330 emit(s: string) { this.currentOutput.emit(s); }
286 emitNoSpace(s: string) { this.currentOutput.emitNoSpace(s); } 331 emitNoSpace(s: string) { this.currentOutput.emitNoSpace(s); }
287 maybeLineBreak() { return this.currentOutput.maybeLineBreak(); } 332 maybeLineBreak() { return this.currentOutput.maybeLineBreak(); }
288 enterCodeComment() { return this.currentOutput.enterCodeComment(); } 333 enterCodeComment() { return this.currentOutput.enterCodeComment(); }
289 exitCodeComment() { return this.currentOutput.exitCodeComment(); } 334 exitCodeComment() { return this.currentOutput.exitCodeComment(); }
335
336 enterTypeArgument() { this.typeArgumentDepth++; }
337 exitTypeArgument() { this.typeArgumentDepth--; }
338 get insideTypeArgument(): boolean { return this.typeArgumentDepth > 0; }
339
290 emitType(s: string, comment: string) { return this.currentOutput.emitType(s, c omment); } 340 emitType(s: string, comment: string) { return this.currentOutput.emitType(s, c omment); }
291 get insideCodeComment() { return this.currentOutput.insideCodeComment; } 341 get insideCodeComment() { return this.currentOutput.insideCodeComment; }
292 342
293 reportError(n: ts.Node, message: string) { 343 reportError(n: ts.Node, message: string) {
294 let file = n.getSourceFile() || this.currentFile; 344 let file = n.getSourceFile() || this.currentFile;
295 let fileName = this.getRelativeFileName(file.fileName); 345 let fileName = this.getRelativeFileName(file.fileName);
296 let start = n.getStart(file); 346 let start = n.getStart(file);
297 let pos = file.getLineAndCharacterOfPosition(start); 347 let pos = file.getLineAndCharacterOfPosition(start);
298 // Line and character are 0-based. 348 // Line and character are 0-based.
299 let fullMessage = `${fileName}:${pos.line + 1}:${pos.character + 1}: ${messa ge}`; 349 let fullMessage = `${fileName}:${pos.line + 1}:${pos.character + 1}: ${messa ge}`;
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
404 // Avoid line breaks inside code comments. 454 // Avoid line breaks inside code comments.
405 return; 455 return;
406 } 456 }
407 457
408 if (!this.firstColumn) { 458 if (!this.firstColumn) {
409 this.emitNoSpace('\n'); 459 this.emitNoSpace('\n');
410 } 460 }
411 } 461 }
412 462
413 emit(str: string) { 463 emit(str: string) {
414 if (this.result.length > 0) { 464 let buffer = this.insideCodeComment ? this.codeCommentResult : this.result;
415 let buffer = this.insideCodeComment ? this.codeCommentResult : this.result ; 465 if (buffer.length > 0) {
416 let lastChar = buffer[buffer.length - 1]; 466 let lastChar = buffer.slice(-1);
417 if (lastChar !== ' ' && lastChar !== '(' && lastChar !== '<' && lastChar ! == '[') { 467 if (lastChar !== ' ' && lastChar !== '(' && lastChar !== '<' && lastChar ! == '[') {
418 // Avoid emitting a space in obvious cases where a space is not required 468 // 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 469 // 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 470 // cannot run such as within a comment where code we emit is not quite
421 // valid Dart code. 471 // valid Dart code.
422 this.emitNoSpace(' '); 472 this.emitNoSpace(' ');
423 } 473 }
424 } 474 }
425 this.emitNoSpace(str); 475 this.emitNoSpace(str);
426 } 476 }
427 477
428 emitNoSpace(str: string) { 478 emitNoSpace(str: string) {
429 if (str.length === 0) return; 479 if (str.length === 0) return;
430 if (this.insideCodeComment) { 480 if (this.insideCodeComment) {
431 this.codeCommentResult += str; 481 this.codeCommentResult += str;
432 return; 482 return;
433 } 483 }
434 this.result += str; 484 this.result += str;
435 this.firstColumn = str[str.length - 1] === '\n'; 485 this.firstColumn = str.slice(-1) === '\n';
436 } 486 }
437 487
438 enterCodeComment() { 488 enterCodeComment() {
439 if (this.insideCodeComment) { 489 if (this.insideCodeComment) {
440 throw 'Cannot nest code comments' + this.codeCommentResult; 490 throw 'Cannot nest code comments' + this.codeCommentResult;
441 } 491 }
442 this.insideCodeComment = true; 492 this.insideCodeComment = true;
443 this.codeCommentResult = ''; 493 this.codeCommentResult = '';
444 } 494 }
445 495
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
494 try { 544 try {
495 let transpiler = new Transpiler(args); 545 let transpiler = new Transpiler(args);
496 console.error('Transpiling', args._, 'to', args.destination); 546 console.error('Transpiling', args._, 'to', args.destination);
497 transpiler.transpile(args._, args.destination); 547 transpiler.transpile(args._, args.destination);
498 } catch (e) { 548 } catch (e) {
499 if (e.name !== 'DartFacadeError') throw e; 549 if (e.name !== 'DartFacadeError') throw e;
500 console.error(e.message); 550 console.error(e.message);
501 process.exit(1); 551 process.exit(1);
502 } 552 }
503 } 553 }
OLDNEW
« no previous file with comments | « lib/facade_converter.ts ('k') | lib/merge.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698