2017-11-20 13:21:17 -05:00
|
|
|
/**
|
|
|
|
* @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 * as o from './output/output_ast';
|
|
|
|
import {OutputContext, error} from './util';
|
|
|
|
|
2018-01-11 18:37:56 -05:00
|
|
|
const CONSTANT_PREFIX = '_c';
|
|
|
|
|
2017-11-20 13:21:17 -05:00
|
|
|
export const enum DefinitionKind {Injector, Directive, Component}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A node that is a place-holder that allows the node to be replaced when the actual
|
|
|
|
* node is known.
|
|
|
|
*
|
|
|
|
* This allows the constant pool to change an expression from a direct reference to
|
|
|
|
* a constant to a shared constant. It returns a fix-up node that is later allowed to
|
|
|
|
* change the referenced expression.
|
|
|
|
*/
|
|
|
|
class FixupExpression extends o.Expression {
|
|
|
|
constructor(public resolved: o.Expression) { super(resolved.type); }
|
|
|
|
|
|
|
|
shared: boolean;
|
|
|
|
|
|
|
|
visitExpression(visitor: o.ExpressionVisitor, context: any): any {
|
2018-01-25 18:38:39 -05:00
|
|
|
return this.resolved.visitExpression(visitor, context);
|
2017-11-20 13:21:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
isEquivalent(e: o.Expression): boolean {
|
|
|
|
return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved);
|
|
|
|
}
|
|
|
|
|
|
|
|
fixup(expression: o.Expression) {
|
|
|
|
this.resolved = expression;
|
|
|
|
this.shared = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A constant pool allows a code emitter to share constant in an output context.
|
|
|
|
*
|
|
|
|
* The constant pool also supports sharing access to ivy definitions references.
|
|
|
|
*/
|
|
|
|
export class ConstantPool {
|
|
|
|
statements: o.Statement[] = [];
|
|
|
|
private literals = new Map<string, FixupExpression>();
|
|
|
|
private injectorDefinitions = new Map<any, FixupExpression>();
|
|
|
|
private directiveDefinitions = new Map<any, FixupExpression>();
|
2018-01-11 18:37:56 -05:00
|
|
|
private componentDefinitions = new Map<any, FixupExpression>();
|
2017-11-20 13:21:17 -05:00
|
|
|
|
|
|
|
private nextNameIndex = 0;
|
|
|
|
|
2018-01-11 18:37:56 -05:00
|
|
|
getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression {
|
2017-11-20 13:21:17 -05:00
|
|
|
const key = this.keyOf(literal);
|
|
|
|
let fixup = this.literals.get(key);
|
2018-01-11 18:37:56 -05:00
|
|
|
let newValue = false;
|
2017-11-20 13:21:17 -05:00
|
|
|
if (!fixup) {
|
|
|
|
fixup = new FixupExpression(literal);
|
|
|
|
this.literals.set(key, fixup);
|
2018-01-11 18:37:56 -05:00
|
|
|
newValue = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
2017-11-20 13:21:17 -05:00
|
|
|
// Replace the expression with a variable
|
|
|
|
const name = this.freshName();
|
|
|
|
this.statements.push(
|
|
|
|
o.variable(name).set(literal).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
|
|
|
fixup.fixup(o.variable(name));
|
|
|
|
}
|
2018-01-11 18:37:56 -05:00
|
|
|
|
2017-11-20 13:21:17 -05:00
|
|
|
return fixup;
|
|
|
|
}
|
|
|
|
|
|
|
|
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
|
|
|
const declarations = kind == DefinitionKind.Component ?
|
2018-01-11 18:37:56 -05:00
|
|
|
this.componentDefinitions :
|
2017-11-20 13:21:17 -05:00
|
|
|
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
|
|
|
let fixup = declarations.get(type);
|
|
|
|
if (!fixup) {
|
|
|
|
const property = kind == DefinitionKind.Component ?
|
|
|
|
'ngComponentDef' :
|
|
|
|
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
|
|
|
|
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
|
|
|
declarations.set(type, fixup);
|
|
|
|
} else if (!fixup.shared) {
|
|
|
|
const name = this.freshName();
|
|
|
|
this.statements.push(
|
|
|
|
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
|
|
|
fixup.fixup(o.variable(name));
|
|
|
|
}
|
|
|
|
return fixup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Produce a unique name.
|
|
|
|
*
|
|
|
|
* The name might be unique among different prefixes if any of the prefixes end in
|
|
|
|
* a digit so the prefix should be a constant string (not based on user input) and
|
|
|
|
* must not end in a digit.
|
|
|
|
*/
|
|
|
|
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
|
|
|
|
2018-01-11 18:37:56 -05:00
|
|
|
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
2017-11-20 13:21:17 -05:00
|
|
|
|
|
|
|
private keyOf(expression: o.Expression) {
|
|
|
|
return expression.visitExpression(new KeyVisitor(), null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class KeyVisitor implements o.ExpressionVisitor {
|
2018-01-11 18:37:56 -05:00
|
|
|
visitLiteralExpr(ast: o.LiteralExpr): string {
|
|
|
|
return `${typeof ast.value === 'string' ? '"' + ast.value + '"' : ast.value}`;
|
|
|
|
}
|
2017-11-20 13:21:17 -05:00
|
|
|
visitLiteralArrayExpr(ast: o.LiteralArrayExpr): string {
|
2018-01-11 18:37:56 -05:00
|
|
|
return `[${ast.entries.map(entry => entry.visitExpression(this, null)).join(',')}]`;
|
2017-11-20 13:21:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
visitLiteralMapExpr(ast: o.LiteralMapExpr): string {
|
2018-01-11 18:37:56 -05:00
|
|
|
const mapEntry = (entry: o.LiteralMapEntry) =>
|
|
|
|
`${entry.key}:${entry.value.visitExpression(this, null)}`;
|
|
|
|
return `{${ast.entries.map(mapEntry).join(',')}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
visitExternalExpr(ast: o.ExternalExpr): string {
|
|
|
|
return ast.value.moduleName ? `EX:${ast.value.moduleName}:${ast.value.name}` :
|
|
|
|
`EX:${ast.value.runtime.name}`;
|
2017-11-20 13:21:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
visitReadVarExpr = invalid;
|
|
|
|
visitWriteVarExpr = invalid;
|
|
|
|
visitWriteKeyExpr = invalid;
|
|
|
|
visitWritePropExpr = invalid;
|
|
|
|
visitInvokeMethodExpr = invalid;
|
|
|
|
visitInvokeFunctionExpr = invalid;
|
|
|
|
visitInstantiateExpr = invalid;
|
|
|
|
visitConditionalExpr = invalid;
|
|
|
|
visitNotExpr = invalid;
|
|
|
|
visitAssertNotNullExpr = invalid;
|
|
|
|
visitCastExpr = invalid;
|
|
|
|
visitFunctionExpr = invalid;
|
|
|
|
visitBinaryOperatorExpr = invalid;
|
|
|
|
visitReadPropExpr = invalid;
|
|
|
|
visitReadKeyExpr = invalid;
|
|
|
|
visitCommaExpr = invalid;
|
|
|
|
}
|
|
|
|
|
|
|
|
function invalid<T>(arg: o.Expression | o.Statement): never {
|
|
|
|
throw new Error(
|
|
|
|
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
|
|
|
}
|