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

Unified Diff: lib/main.ts

Issue 2225953002: Strip more unused features. (Closed) Base URL: git@github.com:dart-lang/js_facade_gen.git@master
Patch Set: Fix types Created 4 years, 4 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/literal.ts ('k') | lib/merge.ts » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/main.ts
diff --git a/lib/main.ts b/lib/main.ts
index 639e9ce343d1f6b4711170a4ec11efcb4ec9de0d..7c3c0d6d4579597d8f5d48e93a3c074be3dbdb45 100644
--- a/lib/main.ts
+++ b/lib/main.ts
@@ -1,19 +1,16 @@
-require('source-map-support').install();
-import {SourceMapGenerator} from 'source-map';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
-import {TranspilerBase} from './base';
+import * as base from './base';
+import {Set, TranspilerBase} from './base';
+
import mkdirP from './mkdirp';
-import CallTranspiler from './call';
import DeclarationTranspiler from './declaration';
-import ExpressionTranspiler from './expression';
+import * as merge from './merge';
import ModuleTranspiler from './module';
-import StatementTranspiler from './statement';
import TypeTranspiler from './type';
-import LiteralTranspiler from './literal';
-import {FacadeConverter} from './facade_converter';
+import {FacadeConverter, NameRewriter} from './facade_converter';
import * as dartStyle from 'dart-style';
export interface TranspilerOptions {
@@ -24,18 +21,15 @@ export interface TranspilerOptions {
failFast?: boolean;
/** Whether to generate 'library a.b.c;' names from relative file paths. */
generateLibraryName?: boolean;
- /** Whether to generate source maps. */
- generateSourceMap?: boolean;
/**
* A base path to relativize absolute file paths against. This is useful for library name
* generation (see above) and nicer file names in error messages.
*/
basePath?: string;
/**
- * Translate calls to builtins, i.e. seemlessly convert from `Array` to `List`, and convert the
- * corresponding methods. Requires type checking.
+ * Use dart:html instead of the raw JavaScript DOM when generated Dart code.
*/
- translateBuiltins?: boolean;
+ useHtml?: boolean;
/**
* Enforce conventions of public/private keyword and underscore prefix
*/
@@ -44,6 +38,16 @@ export interface TranspilerOptions {
* Sets a root path to look for typings used by the facade converter.
*/
typingsRoot?: string;
+
+ /**
+ * Experimental JS Interop specific option to promote properties with function
+ * types to methods instead of properties with a function type. This the makes
+ * the Dart code more readable at the cost of disallowing setting the value of
+ * the property.
+ * Example JS library that benifits from this option:
+ * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/chartjs/chart.d.ts
+ */
+ promoteFunctionLikeMembers?: boolean;
}
export const COMPILER_OPTIONS: ts.CompilerOptions = {
@@ -53,28 +57,40 @@ export const COMPILER_OPTIONS: ts.CompilerOptions = {
target: ts.ScriptTarget.ES5,
};
+/**
+ * Context to ouput code into.
+ */
+export enum OutputContext {
+ Import = 0,
+ Header = 1,
+ Default = 2,
+}
+
+const NUM_OUTPUT_CONTEXTS = 3;
+
export class Transpiler {
- private output: Output;
+ private outputs: Output[];
+ private outputStack: Output[];
private currentFile: ts.SourceFile;
-
+ importsEmitted: Set;
// Comments attach to all following AST nodes before the next 'physical' token. Track the earliest
// offset to avoid printing comments multiple times.
private lastCommentIdx: number = -1;
private errors: string[] = [];
private transpilers: TranspilerBase[];
+ private declarationTranspiler: DeclarationTranspiler;
private fc: FacadeConverter;
+ private nameRewriter: NameRewriter;
constructor(private options: TranspilerOptions = {}) {
- // TODO: Remove the angular2 default when angular uses typingsRoot.
- this.fc = new FacadeConverter(this, options.typingsRoot || 'angular2/typings/');
+ this.nameRewriter = new NameRewriter();
+ this.fc = new FacadeConverter(this, options.typingsRoot, this.nameRewriter, options.useHtml);
+ this.declarationTranspiler = new DeclarationTranspiler(
+ this, this.fc, options.enforceUnderscoreConventions, options.promoteFunctionLikeMembers);
this.transpilers = [
- new CallTranspiler(this, this.fc), // Has to come before StatementTranspiler!
- new DeclarationTranspiler(this, this.fc, options.enforceUnderscoreConventions),
- new ExpressionTranspiler(this, this.fc),
- new LiteralTranspiler(this, this.fc),
+ this.declarationTranspiler,
new ModuleTranspiler(this, this.fc, options.generateLibraryName),
- new StatementTranspiler(this),
new TypeTranspiler(this, this.fc),
];
}
@@ -96,32 +112,29 @@ export class Transpiler {
}
let destinationRoot = destination || this.options.basePath || '';
let program = ts.createProgram(fileNames, this.getCompilerOptions(), host);
- if (this.options.translateBuiltins) {
- this.fc.setTypeChecker(program.getTypeChecker());
- }
+ this.fc.setTypeChecker(program.getTypeChecker());
+ this.declarationTranspiler.setTypeChecker(program.getTypeChecker());
// Only write files that were explicitly passed in.
let fileSet: {[s: string]: boolean} = {};
fileNames.forEach((f) => fileSet[f] = true);
+ let sourceFiles = program.getSourceFiles().filter((sourceFile) => fileSet[sourceFile.fileName]);
this.errors = [];
- program.getSourceFiles()
- .filter((sourceFile) => fileSet[sourceFile.fileName])
- // Do not generate output for .d.ts files.
- .filter((sourceFile: ts.SourceFile) => !sourceFile.fileName.match(/\.d\.ts$/))
- .forEach((f: ts.SourceFile) => {
- let dartCode = this.translate(f);
- let outputFile = this.getOutputPath(path.resolve(f.fileName), destinationRoot);
- mkdirP(path.dirname(outputFile));
- fs.writeFileSync(outputFile, dartCode);
- });
+
+ sourceFiles.forEach((f: ts.SourceFile) => {
+ let dartCode = this.translate(f);
+ let outputFile = this.getOutputPath(path.resolve(f.fileName), destinationRoot);
+ mkdirP(path.dirname(outputFile));
+ fs.writeFileSync(outputFile, dartCode);
+ });
this.checkForErrors(program);
}
translateProgram(program: ts.Program): {[path: string]: string} {
- if (this.options.translateBuiltins) {
- this.fc.setTypeChecker(program.getTypeChecker());
- }
+ this.fc.setTypeChecker(program.getTypeChecker());
+ this.declarationTranspiler.setTypeChecker(program.getTypeChecker());
+
let paths: {[path: string]: string} = {};
this.errors = [];
program.getSourceFiles()
@@ -169,17 +182,42 @@ export class Transpiler {
// Visible for testing.
getOutputPath(filePath: string, destinationRoot: string): string {
let relative = this.getRelativeFileName(filePath);
- let dartFile = relative.replace(/.(js|es6|ts)$/, '.dart');
+ let dartFile = relative.replace(/.(js|es6|d\.ts|ts)$/, '.dart');
return this.normalizeSlashes(path.join(destinationRoot, dartFile));
}
+ public pushContext(context: OutputContext) { this.outputStack.push(this.outputs[context]); }
+
+ public popContext() {
+ if (this.outputStack.length === 0) {
+ this.reportError(null, 'Attempting to pop output stack when already empty');
+ }
+ this.outputStack.pop();
+ }
+
private translate(sourceFile: ts.SourceFile): string {
this.currentFile = sourceFile;
- this.output =
- new Output(sourceFile, this.getRelativeFileName(), this.options.generateSourceMap);
+ this.outputs = [];
+ this.outputStack = [];
+ this.importsEmitted = {};
+ for (let i = 0; i < NUM_OUTPUT_CONTEXTS; ++i) {
+ this.outputs.push(new Output());
+ }
+
this.lastCommentIdx = -1;
+ merge.normalizeSourceFile(sourceFile);
+ this.pushContext(OutputContext.Default);
this.visit(sourceFile);
- let result = this.output.getResult();
+ this.popContext();
+ if (this.outputStack.length > 0) {
+ this.reportError(
+ sourceFile, 'Internal error managing output contexts. ' +
+ 'Inconsistent push and pop context calls.');
+ }
+ let result = '';
+ for (let output of this.outputs) {
+ result += output.getResult();
+ }
return this.formatCode(result, sourceFile);
}
@@ -187,6 +225,7 @@ export class Transpiler {
let result = dartStyle.formatCode(code);
if (result.error) {
this.reportError(context, result.error);
+ return code;
}
return result.code;
}
@@ -196,9 +235,10 @@ export class Transpiler {
let diagnostics = program.getGlobalDiagnostics().concat(program.getSyntacticDiagnostics());
- if ((errors.length || diagnostics.length) && this.options.translateBuiltins) {
- // Only report semantic diagnostics if ts2dart failed; this code is not a generic compiler, so
- // only yields TS errors if they could be the cause of ts2dart issues.
+ if ((errors.length || diagnostics.length)) {
+ // Only report semantic diagnostics if facade generation failed; this
+ // code is not a generic compiler, so only yields TS errors if they could
+ // be the cause of facade generation issues.
// This greatly speeds up tests and execution.
diagnostics = diagnostics.concat(program.getSemanticDiagnostics());
}
@@ -218,7 +258,7 @@ export class Transpiler {
if (errors.length) {
let e = new Error(errors.join('\n'));
- e.name = 'TS2DartError';
+ e.name = 'DartFacadeError';
throw e;
}
}
@@ -240,8 +280,15 @@ export class Transpiler {
return this.normalizeSlashes(path.relative(base, filePath));
}
- emit(s: string) { this.output.emit(s); }
- emitNoSpace(s: string) { this.output.emitNoSpace(s); }
+ private get currentOutput(): Output { return this.outputStack[this.outputStack.length - 1]; }
+
+ emit(s: string) { this.currentOutput.emit(s); }
+ emitNoSpace(s: string) { this.currentOutput.emitNoSpace(s); }
+ maybeLineBreak() { return this.currentOutput.maybeLineBreak(); }
+ enterCodeComment() { return this.currentOutput.enterCodeComment(); }
+ exitCodeComment() { return this.currentOutput.exitCodeComment(); }
+ emitType(s: string, comment: string) { return this.currentOutput.emitType(s, comment); }
+ get insideCodeComment() { return this.currentOutput.insideCodeComment; }
reportError(n: ts.Node, message: string) {
let file = n.getSourceFile() || this.currentFile;
@@ -255,16 +302,26 @@ export class Transpiler {
}
visit(node: ts.Node) {
- this.output.addSourceMapping(node);
+ if (!node) return;
let comments = ts.getLeadingCommentRanges(this.currentFile.text, node.getFullStart());
if (comments) {
comments.forEach((c) => {
+ // Warning: the following check means that comments will only be
+ // emitted correctly if Dart code is emitted in the same order it
+ // appeared in the JavaScript AST.
if (c.pos <= this.lastCommentIdx) return;
this.lastCommentIdx = c.pos;
let text = this.currentFile.text.substring(c.pos, c.end);
- this.emitNoSpace('\n');
- this.emit(this.translateComment(text));
- if (c.hasTrailingNewLine) this.emitNoSpace('\n');
+ if (c.pos > 1) {
+ let prev = this.currentFile.text.substring(c.pos - 2, c.pos);
+ if (prev === '\n\n') {
+ // If the two previous characters are both \n then add a \n
+ // so that we ensure the output has sufficient line breaks to
+ // separate comment blocks.
+ this.currentOutput.emit('\n');
+ }
+ }
+ this.currentOutput.emitComment(this.translateComment(text));
});
}
@@ -280,6 +337,7 @@ export class Transpiler {
private normalizeSlashes(path: string) { return path.replace(/\\/g, '/'); }
private translateComment(comment: string): string {
+ let rawComment = comment;
comment = comment.replace(/\{@link ([^\}]+)\}/g, '[$1]');
// Remove the following tags and following comments till end of line.
@@ -292,7 +350,21 @@ export class Transpiler {
comment = comment.replace(/@description/g, '');
comment = comment.replace(/@deprecated/g, '');
- return comment;
+ // Switch to /* */ comments and // comments to ///
+ let sb = '';
+ for (let line of comment.split('\n')) {
+ line = line.trim();
+ line = line.replace(/^[\/]\*\*?/g, '');
+ line = line.replace(/\*[\/]$/g, '');
+ line = line.replace(/^\*/g, '');
+ line = line.replace(/^\/\/\/?/g, '');
+ line = line.trim();
+ if (line.length > 0) {
+ sb += ' /// ' + line + '\n';
+ }
+ }
+ if (rawComment[0] === '\n') sb = '\n' + sb;
+ return sb;
}
}
@@ -319,60 +391,100 @@ export function getModuleResolver(compilerHost: ts.CompilerHost) {
class Output {
private result: string = '';
- private column: number = 1;
- private line: number = 1;
-
- // Position information.
- private generateSourceMap: boolean;
- private sourceMap: SourceMapGenerator;
-
- constructor(
- private currentFile: ts.SourceFile, private relativeFileName: string,
- generateSourceMap: boolean) {
- if (generateSourceMap) {
- this.sourceMap = new SourceMapGenerator({file: relativeFileName + '.dart'});
- this.sourceMap.setSourceContent(relativeFileName, this.currentFile.text);
+ private firstColumn: boolean = true;
+
+ insideCodeComment: boolean = false;
+ private codeCommentResult: string = '';
+
+ /**
+ * Line break if the current line is not empty.
+ */
+ maybeLineBreak() {
+ if (this.insideCodeComment) {
+ // Avoid line breaks inside code comments.
+ return;
+ }
+
+ if (!this.firstColumn) {
+ this.emitNoSpace('\n');
}
}
emit(str: string) {
- this.emitNoSpace(' ');
+ if (this.result.length > 0) {
+ let buffer = this.insideCodeComment ? this.codeCommentResult : this.result;
+ let lastChar = buffer[buffer.length - 1];
+ if (lastChar !== ' ' && lastChar !== '(' && lastChar !== '<' && lastChar !== '[') {
+ // Avoid emitting a space in obvious cases where a space is not required
+ // to make the output slightly prettier in cases where the DartFormatter
+ // cannot run such as within a comment where code we emit is not quite
+ // valid Dart code.
+ this.emitNoSpace(' ');
+ }
+ }
this.emitNoSpace(str);
}
emitNoSpace(str: string) {
- this.result += str;
- for (let i = 0; i < str.length; i++) {
- if (str[i] === '\n') {
- this.line++;
- this.column = 0;
- } else {
- this.column++;
- }
+ if (str.length === 0) return;
+ if (this.insideCodeComment) {
+ this.codeCommentResult += str;
+ return;
}
+ this.result += str;
+ this.firstColumn = str[str.length - 1] === '\n';
}
- getResult(): string { return this.result + this.generateSourceMapComment(); }
+ enterCodeComment() {
+ if (this.insideCodeComment) {
+ throw 'Cannot nest code comments' + this.codeCommentResult;
+ }
+ this.insideCodeComment = true;
+ this.codeCommentResult = '';
+ }
- addSourceMapping(n: ts.Node) {
- if (!this.generateSourceMap) return; // source maps disabled.
- let file = n.getSourceFile() || this.currentFile;
- let start = n.getStart(file);
- let pos = file.getLineAndCharacterOfPosition(start);
+ emitType(s: string, comment: string) {
+ this.emit(base.formatType(s, comment, this.insideCodeComment));
+ }
- let mapping: SourceMap.Mapping = {
- original: {line: pos.line + 1, column: pos.character},
- generated: {line: this.line, column: this.column},
- source: this.relativeFileName,
- };
+ /**
+ * Always emit comments in the main program body outside of the existing code
+ * comment block.
+ */
+ emitComment(s: string) {
+ if (!this.firstColumn) {
+ this.result += '\n';
+ }
+ this.result += s;
+ this.firstColumn = true;
+ }
- this.sourceMap.addMapping(mapping);
+ exitCodeComment() {
+ if (!this.insideCodeComment) {
+ throw 'Exit code comment called while not within a code comment.';
+ }
+ this.insideCodeComment = false;
+ this.emitNoSpace(' /*');
+ let result = dartStyle.formatCode(this.codeCommentResult);
+ let code = this.codeCommentResult;
+ if (!result.error) {
+ code = result.code;
+ }
+ code = code.trim();
+ this.emitNoSpace(code);
+ this.emitNoSpace('*/');
+
+ // Don't really need an exact column, just need to track
+ // that we aren't on the first column.
+ this.firstColumn = false;
+ this.codeCommentResult = '';
}
- private generateSourceMapComment() {
- if (!this.sourceMap) return '';
- let base64map = new Buffer(JSON.stringify(this.sourceMap)).toString('base64');
- return '\n\n//# sourceMappingURL=data:application/json;base64,' + base64map;
+ getResult(): string {
+ if (this.insideCodeComment) {
+ throw 'Code comment not property terminated.';
+ }
+ return this.result;
}
}
@@ -384,7 +496,7 @@ if (require.main === module) {
console.error('Transpiling', args._, 'to', args.destination);
transpiler.transpile(args._, args.destination);
} catch (e) {
- if (e.name !== 'TS2DartError') throw e;
+ if (e.name !== 'DartFacadeError') throw e;
console.error(e.message);
process.exit(1);
}
« no previous file with comments | « lib/literal.ts ('k') | lib/merge.ts » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698