From 7ac38aa357188efbce920d45f5d91b05056bfac3 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 27 Jan 2017 14:23:12 -0800 Subject: [PATCH] feat(compiler): add support for source map generation (#14258) fixes #14125 PR Close #14258 --- .../compiler/src/output/abstract_emitter.ts | 179 +++++---- .../src/output/abstract_js_emitter.ts | 57 +-- .../compiler/src/output/class_builder.ts | 9 +- .../compiler/src/output/js_emitter.ts | 20 +- .../compiler/src/output/output_ast.ts | 340 +++++++++++------- .../compiler/src/output/output_interpreter.ts | 2 +- .../compiler/src/output/output_jit.ts | 11 +- .../compiler/src/output/source_map.ts | 184 ++++++++++ .../compiler/src/output/ts_emitter.ts | 147 ++++---- .../output/abstract_emitter_node_only_spec.ts | 148 ++++++++ .../test/output/abstract_emitter_spec.ts | 8 +- .../test/output/js_emitter_node_only_spec.ts | 66 ++++ .../compiler/test/output/js_emitter_spec.ts | 8 +- .../compiler/test/output/source_map_spec.ts | 130 +++++++ .../test/output/ts_emitter_node_only_spec.ts | 114 +++--- .../compiler/test/output/ts_emitter_spec.ts | 8 +- .../e2e_test/sourcemap/sourcemap_spec.ts | 1 - npm-shrinkwrap.clean.json | 2 +- npm-shrinkwrap.json | 6 +- package.json | 2 +- 20 files changed, 1061 insertions(+), 381 deletions(-) create mode 100644 modules/@angular/compiler/src/output/source_map.ts create mode 100644 modules/@angular/compiler/test/output/abstract_emitter_node_only_spec.ts create mode 100644 modules/@angular/compiler/test/output/js_emitter_node_only_spec.ts create mode 100644 modules/@angular/compiler/test/output/source_map_spec.ts diff --git a/modules/@angular/compiler/src/output/abstract_emitter.ts b/modules/@angular/compiler/src/output/abstract_emitter.ts index 7ddd257bf7..e4000da68e 100644 --- a/modules/@angular/compiler/src/output/abstract_emitter.ts +++ b/modules/@angular/compiler/src/output/abstract_emitter.ts @@ -7,11 +7,14 @@ */ import {isBlank, isPresent} from '../facade/lang'; +import {ParseSourceSpan} from '../parse_util'; import * as o from './output_ast'; +import {SourceMapGenerator} from './source_map'; const _SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\r|\$/g; const _LEGAL_IDENTIFIER_RE = /^[$A-Z_][0-9A-Z_$]*$/i; +const _INDENT_WITH = ' '; export const CATCH_ERROR_VAR = o.variable('error'); export const CATCH_STACK_VAR = o.variable('stack'); @@ -21,6 +24,7 @@ export abstract class OutputEmitter { class _EmittedLine { parts: string[] = []; + srcSpans: ParseSourceSpan[] = []; constructor(public indent: number) {} } @@ -40,13 +44,16 @@ export class EmitterVisitorContext { isExportedVar(varName: string): boolean { return this._exportedVars.indexOf(varName) !== -1; } - println(lastPart: string = ''): void { this.print(lastPart, true); } + println(from?: {sourceSpan?: ParseSourceSpan}|null, lastPart: string = ''): void { + this.print(from, lastPart, true); + } lineIsEmpty(): boolean { return this._currentLine.parts.length === 0; } - print(part: string, newLine: boolean = false) { + print(from: {sourceSpan?: ParseSourceSpan}|null, part: string, newLine: boolean = false) { if (part.length > 0) { this._currentLine.parts.push(part); + this._currentLine.srcSpans.push(from && from.sourceSpan || null); } if (newLine) { this._lines.push(new _EmittedLine(this._indent)); @@ -77,21 +84,60 @@ export class EmitterVisitorContext { return this._classes.length > 0 ? this._classes[this._classes.length - 1] : null; } - toSource(): any { - let lines = this._lines; - if (lines[lines.length - 1].parts.length === 0) { - lines = lines.slice(0, lines.length - 1); - } - return lines - .map((line) => { - if (line.parts.length > 0) { - return _createIndent(line.indent) + line.parts.join(''); - } else { - return ''; - } - }) + toSource(): string { + return this.sourceLines + .map(l => l.parts.length > 0 ? _createIndent(l.indent) + l.parts.join('') : '') .join('\n'); } + + toSourceMapGenerator(file: string|null = null, startsAtLine: number = 0): SourceMapGenerator { + const map = new SourceMapGenerator(file); + for (let i = 0; i < startsAtLine; i++) { + map.addLine(); + } + + this.sourceLines.forEach(line => { + map.addLine(); + + const spans = line.srcSpans; + const parts = line.parts; + let col0 = line.indent * _INDENT_WITH.length; + let spanIdx = 0; + // skip leading parts without source spans + while (spanIdx < spans.length && !spans[spanIdx]) { + col0 += parts[spanIdx].length; + spanIdx++; + } + + while (spanIdx < spans.length) { + const span = spans[spanIdx]; + const source = span.start.file; + const sourceLine = span.start.line; + const sourceCol = span.start.col; + + map.addSource(source.url, source.content) + .addMapping(col0, source.url, sourceLine, sourceCol); + + col0 += parts[spanIdx].length; + spanIdx++; + + // assign parts without span or the same span to the previous segment + while (spanIdx < spans.length && (span === spans[spanIdx] || !spans[spanIdx])) { + col0 += parts[spanIdx].length; + spanIdx++; + } + } + }); + + return map; + } + + private get sourceLines(): _EmittedLine[] { + if (this._lines.length && this._lines[this._lines.length - 1].parts.length === 0) { + return this._lines.slice(0, -1); + } + return this._lines; + } } export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.ExpressionVisitor { @@ -99,14 +145,14 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex visitExpressionStmt(stmt: o.ExpressionStatement, ctx: EmitterVisitorContext): any { stmt.expr.visitExpression(this, ctx); - ctx.println(';'); + ctx.println(stmt, ';'); return null; } visitReturnStmt(stmt: o.ReturnStatement, ctx: EmitterVisitorContext): any { - ctx.print(`return `); + ctx.print(stmt, `return `); stmt.value.visitExpression(this, ctx); - ctx.println(';'); + ctx.println(stmt, ';'); return null; } @@ -115,82 +161,83 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex abstract visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any; visitIfStmt(stmt: o.IfStmt, ctx: EmitterVisitorContext): any { - ctx.print(`if (`); + ctx.print(stmt, `if (`); stmt.condition.visitExpression(this, ctx); - ctx.print(`) {`); + ctx.print(stmt, `) {`); const hasElseCase = isPresent(stmt.falseCase) && stmt.falseCase.length > 0; if (stmt.trueCase.length <= 1 && !hasElseCase) { - ctx.print(` `); + ctx.print(stmt, ` `); this.visitAllStatements(stmt.trueCase, ctx); ctx.removeEmptyLastLine(); - ctx.print(` `); + ctx.print(stmt, ` `); } else { ctx.println(); ctx.incIndent(); this.visitAllStatements(stmt.trueCase, ctx); ctx.decIndent(); if (hasElseCase) { - ctx.println(`} else {`); + ctx.println(stmt, `} else {`); ctx.incIndent(); this.visitAllStatements(stmt.falseCase, ctx); ctx.decIndent(); } } - ctx.println(`}`); + ctx.println(stmt, `}`); return null; } abstract visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any; visitThrowStmt(stmt: o.ThrowStmt, ctx: EmitterVisitorContext): any { - ctx.print(`throw `); + ctx.print(stmt, `throw `); stmt.error.visitExpression(this, ctx); - ctx.println(`;`); + ctx.println(stmt, `;`); return null; } visitCommentStmt(stmt: o.CommentStmt, ctx: EmitterVisitorContext): any { const lines = stmt.comment.split('\n'); - lines.forEach((line) => { ctx.println(`// ${line}`); }); + lines.forEach((line) => { ctx.println(stmt, `// ${line}`); }); return null; } abstract visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any; + visitWriteVarExpr(expr: o.WriteVarExpr, ctx: EmitterVisitorContext): any { const lineWasEmpty = ctx.lineIsEmpty(); if (!lineWasEmpty) { - ctx.print('('); + ctx.print(expr, '('); } - ctx.print(`${expr.name} = `); + ctx.print(expr, `${expr.name} = `); expr.value.visitExpression(this, ctx); if (!lineWasEmpty) { - ctx.print(')'); + ctx.print(expr, ')'); } return null; } visitWriteKeyExpr(expr: o.WriteKeyExpr, ctx: EmitterVisitorContext): any { const lineWasEmpty = ctx.lineIsEmpty(); if (!lineWasEmpty) { - ctx.print('('); + ctx.print(expr, '('); } expr.receiver.visitExpression(this, ctx); - ctx.print(`[`); + ctx.print(expr, `[`); expr.index.visitExpression(this, ctx); - ctx.print(`] = `); + ctx.print(expr, `] = `); expr.value.visitExpression(this, ctx); if (!lineWasEmpty) { - ctx.print(')'); + ctx.print(expr, ')'); } return null; } visitWritePropExpr(expr: o.WritePropExpr, ctx: EmitterVisitorContext): any { const lineWasEmpty = ctx.lineIsEmpty(); if (!lineWasEmpty) { - ctx.print('('); + ctx.print(expr, '('); } expr.receiver.visitExpression(this, ctx); - ctx.print(`.${expr.name} = `); + ctx.print(expr, `.${expr.name} = `); expr.value.visitExpression(this, ctx); if (!lineWasEmpty) { - ctx.print(')'); + ctx.print(expr, ')'); } return null; } @@ -204,9 +251,9 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex return null; } } - ctx.print(`.${name}(`); + ctx.print(expr, `.${name}(`); this.visitAllExpressions(expr.args, ctx, `,`); - ctx.print(`)`); + ctx.print(expr, `)`); return null; } @@ -214,9 +261,9 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex visitInvokeFunctionExpr(expr: o.InvokeFunctionExpr, ctx: EmitterVisitorContext): any { expr.fn.visitExpression(this, ctx); - ctx.print(`(`); + ctx.print(expr, `(`); this.visitAllExpressions(expr.args, ctx, ','); - ctx.print(`)`); + ctx.print(expr, `)`); return null; } visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any { @@ -239,24 +286,24 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex throw new Error(`Unknown builtin variable ${ast.builtin}`); } } - ctx.print(varName); + ctx.print(ast, varName); return null; } visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): any { - ctx.print(`new `); + ctx.print(ast, `new `); ast.classExpr.visitExpression(this, ctx); - ctx.print(`(`); + ctx.print(ast, `(`); this.visitAllExpressions(ast.args, ctx, ','); - ctx.print(`)`); + ctx.print(ast, `)`); return null; } visitLiteralExpr(ast: o.LiteralExpr, ctx: EmitterVisitorContext): any { const value = ast.value; if (typeof value === 'string') { - ctx.print(escapeIdentifier(value, this._escapeDollarInStrings)); + ctx.print(ast, escapeIdentifier(value, this._escapeDollarInStrings)); } else { - ctx.print(`${value}`); + ctx.print(ast, `${value}`); } return null; } @@ -264,17 +311,17 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex abstract visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any; visitConditionalExpr(ast: o.ConditionalExpr, ctx: EmitterVisitorContext): any { - ctx.print(`(`); + ctx.print(ast, `(`); ast.condition.visitExpression(this, ctx); - ctx.print('? '); + ctx.print(ast, '? '); ast.trueCase.visitExpression(this, ctx); - ctx.print(': '); + ctx.print(ast, ': '); ast.falseCase.visitExpression(this, ctx); - ctx.print(`)`); + ctx.print(ast, `)`); return null; } visitNotExpr(ast: o.NotExpr, ctx: EmitterVisitorContext): any { - ctx.print('!'); + ctx.print(ast, '!'); ast.condition.visitExpression(this, ctx); return null; } @@ -332,46 +379,46 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex default: throw new Error(`Unknown operator ${ast.operator}`); } - ctx.print(`(`); + ctx.print(ast, `(`); ast.lhs.visitExpression(this, ctx); - ctx.print(` ${opStr} `); + ctx.print(ast, ` ${opStr} `); ast.rhs.visitExpression(this, ctx); - ctx.print(`)`); + ctx.print(ast, `)`); return null; } visitReadPropExpr(ast: o.ReadPropExpr, ctx: EmitterVisitorContext): any { ast.receiver.visitExpression(this, ctx); - ctx.print(`.`); - ctx.print(ast.name); + ctx.print(ast, `.`); + ctx.print(ast, ast.name); return null; } visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: EmitterVisitorContext): any { ast.receiver.visitExpression(this, ctx); - ctx.print(`[`); + ctx.print(ast, `[`); ast.index.visitExpression(this, ctx); - ctx.print(`]`); + ctx.print(ast, `]`); return null; } visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any { const useNewLine = ast.entries.length > 1; - ctx.print(`[`, useNewLine); + ctx.print(ast, `[`, useNewLine); ctx.incIndent(); this.visitAllExpressions(ast.entries, ctx, ',', useNewLine); ctx.decIndent(); - ctx.print(`]`, useNewLine); + ctx.print(ast, `]`, useNewLine); return null; } visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: EmitterVisitorContext): any { const useNewLine = ast.entries.length > 1; - ctx.print(`{`, useNewLine); + ctx.print(ast, `{`, useNewLine); ctx.incIndent(); this.visitAllObjects(entry => { - ctx.print(`${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}: `); + ctx.print(ast, `${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}: `); entry.value.visitExpression(this, ctx); }, ast.entries, ctx, ',', useNewLine); ctx.decIndent(); - ctx.print(`}`, useNewLine); + ctx.print(ast, `}`, useNewLine); return null; } @@ -387,7 +434,7 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex newLine: boolean = false): void { for (let i = 0; i < expressions.length; i++) { if (i > 0) { - ctx.print(separator, newLine); + ctx.print(null, separator, newLine); } handler(expressions[i]); } @@ -424,7 +471,7 @@ export function escapeIdentifier( function _createIndent(count: number): string { let res = ''; for (let i = 0; i < count; i++) { - res += ' '; + res += _INDENT_WITH; } return res; } diff --git a/modules/@angular/compiler/src/output/abstract_js_emitter.ts b/modules/@angular/compiler/src/output/abstract_js_emitter.ts index fc506767e8..cf3febd296 100644 --- a/modules/@angular/compiler/src/output/abstract_js_emitter.ts +++ b/modules/@angular/compiler/src/output/abstract_js_emitter.ts @@ -18,9 +18,9 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { this._visitClassConstructor(stmt, ctx); if (isPresent(stmt.parent)) { - ctx.print(`${stmt.name}.prototype = Object.create(`); + ctx.print(stmt, `${stmt.name}.prototype = Object.create(`); stmt.parent.visitExpression(this, ctx); - ctx.println(`.prototype);`); + ctx.println(stmt, `.prototype);`); } stmt.getters.forEach((getter) => this._visitClassGetter(stmt, getter, ctx)); stmt.methods.forEach((method) => this._visitClassMethod(stmt, method, ctx)); @@ -29,50 +29,51 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { } private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) { - ctx.print(`function ${stmt.name}(`); + ctx.print(stmt, `function ${stmt.name}(`); if (isPresent(stmt.constructorMethod)) { this._visitParams(stmt.constructorMethod.params, ctx); } - ctx.println(`) {`); + ctx.println(stmt, `) {`); ctx.incIndent(); if (isPresent(stmt.constructorMethod)) { if (stmt.constructorMethod.body.length > 0) { - ctx.println(`var self = this;`); + ctx.println(stmt, `var self = this;`); this.visitAllStatements(stmt.constructorMethod.body, ctx); } } ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); } private _visitClassGetter(stmt: o.ClassStmt, getter: o.ClassGetter, ctx: EmitterVisitorContext) { ctx.println( + stmt, `Object.defineProperty(${stmt.name}.prototype, '${getter.name}', { get: function() {`); ctx.incIndent(); if (getter.body.length > 0) { - ctx.println(`var self = this;`); + ctx.println(stmt, `var self = this;`); this.visitAllStatements(getter.body, ctx); } ctx.decIndent(); - ctx.println(`}});`); + ctx.println(stmt, `}});`); } private _visitClassMethod(stmt: o.ClassStmt, method: o.ClassMethod, ctx: EmitterVisitorContext) { - ctx.print(`${stmt.name}.prototype.${method.name} = function(`); + ctx.print(stmt, `${stmt.name}.prototype.${method.name} = function(`); this._visitParams(method.params, ctx); - ctx.println(`) {`); + ctx.println(stmt, `) {`); ctx.incIndent(); if (method.body.length > 0) { - ctx.println(`var self = this;`); + ctx.println(stmt, `var self = this;`); this.visitAllStatements(method.body, ctx); } ctx.decIndent(); - ctx.println(`};`); + ctx.println(stmt, `};`); } visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): string { if (ast.builtin === o.BuiltinVar.This) { - ctx.print('self'); + ctx.print(ast, 'self'); } else if (ast.builtin === o.BuiltinVar.Super) { throw new Error( `'super' needs to be handled at a parent ast node, not at the variable level!`); @@ -82,9 +83,9 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { return null; } visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { - ctx.print(`var ${stmt.name} = `); + ctx.print(stmt, `var ${stmt.name} = `); stmt.value.visitExpression(this, ctx); - ctx.println(`;`); + ctx.println(stmt, `;`); return null; } visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any { @@ -95,43 +96,43 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { const fnExpr = expr.fn; if (fnExpr instanceof o.ReadVarExpr && fnExpr.builtin === o.BuiltinVar.Super) { ctx.currentClass.parent.visitExpression(this, ctx); - ctx.print(`.call(this`); + ctx.print(expr, `.call(this`); if (expr.args.length > 0) { - ctx.print(`, `); + ctx.print(expr, `, `); this.visitAllExpressions(expr.args, ctx, ','); } - ctx.print(`)`); + ctx.print(expr, `)`); } else { super.visitInvokeFunctionExpr(expr, ctx); } return null; } visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any { - ctx.print(`function(`); + ctx.print(ast, `function(`); this._visitParams(ast.params, ctx); - ctx.println(`) {`); + ctx.println(ast, `) {`); ctx.incIndent(); this.visitAllStatements(ast.statements, ctx); ctx.decIndent(); - ctx.print(`}`); + ctx.print(ast, `}`); return null; } visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { - ctx.print(`function ${stmt.name}(`); + ctx.print(stmt, `function ${stmt.name}(`); this._visitParams(stmt.params, ctx); - ctx.println(`) {`); + ctx.println(stmt, `) {`); ctx.incIndent(); this.visitAllStatements(stmt.statements, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); return null; } visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any { - ctx.println(`try {`); + ctx.println(stmt, `try {`); ctx.incIndent(); this.visitAllStatements(stmt.bodyStmts, ctx); ctx.decIndent(); - ctx.println(`} catch (${CATCH_ERROR_VAR.name}) {`); + ctx.println(stmt, `} catch (${CATCH_ERROR_VAR.name}) {`); ctx.incIndent(); const catchStmts = [CATCH_STACK_VAR.set(CATCH_ERROR_VAR.prop('stack')).toDeclStmt(null, [ @@ -139,12 +140,12 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { ])].concat(stmt.catchStmts); this.visitAllStatements(catchStmts, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); return null; } private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { - this.visitAllObjects(param => ctx.print(param.name), params, ctx, ','); + this.visitAllObjects(param => ctx.print(null, param.name), params, ctx, ','); } getBuiltinMethodName(method: o.BuiltinMethod): string { diff --git a/modules/@angular/compiler/src/output/class_builder.ts b/modules/@angular/compiler/src/output/class_builder.ts index 869cf89049..465cb2ece1 100644 --- a/modules/@angular/compiler/src/output/class_builder.ts +++ b/modules/@angular/compiler/src/output/class_builder.ts @@ -6,8 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +import {ParseSourceSpan} from '../parse_util'; + import * as o from './output_ast'; + /** * Create a new class stmts based on the given data. */ @@ -16,7 +19,9 @@ export function createClassStmt(config: { parent?: o.Expression, parentArgs?: o.Expression[], ctorParams?: o.FnParam[], - builders: ClassBuilderPart | ClassBuilderPart[], modifiers?: o.StmtModifier[] + builders: ClassBuilderPart | ClassBuilderPart[], + modifiers?: o.StmtModifier[], + sourceSpan?: ParseSourceSpan }): o.ClassStmt { const parentArgs = config.parentArgs || []; const superCtorStmts = config.parent ? [o.SUPER_EXPR.callFn(parentArgs).toStmt()] : []; @@ -27,7 +32,7 @@ export function createClassStmt(config: { return new o.ClassStmt( config.name, config.parent, builder.fields, builder.getters, ctor, builder.methods, - config.modifiers || []); + config.modifiers || [], config.sourceSpan); } function concatClassBuilderParts(builders: ClassBuilderPart[]) { diff --git a/modules/@angular/compiler/src/output/js_emitter.ts b/modules/@angular/compiler/src/output/js_emitter.ts index 4fdcaa9119..fc3b5d64d6 100644 --- a/modules/@angular/compiler/src/output/js_emitter.ts +++ b/modules/@angular/compiler/src/output/js_emitter.ts @@ -18,10 +18,12 @@ import {ImportResolver} from './path_util'; export class JavaScriptEmitter implements OutputEmitter { constructor(private _importResolver: ImportResolver) {} + emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string { const converter = new JsEmitterVisitor(genFilePath, this._importResolver); const ctx = EmitterVisitorContext.createRoot(exportedVars); converter.visitAllStatements(stmts, ctx); + const srcParts: string[] = []; converter.importsWithPrefixes.forEach((prefix, importedFilePath) => { // Note: can't write the real word for import as it screws up system.js auto detection... @@ -29,7 +31,15 @@ export class JavaScriptEmitter implements OutputEmitter { `var ${prefix} = req` + `uire('${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}');`); }); + srcParts.push(ctx.toSource()); + + const prefixLines = converter.importsWithPrefixes.size; + const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment(); + if (sm) { + srcParts.push(sm); + } + return srcParts.join('\n'); } } @@ -55,29 +65,29 @@ class JsEmitterVisitor extends AbstractJsEmitterVisitor { prefix = `import${this.importsWithPrefixes.size}`; this.importsWithPrefixes.set(filePath, prefix); } - ctx.print(`${prefix}.`); + ctx.print(ast, `${prefix}.`); } - ctx.print(name); + ctx.print(ast, name); return null; } visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { super.visitDeclareVarStmt(stmt, ctx); if (ctx.isExportedVar(stmt.name)) { - ctx.println(exportVar(stmt.name)); + ctx.println(stmt, exportVar(stmt.name)); } return null; } visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { super.visitDeclareFunctionStmt(stmt, ctx); if (ctx.isExportedVar(stmt.name)) { - ctx.println(exportVar(stmt.name)); + ctx.println(stmt, exportVar(stmt.name)); } return null; } visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any { super.visitDeclareClassStmt(stmt, ctx); if (ctx.isExportedVar(stmt.name)) { - ctx.println(exportVar(stmt.name)); + ctx.println(stmt, exportVar(stmt.name)); } return null; } diff --git a/modules/@angular/compiler/src/output/output_ast.ts b/modules/@angular/compiler/src/output/output_ast.ts index 31e5d556f2..964700adef 100644 --- a/modules/@angular/compiler/src/output/output_ast.ts +++ b/modules/@angular/compiler/src/output/output_ast.ts @@ -9,6 +9,7 @@ import {CompileIdentifierMetadata} from '../compile_metadata'; import {isPresent} from '../facade/lang'; +import {ParseSourceSpan} from '../parse_util'; //// Types export enum TypeModifier { @@ -101,81 +102,91 @@ export enum BinaryOperator { export abstract class Expression { - constructor(public type: Type) {} + constructor(public type: Type, public sourceSpan?: ParseSourceSpan) {} abstract visitExpression(visitor: ExpressionVisitor, context: any): any; - prop(name: string): ReadPropExpr { return new ReadPropExpr(this, name); } - - key(index: Expression, type: Type = null): ReadKeyExpr { - return new ReadKeyExpr(this, index, type); + prop(name: string, sourceSpan?: ParseSourceSpan): ReadPropExpr { + return new ReadPropExpr(this, name, null, sourceSpan); } - callMethod(name: string|BuiltinMethod, params: Expression[]): InvokeMethodExpr { - return new InvokeMethodExpr(this, name, params); + key(index: Expression, type: Type = null, sourceSpan?: ParseSourceSpan): ReadKeyExpr { + return new ReadKeyExpr(this, index, type, sourceSpan); } - callFn(params: Expression[]): InvokeFunctionExpr { return new InvokeFunctionExpr(this, params); } - - instantiate(params: Expression[], type: Type = null): InstantiateExpr { - return new InstantiateExpr(this, params, type); + callMethod(name: string|BuiltinMethod, params: Expression[], sourceSpan?: ParseSourceSpan): + InvokeMethodExpr { + return new InvokeMethodExpr(this, name, params, null, sourceSpan); } - conditional(trueCase: Expression, falseCase: Expression = null): ConditionalExpr { - return new ConditionalExpr(this, trueCase, falseCase); + callFn(params: Expression[], sourceSpan?: ParseSourceSpan): InvokeFunctionExpr { + return new InvokeFunctionExpr(this, params, null, sourceSpan); } - equals(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Equals, this, rhs); + instantiate(params: Expression[], type: Type = null, sourceSpan?: ParseSourceSpan): + InstantiateExpr { + return new InstantiateExpr(this, params, type, sourceSpan); } - notEquals(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.NotEquals, this, rhs); + + conditional(trueCase: Expression, falseCase: Expression = null, sourceSpan?: ParseSourceSpan): + ConditionalExpr { + return new ConditionalExpr(this, trueCase, falseCase, null, sourceSpan); } - identical(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Identical, this, rhs); + + equals(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Equals, this, rhs, null, sourceSpan); } - notIdentical(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.NotIdentical, this, rhs); + notEquals(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.NotEquals, this, rhs, null, sourceSpan); } - minus(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Minus, this, rhs); + identical(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Identical, this, rhs, null, sourceSpan); } - plus(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Plus, this, rhs); + notIdentical(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.NotIdentical, this, rhs, null, sourceSpan); } - divide(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Divide, this, rhs); + minus(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Minus, this, rhs, null, sourceSpan); } - multiply(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Multiply, this, rhs); + plus(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Plus, this, rhs, null, sourceSpan); } - modulo(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Modulo, this, rhs); + divide(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Divide, this, rhs, null, sourceSpan); } - and(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.And, this, rhs); + multiply(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Multiply, this, rhs, null, sourceSpan); } - or(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Or, this, rhs); + modulo(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Modulo, this, rhs, null, sourceSpan); } - lower(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Lower, this, rhs); + and(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.And, this, rhs, null, sourceSpan); } - lowerEquals(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.LowerEquals, this, rhs); + or(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Or, this, rhs, null, sourceSpan); } - bigger(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.Bigger, this, rhs); + lower(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Lower, this, rhs, null, sourceSpan); } - biggerEquals(rhs: Expression): BinaryOperatorExpr { - return new BinaryOperatorExpr(BinaryOperator.BiggerEquals, this, rhs); + lowerEquals(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.LowerEquals, this, rhs, null, sourceSpan); } - isBlank(): Expression { + bigger(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.Bigger, this, rhs, null, sourceSpan); + } + biggerEquals(rhs: Expression, sourceSpan?: ParseSourceSpan): BinaryOperatorExpr { + return new BinaryOperatorExpr(BinaryOperator.BiggerEquals, this, rhs, null, sourceSpan); + } + isBlank(sourceSpan?: ParseSourceSpan): Expression { // Note: We use equals by purpose here to compare to null and undefined in JS. // We use the typed null to allow strictNullChecks to narrow types. - return this.equals(TYPED_NULL_EXPR); + return this.equals(TYPED_NULL_EXPR, sourceSpan); } - cast(type: Type): Expression { return new CastExpr(this, type); } + cast(type: Type, sourceSpan?: ParseSourceSpan): Expression { + return new CastExpr(this, type, sourceSpan); + } + toStmt(): Statement { return new ExpressionStatement(this); } } @@ -190,8 +201,8 @@ export class ReadVarExpr extends Expression { public name: string; public builtin: BuiltinVar; - constructor(name: string|BuiltinVar, type: Type = null) { - super(type); + constructor(name: string|BuiltinVar, type: Type = null, sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); if (typeof name === 'string') { this.name = name; this.builtin = null; @@ -204,14 +215,17 @@ export class ReadVarExpr extends Expression { return visitor.visitReadVarExpr(this, context); } - set(value: Expression): WriteVarExpr { return new WriteVarExpr(this.name, value); } + set(value: Expression): WriteVarExpr { + return new WriteVarExpr(this.name, value, null, this.sourceSpan); + } } export class WriteVarExpr extends Expression { public value: Expression; - constructor(public name: string, value: Expression, type: Type = null) { - super(type || value.type); + constructor( + public name: string, value: Expression, type: Type = null, sourceSpan?: ParseSourceSpan) { + super(type || value.type, sourceSpan); this.value = value; } @@ -220,7 +234,7 @@ export class WriteVarExpr extends Expression { } toDeclStmt(type: Type = null, modifiers: StmtModifier[] = null): DeclareVarStmt { - return new DeclareVarStmt(this.name, this.value, type, modifiers); + return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan); } } @@ -228,8 +242,9 @@ export class WriteVarExpr extends Expression { export class WriteKeyExpr extends Expression { public value: Expression; constructor( - public receiver: Expression, public index: Expression, value: Expression, type: Type = null) { - super(type || value.type); + public receiver: Expression, public index: Expression, value: Expression, type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type || value.type, sourceSpan); this.value = value; } visitExpression(visitor: ExpressionVisitor, context: any): any { @@ -241,8 +256,9 @@ export class WriteKeyExpr extends Expression { export class WritePropExpr extends Expression { public value: Expression; constructor( - public receiver: Expression, public name: string, value: Expression, type: Type = null) { - super(type || value.type); + public receiver: Expression, public name: string, value: Expression, type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type || value.type, sourceSpan); this.value = value; } visitExpression(visitor: ExpressionVisitor, context: any): any { @@ -261,8 +277,8 @@ export class InvokeMethodExpr extends Expression { public builtin: BuiltinMethod; constructor( public receiver: Expression, method: string|BuiltinMethod, public args: Expression[], - type: Type = null) { - super(type); + type: Type = null, sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); if (typeof method === 'string') { this.name = method; this.builtin = null; @@ -278,7 +294,11 @@ export class InvokeMethodExpr extends Expression { export class InvokeFunctionExpr extends Expression { - constructor(public fn: Expression, public args: Expression[], type: Type = null) { super(type); } + constructor( + public fn: Expression, public args: Expression[], type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); + } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitInvokeFunctionExpr(this, context); } @@ -286,7 +306,11 @@ export class InvokeFunctionExpr extends Expression { export class InstantiateExpr extends Expression { - constructor(public classExpr: Expression, public args: Expression[], type?: Type) { super(type); } + constructor( + public classExpr: Expression, public args: Expression[], type?: Type, + sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); + } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitInstantiateExpr(this, context); } @@ -294,7 +318,9 @@ export class InstantiateExpr extends Expression { export class LiteralExpr extends Expression { - constructor(public value: any, type: Type = null) { super(type); } + constructor(public value: any, type: Type = null, sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); + } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitLiteralExpr(this, context); } @@ -303,9 +329,9 @@ export class LiteralExpr extends Expression { export class ExternalExpr extends Expression { constructor( - public value: CompileIdentifierMetadata, type: Type = null, - public typeParams: Type[] = null) { - super(type); + public value: CompileIdentifierMetadata, type: Type = null, public typeParams: Type[] = null, + sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitExternalExpr(this, context); @@ -317,8 +343,8 @@ export class ConditionalExpr extends Expression { public trueCase: Expression; constructor( public condition: Expression, trueCase: Expression, public falseCase: Expression = null, - type: Type = null) { - super(type || trueCase.type); + type: Type = null, sourceSpan?: ParseSourceSpan) { + super(type || trueCase.type, sourceSpan); this.trueCase = trueCase; } visitExpression(visitor: ExpressionVisitor, context: any): any { @@ -328,14 +354,18 @@ export class ConditionalExpr extends Expression { export class NotExpr extends Expression { - constructor(public condition: Expression) { super(BOOL_TYPE); } + constructor(public condition: Expression, sourceSpan?: ParseSourceSpan) { + super(BOOL_TYPE, sourceSpan); + } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitNotExpr(this, context); } } export class CastExpr extends Expression { - constructor(public value: Expression, type: Type) { super(type); } + constructor(public value: Expression, type: Type, sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); + } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitCastExpr(this, context); } @@ -348,15 +378,18 @@ export class FnParam { export class FunctionExpr extends Expression { - constructor(public params: FnParam[], public statements: Statement[], type: Type = null) { - super(type); + constructor( + public params: FnParam[], public statements: Statement[], type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitFunctionExpr(this, context); } toDeclStmt(name: string, modifiers: StmtModifier[] = null): DeclareFunctionStmt { - return new DeclareFunctionStmt(name, this.params, this.statements, this.type, modifiers); + return new DeclareFunctionStmt( + name, this.params, this.statements, this.type, modifiers, this.sourceSpan); } } @@ -364,8 +397,9 @@ export class FunctionExpr extends Expression { export class BinaryOperatorExpr extends Expression { public lhs: Expression; constructor( - public operator: BinaryOperator, lhs: Expression, public rhs: Expression, type: Type = null) { - super(type || lhs.type); + public operator: BinaryOperator, lhs: Expression, public rhs: Expression, type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type || lhs.type, sourceSpan); this.lhs = lhs; } visitExpression(visitor: ExpressionVisitor, context: any): any { @@ -375,33 +409,39 @@ export class BinaryOperatorExpr extends Expression { export class ReadPropExpr extends Expression { - constructor(public receiver: Expression, public name: string, type: Type = null) { super(type); } + constructor( + public receiver: Expression, public name: string, type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); + } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitReadPropExpr(this, context); } set(value: Expression): WritePropExpr { - return new WritePropExpr(this.receiver, this.name, value); + return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan); } } export class ReadKeyExpr extends Expression { - constructor(public receiver: Expression, public index: Expression, type: Type = null) { - super(type); + constructor( + public receiver: Expression, public index: Expression, type: Type = null, + sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); } visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitReadKeyExpr(this, context); } set(value: Expression): WriteKeyExpr { - return new WriteKeyExpr(this.receiver, this.index, value); + return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan); } } export class LiteralArrayExpr extends Expression { public entries: Expression[]; - constructor(entries: Expression[], type: Type = null) { - super(type); + constructor(entries: Expression[], type: Type = null, sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); this.entries = entries; } visitExpression(visitor: ExpressionVisitor, context: any): any { @@ -415,9 +455,10 @@ export class LiteralMapEntry { export class LiteralMapExpr extends Expression { public valueType: Type = null; - constructor(public entries: LiteralMapEntry[], type: MapType = null) { - super(type); - if (isPresent(type)) { + constructor( + public entries: LiteralMapEntry[], type: MapType = null, sourceSpan?: ParseSourceSpan) { + super(type, sourceSpan); + if (type) { this.valueType = type.valueType; } } @@ -461,7 +502,7 @@ export enum StmtModifier { } export abstract class Statement { - constructor(public modifiers: StmtModifier[] = null) { + constructor(public modifiers: StmtModifier[] = null, public sourceSpan?: ParseSourceSpan) { if (!modifiers) { this.modifiers = []; } @@ -477,8 +518,8 @@ export class DeclareVarStmt extends Statement { public type: Type; constructor( public name: string, public value: Expression, type: Type = null, - modifiers: StmtModifier[] = null) { - super(modifiers); + modifiers: StmtModifier[] = null, sourceSpan?: ParseSourceSpan) { + super(modifiers, sourceSpan); this.type = type || value.type; } @@ -490,8 +531,8 @@ export class DeclareVarStmt extends Statement { export class DeclareFunctionStmt extends Statement { constructor( public name: string, public params: FnParam[], public statements: Statement[], - public type: Type = null, modifiers: StmtModifier[] = null) { - super(modifiers); + public type: Type = null, modifiers: StmtModifier[] = null, sourceSpan?: ParseSourceSpan) { + super(modifiers, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { @@ -500,7 +541,7 @@ export class DeclareFunctionStmt extends Statement { } export class ExpressionStatement extends Statement { - constructor(public expr: Expression) { super(); } + constructor(public expr: Expression, sourceSpan?: ParseSourceSpan) { super(null, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitExpressionStmt(this, context); @@ -509,7 +550,7 @@ export class ExpressionStatement extends Statement { export class ReturnStatement extends Statement { - constructor(public value: Expression) { super(); } + constructor(public value: Expression, sourceSpan?: ParseSourceSpan) { super(null, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitReturnStmt(this, context); } @@ -553,8 +594,9 @@ export class ClassStmt extends Statement { constructor( public name: string, public parent: Expression, public fields: ClassField[], public getters: ClassGetter[], public constructorMethod: ClassMethod, - public methods: ClassMethod[], modifiers: StmtModifier[] = null) { - super(modifiers); + public methods: ClassMethod[], modifiers: StmtModifier[] = null, + sourceSpan?: ParseSourceSpan) { + super(modifiers, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitDeclareClassStmt(this, context); @@ -565,8 +607,8 @@ export class ClassStmt extends Statement { export class IfStmt extends Statement { constructor( public condition: Expression, public trueCase: Statement[], - public falseCase: Statement[] = []) { - super(); + public falseCase: Statement[] = [], sourceSpan?: ParseSourceSpan) { + super(null, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitIfStmt(this, context); @@ -575,7 +617,7 @@ export class IfStmt extends Statement { export class CommentStmt extends Statement { - constructor(public comment: string) { super(); } + constructor(public comment: string, sourceSpan?: ParseSourceSpan) { super(null, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitCommentStmt(this, context); } @@ -583,7 +625,10 @@ export class CommentStmt extends Statement { export class TryCatchStmt extends Statement { - constructor(public bodyStmts: Statement[], public catchStmts: Statement[]) { super(); } + constructor( + public bodyStmts: Statement[], public catchStmts: Statement[], sourceSpan?: ParseSourceSpan) { + super(null, sourceSpan); + } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitTryCatchStmt(this, context); } @@ -591,7 +636,7 @@ export class TryCatchStmt extends Statement { export class ThrowStmt extends Statement { - constructor(public error: Expression) { super(); } + constructor(public error: Expression, sourceSpan?: ParseSourceSpan) { super(null, sourceSpan); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitThrowStmt(this, context); } @@ -611,74 +656,94 @@ export interface StatementVisitor { export class ExpressionTransformer implements StatementVisitor, ExpressionVisitor { visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; } + visitWriteVarExpr(expr: WriteVarExpr, context: any): any { - return new WriteVarExpr(expr.name, expr.value.visitExpression(this, context)); + return new WriteVarExpr( + expr.name, expr.value.visitExpression(this, context), expr.type, expr.sourceSpan); } + visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any { return new WriteKeyExpr( expr.receiver.visitExpression(this, context), expr.index.visitExpression(this, context), - expr.value.visitExpression(this, context)); + expr.value.visitExpression(this, context), expr.type, expr.sourceSpan); } + visitWritePropExpr(expr: WritePropExpr, context: any): any { return new WritePropExpr( expr.receiver.visitExpression(this, context), expr.name, - expr.value.visitExpression(this, context)); + expr.value.visitExpression(this, context), expr.type, expr.sourceSpan); } + visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any { const method = ast.builtin || ast.name; return new InvokeMethodExpr( ast.receiver.visitExpression(this, context), method, - this.visitAllExpressions(ast.args, context), ast.type); + this.visitAllExpressions(ast.args, context), ast.type, ast.sourceSpan); } + visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any { return new InvokeFunctionExpr( ast.fn.visitExpression(this, context), this.visitAllExpressions(ast.args, context), - ast.type); + ast.type, ast.sourceSpan); } + visitInstantiateExpr(ast: InstantiateExpr, context: any): any { return new InstantiateExpr( ast.classExpr.visitExpression(this, context), this.visitAllExpressions(ast.args, context), - ast.type); + ast.type, ast.sourceSpan); } + visitLiteralExpr(ast: LiteralExpr, context: any): any { return ast; } + visitExternalExpr(ast: ExternalExpr, context: any): any { return ast; } + visitConditionalExpr(ast: ConditionalExpr, context: any): any { return new ConditionalExpr( ast.condition.visitExpression(this, context), ast.trueCase.visitExpression(this, context), - ast.falseCase.visitExpression(this, context)); + ast.falseCase.visitExpression(this, context), ast.type, ast.sourceSpan); } + visitNotExpr(ast: NotExpr, context: any): any { - return new NotExpr(ast.condition.visitExpression(this, context)); + return new NotExpr(ast.condition.visitExpression(this, context), ast.sourceSpan); } + visitCastExpr(ast: CastExpr, context: any): any { - return new CastExpr(ast.value.visitExpression(this, context), context); + return new CastExpr(ast.value.visitExpression(this, context), context, ast.sourceSpan); } + visitFunctionExpr(ast: FunctionExpr, context: any): any { // Don't descend into nested functions return ast; } + visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any { return new BinaryOperatorExpr( ast.operator, ast.lhs.visitExpression(this, context), - ast.rhs.visitExpression(this, context), ast.type); + ast.rhs.visitExpression(this, context), ast.type, ast.sourceSpan); } + visitReadPropExpr(ast: ReadPropExpr, context: any): any { - return new ReadPropExpr(ast.receiver.visitExpression(this, context), ast.name, ast.type); + return new ReadPropExpr( + ast.receiver.visitExpression(this, context), ast.name, ast.type, ast.sourceSpan); } + visitReadKeyExpr(ast: ReadKeyExpr, context: any): any { return new ReadKeyExpr( ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context), - ast.type); + ast.type, ast.sourceSpan); } + visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any { - return new LiteralArrayExpr(this.visitAllExpressions(ast.entries, context)); + return new LiteralArrayExpr( + this.visitAllExpressions(ast.entries, context), ast.type, ast.sourceSpan); } visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any { const entries = ast.entries.map( (entry): LiteralMapEntry => new LiteralMapEntry( - entry.key, entry.value.visitExpression(this, context), entry.quoted)); - return new LiteralMapExpr(entries); + entry.key, entry.value.visitExpression(this, context), entry.quoted, )); + const mapType = new MapType(ast.valueType); + return new LiteralMapExpr(entries, mapType, ast.sourceSpan); } visitAllExpressions(exprs: Expression[], context: any): Expression[] { return exprs.map(expr => expr.visitExpression(this, context)); @@ -686,37 +751,46 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any { return new DeclareVarStmt( - stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers); + stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers, + stmt.sourceSpan); } visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any { // Don't descend into nested functions return stmt; } + visitExpressionStmt(stmt: ExpressionStatement, context: any): any { - return new ExpressionStatement(stmt.expr.visitExpression(this, context)); + return new ExpressionStatement(stmt.expr.visitExpression(this, context), stmt.sourceSpan); } + visitReturnStmt(stmt: ReturnStatement, context: any): any { - return new ReturnStatement(stmt.value.visitExpression(this, context)); + return new ReturnStatement(stmt.value.visitExpression(this, context), stmt.sourceSpan); } + visitDeclareClassStmt(stmt: ClassStmt, context: any): any { // Don't descend into nested functions return stmt; } + visitIfStmt(stmt: IfStmt, context: any): any { return new IfStmt( stmt.condition.visitExpression(this, context), this.visitAllStatements(stmt.trueCase, context), - this.visitAllStatements(stmt.falseCase, context)); + this.visitAllStatements(stmt.falseCase, context), stmt.sourceSpan); } + visitTryCatchStmt(stmt: TryCatchStmt, context: any): any { return new TryCatchStmt( this.visitAllStatements(stmt.bodyStmts, context), - this.visitAllStatements(stmt.catchStmts, context)); + this.visitAllStatements(stmt.catchStmts, context), stmt.sourceSpan); } + visitThrowStmt(stmt: ThrowStmt, context: any): any { - return new ThrowStmt(stmt.error.visitExpression(this, context)); + return new ThrowStmt(stmt.error.visitExpression(this, context), stmt.sourceSpan); } + visitCommentStmt(stmt: CommentStmt, context: any): any { return stmt; } + visitAllStatements(stmts: Statement[], context: any): Statement[] { return stmts.map(stmt => stmt.visitStatement(this, context)); } @@ -866,12 +940,15 @@ class _VariableFinder extends RecursiveExpressionVisitor { } } -export function variable(name: string, type: Type = null): ReadVarExpr { - return new ReadVarExpr(name, type); +export function variable( + name: string, type: Type = null, sourceSpan?: ParseSourceSpan): ReadVarExpr { + return new ReadVarExpr(name, type, sourceSpan); } -export function importExpr(id: CompileIdentifierMetadata, typeParams: Type[] = null): ExternalExpr { - return new ExternalExpr(id, null, typeParams); +export function importExpr( + id: CompileIdentifierMetadata, typeParams: Type[] = null, + sourceSpan?: ParseSourceSpan): ExternalExpr { + return new ExternalExpr(id, null, typeParams, sourceSpan); } export function importType( @@ -885,8 +962,9 @@ export function expressionType( return isPresent(expr) ? new ExpressionType(expr, typeModifiers) : null; } -export function literalArr(values: Expression[], type: Type = null): LiteralArrayExpr { - return new LiteralArrayExpr(values, type); +export function literalArr( + values: Expression[], type: Type = null, sourceSpan?: ParseSourceSpan): LiteralArrayExpr { + return new LiteralArrayExpr(values, type, sourceSpan); } export function literalMap( @@ -895,14 +973,16 @@ export function literalMap( values.map(entry => new LiteralMapEntry(entry[0], entry[1], quoted)), type); } -export function not(expr: Expression): NotExpr { - return new NotExpr(expr); +export function not(expr: Expression, sourceSpan?: ParseSourceSpan): NotExpr { + return new NotExpr(expr, sourceSpan); } -export function fn(params: FnParam[], body: Statement[], type: Type = null): FunctionExpr { - return new FunctionExpr(params, body, type); +export function fn( + params: FnParam[], body: Statement[], type: Type = null, + sourceSpan?: ParseSourceSpan): FunctionExpr { + return new FunctionExpr(params, body, type, sourceSpan); } -export function literal(value: any, type: Type = null): LiteralExpr { - return new LiteralExpr(value, type); +export function literal(value: any, type: Type = null, sourceSpan?: ParseSourceSpan): LiteralExpr { + return new LiteralExpr(value, type, sourceSpan); } diff --git a/modules/@angular/compiler/src/output/output_interpreter.ts b/modules/@angular/compiler/src/output/output_interpreter.ts index 06356cbe4e..bfb544a8f4 100644 --- a/modules/@angular/compiler/src/output/output_interpreter.ts +++ b/modules/@angular/compiler/src/output/output_interpreter.ts @@ -28,7 +28,7 @@ function _executeFunctionStatements( childCtx.vars.set(varNames[i], varValues[i]); } const result = visitor.visitAllStatements(statements, childCtx); - return isPresent(result) ? result.value : null; + return result ? result.value : null; } class _ExecutionContext { diff --git a/modules/@angular/compiler/src/output/output_jit.ts b/modules/@angular/compiler/src/output/output_jit.ts index 5cc9ec39c2..0b529618db 100644 --- a/modules/@angular/compiler/src/output/output_jit.ts +++ b/modules/@angular/compiler/src/output/output_jit.ts @@ -7,15 +7,15 @@ */ import {identifierName} from '../compile_metadata'; -import {isPresent} from '../facade/lang'; import {EmitterVisitorContext} from './abstract_emitter'; import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import * as o from './output_ast'; function evalExpression( - sourceUrl: string, expr: string, declarations: string, vars: {[key: string]: any}): any { - const fnBody = `${declarations}\nreturn ${expr}\n//# sourceURL=${sourceUrl}`; + sourceUrl: string, expr: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any { + const fnBody = + `${ctx.toSource()}\nreturn ${expr}\n//# sourceURL=${sourceUrl}\n${ctx.toSourceMapGenerator().toJsComment()}`; const fnArgNames: string[] = []; const fnArgValues: any[] = []; for (const argName in vars) { @@ -25,13 +25,12 @@ function evalExpression( return new Function(...fnArgNames.concat(fnBody))(...fnArgValues); } - export function jitStatements( sourceUrl: string, statements: o.Statement[], resultVar: string): any { const converter = new JitEmitterVisitor(); const ctx = EmitterVisitorContext.createRoot([resultVar]); converter.visitAllStatements(statements, ctx); - return evalExpression(sourceUrl, resultVar, ctx.toSource(), converter.getArgs()); + return evalExpression(sourceUrl, resultVar, ctx, converter.getArgs()); } class JitEmitterVisitor extends AbstractJsEmitterVisitor { @@ -55,7 +54,7 @@ class JitEmitterVisitor extends AbstractJsEmitterVisitor { const name = identifierName(ast.value) || 'val'; this._evalArgNames.push(`jit_${name}${id}`); } - ctx.print(this._evalArgNames[id]); + ctx.print(ast, this._evalArgNames[id]); return null; } } diff --git a/modules/@angular/compiler/src/output/source_map.ts b/modules/@angular/compiler/src/output/source_map.ts new file mode 100644 index 0000000000..5a9139bdd8 --- /dev/null +++ b/modules/@angular/compiler/src/output/source_map.ts @@ -0,0 +1,184 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit +const VERSION = 3; + +const JS_B64_PREFIX = '# sourceMappingURL=data:application/json;base64,'; + +type Segment = { + col0: number, + sourceUrl?: string, + sourceLine0?: number, + sourceCol0?: number, +}; + +export type SourceMap = { + version: number, + file?: string, + sourceRoot: string, + sources: string[], + sourcesContent: string[], + mappings: string, +}; + +export class SourceMapGenerator { + private sourcesContent: Map = new Map(); + private lines: Segment[][] = []; + private lastCol0: number = 0; + private hasMappings = false; + + constructor(private file: string|null = null) {} + + // The content is `null` when the content is expected to be loaded using the URL + addSource(url: string, content: string|null = null): this { + if (!this.sourcesContent.has(url)) { + this.sourcesContent.set(url, content); + } + return this; + } + + addLine(): this { + this.lines.push([]); + this.lastCol0 = 0; + return this; + } + + addMapping(col0: number, sourceUrl?: string, sourceLine0?: number, sourceCol0?: number): this { + if (!this.currentLine) { + throw new Error(`A line must be added before mappings can be added`); + } + if (sourceUrl != null && !this.sourcesContent.has(sourceUrl)) { + throw new Error(`Unknown source file "${sourceUrl}"`); + } + if (col0 == null) { + throw new Error(`The column in the generated code must be provided`); + } + if (col0 < this.lastCol0) { + throw new Error(`Mapping should be added in output order`); + } + if (sourceUrl && (sourceLine0 == null || sourceCol0 == null)) { + throw new Error(`The source location must be provided when a source url is provided`); + } + + this.hasMappings = true; + this.lastCol0 = col0; + this.currentLine.push({col0, sourceUrl, sourceLine0, sourceCol0}); + return this; + } + + private get currentLine(): Segment[]|null { return this.lines.slice(-1)[0]; } + + toJSON(): SourceMap|null { + if (!this.hasMappings) { + return null; + } + + const sourcesIndex = new Map(); + const sources: string[] = []; + const sourcesContent: string[] = []; + + Array.from(this.sourcesContent.keys()).forEach((url: string, i: number) => { + sourcesIndex.set(url, i); + sources.push(url); + sourcesContent.push(this.sourcesContent.get(url) || null); + }); + + let mappings: string = ''; + let lastCol0: number = 0; + let lastSourceIndex: number = 0; + let lastSourceLine0: number = 0; + let lastSourceCol0: number = 0; + + this.lines.forEach(segments => { + lastCol0 = 0; + + mappings += segments + .map(segment => { + // zero-based starting column of the line in the generated code + let segAsStr = toBase64VLQ(segment.col0 - lastCol0); + lastCol0 = segment.col0; + + if (segment.sourceUrl != null) { + // zero-based index into the “sources” list + segAsStr += + toBase64VLQ(sourcesIndex.get(segment.sourceUrl) - lastSourceIndex); + lastSourceIndex = sourcesIndex.get(segment.sourceUrl); + // the zero-based starting line in the original source + segAsStr += toBase64VLQ(segment.sourceLine0 - lastSourceLine0); + lastSourceLine0 = segment.sourceLine0; + // the zero-based starting column in the original source + segAsStr += toBase64VLQ(segment.sourceCol0 - lastSourceCol0); + lastSourceCol0 = segment.sourceCol0; + } + + return segAsStr; + }) + .join(','); + mappings += ';'; + }); + + mappings = mappings.slice(0, -1); + + return { + 'file': this.file || '', + 'version': VERSION, + 'sourceRoot': '', + 'sources': sources, + 'sourcesContent': sourcesContent, + 'mappings': mappings, + }; + } + + toJsComment(): string { + return this.hasMappings ? '//' + JS_B64_PREFIX + toBase64String(JSON.stringify(this, null, 0)) : + ''; + } +} + +export function toBase64String(value: string): string { + let b64 = ''; + + for (let i = 0; i < value.length;) { + const i1 = value.charCodeAt(i++); + const i2 = value.charCodeAt(i++); + const i3 = value.charCodeAt(i++); + b64 += toBase64Digit(i1 >> 2); + b64 += toBase64Digit(((i1 & 3) << 4) | (isNaN(i2) ? 0 : i2 >> 4)); + b64 += isNaN(i2) ? '=' : toBase64Digit(((i2 & 15) << 2) | (i3 >> 6)); + b64 += isNaN(i2) || isNaN(i3) ? '=' : toBase64Digit(i3 & 63); + } + + return b64; +} + +function toBase64VLQ(value: number): string { + value = value < 0 ? ((-value) << 1) + 1 : value << 1; + + let out = ''; + do { + let digit = value & 31; + value = value >> 5; + if (value > 0) { + digit = digit | 32; + } + out += toBase64Digit(digit); + } while (value > 0); + + return out; +} + +const B64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +function toBase64Digit(value: number): string { + if (value < 0 || value >= 64) { + throw new Error(`Can only encode value in the range [0, 63]`); + } + + return B64_DIGITS[value]; +} diff --git a/modules/@angular/compiler/src/output/ts_emitter.ts b/modules/@angular/compiler/src/output/ts_emitter.ts index cfc6817ac7..42d32fdc63 100644 --- a/modules/@angular/compiler/src/output/ts_emitter.ts +++ b/modules/@angular/compiler/src/output/ts_emitter.ts @@ -41,26 +41,41 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T return ctx.toSource(); } + export class TypeScriptEmitter implements OutputEmitter { constructor(private _importResolver: ImportResolver) {} + emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string { const converter = new _TsEmitterVisitor(genFilePath, this._importResolver); + const ctx = EmitterVisitorContext.createRoot(exportedVars); + converter.visitAllStatements(stmts, ctx); + const srcParts: string[] = []; + converter.reexports.forEach((reexports, exportedFilePath) => { const reexportsCode = reexports.map(reexport => `${reexport.name} as ${reexport.as}`).join(','); srcParts.push( `export {${reexportsCode}} from '${this._importResolver.fileNameToModuleName(exportedFilePath, genFilePath)}';`); }); + converter.importsWithPrefixes.forEach((prefix, importedFilePath) => { // Note: can't write the real word for import as it screws up system.js auto detection... srcParts.push( `imp` + `ort * as ${prefix} from '${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}';`); }); + srcParts.push(ctx.toSource()); + + const prefixLines = converter.reexports.size + converter.importsWithPrefixes.size; + const sm = ctx.toSourceMapGenerator(null, prefixLines).toJsComment(); + if (sm) { + srcParts.push(sm); + } + return srcParts.join('\n'); } } @@ -81,14 +96,14 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor t.visitType(this, ctx); this.typeExpression--; } else { - ctx.print(defaultType); + ctx.print(null, defaultType); } } visitLiteralExpr(ast: o.LiteralExpr, ctx: EmitterVisitorContext): any { const value = ast.value; if (isBlank(value) && ast.type != o.NULL_TYPE) { - ctx.print(`(${value} as any)`); + ctx.print(ast, `(${value} as any)`); return null; } return super.visitLiteralExpr(ast, ctx); @@ -101,11 +116,11 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor // start with [].concat. see https://github.com/angular/angular/pull/11846 visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any { if (ast.entries.length === 0) { - ctx.print('('); + ctx.print(ast, '('); } const result = super.visitLiteralArrayExpr(ast, ctx); if (ast.entries.length === 0) { - ctx.print(' as any[])'); + ctx.print(ast, ' as any[])'); } return result; } @@ -130,54 +145,54 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor } } if (ctx.isExportedVar(stmt.name)) { - ctx.print(`export `); + ctx.print(stmt, `export `); } if (stmt.hasModifier(o.StmtModifier.Final)) { - ctx.print(`const`); + ctx.print(stmt, `const`); } else { - ctx.print(`var`); + ctx.print(stmt, `var`); } - ctx.print(` ${stmt.name}:`); + ctx.print(stmt, ` ${stmt.name}:`); this.visitType(stmt.type, ctx); - ctx.print(` = `); + ctx.print(stmt, ` = `); stmt.value.visitExpression(this, ctx); - ctx.println(`;`); + ctx.println(stmt, `;`); return null; } visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any { - ctx.print(`(<`); + ctx.print(ast, `(<`); ast.type.visitType(this, ctx); - ctx.print(`>`); + ctx.print(ast, `>`); ast.value.visitExpression(this, ctx); - ctx.print(`)`); + ctx.print(ast, `)`); return null; } visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): any { - ctx.print(`new `); + ctx.print(ast, `new `); this.typeExpression++; ast.classExpr.visitExpression(this, ctx); this.typeExpression--; - ctx.print(`(`); + ctx.print(ast, `(`); this.visitAllExpressions(ast.args, ctx, ','); - ctx.print(`)`); + ctx.print(ast, `)`); return null; } visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any { ctx.pushClass(stmt); if (ctx.isExportedVar(stmt.name)) { - ctx.print(`export `); + ctx.print(stmt, `export `); } - ctx.print(`class ${stmt.name}`); + ctx.print(stmt, `class ${stmt.name}`); if (isPresent(stmt.parent)) { - ctx.print(` extends `); + ctx.print(stmt, ` extends `); this.typeExpression++; stmt.parent.visitExpression(this, ctx); this.typeExpression--; } - ctx.println(` {`); + ctx.println(stmt, ` {`); ctx.incIndent(); stmt.fields.forEach((field) => this._visitClassField(field, ctx)); if (isPresent(stmt.constructorMethod)) { @@ -186,7 +201,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor stmt.getters.forEach((getter) => this._visitClassGetter(getter, ctx)); stmt.methods.forEach((method) => this._visitClassMethod(method, ctx)); ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); ctx.popClass(); return null; } @@ -194,88 +209,88 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor private _visitClassField(field: o.ClassField, ctx: EmitterVisitorContext) { if (field.hasModifier(o.StmtModifier.Private)) { // comment out as a workaround for #10967 - ctx.print(`/*private*/ `); + ctx.print(null, `/*private*/ `); } - ctx.print(field.name); - ctx.print(':'); + ctx.print(null, field.name); + ctx.print(null, ':'); this.visitType(field.type, ctx); - ctx.println(`;`); + ctx.println(null, `;`); } private _visitClassGetter(getter: o.ClassGetter, ctx: EmitterVisitorContext) { if (getter.hasModifier(o.StmtModifier.Private)) { - ctx.print(`private `); + ctx.print(null, `private `); } - ctx.print(`get ${getter.name}()`); - ctx.print(':'); + ctx.print(null, `get ${getter.name}()`); + ctx.print(null, ':'); this.visitType(getter.type, ctx); - ctx.println(` {`); + ctx.println(null, ` {`); ctx.incIndent(); this.visitAllStatements(getter.body, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(null, `}`); } private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) { - ctx.print(`constructor(`); + ctx.print(stmt, `constructor(`); this._visitParams(stmt.constructorMethod.params, ctx); - ctx.println(`) {`); + ctx.println(stmt, `) {`); ctx.incIndent(); this.visitAllStatements(stmt.constructorMethod.body, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); } private _visitClassMethod(method: o.ClassMethod, ctx: EmitterVisitorContext) { if (method.hasModifier(o.StmtModifier.Private)) { - ctx.print(`private `); + ctx.print(null, `private `); } - ctx.print(`${method.name}(`); + ctx.print(null, `${method.name}(`); this._visitParams(method.params, ctx); - ctx.print(`):`); + ctx.print(null, `):`); this.visitType(method.type, ctx, 'void'); - ctx.println(` {`); + ctx.println(null, ` {`); ctx.incIndent(); this.visitAllStatements(method.body, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(null, `}`); } visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any { - ctx.print(`(`); + ctx.print(ast, `(`); this._visitParams(ast.params, ctx); - ctx.print(`):`); + ctx.print(ast, `):`); this.visitType(ast.type, ctx, 'void'); - ctx.println(` => {`); + ctx.println(ast, ` => {`); ctx.incIndent(); this.visitAllStatements(ast.statements, ctx); ctx.decIndent(); - ctx.print(`}`); + ctx.print(ast, `}`); return null; } visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { if (ctx.isExportedVar(stmt.name)) { - ctx.print(`export `); + ctx.print(stmt, `export `); } - ctx.print(`function ${stmt.name}(`); + ctx.print(stmt, `function ${stmt.name}(`); this._visitParams(stmt.params, ctx); - ctx.print(`):`); + ctx.print(stmt, `):`); this.visitType(stmt.type, ctx, 'void'); - ctx.println(` {`); + ctx.println(stmt, ` {`); ctx.incIndent(); this.visitAllStatements(stmt.statements, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); return null; } visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any { - ctx.println(`try {`); + ctx.println(stmt, `try {`); ctx.incIndent(); this.visitAllStatements(stmt.bodyStmts, ctx); ctx.decIndent(); - ctx.println(`} catch (${CATCH_ERROR_VAR.name}) {`); + ctx.println(stmt, `} catch (${CATCH_ERROR_VAR.name}) {`); ctx.incIndent(); const catchStmts = [CATCH_STACK_VAR.set(CATCH_ERROR_VAR.prop('stack')).toDeclStmt(null, [ @@ -283,7 +298,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor ])].concat(stmt.catchStmts); this.visitAllStatements(catchStmts, ctx); ctx.decIndent(); - ctx.println(`}`); + ctx.println(stmt, `}`); return null; } @@ -311,7 +326,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor default: throw new Error(`Unsupported builtin type ${type.name}`); } - ctx.print(typeStr); + ctx.print(null, typeStr); return null; } @@ -322,14 +337,14 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor visitArrayType(type: o.ArrayType, ctx: EmitterVisitorContext): any { this.visitType(type.of, ctx); - ctx.print(`[]`); + ctx.print(null, `[]`); return null; } visitMapType(type: o.MapType, ctx: EmitterVisitorContext): any { - ctx.print(`{[key: string]:`); + ctx.print(null, `{[key: string]:`); this.visitType(type.valueType, ctx); - ctx.print(`}`); + ctx.print(null, `}`); return null; } @@ -353,8 +368,8 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { this.visitAllObjects(param => { - ctx.print(param.name); - ctx.print(':'); + ctx.print(null, param.name); + ctx.print(null, ':'); this.visitType(param.type, ctx); }, params, ctx, ','); } @@ -383,18 +398,18 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor prefix = `import${this.importsWithPrefixes.size}`; this.importsWithPrefixes.set(filePath, prefix); } - ctx.print(`${prefix}.`); + ctx.print(null, `${prefix}.`); } if (members.length) { - ctx.print(name); - ctx.print('.'); - ctx.print(members.join('.')); + ctx.print(null, name); + ctx.print(null, '.'); + ctx.print(null, members.join('.')); } else { - ctx.print(name); + ctx.print(null, name); } if (this.typeExpression > 0) { - // If we are in a type expreession that refers to a generic type then supply + // If we are in a type expression that refers to a generic type then supply // the required type parameters. If there were not enough type parameters // supplied, supply any as the type. Outside a type expression the reference // should not supply type parameters and be treated as a simple value reference @@ -402,17 +417,17 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor const suppliedParameters = (typeParams && typeParams.length) || 0; const additionalParameters = (arity || 0) - suppliedParameters; if (suppliedParameters > 0 || additionalParameters > 0) { - ctx.print(`<`); + ctx.print(null, `<`); if (suppliedParameters > 0) { this.visitAllObjects(type => type.visitType(this, ctx), typeParams, ctx, ','); } if (additionalParameters > 0) { for (let i = 0; i < additionalParameters; i++) { - if (i > 0 || suppliedParameters > 0) ctx.print(','); - ctx.print('any'); + if (i > 0 || suppliedParameters > 0) ctx.print(null, ','); + ctx.print(null, 'any'); } } - ctx.print(`>`); + ctx.print(null, `>`); } } } diff --git a/modules/@angular/compiler/test/output/abstract_emitter_node_only_spec.ts b/modules/@angular/compiler/test/output/abstract_emitter_node_only_spec.ts new file mode 100644 index 0000000000..8120a75bdb --- /dev/null +++ b/modules/@angular/compiler/test/output/abstract_emitter_node_only_spec.ts @@ -0,0 +1,148 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler'; +import {EmitterVisitorContext} from '@angular/compiler/src/output/abstract_emitter'; +import {SourceMap} from '@angular/compiler/src/output/source_map'; + +const SourceMapConsumer = require('source-map').SourceMapConsumer; +const b64 = require('base64-js'); + + +export function main() { + describe('AbstractEmitter', () => { + describe('EmitterVisitorContext', () => { + const fileA = new ParseSourceFile('a0a1a2a3a4a5a6a7a8a9', 'a.js'); + const fileB = new ParseSourceFile('b0b1b2b3b4b5b6b7b8b9', 'b.js'); + let ctx: EmitterVisitorContext; + + beforeEach(() => { ctx = EmitterVisitorContext.createRoot([]); }); + + it('should add source files to the source map', () => { + ctx.print(createSourceSpan(fileA, 0), 'o0'); + ctx.print(createSourceSpan(fileA, 1), 'o1'); + ctx.print(createSourceSpan(fileB, 0), 'o2'); + ctx.print(createSourceSpan(fileB, 1), 'o3'); + const sm = ctx.toSourceMapGenerator('o.js').toJSON(); + expect(sm.sources).toEqual([fileA.url, fileB.url]); + expect(sm.sourcesContent).toEqual([fileA.content, fileB.content]); + }); + + it('should generate a valid mapping', () => { + ctx.print(createSourceSpan(fileA, 0), 'fileA-0'); + ctx.println(createSourceSpan(fileB, 1), 'fileB-1'); + ctx.print(createSourceSpan(fileA, 2), 'fileA-2'); + + expectMap(ctx, 0, 0, 'a.js', 0, 0); + expectMap(ctx, 0, 7, 'b.js', 0, 2); + expectMap(ctx, 1, 0, 'a.js', 0, 4); + }); + + it('should be able to shift the content', () => { + ctx.print(createSourceSpan(fileA, 0), 'fileA-0'); + + const sm = ctx.toSourceMapGenerator(null, 10).toJSON(); + const smc = new SourceMapConsumer(sm); + expect(smc.originalPositionFor({line: 11, column: 0})).toEqual({ + line: 1, + column: 0, + source: 'a.js', + name: null, + }); + }); + + it('should not map leading segment without span', () => { + ctx.print(null, '....'); + ctx.print(createSourceSpan(fileA, 0), 'fileA-0'); + + expectMap(ctx, 0, 0); + expectMap(ctx, 0, 4, 'a.js', 0, 0); + expect(nbSegmentsPerLine(ctx)).toEqual([1]); + }); + + it('should handle indent', () => { + ctx.incIndent(); + ctx.println(createSourceSpan(fileA, 0), 'fileA-0'); + ctx.incIndent(); + ctx.println(createSourceSpan(fileA, 1), 'fileA-1'); + ctx.decIndent(); + ctx.println(createSourceSpan(fileA, 2), 'fileA-2'); + + expectMap(ctx, 0, 0); + expectMap(ctx, 0, 2, 'a.js', 0, 0); + expectMap(ctx, 1, 0); + expectMap(ctx, 1, 2); + expectMap(ctx, 1, 4, 'a.js', 0, 2); + expectMap(ctx, 2, 0); + expectMap(ctx, 2, 2, 'a.js', 0, 4); + + expect(nbSegmentsPerLine(ctx)).toEqual([1, 1, 1]); + }); + + it('should coalesce identical span', () => { + const span = createSourceSpan(fileA, 0); + ctx.print(span, 'fileA-0'); + ctx.print(null, '...'); + ctx.print(span, 'fileA-0'); + ctx.print(createSourceSpan(fileB, 0), 'fileB-0'); + + expectMap(ctx, 0, 0, 'a.js', 0, 0); + expectMap(ctx, 0, 7, 'a.js', 0, 0); + expectMap(ctx, 0, 10, 'a.js', 0, 0); + expectMap(ctx, 0, 17, 'b.js', 0, 0); + + expect(nbSegmentsPerLine(ctx)).toEqual([2]); + }); + }); + }); +} + + +// All lines / columns indexes are 0-based +// Note: source-map line indexes are 1-based, column 0-based +function expectMap( + ctx: EmitterVisitorContext, genLine: number, genCol: number, source: string = null, + srcLine: number = null, srcCol: number = null) { + const sm = ctx.toSourceMapGenerator().toJSON(); + const smc = new SourceMapConsumer(sm); + const genPosition = {line: genLine + 1, column: genCol}; + const origPosition = smc.originalPositionFor(genPosition); + expect(origPosition.source).toEqual(source); + expect(origPosition.line).toEqual(srcLine === null ? null : srcLine + 1); + expect(origPosition.column).toEqual(srcCol); +} + +// returns the number of segments per line +function nbSegmentsPerLine(ctx: EmitterVisitorContext) { + const sm = ctx.toSourceMapGenerator().toJSON(); + const lines = sm.mappings.split(';'); + return lines.map(l => { + const m = l.match(/,/g); + return m === null ? 1 : m.length + 1; + }); +} + +function createSourceSpan(file: ParseSourceFile, idx: number) { + const col = 2 * idx; + const start = new ParseLocation(file, col, 0, col); + const end = new ParseLocation(file, col + 2, 0, col + 2); + const sourceSpan = new ParseSourceSpan(start, end); + return {sourceSpan}; +} + +export function extractSourceMap(source: string): SourceMap { + let idx = source.lastIndexOf('\n//#'); + if (idx == -1) return null; + const smComment = source.slice(idx).trim(); + const smB64 = smComment.split('sourceMappingURL=data:application/json;base64,')[1]; + return smB64 ? JSON.parse(decodeB64String(smB64)) : null; +} + +function decodeB64String(s: string): string { + return b64.toByteArray(s).reduce((s: string, c: number) => s + String.fromCharCode(c), ''); +} \ No newline at end of file diff --git a/modules/@angular/compiler/test/output/abstract_emitter_spec.ts b/modules/@angular/compiler/test/output/abstract_emitter_spec.ts index 008c26a5f0..ea9546e535 100644 --- a/modules/@angular/compiler/test/output/abstract_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/abstract_emitter_spec.ts @@ -7,7 +7,6 @@ */ import {escapeIdentifier} from '@angular/compiler/src/output/abstract_emitter'; -import {describe, expect, it} from '@angular/core/testing/testing_internal'; export function main() { describe('AbstractEmitter', () => { @@ -31,6 +30,11 @@ export function main() { it('does not escape class (but it probably should)', () => { expect(escapeIdentifier('class', false, false)).toEqual('class'); }); }); - }); } + +export function stripSourceMap(source: string): string { + const smi = source.lastIndexOf('\n//#'); + if (smi == -1) return source; + return source.slice(0, smi); +} \ No newline at end of file diff --git a/modules/@angular/compiler/test/output/js_emitter_node_only_spec.ts b/modules/@angular/compiler/test/output/js_emitter_node_only_spec.ts new file mode 100644 index 0000000000..43d7984010 --- /dev/null +++ b/modules/@angular/compiler/test/output/js_emitter_node_only_spec.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol'; +import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; +import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; +import * as o from '@angular/compiler/src/output/output_ast'; +import {ImportResolver} from '@angular/compiler/src/output/path_util'; +import {SourceMap} from '@angular/compiler/src/output/source_map'; +import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util'; + +import {extractSourceMap} from './abstract_emitter_node_only_spec'; + +const SourceMapConsumer = require('source-map').SourceMapConsumer; + +const someModuleUrl = 'somePackage/somePath'; + +class SimpleJsImportGenerator implements ImportResolver { + fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string { + return importedUrlStr; + } + getImportAs(symbol: StaticSymbol): StaticSymbol { return null; } + getTypeArity(symbol: StaticSymbol): number /*|null*/ { return null; } +} + +export function main() { + describe('JavaScriptEmitter', () => { + let importResolver: ImportResolver; + let emitter: JavaScriptEmitter; + let someVar: o.ReadVarExpr; + + beforeEach(() => { + importResolver = new SimpleJsImportGenerator(); + emitter = new JavaScriptEmitter(importResolver); + }); + + function emitSourceMap( + stmt: o.Statement | o.Statement[], exportedVars: string[] = null): SourceMap { + const stmts = Array.isArray(stmt) ? stmt : [stmt]; + const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []); + return extractSourceMap(source); + } + + describe('source maps', () => { + it('should emit an inline source map', () => { + const source = new ParseSourceFile(';;;var', 'in.js'); + const startLocation = new ParseLocation(source, 0, 0, 3); + const endLocation = new ParseLocation(source, 7, 0, 6); + const sourceSpan = new ParseSourceSpan(startLocation, endLocation); + const someVar = o.variable('someVar', null, sourceSpan); + const sm = emitSourceMap(someVar.toStmt()); + const smc = new SourceMapConsumer(sm); + + expect(sm.sources).toEqual(['in.js']); + expect(sm.sourcesContent).toEqual([';;;var']); + expect(smc.originalPositionFor({line: 1, column: 0})) + .toEqual({line: 1, column: 3, source: 'in.js', name: null}); + }); + }); + }); +} diff --git a/modules/@angular/compiler/test/output/js_emitter_spec.ts b/modules/@angular/compiler/test/output/js_emitter_spec.ts index 0afa6caaa5..fc85cec67d 100644 --- a/modules/@angular/compiler/test/output/js_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/js_emitter_spec.ts @@ -12,6 +12,8 @@ import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import * as o from '@angular/compiler/src/output/output_ast'; import {ImportResolver} from '@angular/compiler/src/output/path_util'; +import {stripSourceMap} from './abstract_emitter_spec'; + const someModuleUrl = 'somePackage/somePath'; const anotherModuleUrl = 'somePackage/someOtherPath'; @@ -47,10 +49,8 @@ export function main() { }); function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string { - if (!exportedVars) { - exportedVars = []; - } - return emitter.emitStatements(someModuleUrl, [stmt], exportedVars); + const source = emitter.emitStatements(someModuleUrl, [stmt], exportedVars || []); + return stripSourceMap(source); } it('should declare variables', () => { diff --git a/modules/@angular/compiler/test/output/source_map_spec.ts b/modules/@angular/compiler/test/output/source_map_spec.ts new file mode 100644 index 0000000000..f8fd03f16e --- /dev/null +++ b/modules/@angular/compiler/test/output/source_map_spec.ts @@ -0,0 +1,130 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {SourceMapGenerator, toBase64String} from '@angular/compiler/src/output/source_map'; + +export function main() { + describe('source map generation', () => { + describe('generation', () => { + it('should generate a valid source map', () => { + const map = new SourceMapGenerator('out.js') + .addSource('a.js', null) + .addLine() + .addMapping(0, 'a.js', 0, 0) + .addMapping(4, 'a.js', 0, 6) + .addMapping(5, 'a.js', 0, 7) + .addMapping(8, 'a.js', 0, 22) + .addMapping(9, 'a.js', 0, 23) + .addMapping(10, 'a.js', 0, 24) + .addLine() + .addMapping(0, 'a.js', 1, 0) + .addMapping(4, 'a.js', 1, 6) + .addMapping(5, 'a.js', 1, 7) + .addMapping(8, 'a.js', 1, 10) + .addMapping(9, 'a.js', 1, 11) + .addMapping(10, 'a.js', 1, 12) + .addLine() + .addMapping(0, 'a.js', 3, 0) + .addMapping(2, 'a.js', 3, 2) + .addMapping(3, 'a.js', 3, 3) + .addMapping(10, 'a.js', 3, 10) + .addMapping(11, 'a.js', 3, 11) + .addMapping(21, 'a.js', 3, 11) + .addMapping(22, 'a.js', 3, 12) + .addLine() + .addMapping(4, 'a.js', 4, 4) + .addMapping(11, 'a.js', 4, 11) + .addMapping(12, 'a.js', 4, 12) + .addMapping(15, 'a.js', 4, 15) + .addMapping(16, 'a.js', 4, 16) + .addMapping(21, 'a.js', 4, 21) + .addMapping(22, 'a.js', 4, 22) + .addMapping(23, 'a.js', 4, 23) + .addLine() + .addMapping(0, 'a.js', 5, 0) + .addMapping(1, 'a.js', 5, 1) + .addMapping(2, 'a.js', 5, 2) + .addMapping(3, 'a.js', 5, 2); + + // Generated with https://sokra.github.io/source-map-visualization using a TS source map + expect(map.toJSON().mappings) + .toEqual( + 'AAAA,IAAM,CAAC,GAAe,CAAC,CAAC;AACxB,IAAM,CAAC,GAAG,CAAC,CAAC;AAEZ,EAAE,CAAC,OAAO,CAAC,UAAA,CAAC;IACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC,CAAC,CAAA'); + }); + + it('should include the files and their contents', () => { + const map = new SourceMapGenerator('out.js') + .addSource('inline.ts', 'inline') + .addSource('inline.ts', 'inline') // make sur the sources are dedup + .addSource('url.ts', null) + .addLine() + .addMapping(0, 'inline.ts', 0, 0) + .toJSON(); + + expect(map.file).toEqual('out.js'); + expect(map.sources).toEqual(['inline.ts', 'url.ts']); + expect(map.sourcesContent).toEqual(['inline', null]); + }); + + it('should not generate source maps when there is no mapping', () => { + const smg = new SourceMapGenerator('out.js').addSource('inline.ts', 'inline').addLine(); + + expect(smg.toJSON()).toEqual(null); + expect(smg.toJsComment()).toEqual(''); + }); + }); + + describe('encodeB64String', () => { + it('should return the b64 encoded value', () => { + [['', ''], ['a', 'YQ=='], ['Foo', 'Rm9v'], ['Foo1', 'Rm9vMQ=='], ['Foo12', 'Rm9vMTI='], + ['Foo123', 'Rm9vMTIz'], + ].forEach(([src, b64]) => expect(toBase64String(src)).toEqual(b64)); + }); + }); + + describe('errors', () => { + it('should throw when mappings are added out of order', () => { + expect(() => { + new SourceMapGenerator('out.js') + .addSource('in.js') + .addLine() + .addMapping(10, 'in.js', 0, 0) + .addMapping(0, 'in.js', 0, 0); + }).toThrowError('Mapping should be added in output order'); + }); + + it('should throw when adding segments before any line is created', () => { + expect(() => { + new SourceMapGenerator('out.js').addSource('in.js').addMapping(0, 'in.js', 0, 0); + }).toThrowError('A line must be added before mappings can be added'); + }); + + it('should throw when adding segments referencing unknown sources', () => { + expect(() => { + new SourceMapGenerator('out.js').addSource('in.js').addLine().addMapping( + 0, 'in_.js', 0, 0); + }).toThrowError('Unknown source file "in_.js"'); + }); + + it('should throw when adding segments without column', () => { + expect(() => { + new SourceMapGenerator('out.js').addSource('in.js').addLine().addMapping(null); + }).toThrowError('The column in the generated code must be provided'); + }); + + it('should throw when adding segments with a source url but no position', () => { + expect(() => { + new SourceMapGenerator('out.js').addSource('in.js').addLine().addMapping(0, 'in.js'); + }).toThrowError('The source location must be provided when a source url is provided'); + expect(() => { + new SourceMapGenerator('out.js').addSource('in.js').addLine().addMapping(0, 'in.js', 0); + }).toThrowError('The source location must be provided when a source url is provided'); + }); + }); + }); +} diff --git a/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts b/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts index b130871ddb..391c948447 100644 --- a/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts +++ b/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts @@ -6,74 +6,66 @@ * found in the LICENSE file at https://angular.io/license */ -import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; +import {ParseLocation, ParseSourceFile} from '@angular/compiler'; +import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol'; import * as o from '@angular/compiler/src/output/output_ast'; import {ImportResolver} from '@angular/compiler/src/output/path_util'; +import {SourceMap} from '@angular/compiler/src/output/source_map'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; -import {convertValueToOutputAst} from '@angular/compiler/src/output/value_util'; -import {MetadataCollector, isClassMetadata, isMetadataSymbolicCallExpression} from '@angular/tsc-wrapped'; -import * as ts from 'typescript'; +import {ParseSourceSpan} from '@angular/compiler/src/parse_util'; -import {MockSummaryResolver} from '../aot/static_symbol_resolver_spec'; +import {extractSourceMap} from './abstract_emitter_node_only_spec'; -describe('TypeScriptEmitter (node only)', () => { - it('should quote identifiers quoted in the source', () => { - const sourceText = ` - import {Component} from '@angular/core'; +const SourceMapConsumer = require('source-map').SourceMapConsumer; - @Component({ - providers: [{ provide: 'SomeToken', useValue: {a: 1, 'b': 2, c: 3, 'd': 4}}] - }) - export class MyComponent {} - `; - const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest); - const collector = new MetadataCollector({quotedNames: true}); - const stubHost = new StubReflectorHost(); - const symbolCache = new StaticSymbolCache(); - const symbolResolver = - new StaticSymbolResolver(stubHost, symbolCache, new MockSummaryResolver()); - const reflector = new StaticReflector(symbolResolver); +const someModuleUrl = 'somePackage/somePath'; - // Get the metadata from the above source - const metadata = collector.getMetadata(source); - const componentMetadata = metadata.metadata['MyComponent']; - - // Get the first argument of the decorator call which is passed to @Component - expect(isClassMetadata(componentMetadata)).toBeTruthy(); - if (!isClassMetadata(componentMetadata)) return; - const decorators = componentMetadata.decorators; - const firstDecorator = decorators[0]; - expect(isMetadataSymbolicCallExpression(firstDecorator)).toBeTruthy(); - if (!isMetadataSymbolicCallExpression(firstDecorator)) return; - const firstArgument = firstDecorator.arguments[0]; - - // Simplify this value using the StaticReflector - const context = reflector.getStaticSymbol('none', 'none'); - const argumentValue = reflector.simplify(context, firstArgument); - - // Convert the value to an output AST - const outputAst = convertValueToOutputAst(argumentValue); - const statement = outputAst.toStmt(); - - // Convert the value to text using the typescript emitter - const emitter = new TypeScriptEmitter(new StubImportResolver()); - const text = emitter.emitStatements('module', [statement], []); - - // Expect the keys for 'b' and 'd' to be quoted but 'a' and 'c' not to be. - expect(text).toContain('\'b\': 2'); - expect(text).toContain('\'d\': 4'); - expect(text).not.toContain('\'a\''); - expect(text).not.toContain('\'c\''); - }); -}); - -class StubReflectorHost implements StaticSymbolResolverHost { - getMetadataFor(modulePath: string): {[key: string]: any}[] { return []; } - moduleNameToFileName(moduleName: string, containingFile: string): string { return 'somePath'; } -} - -class StubImportResolver extends ImportResolver { - fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { return ''; } +class SimpleJsImportGenerator implements ImportResolver { + fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string { + return importedUrlStr; + } getImportAs(symbol: StaticSymbol): StaticSymbol { return null; } getTypeArity(symbol: StaticSymbol): number /*|null*/ { return null; } } + +export function main() { + // Not supported features of our OutputAst in TS: + // - real `const` like in Dart + // - final fields + + describe('TypeScriptEmitter', () => { + let importResolver: ImportResolver; + let emitter: TypeScriptEmitter; + let someVar: o.ReadVarExpr; + + beforeEach(() => { + importResolver = new SimpleJsImportGenerator(); + emitter = new TypeScriptEmitter(importResolver); + someVar = o.variable('someVar'); + }); + + function emitSourceMap( + stmt: o.Statement | o.Statement[], exportedVars: string[] = null): SourceMap { + const stmts = Array.isArray(stmt) ? stmt : [stmt]; + const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []); + return extractSourceMap(source); + } + + describe('source maps', () => { + it('should emit an inline source map', () => { + const source = new ParseSourceFile(';;;var', 'in.js'); + const startLocation = new ParseLocation(source, 0, 0, 3); + const endLocation = new ParseLocation(source, 7, 0, 6); + const sourceSpan = new ParseSourceSpan(startLocation, endLocation); + const someVar = o.variable('someVar', null, sourceSpan); + const sm = emitSourceMap(someVar.toStmt()); + const smc = new SourceMapConsumer(sm); + + expect(sm.sources).toEqual(['in.js']); + expect(sm.sourcesContent).toEqual([';;;var']); + expect(smc.originalPositionFor({line: 1, column: 0})) + .toEqual({line: 1, column: 3, source: 'in.js', name: null}); + }); + }); + }); +} diff --git a/modules/@angular/compiler/test/output/ts_emitter_spec.ts b/modules/@angular/compiler/test/output/ts_emitter_spec.ts index 9cd0c5c322..fb3ee21bd3 100644 --- a/modules/@angular/compiler/test/output/ts_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/ts_emitter_spec.ts @@ -12,6 +12,8 @@ import * as o from '@angular/compiler/src/output/output_ast'; import {ImportResolver} from '@angular/compiler/src/output/path_util'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; +import {stripSourceMap} from './abstract_emitter_spec'; + const someModuleUrl = 'somePackage/somePath'; const anotherModuleUrl = 'somePackage/someOtherPath'; @@ -48,11 +50,9 @@ export function main() { }); function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string { - if (!exportedVars) { - exportedVars = []; - } const stmts = Array.isArray(stmt) ? stmt : [stmt]; - return emitter.emitStatements(someModuleUrl, stmts, exportedVars); + const source = emitter.emitStatements(someModuleUrl, stmts, exportedVars || []); + return stripSourceMap(source); } it('should declare variables', () => { diff --git a/modules/playground/e2e_test/sourcemap/sourcemap_spec.ts b/modules/playground/e2e_test/sourcemap/sourcemap_spec.ts index 893b439c69..9648355734 100644 --- a/modules/playground/e2e_test/sourcemap/sourcemap_spec.ts +++ b/modules/playground/e2e_test/sourcemap/sourcemap_spec.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import * as testUtil from 'e2e_util/e2e_util'; import {$, browser} from 'protractor'; import {logging} from 'selenium-webdriver'; diff --git a/npm-shrinkwrap.clean.json b/npm-shrinkwrap.clean.json index 34e053871a..8598807774 100644 --- a/npm-shrinkwrap.clean.json +++ b/npm-shrinkwrap.clean.json @@ -5847,7 +5847,7 @@ "dev": true }, "source-map": { - "version": "0.3.0", + "version": "0.5.6", "dev": true }, "source-map-support": { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 62a6e3b2b2..a1701cf13d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8549,9 +8549,9 @@ "dev": true }, "source-map": { - "version": "0.3.0", - "from": "source-map@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", + "version": "0.5.6", + "from": "source-map@latest", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "dev": true }, "source-map-support": { diff --git a/package.json b/package.json index 20ca1a1df1..e560c85457 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "rollup-plugin-commonjs": "^5.0.5", "selenium-webdriver": "^2.53.3", "semver": "^5.1.0", - "source-map": "^0.3.0", + "source-map": "^0.5.6", "source-map-support": "^0.4.2", "systemjs": "0.18.10", "ts-api-guardian": "^0.2.0",