feat(compiler): add support for source map generation (#14258)

fixes #14125

PR Close #14258
This commit is contained in:
Victor Berchet 2017-01-27 14:23:12 -08:00 committed by Miško Hevery
parent 53cf2ec573
commit 7ac38aa357
20 changed files with 1061 additions and 381 deletions

View File

@ -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;
}

View File

@ -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 =
[<o.Statement>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 {

View File

@ -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[]) {

View File

@ -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;
}

View File

@ -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);
}

View File

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

View File

@ -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;
}
}

View File

@ -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<string, string> = 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<string, number>();
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];
}

View File

@ -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 =
[<o.Statement>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, `>`);
}
}
}

View File

@ -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), '');
}

View File

@ -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);
}

View File

@ -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});
});
});
});
}

View File

@ -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', () => {

View File

@ -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');
});
});
});
}

View File

@ -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});
});
});
});
}

View File

@ -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', () => {

View File

@ -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';

View File

@ -5847,7 +5847,7 @@
"dev": true
},
"source-map": {
"version": "0.3.0",
"version": "0.5.6",
"dev": true
},
"source-map-support": {

6
npm-shrinkwrap.json generated
View File

@ -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": {

View File

@ -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",