repackaging: all the file moves

This commit is contained in:
Igor Minar
2016-04-28 08:02:15 -07:00
committed by Misko Hevery
parent 4fe0f1fa65
commit 505da6c0a8
739 changed files with 0 additions and 52 deletions

View File

@ -0,0 +1,3 @@
library angular2.core.util.asserions;
void assertArrayOfStrings(String identifier, Object value) {}

View File

@ -0,0 +1,16 @@
import {isArray, isString, isBlank, assertionsEnabled} from '../facade/lang';
import {BaseException} from '../facade/exceptions';
export function assertArrayOfStrings(identifier: string, value: any) {
if (!assertionsEnabled() || isBlank(value)) {
return;
}
if (!isArray(value)) {
throw new BaseException(`Expected '${identifier}' to be an array of strings.`);
}
for (var i = 0; i < value.length; i += 1) {
if (!isString(value[i])) {
throw new BaseException(`Expected '${identifier}' to be an array of strings.`);
}
}
}

View File

@ -0,0 +1,64 @@
export const $EOF = /*@ts2dart_const*/ 0;
export const $TAB = /*@ts2dart_const*/ 9;
export const $LF = /*@ts2dart_const*/ 10;
export const $VTAB = /*@ts2dart_const*/ 11;
export const $FF = /*@ts2dart_const*/ 12;
export const $CR = /*@ts2dart_const*/ 13;
export const $SPACE = /*@ts2dart_const*/ 32;
export const $BANG = /*@ts2dart_const*/ 33;
export const $DQ = /*@ts2dart_const*/ 34;
export const $HASH = /*@ts2dart_const*/ 35;
export const $$ = /*@ts2dart_const*/ 36;
export const $PERCENT = /*@ts2dart_const*/ 37;
export const $AMPERSAND = /*@ts2dart_const*/ 38;
export const $SQ = /*@ts2dart_const*/ 39;
export const $LPAREN = /*@ts2dart_const*/ 40;
export const $RPAREN = /*@ts2dart_const*/ 41;
export const $STAR = /*@ts2dart_const*/ 42;
export const $PLUS = /*@ts2dart_const*/ 43;
export const $COMMA = /*@ts2dart_const*/ 44;
export const $MINUS = /*@ts2dart_const*/ 45;
export const $PERIOD = /*@ts2dart_const*/ 46;
export const $SLASH = /*@ts2dart_const*/ 47;
export const $COLON = /*@ts2dart_const*/ 58;
export const $SEMICOLON = /*@ts2dart_const*/ 59;
export const $LT = /*@ts2dart_const*/ 60;
export const $EQ = /*@ts2dart_const*/ 61;
export const $GT = /*@ts2dart_const*/ 62;
export const $QUESTION = /*@ts2dart_const*/ 63;
export const $0 = /*@ts2dart_const*/ 48;
export const $9 = /*@ts2dart_const*/ 57;
export const $A = /*@ts2dart_const*/ 65;
export const $E = /*@ts2dart_const*/ 69;
export const $Z = /*@ts2dart_const*/ 90;
export const $LBRACKET = /*@ts2dart_const*/ 91;
export const $BACKSLASH = /*@ts2dart_const*/ 92;
export const $RBRACKET = /*@ts2dart_const*/ 93;
export const $CARET = /*@ts2dart_const*/ 94;
export const $_ = /*@ts2dart_const*/ 95;
export const $a = /*@ts2dart_const*/ 97;
export const $e = /*@ts2dart_const*/ 101;
export const $f = /*@ts2dart_const*/ 102;
export const $n = /*@ts2dart_const*/ 110;
export const $r = /*@ts2dart_const*/ 114;
export const $t = /*@ts2dart_const*/ 116;
export const $u = /*@ts2dart_const*/ 117;
export const $v = /*@ts2dart_const*/ 118;
export const $z = /*@ts2dart_const*/ 122;
export const $LBRACE = /*@ts2dart_const*/ 123;
export const $BAR = /*@ts2dart_const*/ 124;
export const $RBRACE = /*@ts2dart_const*/ 125;
export const $NBSP = /*@ts2dart_const*/ 160;
export const $PIPE = /*@ts2dart_const*/ 124;
export const $TILDA = /*@ts2dart_const*/ 126;
export const $AT = /*@ts2dart_const*/ 64;
export function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}

View File

@ -0,0 +1,801 @@
import {
isPresent,
isBlank,
isNumber,
isBoolean,
normalizeBool,
normalizeBlank,
serializeEnum,
Type,
isString,
RegExpWrapper,
StringWrapper,
isArray
} from 'angular2/src/facade/lang';
import {unimplemented, BaseException} from 'angular2/src/facade/exceptions';
import {
StringMapWrapper,
MapWrapper,
SetWrapper,
ListWrapper
} from 'angular2/src/facade/collection';
import {
ChangeDetectionStrategy,
CHANGE_DETECTION_STRATEGY_VALUES
} from 'angular2/src/core/change_detection/change_detection';
import {ViewEncapsulation, VIEW_ENCAPSULATION_VALUES} from 'angular2/src/core/metadata/view';
import {CssSelector} from 'angular2/src/compiler/selector';
import {splitAtColon, sanitizeIdentifier} from './util';
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/metadata/lifecycle_hooks';
import {getUrlScheme} from './url_resolver';
// group 1: "property" from "[property]"
// group 2: "event" from "(event)"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
export abstract class CompileMetadataWithIdentifier {
abstract toJson(): {[key: string]: any};
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
}
export abstract class CompileMetadataWithType extends CompileMetadataWithIdentifier {
abstract toJson(): {[key: string]: any};
get type(): CompileTypeMetadata { return <CompileTypeMetadata>unimplemented(); }
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
}
export function metadataFromJson(data: {[key: string]: any}): any {
return _COMPILE_METADATA_FROM_JSON[data['class']](data);
}
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
runtime: any;
name: string;
prefix: string;
moduleUrl: string;
value: any;
constructor(
{runtime, name, moduleUrl, prefix, value}:
{runtime?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
this.runtime = runtime;
this.name = name;
this.prefix = prefix;
this.moduleUrl = moduleUrl;
this.value = value;
}
static fromJson(data: {[key: string]: any}): CompileIdentifierMetadata {
let value = isArray(data['value']) ? _arrayFromJson(data['value'], metadataFromJson) :
_objFromJson(data['value'], metadataFromJson);
return new CompileIdentifierMetadata(
{name: data['name'], prefix: data['prefix'], moduleUrl: data['moduleUrl'], value: value});
}
toJson(): {[key: string]: any} {
let value = isArray(this.value) ? _arrayToJson(this.value) : _objToJson(this.value);
return {
// Note: Runtime type can't be serialized...
'class': 'Identifier',
'name': this.name,
'moduleUrl': this.moduleUrl,
'prefix': this.prefix,
'value': value
};
}
get identifier(): CompileIdentifierMetadata { return this; }
}
export class CompileDiDependencyMetadata {
isAttribute: boolean;
isSelf: boolean;
isHost: boolean;
isSkipSelf: boolean;
isOptional: boolean;
isValue: boolean;
query: CompileQueryMetadata;
viewQuery: CompileQueryMetadata;
token: CompileTokenMetadata;
value: any;
constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, query, viewQuery,
token, value}: {
isAttribute?: boolean,
isSelf?: boolean,
isHost?: boolean,
isSkipSelf?: boolean,
isOptional?: boolean,
isValue?: boolean,
query?: CompileQueryMetadata,
viewQuery?: CompileQueryMetadata,
token?: CompileTokenMetadata,
value?: any
} = {}) {
this.isAttribute = normalizeBool(isAttribute);
this.isSelf = normalizeBool(isSelf);
this.isHost = normalizeBool(isHost);
this.isSkipSelf = normalizeBool(isSkipSelf);
this.isOptional = normalizeBool(isOptional);
this.isValue = normalizeBool(isValue);
this.query = query;
this.viewQuery = viewQuery;
this.token = token;
this.value = value;
}
static fromJson(data: {[key: string]: any}): CompileDiDependencyMetadata {
return new CompileDiDependencyMetadata({
token: _objFromJson(data['token'], CompileTokenMetadata.fromJson),
query: _objFromJson(data['query'], CompileQueryMetadata.fromJson),
viewQuery: _objFromJson(data['viewQuery'], CompileQueryMetadata.fromJson),
value: data['value'],
isAttribute: data['isAttribute'],
isSelf: data['isSelf'],
isHost: data['isHost'],
isSkipSelf: data['isSkipSelf'],
isOptional: data['isOptional'],
isValue: data['isValue']
});
}
toJson(): {[key: string]: any} {
return {
'token': _objToJson(this.token),
'query': _objToJson(this.query),
'viewQuery': _objToJson(this.viewQuery),
'value': this.value,
'isAttribute': this.isAttribute,
'isSelf': this.isSelf,
'isHost': this.isHost,
'isSkipSelf': this.isSkipSelf,
'isOptional': this.isOptional,
'isValue': this.isValue
};
}
}
export class CompileProviderMetadata {
token: CompileTokenMetadata;
useClass: CompileTypeMetadata;
useValue: any;
useExisting: CompileTokenMetadata;
useFactory: CompileFactoryMetadata;
deps: CompileDiDependencyMetadata[];
multi: boolean;
constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
token?: CompileTokenMetadata,
useClass?: CompileTypeMetadata,
useValue?: any,
useExisting?: CompileTokenMetadata,
useFactory?: CompileFactoryMetadata,
deps?: CompileDiDependencyMetadata[],
multi?: boolean
}) {
this.token = token;
this.useClass = useClass;
this.useValue = useValue;
this.useExisting = useExisting;
this.useFactory = useFactory;
this.deps = normalizeBlank(deps);
this.multi = normalizeBool(multi);
}
static fromJson(data: {[key: string]: any}): CompileProviderMetadata {
return new CompileProviderMetadata({
token: _objFromJson(data['token'], CompileTokenMetadata.fromJson),
useClass: _objFromJson(data['useClass'], CompileTypeMetadata.fromJson),
useExisting: _objFromJson(data['useExisting'], CompileTokenMetadata.fromJson),
useValue: _objFromJson(data['useValue'], CompileIdentifierMetadata.fromJson),
useFactory: _objFromJson(data['useFactory'], CompileFactoryMetadata.fromJson),
multi: data['multi'],
deps: _arrayFromJson(data['deps'], CompileDiDependencyMetadata.fromJson)
});
}
toJson(): {[key: string]: any} {
return {
// Note: Runtime type can't be serialized...
'class': 'Provider',
'token': _objToJson(this.token),
'useClass': _objToJson(this.useClass),
'useExisting': _objToJson(this.useExisting),
'useValue': _objToJson(this.useValue),
'useFactory': _objToJson(this.useFactory),
'multi': this.multi,
'deps': _arrayToJson(this.deps)
};
}
}
export class CompileFactoryMetadata implements CompileIdentifierMetadata,
CompileMetadataWithIdentifier {
runtime: Function;
name: string;
prefix: string;
moduleUrl: string;
value: any;
diDeps: CompileDiDependencyMetadata[];
constructor({runtime, name, moduleUrl, prefix, diDeps, value}: {
runtime?: Function,
name?: string,
prefix?: string,
moduleUrl?: string,
value?: boolean,
diDeps?: CompileDiDependencyMetadata[]
}) {
this.runtime = runtime;
this.name = name;
this.prefix = prefix;
this.moduleUrl = moduleUrl;
this.diDeps = _normalizeArray(diDeps);
this.value = value;
}
get identifier(): CompileIdentifierMetadata { return this; }
static fromJson(data: {[key: string]: any}): CompileFactoryMetadata {
return new CompileFactoryMetadata({
name: data['name'],
prefix: data['prefix'],
moduleUrl: data['moduleUrl'],
value: data['value'],
diDeps: _arrayFromJson(data['diDeps'], CompileDiDependencyMetadata.fromJson)
});
}
toJson(): {[key: string]: any} {
return {
'class': 'Factory',
'name': this.name,
'prefix': this.prefix,
'moduleUrl': this.moduleUrl,
'value': this.value,
'diDeps': _arrayToJson(this.diDeps)
};
}
}
export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
value: any;
identifier: CompileIdentifierMetadata;
identifierIsInstance: boolean;
constructor({value, identifier, identifierIsInstance}: {
value?: any,
identifier?: CompileIdentifierMetadata,
identifierIsInstance?: boolean
}) {
this.value = value;
this.identifier = identifier;
this.identifierIsInstance = normalizeBool(identifierIsInstance);
}
static fromJson(data: {[key: string]: any}): CompileTokenMetadata {
return new CompileTokenMetadata({
value: data['value'],
identifier: _objFromJson(data['identifier'], CompileIdentifierMetadata.fromJson),
identifierIsInstance: data['identifierIsInstance']
});
}
toJson(): {[key: string]: any} {
return {
'value': this.value,
'identifier': _objToJson(this.identifier),
'identifierIsInstance': this.identifierIsInstance
};
}
get runtimeCacheKey(): any {
if (isPresent(this.identifier)) {
return this.identifier.runtime;
} else {
return this.value;
}
}
get assetCacheKey(): any {
if (isPresent(this.identifier)) {
return isPresent(this.identifier.moduleUrl) &&
isPresent(getUrlScheme(this.identifier.moduleUrl)) ?
`${this.identifier.name}|${this.identifier.moduleUrl}|${this.identifierIsInstance}` :
null;
} else {
return this.value;
}
}
equalsTo(token2: CompileTokenMetadata): boolean {
var rk = this.runtimeCacheKey;
var ak = this.assetCacheKey;
return (isPresent(rk) && rk == token2.runtimeCacheKey) ||
(isPresent(ak) && ak == token2.assetCacheKey);
}
get name(): string {
return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name;
}
}
export class CompileTokenMap<VALUE> {
private _valueMap = new Map<any, VALUE>();
private _values: VALUE[] = [];
add(token: CompileTokenMetadata, value: VALUE) {
var existing = this.get(token);
if (isPresent(existing)) {
throw new BaseException(`Can only add to a TokenMap! Token: ${token.name}`);
}
this._values.push(value);
var rk = token.runtimeCacheKey;
if (isPresent(rk)) {
this._valueMap.set(rk, value);
}
var ak = token.assetCacheKey;
if (isPresent(ak)) {
this._valueMap.set(ak, value);
}
}
get(token: CompileTokenMetadata): VALUE {
var rk = token.runtimeCacheKey;
var ak = token.assetCacheKey;
var result;
if (isPresent(rk)) {
result = this._valueMap.get(rk);
}
if (isBlank(result) && isPresent(ak)) {
result = this._valueMap.get(ak);
}
return result;
}
values(): VALUE[] { return this._values; }
get size(): number { return this._values.length; }
}
/**
* Metadata regarding compilation of a type.
*/
export class CompileTypeMetadata implements CompileIdentifierMetadata, CompileMetadataWithType {
runtime: Type;
name: string;
prefix: string;
moduleUrl: string;
isHost: boolean;
value: any;
diDeps: CompileDiDependencyMetadata[];
constructor({runtime, name, moduleUrl, prefix, isHost, value, diDeps}: {
runtime?: Type,
name?: string,
moduleUrl?: string,
prefix?: string,
isHost?: boolean,
value?: any,
diDeps?: CompileDiDependencyMetadata[]
} = {}) {
this.runtime = runtime;
this.name = name;
this.moduleUrl = moduleUrl;
this.prefix = prefix;
this.isHost = normalizeBool(isHost);
this.value = value;
this.diDeps = _normalizeArray(diDeps);
}
static fromJson(data: {[key: string]: any}): CompileTypeMetadata {
return new CompileTypeMetadata({
name: data['name'],
moduleUrl: data['moduleUrl'],
prefix: data['prefix'],
isHost: data['isHost'],
value: data['value'],
diDeps: _arrayFromJson(data['diDeps'], CompileDiDependencyMetadata.fromJson)
});
}
get identifier(): CompileIdentifierMetadata { return this; }
get type(): CompileTypeMetadata { return this; }
toJson(): {[key: string]: any} {
return {
// Note: Runtime type can't be serialized...
'class': 'Type',
'name': this.name,
'moduleUrl': this.moduleUrl,
'prefix': this.prefix,
'isHost': this.isHost,
'value': this.value,
'diDeps': _arrayToJson(this.diDeps)
};
}
}
export class CompileQueryMetadata {
selectors: Array<CompileTokenMetadata>;
descendants: boolean;
first: boolean;
propertyName: string;
read: CompileTokenMetadata;
constructor({selectors, descendants, first, propertyName, read}: {
selectors?: Array<CompileTokenMetadata>,
descendants?: boolean,
first?: boolean,
propertyName?: string,
read?: CompileTokenMetadata
} = {}) {
this.selectors = selectors;
this.descendants = normalizeBool(descendants);
this.first = normalizeBool(first);
this.propertyName = propertyName;
this.read = read;
}
static fromJson(data: {[key: string]: any}): CompileQueryMetadata {
return new CompileQueryMetadata({
selectors: _arrayFromJson(data['selectors'], CompileTokenMetadata.fromJson),
descendants: data['descendants'],
first: data['first'],
propertyName: data['propertyName'],
read: _objFromJson(data['read'], CompileTokenMetadata.fromJson)
});
}
toJson(): {[key: string]: any} {
return {
'selectors': _arrayToJson(this.selectors),
'descendants': this.descendants,
'first': this.first,
'propertyName': this.propertyName,
'read': _objToJson(this.read)
};
}
}
/**
* Metadata regarding compilation of a template.
*/
export class CompileTemplateMetadata {
encapsulation: ViewEncapsulation;
template: string;
templateUrl: string;
styles: string[];
styleUrls: string[];
ngContentSelectors: string[];
baseUrl: string;
constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors,
baseUrl}: {
encapsulation?: ViewEncapsulation,
template?: string,
templateUrl?: string,
styles?: string[],
styleUrls?: string[],
ngContentSelectors?: string[],
baseUrl?: string
} = {}) {
this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.Emulated;
this.template = template;
this.templateUrl = templateUrl;
this.styles = isPresent(styles) ? styles : [];
this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
this.baseUrl = baseUrl;
}
static fromJson(data: {[key: string]: any}): CompileTemplateMetadata {
return new CompileTemplateMetadata({
encapsulation: isPresent(data['encapsulation']) ?
VIEW_ENCAPSULATION_VALUES[data['encapsulation']] :
data['encapsulation'],
template: data['template'],
templateUrl: data['templateUrl'],
styles: data['styles'],
styleUrls: data['styleUrls'],
ngContentSelectors: data['ngContentSelectors'],
baseUrl: data['baseUrl']
});
}
toJson(): {[key: string]: any} {
return {
'encapsulation':
isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation,
'template': this.template,
'templateUrl': this.templateUrl,
'styles': this.styles,
'styleUrls': this.styleUrls,
'ngContentSelectors': this.ngContentSelectors,
'baseUrl': this.baseUrl
};
}
}
/**
* Metadata regarding compilation of a directive.
*/
export class CompileDirectiveMetadata implements CompileMetadataWithType {
static create({type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
lifecycleHooks, providers, viewProviders, queries, viewQueries, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
lifecycleHooks?: LifecycleHooks[],
providers?:
Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
viewProviders?:
Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
template?: CompileTemplateMetadata
} = {}): CompileDirectiveMetadata {
var hostListeners: {[key: string]: string} = {};
var hostProperties: {[key: string]: string} = {};
var hostAttributes: {[key: string]: string} = {};
if (isPresent(host)) {
StringMapWrapper.forEach(host, (value: string, key: string) => {
var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key);
if (isBlank(matches)) {
hostAttributes[key] = value;
} else if (isPresent(matches[1])) {
hostProperties[matches[1]] = value;
} else if (isPresent(matches[2])) {
hostListeners[matches[2]] = value;
}
});
}
var inputsMap: {[key: string]: string} = {};
if (isPresent(inputs)) {
inputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
inputsMap[parts[0]] = parts[1];
});
}
var outputsMap: {[key: string]: string} = {};
if (isPresent(outputs)) {
outputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
outputsMap[parts[0]] = parts[1];
});
}
return new CompileDirectiveMetadata({
type: type,
isComponent: normalizeBool(isComponent),
selector: selector,
exportAs: exportAs,
changeDetection: changeDetection,
inputs: inputsMap,
outputs: outputsMap,
hostListeners: hostListeners,
hostProperties: hostProperties,
hostAttributes: hostAttributes,
lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [],
providers: providers,
viewProviders: viewProviders,
queries: queries,
viewQueries: viewQueries,
template: template
});
}
type: CompileTypeMetadata;
isComponent: boolean;
selector: string;
exportAs: string;
changeDetection: ChangeDetectionStrategy;
inputs: {[key: string]: string};
outputs: {[key: string]: string};
hostListeners: {[key: string]: string};
hostProperties: {[key: string]: string};
hostAttributes: {[key: string]: string};
lifecycleHooks: LifecycleHooks[];
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
template: CompileTemplateMetadata;
constructor({type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
hostListeners, hostProperties, hostAttributes, lifecycleHooks, providers,
viewProviders, queries, viewQueries, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: {[key: string]: string},
outputs?: {[key: string]: string},
hostListeners?: {[key: string]: string},
hostProperties?: {[key: string]: string},
hostAttributes?: {[key: string]: string},
lifecycleHooks?: LifecycleHooks[],
providers?:
Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
viewProviders?:
Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
template?: CompileTemplateMetadata
} = {}) {
this.type = type;
this.isComponent = isComponent;
this.selector = selector;
this.exportAs = exportAs;
this.changeDetection = changeDetection;
this.inputs = inputs;
this.outputs = outputs;
this.hostListeners = hostListeners;
this.hostProperties = hostProperties;
this.hostAttributes = hostAttributes;
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
this.providers = _normalizeArray(providers);
this.viewProviders = _normalizeArray(viewProviders);
this.queries = _normalizeArray(queries);
this.viewQueries = _normalizeArray(viewQueries);
this.template = template;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
static fromJson(data: {[key: string]: any}): CompileDirectiveMetadata {
return new CompileDirectiveMetadata({
isComponent: data['isComponent'],
selector: data['selector'],
exportAs: data['exportAs'],
type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
changeDetection: isPresent(data['changeDetection']) ?
CHANGE_DETECTION_STRATEGY_VALUES[data['changeDetection']] :
data['changeDetection'],
inputs: data['inputs'],
outputs: data['outputs'],
hostListeners: data['hostListeners'],
hostProperties: data['hostProperties'],
hostAttributes: data['hostAttributes'],
lifecycleHooks:
(<any[]>data['lifecycleHooks']).map(hookValue => LIFECYCLE_HOOKS_VALUES[hookValue]),
template: isPresent(data['template']) ? CompileTemplateMetadata.fromJson(data['template']) :
data['template'],
providers: _arrayFromJson(data['providers'], metadataFromJson),
viewProviders: _arrayFromJson(data['viewProviders'], metadataFromJson),
queries: _arrayFromJson(data['queries'], CompileQueryMetadata.fromJson),
viewQueries: _arrayFromJson(data['viewQueries'], CompileQueryMetadata.fromJson)
});
}
toJson(): {[key: string]: any} {
return {
'class': 'Directive',
'isComponent': this.isComponent,
'selector': this.selector,
'exportAs': this.exportAs,
'type': isPresent(this.type) ? this.type.toJson() : this.type,
'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) :
this.changeDetection,
'inputs': this.inputs,
'outputs': this.outputs,
'hostListeners': this.hostListeners,
'hostProperties': this.hostProperties,
'hostAttributes': this.hostAttributes,
'lifecycleHooks': this.lifecycleHooks.map(hook => serializeEnum(hook)),
'template': isPresent(this.template) ? this.template.toJson() : this.template,
'providers': _arrayToJson(this.providers),
'viewProviders': _arrayToJson(this.viewProviders),
'queries': _arrayToJson(this.queries),
'viewQueries': _arrayToJson(this.viewQueries)
};
}
}
/**
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
*/
export function createHostComponentMeta(componentType: CompileTypeMetadata,
componentSelector: string): CompileDirectiveMetadata {
var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate();
return CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({
runtime: Object,
name: `${componentType.name}_Host`,
moduleUrl: componentType.moduleUrl,
isHost: true
}),
template: new CompileTemplateMetadata(
{template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
lifecycleHooks: [],
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
viewQueries: []
});
}
export class CompilePipeMetadata implements CompileMetadataWithType {
type: CompileTypeMetadata;
name: string;
pure: boolean;
lifecycleHooks: LifecycleHooks[];
constructor({type, name, pure, lifecycleHooks}: {
type?: CompileTypeMetadata,
name?: string,
pure?: boolean,
lifecycleHooks?: LifecycleHooks[]
} = {}) {
this.type = type;
this.name = name;
this.pure = normalizeBool(pure);
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
}
get identifier(): CompileIdentifierMetadata { return this.type; }
static fromJson(data: {[key: string]: any}): CompilePipeMetadata {
return new CompilePipeMetadata({
type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
name: data['name'],
pure: data['pure']
});
}
toJson(): {[key: string]: any} {
return {
'class': 'Pipe',
'type': isPresent(this.type) ? this.type.toJson() : null,
'name': this.name,
'pure': this.pure
};
}
}
var _COMPILE_METADATA_FROM_JSON = {
'Directive': CompileDirectiveMetadata.fromJson,
'Pipe': CompilePipeMetadata.fromJson,
'Type': CompileTypeMetadata.fromJson,
'Provider': CompileProviderMetadata.fromJson,
'Identifier': CompileIdentifierMetadata.fromJson,
'Factory': CompileFactoryMetadata.fromJson
};
function _arrayFromJson(obj: any[], fn: (a: {[key: string]: any}) => any): any {
return isBlank(obj) ? null : obj.map(o => _objFromJson(o, fn));
}
function _arrayToJson(obj: any[]): string | {[key: string]: any} {
return isBlank(obj) ? null : obj.map(_objToJson);
}
function _objFromJson(obj: any, fn: (a: {[key: string]: any}) => any): any {
if (isArray(obj)) return _arrayFromJson(obj, fn);
if (isString(obj) || isBlank(obj) || isBoolean(obj) || isNumber(obj)) return obj;
return fn(obj);
}
function _objToJson(obj: any): string | {[key: string]: any} {
if (isArray(obj)) return _arrayToJson(obj);
if (isString(obj) || isBlank(obj) || isBoolean(obj) || isNumber(obj)) return obj;
return obj.toJson();
}
function _normalizeArray(obj: any[]): any[] {
return isPresent(obj) ? obj : [];
}

View File

@ -0,0 +1,62 @@
export {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes';
export * from 'angular2/src/compiler/template_ast';
export {TEMPLATE_TRANSFORMS} from 'angular2/src/compiler/template_parser';
export {CompilerConfig, RenderTypes} from './config';
export * from './compile_metadata';
export * from './offline_compiler';
export {RuntimeCompiler} from './runtime_compiler';
export * from 'angular2/src/compiler/url_resolver';
export * from 'angular2/src/compiler/xhr';
export {ViewResolver} from './view_resolver';
export {DirectiveResolver} from './directive_resolver';
export {PipeResolver} from './pipe_resolver';
import {assertionsEnabled, Type} from 'angular2/src/facade/lang';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {DirectiveNormalizer} from 'angular2/src/compiler/directive_normalizer';
import {CompileMetadataResolver} from 'angular2/src/compiler/metadata_resolver';
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
import {ViewCompiler} from 'angular2/src/compiler/view_compiler/view_compiler';
import {CompilerConfig} from './config';
import {ComponentResolver} from 'angular2/src/core/linker/component_resolver';
import {RuntimeCompiler} from 'angular2/src/compiler/runtime_compiler';
import {ElementSchemaRegistry} from 'angular2/src/compiler/schema/element_schema_registry';
import {DomElementSchemaRegistry} from 'angular2/src/compiler/schema/dom_element_schema_registry';
import {UrlResolver, DEFAULT_PACKAGE_URL_PROVIDER} from 'angular2/src/compiler/url_resolver';
import {Parser} from './expression_parser/parser';
import {Lexer} from './expression_parser/lexer';
import {ViewResolver} from './view_resolver';
import {DirectiveResolver} from './directive_resolver';
import {PipeResolver} from './pipe_resolver';
function _createCompilerConfig() {
return new CompilerConfig(assertionsEnabled(), false, true);
}
/**
* A set of providers that provide `RuntimeCompiler` and its dependencies to use for
* template compilation.
*/
export const COMPILER_PROVIDERS: Array<any | Type | {[k: string]: any} | any[]> =
/*@ts2dart_const*/[
Lexer,
Parser,
HtmlParser,
TemplateParser,
DirectiveNormalizer,
CompileMetadataResolver,
DEFAULT_PACKAGE_URL_PROVIDER,
StyleCompiler,
ViewCompiler,
/*@ts2dart_Provider*/ {provide: CompilerConfig, useFactory: _createCompilerConfig, deps: []},
RuntimeCompiler,
/*@ts2dart_Provider*/ {provide: ComponentResolver, useExisting: RuntimeCompiler},
DomElementSchemaRegistry,
/*@ts2dart_Provider*/ {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
UrlResolver,
ViewResolver,
DirectiveResolver,
PipeResolver
];

View File

@ -0,0 +1,38 @@
import {isBlank} from 'angular2/src/facade/lang';
import {unimplemented} from 'angular2/src/facade/exceptions';
import {Identifiers} from './identifiers';
import {CompileIdentifierMetadata} from './compile_metadata';
export class CompilerConfig {
public renderTypes: RenderTypes;
constructor(public genDebugInfo: boolean, public logBindingUpdate: boolean,
public useJit: boolean, renderTypes: RenderTypes = null) {
if (isBlank(renderTypes)) {
renderTypes = new DefaultRenderTypes();
}
this.renderTypes = renderTypes;
}
}
/**
* Types used for the renderer.
* Can be replaced to specialize the generated output to a specific renderer
* to help tree shaking.
*/
export abstract class RenderTypes {
get renderer(): CompileIdentifierMetadata { return unimplemented(); }
get renderText(): CompileIdentifierMetadata { return unimplemented(); }
get renderElement(): CompileIdentifierMetadata { return unimplemented(); }
get renderComment(): CompileIdentifierMetadata { return unimplemented(); }
get renderNode(): CompileIdentifierMetadata { return unimplemented(); }
get renderEvent(): CompileIdentifierMetadata { return unimplemented(); }
}
export class DefaultRenderTypes implements RenderTypes {
renderer = Identifiers.Renderer;
renderText = null;
renderElement = null;
renderComment = null;
renderNode = null;
renderEvent = null;
}

View File

@ -0,0 +1,756 @@
import {NumberWrapper, StringWrapper, isPresent, resolveEnumToken} from "angular2/src/facade/lang";
import {BaseException} from 'angular2/src/facade/exceptions';
import {
isWhitespace,
$EOF,
$HASH,
$TILDA,
$CARET,
$PERCENT,
$$,
$_,
$COLON,
$SQ,
$DQ,
$EQ,
$SLASH,
$BACKSLASH,
$PERIOD,
$STAR,
$PLUS,
$LPAREN,
$RPAREN,
$LBRACE,
$RBRACE,
$LBRACKET,
$RBRACKET,
$PIPE,
$COMMA,
$SEMICOLON,
$MINUS,
$BANG,
$QUESTION,
$AT,
$AMPERSAND,
$GT,
$a,
$A,
$z,
$Z,
$0,
$9,
$FF,
$CR,
$LF,
$VTAB
} from "angular2/src/compiler/chars";
export {
$EOF,
$AT,
$RBRACE,
$LBRACE,
$LBRACKET,
$RBRACKET,
$LPAREN,
$RPAREN,
$COMMA,
$COLON,
$SEMICOLON,
isWhitespace
} from "angular2/src/compiler/chars";
export enum CssTokenType {
EOF,
String,
Comment,
Identifier,
Number,
IdentifierOrNumber,
AtKeyword,
Character,
Whitespace,
Invalid
}
export enum CssLexerMode {
ALL,
ALL_TRACK_WS,
SELECTOR,
PSEUDO_SELECTOR,
ATTRIBUTE_SELECTOR,
AT_RULE_QUERY,
MEDIA_QUERY,
BLOCK,
KEYFRAME_BLOCK,
STYLE_BLOCK,
STYLE_VALUE,
STYLE_VALUE_FUNCTION,
STYLE_CALC_FUNCTION
}
export class LexedCssResult {
constructor(public error: CssScannerError, public token: CssToken) {}
}
export function generateErrorMessage(input: string, message: string, errorValue: string,
index: number, row: number, column: number): string {
return `${message} at column ${row}:${column} in expression [` +
findProblemCode(input, errorValue, index, column) + ']';
}
export function findProblemCode(input: string, errorValue: string, index: number,
column: number): string {
var endOfProblemLine = index;
var current = charCode(input, index);
while (current > 0 && !isNewline(current)) {
current = charCode(input, ++endOfProblemLine);
}
var choppedString = input.substring(0, endOfProblemLine);
var pointerPadding = "";
for (var i = 0; i < column; i++) {
pointerPadding += " ";
}
var pointerString = "";
for (var i = 0; i < errorValue.length; i++) {
pointerString += "^";
}
return choppedString + "\n" + pointerPadding + pointerString + "\n";
}
export class CssToken {
numValue: number;
constructor(public index: number, public column: number, public line: number,
public type: CssTokenType, public strValue: string) {
this.numValue = charCode(strValue, 0);
}
}
export class CssLexer {
scan(text: string, trackComments: boolean = false): CssScanner {
return new CssScanner(text, trackComments);
}
}
export class CssScannerError extends BaseException {
public rawMessage: string;
public message: string;
constructor(public token: CssToken, message) {
super('Css Parse Error: ' + message);
this.rawMessage = message;
}
toString(): string { return this.message; }
}
function _trackWhitespace(mode: CssLexerMode) {
switch (mode) {
case CssLexerMode.SELECTOR:
case CssLexerMode.ALL_TRACK_WS:
case CssLexerMode.STYLE_VALUE:
return true;
default:
return false;
}
}
export class CssScanner {
peek: number;
peekPeek: number;
length: number = 0;
index: number = -1;
column: number = -1;
line: number = 0;
/** @internal */
_currentMode: CssLexerMode = CssLexerMode.BLOCK;
/** @internal */
_currentError: CssScannerError = null;
constructor(public input: string, private _trackComments: boolean = false) {
this.length = this.input.length;
this.peekPeek = this.peekAt(0);
this.advance();
}
getMode(): CssLexerMode { return this._currentMode; }
setMode(mode: CssLexerMode) {
if (this._currentMode != mode) {
if (_trackWhitespace(this._currentMode)) {
this.consumeWhitespace();
}
this._currentMode = mode;
}
}
advance(): void {
if (isNewline(this.peek)) {
this.column = 0;
this.line++;
} else {
this.column++;
}
this.index++;
this.peek = this.peekPeek;
this.peekPeek = this.peekAt(this.index + 1);
}
peekAt(index: number): number {
return index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, index);
}
consumeEmptyStatements(): void {
this.consumeWhitespace();
while (this.peek == $SEMICOLON) {
this.advance();
this.consumeWhitespace();
}
}
consumeWhitespace(): void {
while (isWhitespace(this.peek) || isNewline(this.peek)) {
this.advance();
if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) {
this.advance(); // /
this.advance(); // *
while (!isCommentEnd(this.peek, this.peekPeek)) {
if (this.peek == $EOF) {
this.error('Unterminated comment');
}
this.advance();
}
this.advance(); // *
this.advance(); // /
}
}
}
consume(type: CssTokenType, value: string = null): LexedCssResult {
var mode = this._currentMode;
this.setMode(CssLexerMode.ALL);
var previousIndex = this.index;
var previousLine = this.line;
var previousColumn = this.column;
var output = this.scan();
// just incase the inner scan method returned an error
if (isPresent(output.error)) {
this.setMode(mode);
return output;
}
var next = output.token;
if (!isPresent(next)) {
next = new CssToken(0, 0, 0, CssTokenType.EOF, "end of file");
}
var isMatchingType;
if (type == CssTokenType.IdentifierOrNumber) {
// TODO (matsko): implement array traversal for lookup here
isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier;
} else {
isMatchingType = next.type == type;
}
// before throwing the error we need to bring back the former
// mode so that the parser can recover...
this.setMode(mode);
var error = null;
if (!isMatchingType || (isPresent(value) && value != next.strValue)) {
var errorMessage = resolveEnumToken(CssTokenType, next.type) + " does not match expected " +
resolveEnumToken(CssTokenType, type) + " value";
if (isPresent(value)) {
errorMessage += ' ("' + next.strValue + '" should match "' + value + '")';
}
error = new CssScannerError(
next, generateErrorMessage(this.input, errorMessage, next.strValue, previousIndex,
previousLine, previousColumn));
}
return new LexedCssResult(error, next);
}
scan(): LexedCssResult {
var trackWS = _trackWhitespace(this._currentMode);
if (this.index == 0 && !trackWS) { // first scan
this.consumeWhitespace();
}
var token = this._scan();
if (token == null) return null;
var error = this._currentError;
this._currentError = null;
if (!trackWS) {
this.consumeWhitespace();
}
return new LexedCssResult(error, token);
}
/** @internal */
_scan(): CssToken {
var peek = this.peek;
var peekPeek = this.peekPeek;
if (peek == $EOF) return null;
if (isCommentStart(peek, peekPeek)) {
// even if comments are not tracked we still lex the
// comment so we can move the pointer forward
var commentToken = this.scanComment();
if (this._trackComments) {
return commentToken;
}
}
if (_trackWhitespace(this._currentMode) && (isWhitespace(peek) || isNewline(peek))) {
return this.scanWhitespace();
}
peek = this.peek;
peekPeek = this.peekPeek;
if (peek == $EOF) return null;
if (isStringStart(peek, peekPeek)) {
return this.scanString();
}
// something like url(cool)
if (this._currentMode == CssLexerMode.STYLE_VALUE_FUNCTION) {
return this.scanCssValueFunction();
}
var isModifier = peek == $PLUS || peek == $MINUS;
var digitA = isModifier ? false : isDigit(peek);
var digitB = isDigit(peekPeek);
if (digitA || (isModifier && (peekPeek == $PERIOD || digitB)) || (peek == $PERIOD && digitB)) {
return this.scanNumber();
}
if (peek == $AT) {
return this.scanAtExpression();
}
if (isIdentifierStart(peek, peekPeek)) {
return this.scanIdentifier();
}
if (isValidCssCharacter(peek, this._currentMode)) {
return this.scanCharacter();
}
return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`);
}
scanComment(): CssToken {
if (this.assertCondition(isCommentStart(this.peek, this.peekPeek),
"Expected comment start value")) {
return null;
}
var start = this.index;
var startingColumn = this.column;
var startingLine = this.line;
this.advance(); // /
this.advance(); // *
while (!isCommentEnd(this.peek, this.peekPeek)) {
if (this.peek == $EOF) {
this.error('Unterminated comment');
}
this.advance();
}
this.advance(); // *
this.advance(); // /
var str = this.input.substring(start, this.index);
return new CssToken(start, startingColumn, startingLine, CssTokenType.Comment, str);
}
scanWhitespace(): CssToken {
var start = this.index;
var startingColumn = this.column;
var startingLine = this.line;
while (isWhitespace(this.peek) && this.peek != $EOF) {
this.advance();
}
var str = this.input.substring(start, this.index);
return new CssToken(start, startingColumn, startingLine, CssTokenType.Whitespace, str);
}
scanString(): CssToken {
if (this.assertCondition(isStringStart(this.peek, this.peekPeek),
"Unexpected non-string starting value")) {
return null;
}
var target = this.peek;
var start = this.index;
var startingColumn = this.column;
var startingLine = this.line;
var previous = target;
this.advance();
while (!isCharMatch(target, previous, this.peek)) {
if (this.peek == $EOF || isNewline(this.peek)) {
this.error('Unterminated quote');
}
previous = this.peek;
this.advance();
}
if (this.assertCondition(this.peek == target, "Unterminated quote")) {
return null;
}
this.advance();
var str = this.input.substring(start, this.index);
return new CssToken(start, startingColumn, startingLine, CssTokenType.String, str);
}
scanNumber(): CssToken {
var start = this.index;
var startingColumn = this.column;
if (this.peek == $PLUS || this.peek == $MINUS) {
this.advance();
}
var periodUsed = false;
while (isDigit(this.peek) || this.peek == $PERIOD) {
if (this.peek == $PERIOD) {
if (periodUsed) {
this.error('Unexpected use of a second period value');
}
periodUsed = true;
}
this.advance();
}
var strValue = this.input.substring(start, this.index);
return new CssToken(start, startingColumn, this.line, CssTokenType.Number, strValue);
}
scanIdentifier(): CssToken {
if (this.assertCondition(isIdentifierStart(this.peek, this.peekPeek),
'Expected identifier starting value')) {
return null;
}
var start = this.index;
var startingColumn = this.column;
while (isIdentifierPart(this.peek)) {
this.advance();
}
var strValue = this.input.substring(start, this.index);
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
}
scanCssValueFunction(): CssToken {
var start = this.index;
var startingColumn = this.column;
while (this.peek != $EOF && this.peek != $RPAREN) {
this.advance();
}
var strValue = this.input.substring(start, this.index);
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
}
scanCharacter(): CssToken {
var start = this.index;
var startingColumn = this.column;
if (this.assertCondition(isValidCssCharacter(this.peek, this._currentMode),
charStr(this.peek) + ' is not a valid CSS character')) {
return null;
}
var c = this.input.substring(start, start + 1);
this.advance();
return new CssToken(start, startingColumn, this.line, CssTokenType.Character, c);
}
scanAtExpression(): CssToken {
if (this.assertCondition(this.peek == $AT, 'Expected @ value')) {
return null;
}
var start = this.index;
var startingColumn = this.column;
this.advance();
if (isIdentifierStart(this.peek, this.peekPeek)) {
var ident = this.scanIdentifier();
var strValue = '@' + ident.strValue;
return new CssToken(start, startingColumn, this.line, CssTokenType.AtKeyword, strValue);
} else {
return this.scanCharacter();
}
}
assertCondition(status: boolean, errorMessage: string): boolean {
if (!status) {
this.error(errorMessage);
return true;
}
return false;
}
error(message: string, errorTokenValue: string = null, doNotAdvance: boolean = false): CssToken {
var index: number = this.index;
var column: number = this.column;
var line: number = this.line;
errorTokenValue =
isPresent(errorTokenValue) ? errorTokenValue : StringWrapper.fromCharCode(this.peek);
var invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue);
var errorMessage =
generateErrorMessage(this.input, message, errorTokenValue, index, line, column);
if (!doNotAdvance) {
this.advance();
}
this._currentError = new CssScannerError(invalidToken, errorMessage);
return invalidToken;
}
}
function isAtKeyword(current: CssToken, next: CssToken): boolean {
return current.numValue == $AT && next.type == CssTokenType.Identifier;
}
function isCharMatch(target: number, previous: number, code: number): boolean {
return code == target && previous != $BACKSLASH;
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
}
function isCommentStart(code: number, next: number): boolean {
return code == $SLASH && next == $STAR;
}
function isCommentEnd(code: number, next: number): boolean {
return code == $STAR && next == $SLASH;
}
function isStringStart(code: number, next: number): boolean {
var target = code;
if (target == $BACKSLASH) {
target = next;
}
return target == $DQ || target == $SQ;
}
function isIdentifierStart(code: number, next: number): boolean {
var target = code;
if (target == $MINUS) {
target = next;
}
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
target == $MINUS || target == $_;
}
function isIdentifierPart(target: number): boolean {
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
target == $MINUS || target == $_ || isDigit(target);
}
function isValidPseudoSelectorCharacter(code: number): boolean {
switch (code) {
case $LPAREN:
case $RPAREN:
return true;
default:
return false;
}
}
function isValidKeyframeBlockCharacter(code: number): boolean {
return code == $PERCENT;
}
function isValidAttributeSelectorCharacter(code: number): boolean {
// value^*|$~=something
switch (code) {
case $$:
case $PIPE:
case $CARET:
case $TILDA:
case $STAR:
case $EQ:
return true;
default:
return false;
}
}
function isValidSelectorCharacter(code: number): boolean {
// selector [ key = value ]
// IDENT C IDENT C IDENT C
// #id, .class, *+~>
// tag:PSEUDO
switch (code) {
case $HASH:
case $PERIOD:
case $TILDA:
case $STAR:
case $PLUS:
case $GT:
case $COLON:
case $PIPE:
case $COMMA:
return true;
default:
return false;
}
}
function isValidStyleBlockCharacter(code: number): boolean {
// key:value;
// key:calc(something ... )
switch (code) {
case $HASH:
case $SEMICOLON:
case $COLON:
case $PERCENT:
case $SLASH:
case $BACKSLASH:
case $BANG:
case $PERIOD:
case $LPAREN:
case $RPAREN:
return true;
default:
return false;
}
}
function isValidMediaQueryRuleCharacter(code: number): boolean {
// (min-width: 7.5em) and (orientation: landscape)
switch (code) {
case $LPAREN:
case $RPAREN:
case $COLON:
case $PERCENT:
case $PERIOD:
return true;
default:
return false;
}
}
function isValidAtRuleCharacter(code: number): boolean {
// @document url(http://www.w3.org/page?something=on#hash),
switch (code) {
case $LPAREN:
case $RPAREN:
case $COLON:
case $PERCENT:
case $PERIOD:
case $SLASH:
case $BACKSLASH:
case $HASH:
case $EQ:
case $QUESTION:
case $AMPERSAND:
case $STAR:
case $COMMA:
case $MINUS:
case $PLUS:
return true;
default:
return false;
}
}
function isValidStyleFunctionCharacter(code: number): boolean {
switch (code) {
case $PERIOD:
case $MINUS:
case $PLUS:
case $STAR:
case $SLASH:
case $LPAREN:
case $RPAREN:
case $COMMA:
return true;
default:
return false;
}
}
function isValidBlockCharacter(code: number): boolean {
// @something { }
// IDENT
return code == $AT;
}
function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
switch (mode) {
case CssLexerMode.ALL:
case CssLexerMode.ALL_TRACK_WS:
return true;
case CssLexerMode.SELECTOR:
return isValidSelectorCharacter(code);
case CssLexerMode.PSEUDO_SELECTOR:
return isValidPseudoSelectorCharacter(code);
case CssLexerMode.ATTRIBUTE_SELECTOR:
return isValidAttributeSelectorCharacter(code);
case CssLexerMode.MEDIA_QUERY:
return isValidMediaQueryRuleCharacter(code);
case CssLexerMode.AT_RULE_QUERY:
return isValidAtRuleCharacter(code);
case CssLexerMode.KEYFRAME_BLOCK:
return isValidKeyframeBlockCharacter(code);
case CssLexerMode.STYLE_BLOCK:
case CssLexerMode.STYLE_VALUE:
return isValidStyleBlockCharacter(code);
case CssLexerMode.STYLE_CALC_FUNCTION:
return isValidStyleFunctionCharacter(code);
case CssLexerMode.BLOCK:
return isValidBlockCharacter(code);
default:
return false;
}
}
function charCode(input, index): number {
return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index);
}
function charStr(code: number): string {
return StringWrapper.fromCharCode(code);
}
export function isNewline(code): boolean {
switch (code) {
case $FF:
case $CR:
case $LF:
case $VTAB:
return true;
default:
return false;
}
}

View File

@ -0,0 +1,740 @@
import {
ParseSourceSpan,
ParseSourceFile,
ParseLocation,
ParseError
} from "angular2/src/compiler/parse_util";
import {
bitWiseOr,
bitWiseAnd,
NumberWrapper,
StringWrapper,
isPresent
} from "angular2/src/facade/lang";
import {
CssLexerMode,
CssToken,
CssTokenType,
CssScanner,
CssScannerError,
generateErrorMessage,
$AT,
$EOF,
$RBRACE,
$LBRACE,
$LBRACKET,
$RBRACKET,
$LPAREN,
$RPAREN,
$COMMA,
$COLON,
$SEMICOLON,
isNewline
} from "angular2/src/compiler/css/lexer";
export {CssToken} from "angular2/src/compiler/css/lexer";
export enum BlockType {
Import,
Charset,
Namespace,
Supports,
Keyframes,
MediaQuery,
Selector,
FontFace,
Page,
Document,
Viewport,
Unsupported
}
const EOF_DELIM = 1;
const RBRACE_DELIM = 2;
const LBRACE_DELIM = 4;
const COMMA_DELIM = 8;
const COLON_DELIM = 16;
const SEMICOLON_DELIM = 32;
const NEWLINE_DELIM = 64;
const RPAREN_DELIM = 128;
function mergeTokens(tokens: CssToken[], separator: string = ""): CssToken {
var mainToken = tokens[0];
var str = mainToken.strValue;
for (var i = 1; i < tokens.length; i++) {
str += separator + tokens[i].strValue;
}
return new CssToken(mainToken.index, mainToken.column, mainToken.line, mainToken.type, str);
}
function getDelimFromToken(token: CssToken): number {
return getDelimFromCharacter(token.numValue);
}
function getDelimFromCharacter(code: number): number {
switch (code) {
case $EOF:
return EOF_DELIM;
case $COMMA:
return COMMA_DELIM;
case $COLON:
return COLON_DELIM;
case $SEMICOLON:
return SEMICOLON_DELIM;
case $RBRACE:
return RBRACE_DELIM;
case $LBRACE:
return LBRACE_DELIM;
case $RPAREN:
return RPAREN_DELIM;
default:
return isNewline(code) ? NEWLINE_DELIM : 0;
}
}
function characterContainsDelimiter(code: number, delimiters: number): boolean {
return bitWiseAnd([getDelimFromCharacter(code), delimiters]) > 0;
}
export class CssAST {
visit(visitor: CssASTVisitor, context?: any): void {}
}
export interface CssASTVisitor {
visitCssValue(ast: CssStyleValueAST, context?: any): void;
visitInlineCssRule(ast: CssInlineRuleAST, context?: any): void;
visitCssKeyframeRule(ast: CssKeyframeRuleAST, context?: any): void;
visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAST, context?: any): void;
visitCssMediaQueryRule(ast: CssMediaQueryRuleAST, context?: any): void;
visitCssSelectorRule(ast: CssSelectorRuleAST, context?: any): void;
visitCssSelector(ast: CssSelectorAST, context?: any): void;
visitCssDefinition(ast: CssDefinitionAST, context?: any): void;
visitCssBlock(ast: CssBlockAST, context?: any): void;
visitCssStyleSheet(ast: CssStyleSheetAST, context?: any): void;
visitUnkownRule(ast: CssUnknownTokenListAST, context?: any): void;
}
export class ParsedCssResult {
constructor(public errors: CssParseError[], public ast: CssStyleSheetAST) {}
}
export class CssParser {
private _errors: CssParseError[] = [];
private _file: ParseSourceFile;
constructor(private _scanner: CssScanner, private _fileName: string) {
this._file = new ParseSourceFile(this._scanner.input, _fileName);
}
/** @internal */
_resolveBlockType(token: CssToken): BlockType {
switch (token.strValue) {
case '@-o-keyframes':
case '@-moz-keyframes':
case '@-webkit-keyframes':
case '@keyframes':
return BlockType.Keyframes;
case '@charset':
return BlockType.Charset;
case '@import':
return BlockType.Import;
case '@namespace':
return BlockType.Namespace;
case '@page':
return BlockType.Page;
case '@document':
return BlockType.Document;
case '@media':
return BlockType.MediaQuery;
case '@font-face':
return BlockType.FontFace;
case '@viewport':
return BlockType.Viewport;
case '@supports':
return BlockType.Supports;
default:
return BlockType.Unsupported;
}
}
parse(): ParsedCssResult {
var delimiters: number = EOF_DELIM;
var ast = this._parseStyleSheet(delimiters);
var errors = this._errors;
this._errors = [];
return new ParsedCssResult(errors, ast);
}
/** @internal */
_parseStyleSheet(delimiters): CssStyleSheetAST {
var results = [];
this._scanner.consumeEmptyStatements();
while (this._scanner.peek != $EOF) {
this._scanner.setMode(CssLexerMode.BLOCK);
results.push(this._parseRule(delimiters));
}
return new CssStyleSheetAST(results);
}
/** @internal */
_parseRule(delimiters: number): CssRuleAST {
if (this._scanner.peek == $AT) {
return this._parseAtRule(delimiters);
}
return this._parseSelectorRule(delimiters);
}
/** @internal */
_parseAtRule(delimiters: number): CssRuleAST {
this._scanner.setMode(CssLexerMode.BLOCK);
var token = this._scan();
this._assertCondition(token.type == CssTokenType.AtKeyword,
`The CSS Rule ${token.strValue} is not a valid [@] rule.`, token);
var block, type = this._resolveBlockType(token);
switch (type) {
case BlockType.Charset:
case BlockType.Namespace:
case BlockType.Import:
var value = this._parseValue(delimiters);
this._scanner.setMode(CssLexerMode.BLOCK);
this._scanner.consumeEmptyStatements();
return new CssInlineRuleAST(type, value);
case BlockType.Viewport:
case BlockType.FontFace:
block = this._parseStyleBlock(delimiters);
return new CssBlockRuleAST(type, block);
case BlockType.Keyframes:
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
// keyframes only have one identifier name
var name = tokens[0];
return new CssKeyframeRuleAST(name, this._parseKeyframeBlock(delimiters));
case BlockType.MediaQuery:
this._scanner.setMode(CssLexerMode.MEDIA_QUERY);
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
return new CssMediaQueryRuleAST(tokens, this._parseBlock(delimiters));
case BlockType.Document:
case BlockType.Supports:
case BlockType.Page:
this._scanner.setMode(CssLexerMode.AT_RULE_QUERY);
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
return new CssBlockDefinitionRuleAST(type, tokens, this._parseBlock(delimiters));
// if a custom @rule { ... } is used it should still tokenize the insides
default:
var listOfTokens = [];
this._scanner.setMode(CssLexerMode.ALL);
this._error(generateErrorMessage(
this._scanner.input,
`The CSS "at" rule "${token.strValue}" is not allowed to used here`,
token.strValue, token.index, token.line, token.column),
token);
this._collectUntilDelim(bitWiseOr([delimiters, LBRACE_DELIM, SEMICOLON_DELIM]))
.forEach((token) => { listOfTokens.push(token); });
if (this._scanner.peek == $LBRACE) {
this._consume(CssTokenType.Character, '{');
this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]))
.forEach((token) => { listOfTokens.push(token); });
this._consume(CssTokenType.Character, '}');
}
return new CssUnknownTokenListAST(token, listOfTokens);
}
}
/** @internal */
_parseSelectorRule(delimiters: number): CssSelectorRuleAST {
var selectors = this._parseSelectors(delimiters);
var block = this._parseStyleBlock(delimiters);
this._scanner.setMode(CssLexerMode.BLOCK);
this._scanner.consumeEmptyStatements();
return new CssSelectorRuleAST(selectors, block);
}
/** @internal */
_parseSelectors(delimiters: number): CssSelectorAST[] {
delimiters = bitWiseOr([delimiters, LBRACE_DELIM]);
var selectors = [];
var isParsingSelectors = true;
while (isParsingSelectors) {
selectors.push(this._parseSelector(delimiters));
isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters);
if (isParsingSelectors) {
this._consume(CssTokenType.Character, ',');
isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters);
}
}
return selectors;
}
/** @internal */
_scan(): CssToken {
var output = this._scanner.scan();
var token = output.token;
var error = output.error;
if (isPresent(error)) {
this._error(error.rawMessage, token);
}
return token;
}
/** @internal */
_consume(type: CssTokenType, value: string = null): CssToken {
var output = this._scanner.consume(type, value);
var token = output.token;
var error = output.error;
if (isPresent(error)) {
this._error(error.rawMessage, token);
}
return token;
}
/** @internal */
_parseKeyframeBlock(delimiters: number): CssBlockAST {
delimiters = bitWiseOr([delimiters, RBRACE_DELIM]);
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
this._consume(CssTokenType.Character, '{');
var definitions = [];
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
definitions.push(this._parseKeyframeDefinition(delimiters));
}
this._consume(CssTokenType.Character, '}');
return new CssBlockAST(definitions);
}
/** @internal */
_parseKeyframeDefinition(delimiters: number): CssKeyframeDefinitionAST {
var stepTokens = [];
delimiters = bitWiseOr([delimiters, LBRACE_DELIM]);
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
stepTokens.push(this._parseKeyframeLabel(bitWiseOr([delimiters, COMMA_DELIM])));
if (this._scanner.peek != $LBRACE) {
this._consume(CssTokenType.Character, ',');
}
}
var styles = this._parseStyleBlock(bitWiseOr([delimiters, RBRACE_DELIM]));
this._scanner.setMode(CssLexerMode.BLOCK);
return new CssKeyframeDefinitionAST(stepTokens, styles);
}
/** @internal */
_parseKeyframeLabel(delimiters: number): CssToken {
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
return mergeTokens(this._collectUntilDelim(delimiters));
}
/** @internal */
_parseSelector(delimiters: number): CssSelectorAST {
delimiters = bitWiseOr([delimiters, COMMA_DELIM, LBRACE_DELIM]);
this._scanner.setMode(CssLexerMode.SELECTOR);
var selectorCssTokens = [];
var isComplex = false;
var wsCssToken;
var previousToken;
var parenCount = 0;
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
var code = this._scanner.peek;
switch (code) {
case $LPAREN:
parenCount++;
break;
case $RPAREN:
parenCount--;
break;
case $COLON:
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR);
previousToken = this._consume(CssTokenType.Character, ':');
selectorCssTokens.push(previousToken);
continue;
case $LBRACKET:
// if we are already inside an attribute selector then we can't
// jump into the mode again. Therefore this error will get picked
// up when the scan method is called below.
if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) {
selectorCssTokens.push(this._consume(CssTokenType.Character, '['));
this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR);
continue;
}
break;
case $RBRACKET:
selectorCssTokens.push(this._consume(CssTokenType.Character, ']'));
this._scanner.setMode(CssLexerMode.SELECTOR);
continue;
}
var token = this._scan();
// special case for the ":not(" selector since it
// contains an inner selector that needs to be parsed
// in isolation
if (this._scanner.getMode() == CssLexerMode.PSEUDO_SELECTOR && isPresent(previousToken) &&
previousToken.numValue == $COLON && token.strValue == "not" &&
this._scanner.peek == $LPAREN) {
selectorCssTokens.push(token);
selectorCssTokens.push(this._consume(CssTokenType.Character, '('));
// the inner selector inside of :not(...) can only be one
// CSS selector (no commas allowed) therefore we parse only
// one selector by calling the method below
this._parseSelector(bitWiseOr([delimiters, RPAREN_DELIM]))
.tokens.forEach(
(innerSelectorToken) => { selectorCssTokens.push(innerSelectorToken); });
selectorCssTokens.push(this._consume(CssTokenType.Character, ')'));
continue;
}
previousToken = token;
if (token.type == CssTokenType.Whitespace) {
wsCssToken = token;
} else {
if (isPresent(wsCssToken)) {
selectorCssTokens.push(wsCssToken);
wsCssToken = null;
isComplex = true;
}
selectorCssTokens.push(token);
}
}
if (this._scanner.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR) {
this._error(
`Unbalanced CSS attribute selector at column ${previousToken.line}:${previousToken.column}`,
previousToken);
} else if (parenCount > 0) {
this._error(
`Unbalanced pseudo selector function value at column ${previousToken.line}:${previousToken.column}`,
previousToken);
}
return new CssSelectorAST(selectorCssTokens, isComplex);
}
/** @internal */
_parseValue(delimiters: number): CssStyleValueAST {
delimiters = bitWiseOr([delimiters, RBRACE_DELIM, SEMICOLON_DELIM, NEWLINE_DELIM]);
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
var strValue = "";
var tokens = [];
var previous: CssToken;
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
var token;
if (isPresent(previous) && previous.type == CssTokenType.Identifier &&
this._scanner.peek == $LPAREN) {
token = this._consume(CssTokenType.Character, '(');
tokens.push(token);
strValue += token.strValue;
this._scanner.setMode(CssLexerMode.STYLE_VALUE_FUNCTION);
token = this._scan();
tokens.push(token);
strValue += token.strValue;
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
token = this._consume(CssTokenType.Character, ')');
tokens.push(token);
strValue += token.strValue;
} else {
token = this._scan();
if (token.type != CssTokenType.Whitespace) {
tokens.push(token);
}
strValue += token.strValue;
}
previous = token;
}
this._scanner.consumeWhitespace();
var code = this._scanner.peek;
if (code == $SEMICOLON) {
this._consume(CssTokenType.Character, ';');
} else if (code != $RBRACE) {
this._error(
generateErrorMessage(this._scanner.input,
`The CSS key/value definition did not end with a semicolon`,
previous.strValue, previous.index, previous.line, previous.column),
previous);
}
return new CssStyleValueAST(tokens, strValue);
}
/** @internal */
_collectUntilDelim(delimiters: number, assertType: CssTokenType = null): CssToken[] {
var tokens = [];
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
var val = isPresent(assertType) ? this._consume(assertType) : this._scan();
tokens.push(val);
}
return tokens;
}
/** @internal */
_parseBlock(delimiters: number): CssBlockAST {
delimiters = bitWiseOr([delimiters, RBRACE_DELIM]);
this._scanner.setMode(CssLexerMode.BLOCK);
this._consume(CssTokenType.Character, '{');
this._scanner.consumeEmptyStatements();
var results = [];
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
results.push(this._parseRule(delimiters));
}
this._consume(CssTokenType.Character, '}');
this._scanner.setMode(CssLexerMode.BLOCK);
this._scanner.consumeEmptyStatements();
return new CssBlockAST(results);
}
/** @internal */
_parseStyleBlock(delimiters: number): CssBlockAST {
delimiters = bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]);
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
this._consume(CssTokenType.Character, '{');
this._scanner.consumeEmptyStatements();
var definitions = [];
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
definitions.push(this._parseDefinition(delimiters));
this._scanner.consumeEmptyStatements();
}
this._consume(CssTokenType.Character, '}');
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
this._scanner.consumeEmptyStatements();
return new CssBlockAST(definitions);
}
/** @internal */
_parseDefinition(delimiters: number): CssDefinitionAST {
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
var prop = this._consume(CssTokenType.Identifier);
var parseValue, value = null;
// the colon value separates the prop from the style.
// there are a few cases as to what could happen if it
// is missing
switch (this._scanner.peek) {
case $COLON:
this._consume(CssTokenType.Character, ':');
parseValue = true;
break;
case $SEMICOLON:
case $RBRACE:
case $EOF:
parseValue = false;
break;
default:
var propStr = [prop.strValue];
if (this._scanner.peek != $COLON) {
// this will throw the error
var nextValue = this._consume(CssTokenType.Character, ':');
propStr.push(nextValue.strValue);
var remainingTokens = this._collectUntilDelim(
bitWiseOr([delimiters, COLON_DELIM, SEMICOLON_DELIM]), CssTokenType.Identifier);
if (remainingTokens.length > 0) {
remainingTokens.forEach((token) => { propStr.push(token.strValue); });
}
prop = new CssToken(prop.index, prop.column, prop.line, prop.type, propStr.join(" "));
}
// this means we've reached the end of the definition and/or block
if (this._scanner.peek == $COLON) {
this._consume(CssTokenType.Character, ':');
parseValue = true;
} else {
parseValue = false;
}
break;
}
if (parseValue) {
value = this._parseValue(delimiters);
} else {
this._error(generateErrorMessage(this._scanner.input,
`The CSS property was not paired with a style value`,
prop.strValue, prop.index, prop.line, prop.column),
prop);
}
return new CssDefinitionAST(prop, value);
}
/** @internal */
_assertCondition(status: boolean, errorMessage: string, problemToken: CssToken): boolean {
if (!status) {
this._error(errorMessage, problemToken);
return true;
}
return false;
}
/** @internal */
_error(message: string, problemToken: CssToken) {
var length = problemToken.strValue.length;
var error = CssParseError.create(this._file, 0, problemToken.line, problemToken.column, length,
message);
this._errors.push(error);
}
}
export class CssStyleValueAST extends CssAST {
constructor(public tokens: CssToken[], public strValue: string) { super(); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssValue(this); }
}
export class CssRuleAST extends CssAST {}
export class CssBlockRuleAST extends CssRuleAST {
constructor(public type: BlockType, public block: CssBlockAST, public name: CssToken = null) {
super();
}
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); }
}
export class CssKeyframeRuleAST extends CssBlockRuleAST {
constructor(name: CssToken, block: CssBlockAST) { super(BlockType.Keyframes, block, name); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssKeyframeRule(this, context); }
}
export class CssKeyframeDefinitionAST extends CssBlockRuleAST {
public steps;
constructor(_steps: CssToken[], block: CssBlockAST) {
super(BlockType.Keyframes, block, mergeTokens(_steps, ","));
this.steps = _steps;
}
visit(visitor: CssASTVisitor, context?: any) {
visitor.visitCssKeyframeDefinition(this, context);
}
}
export class CssBlockDefinitionRuleAST extends CssBlockRuleAST {
public strValue: string;
constructor(type: BlockType, public query: CssToken[], block: CssBlockAST) {
super(type, block);
this.strValue = query.map(token => token.strValue).join("");
var firstCssToken: CssToken = query[0];
this.name = new CssToken(firstCssToken.index, firstCssToken.column, firstCssToken.line,
CssTokenType.Identifier, this.strValue);
}
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); }
}
export class CssMediaQueryRuleAST extends CssBlockDefinitionRuleAST {
constructor(query: CssToken[], block: CssBlockAST) { super(BlockType.MediaQuery, query, block); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssMediaQueryRule(this, context); }
}
export class CssInlineRuleAST extends CssRuleAST {
constructor(public type: BlockType, public value: CssStyleValueAST) { super(); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitInlineCssRule(this, context); }
}
export class CssSelectorRuleAST extends CssBlockRuleAST {
public strValue: string;
constructor(public selectors: CssSelectorAST[], block: CssBlockAST) {
super(BlockType.Selector, block);
this.strValue = selectors.map(selector => selector.strValue).join(",");
}
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelectorRule(this, context); }
}
export class CssDefinitionAST extends CssAST {
constructor(public property: CssToken, public value: CssStyleValueAST) { super(); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssDefinition(this, context); }
}
export class CssSelectorAST extends CssAST {
public strValue;
constructor(public tokens: CssToken[], public isComplex: boolean = false) {
super();
this.strValue = tokens.map(token => token.strValue).join("");
}
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelector(this, context); }
}
export class CssBlockAST extends CssAST {
constructor(public entries: CssAST[]) { super(); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this, context); }
}
export class CssStyleSheetAST extends CssAST {
constructor(public rules: CssAST[]) { super(); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssStyleSheet(this, context); }
}
export class CssParseError extends ParseError {
static create(file: ParseSourceFile, offset: number, line: number, col: number, length: number,
errMsg: string): CssParseError {
var start = new ParseLocation(file, offset, line, col);
var end = new ParseLocation(file, offset, line, col + length);
var span = new ParseSourceSpan(start, end);
return new CssParseError(span, "CSS Parse Error: " + errMsg);
}
constructor(span: ParseSourceSpan, message: string) { super(span, message); }
}
export class CssUnknownTokenListAST extends CssRuleAST {
constructor(public name, public tokens: CssToken[]) { super(); }
visit(visitor: CssASTVisitor, context?: any) { visitor.visitUnkownRule(this, context); }
}

View File

@ -0,0 +1,21 @@
library angular2.src.core.compiler.directive_lifecycle_reflector;
import 'package:angular2/src/core/reflection/reflection.dart';
import 'package:angular2/src/core/metadata/lifecycle_hooks.dart';
const INTERFACES = const {
LifecycleHooks.OnInit: OnInit,
LifecycleHooks.OnDestroy: OnDestroy,
LifecycleHooks.DoCheck: DoCheck,
LifecycleHooks.OnChanges: OnChanges,
LifecycleHooks.AfterContentInit: AfterContentInit,
LifecycleHooks.AfterContentChecked: AfterContentChecked,
LifecycleHooks.AfterViewInit: AfterViewInit,
LifecycleHooks.AfterViewChecked: AfterViewChecked,
};
bool hasLifecycleHook(LifecycleHooks interface, token) {
if (token is! Type) return false;
Type interfaceType = INTERFACES[interface];
return reflector.interfaces(token).contains(interfaceType);
}

View File

@ -0,0 +1,29 @@
import {Type} from 'angular2/src/facade/lang';
import {LifecycleHooks} from 'angular2/src/core/metadata/lifecycle_hooks';
export function hasLifecycleHook(lcInterface: LifecycleHooks, token): boolean {
if (!(token instanceof Type)) return false;
var proto = (<any>token).prototype;
switch (lcInterface) {
case LifecycleHooks.AfterContentInit:
return !!proto.ngAfterContentInit;
case LifecycleHooks.AfterContentChecked:
return !!proto.ngAfterContentChecked;
case LifecycleHooks.AfterViewInit:
return !!proto.ngAfterViewInit;
case LifecycleHooks.AfterViewChecked:
return !!proto.ngAfterViewChecked;
case LifecycleHooks.OnChanges:
return !!proto.ngOnChanges;
case LifecycleHooks.DoCheck:
return !!proto.ngDoCheck;
case LifecycleHooks.OnDestroy:
return !!proto.ngOnDestroy;
case LifecycleHooks.OnInit:
return !!proto.ngOnInit;
default:
return false;
}
}

View File

@ -0,0 +1,166 @@
import {
CompileTypeMetadata,
CompileDirectiveMetadata,
CompileTemplateMetadata,
CompileProviderMetadata,
CompileTokenMetadata
} from './compile_metadata';
import {isPresent, isBlank, isArray} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {XHR} from 'angular2/src/compiler/xhr';
import {UrlResolver} from 'angular2/src/compiler/url_resolver';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {Injectable} from 'angular2/src/core/di';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {
HtmlAstVisitor,
HtmlElementAst,
HtmlTextAst,
HtmlAttrAst,
HtmlAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst,
htmlVisitAll
} from './html_ast';
import {HtmlParser} from './html_parser';
import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
@Injectable()
export class DirectiveNormalizer {
constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
private _htmlParser: HtmlParser) {}
normalizeDirective(directive: CompileDirectiveMetadata): Promise<CompileDirectiveMetadata> {
if (!directive.isComponent) {
// For non components there is nothing to be normalized yet.
return PromiseWrapper.resolve(directive);
}
return this.normalizeTemplate(directive.type, directive.template)
.then((normalizedTemplate: CompileTemplateMetadata) => new CompileDirectiveMetadata({
type: directive.type,
isComponent: directive.isComponent,
selector: directive.selector,
exportAs: directive.exportAs,
changeDetection: directive.changeDetection,
inputs: directive.inputs,
outputs: directive.outputs,
hostListeners: directive.hostListeners,
hostProperties: directive.hostProperties,
hostAttributes: directive.hostAttributes,
lifecycleHooks: directive.lifecycleHooks,
providers: directive.providers,
viewProviders: directive.viewProviders,
queries: directive.queries,
viewQueries: directive.viewQueries,
template: normalizedTemplate
}));
}
normalizeTemplate(directiveType: CompileTypeMetadata,
template: CompileTemplateMetadata): Promise<CompileTemplateMetadata> {
if (isPresent(template.template)) {
return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
directiveType, template, template.template, template.baseUrl));
} else if (isPresent(template.templateUrl)) {
var sourceAbsUrl = this._urlResolver.resolve(template.baseUrl, template.templateUrl);
return this._xhr.get(sourceAbsUrl)
.then(templateContent => this.normalizeLoadedTemplate(directiveType, template,
templateContent, sourceAbsUrl));
} else {
throw new BaseException(`No template specified for component ${directiveType.name}`);
}
}
normalizeLoadedTemplate(directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata,
template: string, templateAbsUrl: string): CompileTemplateMetadata {
var rootNodesAndErrors = this._htmlParser.parse(template, directiveType.name);
if (rootNodesAndErrors.errors.length > 0) {
var errorString = rootNodesAndErrors.errors.join('\n');
throw new BaseException(`Template parse errors:\n${errorString}`);
}
var visitor = new TemplatePreparseVisitor();
htmlVisitAll(visitor, rootNodesAndErrors.rootNodes);
var allStyles = templateMeta.styles.concat(visitor.styles);
var allStyleAbsUrls =
visitor.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(templateAbsUrl, url))
.concat(templateMeta.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(templateMeta.baseUrl, url)));
var allResolvedStyles = allStyles.map(style => {
var styleWithImports = extractStyleUrls(this._urlResolver, templateAbsUrl, style);
styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl));
return styleWithImports.style;
});
var encapsulation = templateMeta.encapsulation;
if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 &&
allStyleAbsUrls.length === 0) {
encapsulation = ViewEncapsulation.None;
}
return new CompileTemplateMetadata({
encapsulation: encapsulation,
template: template,
templateUrl: templateAbsUrl,
styles: allResolvedStyles,
styleUrls: allStyleAbsUrls,
ngContentSelectors: visitor.ngContentSelectors
});
}
}
class TemplatePreparseVisitor implements HtmlAstVisitor {
ngContentSelectors: string[] = [];
styles: string[] = [];
styleUrls: string[] = [];
ngNonBindableStackCount: number = 0;
visitElement(ast: HtmlElementAst, context: any): any {
var preparsedElement = preparseElement(ast);
switch (preparsedElement.type) {
case PreparsedElementType.NG_CONTENT:
if (this.ngNonBindableStackCount === 0) {
this.ngContentSelectors.push(preparsedElement.selectAttr);
}
break;
case PreparsedElementType.STYLE:
var textContent = '';
ast.children.forEach(child => {
if (child instanceof HtmlTextAst) {
textContent += (<HtmlTextAst>child).value;
}
});
this.styles.push(textContent);
break;
case PreparsedElementType.STYLESHEET:
this.styleUrls.push(preparsedElement.hrefAttr);
break;
default:
// DDC reports this as error. See:
// https://github.com/dart-lang/dev_compiler/issues/428
break;
}
if (preparsedElement.nonBindable) {
this.ngNonBindableStackCount++;
}
htmlVisitAll(this, ast.children);
if (preparsedElement.nonBindable) {
this.ngNonBindableStackCount--;
}
return null;
}
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, context: any): any { return null; }
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
}

View File

@ -0,0 +1,169 @@
import {resolveForwardRef, Injectable} from 'angular2/src/core/di';
import {Type, isPresent, isBlank, stringify} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
DirectiveMetadata,
ComponentMetadata,
InputMetadata,
OutputMetadata,
HostBindingMetadata,
HostListenerMetadata,
ContentChildrenMetadata,
ViewChildrenMetadata,
ContentChildMetadata,
ViewChildMetadata
} from 'angular2/src/core/metadata';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
function _isDirectiveMetadata(type: any): boolean {
return type instanceof DirectiveMetadata;
}
/*
* Resolve a `Type` for {@link DirectiveMetadata}.
*
* This interface can be overridden by the application developer to create custom behavior.
*
* See {@link Compiler}
*/
@Injectable()
export class DirectiveResolver {
private _reflector: ReflectorReader;
constructor(_reflector?: ReflectorReader) {
if (isPresent(_reflector)) {
this._reflector = _reflector;
} else {
this._reflector = reflector;
}
}
/**
* Return {@link DirectiveMetadata} for a given `Type`.
*/
resolve(type: Type): DirectiveMetadata {
var typeMetadata = this._reflector.annotations(resolveForwardRef(type));
if (isPresent(typeMetadata)) {
var metadata = typeMetadata.find(_isDirectiveMetadata);
if (isPresent(metadata)) {
var propertyMetadata = this._reflector.propMetadata(type);
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
}
}
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
}
private _mergeWithPropertyMetadata(dm: DirectiveMetadata,
propertyMetadata: {[key: string]: any[]},
directiveType: Type): DirectiveMetadata {
var inputs = [];
var outputs = [];
var host: {[key: string]: string} = {};
var queries: {[key: string]: any} = {};
StringMapWrapper.forEach(propertyMetadata, (metadata: any[], propName: string) => {
metadata.forEach(a => {
if (a instanceof InputMetadata) {
if (isPresent(a.bindingPropertyName)) {
inputs.push(`${propName}: ${a.bindingPropertyName}`);
} else {
inputs.push(propName);
}
}
if (a instanceof OutputMetadata) {
if (isPresent(a.bindingPropertyName)) {
outputs.push(`${propName}: ${a.bindingPropertyName}`);
} else {
outputs.push(propName);
}
}
if (a instanceof HostBindingMetadata) {
if (isPresent(a.hostPropertyName)) {
host[`[${a.hostPropertyName}]`] = propName;
} else {
host[`[${propName}]`] = propName;
}
}
if (a instanceof HostListenerMetadata) {
var args = isPresent(a.args) ? (<any[]>a.args).join(', ') : '';
host[`(${a.eventName})`] = `${propName}(${args})`;
}
if (a instanceof ContentChildrenMetadata) {
queries[propName] = a;
}
if (a instanceof ViewChildrenMetadata) {
queries[propName] = a;
}
if (a instanceof ContentChildMetadata) {
queries[propName] = a;
}
if (a instanceof ViewChildMetadata) {
queries[propName] = a;
}
});
});
return this._merge(dm, inputs, outputs, host, queries, directiveType);
}
private _merge(dm: DirectiveMetadata, inputs: string[], outputs: string[],
host: {[key: string]: string}, queries: {[key: string]: any},
directiveType: Type): DirectiveMetadata {
var mergedInputs = isPresent(dm.inputs) ? ListWrapper.concat(dm.inputs, inputs) : inputs;
var mergedOutputs;
if (isPresent(dm.outputs)) {
dm.outputs.forEach((propName: string) => {
if (ListWrapper.contains(outputs, propName)) {
throw new BaseException(
`Output event '${propName}' defined multiple times in '${stringify(directiveType)}'`);
}
});
mergedOutputs = ListWrapper.concat(dm.outputs, outputs);
} else {
mergedOutputs = outputs;
}
var mergedHost = isPresent(dm.host) ? StringMapWrapper.merge(dm.host, host) : host;
var mergedQueries =
isPresent(dm.queries) ? StringMapWrapper.merge(dm.queries, queries) : queries;
if (dm instanceof ComponentMetadata) {
return new ComponentMetadata({
selector: dm.selector,
inputs: mergedInputs,
outputs: mergedOutputs,
host: mergedHost,
exportAs: dm.exportAs,
moduleId: dm.moduleId,
queries: mergedQueries,
changeDetection: dm.changeDetection,
providers: dm.providers,
viewProviders: dm.viewProviders
});
} else {
return new DirectiveMetadata({
selector: dm.selector,
inputs: mergedInputs,
outputs: mergedOutputs,
host: mergedHost,
exportAs: dm.exportAs,
queries: mergedQueries,
providers: dm.providers
});
}
}
}
export var CODEGEN_DIRECTIVE_RESOLVER = new DirectiveResolver(reflector);

View File

@ -0,0 +1,348 @@
import {ListWrapper} from "angular2/src/facade/collection";
export class AST {
visit(visitor: AstVisitor, context: any = null): any { return null; }
toString(): string { return "AST"; }
}
/**
* Represents a quoted expression of the form:
*
* quote = prefix `:` uninterpretedExpression
* prefix = identifier
* uninterpretedExpression = arbitrary string
*
* A quoted expression is meant to be pre-processed by an AST transformer that
* converts it into another AST that no longer contains quoted expressions.
* It is meant to allow third-party developers to extend Angular template
* expression language. The `uninterpretedExpression` part of the quote is
* therefore not interpreted by the Angular's own expression parser.
*/
export class Quote extends AST {
constructor(public prefix: string, public uninterpretedExpression: string, public location: any) {
super();
}
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitQuote(this, context); }
toString(): string { return "Quote"; }
}
export class EmptyExpr extends AST {
visit(visitor: AstVisitor, context: any = null) {
// do nothing
}
}
export class ImplicitReceiver extends AST {
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitImplicitReceiver(this, context);
}
}
/**
* Multiple expressions separated by a semicolon.
*/
export class Chain extends AST {
constructor(public expressions: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitChain(this, context); }
}
export class Conditional extends AST {
constructor(public condition: AST, public trueExp: AST, public falseExp: AST) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitConditional(this, context);
}
}
export class PropertyRead extends AST {
constructor(public receiver: AST, public name: string) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitPropertyRead(this, context);
}
}
export class PropertyWrite extends AST {
constructor(public receiver: AST, public name: string, public value: AST) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitPropertyWrite(this, context);
}
}
export class SafePropertyRead extends AST {
constructor(public receiver: AST, public name: string) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitSafePropertyRead(this, context);
}
}
export class KeyedRead extends AST {
constructor(public obj: AST, public key: AST) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitKeyedRead(this, context);
}
}
export class KeyedWrite extends AST {
constructor(public obj: AST, public key: AST, public value: AST) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitKeyedWrite(this, context);
}
}
export class BindingPipe extends AST {
constructor(public exp: AST, public name: string, public args: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any { return visitor.visitPipe(this, context); }
}
export class LiteralPrimitive extends AST {
constructor(public value) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitLiteralPrimitive(this, context);
}
}
export class LiteralArray extends AST {
constructor(public expressions: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitLiteralArray(this, context);
}
}
export class LiteralMap extends AST {
constructor(public keys: any[], public values: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitLiteralMap(this, context);
}
}
export class Interpolation extends AST {
constructor(public strings: any[], public expressions: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitInterpolation(this, context);
}
}
export class Binary extends AST {
constructor(public operation: string, public left: AST, public right: AST) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitBinary(this, context);
}
}
export class PrefixNot extends AST {
constructor(public expression: AST) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitPrefixNot(this, context);
}
}
export class MethodCall extends AST {
constructor(public receiver: AST, public name: string, public args: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitMethodCall(this, context);
}
}
export class SafeMethodCall extends AST {
constructor(public receiver: AST, public name: string, public args: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitSafeMethodCall(this, context);
}
}
export class FunctionCall extends AST {
constructor(public target: AST, public args: any[]) { super(); }
visit(visitor: AstVisitor, context: any = null): any {
return visitor.visitFunctionCall(this, context);
}
}
export class ASTWithSource extends AST {
constructor(public ast: AST, public source: string, public location: string) { super(); }
visit(visitor: AstVisitor, context: any = null): any { return this.ast.visit(visitor, context); }
toString(): string { return `${this.source} in ${this.location}`; }
}
export class TemplateBinding {
constructor(public key: string, public keyIsVar: boolean, public name: string,
public expression: ASTWithSource) {}
}
export interface AstVisitor {
visitBinary(ast: Binary, context: any): any;
visitChain(ast: Chain, context: any): any;
visitConditional(ast: Conditional, context: any): any;
visitFunctionCall(ast: FunctionCall, context: any): any;
visitImplicitReceiver(ast: ImplicitReceiver, context: any): any;
visitInterpolation(ast: Interpolation, context: any): any;
visitKeyedRead(ast: KeyedRead, context: any): any;
visitKeyedWrite(ast: KeyedWrite, context: any): any;
visitLiteralArray(ast: LiteralArray, context: any): any;
visitLiteralMap(ast: LiteralMap, context: any): any;
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any;
visitMethodCall(ast: MethodCall, context: any): any;
visitPipe(ast: BindingPipe, context: any): any;
visitPrefixNot(ast: PrefixNot, context: any): any;
visitPropertyRead(ast: PropertyRead, context: any): any;
visitPropertyWrite(ast: PropertyWrite, context: any): any;
visitQuote(ast: Quote, context: any): any;
visitSafeMethodCall(ast: SafeMethodCall, context: any): any;
visitSafePropertyRead(ast: SafePropertyRead, context: any): any;
}
export class RecursiveAstVisitor implements AstVisitor {
visitBinary(ast: Binary, context: any): any {
ast.left.visit(this);
ast.right.visit(this);
return null;
}
visitChain(ast: Chain, context: any): any { return this.visitAll(ast.expressions, context); }
visitConditional(ast: Conditional, context: any): any {
ast.condition.visit(this);
ast.trueExp.visit(this);
ast.falseExp.visit(this);
return null;
}
visitPipe(ast: BindingPipe, context: any): any {
ast.exp.visit(this);
this.visitAll(ast.args, context);
return null;
}
visitFunctionCall(ast: FunctionCall, context: any): any {
ast.target.visit(this);
this.visitAll(ast.args, context);
return null;
}
visitImplicitReceiver(ast: ImplicitReceiver, context: any): any { return null; }
visitInterpolation(ast: Interpolation, context: any): any {
return this.visitAll(ast.expressions, context);
}
visitKeyedRead(ast: KeyedRead, context: any): any {
ast.obj.visit(this);
ast.key.visit(this);
return null;
}
visitKeyedWrite(ast: KeyedWrite, context: any): any {
ast.obj.visit(this);
ast.key.visit(this);
ast.value.visit(this);
return null;
}
visitLiteralArray(ast: LiteralArray, context: any): any {
return this.visitAll(ast.expressions, context);
}
visitLiteralMap(ast: LiteralMap, context: any): any { return this.visitAll(ast.values, context); }
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): any { return null; }
visitMethodCall(ast: MethodCall, context: any): any {
ast.receiver.visit(this);
return this.visitAll(ast.args, context);
}
visitPrefixNot(ast: PrefixNot, context: any): any {
ast.expression.visit(this);
return null;
}
visitPropertyRead(ast: PropertyRead, context: any): any {
ast.receiver.visit(this);
return null;
}
visitPropertyWrite(ast: PropertyWrite, context: any): any {
ast.receiver.visit(this);
ast.value.visit(this);
return null;
}
visitSafePropertyRead(ast: SafePropertyRead, context: any): any {
ast.receiver.visit(this);
return null;
}
visitSafeMethodCall(ast: SafeMethodCall, context: any): any {
ast.receiver.visit(this);
return this.visitAll(ast.args, context);
}
visitAll(asts: AST[], context: any): any {
asts.forEach(ast => ast.visit(this, context));
return null;
}
visitQuote(ast: Quote, context: any): any { return null; }
}
export class AstTransformer implements AstVisitor {
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
visitInterpolation(ast: Interpolation, context: any): AST {
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
}
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST {
return new LiteralPrimitive(ast.value);
}
visitPropertyRead(ast: PropertyRead, context: any): AST {
return new PropertyRead(ast.receiver.visit(this), ast.name);
}
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
return new PropertyWrite(ast.receiver.visit(this), ast.name, ast.value);
}
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
return new SafePropertyRead(ast.receiver.visit(this), ast.name);
}
visitMethodCall(ast: MethodCall, context: any): AST {
return new MethodCall(ast.receiver.visit(this), ast.name, this.visitAll(ast.args));
}
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
return new SafeMethodCall(ast.receiver.visit(this), ast.name, this.visitAll(ast.args));
}
visitFunctionCall(ast: FunctionCall, context: any): AST {
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
}
visitLiteralArray(ast: LiteralArray, context: any): AST {
return new LiteralArray(this.visitAll(ast.expressions));
}
visitLiteralMap(ast: LiteralMap, context: any): AST {
return new LiteralMap(ast.keys, this.visitAll(ast.values));
}
visitBinary(ast: Binary, context: any): AST {
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
}
visitPrefixNot(ast: PrefixNot, context: any): AST {
return new PrefixNot(ast.expression.visit(this));
}
visitConditional(ast: Conditional, context: any): AST {
return new Conditional(ast.condition.visit(this), ast.trueExp.visit(this),
ast.falseExp.visit(this));
}
visitPipe(ast: BindingPipe, context: any): AST {
return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args));
}
visitKeyedRead(ast: KeyedRead, context: any): AST {
return new KeyedRead(ast.obj.visit(this), ast.key.visit(this));
}
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
return new KeyedWrite(ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
}
visitAll(asts: any[]): any[] {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
visitChain(ast: Chain, context: any): AST { return new Chain(this.visitAll(ast.expressions)); }
visitQuote(ast: Quote, context: any): AST {
return new Quote(ast.prefix, ast.uninterpretedExpression, ast.location);
}
}

View File

@ -0,0 +1,473 @@
import {Injectable} from 'angular2/src/core/di/decorators';
import {ListWrapper, SetWrapper} from "angular2/src/facade/collection";
import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from "angular2/src/facade/lang";
import {BaseException} from 'angular2/src/facade/exceptions';
export enum TokenType {
Character,
Identifier,
Keyword,
String,
Operator,
Number
}
@Injectable()
export class Lexer {
tokenize(text: string): any[] {
var scanner = new _Scanner(text);
var tokens = [];
var token = scanner.scanToken();
while (token != null) {
tokens.push(token);
token = scanner.scanToken();
}
return tokens;
}
}
export class Token {
constructor(public index: number, public type: TokenType, public numValue: number,
public strValue: string) {}
isCharacter(code: number): boolean {
return (this.type == TokenType.Character && this.numValue == code);
}
isNumber(): boolean { return (this.type == TokenType.Number); }
isString(): boolean { return (this.type == TokenType.String); }
isOperator(operater: string): boolean {
return (this.type == TokenType.Operator && this.strValue == operater);
}
isIdentifier(): boolean { return (this.type == TokenType.Identifier); }
isKeyword(): boolean { return (this.type == TokenType.Keyword); }
isKeywordDeprecatedVar(): boolean {
return (this.type == TokenType.Keyword && this.strValue == "var");
}
isKeywordLet(): boolean { return (this.type == TokenType.Keyword && this.strValue == "let"); }
isKeywordNull(): boolean { return (this.type == TokenType.Keyword && this.strValue == "null"); }
isKeywordUndefined(): boolean {
return (this.type == TokenType.Keyword && this.strValue == "undefined");
}
isKeywordTrue(): boolean { return (this.type == TokenType.Keyword && this.strValue == "true"); }
isKeywordFalse(): boolean { return (this.type == TokenType.Keyword && this.strValue == "false"); }
toNumber(): number {
// -1 instead of NULL ok?
return (this.type == TokenType.Number) ? this.numValue : -1;
}
toString(): string {
switch (this.type) {
case TokenType.Character:
case TokenType.Identifier:
case TokenType.Keyword:
case TokenType.Operator:
case TokenType.String:
return this.strValue;
case TokenType.Number:
return this.numValue.toString();
default:
return null;
}
}
}
function newCharacterToken(index: number, code: number): Token {
return new Token(index, TokenType.Character, code, StringWrapper.fromCharCode(code));
}
function newIdentifierToken(index: number, text: string): Token {
return new Token(index, TokenType.Identifier, 0, text);
}
function newKeywordToken(index: number, text: string): Token {
return new Token(index, TokenType.Keyword, 0, text);
}
function newOperatorToken(index: number, text: string): Token {
return new Token(index, TokenType.Operator, 0, text);
}
function newStringToken(index: number, text: string): Token {
return new Token(index, TokenType.String, 0, text);
}
function newNumberToken(index: number, n: number): Token {
return new Token(index, TokenType.Number, n, "");
}
export var EOF: Token = new Token(-1, TokenType.Character, 0, "");
export const $EOF = /*@ts2dart_const*/ 0;
export const $TAB = /*@ts2dart_const*/ 9;
export const $LF = /*@ts2dart_const*/ 10;
export const $VTAB = /*@ts2dart_const*/ 11;
export const $FF = /*@ts2dart_const*/ 12;
export const $CR = /*@ts2dart_const*/ 13;
export const $SPACE = /*@ts2dart_const*/ 32;
export const $BANG = /*@ts2dart_const*/ 33;
export const $DQ = /*@ts2dart_const*/ 34;
export const $HASH = /*@ts2dart_const*/ 35;
export const $$ = /*@ts2dart_const*/ 36;
export const $PERCENT = /*@ts2dart_const*/ 37;
export const $AMPERSAND = /*@ts2dart_const*/ 38;
export const $SQ = /*@ts2dart_const*/ 39;
export const $LPAREN = /*@ts2dart_const*/ 40;
export const $RPAREN = /*@ts2dart_const*/ 41;
export const $STAR = /*@ts2dart_const*/ 42;
export const $PLUS = /*@ts2dart_const*/ 43;
export const $COMMA = /*@ts2dart_const*/ 44;
export const $MINUS = /*@ts2dart_const*/ 45;
export const $PERIOD = /*@ts2dart_const*/ 46;
export const $SLASH = /*@ts2dart_const*/ 47;
export const $COLON = /*@ts2dart_const*/ 58;
export const $SEMICOLON = /*@ts2dart_const*/ 59;
export const $LT = /*@ts2dart_const*/ 60;
export const $EQ = /*@ts2dart_const*/ 61;
export const $GT = /*@ts2dart_const*/ 62;
export const $QUESTION = /*@ts2dart_const*/ 63;
const $0 = /*@ts2dart_const*/ 48;
const $9 = /*@ts2dart_const*/ 57;
const $A = /*@ts2dart_const*/ 65, $E = /*@ts2dart_const*/ 69, $Z = /*@ts2dart_const*/ 90;
export const $LBRACKET = /*@ts2dart_const*/ 91;
export const $BACKSLASH = /*@ts2dart_const*/ 92;
export const $RBRACKET = /*@ts2dart_const*/ 93;
const $CARET = /*@ts2dart_const*/ 94;
const $_ = /*@ts2dart_const*/ 95;
export const $BT = /*@ts2dart_const*/ 96;
const $a = /*@ts2dart_const*/ 97, $e = /*@ts2dart_const*/ 101, $f = /*@ts2dart_const*/ 102;
const $n = /*@ts2dart_const*/ 110, $r = /*@ts2dart_const*/ 114, $t = /*@ts2dart_const*/ 116,
$u = /*@ts2dart_const*/ 117, $v = /*@ts2dart_const*/ 118, $z = /*@ts2dart_const*/ 122;
export const $LBRACE = /*@ts2dart_const*/ 123;
export const $BAR = /*@ts2dart_const*/ 124;
export const $RBRACE = /*@ts2dart_const*/ 125;
const $NBSP = /*@ts2dart_const*/ 160;
export class ScannerError extends BaseException {
constructor(public message) { super(); }
toString(): string { return this.message; }
}
class _Scanner {
length: number;
peek: number = 0;
index: number = -1;
constructor(public input: string) {
this.length = input.length;
this.advance();
}
advance() {
this.peek =
++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
}
scanToken(): Token {
var input = this.input, length = this.length, peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
} else {
peek = StringWrapper.charCodeAt(input, index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier();
if (isDigit(peek)) return this.scanNumber(index);
var start: number = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $QUESTION:
return this.scanComplexOperator(start, '?', $PERIOD, '.');
case $LT:
case $GT:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=');
case $BANG:
case $EQ:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=', $EQ,
'=');
case $AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
case $BAR:
return this.scanComplexOperator(start, '|', $BAR, '|');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
return null;
}
scanCharacter(start: number, code: number): Token {
this.advance();
return newCharacterToken(start, code);
}
scanOperator(start: number, str: string): Token {
this.advance();
return newOperatorToken(start, str);
}
/**
* Tokenize a 2/3 char long operator
*
* @param start start index in the expression
* @param one first symbol (always part of the operator)
* @param twoCode code point for the second symbol
* @param two second symbol (part of the operator when the second code point matches)
* @param threeCode code point for the third symbol
* @param three third symbol (part of the operator when provided and matches source expression)
* @returns {Token}
*/
scanComplexOperator(start: number, one: string, twoCode: number, two: string, threeCode?: number,
three?: string): Token {
this.advance();
var str: string = one;
if (this.peek == twoCode) {
this.advance();
str += two;
}
if (isPresent(threeCode) && this.peek == threeCode) {
this.advance();
str += three;
}
return newOperatorToken(start, str);
}
scanIdentifier(): Token {
var start: number = this.index;
this.advance();
while (isIdentifierPart(this.peek)) this.advance();
var str: string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) {
return newKeywordToken(start, str);
} else {
return newIdentifierToken(start, str);
}
}
scanNumber(start: number): Token {
var simple: boolean = (this.index === start);
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
// Do nothing.
} else if (this.peek == $PERIOD) {
simple = false;
} else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek)) this.advance();
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
simple = false;
} else {
break;
}
this.advance();
}
var str: string = this.input.substring(start, this.index);
// TODO
var value: number =
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value);
}
scanString(): Token {
var start: number = this.index;
var quote: number = this.peek;
this.advance(); // Skip initial quote.
var buffer: StringJoiner;
var marker: number = this.index;
var input: string = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index));
this.advance();
var unescapedCode: number;
if (this.peek == $u) {
// 4 character hex code for unicode character.
var hex: string = input.substring(this.index + 1, this.index + 5);
try {
unescapedCode = NumberWrapper.parseInt(hex, 16);
} catch (e) {
this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (var i: number = 0; i < 5; i++) {
this.advance();
}
} else {
unescapedCode = unescape(this.peek);
this.advance();
}
buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index;
} else if (this.peek == $EOF) {
this.error('Unterminated quote', 0);
} else {
this.advance();
}
}
var last: string = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
// Compute the unescaped string value.
var unescaped: string = last;
if (buffer != null) {
buffer.add(last);
unescaped = buffer.toString();
}
return newStringToken(start, unescaped);
}
error(message: string, offset: number) {
var position: number = this.index + offset;
throw new ScannerError(
`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || (code == $_) || (code == $$);
}
export function isIdentifier(input: string): boolean {
if (input.length == 0) return false;
var scanner = new _Scanner(input);
if (!isIdentifierStart(scanner.peek)) return false;
scanner.advance();
while (scanner.peek !== $EOF) {
if (!isIdentifierPart(scanner.peek)) return false;
scanner.advance();
}
return true;
}
function isIdentifierPart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) ||
(code == $_) || (code == $$);
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
}
function isExponentStart(code: number): boolean {
return code == $e || code == $E;
}
function isExponentSign(code: number): boolean {
return code == $MINUS || code == $PLUS;
}
export function isQuote(code: number): boolean {
return code === $SQ || code === $DQ || code === $BT;
}
function unescape(code: number): number {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
default:
return code;
}
}
var OPERATORS = SetWrapper.createFromList([
'+',
'-',
'*',
'/',
'%',
'^',
'=',
'==',
'!=',
'===',
'!==',
'<',
'>',
'<=',
'>=',
'&&',
'||',
'&',
'|',
'!',
'?',
'#',
'?.'
]);
var KEYWORDS =
SetWrapper.createFromList(['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else']);

View File

@ -0,0 +1,732 @@
import {Injectable} from 'angular2/src/core/di/decorators';
import {isBlank, isPresent, StringWrapper} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection';
import {
Lexer,
EOF,
isIdentifier,
isQuote,
Token,
$PERIOD,
$COLON,
$SEMICOLON,
$LBRACKET,
$RBRACKET,
$COMMA,
$LBRACE,
$RBRACE,
$LPAREN,
$RPAREN,
$SLASH
} from './lexer';
import {
AST,
EmptyExpr,
ImplicitReceiver,
PropertyRead,
PropertyWrite,
SafePropertyRead,
LiteralPrimitive,
Binary,
PrefixNot,
Conditional,
BindingPipe,
Chain,
KeyedRead,
KeyedWrite,
LiteralArray,
LiteralMap,
Interpolation,
MethodCall,
SafeMethodCall,
FunctionCall,
TemplateBinding,
ASTWithSource,
AstVisitor,
Quote
} from './ast';
var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = /\{\{([\s\S]*?)\}\}/g;
class ParseException extends BaseException {
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
super(`Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`);
}
}
export class SplitInterpolation {
constructor(public strings: string[], public expressions: string[]) {}
}
export class TemplateBindingParseResult {
constructor(public templateBindings: TemplateBinding[], public warnings: string[]) {}
}
@Injectable()
export class Parser {
constructor(/** @internal */
public _lexer: Lexer) {}
parseAction(input: string, location: any): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(this._stripComments(input));
var ast = new _ParseAST(input, location, tokens, true).parseChain();
return new ASTWithSource(ast, input, location);
}
parseBinding(input: string, location: any): ASTWithSource {
var ast = this._parseBindingAst(input, location);
return new ASTWithSource(ast, input, location);
}
parseSimpleBinding(input: string, location: string): ASTWithSource {
var ast = this._parseBindingAst(input, location);
if (!SimpleExpressionChecker.check(ast)) {
throw new ParseException(
'Host binding expression can only contain field access and constants', input, location);
}
return new ASTWithSource(ast, input, location);
}
private _parseBindingAst(input: string, location: string): AST {
// Quotes expressions use 3rd-party expression language. We don't want to use
// our lexer or parser for that, so we check for that ahead of time.
var quote = this._parseQuote(input, location);
if (isPresent(quote)) {
return quote;
}
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(this._stripComments(input));
return new _ParseAST(input, location, tokens, false).parseChain();
}
private _parseQuote(input: string, location: any): AST {
if (isBlank(input)) return null;
var prefixSeparatorIndex = input.indexOf(':');
if (prefixSeparatorIndex == -1) return null;
var prefix = input.substring(0, prefixSeparatorIndex).trim();
if (!isIdentifier(prefix)) return null;
var uninterpretedExpression = input.substring(prefixSeparatorIndex + 1);
return new Quote(prefix, uninterpretedExpression, location);
}
parseTemplateBindings(input: string, location: any): TemplateBindingParseResult {
var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, false).parseTemplateBindings();
}
parseInterpolation(input: string, location: any): ASTWithSource {
let split = this.splitInterpolation(input, location);
if (split == null) return null;
let expressions = [];
for (let i = 0; i < split.expressions.length; ++i) {
var tokens = this._lexer.tokenize(this._stripComments(split.expressions[i]));
var ast = new _ParseAST(input, location, tokens, false).parseChain();
expressions.push(ast);
}
return new ASTWithSource(new Interpolation(split.strings, expressions), input, location);
}
splitInterpolation(input: string, location: string): SplitInterpolation {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length <= 1) {
return null;
}
var strings = [];
var expressions = [];
for (var i = 0; i < parts.length; i++) {
var part: string = parts[i];
if (i % 2 === 0) {
// fixed string
strings.push(part);
} else if (part.trim().length > 0) {
expressions.push(part);
} else {
throw new ParseException('Blank expressions are not allowed in interpolated strings', input,
`at column ${this._findInterpolationErrorColumn(parts, i)} in`,
location);
}
}
return new SplitInterpolation(strings, expressions);
}
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
return new ASTWithSource(new LiteralPrimitive(input), input, location);
}
private _stripComments(input: string): string {
let i = this._commentStart(input);
return isPresent(i) ? input.substring(0, i).trim() : input;
}
private _commentStart(input: string): number {
var outerQuote = null;
for (var i = 0; i < input.length - 1; i++) {
let char = StringWrapper.charCodeAt(input, i);
let nextChar = StringWrapper.charCodeAt(input, i + 1);
if (char === $SLASH && nextChar == $SLASH && isBlank(outerQuote)) return i;
if (outerQuote === char) {
outerQuote = null;
} else if (isBlank(outerQuote) && isQuote(char)) {
outerQuote = char;
}
}
return null;
}
private _checkNoInterpolation(input: string, location: any): void {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length > 1) {
throw new ParseException('Got interpolation ({{}}) where expression was expected', input,
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`,
location);
}
}
private _findInterpolationErrorColumn(parts: string[], partInErrIdx: number): number {
var errLocation = '';
for (var j = 0; j < partInErrIdx; j++) {
errLocation += j % 2 === 0 ? parts[j] : `{{${parts[j]}}}`;
}
return errLocation.length;
}
}
export class _ParseAST {
index: number = 0;
constructor(public input: string, public location: any, public tokens: any[],
public parseAction: boolean) {}
peek(offset: number): Token {
var i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF;
}
get next(): Token { return this.peek(0); }
get inputIndex(): number {
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
}
advance() { this.index++; }
optionalCharacter(code: number): boolean {
if (this.next.isCharacter(code)) {
this.advance();
return true;
} else {
return false;
}
}
peekKeywordLet(): boolean { return this.next.isKeywordLet(); }
peekDeprecatedKeywordVar(): boolean { return this.next.isKeywordDeprecatedVar(); }
peekDeprecatedOperatorHash(): boolean { return this.next.isOperator('#'); }
expectCharacter(code: number) {
if (this.optionalCharacter(code)) return;
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
}
optionalOperator(op: string): boolean {
if (this.next.isOperator(op)) {
this.advance();
return true;
} else {
return false;
}
}
expectOperator(operator: string) {
if (this.optionalOperator(operator)) return;
this.error(`Missing expected operator ${operator}`);
}
expectIdentifierOrKeyword(): string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) {
this.error(`Unexpected token ${n}, expected identifier or keyword`);
}
this.advance();
return n.toString();
}
expectIdentifierOrKeywordOrString(): string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
this.error(`Unexpected token ${n}, expected identifier, keyword, or string`);
}
this.advance();
return n.toString();
}
parseChain(): AST {
var exprs = [];
while (this.index < this.tokens.length) {
var expr = this.parsePipe();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression");
}
while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
} else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`);
}
}
if (exprs.length == 0) return new EmptyExpr();
if (exprs.length == 1) return exprs[0];
return new Chain(exprs);
}
parsePipe(): AST {
var result = this.parseExpression();
if (this.optionalOperator("|")) {
if (this.parseAction) {
this.error("Cannot have a pipe in an action expression");
}
do {
var name = this.expectIdentifierOrKeyword();
var args = [];
while (this.optionalCharacter($COLON)) {
args.push(this.parseExpression());
}
result = new BindingPipe(result, name, args);
} while (this.optionalOperator("|"));
}
return result;
}
parseExpression(): AST { return this.parseConditional(); }
parseConditional(): AST {
var start = this.inputIndex;
var result = this.parseLogicalOr();
if (this.optionalOperator('?')) {
var yes = this.parsePipe();
if (!this.optionalCharacter($COLON)) {
var end = this.inputIndex;
var expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
}
var no = this.parsePipe();
return new Conditional(result, yes, no);
} else {
return result;
}
}
parseLogicalOr(): AST {
// '||'
var result = this.parseLogicalAnd();
while (this.optionalOperator('||')) {
result = new Binary('||', result, this.parseLogicalAnd());
}
return result;
}
parseLogicalAnd(): AST {
// '&&'
var result = this.parseEquality();
while (this.optionalOperator('&&')) {
result = new Binary('&&', result, this.parseEquality());
}
return result;
}
parseEquality(): AST {
// '==','!=','===','!=='
var result = this.parseRelational();
while (true) {
if (this.optionalOperator('==')) {
result = new Binary('==', result, this.parseRelational());
} else if (this.optionalOperator('===')) {
result = new Binary('===', result, this.parseRelational());
} else if (this.optionalOperator('!=')) {
result = new Binary('!=', result, this.parseRelational());
} else if (this.optionalOperator('!==')) {
result = new Binary('!==', result, this.parseRelational());
} else {
return result;
}
}
}
parseRelational(): AST {
// '<', '>', '<=', '>='
var result = this.parseAdditive();
while (true) {
if (this.optionalOperator('<')) {
result = new Binary('<', result, this.parseAdditive());
} else if (this.optionalOperator('>')) {
result = new Binary('>', result, this.parseAdditive());
} else if (this.optionalOperator('<=')) {
result = new Binary('<=', result, this.parseAdditive());
} else if (this.optionalOperator('>=')) {
result = new Binary('>=', result, this.parseAdditive());
} else {
return result;
}
}
}
parseAdditive(): AST {
// '+', '-'
var result = this.parseMultiplicative();
while (true) {
if (this.optionalOperator('+')) {
result = new Binary('+', result, this.parseMultiplicative());
} else if (this.optionalOperator('-')) {
result = new Binary('-', result, this.parseMultiplicative());
} else {
return result;
}
}
}
parseMultiplicative(): AST {
// '*', '%', '/'
var result = this.parsePrefix();
while (true) {
if (this.optionalOperator('*')) {
result = new Binary('*', result, this.parsePrefix());
} else if (this.optionalOperator('%')) {
result = new Binary('%', result, this.parsePrefix());
} else if (this.optionalOperator('/')) {
result = new Binary('/', result, this.parsePrefix());
} else {
return result;
}
}
}
parsePrefix(): AST {
if (this.optionalOperator('+')) {
return this.parsePrefix();
} else if (this.optionalOperator('-')) {
return new Binary('-', new LiteralPrimitive(0), this.parsePrefix());
} else if (this.optionalOperator('!')) {
return new PrefixNot(this.parsePrefix());
} else {
return this.parseCallChain();
}
}
parseCallChain(): AST {
var result = this.parsePrimary();
while (true) {
if (this.optionalCharacter($PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, false);
} else if (this.optionalOperator('?.')) {
result = this.parseAccessMemberOrMethodCall(result, true);
} else if (this.optionalCharacter($LBRACKET)) {
var key = this.parsePipe();
this.expectCharacter($RBRACKET);
if (this.optionalOperator("=")) {
var value = this.parseConditional();
result = new KeyedWrite(result, key, value);
} else {
result = new KeyedRead(result, key);
}
} else if (this.optionalCharacter($LPAREN)) {
var args = this.parseCallArguments();
this.expectCharacter($RPAREN);
result = new FunctionCall(result, args);
} else {
return result;
}
}
}
parsePrimary(): AST {
if (this.optionalCharacter($LPAREN)) {
let result = this.parsePipe();
this.expectCharacter($RPAREN);
return result;
} else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) {
this.advance();
return new LiteralPrimitive(null);
} else if (this.next.isKeywordTrue()) {
this.advance();
return new LiteralPrimitive(true);
} else if (this.next.isKeywordFalse()) {
this.advance();
return new LiteralPrimitive(false);
} else if (this.optionalCharacter($LBRACKET)) {
var elements = this.parseExpressionList($RBRACKET);
this.expectCharacter($RBRACKET);
return new LiteralArray(elements);
} else if (this.next.isCharacter($LBRACE)) {
return this.parseLiteralMap();
} else if (this.next.isIdentifier()) {
return this.parseAccessMemberOrMethodCall(_implicitReceiver, false);
} else if (this.next.isNumber()) {
var value = this.next.toNumber();
this.advance();
return new LiteralPrimitive(value);
} else if (this.next.isString()) {
var literalValue = this.next.toString();
this.advance();
return new LiteralPrimitive(literalValue);
} else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`);
} else {
this.error(`Unexpected token ${this.next}`);
}
// error() throws, so we don't reach here.
throw new BaseException("Fell through all cases in parsePrimary");
}
parseExpressionList(terminator: number): any[] {
var result = [];
if (!this.next.isCharacter(terminator)) {
do {
result.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
}
return result;
}
parseLiteralMap(): LiteralMap {
var keys = [];
var values = [];
this.expectCharacter($LBRACE);
if (!this.optionalCharacter($RBRACE)) {
do {
var key = this.expectIdentifierOrKeywordOrString();
keys.push(key);
this.expectCharacter($COLON);
values.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
this.expectCharacter($RBRACE);
}
return new LiteralMap(keys, values);
}
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
let id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) {
let args = this.parseCallArguments();
this.expectCharacter($RPAREN);
return isSafe ? new SafeMethodCall(receiver, id, args) : new MethodCall(receiver, id, args);
} else {
if (isSafe) {
if (this.optionalOperator("=")) {
this.error("The '?.' operator cannot be used in the assignment");
} else {
return new SafePropertyRead(receiver, id);
}
} else {
if (this.optionalOperator("=")) {
if (!this.parseAction) {
this.error("Bindings cannot contain assignments");
}
let value = this.parseConditional();
return new PropertyWrite(receiver, id, value);
} else {
return new PropertyRead(receiver, id);
}
}
}
return null;
}
parseCallArguments(): BindingPipe[] {
if (this.next.isCharacter($RPAREN)) return [];
var positionals = [];
do {
positionals.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
return positionals;
}
parseBlockContent(): AST {
if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression");
}
var exprs = [];
while (this.index < this.tokens.length && !this.next.isCharacter($RBRACE)) {
var expr = this.parseExpression();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
}
}
if (exprs.length == 0) return new EmptyExpr();
if (exprs.length == 1) return exprs[0];
return new Chain(exprs);
}
/**
* An identifier, a keyword, a string with an optional `-` inbetween.
*/
expectTemplateBindingKey(): string {
var result = '';
var operatorFound = false;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.optionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return result.toString();
}
parseTemplateBindings(): TemplateBindingParseResult {
var bindings: TemplateBinding[] = [];
var prefix = null;
var warnings: string[] = [];
while (this.index < this.tokens.length) {
var keyIsVar: boolean = this.peekKeywordLet();
if (!keyIsVar && this.peekDeprecatedKeywordVar()) {
keyIsVar = true;
warnings.push(`"var" inside of expressions is deprecated. Use "let" instead!`);
}
if (!keyIsVar && this.peekDeprecatedOperatorHash()) {
keyIsVar = true;
warnings.push(`"#" inside of expressions is deprecated. Use "let" instead!`);
}
if (keyIsVar) {
this.advance();
}
var key = this.expectTemplateBindingKey();
if (!keyIsVar) {
if (prefix == null) {
prefix = key;
} else {
key = prefix + key[0].toUpperCase() + key.substring(1);
}
}
this.optionalCharacter($COLON);
var name = null;
var expression = null;
if (keyIsVar) {
if (this.optionalOperator("=")) {
name = this.expectTemplateBindingKey();
} else {
name = '\$implicit';
}
} else if (this.next !== EOF && !this.peekKeywordLet() && !this.peekDeprecatedKeywordVar() &&
!this.peekDeprecatedOperatorHash()) {
var start = this.inputIndex;
var ast = this.parsePipe();
var source = this.input.substring(start, this.inputIndex);
expression = new ASTWithSource(ast, source, this.location);
}
bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
if (!this.optionalCharacter($SEMICOLON)) {
this.optionalCharacter($COMMA);
}
}
return new TemplateBindingParseResult(bindings, warnings);
}
error(message: string, index: number = null) {
if (isBlank(index)) index = this.index;
var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
`at the end of the expression`;
throw new ParseException(message, this.input, location, this.location);
}
}
class SimpleExpressionChecker implements AstVisitor {
static check(ast: AST): boolean {
var s = new SimpleExpressionChecker();
ast.visit(s);
return s.simple;
}
simple = true;
visitImplicitReceiver(ast: ImplicitReceiver, context: any) {}
visitInterpolation(ast: Interpolation, context: any) { this.simple = false; }
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {}
visitPropertyRead(ast: PropertyRead, context: any) {}
visitPropertyWrite(ast: PropertyWrite, context: any) { this.simple = false; }
visitSafePropertyRead(ast: SafePropertyRead, context: any) { this.simple = false; }
visitMethodCall(ast: MethodCall, context: any) { this.simple = false; }
visitSafeMethodCall(ast: SafeMethodCall, context: any) { this.simple = false; }
visitFunctionCall(ast: FunctionCall, context: any) { this.simple = false; }
visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); }
visitBinary(ast: Binary, context: any) { this.simple = false; }
visitPrefixNot(ast: PrefixNot, context: any) { this.simple = false; }
visitConditional(ast: Conditional, context: any) { this.simple = false; }
visitPipe(ast: BindingPipe, context: any) { this.simple = false; }
visitKeyedRead(ast: KeyedRead, context: any) { this.simple = false; }
visitKeyedWrite(ast: KeyedWrite, context: any) { this.simple = false; }
visitAll(asts: any[]): any[] {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
visitChain(ast: Chain, context: any) { this.simple = false; }
visitQuote(ast: Quote, context: any) { this.simple = false; }
}

View File

@ -0,0 +1,68 @@
import {isPresent} from 'angular2/src/facade/lang';
import {ParseSourceSpan} from './parse_util';
export interface HtmlAst {
sourceSpan: ParseSourceSpan;
visit(visitor: HtmlAstVisitor, context: any): any;
}
export class HtmlTextAst implements HtmlAst {
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitText(this, context); }
}
export class HtmlExpansionAst implements HtmlAst {
constructor(public switchValue: string, public type: string, public cases: HtmlExpansionCaseAst[],
public sourceSpan: ParseSourceSpan, public switchValueSourceSpan: ParseSourceSpan) {}
visit(visitor: HtmlAstVisitor, context: any): any {
return visitor.visitExpansion(this, context);
}
}
export class HtmlExpansionCaseAst implements HtmlAst {
constructor(public value: string, public expression: HtmlAst[],
public sourceSpan: ParseSourceSpan, public valueSourceSpan: ParseSourceSpan,
public expSourceSpan: ParseSourceSpan) {}
visit(visitor: HtmlAstVisitor, context: any): any {
return visitor.visitExpansionCase(this, context);
}
}
export class HtmlAttrAst implements HtmlAst {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
}
export class HtmlElementAst implements HtmlAst {
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[],
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
public endSourceSpan: ParseSourceSpan) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); }
}
export class HtmlCommentAst implements HtmlAst {
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitComment(this, context); }
}
export interface HtmlAstVisitor {
visitElement(ast: HtmlElementAst, context: any): any;
visitAttr(ast: HtmlAttrAst, context: any): any;
visitText(ast: HtmlTextAst, context: any): any;
visitComment(ast: HtmlCommentAst, context: any): any;
visitExpansion(ast: HtmlExpansionAst, context: any): any;
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any;
}
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
var result = [];
asts.forEach(ast => {
var astResult = ast.visit(visitor, context);
if (isPresent(astResult)) {
result.push(astResult);
}
});
return result;
}

View File

@ -0,0 +1,728 @@
import {
StringWrapper,
NumberWrapper,
isPresent,
isBlank,
serializeEnum
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ParseLocation, ParseError, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {getHtmlTagDefinition, HtmlTagContentType, NAMED_ENTITIES} from './html_tags';
export enum HtmlTokenType {
TAG_OPEN_START,
TAG_OPEN_END,
TAG_OPEN_END_VOID,
TAG_CLOSE,
TEXT,
ESCAPABLE_RAW_TEXT,
RAW_TEXT,
COMMENT_START,
COMMENT_END,
CDATA_START,
CDATA_END,
ATTR_NAME,
ATTR_VALUE,
DOC_TYPE,
EXPANSION_FORM_START,
EXPANSION_CASE_VALUE,
EXPANSION_CASE_EXP_START,
EXPANSION_CASE_EXP_END,
EXPANSION_FORM_END,
EOF
}
export class HtmlToken {
constructor(public type: HtmlTokenType, public parts: string[],
public sourceSpan: ParseSourceSpan) {}
}
export class HtmlTokenError extends ParseError {
constructor(errorMsg: string, public tokenType: HtmlTokenType, span: ParseSourceSpan) {
super(span, errorMsg);
}
}
export class HtmlTokenizeResult {
constructor(public tokens: HtmlToken[], public errors: HtmlTokenError[]) {}
}
export function tokenizeHtml(sourceContent: string, sourceUrl: string,
tokenizeExpansionForms: boolean = false): HtmlTokenizeResult {
return new _HtmlTokenizer(new ParseSourceFile(sourceContent, sourceUrl), tokenizeExpansionForms)
.tokenize();
}
const $EOF = 0;
const $TAB = 9;
const $LF = 10;
const $FF = 12;
const $CR = 13;
const $SPACE = 32;
const $BANG = 33;
const $DQ = 34;
const $HASH = 35;
const $$ = 36;
const $AMPERSAND = 38;
const $SQ = 39;
const $MINUS = 45;
const $SLASH = 47;
const $0 = 48;
const $SEMICOLON = 59;
const $9 = 57;
const $COLON = 58;
const $LT = 60;
const $EQ = 61;
const $GT = 62;
const $QUESTION = 63;
const $LBRACKET = 91;
const $RBRACKET = 93;
const $LBRACE = 123;
const $RBRACE = 125;
const $COMMA = 44;
const $A = 65;
const $F = 70;
const $X = 88;
const $Z = 90;
const $a = 97;
const $f = 102;
const $z = 122;
const $x = 120;
const $NBSP = 160;
var CR_OR_CRLF_REGEXP = /\r\n?/g;
function unexpectedCharacterErrorMsg(charCode: number): string {
var char = charCode === $EOF ? 'EOF' : StringWrapper.fromCharCode(charCode);
return `Unexpected character "${char}"`;
}
function unknownEntityErrorMsg(entitySrc: string): string {
return `Unknown entity "${entitySrc}" - use the "&#<decimal>;" or "&#x<hex>;" syntax`;
}
class ControlFlowError {
constructor(public error: HtmlTokenError) {}
}
// See http://www.w3.org/TR/html51/syntax.html#writing
class _HtmlTokenizer {
private input: string;
private length: number;
// Note: this is always lowercase!
private peek: number = -1;
private nextPeek: number = -1;
private index: number = -1;
private line: number = 0;
private column: number = -1;
private currentTokenStart: ParseLocation;
private currentTokenType: HtmlTokenType;
private expansionCaseStack = [];
tokens: HtmlToken[] = [];
errors: HtmlTokenError[] = [];
constructor(private file: ParseSourceFile, private tokenizeExpansionForms: boolean) {
this.input = file.content;
this.length = file.content.length;
this._advance();
}
private _processCarriageReturns(content: string): string {
// http://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
// In order to keep the original position in the source, we can not
// pre-process it.
// Instead CRs are processed right before instantiating the tokens.
return StringWrapper.replaceAll(content, CR_OR_CRLF_REGEXP, '\n');
}
tokenize(): HtmlTokenizeResult {
while (this.peek !== $EOF) {
var start = this._getLocation();
try {
if (this._attemptCharCode($LT)) {
if (this._attemptCharCode($BANG)) {
if (this._attemptCharCode($LBRACKET)) {
this._consumeCdata(start);
} else if (this._attemptCharCode($MINUS)) {
this._consumeComment(start);
} else {
this._consumeDocType(start);
}
} else if (this._attemptCharCode($SLASH)) {
this._consumeTagClose(start);
} else {
this._consumeTagOpen(start);
}
} else if (isSpecialFormStart(this.peek, this.nextPeek) && this.tokenizeExpansionForms) {
this._consumeExpansionFormStart();
} else if (this.peek === $EQ && this.tokenizeExpansionForms) {
this._consumeExpansionCaseStart();
} else if (this.peek === $RBRACE && this.isInExpansionCase() &&
this.tokenizeExpansionForms) {
this._consumeExpansionCaseEnd();
} else if (this.peek === $RBRACE && this.isInExpansionForm() &&
this.tokenizeExpansionForms) {
this._consumeExpansionFormEnd();
} else {
this._consumeText();
}
} catch (e) {
if (e instanceof ControlFlowError) {
this.errors.push(e.error);
} else {
throw e;
}
}
}
this._beginToken(HtmlTokenType.EOF);
this._endToken([]);
return new HtmlTokenizeResult(mergeTextTokens(this.tokens), this.errors);
}
private _getLocation(): ParseLocation {
return new ParseLocation(this.file, this.index, this.line, this.column);
}
private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan {
if (isBlank(start)) {
start = this._getLocation();
}
if (isBlank(end)) {
end = this._getLocation();
}
return new ParseSourceSpan(start, end);
}
private _beginToken(type: HtmlTokenType, start: ParseLocation = null) {
if (isBlank(start)) {
start = this._getLocation();
}
this.currentTokenStart = start;
this.currentTokenType = type;
}
private _endToken(parts: string[], end: ParseLocation = null): HtmlToken {
if (isBlank(end)) {
end = this._getLocation();
}
var token = new HtmlToken(this.currentTokenType, parts,
new ParseSourceSpan(this.currentTokenStart, end));
this.tokens.push(token);
this.currentTokenStart = null;
this.currentTokenType = null;
return token;
}
private _createError(msg: string, span: ParseSourceSpan): ControlFlowError {
var error = new HtmlTokenError(msg, this.currentTokenType, span);
this.currentTokenStart = null;
this.currentTokenType = null;
return new ControlFlowError(error);
}
private _advance() {
if (this.index >= this.length) {
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getSpan());
}
if (this.peek === $LF) {
this.line++;
this.column = 0;
} else if (this.peek !== $LF && this.peek !== $CR) {
this.column++;
}
this.index++;
this.peek = this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
this.nextPeek =
this.index + 1 >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index + 1);
}
private _attemptCharCode(charCode: number): boolean {
if (this.peek === charCode) {
this._advance();
return true;
}
return false;
}
private _attemptCharCodeCaseInsensitive(charCode: number): boolean {
if (compareCharCodeCaseInsensitive(this.peek, charCode)) {
this._advance();
return true;
}
return false;
}
private _requireCharCode(charCode: number) {
var location = this._getLocation();
if (!this._attemptCharCode(charCode)) {
throw this._createError(unexpectedCharacterErrorMsg(this.peek),
this._getSpan(location, location));
}
}
private _attemptStr(chars: string): boolean {
for (var i = 0; i < chars.length; i++) {
if (!this._attemptCharCode(StringWrapper.charCodeAt(chars, i))) {
return false;
}
}
return true;
}
private _attemptStrCaseInsensitive(chars: string): boolean {
for (var i = 0; i < chars.length; i++) {
if (!this._attemptCharCodeCaseInsensitive(StringWrapper.charCodeAt(chars, i))) {
return false;
}
}
return true;
}
private _requireStr(chars: string) {
var location = this._getLocation();
if (!this._attemptStr(chars)) {
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(location));
}
}
private _attemptCharCodeUntilFn(predicate: Function) {
while (!predicate(this.peek)) {
this._advance();
}
}
private _requireCharCodeUntilFn(predicate: Function, len: number) {
var start = this._getLocation();
this._attemptCharCodeUntilFn(predicate);
if (this.index - start.offset < len) {
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(start, start));
}
}
private _attemptUntilChar(char: number) {
while (this.peek !== char) {
this._advance();
}
}
private _readChar(decodeEntities: boolean): string {
if (decodeEntities && this.peek === $AMPERSAND) {
return this._decodeEntity();
} else {
var index = this.index;
this._advance();
return this.input[index];
}
}
private _decodeEntity(): string {
var start = this._getLocation();
this._advance();
if (this._attemptCharCode($HASH)) {
let isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
let numberStart = this._getLocation().offset;
this._attemptCharCodeUntilFn(isDigitEntityEnd);
if (this.peek != $SEMICOLON) {
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
}
this._advance();
let strNum = this.input.substring(numberStart, this.index - 1);
try {
let charCode = NumberWrapper.parseInt(strNum, isHex ? 16 : 10);
return StringWrapper.fromCharCode(charCode);
} catch (e) {
let entity = this.input.substring(start.offset + 1, this.index - 1);
throw this._createError(unknownEntityErrorMsg(entity), this._getSpan(start));
}
} else {
let startPosition = this._savePosition();
this._attemptCharCodeUntilFn(isNamedEntityEnd);
if (this.peek != $SEMICOLON) {
this._restorePosition(startPosition);
return '&';
}
this._advance();
let name = this.input.substring(start.offset + 1, this.index - 1);
let char = NAMED_ENTITIES[name];
if (isBlank(char)) {
throw this._createError(unknownEntityErrorMsg(name), this._getSpan(start));
}
return char;
}
}
private _consumeRawText(decodeEntities: boolean, firstCharOfEnd: number,
attemptEndRest: Function): HtmlToken {
var tagCloseStart;
var textStart = this._getLocation();
this._beginToken(decodeEntities ? HtmlTokenType.ESCAPABLE_RAW_TEXT : HtmlTokenType.RAW_TEXT,
textStart);
var parts = [];
while (true) {
tagCloseStart = this._getLocation();
if (this._attemptCharCode(firstCharOfEnd) && attemptEndRest()) {
break;
}
if (this.index > tagCloseStart.offset) {
parts.push(this.input.substring(tagCloseStart.offset, this.index));
}
while (this.peek !== firstCharOfEnd) {
parts.push(this._readChar(decodeEntities));
}
}
return this._endToken([this._processCarriageReturns(parts.join(''))], tagCloseStart);
}
private _consumeComment(start: ParseLocation) {
this._beginToken(HtmlTokenType.COMMENT_START, start);
this._requireCharCode($MINUS);
this._endToken([]);
var textToken = this._consumeRawText(false, $MINUS, () => this._attemptStr('->'));
this._beginToken(HtmlTokenType.COMMENT_END, textToken.sourceSpan.end);
this._endToken([]);
}
private _consumeCdata(start: ParseLocation) {
this._beginToken(HtmlTokenType.CDATA_START, start);
this._requireStr('CDATA[');
this._endToken([]);
var textToken = this._consumeRawText(false, $RBRACKET, () => this._attemptStr(']>'));
this._beginToken(HtmlTokenType.CDATA_END, textToken.sourceSpan.end);
this._endToken([]);
}
private _consumeDocType(start: ParseLocation) {
this._beginToken(HtmlTokenType.DOC_TYPE, start);
this._attemptUntilChar($GT);
this._advance();
this._endToken([this.input.substring(start.offset + 2, this.index - 1)]);
}
private _consumePrefixAndName(): string[] {
var nameOrPrefixStart = this.index;
var prefix = null;
while (this.peek !== $COLON && !isPrefixEnd(this.peek)) {
this._advance();
}
var nameStart;
if (this.peek === $COLON) {
this._advance();
prefix = this.input.substring(nameOrPrefixStart, this.index - 1);
nameStart = this.index;
} else {
nameStart = nameOrPrefixStart;
}
this._requireCharCodeUntilFn(isNameEnd, this.index === nameStart ? 1 : 0);
var name = this.input.substring(nameStart, this.index);
return [prefix, name];
}
private _consumeTagOpen(start: ParseLocation) {
let savedPos = this._savePosition();
let lowercaseTagName;
try {
if (!isAsciiLetter(this.peek)) {
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
}
var nameStart = this.index;
this._consumeTagOpenStart(start);
lowercaseTagName = this.input.substring(nameStart, this.index).toLowerCase();
this._attemptCharCodeUntilFn(isNotWhitespace);
while (this.peek !== $SLASH && this.peek !== $GT) {
this._consumeAttributeName();
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode($EQ)) {
this._attemptCharCodeUntilFn(isNotWhitespace);
this._consumeAttributeValue();
}
this._attemptCharCodeUntilFn(isNotWhitespace);
}
this._consumeTagOpenEnd();
} catch (e) {
if (e instanceof ControlFlowError) {
// When the start tag is invalid, assume we want a "<"
this._restorePosition(savedPos);
// Back to back text tokens are merged at the end
this._beginToken(HtmlTokenType.TEXT, start);
this._endToken(['<']);
return;
}
throw e;
}
var contentTokenType = getHtmlTagDefinition(lowercaseTagName).contentType;
if (contentTokenType === HtmlTagContentType.RAW_TEXT) {
this._consumeRawTextWithTagClose(lowercaseTagName, false);
} else if (contentTokenType === HtmlTagContentType.ESCAPABLE_RAW_TEXT) {
this._consumeRawTextWithTagClose(lowercaseTagName, true);
}
}
private _consumeRawTextWithTagClose(lowercaseTagName: string, decodeEntities: boolean) {
var textToken = this._consumeRawText(decodeEntities, $LT, () => {
if (!this._attemptCharCode($SLASH)) return false;
this._attemptCharCodeUntilFn(isNotWhitespace);
if (!this._attemptStrCaseInsensitive(lowercaseTagName)) return false;
this._attemptCharCodeUntilFn(isNotWhitespace);
if (!this._attemptCharCode($GT)) return false;
return true;
});
this._beginToken(HtmlTokenType.TAG_CLOSE, textToken.sourceSpan.end);
this._endToken([null, lowercaseTagName]);
}
private _consumeTagOpenStart(start: ParseLocation) {
this._beginToken(HtmlTokenType.TAG_OPEN_START, start);
var parts = this._consumePrefixAndName();
this._endToken(parts);
}
private _consumeAttributeName() {
this._beginToken(HtmlTokenType.ATTR_NAME);
var prefixAndName = this._consumePrefixAndName();
this._endToken(prefixAndName);
}
private _consumeAttributeValue() {
this._beginToken(HtmlTokenType.ATTR_VALUE);
var value;
if (this.peek === $SQ || this.peek === $DQ) {
var quoteChar = this.peek;
this._advance();
var parts = [];
while (this.peek !== quoteChar) {
parts.push(this._readChar(true));
}
value = parts.join('');
this._advance();
} else {
var valueStart = this.index;
this._requireCharCodeUntilFn(isNameEnd, 1);
value = this.input.substring(valueStart, this.index);
}
this._endToken([this._processCarriageReturns(value)]);
}
private _consumeTagOpenEnd() {
var tokenType = this._attemptCharCode($SLASH) ? HtmlTokenType.TAG_OPEN_END_VOID :
HtmlTokenType.TAG_OPEN_END;
this._beginToken(tokenType);
this._requireCharCode($GT);
this._endToken([]);
}
private _consumeTagClose(start: ParseLocation) {
this._beginToken(HtmlTokenType.TAG_CLOSE, start);
this._attemptCharCodeUntilFn(isNotWhitespace);
var prefixAndName;
prefixAndName = this._consumePrefixAndName();
this._attemptCharCodeUntilFn(isNotWhitespace);
this._requireCharCode($GT);
this._endToken(prefixAndName);
}
private _consumeExpansionFormStart() {
this._beginToken(HtmlTokenType.EXPANSION_FORM_START, this._getLocation());
this._requireCharCode($LBRACE);
this._endToken([]);
this._beginToken(HtmlTokenType.RAW_TEXT, this._getLocation());
let condition = this._readUntil($COMMA);
this._endToken([condition], this._getLocation());
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._beginToken(HtmlTokenType.RAW_TEXT, this._getLocation());
let type = this._readUntil($COMMA);
this._endToken([type], this._getLocation());
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
this.expansionCaseStack.push(HtmlTokenType.EXPANSION_FORM_START);
}
private _consumeExpansionCaseStart() {
this._requireCharCode($EQ);
this._beginToken(HtmlTokenType.EXPANSION_CASE_VALUE, this._getLocation());
let value = this._readUntil($LBRACE).trim();
this._endToken([value], this._getLocation());
this._attemptCharCodeUntilFn(isNotWhitespace);
this._beginToken(HtmlTokenType.EXPANSION_CASE_EXP_START, this._getLocation());
this._requireCharCode($LBRACE);
this._endToken([], this._getLocation());
this._attemptCharCodeUntilFn(isNotWhitespace);
this.expansionCaseStack.push(HtmlTokenType.EXPANSION_CASE_EXP_START);
}
private _consumeExpansionCaseEnd() {
this._beginToken(HtmlTokenType.EXPANSION_CASE_EXP_END, this._getLocation());
this._requireCharCode($RBRACE);
this._endToken([], this._getLocation());
this._attemptCharCodeUntilFn(isNotWhitespace);
this.expansionCaseStack.pop();
}
private _consumeExpansionFormEnd() {
this._beginToken(HtmlTokenType.EXPANSION_FORM_END, this._getLocation());
this._requireCharCode($RBRACE);
this._endToken([]);
this.expansionCaseStack.pop();
}
private _consumeText() {
var start = this._getLocation();
this._beginToken(HtmlTokenType.TEXT, start);
var parts = [];
let interpolation = false;
if (this.peek === $LBRACE && this.nextPeek === $LBRACE) {
parts.push(this._readChar(true));
parts.push(this._readChar(true));
interpolation = true;
} else {
parts.push(this._readChar(true));
}
while (!this.isTextEnd(interpolation)) {
if (this.peek === $LBRACE && this.nextPeek === $LBRACE) {
parts.push(this._readChar(true));
parts.push(this._readChar(true));
interpolation = true;
} else if (this.peek === $RBRACE && this.nextPeek === $RBRACE && interpolation) {
parts.push(this._readChar(true));
parts.push(this._readChar(true));
interpolation = false;
} else {
parts.push(this._readChar(true));
}
}
this._endToken([this._processCarriageReturns(parts.join(''))]);
}
private isTextEnd(interpolation: boolean): boolean {
if (this.peek === $LT || this.peek === $EOF) return true;
if (this.tokenizeExpansionForms) {
if (isSpecialFormStart(this.peek, this.nextPeek)) return true;
if (this.peek === $RBRACE && !interpolation &&
(this.isInExpansionCase() || this.isInExpansionForm()))
return true;
}
return false;
}
private _savePosition(): number[] {
return [this.peek, this.index, this.column, this.line, this.tokens.length];
}
private _readUntil(char: number): string {
let start = this.index;
this._attemptUntilChar(char);
return this.input.substring(start, this.index);
}
private _restorePosition(position: number[]): void {
this.peek = position[0];
this.index = position[1];
this.column = position[2];
this.line = position[3];
let nbTokens = position[4];
if (nbTokens < this.tokens.length) {
// remove any extra tokens
this.tokens = ListWrapper.slice(this.tokens, 0, nbTokens);
}
}
private isInExpansionCase(): boolean {
return this.expansionCaseStack.length > 0 &&
this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
HtmlTokenType.EXPANSION_CASE_EXP_START;
}
private isInExpansionForm(): boolean {
return this.expansionCaseStack.length > 0 &&
this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
HtmlTokenType.EXPANSION_FORM_START;
}
}
function isNotWhitespace(code: number): boolean {
return !isWhitespace(code) || code === $EOF;
}
function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code === $NBSP);
}
function isNameEnd(code: number): boolean {
return isWhitespace(code) || code === $GT || code === $SLASH || code === $SQ || code === $DQ ||
code === $EQ;
}
function isPrefixEnd(code: number): boolean {
return (code < $a || $z < code) && (code < $A || $Z < code) && (code < $0 || code > $9);
}
function isDigitEntityEnd(code: number): boolean {
return code == $SEMICOLON || code == $EOF || !isAsciiHexDigit(code);
}
function isNamedEntityEnd(code: number): boolean {
return code == $SEMICOLON || code == $EOF || !isAsciiLetter(code);
}
function isSpecialFormStart(peek: number, nextPeek: number): boolean {
return peek === $LBRACE && nextPeek != $LBRACE;
}
function isAsciiLetter(code: number): boolean {
return code >= $a && code <= $z || code >= $A && code <= $Z;
}
function isAsciiHexDigit(code: number): boolean {
return code >= $a && code <= $f || code >= $A && code <= $F || code >= $0 && code <= $9;
}
function compareCharCodeCaseInsensitive(code1: number, code2: number): boolean {
return toUpperCaseCharCode(code1) == toUpperCaseCharCode(code2);
}
function toUpperCaseCharCode(code: number): number {
return code >= $a && code <= $z ? code - $a + $A : code;
}
function mergeTextTokens(srcTokens: HtmlToken[]): HtmlToken[] {
let dstTokens = [];
let lastDstToken: HtmlToken;
for (let i = 0; i < srcTokens.length; i++) {
let token = srcTokens[i];
if (isPresent(lastDstToken) && lastDstToken.type == HtmlTokenType.TEXT &&
token.type == HtmlTokenType.TEXT) {
lastDstToken.parts[0] += token.parts[0];
lastDstToken.sourceSpan.end = token.sourceSpan.end;
} else {
lastDstToken = token;
dstTokens.push(lastDstToken);
}
}
return dstTokens;
}

View File

@ -0,0 +1,369 @@
import {
isPresent,
isBlank,
StringWrapper,
stringify,
assertionsEnabled,
StringJoiner,
serializeEnum,
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {
HtmlAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
HtmlElementAst,
HtmlExpansionAst,
HtmlExpansionCaseAst
} from './html_ast';
import {Injectable} from 'angular2/src/core/di';
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
import {ParseError, ParseLocation, ParseSourceSpan} from './parse_util';
import {HtmlTagDefinition, getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags';
export class HtmlTreeError extends ParseError {
static create(elementName: string, span: ParseSourceSpan, msg: string): HtmlTreeError {
return new HtmlTreeError(elementName, span, msg);
}
constructor(public elementName: string, span: ParseSourceSpan, msg: string) { super(span, msg); }
}
export class HtmlParseTreeResult {
constructor(public rootNodes: HtmlAst[], public errors: ParseError[]) {}
}
@Injectable()
export class HtmlParser {
parse(sourceContent: string, sourceUrl: string,
parseExpansionForms: boolean = false): HtmlParseTreeResult {
var tokensAndErrors = tokenizeHtml(sourceContent, sourceUrl, parseExpansionForms);
var treeAndErrors = new TreeBuilder(tokensAndErrors.tokens).build();
return new HtmlParseTreeResult(treeAndErrors.rootNodes, (<ParseError[]>tokensAndErrors.errors)
.concat(treeAndErrors.errors));
}
}
class TreeBuilder {
private index: number = -1;
private peek: HtmlToken;
private rootNodes: HtmlAst[] = [];
private errors: HtmlTreeError[] = [];
private elementStack: HtmlElementAst[] = [];
constructor(private tokens: HtmlToken[]) { this._advance(); }
build(): HtmlParseTreeResult {
while (this.peek.type !== HtmlTokenType.EOF) {
if (this.peek.type === HtmlTokenType.TAG_OPEN_START) {
this._consumeStartTag(this._advance());
} else if (this.peek.type === HtmlTokenType.TAG_CLOSE) {
this._consumeEndTag(this._advance());
} else if (this.peek.type === HtmlTokenType.CDATA_START) {
this._closeVoidElement();
this._consumeCdata(this._advance());
} else if (this.peek.type === HtmlTokenType.COMMENT_START) {
this._closeVoidElement();
this._consumeComment(this._advance());
} else if (this.peek.type === HtmlTokenType.TEXT ||
this.peek.type === HtmlTokenType.RAW_TEXT ||
this.peek.type === HtmlTokenType.ESCAPABLE_RAW_TEXT) {
this._closeVoidElement();
this._consumeText(this._advance());
} else if (this.peek.type === HtmlTokenType.EXPANSION_FORM_START) {
this._consumeExpansion(this._advance());
} else {
// Skip all other tokens...
this._advance();
}
}
return new HtmlParseTreeResult(this.rootNodes, this.errors);
}
private _advance(): HtmlToken {
var prev = this.peek;
if (this.index < this.tokens.length - 1) {
// Note: there is always an EOF token at the end
this.index++;
}
this.peek = this.tokens[this.index];
return prev;
}
private _advanceIf(type: HtmlTokenType): HtmlToken {
if (this.peek.type === type) {
return this._advance();
}
return null;
}
private _consumeCdata(startToken: HtmlToken) {
this._consumeText(this._advance());
this._advanceIf(HtmlTokenType.CDATA_END);
}
private _consumeComment(token: HtmlToken) {
var text = this._advanceIf(HtmlTokenType.RAW_TEXT);
this._advanceIf(HtmlTokenType.COMMENT_END);
var value = isPresent(text) ? text.parts[0].trim() : null;
this._addToParent(new HtmlCommentAst(value, token.sourceSpan));
}
private _consumeExpansion(token: HtmlToken) {
let switchValue = this._advance();
let type = this._advance();
let cases = [];
// read =
while (this.peek.type === HtmlTokenType.EXPANSION_CASE_VALUE) {
let expCase = this._parseExpansionCase();
if (isBlank(expCase)) return; // error
cases.push(expCase);
}
// read the final }
if (this.peek.type !== HtmlTokenType.EXPANSION_FORM_END) {
this.errors.push(
HtmlTreeError.create(null, this.peek.sourceSpan, `Invalid expansion form. Missing '}'.`));
return;
}
this._advance();
let mainSourceSpan = new ParseSourceSpan(token.sourceSpan.start, this.peek.sourceSpan.end);
this._addToParent(new HtmlExpansionAst(switchValue.parts[0], type.parts[0], cases,
mainSourceSpan, switchValue.sourceSpan));
}
private _parseExpansionCase(): HtmlExpansionCaseAst {
let value = this._advance();
// read {
if (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_START) {
this.errors.push(HtmlTreeError.create(null, this.peek.sourceSpan,
`Invalid expansion form. Missing '{'.,`));
return null;
}
// read until }
let start = this._advance();
let exp = this._collectExpansionExpTokens(start);
if (isBlank(exp)) return null;
let end = this._advance();
exp.push(new HtmlToken(HtmlTokenType.EOF, [], end.sourceSpan));
// parse everything in between { and }
let parsedExp = new TreeBuilder(exp).build();
if (parsedExp.errors.length > 0) {
this.errors = this.errors.concat(<HtmlTreeError[]>parsedExp.errors);
return null;
}
let sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
let expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
return new HtmlExpansionCaseAst(value.parts[0], parsedExp.rootNodes, sourceSpan,
value.sourceSpan, expSourceSpan);
}
private _collectExpansionExpTokens(start: HtmlToken): HtmlToken[] {
let exp = [];
let expansionFormStack = [HtmlTokenType.EXPANSION_CASE_EXP_START];
while (true) {
if (this.peek.type === HtmlTokenType.EXPANSION_FORM_START ||
this.peek.type === HtmlTokenType.EXPANSION_CASE_EXP_START) {
expansionFormStack.push(this.peek.type);
}
if (this.peek.type === HtmlTokenType.EXPANSION_CASE_EXP_END) {
if (lastOnStack(expansionFormStack, HtmlTokenType.EXPANSION_CASE_EXP_START)) {
expansionFormStack.pop();
if (expansionFormStack.length == 0) return exp;
} else {
this.errors.push(
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
return null;
}
}
if (this.peek.type === HtmlTokenType.EXPANSION_FORM_END) {
if (lastOnStack(expansionFormStack, HtmlTokenType.EXPANSION_FORM_START)) {
expansionFormStack.pop();
} else {
this.errors.push(
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
return null;
}
}
if (this.peek.type === HtmlTokenType.EOF) {
this.errors.push(
HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
return null;
}
exp.push(this._advance());
}
}
private _consumeText(token: HtmlToken) {
let text = token.parts[0];
if (text.length > 0 && text[0] == '\n') {
let parent = this._getParentElement();
if (isPresent(parent) && parent.children.length == 0 &&
getHtmlTagDefinition(parent.name).ignoreFirstLf) {
text = text.substring(1);
}
}
if (text.length > 0) {
this._addToParent(new HtmlTextAst(text, token.sourceSpan));
}
}
private _closeVoidElement(): void {
if (this.elementStack.length > 0) {
let el = ListWrapper.last(this.elementStack);
if (getHtmlTagDefinition(el.name).isVoid) {
this.elementStack.pop();
}
}
}
private _consumeStartTag(startTagToken: HtmlToken) {
var prefix = startTagToken.parts[0];
var name = startTagToken.parts[1];
var attrs = [];
while (this.peek.type === HtmlTokenType.ATTR_NAME) {
attrs.push(this._consumeAttr(this._advance()));
}
var fullName = getElementFullName(prefix, name, this._getParentElement());
var selfClosing = false;
// Note: There could have been a tokenizer error
// so that we don't get a token for the end tag...
if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) {
this._advance();
selfClosing = true;
if (getNsPrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) {
this.errors.push(HtmlTreeError.create(
fullName, startTagToken.sourceSpan,
`Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`));
}
} else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) {
this._advance();
selfClosing = false;
}
var end = this.peek.sourceSpan.start;
let span = new ParseSourceSpan(startTagToken.sourceSpan.start, end);
var el = new HtmlElementAst(fullName, attrs, [], span, span, null);
this._pushElement(el);
if (selfClosing) {
this._popElement(fullName);
el.endSourceSpan = span;
}
}
private _pushElement(el: HtmlElementAst) {
if (this.elementStack.length > 0) {
var parentEl = ListWrapper.last(this.elementStack);
if (getHtmlTagDefinition(parentEl.name).isClosedByChild(el.name)) {
this.elementStack.pop();
}
}
var tagDef = getHtmlTagDefinition(el.name);
var parentEl = this._getParentElement();
if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan,
el.startSourceSpan, el.endSourceSpan);
this._addToParent(newParent);
this.elementStack.push(newParent);
this.elementStack.push(el);
} else {
this._addToParent(el);
this.elementStack.push(el);
}
}
private _consumeEndTag(endTagToken: HtmlToken) {
var fullName =
getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
this._getParentElement().endSourceSpan = endTagToken.sourceSpan;
if (getHtmlTagDefinition(fullName).isVoid) {
this.errors.push(
HtmlTreeError.create(fullName, endTagToken.sourceSpan,
`Void elements do not have end tags "${endTagToken.parts[1]}"`));
} else if (!this._popElement(fullName)) {
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan,
`Unexpected closing tag "${endTagToken.parts[1]}"`));
}
}
private _popElement(fullName: string): boolean {
for (let stackIndex = this.elementStack.length - 1; stackIndex >= 0; stackIndex--) {
let el = this.elementStack[stackIndex];
if (el.name == fullName) {
ListWrapper.splice(this.elementStack, stackIndex, this.elementStack.length - stackIndex);
return true;
}
if (!getHtmlTagDefinition(el.name).closedByParent) {
return false;
}
}
return false;
}
private _consumeAttr(attrName: HtmlToken): HtmlAttrAst {
var fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
var end = attrName.sourceSpan.end;
var value = '';
if (this.peek.type === HtmlTokenType.ATTR_VALUE) {
var valueToken = this._advance();
value = valueToken.parts[0];
end = valueToken.sourceSpan.end;
}
return new HtmlAttrAst(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, end));
}
private _getParentElement(): HtmlElementAst {
return this.elementStack.length > 0 ? ListWrapper.last(this.elementStack) : null;
}
private _addToParent(node: HtmlAst) {
var parent = this._getParentElement();
if (isPresent(parent)) {
parent.children.push(node);
} else {
this.rootNodes.push(node);
}
}
}
function getElementFullName(prefix: string, localName: string,
parentElement: HtmlElementAst): string {
if (isBlank(prefix)) {
prefix = getHtmlTagDefinition(localName).implicitNamespacePrefix;
if (isBlank(prefix) && isPresent(parentElement)) {
prefix = getNsPrefix(parentElement.name);
}
}
return mergeNsAndName(prefix, localName);
}
function lastOnStack(stack: any[], element: any): boolean {
return stack.length > 0 && stack[stack.length - 1] === element;
}

View File

@ -0,0 +1,427 @@
import {
isPresent,
isBlank,
normalizeBool,
RegExpWrapper,
} from 'angular2/src/facade/lang';
// see http://www.w3.org/TR/html51/syntax.html#named-character-references
// see https://html.spec.whatwg.org/multipage/entities.json
// This list is not exhaustive to keep the compiler footprint low.
// The `&#123;` / `&#x1ab;` syntax should be used when the named character reference does not exist.
export const NAMED_ENTITIES = /*@ts2dart_const*/ {
'Aacute': '\u00C1',
'aacute': '\u00E1',
'Acirc': '\u00C2',
'acirc': '\u00E2',
'acute': '\u00B4',
'AElig': '\u00C6',
'aelig': '\u00E6',
'Agrave': '\u00C0',
'agrave': '\u00E0',
'alefsym': '\u2135',
'Alpha': '\u0391',
'alpha': '\u03B1',
'amp': '&',
'and': '\u2227',
'ang': '\u2220',
'apos': '\u0027',
'Aring': '\u00C5',
'aring': '\u00E5',
'asymp': '\u2248',
'Atilde': '\u00C3',
'atilde': '\u00E3',
'Auml': '\u00C4',
'auml': '\u00E4',
'bdquo': '\u201E',
'Beta': '\u0392',
'beta': '\u03B2',
'brvbar': '\u00A6',
'bull': '\u2022',
'cap': '\u2229',
'Ccedil': '\u00C7',
'ccedil': '\u00E7',
'cedil': '\u00B8',
'cent': '\u00A2',
'Chi': '\u03A7',
'chi': '\u03C7',
'circ': '\u02C6',
'clubs': '\u2663',
'cong': '\u2245',
'copy': '\u00A9',
'crarr': '\u21B5',
'cup': '\u222A',
'curren': '\u00A4',
'dagger': '\u2020',
'Dagger': '\u2021',
'darr': '\u2193',
'dArr': '\u21D3',
'deg': '\u00B0',
'Delta': '\u0394',
'delta': '\u03B4',
'diams': '\u2666',
'divide': '\u00F7',
'Eacute': '\u00C9',
'eacute': '\u00E9',
'Ecirc': '\u00CA',
'ecirc': '\u00EA',
'Egrave': '\u00C8',
'egrave': '\u00E8',
'empty': '\u2205',
'emsp': '\u2003',
'ensp': '\u2002',
'Epsilon': '\u0395',
'epsilon': '\u03B5',
'equiv': '\u2261',
'Eta': '\u0397',
'eta': '\u03B7',
'ETH': '\u00D0',
'eth': '\u00F0',
'Euml': '\u00CB',
'euml': '\u00EB',
'euro': '\u20AC',
'exist': '\u2203',
'fnof': '\u0192',
'forall': '\u2200',
'frac12': '\u00BD',
'frac14': '\u00BC',
'frac34': '\u00BE',
'frasl': '\u2044',
'Gamma': '\u0393',
'gamma': '\u03B3',
'ge': '\u2265',
'gt': '>',
'harr': '\u2194',
'hArr': '\u21D4',
'hearts': '\u2665',
'hellip': '\u2026',
'Iacute': '\u00CD',
'iacute': '\u00ED',
'Icirc': '\u00CE',
'icirc': '\u00EE',
'iexcl': '\u00A1',
'Igrave': '\u00CC',
'igrave': '\u00EC',
'image': '\u2111',
'infin': '\u221E',
'int': '\u222B',
'Iota': '\u0399',
'iota': '\u03B9',
'iquest': '\u00BF',
'isin': '\u2208',
'Iuml': '\u00CF',
'iuml': '\u00EF',
'Kappa': '\u039A',
'kappa': '\u03BA',
'Lambda': '\u039B',
'lambda': '\u03BB',
'lang': '\u27E8',
'laquo': '\u00AB',
'larr': '\u2190',
'lArr': '\u21D0',
'lceil': '\u2308',
'ldquo': '\u201C',
'le': '\u2264',
'lfloor': '\u230A',
'lowast': '\u2217',
'loz': '\u25CA',
'lrm': '\u200E',
'lsaquo': '\u2039',
'lsquo': '\u2018',
'lt': '<',
'macr': '\u00AF',
'mdash': '\u2014',
'micro': '\u00B5',
'middot': '\u00B7',
'minus': '\u2212',
'Mu': '\u039C',
'mu': '\u03BC',
'nabla': '\u2207',
'nbsp': '\u00A0',
'ndash': '\u2013',
'ne': '\u2260',
'ni': '\u220B',
'not': '\u00AC',
'notin': '\u2209',
'nsub': '\u2284',
'Ntilde': '\u00D1',
'ntilde': '\u00F1',
'Nu': '\u039D',
'nu': '\u03BD',
'Oacute': '\u00D3',
'oacute': '\u00F3',
'Ocirc': '\u00D4',
'ocirc': '\u00F4',
'OElig': '\u0152',
'oelig': '\u0153',
'Ograve': '\u00D2',
'ograve': '\u00F2',
'oline': '\u203E',
'Omega': '\u03A9',
'omega': '\u03C9',
'Omicron': '\u039F',
'omicron': '\u03BF',
'oplus': '\u2295',
'or': '\u2228',
'ordf': '\u00AA',
'ordm': '\u00BA',
'Oslash': '\u00D8',
'oslash': '\u00F8',
'Otilde': '\u00D5',
'otilde': '\u00F5',
'otimes': '\u2297',
'Ouml': '\u00D6',
'ouml': '\u00F6',
'para': '\u00B6',
'permil': '\u2030',
'perp': '\u22A5',
'Phi': '\u03A6',
'phi': '\u03C6',
'Pi': '\u03A0',
'pi': '\u03C0',
'piv': '\u03D6',
'plusmn': '\u00B1',
'pound': '\u00A3',
'prime': '\u2032',
'Prime': '\u2033',
'prod': '\u220F',
'prop': '\u221D',
'Psi': '\u03A8',
'psi': '\u03C8',
'quot': '\u0022',
'radic': '\u221A',
'rang': '\u27E9',
'raquo': '\u00BB',
'rarr': '\u2192',
'rArr': '\u21D2',
'rceil': '\u2309',
'rdquo': '\u201D',
'real': '\u211C',
'reg': '\u00AE',
'rfloor': '\u230B',
'Rho': '\u03A1',
'rho': '\u03C1',
'rlm': '\u200F',
'rsaquo': '\u203A',
'rsquo': '\u2019',
'sbquo': '\u201A',
'Scaron': '\u0160',
'scaron': '\u0161',
'sdot': '\u22C5',
'sect': '\u00A7',
'shy': '\u00AD',
'Sigma': '\u03A3',
'sigma': '\u03C3',
'sigmaf': '\u03C2',
'sim': '\u223C',
'spades': '\u2660',
'sub': '\u2282',
'sube': '\u2286',
'sum': '\u2211',
'sup': '\u2283',
'sup1': '\u00B9',
'sup2': '\u00B2',
'sup3': '\u00B3',
'supe': '\u2287',
'szlig': '\u00DF',
'Tau': '\u03A4',
'tau': '\u03C4',
'there4': '\u2234',
'Theta': '\u0398',
'theta': '\u03B8',
'thetasym': '\u03D1',
'thinsp': '\u2009',
'THORN': '\u00DE',
'thorn': '\u00FE',
'tilde': '\u02DC',
'times': '\u00D7',
'trade': '\u2122',
'Uacute': '\u00DA',
'uacute': '\u00FA',
'uarr': '\u2191',
'uArr': '\u21D1',
'Ucirc': '\u00DB',
'ucirc': '\u00FB',
'Ugrave': '\u00D9',
'ugrave': '\u00F9',
'uml': '\u00A8',
'upsih': '\u03D2',
'Upsilon': '\u03A5',
'upsilon': '\u03C5',
'Uuml': '\u00DC',
'uuml': '\u00FC',
'weierp': '\u2118',
'Xi': '\u039E',
'xi': '\u03BE',
'Yacute': '\u00DD',
'yacute': '\u00FD',
'yen': '\u00A5',
'yuml': '\u00FF',
'Yuml': '\u0178',
'Zeta': '\u0396',
'zeta': '\u03B6',
'zwj': '\u200D',
'zwnj': '\u200C',
};
export enum HtmlTagContentType {
RAW_TEXT,
ESCAPABLE_RAW_TEXT,
PARSABLE_DATA
}
export class HtmlTagDefinition {
private closedByChildren: {[key: string]: boolean} = {};
public closedByParent: boolean = false;
public requiredParents: {[key: string]: boolean};
public parentToAdd: string;
public implicitNamespacePrefix: string;
public contentType: HtmlTagContentType;
public isVoid: boolean;
public ignoreFirstLf: boolean;
constructor({closedByChildren, requiredParents, implicitNamespacePrefix, contentType,
closedByParent, isVoid, ignoreFirstLf}: {
closedByChildren?: string[],
closedByParent?: boolean,
requiredParents?: string[],
implicitNamespacePrefix?: string,
contentType?: HtmlTagContentType,
isVoid?: boolean,
ignoreFirstLf?: boolean
} = {}) {
if (isPresent(closedByChildren) && closedByChildren.length > 0) {
closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
}
this.isVoid = normalizeBool(isVoid);
this.closedByParent = normalizeBool(closedByParent) || this.isVoid;
if (isPresent(requiredParents) && requiredParents.length > 0) {
this.requiredParents = {};
this.parentToAdd = requiredParents[0];
requiredParents.forEach(tagName => this.requiredParents[tagName] = true);
}
this.implicitNamespacePrefix = implicitNamespacePrefix;
this.contentType = isPresent(contentType) ? contentType : HtmlTagContentType.PARSABLE_DATA;
this.ignoreFirstLf = normalizeBool(ignoreFirstLf);
}
requireExtraParent(currentParent: string): boolean {
if (isBlank(this.requiredParents)) {
return false;
}
if (isBlank(currentParent)) {
return true;
}
let lcParent = currentParent.toLowerCase();
return this.requiredParents[lcParent] != true && lcParent != 'template';
}
isClosedByChild(name: string): boolean {
return this.isVoid || normalizeBool(this.closedByChildren[name.toLowerCase()]);
}
}
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
// This implementation does not fully conform to the HTML5 spec.
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
'base': new HtmlTagDefinition({isVoid: true}),
'meta': new HtmlTagDefinition({isVoid: true}),
'area': new HtmlTagDefinition({isVoid: true}),
'embed': new HtmlTagDefinition({isVoid: true}),
'link': new HtmlTagDefinition({isVoid: true}),
'img': new HtmlTagDefinition({isVoid: true}),
'input': new HtmlTagDefinition({isVoid: true}),
'param': new HtmlTagDefinition({isVoid: true}),
'hr': new HtmlTagDefinition({isVoid: true}),
'br': new HtmlTagDefinition({isVoid: true}),
'source': new HtmlTagDefinition({isVoid: true}),
'track': new HtmlTagDefinition({isVoid: true}),
'wbr': new HtmlTagDefinition({isVoid: true}),
'p': new HtmlTagDefinition({
closedByChildren: [
'address',
'article',
'aside',
'blockquote',
'div',
'dl',
'fieldset',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'header',
'hgroup',
'hr',
'main',
'nav',
'ol',
'p',
'pre',
'section',
'table',
'ul'
],
closedByParent: true
}),
'thead': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot']}),
'tbody': new HtmlTagDefinition({closedByChildren: ['tbody', 'tfoot'], closedByParent: true}),
'tfoot': new HtmlTagDefinition({closedByChildren: ['tbody'], closedByParent: true}),
'tr': new HtmlTagDefinition({
closedByChildren: ['tr'],
requiredParents: ['tbody', 'tfoot', 'thead'],
closedByParent: true
}),
'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
'col': new HtmlTagDefinition({requiredParents: ['colgroup'], isVoid: true}),
'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),
'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}),
'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}),
'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}),
'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}),
'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}),
'rt': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}),
'rtc': new HtmlTagDefinition({closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true}),
'rp': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true}),
'optgroup': new HtmlTagDefinition({closedByChildren: ['optgroup'], closedByParent: true}),
'option': new HtmlTagDefinition({closedByChildren: ['option', 'optgroup'], closedByParent: true}),
'pre': new HtmlTagDefinition({ignoreFirstLf: true}),
'listing': new HtmlTagDefinition({ignoreFirstLf: true}),
'style': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}),
'script': new HtmlTagDefinition({contentType: HtmlTagContentType.RAW_TEXT}),
'title': new HtmlTagDefinition({contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT}),
'textarea': new HtmlTagDefinition(
{contentType: HtmlTagContentType.ESCAPABLE_RAW_TEXT, ignoreFirstLf: true}),
};
var DEFAULT_TAG_DEFINITION = new HtmlTagDefinition();
export function getHtmlTagDefinition(tagName: string): HtmlTagDefinition {
var result = TAG_DEFINITIONS[tagName.toLowerCase()];
return isPresent(result) ? result : DEFAULT_TAG_DEFINITION;
}
var NS_PREFIX_RE = /^@([^:]+):(.+)/g;
export function splitNsName(elementName: string): string[] {
if (elementName[0] != '@') {
return [null, elementName];
}
let match = RegExpWrapper.firstMatch(NS_PREFIX_RE, elementName);
return [match[1], match[2]];
}
export function getNsPrefix(elementName: string): string {
return splitNsName(elementName)[0];
}
export function mergeNsAndName(prefix: string, localName: string): string {
return isPresent(prefix) ? `@${prefix}:${localName}` : localName;
}

View File

@ -0,0 +1,116 @@
import {
HtmlAst,
HtmlAstVisitor,
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst,
htmlVisitAll
} from 'angular2/src/compiler/html_ast';
import {BaseException} from 'angular2/src/facade/exceptions';
/**
* Expands special forms into elements.
*
* For example,
*
* ```
* { messages.length, plural,
* =0 {zero}
* =1 {one}
* =other {more than one}
* }
* ```
*
* will be expanded into
*
* ```
* <ul [ngPlural]="messages.length">
* <template [ngPluralCase]="0"><li i18n="plural_0">zero</li></template>
* <template [ngPluralCase]="1"><li i18n="plural_1">one</li></template>
* <template [ngPluralCase]="other"><li i18n="plural_other">more than one</li></template>
* </ul>
* ```
*/
export function expandNodes(nodes: HtmlAst[]): ExpansionResult {
let e = new _Expander();
let n = htmlVisitAll(e, nodes);
return new ExpansionResult(n, e.expanded);
}
export class ExpansionResult {
constructor(public nodes: HtmlAst[], public expanded: boolean) {}
}
class _Expander implements HtmlAstVisitor {
expanded: boolean = false;
constructor() {}
visitElement(ast: HtmlElementAst, context: any): any {
return new HtmlElementAst(ast.name, ast.attrs, htmlVisitAll(this, ast.children), ast.sourceSpan,
ast.startSourceSpan, ast.endSourceSpan);
}
visitAttr(ast: HtmlAttrAst, context: any): any { return ast; }
visitText(ast: HtmlTextAst, context: any): any { return ast; }
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
visitExpansion(ast: HtmlExpansionAst, context: any): any {
this.expanded = true;
return ast.type == "plural" ? _expandPluralForm(ast) : _expandDefaultForm(ast);
}
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any {
throw new BaseException("Should not be reached");
}
}
function _expandPluralForm(ast: HtmlExpansionAst): HtmlElementAst {
let children = ast.cases.map(c => {
let expansionResult = expandNodes(c.expression);
let i18nAttrs = expansionResult.expanded ?
[] :
[new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)];
return new HtmlElementAst(`template`,
[
new HtmlAttrAst("ngPluralCase", c.value, c.valueSourceSpan),
],
[
new HtmlElementAst(`li`, i18nAttrs, expansionResult.nodes,
c.sourceSpan, c.sourceSpan, c.sourceSpan)
],
c.sourceSpan, c.sourceSpan, c.sourceSpan);
});
let switchAttr = new HtmlAttrAst("[ngPlural]", ast.switchValue, ast.switchValueSourceSpan);
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
ast.sourceSpan);
}
function _expandDefaultForm(ast: HtmlExpansionAst): HtmlElementAst {
let children = ast.cases.map(c => {
let expansionResult = expandNodes(c.expression);
let i18nAttrs = expansionResult.expanded ?
[] :
[new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)];
return new HtmlElementAst(`template`,
[
new HtmlAttrAst("ngSwitchWhen", c.value, c.valueSourceSpan),
],
[
new HtmlElementAst(`li`, i18nAttrs, expansionResult.nodes,
c.sourceSpan, c.sourceSpan, c.sourceSpan)
],
c.sourceSpan, c.sourceSpan, c.sourceSpan);
});
let switchAttr = new HtmlAttrAst("[ngSwitch]", ast.switchValue, ast.switchValueSourceSpan);
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
ast.sourceSpan);
}

View File

@ -0,0 +1,377 @@
import {HtmlParser, HtmlParseTreeResult} from 'angular2/src/compiler/html_parser';
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
import {
HtmlAst,
HtmlAstVisitor,
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst,
htmlVisitAll
} from 'angular2/src/compiler/html_ast';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {RegExpWrapper, NumberWrapper, isPresent} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
import {Message, id} from './message';
import {expandNodes} from './expander';
import {
messageFromAttribute,
I18nError,
I18N_ATTR_PREFIX,
I18N_ATTR,
partition,
Part,
stringifyNodes,
meaning,
getPhNameFromBinding,
dedupePhName
} from './shared';
const _I18N_ATTR = "i18n";
const _PLACEHOLDER_ELEMENT = "ph";
const _NAME_ATTR = "name";
const _I18N_ATTR_PREFIX = "i18n-";
let _PLACEHOLDER_EXPANDED_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\w)+")\\>\\<\\/ph\\>`);
/**
* Creates an i18n-ed version of the parsed template.
*
* Algorithm:
*
* To understand the algorithm, you need to know how partitioning works.
* Partitioning is required as we can use two i18n comments to group node siblings together.
* That is why we cannot just use nodes.
*
* Partitioning transforms an array of HtmlAst into an array of Part.
* A part can optionally contain a root element or a root text node. And it can also contain
* children.
* A part can contain i18n property, in which case it needs to be transalted.
*
* Example:
*
* The following array of nodes will be split into four parts:
*
* ```
* <a>A</a>
* <b i18n>B</b>
* <!-- i18n -->
* <c>C</c>
* D
* <!-- /i18n -->
* E
* ```
*
* Part 1 containing the a tag. It should not be translated.
* Part 2 containing the b tag. It should be translated.
* Part 3 containing the c tag and the D text node. It should be translated.
* Part 4 containing the E text node. It should not be translated.
*
*
* It is also important to understand how we stringify nodes to create a message.
*
* We walk the tree and replace every element node with a placeholder. We also replace
* all expressions in interpolation with placeholders. We also insert a placeholder element
* to wrap a text node containing interpolation.
*
* Example:
*
* The following tree:
*
* ```
* <a>A{{I}}</a><b>B</b>
* ```
*
* will be stringified into:
* ```
* <ph name="e0"><ph name="t1">A<ph name="0"/></ph></ph><ph name="e2">B</ph>
* ```
*
* This is what the algorithm does:
*
* 1. Use the provided html parser to get the html AST of the template.
* 2. Partition the root nodes, and process each part separately.
* 3. If a part does not have the i18n attribute, recurse to process children and attributes.
* 4. If a part has the i18n attribute, merge the translated i18n part with the original tree.
*
* This is how the merging works:
*
* 1. Use the stringify function to get the message id. Look up the message in the map.
* 2. Get the translated message. At this point we have two trees: the original tree
* and the translated tree, where all the elements are replaced with placeholders.
* 3. Use the original tree to create a mapping Index:number -> HtmlAst.
* 4. Walk the translated tree.
* 5. If we encounter a placeholder element, get is name property.
* 6. Get the type and the index of the node using the name property.
* 7. If the type is 'e', which means element, then:
* - translate the attributes of the original element
* - recurse to merge the children
* - create a new element using the original element name, original position,
* and translated children and attributes
* 8. If the type if 't', which means text, then:
* - get the list of expressions from the original node.
* - get the string version of the interpolation subtree
* - find all the placeholders in the translated message, and replace them with the
* corresponding original expressions
*/
export class I18nHtmlParser implements HtmlParser {
errors: ParseError[];
constructor(private _htmlParser: HtmlParser, private _parser: Parser,
private _messagesContent: string, private _messages: {[key: string]: HtmlAst[]}) {}
parse(sourceContent: string, sourceUrl: string,
parseExpansionForms: boolean = false): HtmlParseTreeResult {
this.errors = [];
let res = this._htmlParser.parse(sourceContent, sourceUrl, true);
if (res.errors.length > 0) {
return res;
} else {
let nodes = this._recurse(expandNodes(res.rootNodes).nodes);
return this.errors.length > 0 ? new HtmlParseTreeResult([], this.errors) :
new HtmlParseTreeResult(nodes, []);
}
}
private _processI18nPart(p: Part): HtmlAst[] {
try {
return p.hasI18n ? this._mergeI18Part(p) : this._recurseIntoI18nPart(p);
} catch (e) {
if (e instanceof I18nError) {
this.errors.push(e);
return [];
} else {
throw e;
}
}
}
private _mergeI18Part(p: Part): HtmlAst[] {
let message = p.createMessage(this._parser);
let messageId = id(message);
if (!StringMapWrapper.contains(this._messages, messageId)) {
throw new I18nError(
p.sourceSpan, `Cannot find message for id '${messageId}', content '${message.content}'.`);
}
let parsedMessage = this._messages[messageId];
return this._mergeTrees(p, parsedMessage, p.children);
}
private _recurseIntoI18nPart(p: Part): HtmlAst[] {
// we found an element without an i18n attribute
// we need to recurse in cause its children may have i18n set
// we also need to translate its attributes
if (isPresent(p.rootElement)) {
let root = p.rootElement;
let children = this._recurse(p.children);
let attrs = this._i18nAttributes(root);
return [
new HtmlElementAst(root.name, attrs, children, root.sourceSpan, root.startSourceSpan,
root.endSourceSpan)
];
// a text node without i18n or interpolation, nothing to do
} else if (isPresent(p.rootTextNode)) {
return [p.rootTextNode];
} else {
return this._recurse(p.children);
}
}
private _recurse(nodes: HtmlAst[]): HtmlAst[] {
let ps = partition(nodes, this.errors);
return ListWrapper.flatten(ps.map(p => this._processI18nPart(p)));
}
private _mergeTrees(p: Part, translated: HtmlAst[], original: HtmlAst[]): HtmlAst[] {
let l = new _CreateNodeMapping();
htmlVisitAll(l, original);
// merge the translated tree with the original tree.
// we do it by preserving the source code position of the original tree
let merged = this._mergeTreesHelper(translated, l.mapping);
// if the root element is present, we need to create a new root element with its attributes
// translated
if (isPresent(p.rootElement)) {
let root = p.rootElement;
let attrs = this._i18nAttributes(root);
return [
new HtmlElementAst(root.name, attrs, merged, root.sourceSpan, root.startSourceSpan,
root.endSourceSpan)
];
// this should never happen with a part. Parts that have root text node should not be merged.
} else if (isPresent(p.rootTextNode)) {
throw new BaseException("should not be reached");
} else {
return merged;
}
}
private _mergeTreesHelper(translated: HtmlAst[], mapping: HtmlAst[]): HtmlAst[] {
return translated.map(t => {
if (t instanceof HtmlElementAst) {
return this._mergeElementOrInterpolation(t, translated, mapping);
} else if (t instanceof HtmlTextAst) {
return t;
} else {
throw new BaseException("should not be reached");
}
});
}
private _mergeElementOrInterpolation(t: HtmlElementAst, translated: HtmlAst[],
mapping: HtmlAst[]): HtmlAst {
let name = this._getName(t);
let type = name[0];
let index = NumberWrapper.parseInt(name.substring(1), 10);
let originalNode = mapping[index];
if (type == "t") {
return this._mergeTextInterpolation(t, <HtmlTextAst>originalNode);
} else if (type == "e") {
return this._mergeElement(t, <HtmlElementAst>originalNode, mapping);
} else {
throw new BaseException("should not be reached");
}
}
private _getName(t: HtmlElementAst): string {
if (t.name != _PLACEHOLDER_ELEMENT) {
throw new I18nError(
t.sourceSpan,
`Unexpected tag "${t.name}". Only "${_PLACEHOLDER_ELEMENT}" tags are allowed.`);
}
let names = t.attrs.filter(a => a.name == _NAME_ATTR);
if (names.length == 0) {
throw new I18nError(t.sourceSpan, `Missing "${_NAME_ATTR}" attribute.`);
}
return names[0].value;
}
private _mergeTextInterpolation(t: HtmlElementAst, originalNode: HtmlTextAst): HtmlTextAst {
let split =
this._parser.splitInterpolation(originalNode.value, originalNode.sourceSpan.toString());
let exps = isPresent(split) ? split.expressions : [];
let messageSubstring =
this._messagesContent.substring(t.startSourceSpan.end.offset, t.endSourceSpan.start.offset);
let translated =
this._replacePlaceholdersWithExpressions(messageSubstring, exps, originalNode.sourceSpan);
return new HtmlTextAst(translated, originalNode.sourceSpan);
}
private _mergeElement(t: HtmlElementAst, originalNode: HtmlElementAst,
mapping: HtmlAst[]): HtmlElementAst {
let children = this._mergeTreesHelper(t.children, mapping);
return new HtmlElementAst(originalNode.name, this._i18nAttributes(originalNode), children,
originalNode.sourceSpan, originalNode.startSourceSpan,
originalNode.endSourceSpan);
}
private _i18nAttributes(el: HtmlElementAst): HtmlAttrAst[] {
let res = [];
el.attrs.forEach(attr => {
if (attr.name.startsWith(I18N_ATTR_PREFIX) || attr.name == I18N_ATTR) return;
let i18ns = el.attrs.filter(a => a.name == `i18n-${attr.name}`);
if (i18ns.length == 0) {
res.push(attr);
return;
}
let i18n = i18ns[0];
let message = messageFromAttribute(this._parser, el, i18n);
let messageId = id(message);
if (StringMapWrapper.contains(this._messages, messageId)) {
let updatedMessage = this._replaceInterpolationInAttr(attr, this._messages[messageId]);
res.push(new HtmlAttrAst(attr.name, updatedMessage, attr.sourceSpan));
} else {
throw new I18nError(
attr.sourceSpan,
`Cannot find message for id '${messageId}', content '${message.content}'.`);
}
});
return res;
}
private _replaceInterpolationInAttr(attr: HtmlAttrAst, msg: HtmlAst[]): string {
let split = this._parser.splitInterpolation(attr.value, attr.sourceSpan.toString());
let exps = isPresent(split) ? split.expressions : [];
let first = msg[0];
let last = msg[msg.length - 1];
let start = first.sourceSpan.start.offset;
let end =
last instanceof HtmlElementAst ? last.endSourceSpan.end.offset : last.sourceSpan.end.offset;
let messageSubstring = this._messagesContent.substring(start, end);
return this._replacePlaceholdersWithExpressions(messageSubstring, exps, attr.sourceSpan);
};
private _replacePlaceholdersWithExpressions(message: string, exps: string[],
sourceSpan: ParseSourceSpan): string {
let expMap = this._buildExprMap(exps);
return RegExpWrapper.replaceAll(_PLACEHOLDER_EXPANDED_REGEXP, message, (match) => {
let nameWithQuotes = match[2];
let name = nameWithQuotes.substring(1, nameWithQuotes.length - 1);
return this._convertIntoExpression(name, expMap, sourceSpan);
});
}
private _buildExprMap(exps: string[]): Map<string, string> {
let expMap = new Map<string, string>();
let usedNames = new Map<string, number>();
for (var i = 0; i < exps.length; i++) {
let phName = getPhNameFromBinding(exps[i], i);
expMap.set(dedupePhName(usedNames, phName), exps[i]);
}
return expMap;
}
private _convertIntoExpression(name: string, expMap: Map<string, string>,
sourceSpan: ParseSourceSpan) {
if (expMap.has(name)) {
return `{{${expMap.get(name)}}}`;
} else {
throw new I18nError(sourceSpan, `Invalid interpolation name '${name}'`);
}
}
}
class _CreateNodeMapping implements HtmlAstVisitor {
mapping: HtmlAst[] = [];
visitElement(ast: HtmlElementAst, context: any): any {
this.mapping.push(ast);
htmlVisitAll(this, ast.children);
return null;
}
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, context: any): any {
this.mapping.push(ast);
return null;
}
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
visitComment(ast: HtmlCommentAst, context: any): any { return ""; }
}

View File

@ -0,0 +1,21 @@
import {isPresent, escape} from 'angular2/src/facade/lang';
/**
* A message extracted from a template.
*
* The identity of a message is comprised of `content` and `meaning`.
*
* `description` is additional information provided to the translator.
*/
export class Message {
constructor(public content: string, public meaning: string, public description: string = null) {}
}
/**
* Computes the id of a message
*/
export function id(m: Message): string {
let meaning = isPresent(m.meaning) ? m.meaning : "";
let content = isPresent(m.content) ? m.content : "";
return escape(`$ng|${meaning}|${content}`);
}

View File

@ -0,0 +1,178 @@
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
import {
HtmlAst,
HtmlAstVisitor,
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
htmlVisitAll
} from 'angular2/src/compiler/html_ast';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
import {Message, id} from './message';
import {expandNodes} from './expander';
import {
I18nError,
Part,
I18N_ATTR_PREFIX,
partition,
meaning,
description,
stringifyNodes,
messageFromAttribute
} from './shared';
/**
* All messages extracted from a template.
*/
export class ExtractionResult {
constructor(public messages: Message[], public errors: ParseError[]) {}
}
/**
* Removes duplicate messages.
*
* E.g.
*
* ```
* var m = [new Message("message", "meaning", "desc1"), new Message("message", "meaning",
* "desc2")];
* expect(removeDuplicates(m)).toEqual([new Message("message", "meaning", "desc1")]);
* ```
*/
export function removeDuplicates(messages: Message[]): Message[] {
let uniq: {[key: string]: Message} = {};
messages.forEach(m => {
if (!StringMapWrapper.contains(uniq, id(m))) {
uniq[id(m)] = m;
}
});
return StringMapWrapper.values(uniq);
}
/**
* Extracts all messages from a template.
*
* Algorithm:
*
* To understand the algorithm, you need to know how partitioning works.
* Partitioning is required as we can use two i18n comments to group node siblings together.
* That is why we cannot just use nodes.
*
* Partitioning transforms an array of HtmlAst into an array of Part.
* A part can optionally contain a root element or a root text node. And it can also contain
* children.
* A part can contain i18n property, in which case it needs to be extracted.
*
* Example:
*
* The following array of nodes will be split into four parts:
*
* ```
* <a>A</a>
* <b i18n>B</b>
* <!-- i18n -->
* <c>C</c>
* D
* <!-- /i18n -->
* E
* ```
*
* Part 1 containing the a tag. It should not be translated.
* Part 2 containing the b tag. It should be translated.
* Part 3 containing the c tag and the D text node. It should be translated.
* Part 4 containing the E text node. It should not be translated..
*
* It is also important to understand how we stringify nodes to create a message.
*
* We walk the tree and replace every element node with a placeholder. We also replace
* all expressions in interpolation with placeholders. We also insert a placeholder element
* to wrap a text node containing interpolation.
*
* Example:
*
* The following tree:
*
* ```
* <a>A{{I}}</a><b>B</b>
* ```
*
* will be stringified into:
* ```
* <ph name="e0"><ph name="t1">A<ph name="0"/></ph></ph><ph name="e2">B</ph>
* ```
*
* This is what the algorithm does:
*
* 1. Use the provided html parser to get the html AST of the template.
* 2. Partition the root nodes, and process each part separately.
* 3. If a part does not have the i18n attribute, recurse to process children and attributes.
* 4. If a part has the i18n attribute, stringify the nodes to create a Message.
*/
export class MessageExtractor {
messages: Message[];
errors: ParseError[];
constructor(private _htmlParser: HtmlParser, private _parser: Parser) {}
extract(template: string, sourceUrl: string): ExtractionResult {
this.messages = [];
this.errors = [];
let res = this._htmlParser.parse(template, sourceUrl, true);
if (res.errors.length > 0) {
return new ExtractionResult([], res.errors);
} else {
this._recurse(expandNodes(res.rootNodes).nodes);
return new ExtractionResult(this.messages, this.errors);
}
}
private _extractMessagesFromPart(p: Part): void {
if (p.hasI18n) {
this.messages.push(p.createMessage(this._parser));
this._recurseToExtractMessagesFromAttributes(p.children);
} else {
this._recurse(p.children);
}
if (isPresent(p.rootElement)) {
this._extractMessagesFromAttributes(p.rootElement);
}
}
private _recurse(nodes: HtmlAst[]): void {
if (isPresent(nodes)) {
let ps = partition(nodes, this.errors);
ps.forEach(p => this._extractMessagesFromPart(p));
}
}
private _recurseToExtractMessagesFromAttributes(nodes: HtmlAst[]): void {
nodes.forEach(n => {
if (n instanceof HtmlElementAst) {
this._extractMessagesFromAttributes(n);
this._recurseToExtractMessagesFromAttributes(n.children);
}
});
}
private _extractMessagesFromAttributes(p: HtmlElementAst): void {
p.attrs.forEach(attr => {
if (attr.name.startsWith(I18N_ATTR_PREFIX)) {
try {
this.messages.push(messageFromAttribute(this._parser, p, attr));
} catch (e) {
if (e instanceof I18nError) {
this.errors.push(e);
} else {
throw e;
}
}
}
});
}
}

View File

@ -0,0 +1,191 @@
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
import {
HtmlAst,
HtmlAstVisitor,
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst,
htmlVisitAll
} from 'angular2/src/compiler/html_ast';
import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
import {Message} from './message';
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
export const I18N_ATTR = "i18n";
export const I18N_ATTR_PREFIX = "i18n-";
var CUSTOM_PH_EXP = /\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*"([\s\S]*?)"[\s\S]*\)/g;
/**
* An i18n error.
*/
export class I18nError extends ParseError {
constructor(span: ParseSourceSpan, msg: string) { super(span, msg); }
}
// Man, this is so ugly!
export function partition(nodes: HtmlAst[], errors: ParseError[]): Part[] {
let res = [];
for (let i = 0; i < nodes.length; ++i) {
let n = nodes[i];
let temp = [];
if (_isOpeningComment(n)) {
let i18n = (<HtmlCommentAst>n).value.substring(5).trim();
i++;
while (!_isClosingComment(nodes[i])) {
temp.push(nodes[i++]);
if (i === nodes.length) {
errors.push(new I18nError(n.sourceSpan, "Missing closing 'i18n' comment."));
break;
}
}
res.push(new Part(null, null, temp, i18n, true));
} else if (n instanceof HtmlElementAst) {
let i18n = _findI18nAttr(n);
res.push(new Part(n, null, n.children, isPresent(i18n) ? i18n.value : null, isPresent(i18n)));
} else if (n instanceof HtmlTextAst) {
res.push(new Part(null, n, null, null, false));
}
}
return res;
}
export class Part {
constructor(public rootElement: HtmlElementAst, public rootTextNode: HtmlTextAst,
public children: HtmlAst[], public i18n: string, public hasI18n: boolean) {}
get sourceSpan(): ParseSourceSpan {
if (isPresent(this.rootElement))
return this.rootElement.sourceSpan;
else if (isPresent(this.rootTextNode))
return this.rootTextNode.sourceSpan;
else
return this.children[0].sourceSpan;
}
createMessage(parser: Parser): Message {
return new Message(stringifyNodes(this.children, parser), meaning(this.i18n),
description(this.i18n));
}
}
function _isOpeningComment(n: HtmlAst): boolean {
return n instanceof HtmlCommentAst && isPresent(n.value) && n.value.startsWith("i18n:");
}
function _isClosingComment(n: HtmlAst): boolean {
return n instanceof HtmlCommentAst && isPresent(n.value) && n.value == "/i18n";
}
function _findI18nAttr(p: HtmlElementAst): HtmlAttrAst {
let i18n = p.attrs.filter(a => a.name == I18N_ATTR);
return i18n.length == 0 ? null : i18n[0];
}
export function meaning(i18n: string): string {
if (isBlank(i18n) || i18n == "") return null;
return i18n.split("|")[0];
}
export function description(i18n: string): string {
if (isBlank(i18n) || i18n == "") return null;
let parts = i18n.split("|");
return parts.length > 1 ? parts[1] : null;
}
export function messageFromAttribute(parser: Parser, p: HtmlElementAst,
attr: HtmlAttrAst): Message {
let expectedName = attr.name.substring(5);
let matching = p.attrs.filter(a => a.name == expectedName);
if (matching.length > 0) {
let value = removeInterpolation(matching[0].value, matching[0].sourceSpan, parser);
return new Message(value, meaning(attr.value), description(attr.value));
} else {
throw new I18nError(p.sourceSpan, `Missing attribute '${expectedName}'.`);
}
}
export function removeInterpolation(value: string, source: ParseSourceSpan,
parser: Parser): string {
try {
let parsed = parser.splitInterpolation(value, source.toString());
let usedNames = new Map<string, number>();
if (isPresent(parsed)) {
let res = "";
for (let i = 0; i < parsed.strings.length; ++i) {
res += parsed.strings[i];
if (i != parsed.strings.length - 1) {
let customPhName = getPhNameFromBinding(parsed.expressions[i], i);
customPhName = dedupePhName(usedNames, customPhName);
res += `<ph name="${customPhName}"/>`;
}
}
return res;
} else {
return value;
}
} catch (e) {
return value;
}
}
export function getPhNameFromBinding(input: string, index: number): string {
let customPhMatch = StringWrapper.split(input, CUSTOM_PH_EXP);
return customPhMatch.length > 1 ? customPhMatch[1] : `${index}`;
}
export function dedupePhName(usedNames: Map<string, number>, name: string): string {
let duplicateNameCount = usedNames.get(name);
if (isPresent(duplicateNameCount)) {
usedNames.set(name, duplicateNameCount + 1);
return `${name}_${duplicateNameCount}`;
} else {
usedNames.set(name, 1);
return name;
}
}
export function stringifyNodes(nodes: HtmlAst[], parser: Parser): string {
let visitor = new _StringifyVisitor(parser);
return htmlVisitAll(visitor, nodes).join("");
}
class _StringifyVisitor implements HtmlAstVisitor {
private _index: number = 0;
constructor(private _parser: Parser) {}
visitElement(ast: HtmlElementAst, context: any): any {
let name = this._index++;
let children = this._join(htmlVisitAll(this, ast.children), "");
return `<ph name="e${name}">${children}</ph>`;
}
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, context: any): any {
let index = this._index++;
let noInterpolation = removeInterpolation(ast.value, ast.sourceSpan, this._parser);
if (noInterpolation != ast.value) {
return `<ph name="t${index}">${noInterpolation}</ph>`;
} else {
return ast.value;
}
}
visitComment(ast: HtmlCommentAst, context: any): any { return ""; }
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
private _join(strs: string[], str: string): string {
return strs.filter(s => s.length > 0).join(str);
}
}

View File

@ -0,0 +1,95 @@
import {isPresent, isBlank, RegExpWrapper} from 'angular2/src/facade/lang';
import {HtmlAst, HtmlElementAst} from 'angular2/src/compiler/html_ast';
import {Message, id} from './message';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {ParseSourceSpan, ParseError} from 'angular2/src/compiler/parse_util';
let _PLACEHOLDER_REGEXP = RegExpWrapper.create(`\\<ph(\\s)+name=("(\\w)+")\\/\\>`);
const _ID_ATTR = "id";
const _MSG_ELEMENT = "msg";
const _BUNDLE_ELEMENT = "message-bundle";
export function serializeXmb(messages: Message[]): string {
let ms = messages.map((m) => _serializeMessage(m)).join("");
return `<message-bundle>${ms}</message-bundle>`;
}
export class XmbDeserializationResult {
constructor(public content: string, public messages: {[key: string]: HtmlAst[]},
public errors: ParseError[]) {}
}
export class XmbDeserializationError extends ParseError {
constructor(span: ParseSourceSpan, msg: string) { super(span, msg); }
}
export function deserializeXmb(content: string, url: string): XmbDeserializationResult {
let parser = new HtmlParser();
let normalizedContent = _expandPlaceholder(content.trim());
let parsed = parser.parse(normalizedContent, url);
if (parsed.errors.length > 0) {
return new XmbDeserializationResult(null, {}, parsed.errors);
}
if (_checkRootElement(parsed.rootNodes)) {
return new XmbDeserializationResult(
null, {}, [new XmbDeserializationError(null, `Missing element "${_BUNDLE_ELEMENT}"`)]);
}
let bundleEl = <HtmlElementAst>parsed.rootNodes[0]; // test this
let errors = [];
let messages: {[key: string]: HtmlAst[]} = {};
_createMessages(bundleEl.children, messages, errors);
return (errors.length == 0) ?
new XmbDeserializationResult(normalizedContent, messages, []) :
new XmbDeserializationResult(null, <{[key: string]: HtmlAst[]}>{}, errors);
}
function _checkRootElement(nodes: HtmlAst[]): boolean {
return nodes.length < 1 || !(nodes[0] instanceof HtmlElementAst) ||
(<HtmlElementAst>nodes[0]).name != _BUNDLE_ELEMENT;
}
function _createMessages(nodes: HtmlAst[], messages: {[key: string]: HtmlAst[]},
errors: ParseError[]): void {
nodes.forEach((item) => {
if (item instanceof HtmlElementAst) {
let msg = <HtmlElementAst>item;
if (msg.name != _MSG_ELEMENT) {
errors.push(
new XmbDeserializationError(item.sourceSpan, `Unexpected element "${msg.name}"`));
return;
}
let id = _id(msg);
if (isBlank(id)) {
errors.push(
new XmbDeserializationError(item.sourceSpan, `"${_ID_ATTR}" attribute is missing`));
return;
}
messages[id] = msg.children;
}
});
}
function _id(el: HtmlElementAst): string {
let ids = el.attrs.filter(a => a.name == _ID_ATTR);
return ids.length > 0 ? ids[0].value : null;
}
function _serializeMessage(m: Message): string {
let desc = isPresent(m.description) ? ` desc='${m.description}'` : "";
return `<msg id='${id(m)}'${desc}>${m.content}</msg>`;
}
function _expandPlaceholder(input: string): string {
return RegExpWrapper.replaceAll(_PLACEHOLDER_REGEXP, input, (match) => {
let nameWithQuotes = match[2];
return `<ph name=${nameWithQuotes}></ph>`;
});
}

View File

@ -0,0 +1,218 @@
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AppView, DebugAppView} from 'angular2/src/core/linker/view';
import {StaticNodeDebugInfo, DebugContext} from 'angular2/src/core/linker/debug_context';
import {
ViewUtils,
flattenNestedViewRenderNodes,
interpolate,
checkBinding,
castByValue,
EMPTY_ARRAY,
EMPTY_MAP,
pureProxy1,
pureProxy2,
pureProxy3,
pureProxy4,
pureProxy5,
pureProxy6,
pureProxy7,
pureProxy8,
pureProxy9,
pureProxy10
} from 'angular2/src/core/linker/view_utils';
import {
uninitialized,
devModeEqual,
SimpleChange,
ValueUnwrapper,
ChangeDetectorRef,
ChangeDetectorState,
ChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';
import {AppElement} from 'angular2/src/core/linker/element';
import {ElementRef} from 'angular2/src/core/linker/element_ref';
import {ViewContainerRef} from 'angular2/src/core/linker/view_container_ref';
import {Renderer, RenderComponentType, RenderDebugInfo} from 'angular2/src/core/render/api';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {ViewType} from 'angular2/src/core/linker/view_type';
import {QueryList} from 'angular2/src/core/linker';
import {Injector} from 'angular2/src/core/di/injector';
import {TemplateRef, TemplateRef_} from 'angular2/src/core/linker/template_ref';
import {MODULE_SUFFIX} from './util';
var APP_VIEW_MODULE_URL = 'asset:angular2/lib/src/core/linker/view' + MODULE_SUFFIX;
var VIEW_UTILS_MODULE_URL = 'asset:angular2/lib/src/core/linker/view_utils' + MODULE_SUFFIX;
var CD_MODULE_URL = 'asset:angular2/lib/src/core/change_detection/change_detection' + MODULE_SUFFIX;
// Reassign the imports to different variables so we can
// define static variables with the name of the import.
// (only needed for Dart).
var impViewUtils = ViewUtils;
var impAppView = AppView;
var impDebugAppView = DebugAppView;
var impDebugContext = DebugContext;
var impAppElement = AppElement;
var impElementRef = ElementRef;
var impViewContainerRef = ViewContainerRef;
var impChangeDetectorRef = ChangeDetectorRef;
var impRenderComponentType = RenderComponentType;
var impQueryList = QueryList;
var impTemplateRef = TemplateRef;
var impTemplateRef_ = TemplateRef_;
var impValueUnwrapper = ValueUnwrapper;
var impInjector = Injector;
var impViewEncapsulation = ViewEncapsulation;
var impViewType = ViewType;
var impChangeDetectionStrategy = ChangeDetectionStrategy;
var impStaticNodeDebugInfo = StaticNodeDebugInfo;
var impRenderer = Renderer;
var impSimpleChange = SimpleChange;
var impUninitialized = uninitialized;
var impChangeDetectorState = ChangeDetectorState;
var impFlattenNestedViewRenderNodes = flattenNestedViewRenderNodes;
var impDevModeEqual = devModeEqual;
var impInterpolate = interpolate;
var impCheckBinding = checkBinding;
var impCastByValue = castByValue;
var impEMPTY_ARRAY = EMPTY_ARRAY;
var impEMPTY_MAP = EMPTY_MAP;
export class Identifiers {
static ViewUtils = new CompileIdentifierMetadata({
name: 'ViewUtils',
moduleUrl: 'asset:angular2/lib/src/core/linker/view_utils' + MODULE_SUFFIX,
runtime: impViewUtils
});
static AppView = new CompileIdentifierMetadata(
{name: 'AppView', moduleUrl: APP_VIEW_MODULE_URL, runtime: impAppView});
static DebugAppView = new CompileIdentifierMetadata(
{name: 'DebugAppView', moduleUrl: APP_VIEW_MODULE_URL, runtime: impDebugAppView});
static AppElement = new CompileIdentifierMetadata({
name: 'AppElement',
moduleUrl: 'asset:angular2/lib/src/core/linker/element' + MODULE_SUFFIX,
runtime: impAppElement
});
static ElementRef = new CompileIdentifierMetadata({
name: 'ElementRef',
moduleUrl: 'asset:angular2/lib/src/core/linker/element_ref' + MODULE_SUFFIX,
runtime: impElementRef
});
static ViewContainerRef = new CompileIdentifierMetadata({
name: 'ViewContainerRef',
moduleUrl: 'asset:angular2/lib/src/core/linker/view_container_ref' + MODULE_SUFFIX,
runtime: impViewContainerRef
});
static ChangeDetectorRef = new CompileIdentifierMetadata({
name: 'ChangeDetectorRef',
moduleUrl: 'asset:angular2/lib/src/core/change_detection/change_detector_ref' + MODULE_SUFFIX,
runtime: impChangeDetectorRef
});
static RenderComponentType = new CompileIdentifierMetadata({
name: 'RenderComponentType',
moduleUrl: 'asset:angular2/lib/src/core/render/api' + MODULE_SUFFIX,
runtime: impRenderComponentType
});
static QueryList = new CompileIdentifierMetadata({
name: 'QueryList',
moduleUrl: 'asset:angular2/lib/src/core/linker/query_list' + MODULE_SUFFIX,
runtime: impQueryList
});
static TemplateRef = new CompileIdentifierMetadata({
name: 'TemplateRef',
moduleUrl: 'asset:angular2/lib/src/core/linker/template_ref' + MODULE_SUFFIX,
runtime: impTemplateRef
});
static TemplateRef_ = new CompileIdentifierMetadata({
name: 'TemplateRef_',
moduleUrl: 'asset:angular2/lib/src/core/linker/template_ref' + MODULE_SUFFIX,
runtime: impTemplateRef_
});
static ValueUnwrapper = new CompileIdentifierMetadata(
{name: 'ValueUnwrapper', moduleUrl: CD_MODULE_URL, runtime: impValueUnwrapper});
static Injector = new CompileIdentifierMetadata({
name: 'Injector',
moduleUrl: `asset:angular2/lib/src/core/di/injector${MODULE_SUFFIX}`,
runtime: impInjector
});
static ViewEncapsulation = new CompileIdentifierMetadata({
name: 'ViewEncapsulation',
moduleUrl: 'asset:angular2/lib/src/core/metadata/view' + MODULE_SUFFIX,
runtime: impViewEncapsulation
});
static ViewType = new CompileIdentifierMetadata({
name: 'ViewType',
moduleUrl: `asset:angular2/lib/src/core/linker/view_type${MODULE_SUFFIX}`,
runtime: impViewType
});
static ChangeDetectionStrategy = new CompileIdentifierMetadata({
name: 'ChangeDetectionStrategy',
moduleUrl: CD_MODULE_URL,
runtime: impChangeDetectionStrategy
});
static StaticNodeDebugInfo = new CompileIdentifierMetadata({
name: 'StaticNodeDebugInfo',
moduleUrl: `asset:angular2/lib/src/core/linker/debug_context${MODULE_SUFFIX}`,
runtime: impStaticNodeDebugInfo
});
static DebugContext = new CompileIdentifierMetadata({
name: 'DebugContext',
moduleUrl: `asset:angular2/lib/src/core/linker/debug_context${MODULE_SUFFIX}`,
runtime: impDebugContext
});
static Renderer = new CompileIdentifierMetadata({
name: 'Renderer',
moduleUrl: 'asset:angular2/lib/src/core/render/api' + MODULE_SUFFIX,
runtime: impRenderer
});
static SimpleChange = new CompileIdentifierMetadata(
{name: 'SimpleChange', moduleUrl: CD_MODULE_URL, runtime: impSimpleChange});
static uninitialized = new CompileIdentifierMetadata(
{name: 'uninitialized', moduleUrl: CD_MODULE_URL, runtime: impUninitialized});
static ChangeDetectorState = new CompileIdentifierMetadata(
{name: 'ChangeDetectorState', moduleUrl: CD_MODULE_URL, runtime: impChangeDetectorState});
static checkBinding = new CompileIdentifierMetadata(
{name: 'checkBinding', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impCheckBinding});
static flattenNestedViewRenderNodes = new CompileIdentifierMetadata({
name: 'flattenNestedViewRenderNodes',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: impFlattenNestedViewRenderNodes
});
static devModeEqual = new CompileIdentifierMetadata(
{name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: impDevModeEqual});
static interpolate = new CompileIdentifierMetadata(
{name: 'interpolate', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impInterpolate});
static castByValue = new CompileIdentifierMetadata(
{name: 'castByValue', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impCastByValue});
static EMPTY_ARRAY = new CompileIdentifierMetadata(
{name: 'EMPTY_ARRAY', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impEMPTY_ARRAY});
static EMPTY_MAP = new CompileIdentifierMetadata(
{name: 'EMPTY_MAP', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impEMPTY_MAP});
static pureProxies = [
null,
new CompileIdentifierMetadata(
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy1}),
new CompileIdentifierMetadata(
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy2}),
new CompileIdentifierMetadata(
{name: 'pureProxy3', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy3}),
new CompileIdentifierMetadata(
{name: 'pureProxy4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy4}),
new CompileIdentifierMetadata(
{name: 'pureProxy5', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy5}),
new CompileIdentifierMetadata(
{name: 'pureProxy6', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy6}),
new CompileIdentifierMetadata(
{name: 'pureProxy7', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy7}),
new CompileIdentifierMetadata(
{name: 'pureProxy8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy8}),
new CompileIdentifierMetadata(
{name: 'pureProxy9', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy9}),
new CompileIdentifierMetadata(
{name: 'pureProxy10', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy10}),
];
}
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
return new CompileTokenMetadata({identifier: identifier});
}

View File

@ -0,0 +1,229 @@
import {Injectable, Provider, provide} from 'angular2/src/core/di';
import {StringWrapper, RegExpWrapper, isBlank, isPresent} from 'angular2/src/facade/lang';
import {
HtmlAstVisitor,
HtmlAttrAst,
HtmlElementAst,
HtmlTextAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst,
HtmlAst
} from './html_ast';
import {HtmlParser, HtmlParseTreeResult} from './html_parser';
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var LONG_SYNTAX_REGEXP = /^(?:on-(.*)|bindon-(.*)|bind-(.*)|var-(.*))$/ig;
var SHORT_SYNTAX_REGEXP = /^(?:\((.*)\)|\[\((.*)\)\]|\[(.*)\]|#(.*))$/ig;
var VARIABLE_TPL_BINDING_REGEXP = /(\bvar\s+|#)(\S+)/ig;
var TEMPLATE_SELECTOR_REGEXP = /^(\S+)/g;
var SPECIAL_PREFIXES_REGEXP = /^(class|style|attr)\./ig;
var INTERPOLATION_REGEXP = /\{\{.*?\}\}/g;
const SPECIAL_CASES = /*@ts2dart_const*/[
'ng-non-bindable',
'ng-default-control',
'ng-no-form',
];
/**
* Convert templates to the case sensitive syntax
*
* @internal
*/
export class LegacyHtmlAstTransformer implements HtmlAstVisitor {
rewrittenAst: HtmlAst[] = [];
visitingTemplateEl: boolean = false;
constructor(private dashCaseSelectors?: string[]) {}
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
visitElement(ast: HtmlElementAst, context: any): HtmlElementAst {
this.visitingTemplateEl = ast.name.toLowerCase() == 'template';
let attrs = ast.attrs.map(attr => attr.visit(this, null));
let children = ast.children.map(child => child.visit(this, null));
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan, ast.startSourceSpan,
ast.endSourceSpan);
}
visitAttr(originalAst: HtmlAttrAst, context: any): HtmlAttrAst {
let ast = originalAst;
if (this.visitingTemplateEl) {
if (isPresent(RegExpWrapper.firstMatch(LONG_SYNTAX_REGEXP, ast.name))) {
// preserve the "-" in the prefix for the long syntax
ast = this._rewriteLongSyntax(ast);
} else {
// rewrite any other attribute
let name = dashCaseToCamelCase(ast.name);
ast = name == ast.name ? ast : new HtmlAttrAst(name, ast.value, ast.sourceSpan);
}
} else {
ast = this._rewriteTemplateAttribute(ast);
ast = this._rewriteLongSyntax(ast);
ast = this._rewriteShortSyntax(ast);
ast = this._rewriteStar(ast);
ast = this._rewriteInterpolation(ast);
ast = this._rewriteSpecialCases(ast);
}
if (ast !== originalAst) {
this.rewrittenAst.push(ast);
}
return ast;
}
visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; }
visitExpansion(ast: HtmlExpansionAst, context: any): any {
let cases = ast.cases.map(c => c.visit(this, null));
return new HtmlExpansionAst(ast.switchValue, ast.type, cases, ast.sourceSpan,
ast.switchValueSourceSpan);
}
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
private _rewriteLongSyntax(ast: HtmlAttrAst): HtmlAttrAst {
let m = RegExpWrapper.firstMatch(LONG_SYNTAX_REGEXP, ast.name);
let attrName = ast.name;
let attrValue = ast.value;
if (isPresent(m)) {
if (isPresent(m[1])) {
attrName = `on-${dashCaseToCamelCase(m[1])}`;
} else if (isPresent(m[2])) {
attrName = `bindon-${dashCaseToCamelCase(m[2])}`;
} else if (isPresent(m[3])) {
attrName = `bind-${dashCaseToCamelCase(m[3])}`;
} else if (isPresent(m[4])) {
attrName = `var-${dashCaseToCamelCase(m[4])}`;
attrValue = dashCaseToCamelCase(attrValue);
}
}
return attrName == ast.name && attrValue == ast.value ?
ast :
new HtmlAttrAst(attrName, attrValue, ast.sourceSpan);
}
private _rewriteTemplateAttribute(ast: HtmlAttrAst): HtmlAttrAst {
let name = ast.name;
let value = ast.value;
if (name.toLowerCase() == 'template') {
name = 'template';
// rewrite the directive selector
value = StringWrapper.replaceAllMapped(value, TEMPLATE_SELECTOR_REGEXP,
(m) => { return dashCaseToCamelCase(m[1]); });
// rewrite the var declarations
value = StringWrapper.replaceAllMapped(value, VARIABLE_TPL_BINDING_REGEXP, m => {
return `${m[1].toLowerCase()}${dashCaseToCamelCase(m[2])}`;
});
}
if (name == ast.name && value == ast.value) {
return ast;
}
return new HtmlAttrAst(name, value, ast.sourceSpan);
}
private _rewriteShortSyntax(ast: HtmlAttrAst): HtmlAttrAst {
let m = RegExpWrapper.firstMatch(SHORT_SYNTAX_REGEXP, ast.name);
let attrName = ast.name;
let attrValue = ast.value;
if (isPresent(m)) {
if (isPresent(m[1])) {
attrName = `(${dashCaseToCamelCase(m[1])})`;
} else if (isPresent(m[2])) {
attrName = `[(${dashCaseToCamelCase(m[2])})]`;
} else if (isPresent(m[3])) {
let prop = StringWrapper.replaceAllMapped(m[3], SPECIAL_PREFIXES_REGEXP,
(m) => { return m[1].toLowerCase() + '.'; });
if (prop.startsWith('class.') || prop.startsWith('attr.') || prop.startsWith('style.')) {
attrName = `[${prop}]`;
} else {
attrName = `[${dashCaseToCamelCase(prop)}]`;
}
} else if (isPresent(m[4])) {
attrName = `#${dashCaseToCamelCase(m[4])}`;
attrValue = dashCaseToCamelCase(attrValue);
}
}
return attrName == ast.name && attrValue == ast.value ?
ast :
new HtmlAttrAst(attrName, attrValue, ast.sourceSpan);
}
private _rewriteStar(ast: HtmlAttrAst): HtmlAttrAst {
let attrName = ast.name;
let attrValue = ast.value;
if (attrName[0] == '*') {
attrName = dashCaseToCamelCase(attrName);
// rewrite the var declarations
attrValue = StringWrapper.replaceAllMapped(attrValue, VARIABLE_TPL_BINDING_REGEXP, m => {
return `${m[1].toLowerCase()}${dashCaseToCamelCase(m[2])}`;
});
}
return attrName == ast.name && attrValue == ast.value ?
ast :
new HtmlAttrAst(attrName, attrValue, ast.sourceSpan);
}
private _rewriteInterpolation(ast: HtmlAttrAst): HtmlAttrAst {
let hasInterpolation = RegExpWrapper.test(INTERPOLATION_REGEXP, ast.value);
if (!hasInterpolation) {
return ast;
}
let name = ast.name;
if (!(name.startsWith('attr.') || name.startsWith('class.') || name.startsWith('style.'))) {
name = dashCaseToCamelCase(ast.name);
}
return name == ast.name ? ast : new HtmlAttrAst(name, ast.value, ast.sourceSpan);
}
private _rewriteSpecialCases(ast: HtmlAttrAst): HtmlAttrAst {
let attrName = ast.name;
if (SPECIAL_CASES.indexOf(attrName) > -1) {
return new HtmlAttrAst(dashCaseToCamelCase(attrName), ast.value, ast.sourceSpan);
}
if (isPresent(this.dashCaseSelectors) && this.dashCaseSelectors.indexOf(attrName) > -1) {
return new HtmlAttrAst(dashCaseToCamelCase(attrName), ast.value, ast.sourceSpan);
}
return ast;
}
}
@Injectable()
export class LegacyHtmlParser extends HtmlParser {
parse(sourceContent: string, sourceUrl: string,
parseExpansionForms: boolean = false): HtmlParseTreeResult {
let transformer = new LegacyHtmlAstTransformer();
let htmlParseTreeResult = super.parse(sourceContent, sourceUrl, parseExpansionForms);
let rootNodes = htmlParseTreeResult.rootNodes.map(node => node.visit(transformer, null));
return transformer.rewrittenAst.length > 0 ?
new HtmlParseTreeResult(rootNodes, htmlParseTreeResult.errors) :
htmlParseTreeResult;
}
}

View File

@ -0,0 +1,433 @@
import {resolveForwardRef} from 'angular2/src/core/di';
import {
Type,
isBlank,
isPresent,
isArray,
stringify,
isString,
isStringMap,
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as cpl from './compile_metadata';
import * as md from 'angular2/src/core/metadata/directives';
import * as dimd from 'angular2/src/core/metadata/di';
import {DirectiveResolver} from './directive_resolver';
import {PipeResolver} from './pipe_resolver';
import {ViewResolver} from './view_resolver';
import {ViewMetadata} from 'angular2/src/core/metadata/view';
import {hasLifecycleHook} from './directive_lifecycle_reflector';
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/metadata/lifecycle_hooks';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {Injectable, Inject, Optional} from 'angular2/src/core/di';
import {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes';
import {MODULE_SUFFIX, sanitizeIdentifier, ValueTransformer, visitValue} from './util';
import {assertArrayOfStrings} from './assertions';
import {getUrlScheme} from 'angular2/src/compiler/url_resolver';
import {Provider} from 'angular2/src/core/di/provider';
import {
OptionalMetadata,
SelfMetadata,
HostMetadata,
SkipSelfMetadata,
InjectMetadata
} from 'angular2/src/core/di/metadata';
import {AttributeMetadata, QueryMetadata} from 'angular2/src/core/metadata/di';
import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
import {isProviderLiteral, createProvider} from '../core/di/provider_util';
@Injectable()
export class CompileMetadataResolver {
private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>();
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
private _anonymousTypes = new Map<Object, number>();
private _anonymousTypeIndex = 0;
private _reflector: ReflectorReader;
constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
private _viewResolver: ViewResolver,
@Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[],
@Optional() @Inject(PLATFORM_PIPES) private _platformPipes: Type[],
_reflector?: ReflectorReader) {
if (isPresent(_reflector)) {
this._reflector = _reflector;
} else {
this._reflector = reflector;
}
}
private sanitizeTokenName(token: any): string {
let identifier = stringify(token);
if (identifier.indexOf('(') >= 0) {
// case: anonymous functions!
let found = this._anonymousTypes.get(token);
if (isBlank(found)) {
this._anonymousTypes.set(token, this._anonymousTypeIndex++);
found = this._anonymousTypes.get(token);
}
identifier = `anonymous_token_${found}_`;
}
return sanitizeIdentifier(identifier);
}
getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
var meta = this._directiveCache.get(directiveType);
if (isBlank(meta)) {
var dirMeta = this._directiveResolver.resolve(directiveType);
var templateMeta = null;
var changeDetectionStrategy = null;
var viewProviders = [];
if (dirMeta instanceof md.ComponentMetadata) {
assertArrayOfStrings('styles', dirMeta.styles);
var cmpMeta = <md.ComponentMetadata>dirMeta;
var viewMeta = this._viewResolver.resolve(directiveType);
assertArrayOfStrings('styles', viewMeta.styles);
templateMeta = new cpl.CompileTemplateMetadata({
encapsulation: viewMeta.encapsulation,
template: viewMeta.template,
templateUrl: viewMeta.templateUrl,
styles: viewMeta.styles,
styleUrls: viewMeta.styleUrls,
baseUrl: calcTemplateBaseUrl(this._reflector, directiveType, cmpMeta)
});
changeDetectionStrategy = cmpMeta.changeDetection;
if (isPresent(dirMeta.viewProviders)) {
viewProviders = this.getProvidersMetadata(dirMeta.viewProviders);
}
}
var providers = [];
if (isPresent(dirMeta.providers)) {
providers = this.getProvidersMetadata(dirMeta.providers);
}
var queries = [];
var viewQueries = [];
if (isPresent(dirMeta.queries)) {
queries = this.getQueriesMetadata(dirMeta.queries, false);
viewQueries = this.getQueriesMetadata(dirMeta.queries, true);
}
meta = cpl.CompileDirectiveMetadata.create({
selector: dirMeta.selector,
exportAs: dirMeta.exportAs,
isComponent: isPresent(templateMeta),
type: this.getTypeMetadata(directiveType, staticTypeModuleUrl(directiveType)),
template: templateMeta,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs,
outputs: dirMeta.outputs,
host: dirMeta.host,
lifecycleHooks:
LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, directiveType)),
providers: providers,
viewProviders: viewProviders,
queries: queries,
viewQueries: viewQueries
});
this._directiveCache.set(directiveType, meta);
}
return meta;
}
/**
* @param someType a symbol which may or may not be a directive type
* @returns {cpl.CompileDirectiveMetadata} if possible, otherwise null.
*/
maybeGetDirectiveMetadata(someType: Type): cpl.CompileDirectiveMetadata {
try {
return this.getDirectiveMetadata(someType);
} catch (e) {
if (e.message.indexOf('No Directive annotation') !== -1) {
return null;
}
throw e;
}
}
getTypeMetadata(type: Type, moduleUrl: string): cpl.CompileTypeMetadata {
return new cpl.CompileTypeMetadata({
name: this.sanitizeTokenName(type),
moduleUrl: moduleUrl,
runtime: type,
diDeps: this.getDependenciesMetadata(type, null)
});
}
getFactoryMetadata(factory: Function, moduleUrl: string): cpl.CompileFactoryMetadata {
return new cpl.CompileFactoryMetadata({
name: this.sanitizeTokenName(factory),
moduleUrl: moduleUrl,
runtime: factory,
diDeps: this.getDependenciesMetadata(factory, null)
});
}
getPipeMetadata(pipeType: Type): cpl.CompilePipeMetadata {
var meta = this._pipeCache.get(pipeType);
if (isBlank(meta)) {
var pipeMeta = this._pipeResolver.resolve(pipeType);
meta = new cpl.CompilePipeMetadata({
type: this.getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeMeta.name,
pure: pipeMeta.pure,
lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, pipeType)),
});
this._pipeCache.set(pipeType, meta);
}
return meta;
}
getViewDirectivesMetadata(component: Type): cpl.CompileDirectiveMetadata[] {
var view = this._viewResolver.resolve(component);
var directives = flattenDirectives(view, this._platformDirectives);
for (var i = 0; i < directives.length; i++) {
if (!isValidType(directives[i])) {
throw new BaseException(
`Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`);
}
}
return directives.map(type => this.getDirectiveMetadata(type));
}
getViewPipesMetadata(component: Type): cpl.CompilePipeMetadata[] {
var view = this._viewResolver.resolve(component);
var pipes = flattenPipes(view, this._platformPipes);
for (var i = 0; i < pipes.length; i++) {
if (!isValidType(pipes[i])) {
throw new BaseException(
`Unexpected piped value '${stringify(pipes[i])}' on the View of component '${stringify(component)}'`);
}
}
return pipes.map(type => this.getPipeMetadata(type));
}
getDependenciesMetadata(typeOrFunc: Type | Function,
dependencies: any[]): cpl.CompileDiDependencyMetadata[] {
let params = isPresent(dependencies) ? dependencies : this._reflector.parameters(typeOrFunc);
if (isBlank(params)) {
params = [];
}
return params.map((param) => {
if (isBlank(param)) {
return null;
}
let isAttribute = false;
let isHost = false;
let isSelf = false;
let isSkipSelf = false;
let isOptional = false;
let query: dimd.QueryMetadata = null;
let viewQuery: dimd.ViewQueryMetadata = null;
var token = null;
if (isArray(param)) {
(<any[]>param)
.forEach((paramEntry) => {
if (paramEntry instanceof HostMetadata) {
isHost = true;
} else if (paramEntry instanceof SelfMetadata) {
isSelf = true;
} else if (paramEntry instanceof SkipSelfMetadata) {
isSkipSelf = true;
} else if (paramEntry instanceof OptionalMetadata) {
isOptional = true;
} else if (paramEntry instanceof AttributeMetadata) {
isAttribute = true;
token = paramEntry.attributeName;
} else if (paramEntry instanceof QueryMetadata) {
if (paramEntry.isViewQuery) {
viewQuery = paramEntry;
} else {
query = paramEntry;
}
} else if (paramEntry instanceof InjectMetadata) {
token = paramEntry.token;
} else if (isValidType(paramEntry) && isBlank(token)) {
token = paramEntry;
}
});
} else {
token = param;
}
if (isBlank(token)) {
return null;
}
return new cpl.CompileDiDependencyMetadata({
isAttribute: isAttribute,
isHost: isHost,
isSelf: isSelf,
isSkipSelf: isSkipSelf,
isOptional: isOptional,
query: isPresent(query) ? this.getQueryMetadata(query, null) : null,
viewQuery: isPresent(viewQuery) ? this.getQueryMetadata(viewQuery, null) : null,
token: this.getTokenMetadata(token)
});
});
}
getTokenMetadata(token: any): cpl.CompileTokenMetadata {
token = resolveForwardRef(token);
var compileToken;
if (isString(token)) {
compileToken = new cpl.CompileTokenMetadata({value: token});
} else {
compileToken = new cpl.CompileTokenMetadata({
identifier: new cpl.CompileIdentifierMetadata({
runtime: token,
name: this.sanitizeTokenName(token),
moduleUrl: staticTypeModuleUrl(token)
})
});
}
return compileToken;
}
getProvidersMetadata(providers: any[]):
Array<cpl.CompileProviderMetadata | cpl.CompileTypeMetadata | any[]> {
return providers.map((provider) => {
provider = resolveForwardRef(provider);
if (isArray(provider)) {
return this.getProvidersMetadata(provider);
} else if (provider instanceof Provider) {
return this.getProviderMetadata(provider);
} else if (isProviderLiteral(provider)) {
return this.getProviderMetadata(createProvider(provider));
} else {
return this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
}
});
}
getProviderMetadata(provider: Provider): cpl.CompileProviderMetadata {
var compileDeps;
if (isPresent(provider.useClass)) {
compileDeps = this.getDependenciesMetadata(provider.useClass, provider.dependencies);
} else if (isPresent(provider.useFactory)) {
compileDeps = this.getDependenciesMetadata(provider.useFactory, provider.dependencies);
}
return new cpl.CompileProviderMetadata({
token: this.getTokenMetadata(provider.token),
useClass:
isPresent(provider.useClass) ?
this.getTypeMetadata(provider.useClass, staticTypeModuleUrl(provider.useClass)) :
null,
useValue: convertToCompileValue(provider.useValue),
useFactory: isPresent(provider.useFactory) ?
this.getFactoryMetadata(provider.useFactory,
staticTypeModuleUrl(provider.useFactory)) :
null,
useExisting: isPresent(provider.useExisting) ? this.getTokenMetadata(provider.useExisting) :
null,
deps: compileDeps,
multi: provider.multi
});
}
getQueriesMetadata(queries: {[key: string]: dimd.QueryMetadata},
isViewQuery: boolean): cpl.CompileQueryMetadata[] {
var compileQueries = [];
StringMapWrapper.forEach(queries, (query, propertyName) => {
if (query.isViewQuery === isViewQuery) {
compileQueries.push(this.getQueryMetadata(query, propertyName));
}
});
return compileQueries;
}
getQueryMetadata(q: dimd.QueryMetadata, propertyName: string): cpl.CompileQueryMetadata {
var selectors;
if (q.isVarBindingQuery) {
selectors = q.varBindings.map(varName => this.getTokenMetadata(varName));
} else {
selectors = [this.getTokenMetadata(q.selector)];
}
return new cpl.CompileQueryMetadata({
selectors: selectors,
first: q.first,
descendants: q.descendants,
propertyName: propertyName,
read: isPresent(q.read) ? this.getTokenMetadata(q.read) : null
});
}
}
function flattenDirectives(view: ViewMetadata, platformDirectives: any[]): Type[] {
let directives = [];
if (isPresent(platformDirectives)) {
flattenArray(platformDirectives, directives);
}
if (isPresent(view.directives)) {
flattenArray(view.directives, directives);
}
return directives;
}
function flattenPipes(view: ViewMetadata, platformPipes: any[]): Type[] {
let pipes = [];
if (isPresent(platformPipes)) {
flattenArray(platformPipes, pipes);
}
if (isPresent(view.pipes)) {
flattenArray(view.pipes, pipes);
}
return pipes;
}
function flattenArray(tree: any[], out: Array<Type | any[]>): void {
for (var i = 0; i < tree.length; i++) {
var item = resolveForwardRef(tree[i]);
if (isArray(item)) {
flattenArray(item, out);
} else {
out.push(item);
}
}
}
function isStaticType(value: any): boolean {
return isStringMap(value) && isPresent(value['name']) && isPresent(value['moduleId']);
}
function isValidType(value: any): boolean {
return isStaticType(value) || (value instanceof Type);
}
function staticTypeModuleUrl(value: any): string {
return isStaticType(value) ? value['moduleId'] : null;
}
function calcTemplateBaseUrl(reflector: ReflectorReader, type: any,
cmpMetadata: md.ComponentMetadata): string {
if (isStaticType(type)) {
return type['filePath'];
}
if (isPresent(cmpMetadata.moduleId)) {
var moduleId = cmpMetadata.moduleId;
var scheme = getUrlScheme(moduleId);
return isPresent(scheme) && scheme.length > 0 ? moduleId :
`package:${moduleId}${MODULE_SUFFIX}`;
}
return reflector.importUri(type);
}
// Only fill CompileIdentifierMetadata.runtime if needed...
function convertToCompileValue(value: any): any {
return visitValue(value, new _CompileValueConverter(), null);
}
class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, context: any): any {
if (isStaticType(value)) {
return new cpl.CompileIdentifierMetadata(
{name: value['name'], moduleUrl: staticTypeModuleUrl(value)});
} else {
return new cpl.CompileIdentifierMetadata({runtime: value});
}
}
}

View File

@ -0,0 +1,141 @@
import {
CompileDirectiveMetadata,
CompileIdentifierMetadata,
CompilePipeMetadata,
createHostComponentMeta
} from './compile_metadata';
import {BaseException, unimplemented} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection';
import {StyleCompiler, StylesCompileDependency, StylesCompileResult} from './style_compiler';
import {ViewCompiler, ViewCompileResult} from './view_compiler/view_compiler';
import {TemplateParser} from './template_parser';
import {DirectiveNormalizer} from './directive_normalizer';
import {OutputEmitter} from './output/abstract_emitter';
import * as o from './output/output_ast';
import {ComponentFactory} from 'angular2/src/core/linker/component_factory';
import {
MODULE_SUFFIX,
} from './util';
var _COMPONENT_FACTORY_IDENTIFIER = new CompileIdentifierMetadata({
name: 'ComponentFactory',
runtime: ComponentFactory,
moduleUrl: `asset:angular2/lib/src/core/linker/component_factory${MODULE_SUFFIX}`
});
export class SourceModule {
constructor(public moduleUrl: string, public source: string) {}
}
export class NormalizedComponentWithViewDirectives {
constructor(public component: CompileDirectiveMetadata,
public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {}
}
export class OfflineCompiler {
constructor(private _directiveNormalizer: DirectiveNormalizer,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _outputEmitter: OutputEmitter) {}
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata):
Promise<CompileDirectiveMetadata> {
return this._directiveNormalizer.normalizeDirective(directive);
}
compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule {
if (components.length === 0) {
throw new BaseException('No components given');
}
var statements = [];
var exportedVars = [];
var moduleUrl = _templateModuleUrl(components[0].component);
components.forEach(componentWithDirs => {
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
_assertComponent(compMeta);
var compViewFactoryVar = this._compileComponent(compMeta, componentWithDirs.directives,
componentWithDirs.pipes, statements);
exportedVars.push(compViewFactoryVar);
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
var hostViewFactoryVar = this._compileComponent(hostMeta, [compMeta], [], statements);
var compFactoryVar = `${compMeta.type.name}NgFactory`;
statements.push(
o.variable(compFactoryVar)
.set(o.importExpr(_COMPONENT_FACTORY_IDENTIFIER, [o.importType(compMeta.type)])
.instantiate(
[
o.literal(compMeta.selector),
o.variable(hostViewFactoryVar),
o.importExpr(compMeta.type)
],
o.importType(_COMPONENT_FACTORY_IDENTIFIER,
[o.importType(compMeta.type)], [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]));
exportedVars.push(compFactoryVar);
});
return this._codegenSourceModule(moduleUrl, statements, exportedVars);
}
compileStylesheet(stylesheetUrl: string, cssText: string): SourceModule[] {
var plainStyles = this._styleCompiler.compileStylesheet(stylesheetUrl, cssText, false);
var shimStyles = this._styleCompiler.compileStylesheet(stylesheetUrl, cssText, true);
return [
this._codegenSourceModule(_stylesModuleUrl(stylesheetUrl, false),
_resolveStyleStatements(plainStyles), [plainStyles.stylesVar]),
this._codegenSourceModule(_stylesModuleUrl(stylesheetUrl, true),
_resolveStyleStatements(shimStyles), [shimStyles.stylesVar])
];
}
private _compileComponent(compMeta: CompileDirectiveMetadata,
directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
targetStatements: o.Statement[]): string {
var styleResult = this._styleCompiler.compileComponent(compMeta);
var parsedTemplate = this._templateParser.parse(compMeta, compMeta.template.template,
directives, pipes, compMeta.type.name);
var viewResult = this._viewCompiler.compileComponent(compMeta, parsedTemplate,
o.variable(styleResult.stylesVar), pipes);
ListWrapper.addAll(targetStatements, _resolveStyleStatements(styleResult));
ListWrapper.addAll(targetStatements, _resolveViewStatements(viewResult));
return viewResult.viewFactoryVar;
}
private _codegenSourceModule(moduleUrl: string, statements: o.Statement[],
exportedVars: string[]): SourceModule {
return new SourceModule(
moduleUrl, this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
}
}
function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] {
compileResult.dependencies.forEach(
(dep) => { dep.factoryPlaceholder.moduleUrl = _templateModuleUrl(dep.comp); });
return compileResult.statements;
}
function _resolveStyleStatements(compileResult: StylesCompileResult): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.sourceUrl, dep.isShimmed);
});
return compileResult.statements;
}
function _templateModuleUrl(comp: CompileDirectiveMetadata): string {
var moduleUrl = comp.type.moduleUrl;
var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length);
return `${urlWithoutSuffix}.ngfactory${MODULE_SUFFIX}`;
}
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean): string {
return shim ? `${stylesheetUrl}.shim${MODULE_SUFFIX}` : `${stylesheetUrl}${MODULE_SUFFIX}`;
}
function _assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`);
}
}

View File

@ -0,0 +1,426 @@
import {
isPresent,
isBlank,
isString,
evalExpression,
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {BaseException, unimplemented} from 'angular2/src/facade/exceptions';
import * as o from './output_ast';
var _SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\r|\$/g;
export var CATCH_ERROR_VAR = o.variable('error');
export var CATCH_STACK_VAR = o.variable('stack');
export abstract class OutputEmitter {
abstract emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string;
}
class _EmittedLine {
parts: string[] = [];
constructor(public indent: number) {}
}
export class EmitterVisitorContext {
static createRoot(exportedVars: string[]): EmitterVisitorContext {
return new EmitterVisitorContext(exportedVars, 0);
}
private _lines: _EmittedLine[];
private _classes: o.ClassStmt[] = [];
constructor(private _exportedVars: string[], private _indent: number) {
this._lines = [new _EmittedLine(_indent)];
}
private get _currentLine(): _EmittedLine { return this._lines[this._lines.length - 1]; }
isExportedVar(varName: string): boolean { return this._exportedVars.indexOf(varName) !== -1; }
println(lastPart: string = ''): void { this.print(lastPart, true); }
lineIsEmpty(): boolean { return this._currentLine.parts.length === 0; }
print(part: string, newLine: boolean = false) {
if (part.length > 0) {
this._currentLine.parts.push(part);
}
if (newLine) {
this._lines.push(new _EmittedLine(this._indent));
}
}
removeEmptyLastLine() {
if (this.lineIsEmpty()) {
this._lines.pop();
}
}
incIndent() {
this._indent++;
this._currentLine.indent = this._indent;
}
decIndent() {
this._indent--;
this._currentLine.indent = this._indent;
}
pushClass(clazz: o.ClassStmt) { this._classes.push(clazz); }
popClass(): o.ClassStmt { return this._classes.pop(); }
get currentClass(): o.ClassStmt {
return this._classes.length > 0 ? this._classes[this._classes.length - 1] : null;
}
toSource(): any {
var 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 '';
}
})
.join('\n');
}
}
export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.ExpressionVisitor {
constructor(private _escapeDollarInStrings: boolean) {}
visitExpressionStmt(stmt: o.ExpressionStatement, ctx: EmitterVisitorContext): any {
stmt.expr.visitExpression(this, ctx);
ctx.println(';');
return null;
}
visitReturnStmt(stmt: o.ReturnStatement, ctx: EmitterVisitorContext): any {
ctx.print(`return `);
stmt.value.visitExpression(this, ctx);
ctx.println(';');
return null;
}
abstract visitCastExpr(ast: o.CastExpr, context: any): any;
abstract visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any;
visitIfStmt(stmt: o.IfStmt, ctx: EmitterVisitorContext): any {
ctx.print(`if (`);
stmt.condition.visitExpression(this, ctx);
ctx.print(`) {`);
var hasElseCase = isPresent(stmt.falseCase) && stmt.falseCase.length > 0;
if (stmt.trueCase.length <= 1 && !hasElseCase) {
ctx.print(` `);
this.visitAllStatements(stmt.trueCase, ctx);
ctx.removeEmptyLastLine();
ctx.print(` `);
} else {
ctx.println();
ctx.incIndent();
this.visitAllStatements(stmt.trueCase, ctx);
ctx.decIndent();
if (hasElseCase) {
ctx.println(`} else {`);
ctx.incIndent();
this.visitAllStatements(stmt.falseCase, ctx);
ctx.decIndent();
}
}
ctx.println(`}`);
return null;
}
abstract visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any;
visitThrowStmt(stmt: o.ThrowStmt, ctx: EmitterVisitorContext): any {
ctx.print(`throw `);
stmt.error.visitExpression(this, ctx);
ctx.println(`;`);
return null;
}
visitCommentStmt(stmt: o.CommentStmt, ctx: EmitterVisitorContext): any {
var lines = stmt.comment.split('\n');
lines.forEach((line) => { ctx.println(`// ${line}`); });
return null;
}
abstract visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any;
visitWriteVarExpr(expr: o.WriteVarExpr, ctx: EmitterVisitorContext): any {
var lineWasEmpty = ctx.lineIsEmpty();
if (!lineWasEmpty) {
ctx.print('(');
}
ctx.print(`${expr.name} = `);
expr.value.visitExpression(this, ctx);
if (!lineWasEmpty) {
ctx.print(')');
}
return null;
}
visitWriteKeyExpr(expr: o.WriteKeyExpr, ctx: EmitterVisitorContext): any {
var lineWasEmpty = ctx.lineIsEmpty();
if (!lineWasEmpty) {
ctx.print('(');
}
expr.receiver.visitExpression(this, ctx);
ctx.print(`[`);
expr.index.visitExpression(this, ctx);
ctx.print(`] = `);
expr.value.visitExpression(this, ctx);
if (!lineWasEmpty) {
ctx.print(')');
}
return null;
}
visitWritePropExpr(expr: o.WritePropExpr, ctx: EmitterVisitorContext): any {
var lineWasEmpty = ctx.lineIsEmpty();
if (!lineWasEmpty) {
ctx.print('(');
}
expr.receiver.visitExpression(this, ctx);
ctx.print(`.${expr.name} = `);
expr.value.visitExpression(this, ctx);
if (!lineWasEmpty) {
ctx.print(')');
}
return null;
}
visitInvokeMethodExpr(expr: o.InvokeMethodExpr, ctx: EmitterVisitorContext): any {
expr.receiver.visitExpression(this, ctx);
var name = expr.name;
if (isPresent(expr.builtin)) {
name = this.getBuiltinMethodName(expr.builtin);
if (isBlank(name)) {
// some builtins just mean to skip the call.
// e.g. `bind` in Dart.
return null;
}
}
ctx.print(`.${name}(`);
this.visitAllExpressions(expr.args, ctx, `,`);
ctx.print(`)`);
return null;
}
abstract getBuiltinMethodName(method: o.BuiltinMethod): string;
visitInvokeFunctionExpr(expr: o.InvokeFunctionExpr, ctx: EmitterVisitorContext): any {
expr.fn.visitExpression(this, ctx);
ctx.print(`(`);
this.visitAllExpressions(expr.args, ctx, ',');
ctx.print(`)`);
return null;
}
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any {
var varName = ast.name;
if (isPresent(ast.builtin)) {
switch (ast.builtin) {
case o.BuiltinVar.Super:
varName = 'super';
break;
case o.BuiltinVar.This:
varName = 'this';
break;
case o.BuiltinVar.CatchError:
varName = CATCH_ERROR_VAR.name;
break;
case o.BuiltinVar.CatchStack:
varName = CATCH_STACK_VAR.name;
break;
default:
throw new BaseException(`Unknown builtin variable ${ast.builtin}`);
}
}
ctx.print(varName);
return null;
}
visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): any {
ctx.print(`new `);
ast.classExpr.visitExpression(this, ctx);
ctx.print(`(`);
this.visitAllExpressions(ast.args, ctx, ',');
ctx.print(`)`);
return null;
}
visitLiteralExpr(ast: o.LiteralExpr, ctx: EmitterVisitorContext): any {
var value = ast.value;
if (isString(value)) {
ctx.print(escapeSingleQuoteString(value, this._escapeDollarInStrings));
} else if (isBlank(value)) {
ctx.print('null');
} else {
ctx.print(`${value}`);
}
return null;
}
abstract visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any;
visitConditionalExpr(ast: o.ConditionalExpr, ctx: EmitterVisitorContext): any {
ctx.print(`(`);
ast.condition.visitExpression(this, ctx);
ctx.print('? ');
ast.trueCase.visitExpression(this, ctx);
ctx.print(': ');
ast.falseCase.visitExpression(this, ctx);
ctx.print(`)`);
return null;
}
visitNotExpr(ast: o.NotExpr, ctx: EmitterVisitorContext): any {
ctx.print('!');
ast.condition.visitExpression(this, ctx);
return null;
}
abstract visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any;
abstract visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, context: any): any;
visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: EmitterVisitorContext): any {
var opStr;
switch (ast.operator) {
case o.BinaryOperator.Equals:
opStr = '==';
break;
case o.BinaryOperator.Identical:
opStr = '===';
break;
case o.BinaryOperator.NotEquals:
opStr = '!=';
break;
case o.BinaryOperator.NotIdentical:
opStr = '!==';
break;
case o.BinaryOperator.And:
opStr = '&&';
break;
case o.BinaryOperator.Or:
opStr = '||';
break;
case o.BinaryOperator.Plus:
opStr = '+';
break;
case o.BinaryOperator.Minus:
opStr = '-';
break;
case o.BinaryOperator.Divide:
opStr = '/';
break;
case o.BinaryOperator.Multiply:
opStr = '*';
break;
case o.BinaryOperator.Modulo:
opStr = '%';
break;
case o.BinaryOperator.Lower:
opStr = '<';
break;
case o.BinaryOperator.LowerEquals:
opStr = '<=';
break;
case o.BinaryOperator.Bigger:
opStr = '>';
break;
case o.BinaryOperator.BiggerEquals:
opStr = '>=';
break;
default:
throw new BaseException(`Unknown operator ${ast.operator}`);
}
ctx.print(`(`);
ast.lhs.visitExpression(this, ctx);
ctx.print(` ${opStr} `);
ast.rhs.visitExpression(this, ctx);
ctx.print(`)`);
return null;
}
visitReadPropExpr(ast: o.ReadPropExpr, ctx: EmitterVisitorContext): any {
ast.receiver.visitExpression(this, ctx);
ctx.print(`.`);
ctx.print(ast.name);
return null;
}
visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: EmitterVisitorContext): any {
ast.receiver.visitExpression(this, ctx);
ctx.print(`[`);
ast.index.visitExpression(this, ctx);
ctx.print(`]`);
return null;
}
visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any {
var useNewLine = ast.entries.length > 1;
ctx.print(`[`, useNewLine);
ctx.incIndent();
this.visitAllExpressions(ast.entries, ctx, ',', useNewLine);
ctx.decIndent();
ctx.print(`]`, useNewLine);
return null;
}
visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: EmitterVisitorContext): any {
var useNewLine = ast.entries.length > 1;
ctx.print(`{`, useNewLine);
ctx.incIndent();
this.visitAllObjects((entry) => {
ctx.print(`${escapeSingleQuoteString(entry[0], this._escapeDollarInStrings)}: `);
entry[1].visitExpression(this, ctx);
}, ast.entries, ctx, ',', useNewLine);
ctx.decIndent();
ctx.print(`}`, useNewLine);
return null;
}
visitAllExpressions(expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
newLine: boolean = false): void {
this.visitAllObjects((expr) => expr.visitExpression(this, ctx), expressions, ctx, separator,
newLine);
}
visitAllObjects(handler: Function, expressions: any, ctx: EmitterVisitorContext,
separator: string, newLine: boolean = false): void {
for (var i = 0; i < expressions.length; i++) {
if (i > 0) {
ctx.print(separator, newLine);
}
handler(expressions[i]);
}
if (newLine) {
ctx.println();
}
}
visitAllStatements(statements: o.Statement[], ctx: EmitterVisitorContext): void {
statements.forEach((stmt) => { return stmt.visitStatement(this, ctx); });
}
}
export function escapeSingleQuoteString(input: string, escapeDollar: boolean): any {
if (isBlank(input)) {
return null;
}
var body = StringWrapper.replaceAllMapped(input, _SINGLE_QUOTE_ESCAPE_STRING_RE, (match) => {
if (match[0] == '$') {
return escapeDollar ? '\\$' : '$';
} else if (match[0] == '\n') {
return '\\n';
} else if (match[0] == '\r') {
return '\\r';
} else {
return `\\${match[0]}`;
}
});
return `'${body}'`;
}
function _createIndent(count: number): string {
var res = '';
for (var i = 0; i < count; i++) {
res += ' ';
}
return res;
}

View File

@ -0,0 +1,164 @@
import {isPresent} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from './output_ast';
import {
EmitterVisitorContext,
AbstractEmitterVisitor,
CATCH_ERROR_VAR,
CATCH_STACK_VAR
} from './abstract_emitter';
export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
constructor() { super(false); }
visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any {
ctx.pushClass(stmt);
this._visitClassConstructor(stmt, ctx);
if (isPresent(stmt.parent)) {
ctx.print(`${stmt.name}.prototype = Object.create(`);
stmt.parent.visitExpression(this, ctx);
ctx.println(`.prototype);`);
}
stmt.getters.forEach((getter) => this._visitClassGetter(stmt, getter, ctx));
stmt.methods.forEach((method) => this._visitClassMethod(stmt, method, ctx));
ctx.popClass();
return null;
}
private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) {
ctx.print(`function ${stmt.name}(`);
if (isPresent(stmt.constructorMethod)) {
this._visitParams(stmt.constructorMethod.params, ctx);
}
ctx.println(`) {`);
ctx.incIndent();
if (isPresent(stmt.constructorMethod)) {
if (stmt.constructorMethod.body.length > 0) {
ctx.println(`var self = this;`);
this.visitAllStatements(stmt.constructorMethod.body, ctx);
}
}
ctx.decIndent();
ctx.println(`}`);
}
private _visitClassGetter(stmt: o.ClassStmt, getter: o.ClassGetter, ctx: EmitterVisitorContext) {
ctx.println(
`Object.defineProperty(${stmt.name}.prototype, '${getter.name}', { get: function() {`);
ctx.incIndent();
if (getter.body.length > 0) {
ctx.println(`var self = this;`);
this.visitAllStatements(getter.body, ctx);
}
ctx.decIndent();
ctx.println(`}});`);
}
private _visitClassMethod(stmt: o.ClassStmt, method: o.ClassMethod, ctx: EmitterVisitorContext) {
ctx.print(`${stmt.name}.prototype.${method.name} = function(`);
this._visitParams(method.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
if (method.body.length > 0) {
ctx.println(`var self = this;`);
this.visitAllStatements(method.body, ctx);
}
ctx.decIndent();
ctx.println(`};`);
}
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): string {
if (ast.builtin === o.BuiltinVar.This) {
ctx.print('self');
} else if (ast.builtin === o.BuiltinVar.Super) {
throw new BaseException(
`'super' needs to be handled at a parent ast node, not at the variable level!`);
} else {
super.visitReadVarExpr(ast, ctx);
}
return null;
}
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
ctx.print(`var ${stmt.name} = `);
stmt.value.visitExpression(this, ctx);
ctx.println(`;`);
return null;
}
visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any {
ast.value.visitExpression(this, ctx);
return null;
}
visitInvokeFunctionExpr(expr: o.InvokeFunctionExpr, ctx: EmitterVisitorContext): string {
var fnExpr = expr.fn;
if (fnExpr instanceof o.ReadVarExpr && fnExpr.builtin === o.BuiltinVar.Super) {
ctx.currentClass.parent.visitExpression(this, ctx);
ctx.print(`.call(this`);
if (expr.args.length > 0) {
ctx.print(`, `);
this.visitAllExpressions(expr.args, ctx, ',');
}
ctx.print(`)`);
} else {
super.visitInvokeFunctionExpr(expr, ctx);
}
return null;
}
visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any {
ctx.print(`function(`);
this._visitParams(ast.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
this.visitAllStatements(ast.statements, ctx);
ctx.decIndent();
ctx.print(`}`);
return null;
}
visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any {
ctx.print(`function ${stmt.name}(`);
this._visitParams(stmt.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
this.visitAllStatements(stmt.statements, ctx);
ctx.decIndent();
ctx.println(`}`);
return null;
}
visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any {
ctx.println(`try {`);
ctx.incIndent();
this.visitAllStatements(stmt.bodyStmts, ctx);
ctx.decIndent();
ctx.println(`} catch (${CATCH_ERROR_VAR.name}) {`);
ctx.incIndent();
var catchStmts = [
<o.Statement>CATCH_STACK_VAR.set(CATCH_ERROR_VAR.prop('stack'))
.toDeclStmt(null, [o.StmtModifier.Final])
].concat(stmt.catchStmts);
this.visitAllStatements(catchStmts, ctx);
ctx.decIndent();
ctx.println(`}`);
return null;
}
private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void {
this.visitAllObjects((param) => ctx.print(param.name), params, ctx, ',');
}
getBuiltinMethodName(method: o.BuiltinMethod): string {
var name;
switch (method) {
case o.BuiltinMethod.ConcatArray:
name = 'concat';
break;
case o.BuiltinMethod.SubscribeObservable:
name = 'subscribe';
break;
case o.BuiltinMethod.bind:
name = 'bind';
break;
default:
throw new BaseException(`Unknown builtin method: ${method}`);
}
return name;
}
}

View File

@ -0,0 +1,384 @@
import {
StringWrapper,
RegExpWrapper,
isPresent,
isBlank,
Math,
isString,
isArray
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {BaseException} from 'angular2/src/facade/exceptions';
import {CompileIdentifierMetadata} from '../compile_metadata';
import * as o from './output_ast';
import {
OutputEmitter,
EmitterVisitorContext,
AbstractEmitterVisitor,
CATCH_ERROR_VAR,
CATCH_STACK_VAR,
escapeSingleQuoteString
} from './abstract_emitter';
import {getImportModulePath, ImportEnv} from './path_util';
var _debugModuleUrl = 'asset://debug/lib';
export function debugOutputAstAsDart(ast: o.Statement | o.Expression | o.Type | any[]): string {
var converter = new _DartEmitterVisitor(_debugModuleUrl);
var ctx = EmitterVisitorContext.createRoot([]);
var asts: any[];
if (isArray(ast)) {
asts = <any[]>ast;
} else {
asts = [ast];
}
asts.forEach((ast) => {
if (ast instanceof o.Statement) {
ast.visitStatement(converter, ctx);
} else if (ast instanceof o.Expression) {
ast.visitExpression(converter, ctx);
} else if (ast instanceof o.Type) {
ast.visitType(converter, ctx);
} else {
throw new BaseException(`Don't know how to print debug info for ${ast}`);
}
});
return ctx.toSource();
}
export class DartEmitter implements OutputEmitter {
constructor() {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
var srcParts = [];
// Note: We are not creating a library here as Dart does not need it.
// Dart analzyer might complain about it though.
var converter = new _DartEmitterVisitor(moduleUrl);
var ctx = EmitterVisitorContext.createRoot(exportedVars);
converter.visitAllStatements(stmts, ctx);
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
srcParts.push(
`import '${getImportModulePath(moduleUrl, importedModuleUrl, ImportEnv.Dart)}' as ${prefix};`);
});
srcParts.push(ctx.toSource());
return srcParts.join('\n');
}
}
class _DartEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor {
importsWithPrefixes = new Map<string, string>();
constructor(private _moduleUrl: string) { super(true); }
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
this._visitIdentifier(ast.value, ast.typeParams, ctx);
return null;
}
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
if (stmt.hasModifier(o.StmtModifier.Final)) {
if (isConstType(stmt.type)) {
ctx.print(`const `);
} else {
ctx.print(`final `);
}
} else if (isBlank(stmt.type)) {
ctx.print(`var `);
}
if (isPresent(stmt.type)) {
stmt.type.visitType(this, ctx);
ctx.print(` `);
}
ctx.print(`${stmt.name} = `);
stmt.value.visitExpression(this, ctx);
ctx.println(`;`);
return null;
}
visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any {
ctx.print(`(`);
ast.value.visitExpression(this, ctx);
ctx.print(` as `);
ast.type.visitType(this, ctx);
ctx.print(`)`);
return null;
}
visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any {
ctx.pushClass(stmt);
ctx.print(`class ${stmt.name}`);
if (isPresent(stmt.parent)) {
ctx.print(` extends `);
stmt.parent.visitExpression(this, ctx);
}
ctx.println(` {`);
ctx.incIndent();
stmt.fields.forEach((field) => this._visitClassField(field, ctx));
if (isPresent(stmt.constructorMethod)) {
this._visitClassConstructor(stmt, ctx);
}
stmt.getters.forEach((getter) => this._visitClassGetter(getter, ctx));
stmt.methods.forEach((method) => this._visitClassMethod(method, ctx));
ctx.decIndent();
ctx.println(`}`);
ctx.popClass();
return null;
}
private _visitClassField(field: o.ClassField, ctx: EmitterVisitorContext) {
if (field.hasModifier(o.StmtModifier.Final)) {
ctx.print(`final `);
} else if (isBlank(field.type)) {
ctx.print(`var `);
}
if (isPresent(field.type)) {
field.type.visitType(this, ctx);
ctx.print(` `);
}
ctx.println(`${field.name};`);
}
private _visitClassGetter(getter: o.ClassGetter, ctx: EmitterVisitorContext) {
if (isPresent(getter.type)) {
getter.type.visitType(this, ctx);
ctx.print(` `);
}
ctx.println(`get ${getter.name} {`);
ctx.incIndent();
this.visitAllStatements(getter.body, ctx);
ctx.decIndent();
ctx.println(`}`);
}
private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) {
ctx.print(`${stmt.name}(`);
this._visitParams(stmt.constructorMethod.params, ctx);
ctx.print(`)`);
var ctorStmts = stmt.constructorMethod.body;
var superCtorExpr = ctorStmts.length > 0 ? getSuperConstructorCallExpr(ctorStmts[0]) : null;
if (isPresent(superCtorExpr)) {
ctx.print(`: `);
superCtorExpr.visitExpression(this, ctx);
ctorStmts = ctorStmts.slice(1);
}
ctx.println(` {`);
ctx.incIndent();
this.visitAllStatements(ctorStmts, ctx);
ctx.decIndent();
ctx.println(`}`);
}
private _visitClassMethod(method: o.ClassMethod, ctx: EmitterVisitorContext) {
if (isPresent(method.type)) {
method.type.visitType(this, ctx);
} else {
ctx.print(`void`);
}
ctx.print(` ${method.name}(`);
this._visitParams(method.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
this.visitAllStatements(method.body, ctx);
ctx.decIndent();
ctx.println(`}`);
}
visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any {
ctx.print(`(`);
this._visitParams(ast.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
this.visitAllStatements(ast.statements, ctx);
ctx.decIndent();
ctx.print(`}`);
return null;
}
visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any {
if (isPresent(stmt.type)) {
stmt.type.visitType(this, ctx);
} else {
ctx.print(`void`);
}
ctx.print(` ${stmt.name}(`);
this._visitParams(stmt.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
this.visitAllStatements(stmt.statements, ctx);
ctx.decIndent();
ctx.println(`}`);
return null;
}
getBuiltinMethodName(method: o.BuiltinMethod): string {
var name;
switch (method) {
case o.BuiltinMethod.ConcatArray:
name = '.addAll';
break;
case o.BuiltinMethod.SubscribeObservable:
name = 'listen';
break;
case o.BuiltinMethod.bind:
name = null;
break;
default:
throw new BaseException(`Unknown builtin method: ${method}`);
}
return name;
}
visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any {
ctx.println(`try {`);
ctx.incIndent();
this.visitAllStatements(stmt.bodyStmts, ctx);
ctx.decIndent();
ctx.println(`} catch (${CATCH_ERROR_VAR.name}, ${CATCH_STACK_VAR.name}) {`);
ctx.incIndent();
this.visitAllStatements(stmt.catchStmts, ctx);
ctx.decIndent();
ctx.println(`}`);
return null;
}
visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: EmitterVisitorContext): any {
switch (ast.operator) {
case o.BinaryOperator.Identical:
ctx.print(`identical(`);
ast.lhs.visitExpression(this, ctx);
ctx.print(`, `);
ast.rhs.visitExpression(this, ctx);
ctx.print(`)`);
break;
case o.BinaryOperator.NotIdentical:
ctx.print(`!identical(`);
ast.lhs.visitExpression(this, ctx);
ctx.print(`, `);
ast.rhs.visitExpression(this, ctx);
ctx.print(`)`);
break;
default:
super.visitBinaryOperatorExpr(ast, ctx);
}
return null;
}
visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any {
if (isConstType(ast.type)) {
ctx.print(`const `);
}
return super.visitLiteralArrayExpr(ast, ctx);
}
visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: EmitterVisitorContext): any {
if (isConstType(ast.type)) {
ctx.print(`const `);
}
if (isPresent(ast.valueType)) {
ctx.print(`<String, `);
ast.valueType.visitType(this, ctx);
ctx.print(`>`);
}
return super.visitLiteralMapExpr(ast, ctx);
}
visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): any {
ctx.print(isConstType(ast.type) ? `const` : `new`);
ctx.print(' ');
ast.classExpr.visitExpression(this, ctx);
ctx.print(`(`);
this.visitAllExpressions(ast.args, ctx, `,`);
ctx.print(`)`);
return null;
}
visitBuiltintType(type: o.BuiltinType, ctx: EmitterVisitorContext): any {
var typeStr;
switch (type.name) {
case o.BuiltinTypeName.Bool:
typeStr = 'bool';
break;
case o.BuiltinTypeName.Dynamic:
typeStr = 'dynamic';
break;
case o.BuiltinTypeName.Function:
typeStr = 'Function';
break;
case o.BuiltinTypeName.Number:
typeStr = 'num';
break;
case o.BuiltinTypeName.Int:
typeStr = 'int';
break;
case o.BuiltinTypeName.String:
typeStr = 'String';
break;
default:
throw new BaseException(`Unsupported builtin type ${type.name}`);
}
ctx.print(typeStr);
return null;
}
visitExternalType(ast: o.ExternalType, ctx: EmitterVisitorContext): any {
this._visitIdentifier(ast.value, ast.typeParams, ctx);
return null;
}
visitArrayType(type: o.ArrayType, ctx: EmitterVisitorContext): any {
ctx.print(`List<`);
if (isPresent(type.of)) {
type.of.visitType(this, ctx);
} else {
ctx.print(`dynamic`);
}
ctx.print(`>`);
return null;
}
visitMapType(type: o.MapType, ctx: EmitterVisitorContext): any {
ctx.print(`Map<String, `);
if (isPresent(type.valueType)) {
type.valueType.visitType(this, ctx);
} else {
ctx.print(`dynamic`);
}
ctx.print(`>`);
return null;
}
private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void {
this.visitAllObjects((param) => {
if (isPresent(param.type)) {
param.type.visitType(this, ctx);
ctx.print(' ');
}
ctx.print(param.name);
}, params, ctx, ',');
}
private _visitIdentifier(value: CompileIdentifierMetadata, typeParams: o.Type[],
ctx: EmitterVisitorContext): void {
if (isBlank(value.name)) {
throw new BaseException(`Internal error: unknown identifier ${value}`);
}
if (isPresent(value.moduleUrl) && value.moduleUrl != this._moduleUrl) {
var prefix = this.importsWithPrefixes.get(value.moduleUrl);
if (isBlank(prefix)) {
prefix = `import${this.importsWithPrefixes.size}`;
this.importsWithPrefixes.set(value.moduleUrl, prefix);
}
ctx.print(`${prefix}.`);
}
ctx.print(value.name);
if (isPresent(typeParams) && typeParams.length > 0) {
ctx.print(`<`);
this.visitAllObjects((type) => type.visitType(this, ctx), typeParams, ctx, ',');
ctx.print(`>`);
}
}
}
function getSuperConstructorCallExpr(stmt: o.Statement): o.Expression {
if (stmt instanceof o.ExpressionStatement) {
var expr = stmt.expr;
if (expr instanceof o.InvokeFunctionExpr) {
var fn = expr.fn;
if (fn instanceof o.ReadVarExpr) {
if (fn.builtin === o.BuiltinVar.Super) {
return expr;
}
}
}
}
return null;
}
function isConstType(type: o.Type): boolean {
return isPresent(type) && type.hasModifier(o.TypeModifier.Const);
}

View File

@ -0,0 +1,68 @@
import {isPresent} from 'angular2/src/facade/lang';
import {AppView, DebugAppView} from 'angular2/src/core/linker/view';
import {AppElement} from 'angular2/src/core/linker/element';
import {BaseException} from 'angular2/src/facade/exceptions';
import {InstanceFactory, DynamicInstance} from './output_interpreter';
export class InterpretiveAppViewInstanceFactory implements InstanceFactory {
createInstance(superClass: any, clazz: any, args: any[], props: Map<string, any>,
getters: Map<string, Function>, methods: Map<string, Function>): any {
if (superClass === AppView) {
// We are always using DebugAppView as parent.
// However, in prod mode we generate a constructor call that does
// not have the argument for the debugNodeInfos.
args = args.concat([null]);
return new _InterpretiveAppView(args, props, getters, methods);
} else if (superClass === DebugAppView) {
return new _InterpretiveAppView(args, props, getters, methods);
}
throw new BaseException(`Can't instantiate class ${superClass} in interpretative mode`);
}
}
class _InterpretiveAppView extends DebugAppView<any> implements DynamicInstance {
constructor(args: any[], public props: Map<string, any>, public getters: Map<string, Function>,
public methods: Map<string, Function>) {
super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
}
createInternal(rootSelector: string | any): AppElement {
var m = this.methods.get('createInternal');
if (isPresent(m)) {
return m(rootSelector);
} else {
return super.createInternal(rootSelector);
}
}
injectorGetInternal(token: any, nodeIndex: number, notFoundResult: any): any {
var m = this.methods.get('injectorGetInternal');
if (isPresent(m)) {
return m(token, nodeIndex, notFoundResult);
} else {
return super.injectorGet(token, nodeIndex, notFoundResult);
}
}
destroyInternal(): void {
var m = this.methods.get('destroyInternal');
if (isPresent(m)) {
return m();
} else {
return super.destroyInternal();
}
}
dirtyParentQueriesInternal(): void {
var m = this.methods.get('dirtyParentQueriesInternal');
if (isPresent(m)) {
return m();
} else {
return super.dirtyParentQueriesInternal();
}
}
detectChangesInternal(throwOnChange: boolean): void {
var m = this.methods.get('detectChangesInternal');
if (isPresent(m)) {
return m(throwOnChange);
} else {
return super.detectChangesInternal(throwOnChange);
}
}
}

View File

@ -0,0 +1,77 @@
import * as o from './output_ast';
import {
isPresent,
isBlank,
isString,
evalExpression,
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {OutputEmitter, EmitterVisitorContext} from './abstract_emitter';
import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
import {getImportModulePath, ImportEnv} from './path_util';
export class JavaScriptEmitter implements OutputEmitter {
constructor() {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
var converter = new JsEmitterVisitor(moduleUrl);
var ctx = EmitterVisitorContext.createRoot(exportedVars);
converter.visitAllStatements(stmts, ctx);
var srcParts = [];
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
// Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(`var ${prefix} = req` +
`uire('${getImportModulePath(moduleUrl, importedModuleUrl, ImportEnv.JS)}');`);
});
srcParts.push(ctx.toSource());
return srcParts.join('\n');
}
}
class JsEmitterVisitor extends AbstractJsEmitterVisitor {
importsWithPrefixes = new Map<string, string>();
constructor(private _moduleUrl: string) { super(); }
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
if (isBlank(ast.value.name)) {
throw new BaseException(`Internal error: unknown identifier ${ast.value}`);
}
if (isPresent(ast.value.moduleUrl) && ast.value.moduleUrl != this._moduleUrl) {
var prefix = this.importsWithPrefixes.get(ast.value.moduleUrl);
if (isBlank(prefix)) {
prefix = `import${this.importsWithPrefixes.size}`;
this.importsWithPrefixes.set(ast.value.moduleUrl, prefix);
}
ctx.print(`${prefix}.`);
}
ctx.print(ast.value.name);
return null;
}
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
super.visitDeclareVarStmt(stmt, ctx);
if (ctx.isExportedVar(stmt.name)) {
ctx.println(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));
}
return null;
}
visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any {
super.visitDeclareClassStmt(stmt, ctx);
if (ctx.isExportedVar(stmt.name)) {
ctx.println(exportVar(stmt.name));
}
return null;
}
}
function exportVar(varName: string): string {
return `Object.defineProperty(exports, '${varName}', { get: function() { return ${varName}; }});`;
}

View File

@ -0,0 +1,870 @@
import {isString, isPresent, isBlank} from 'angular2/src/facade/lang';
import {CompileIdentifierMetadata} from '../compile_metadata';
//// Types
export enum TypeModifier {
Const
}
export abstract class Type {
constructor(public modifiers: TypeModifier[] = null) {
if (isBlank(modifiers)) {
this.modifiers = [];
}
}
abstract visitType(visitor: TypeVisitor, context: any): any;
hasModifier(modifier: TypeModifier): boolean { return this.modifiers.indexOf(modifier) !== -1; }
}
export enum BuiltinTypeName {
Dynamic,
Bool,
String,
Int,
Number,
Function
}
export class BuiltinType extends Type {
constructor(public name: BuiltinTypeName, modifiers: TypeModifier[] = null) { super(modifiers); }
visitType(visitor: TypeVisitor, context: any): any {
return visitor.visitBuiltintType(this, context);
}
}
export class ExternalType extends Type {
constructor(public value: CompileIdentifierMetadata, public typeParams: Type[] = null,
modifiers: TypeModifier[] = null) {
super(modifiers);
}
visitType(visitor: TypeVisitor, context: any): any {
return visitor.visitExternalType(this, context);
}
}
export class ArrayType extends Type {
constructor(public of: Type, modifiers: TypeModifier[] = null) { super(modifiers); }
visitType(visitor: TypeVisitor, context: any): any {
return visitor.visitArrayType(this, context);
}
}
export class MapType extends Type {
constructor(public valueType: Type, modifiers: TypeModifier[] = null) { super(modifiers); }
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
}
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export interface TypeVisitor {
visitBuiltintType(type: BuiltinType, context: any): any;
visitExternalType(type: ExternalType, context: any): any;
visitArrayType(type: ArrayType, context: any): any;
visitMapType(type: MapType, context: any): any;
}
///// Expressions
export enum BinaryOperator {
Equals,
NotEquals,
Identical,
NotIdentical,
Minus,
Plus,
Divide,
Multiply,
Modulo,
And,
Or,
Lower,
LowerEquals,
Bigger,
BiggerEquals
}
export abstract class Expression {
constructor(public type: Type) {}
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);
}
callMethod(name: string | BuiltinMethod, params: Expression[]): InvokeMethodExpr {
return new InvokeMethodExpr(this, name, params);
}
callFn(params: Expression[]): InvokeFunctionExpr { return new InvokeFunctionExpr(this, params); }
instantiate(params: Expression[], type: Type = null): InstantiateExpr {
return new InstantiateExpr(this, params, type);
}
conditional(trueCase: Expression, falseCase: Expression = null): ConditionalExpr {
return new ConditionalExpr(this, trueCase, falseCase);
}
equals(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Equals, this, rhs);
}
notEquals(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.NotEquals, this, rhs);
}
identical(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Identical, this, rhs);
}
notIdentical(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.NotIdentical, this, rhs);
}
minus(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Minus, this, rhs);
}
plus(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Plus, this, rhs);
}
divide(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Divide, this, rhs);
}
multiply(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Multiply, this, rhs);
}
modulo(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Modulo, this, rhs);
}
and(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.And, this, rhs);
}
or(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Or, this, rhs);
}
lower(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Lower, this, rhs);
}
lowerEquals(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.LowerEquals, this, rhs);
}
bigger(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.Bigger, this, rhs);
}
biggerEquals(rhs: Expression): BinaryOperatorExpr {
return new BinaryOperatorExpr(BinaryOperator.BiggerEquals, this, rhs);
}
isBlank(): Expression {
// Note: We use equals by purpose here to compare to null and undefined in JS.
return this.equals(NULL_EXPR);
}
cast(type: Type): Expression { return new CastExpr(this, type); }
toStmt(): Statement { return new ExpressionStatement(this); }
}
export enum BuiltinVar {
This,
Super,
CatchError,
CatchStack
}
export class ReadVarExpr extends Expression {
public name;
public builtin: BuiltinVar;
constructor(name: string | BuiltinVar, type: Type = null) {
super(type);
if (isString(name)) {
this.name = <string>name;
this.builtin = null;
} else {
this.name = null;
this.builtin = <BuiltinVar>name;
}
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadVarExpr(this, context);
}
set(value: Expression): WriteVarExpr { return new WriteVarExpr(this.name, value); }
}
export class WriteVarExpr extends Expression {
public value: Expression;
constructor(public name: string, value: Expression, type: Type = null) {
super(isPresent(type) ? type : value.type);
this.value = value;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteVarExpr(this, context);
}
toDeclStmt(type: Type = null, modifiers: StmtModifier[] = null): DeclareVarStmt {
return new DeclareVarStmt(this.name, this.value, type, modifiers);
}
}
export class WriteKeyExpr extends Expression {
public value: Expression;
constructor(public receiver: Expression, public index: Expression, value: Expression,
type: Type = null) {
super(isPresent(type) ? type : value.type);
this.value = value;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteKeyExpr(this, context);
}
}
export class WritePropExpr extends Expression {
public value: Expression;
constructor(public receiver: Expression, public name: string, value: Expression,
type: Type = null) {
super(isPresent(type) ? type : value.type);
this.value = value;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWritePropExpr(this, context);
}
}
export enum BuiltinMethod {
ConcatArray,
SubscribeObservable,
bind
}
export class InvokeMethodExpr extends Expression {
public name: string;
public builtin: BuiltinMethod;
constructor(public receiver: Expression, method: string | BuiltinMethod,
public args: Expression[], type: Type = null) {
super(type);
if (isString(method)) {
this.name = <string>method;
this.builtin = null;
} else {
this.name = null;
this.builtin = <BuiltinMethod>method;
}
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeMethodExpr(this, context);
}
}
export class InvokeFunctionExpr extends Expression {
constructor(public fn: Expression, public args: Expression[], type: Type = null) { super(type); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeFunctionExpr(this, context);
}
}
export class InstantiateExpr extends Expression {
constructor(public classExpr: Expression, public args: Expression[], type?: Type) { super(type); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInstantiateExpr(this, context);
}
}
export class LiteralExpr extends Expression {
constructor(public value: any, type: Type = null) { super(type); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralExpr(this, context);
}
}
export class ExternalExpr extends Expression {
constructor(public value: CompileIdentifierMetadata, type: Type = null,
public typeParams: Type[] = null) {
super(type);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitExternalExpr(this, context);
}
}
export class ConditionalExpr extends Expression {
public trueCase: Expression;
constructor(public condition: Expression, trueCase: Expression,
public falseCase: Expression = null, type: Type = null) {
super(isPresent(type) ? type : trueCase.type);
this.trueCase = trueCase;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitConditionalExpr(this, context);
}
}
export class NotExpr extends Expression {
constructor(public condition: Expression) { super(BOOL_TYPE); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitNotExpr(this, context);
}
}
export class CastExpr extends Expression {
constructor(public value: Expression, type: Type) { super(type); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCastExpr(this, context);
}
}
export class FnParam {
constructor(public name: string, public type: Type = null) {}
}
export class FunctionExpr extends Expression {
constructor(public params: FnParam[], public statements: Statement[], type: Type = null) {
super(type);
}
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);
}
}
export class BinaryOperatorExpr extends Expression {
public lhs: Expression;
constructor(public operator: BinaryOperator, lhs: Expression, public rhs: Expression,
type: Type = null) {
super(isPresent(type) ? type : lhs.type);
this.lhs = lhs;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitBinaryOperatorExpr(this, context);
}
}
export class ReadPropExpr extends Expression {
constructor(public receiver: Expression, public name: string, type: Type = null) { super(type); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadPropExpr(this, context);
}
set(value: Expression): WritePropExpr {
return new WritePropExpr(this.receiver, this.name, value);
}
}
export class ReadKeyExpr extends Expression {
constructor(public receiver: Expression, public index: Expression, type: Type = null) {
super(type);
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadKeyExpr(this, context);
}
set(value: Expression): WriteKeyExpr {
return new WriteKeyExpr(this.receiver, this.index, value);
}
}
export class LiteralArrayExpr extends Expression {
public entries: Expression[];
constructor(entries: Expression[], type: Type = null) {
super(type);
this.entries = entries;
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralArrayExpr(this, context);
}
}
export class LiteralMapExpr extends Expression {
public valueType: Type = null;
;
constructor(public entries: Array<Array<string | Expression>>, type: MapType = null) {
super(type);
if (isPresent(type)) {
this.valueType = type.valueType;
}
}
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralMapExpr(this, context);
}
}
export interface ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any;
visitWriteVarExpr(expr: WriteVarExpr, context: any): any;
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any;
visitWritePropExpr(expr: WritePropExpr, context: any): any;
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any;
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any;
visitInstantiateExpr(ast: InstantiateExpr, context: any): any;
visitLiteralExpr(ast: LiteralExpr, context: any): any;
visitExternalExpr(ast: ExternalExpr, context: any): any;
visitConditionalExpr(ast: ConditionalExpr, context: any): any;
visitNotExpr(ast: NotExpr, context: any): any;
visitCastExpr(ast: CastExpr, context: any): any;
visitFunctionExpr(ast: FunctionExpr, context: any): any;
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any;
visitReadPropExpr(ast: ReadPropExpr, context: any): any;
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any;
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any;
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
}
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
export var NULL_EXPR = new LiteralExpr(null, null);
//// Statements
export enum StmtModifier {
Final,
Private
}
export abstract class Statement {
constructor(public modifiers: StmtModifier[] = null) {
if (isBlank(modifiers)) {
this.modifiers = [];
}
}
abstract visitStatement(visitor: StatementVisitor, context: any): any;
hasModifier(modifier: StmtModifier): boolean { return this.modifiers.indexOf(modifier) !== -1; }
}
export class DeclareVarStmt extends Statement {
public type: Type;
constructor(public name: string, public value: Expression, type: Type = null,
modifiers: StmtModifier[] = null) {
super(modifiers);
this.type = isPresent(type) ? type : value.type;
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareVarStmt(this, context);
}
}
export class DeclareFunctionStmt extends Statement {
constructor(public name: string, public params: FnParam[], public statements: Statement[],
public type: Type = null, modifiers: StmtModifier[] = null) {
super(modifiers);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareFunctionStmt(this, context);
}
}
export class ExpressionStatement extends Statement {
constructor(public expr: Expression) { super(); }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitExpressionStmt(this, context);
}
}
export class ReturnStatement extends Statement {
constructor(public value: Expression) { super(); }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitReturnStmt(this, context);
}
}
export class AbstractClassPart {
constructor(public type: Type = null, public modifiers: StmtModifier[]) {
if (isBlank(modifiers)) {
this.modifiers = [];
}
}
hasModifier(modifier: StmtModifier): boolean { return this.modifiers.indexOf(modifier) !== -1; }
}
export class ClassField extends AbstractClassPart {
constructor(public name: string, type: Type = null, modifiers: StmtModifier[] = null) {
super(type, modifiers);
}
}
export class ClassMethod extends AbstractClassPart {
constructor(public name: string, public params: FnParam[], public body: Statement[],
type: Type = null, modifiers: StmtModifier[] = null) {
super(type, modifiers);
}
}
export class ClassGetter extends AbstractClassPart {
constructor(public name: string, public body: Statement[], type: Type = null,
modifiers: StmtModifier[] = null) {
super(type, modifiers);
}
}
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);
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitDeclareClassStmt(this, context);
}
}
export class IfStmt extends Statement {
constructor(public condition: Expression, public trueCase: Statement[],
public falseCase: Statement[] = /*@ts2dart_const*/[]) {
super();
}
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitIfStmt(this, context);
}
}
export class CommentStmt extends Statement {
constructor(public comment: string) { super(); }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitCommentStmt(this, context);
}
}
export class TryCatchStmt extends Statement {
constructor(public bodyStmts: Statement[], public catchStmts: Statement[]) { super(); }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitTryCatchStmt(this, context);
}
}
export class ThrowStmt extends Statement {
constructor(public error: Expression) { super(); }
visitStatement(visitor: StatementVisitor, context: any): any {
return visitor.visitThrowStmt(this, context);
}
}
export interface StatementVisitor {
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any;
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any;
visitExpressionStmt(stmt: ExpressionStatement, context: any): any;
visitReturnStmt(stmt: ReturnStatement, context: any): any;
visitDeclareClassStmt(stmt: ClassStmt, context: any): any;
visitIfStmt(stmt: IfStmt, context: any): any;
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any;
visitThrowStmt(stmt: ThrowStmt, context: any): any;
visitCommentStmt(stmt: CommentStmt, context: any): any;
}
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));
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
return new WriteKeyExpr(expr.receiver.visitExpression(this, context),
expr.index.visitExpression(this, context),
expr.value.visitExpression(this, context));
}
visitWritePropExpr(expr: WritePropExpr, context: any): any {
return new WritePropExpr(expr.receiver.visitExpression(this, context), expr.name,
expr.value.visitExpression(this, context));
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
var method = isPresent(ast.builtin) ? ast.builtin : ast.name;
return new InvokeMethodExpr(ast.receiver.visitExpression(this, context), method,
this.visitAllExpressions(ast.args, context), ast.type);
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
return new InvokeFunctionExpr(ast.fn.visitExpression(this, context),
this.visitAllExpressions(ast.args, context), ast.type);
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
return new InstantiateExpr(ast.classExpr.visitExpression(this, context),
this.visitAllExpressions(ast.args, context), ast.type);
}
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));
}
visitNotExpr(ast: NotExpr, context: any): any {
return new NotExpr(ast.condition.visitExpression(this, context));
}
visitCastExpr(ast: CastExpr, context: any): any {
return new CastExpr(ast.value.visitExpression(this, context), context);
}
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);
}
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
return new ReadPropExpr(ast.receiver.visitExpression(this, context), ast.name, ast.type);
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
return new ReadKeyExpr(ast.receiver.visitExpression(this, context),
ast.index.visitExpression(this, context), ast.type);
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
return new LiteralArrayExpr(this.visitAllExpressions(ast.entries, context));
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
return new LiteralMapExpr(ast.entries.map(
(entry) => [entry[0], (<Expression>entry[1]).visitExpression(this, context)]));
}
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
return exprs.map(expr => expr.visitExpression(this, context));
}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
return new DeclareVarStmt(stmt.name, stmt.value.visitExpression(this, context), stmt.type,
stmt.modifiers);
}
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));
}
visitReturnStmt(stmt: ReturnStatement, context: any): any {
return new ReturnStatement(stmt.value.visitExpression(this, context));
}
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));
}
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
return new TryCatchStmt(this.visitAllStatements(stmt.bodyStmts, context),
this.visitAllStatements(stmt.catchStmts, context));
}
visitThrowStmt(stmt: ThrowStmt, context: any): any {
return new ThrowStmt(stmt.error.visitExpression(this, context));
}
visitCommentStmt(stmt: CommentStmt, context: any): any { return stmt; }
visitAllStatements(stmts: Statement[], context: any): Statement[] {
return stmts.map(stmt => stmt.visitStatement(this, context));
}
}
export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; }
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
expr.value.visitExpression(this, context);
return expr;
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
expr.receiver.visitExpression(this, context);
expr.index.visitExpression(this, context);
expr.value.visitExpression(this, context);
return expr;
}
visitWritePropExpr(expr: WritePropExpr, context: any): any {
expr.receiver.visitExpression(this, context);
expr.value.visitExpression(this, context);
return expr;
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
ast.receiver.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return ast;
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
ast.fn.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return ast;
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
ast.classExpr.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return ast;
}
visitLiteralExpr(ast: LiteralExpr, context: any): any { return ast; }
visitExternalExpr(ast: ExternalExpr, context: any): any { return ast; }
visitConditionalExpr(ast: ConditionalExpr, context: any): any {
ast.condition.visitExpression(this, context);
ast.trueCase.visitExpression(this, context);
ast.falseCase.visitExpression(this, context);
return ast;
}
visitNotExpr(ast: NotExpr, context: any): any {
ast.condition.visitExpression(this, context);
return ast;
}
visitCastExpr(ast: CastExpr, context: any): any {
ast.value.visitExpression(this, context);
return ast;
}
visitFunctionExpr(ast: FunctionExpr, context: any): any { return ast; }
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
ast.lhs.visitExpression(this, context);
ast.rhs.visitExpression(this, context);
return ast;
}
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
ast.receiver.visitExpression(this, context);
return ast;
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.index.visitExpression(this, context);
return ast;
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
this.visitAllExpressions(ast.entries, context);
return ast;
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
ast.entries.forEach((entry) => (<Expression>entry[1]).visitExpression(this, context));
return ast;
}
visitAllExpressions(exprs: Expression[], context: any): void {
exprs.forEach(expr => expr.visitExpression(this, context));
}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
stmt.value.visitExpression(this, context);
return stmt;
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
// Don't descend into nested functions
return stmt;
}
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
stmt.expr.visitExpression(this, context);
return stmt;
}
visitReturnStmt(stmt: ReturnStatement, context: any): any {
stmt.value.visitExpression(this, context);
return stmt;
}
visitDeclareClassStmt(stmt: ClassStmt, context: any): any {
// Don't descend into nested functions
return stmt;
}
visitIfStmt(stmt: IfStmt, context: any): any {
stmt.condition.visitExpression(this, context);
this.visitAllStatements(stmt.trueCase, context);
this.visitAllStatements(stmt.falseCase, context);
return stmt;
}
visitTryCatchStmt(stmt: TryCatchStmt, context: any): any {
this.visitAllStatements(stmt.bodyStmts, context);
this.visitAllStatements(stmt.catchStmts, context);
return stmt;
}
visitThrowStmt(stmt: ThrowStmt, context: any): any {
stmt.error.visitExpression(this, context);
return stmt;
}
visitCommentStmt(stmt: CommentStmt, context: any): any { return stmt; }
visitAllStatements(stmts: Statement[], context: any): void {
stmts.forEach(stmt => stmt.visitStatement(this, context));
}
}
export function replaceVarInExpression(varName: string, newValue: Expression,
expression: Expression): Expression {
var transformer = new _ReplaceVariableTransformer(varName, newValue);
return expression.visitExpression(transformer, null);
}
class _ReplaceVariableTransformer extends ExpressionTransformer {
constructor(private _varName: string, private _newValue: Expression) { super(); }
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
return ast.name == this._varName ? this._newValue : ast;
}
}
export function findReadVarNames(stmts: Statement[]): Set<string> {
var finder = new _VariableFinder();
finder.visitAllStatements(stmts, null);
return finder.varNames;
}
class _VariableFinder extends RecursiveExpressionVisitor {
varNames = new Set<string>();
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
this.varNames.add(ast.name);
return null;
}
}
export function variable(name: string, type: Type = null): ReadVarExpr {
return new ReadVarExpr(name, type);
}
export function importExpr(id: CompileIdentifierMetadata, typeParams: Type[] = null): ExternalExpr {
return new ExternalExpr(id, null, typeParams);
}
export function importType(id: CompileIdentifierMetadata, typeParams: Type[] = null,
typeModifiers: TypeModifier[] = null): ExternalType {
return isPresent(id) ? new ExternalType(id, typeParams, typeModifiers) : null;
}
export function literal(value: any, type: Type = null): LiteralExpr {
return new LiteralExpr(value, type);
}
export function literalArr(values: Expression[], type: Type = null): LiteralArrayExpr {
return new LiteralArrayExpr(values, type);
}
export function literalMap(values: Array<Array<string | Expression>>,
type: MapType = null): LiteralMapExpr {
return new LiteralMapExpr(values, type);
}
export function not(expr: Expression): NotExpr {
return new NotExpr(expr);
}
export function fn(params: FnParam[], body: Statement[], type: Type = null): FunctionExpr {
return new FunctionExpr(params, body, type);
}

View File

@ -0,0 +1,422 @@
import {
isPresent,
isBlank,
isString,
evalExpression,
IS_DART,
FunctionWrapper
} from 'angular2/src/facade/lang';
import {ObservableWrapper} from 'angular2/src/facade/async';
import * as o from './output_ast';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {BaseException, unimplemented} from 'angular2/src/facade/exceptions';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {debugOutputAstAsDart} from './dart_emitter';
import {debugOutputAstAsTypeScript} from './ts_emitter';
export function interpretStatements(statements: o.Statement[], resultVar: string,
instanceFactory: InstanceFactory): any {
var stmtsWithReturn = statements.concat([new o.ReturnStatement(o.variable(resultVar))]);
var ctx = new _ExecutionContext(null, null, null, null, new Map<string, any>(),
new Map<string, any>(), new Map<string, Function>(),
new Map<string, Function>(), instanceFactory);
var visitor = new StatementInterpreter();
var result = visitor.visitAllStatements(stmtsWithReturn, ctx);
return isPresent(result) ? result.value : null;
}
export interface InstanceFactory {
createInstance(superClass: any, clazz: any, constructorArgs: any[], props: Map<string, any>,
getters: Map<string, Function>, methods: Map<string, Function>): DynamicInstance;
}
export abstract class DynamicInstance {
get props(): Map<string, any> { return unimplemented(); }
get getters(): Map<string, Function> { return unimplemented(); }
get methods(): Map<string, any> { return unimplemented(); }
get clazz(): any { return unimplemented(); }
}
function isDynamicInstance(instance: any): any {
if (IS_DART) {
return instance instanceof DynamicInstance;
} else {
return isPresent(instance) && isPresent(instance.props) && isPresent(instance.getters) &&
isPresent(instance.methods);
}
}
function _executeFunctionStatements(varNames: string[], varValues: any[], statements: o.Statement[],
ctx: _ExecutionContext, visitor: StatementInterpreter): any {
var childCtx = ctx.createChildWihtLocalVars();
for (var i = 0; i < varNames.length; i++) {
childCtx.vars.set(varNames[i], varValues[i]);
}
var result = visitor.visitAllStatements(statements, childCtx);
return isPresent(result) ? result.value : null;
}
class _ExecutionContext {
constructor(public parent: _ExecutionContext, public superClass: any, public superInstance: any,
public className: string, public vars: Map<string, any>,
public props: Map<string, any>, public getters: Map<string, Function>,
public methods: Map<string, Function>, public instanceFactory: InstanceFactory) {}
createChildWihtLocalVars(): _ExecutionContext {
return new _ExecutionContext(this, this.superClass, this.superInstance, this.className,
new Map<string, any>(), this.props, this.getters, this.methods,
this.instanceFactory);
}
}
class ReturnValue {
constructor(public value: any) {}
}
class _DynamicClass {
constructor(private _classStmt: o.ClassStmt, private _ctx: _ExecutionContext,
private _visitor: StatementInterpreter) {}
instantiate(args: any[]): DynamicInstance {
var props = new Map<string, any>();
var getters = new Map<string, Function>();
var methods = new Map<string, Function>();
var superClass = this._classStmt.parent.visitExpression(this._visitor, this._ctx);
var instanceCtx =
new _ExecutionContext(this._ctx, superClass, null, this._classStmt.name, this._ctx.vars,
props, getters, methods, this._ctx.instanceFactory);
this._classStmt.fields.forEach((field: o.ClassField) => { props.set(field.name, null); });
this._classStmt.getters.forEach((getter: o.ClassGetter) => {
getters.set(getter.name, () => _executeFunctionStatements([], [], getter.body, instanceCtx,
this._visitor));
});
this._classStmt.methods.forEach((method: o.ClassMethod) => {
var paramNames = method.params.map(param => param.name);
methods.set(method.name, _declareFn(paramNames, method.body, instanceCtx, this._visitor));
});
var ctorParamNames = this._classStmt.constructorMethod.params.map(param => param.name);
_executeFunctionStatements(ctorParamNames, args, this._classStmt.constructorMethod.body,
instanceCtx, this._visitor);
return instanceCtx.superInstance;
}
debugAst(): string { return this._visitor.debugAst(this._classStmt); }
}
class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
debugAst(ast: o.Expression | o.Statement | o.Type): string {
return IS_DART ? debugOutputAstAsDart(ast) : debugOutputAstAsTypeScript(ast);
}
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: _ExecutionContext): any {
ctx.vars.set(stmt.name, stmt.value.visitExpression(this, ctx));
return null;
}
visitWriteVarExpr(expr: o.WriteVarExpr, ctx: _ExecutionContext): any {
var value = expr.value.visitExpression(this, ctx);
var currCtx = ctx;
while (currCtx != null) {
if (currCtx.vars.has(expr.name)) {
currCtx.vars.set(expr.name, value);
return value;
}
currCtx = currCtx.parent;
}
throw new BaseException(`Not declared variable ${expr.name}`);
}
visitReadVarExpr(ast: o.ReadVarExpr, ctx: _ExecutionContext): any {
var varName = ast.name;
if (isPresent(ast.builtin)) {
switch (ast.builtin) {
case o.BuiltinVar.Super:
case o.BuiltinVar.This:
return ctx.superInstance;
case o.BuiltinVar.CatchError:
varName = CATCH_ERROR_VAR;
break;
case o.BuiltinVar.CatchStack:
varName = CATCH_STACK_VAR;
break;
default:
throw new BaseException(`Unknown builtin variable ${ast.builtin}`);
}
}
var currCtx = ctx;
while (currCtx != null) {
if (currCtx.vars.has(varName)) {
return currCtx.vars.get(varName);
}
currCtx = currCtx.parent;
}
throw new BaseException(`Not declared variable ${varName}`);
}
visitWriteKeyExpr(expr: o.WriteKeyExpr, ctx: _ExecutionContext): any {
var receiver = expr.receiver.visitExpression(this, ctx);
var index = expr.index.visitExpression(this, ctx);
var value = expr.value.visitExpression(this, ctx);
receiver[index] = value;
return value;
}
visitWritePropExpr(expr: o.WritePropExpr, ctx: _ExecutionContext): any {
var receiver = expr.receiver.visitExpression(this, ctx);
var value = expr.value.visitExpression(this, ctx);
if (isDynamicInstance(receiver)) {
var di = <DynamicInstance>receiver;
if (di.props.has(expr.name)) {
di.props.set(expr.name, value);
} else {
reflector.setter(expr.name)(receiver, value);
}
} else {
reflector.setter(expr.name)(receiver, value);
}
return value;
}
visitInvokeMethodExpr(expr: o.InvokeMethodExpr, ctx: _ExecutionContext): any {
var receiver = expr.receiver.visitExpression(this, ctx);
var args = this.visitAllExpressions(expr.args, ctx);
var result;
if (isPresent(expr.builtin)) {
switch (expr.builtin) {
case o.BuiltinMethod.ConcatArray:
result = ListWrapper.concat(receiver, args[0]);
break;
case o.BuiltinMethod.SubscribeObservable:
result = ObservableWrapper.subscribe(receiver, args[0]);
break;
case o.BuiltinMethod.bind:
if (IS_DART) {
result = receiver;
} else {
result = receiver.bind(args[0]);
}
break;
default:
throw new BaseException(`Unknown builtin method ${expr.builtin}`);
}
} else if (isDynamicInstance(receiver)) {
var di = <DynamicInstance>receiver;
if (di.methods.has(expr.name)) {
result = FunctionWrapper.apply(di.methods.get(expr.name), args);
} else {
result = reflector.method(expr.name)(receiver, args);
}
} else {
result = reflector.method(expr.name)(receiver, args);
}
return result;
}
visitInvokeFunctionExpr(stmt: o.InvokeFunctionExpr, ctx: _ExecutionContext): any {
var args = this.visitAllExpressions(stmt.args, ctx);
var fnExpr = stmt.fn;
if (fnExpr instanceof o.ReadVarExpr && fnExpr.builtin === o.BuiltinVar.Super) {
ctx.superInstance = ctx.instanceFactory.createInstance(ctx.superClass, ctx.className, args,
ctx.props, ctx.getters, ctx.methods);
ctx.parent.superInstance = ctx.superInstance;
return null;
} else {
var fn = stmt.fn.visitExpression(this, ctx);
return FunctionWrapper.apply(fn, args);
}
}
visitReturnStmt(stmt: o.ReturnStatement, ctx: _ExecutionContext): any {
return new ReturnValue(stmt.value.visitExpression(this, ctx));
}
visitDeclareClassStmt(stmt: o.ClassStmt, ctx: _ExecutionContext): any {
var clazz = new _DynamicClass(stmt, ctx, this);
ctx.vars.set(stmt.name, clazz);
return null;
}
visitExpressionStmt(stmt: o.ExpressionStatement, ctx: _ExecutionContext): any {
return stmt.expr.visitExpression(this, ctx);
}
visitIfStmt(stmt: o.IfStmt, ctx: _ExecutionContext): any {
var condition = stmt.condition.visitExpression(this, ctx);
if (condition) {
return this.visitAllStatements(stmt.trueCase, ctx);
} else if (isPresent(stmt.falseCase)) {
return this.visitAllStatements(stmt.falseCase, ctx);
}
return null;
}
visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: _ExecutionContext): any {
try {
return this.visitAllStatements(stmt.bodyStmts, ctx);
} catch (e) {
var childCtx = ctx.createChildWihtLocalVars();
childCtx.vars.set(CATCH_ERROR_VAR, e);
childCtx.vars.set(CATCH_STACK_VAR, e.stack);
return this.visitAllStatements(stmt.catchStmts, childCtx);
}
}
visitThrowStmt(stmt: o.ThrowStmt, ctx: _ExecutionContext): any {
throw stmt.error.visitExpression(this, ctx);
}
visitCommentStmt(stmt: o.CommentStmt, context?: any): any { return null; }
visitInstantiateExpr(ast: o.InstantiateExpr, ctx: _ExecutionContext): any {
var args = this.visitAllExpressions(ast.args, ctx);
var clazz = ast.classExpr.visitExpression(this, ctx);
if (clazz instanceof _DynamicClass) {
return clazz.instantiate(args);
} else {
return FunctionWrapper.apply(reflector.factory(clazz), args);
}
}
visitLiteralExpr(ast: o.LiteralExpr, ctx: _ExecutionContext): any { return ast.value; }
visitExternalExpr(ast: o.ExternalExpr, ctx: _ExecutionContext): any { return ast.value.runtime; }
visitConditionalExpr(ast: o.ConditionalExpr, ctx: _ExecutionContext): any {
if (ast.condition.visitExpression(this, ctx)) {
return ast.trueCase.visitExpression(this, ctx);
} else if (isPresent(ast.falseCase)) {
return ast.falseCase.visitExpression(this, ctx);
}
return null;
}
visitNotExpr(ast: o.NotExpr, ctx: _ExecutionContext): any {
return !ast.condition.visitExpression(this, ctx);
}
visitCastExpr(ast: o.CastExpr, ctx: _ExecutionContext): any {
return ast.value.visitExpression(this, ctx);
}
visitFunctionExpr(ast: o.FunctionExpr, ctx: _ExecutionContext): any {
var paramNames = ast.params.map((param) => param.name);
return _declareFn(paramNames, ast.statements, ctx, this);
}
visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: _ExecutionContext): any {
var paramNames = stmt.params.map((param) => param.name);
ctx.vars.set(stmt.name, _declareFn(paramNames, stmt.statements, ctx, this));
return null;
}
visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: _ExecutionContext): any {
var lhs = () => ast.lhs.visitExpression(this, ctx);
var rhs = () => ast.rhs.visitExpression(this, ctx);
switch (ast.operator) {
case o.BinaryOperator.Equals:
return lhs() == rhs();
case o.BinaryOperator.Identical:
return lhs() === rhs();
case o.BinaryOperator.NotEquals:
return lhs() != rhs();
case o.BinaryOperator.NotIdentical:
return lhs() !== rhs();
case o.BinaryOperator.And:
return lhs() && rhs();
case o.BinaryOperator.Or:
return lhs() || rhs();
case o.BinaryOperator.Plus:
return lhs() + rhs();
case o.BinaryOperator.Minus:
return lhs() - rhs();
case o.BinaryOperator.Divide:
return lhs() / rhs();
case o.BinaryOperator.Multiply:
return lhs() * rhs();
case o.BinaryOperator.Modulo:
return lhs() % rhs();
case o.BinaryOperator.Lower:
return lhs() < rhs();
case o.BinaryOperator.LowerEquals:
return lhs() <= rhs();
case o.BinaryOperator.Bigger:
return lhs() > rhs();
case o.BinaryOperator.BiggerEquals:
return lhs() >= rhs();
default:
throw new BaseException(`Unknown operator ${ast.operator}`);
}
}
visitReadPropExpr(ast: o.ReadPropExpr, ctx: _ExecutionContext): any {
var result;
var receiver = ast.receiver.visitExpression(this, ctx);
if (isDynamicInstance(receiver)) {
var di = <DynamicInstance>receiver;
if (di.props.has(ast.name)) {
result = di.props.get(ast.name);
} else if (di.getters.has(ast.name)) {
result = di.getters.get(ast.name)();
} else if (di.methods.has(ast.name)) {
result = di.methods.get(ast.name);
} else {
result = reflector.getter(ast.name)(receiver);
}
} else {
result = reflector.getter(ast.name)(receiver);
}
return result;
}
visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: _ExecutionContext): any {
var receiver = ast.receiver.visitExpression(this, ctx);
var prop = ast.index.visitExpression(this, ctx);
return receiver[prop];
}
visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: _ExecutionContext): any {
return this.visitAllExpressions(ast.entries, ctx);
}
visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: _ExecutionContext): any {
var result = {};
ast.entries.forEach((entry) => result[<string>entry[0]] =
(<o.Expression>entry[1]).visitExpression(this, ctx));
return result;
}
visitAllExpressions(expressions: o.Expression[], ctx: _ExecutionContext): any {
return expressions.map((expr) => expr.visitExpression(this, ctx));
}
visitAllStatements(statements: o.Statement[], ctx: _ExecutionContext): ReturnValue {
for (var i = 0; i < statements.length; i++) {
var stmt = statements[i];
var val = stmt.visitStatement(this, ctx);
if (val instanceof ReturnValue) {
return val;
}
}
return null;
}
}
function _declareFn(varNames: string[], statements: o.Statement[], ctx: _ExecutionContext,
visitor: StatementInterpreter): Function {
switch (varNames.length) {
case 0:
return () => _executeFunctionStatements(varNames, [], statements, ctx, visitor);
case 1:
return (d0) => _executeFunctionStatements(varNames, [d0], statements, ctx, visitor);
case 2:
return (d0, d1) => _executeFunctionStatements(varNames, [d0, d1], statements, ctx, visitor);
case 3:
return (d0, d1, d2) =>
_executeFunctionStatements(varNames, [d0, d1, d2], statements, ctx, visitor);
case 4:
return (d0, d1, d2, d3) =>
_executeFunctionStatements(varNames, [d0, d1, d2, d3], statements, ctx, visitor);
case 5:
return (d0, d1, d2, d3, d4) => _executeFunctionStatements(varNames, [d0, d1, d2, d3, d4],
statements, ctx, visitor);
case 6:
return (d0, d1, d2, d3, d4, d5) => _executeFunctionStatements(
varNames, [d0, d1, d2, d3, d4, d5], statements, ctx, visitor);
case 7:
return (d0, d1, d2, d3, d4, d5, d6) => _executeFunctionStatements(
varNames, [d0, d1, d2, d3, d4, d5, d6], statements, ctx, visitor);
case 8:
return (d0, d1, d2, d3, d4, d5, d6, d7) => _executeFunctionStatements(
varNames, [d0, d1, d2, d3, d4, d5, d6, d7], statements, ctx, visitor);
case 9:
return (d0, d1, d2, d3, d4, d5, d6, d7, d8) => _executeFunctionStatements(
varNames, [d0, d1, d2, d3, d4, d5, d6, d7, d8], statements, ctx, visitor);
case 10:
return (d0, d1, d2, d3, d4, d5, d6, d7, d8, d9) => _executeFunctionStatements(
varNames, [d0, d1, d2, d3, d4, d5, d6, d7, d8, d9], statements, ctx, visitor);
default:
throw new BaseException(
'Declaring functions with more than 10 arguments is not supported right now');
}
}
var CATCH_ERROR_VAR = 'error';
var CATCH_STACK_VAR = 'stack';

View File

@ -0,0 +1,46 @@
import {
isPresent,
isBlank,
isString,
evalExpression,
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import * as o from './output_ast';
import {EmitterVisitorContext} from './abstract_emitter';
import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
import {sanitizeIdentifier} from '../util';
export function jitStatements(sourceUrl: string, statements: o.Statement[],
resultVar: string): any {
var converter = new JitEmitterVisitor();
var ctx = EmitterVisitorContext.createRoot([resultVar]);
converter.visitAllStatements(statements, ctx);
return evalExpression(sourceUrl, resultVar, ctx.toSource(), converter.getArgs());
}
class JitEmitterVisitor extends AbstractJsEmitterVisitor {
private _evalArgNames: string[] = [];
private _evalArgValues: any[] = [];
getArgs(): {[key: string]: any} {
var result = {};
for (var i = 0; i < this._evalArgNames.length; i++) {
result[this._evalArgNames[i]] = this._evalArgValues[i];
}
return result;
}
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
var value = ast.value.runtime;
var id = this._evalArgValues.indexOf(value);
if (id === -1) {
id = this._evalArgValues.length;
this._evalArgValues.push(value);
var name = isPresent(ast.value.name) ? sanitizeIdentifier(ast.value.name) : 'val';
this._evalArgNames.push(sanitizeIdentifier(`jit_${name}${id}`));
}
ctx.print(this._evalArgNames[id]);
return null;
}
}

View File

@ -0,0 +1,80 @@
import {BaseException} from 'angular2/src/facade/exceptions';
import {isPresent, isBlank, RegExpWrapper, Math} from 'angular2/src/facade/lang';
// asset:<package-name>/<realm>/<path-to-module>
var _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/g;
var _PATH_SEP = '/';
var _PATH_SEP_RE = /\//g;
export enum ImportEnv {
Dart,
JS
}
/**
* Returns the module path to use for an import.
*/
export function getImportModulePath(moduleUrlStr: string, importedUrlStr: string,
importEnv: ImportEnv): string {
var absolutePathPrefix: string = importEnv === ImportEnv.Dart ? `package:` : '';
var moduleUrl = _AssetUrl.parse(moduleUrlStr, false);
var importedUrl = _AssetUrl.parse(importedUrlStr, true);
if (isBlank(importedUrl)) {
return importedUrlStr;
}
// Try to create a relative path first
if (moduleUrl.firstLevelDir == importedUrl.firstLevelDir &&
moduleUrl.packageName == importedUrl.packageName) {
return getRelativePath(moduleUrl.modulePath, importedUrl.modulePath, importEnv);
} else if (importedUrl.firstLevelDir == 'lib') {
return `${absolutePathPrefix}${importedUrl.packageName}/${importedUrl.modulePath}`;
}
throw new BaseException(`Can't import url ${importedUrlStr} from ${moduleUrlStr}`);
}
class _AssetUrl {
static parse(url: string, allowNonMatching: boolean): _AssetUrl {
var match = RegExpWrapper.firstMatch(_ASSET_URL_RE, url);
if (isPresent(match)) {
return new _AssetUrl(match[1], match[2], match[3]);
}
if (allowNonMatching) {
return null;
}
throw new BaseException(`Url ${url} is not a valid asset: url`);
}
constructor(public packageName: string, public firstLevelDir: string, public modulePath: string) {
}
}
export function getRelativePath(modulePath: string, importedPath: string,
importEnv: ImportEnv): string {
var moduleParts = modulePath.split(_PATH_SEP_RE);
var importedParts = importedPath.split(_PATH_SEP_RE);
var longestPrefix = getLongestPathSegmentPrefix(moduleParts, importedParts);
var resultParts = [];
var goParentCount = moduleParts.length - 1 - longestPrefix;
for (var i = 0; i < goParentCount; i++) {
resultParts.push('..');
}
if (goParentCount <= 0 && importEnv === ImportEnv.JS) {
resultParts.push('.');
}
for (var i = longestPrefix; i < importedParts.length; i++) {
resultParts.push(importedParts[i]);
}
return resultParts.join(_PATH_SEP);
}
export function getLongestPathSegmentPrefix(arr1: string[], arr2: string[]): number {
var prefixSize = 0;
var minLen = Math.min(arr1.length, arr2.length);
while (prefixSize < minLen && arr1[prefixSize] == arr2[prefixSize]) {
prefixSize++;
}
return prefixSize;
}

View File

@ -0,0 +1,332 @@
import * as o from './output_ast';
import {
isPresent,
isBlank,
isString,
evalExpression,
RegExpWrapper,
StringWrapper,
isArray
} from 'angular2/src/facade/lang';
import {CompileIdentifierMetadata} from '../compile_metadata';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
OutputEmitter,
EmitterVisitorContext,
AbstractEmitterVisitor,
CATCH_ERROR_VAR,
CATCH_STACK_VAR
} from './abstract_emitter';
import {getImportModulePath, ImportEnv} from './path_util';
var _debugModuleUrl = 'asset://debug/lib';
export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.Type |
any[]): string {
var converter = new _TsEmitterVisitor(_debugModuleUrl);
var ctx = EmitterVisitorContext.createRoot([]);
var asts: any[];
if (isArray(ast)) {
asts = <any[]>ast;
} else {
asts = [ast];
}
asts.forEach((ast) => {
if (ast instanceof o.Statement) {
ast.visitStatement(converter, ctx);
} else if (ast instanceof o.Expression) {
ast.visitExpression(converter, ctx);
} else if (ast instanceof o.Type) {
ast.visitType(converter, ctx);
} else {
throw new BaseException(`Don't know how to print debug info for ${ast}`);
}
});
return ctx.toSource();
}
export class TypeScriptEmitter implements OutputEmitter {
constructor() {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
var converter = new _TsEmitterVisitor(moduleUrl);
var ctx = EmitterVisitorContext.createRoot(exportedVars);
converter.visitAllStatements(stmts, ctx);
var srcParts = [];
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
// Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(
`imp` +
`ort * as ${prefix} from '${getImportModulePath(moduleUrl, importedModuleUrl, ImportEnv.JS)}';`);
});
srcParts.push(ctx.toSource());
return srcParts.join('\n');
}
}
class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor {
constructor(private _moduleUrl: string) { super(false); }
importsWithPrefixes = new Map<string, string>();
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
this._visitIdentifier(ast.value, ast.typeParams, ctx);
return null;
}
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
if (ctx.isExportedVar(stmt.name)) {
ctx.print(`export `);
}
if (stmt.hasModifier(o.StmtModifier.Final)) {
ctx.print(`const`);
} else {
ctx.print(`var`);
}
ctx.print(` ${stmt.name}`);
if (isPresent(stmt.type)) {
ctx.print(`:`);
stmt.type.visitType(this, ctx);
}
ctx.print(` = `);
stmt.value.visitExpression(this, ctx);
ctx.println(`;`);
return null;
}
visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any {
ctx.print(`(<`);
ast.type.visitType(this, ctx);
ctx.print(`>`);
ast.value.visitExpression(this, ctx);
ctx.print(`)`);
return null;
}
visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any {
ctx.pushClass(stmt);
if (ctx.isExportedVar(stmt.name)) {
ctx.print(`export `);
}
ctx.print(`class ${stmt.name}`);
if (isPresent(stmt.parent)) {
ctx.print(` extends `);
stmt.parent.visitExpression(this, ctx);
}
ctx.println(` {`);
ctx.incIndent();
stmt.fields.forEach((field) => this._visitClassField(field, ctx));
if (isPresent(stmt.constructorMethod)) {
this._visitClassConstructor(stmt, ctx);
}
stmt.getters.forEach((getter) => this._visitClassGetter(getter, ctx));
stmt.methods.forEach((method) => this._visitClassMethod(method, ctx));
ctx.decIndent();
ctx.println(`}`);
ctx.popClass();
return null;
}
private _visitClassField(field: o.ClassField, ctx: EmitterVisitorContext) {
if (field.hasModifier(o.StmtModifier.Private)) {
ctx.print(`private `);
}
ctx.print(field.name);
if (isPresent(field.type)) {
ctx.print(`:`);
field.type.visitType(this, ctx);
} else {
ctx.print(`: any`);
}
ctx.println(`;`);
}
private _visitClassGetter(getter: o.ClassGetter, ctx: EmitterVisitorContext) {
if (getter.hasModifier(o.StmtModifier.Private)) {
ctx.print(`private `);
}
ctx.print(`get ${getter.name}()`);
if (isPresent(getter.type)) {
ctx.print(`:`);
getter.type.visitType(this, ctx);
}
ctx.println(` {`);
ctx.incIndent();
this.visitAllStatements(getter.body, ctx);
ctx.decIndent();
ctx.println(`}`);
}
private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) {
ctx.print(`constructor(`);
this._visitParams(stmt.constructorMethod.params, ctx);
ctx.println(`) {`);
ctx.incIndent();
this.visitAllStatements(stmt.constructorMethod.body, ctx);
ctx.decIndent();
ctx.println(`}`);
}
private _visitClassMethod(method: o.ClassMethod, ctx: EmitterVisitorContext) {
if (method.hasModifier(o.StmtModifier.Private)) {
ctx.print(`private `);
}
ctx.print(`${method.name}(`);
this._visitParams(method.params, ctx);
ctx.print(`):`);
if (isPresent(method.type)) {
method.type.visitType(this, ctx);
} else {
ctx.print(`void`);
}
ctx.println(` {`);
ctx.incIndent();
this.visitAllStatements(method.body, ctx);
ctx.decIndent();
ctx.println(`}`);
}
visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any {
ctx.print(`(`);
this._visitParams(ast.params, ctx);
ctx.print(`):`);
if (isPresent(ast.type)) {
ast.type.visitType(this, ctx);
} else {
ctx.print(`void`);
}
ctx.println(` => {`);
ctx.incIndent();
this.visitAllStatements(ast.statements, ctx);
ctx.decIndent();
ctx.print(`}`);
return null;
}
visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any {
if (ctx.isExportedVar(stmt.name)) {
ctx.print(`export `);
}
ctx.print(`function ${stmt.name}(`);
this._visitParams(stmt.params, ctx);
ctx.print(`):`);
if (isPresent(stmt.type)) {
stmt.type.visitType(this, ctx);
} else {
ctx.print(`void`);
}
ctx.println(` {`);
ctx.incIndent();
this.visitAllStatements(stmt.statements, ctx);
ctx.decIndent();
ctx.println(`}`);
return null;
}
visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any {
ctx.println(`try {`);
ctx.incIndent();
this.visitAllStatements(stmt.bodyStmts, ctx);
ctx.decIndent();
ctx.println(`} catch (${CATCH_ERROR_VAR.name}) {`);
ctx.incIndent();
var catchStmts = [
<o.Statement>CATCH_STACK_VAR.set(CATCH_ERROR_VAR.prop('stack'))
.toDeclStmt(null, [o.StmtModifier.Final])
].concat(stmt.catchStmts);
this.visitAllStatements(catchStmts, ctx);
ctx.decIndent();
ctx.println(`}`);
return null;
}
visitBuiltintType(type: o.BuiltinType, ctx: EmitterVisitorContext): any {
var typeStr;
switch (type.name) {
case o.BuiltinTypeName.Bool:
typeStr = 'boolean';
break;
case o.BuiltinTypeName.Dynamic:
typeStr = 'any';
break;
case o.BuiltinTypeName.Function:
typeStr = 'Function';
break;
case o.BuiltinTypeName.Number:
typeStr = 'number';
break;
case o.BuiltinTypeName.Int:
typeStr = 'number';
break;
case o.BuiltinTypeName.String:
typeStr = 'string';
break;
default:
throw new BaseException(`Unsupported builtin type ${type.name}`);
}
ctx.print(typeStr);
return null;
}
visitExternalType(ast: o.ExternalType, ctx: EmitterVisitorContext): any {
this._visitIdentifier(ast.value, ast.typeParams, ctx);
return null;
}
visitArrayType(type: o.ArrayType, ctx: EmitterVisitorContext): any {
if (isPresent(type.of)) {
type.of.visitType(this, ctx);
} else {
ctx.print(`any`);
}
ctx.print(`[]`);
return null;
}
visitMapType(type: o.MapType, ctx: EmitterVisitorContext): any {
ctx.print(`{[key: string]:`);
if (isPresent(type.valueType)) {
type.valueType.visitType(this, ctx);
} else {
ctx.print(`any`);
}
ctx.print(`}`);
return null;
}
getBuiltinMethodName(method: o.BuiltinMethod): string {
var name;
switch (method) {
case o.BuiltinMethod.ConcatArray:
name = 'concat';
break;
case o.BuiltinMethod.SubscribeObservable:
name = 'subscribe';
break;
case o.BuiltinMethod.bind:
name = 'bind';
break;
default:
throw new BaseException(`Unknown builtin method: ${method}`);
}
return name;
}
private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void {
this.visitAllObjects((param) => {
ctx.print(param.name);
if (isPresent(param.type)) {
ctx.print(`:`);
param.type.visitType(this, ctx);
}
}, params, ctx, ',');
}
private _visitIdentifier(value: CompileIdentifierMetadata, typeParams: o.Type[],
ctx: EmitterVisitorContext): void {
if (isBlank(value.name)) {
throw new BaseException(`Internal error: unknown identifier ${value}`);
}
if (isPresent(value.moduleUrl) && value.moduleUrl != this._moduleUrl) {
var prefix = this.importsWithPrefixes.get(value.moduleUrl);
if (isBlank(prefix)) {
prefix = `import${this.importsWithPrefixes.size}`;
this.importsWithPrefixes.set(value.moduleUrl, prefix);
}
ctx.print(`${prefix}.`);
}
ctx.print(value.name);
if (isPresent(typeParams) && typeParams.length > 0) {
ctx.print(`<`);
this.visitAllObjects((type) => type.visitType(this, ctx), typeParams, ctx, ',');
ctx.print(`>`);
}
}
}

View File

@ -0,0 +1,66 @@
export class ParseLocation {
constructor(public file: ParseSourceFile, public offset: number, public line: number,
public col: number) {}
toString(): string { return `${this.file.url}@${this.line}:${this.col}`; }
}
export class ParseSourceFile {
constructor(public content: string, public url: string) {}
}
export class ParseSourceSpan {
constructor(public start: ParseLocation, public end: ParseLocation) {}
toString(): string {
return this.start.file.content.substring(this.start.offset, this.end.offset);
}
}
export enum ParseErrorLevel {
WARNING,
FATAL
}
export abstract class ParseError {
constructor(public span: ParseSourceSpan, public msg: string,
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
toString(): string {
var source = this.span.start.file.content;
var ctxStart = this.span.start.offset;
if (ctxStart > source.length - 1) {
ctxStart = source.length - 1;
}
var ctxEnd = ctxStart;
var ctxLen = 0;
var ctxLines = 0;
while (ctxLen < 100 && ctxStart > 0) {
ctxStart--;
ctxLen++;
if (source[ctxStart] == "\n") {
if (++ctxLines == 3) {
break;
}
}
}
ctxLen = 0;
ctxLines = 0;
while (ctxLen < 100 && ctxEnd < source.length - 1) {
ctxEnd++;
ctxLen++;
if (source[ctxEnd] == "\n") {
if (++ctxLines == 3) {
break;
}
}
}
let context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
source.substring(this.span.start.offset, ctxEnd + 1);
return `${this.msg} ("${context}"): ${this.span.start}`;
}
}

View File

@ -0,0 +1,45 @@
import {resolveForwardRef, Injectable} from 'angular2/src/core/di';
import {Type, isPresent, stringify} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {PipeMetadata} from 'angular2/src/core/metadata';
import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
import {reflector} from 'angular2/src/core/reflection/reflection';
function _isPipeMetadata(type: any): boolean {
return type instanceof PipeMetadata;
}
/**
* Resolve a `Type` for {@link PipeMetadata}.
*
* This interface can be overridden by the application developer to create custom behavior.
*
* See {@link Compiler}
*/
@Injectable()
export class PipeResolver {
private _reflector: ReflectorReader;
constructor(_reflector?: ReflectorReader) {
if (isPresent(_reflector)) {
this._reflector = _reflector;
} else {
this._reflector = reflector;
}
}
/**
* Return {@link PipeMetadata} for a given `Type`.
*/
resolve(type: Type): PipeMetadata {
var metas = this._reflector.annotations(resolveForwardRef(type));
if (isPresent(metas)) {
var annotation = metas.find(_isPipeMetadata);
if (isPresent(annotation)) {
return annotation;
}
}
throw new BaseException(`No Pipe decorator found on ${stringify(type)}`);
}
}
export var CODEGEN_PIPE_RESOLVER = new PipeResolver(reflector);

View File

@ -0,0 +1,431 @@
import {isPresent, isBlank, isArray, normalizeBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
ReferenceAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll,
PropertyBindingType,
ProviderAst,
ProviderAstType
} from './template_ast';
import {
CompileTypeMetadata,
CompileTokenMap,
CompileQueryMetadata,
CompileTokenMetadata,
CompileProviderMetadata,
CompileDirectiveMetadata,
CompileDiDependencyMetadata
} from './compile_metadata';
import {Identifiers, identifierToken} from './identifiers';
import {ParseSourceSpan, ParseError, ParseLocation} from './parse_util';
export class ProviderError extends ParseError {
constructor(message: string, span: ParseSourceSpan) { super(span, message); }
}
export class ProviderViewContext {
/**
* @internal
*/
viewQueries: CompileTokenMap<CompileQueryMetadata[]>;
/**
* @internal
*/
viewProviders: CompileTokenMap<boolean>;
errors: ProviderError[] = [];
constructor(public component: CompileDirectiveMetadata, public sourceSpan: ParseSourceSpan) {
this.viewQueries = _getViewQueries(component);
this.viewProviders = new CompileTokenMap<boolean>();
_normalizeProviders(component.viewProviders, sourceSpan, this.errors)
.forEach((provider) => {
if (isBlank(this.viewProviders.get(provider.token))) {
this.viewProviders.add(provider.token, true);
}
});
}
}
export class ProviderElementContext {
private _contentQueries: CompileTokenMap<CompileQueryMetadata[]>;
private _transformedProviders = new CompileTokenMap<ProviderAst>();
private _seenProviders = new CompileTokenMap<boolean>();
private _allProviders: CompileTokenMap<ProviderAst>;
private _attrs: {[key: string]: string};
private _hasViewContainer: boolean = false;
constructor(private _viewContext: ProviderViewContext, private _parent: ProviderElementContext,
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[],
attrs: AttrAst[], refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
this._attrs = {};
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
var directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
this._allProviders =
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, _viewContext.errors);
this._contentQueries = _getContentQueries(directivesMeta);
var queriedTokens = new CompileTokenMap<boolean>();
this._allProviders.values().forEach(
(provider) => { this._addQueryReadsTo(provider.token, queriedTokens); });
refs.forEach((refAst) => {
this._addQueryReadsTo(new CompileTokenMetadata({value: refAst.name}), queriedTokens);
});
if (isPresent(queriedTokens.get(identifierToken(Identifiers.ViewContainerRef)))) {
this._hasViewContainer = true;
}
// create the providers that we know are eager first
this._allProviders.values().forEach((provider) => {
var eager = provider.eager || isPresent(queriedTokens.get(provider.token));
if (eager) {
this._getOrCreateLocalProvider(provider.providerType, provider.token, true);
}
});
}
afterElement() {
// collect lazy providers
this._allProviders.values().forEach((provider) => {
this._getOrCreateLocalProvider(provider.providerType, provider.token, false);
});
}
get transformProviders(): ProviderAst[] { return this._transformedProviders.values(); }
get transformedDirectiveAsts(): DirectiveAst[] {
var sortedProviderTypes =
this._transformedProviders.values().map(provider => provider.token.identifier);
var sortedDirectives = ListWrapper.clone(this._directiveAsts);
ListWrapper.sort(sortedDirectives,
(dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) -
sortedProviderTypes.indexOf(dir2.directive.type));
return sortedDirectives;
}
get transformedHasViewContainer(): boolean { return this._hasViewContainer; }
private _addQueryReadsTo(token: CompileTokenMetadata, queryReadTokens: CompileTokenMap<boolean>) {
this._getQueriesFor(token).forEach((query) => {
var queryReadToken = isPresent(query.read) ? query.read : token;
if (isBlank(queryReadTokens.get(queryReadToken))) {
queryReadTokens.add(queryReadToken, true);
}
});
}
private _getQueriesFor(token: CompileTokenMetadata): CompileQueryMetadata[] {
var result: CompileQueryMetadata[] = [];
var currentEl: ProviderElementContext = this;
var distance = 0;
var queries: CompileQueryMetadata[];
while (currentEl !== null) {
queries = currentEl._contentQueries.get(token);
if (isPresent(queries)) {
ListWrapper.addAll(result, queries.filter((query) => query.descendants || distance <= 1));
}
if (currentEl._directiveAsts.length > 0) {
distance++;
}
currentEl = currentEl._parent;
}
queries = this._viewContext.viewQueries.get(token);
if (isPresent(queries)) {
ListWrapper.addAll(result, queries);
}
return result;
}
private _getOrCreateLocalProvider(requestingProviderType: ProviderAstType,
token: CompileTokenMetadata, eager: boolean): ProviderAst {
var resolvedProvider = this._allProviders.get(token);
if (isBlank(resolvedProvider) ||
((requestingProviderType === ProviderAstType.Directive ||
requestingProviderType === ProviderAstType.PublicService) &&
resolvedProvider.providerType === ProviderAstType.PrivateService) ||
((requestingProviderType === ProviderAstType.PrivateService ||
requestingProviderType === ProviderAstType.PublicService) &&
resolvedProvider.providerType === ProviderAstType.Builtin)) {
return null;
}
var transformedProviderAst = this._transformedProviders.get(token);
if (isPresent(transformedProviderAst)) {
return transformedProviderAst;
}
if (isPresent(this._seenProviders.get(token))) {
this._viewContext.errors.push(new ProviderError(
`Cannot instantiate cyclic dependency! ${token.name}`, this._sourceSpan));
return null;
}
this._seenProviders.add(token, true);
var transformedProviders = resolvedProvider.providers.map((provider) => {
var transformedUseValue = provider.useValue;
var transformedUseExisting = provider.useExisting;
var transformedDeps;
if (isPresent(provider.useExisting)) {
var existingDiDep = this._getDependency(
resolvedProvider.providerType,
new CompileDiDependencyMetadata({token: provider.useExisting}), eager);
if (isPresent(existingDiDep.token)) {
transformedUseExisting = existingDiDep.token;
} else {
transformedUseExisting = null;
transformedUseValue = existingDiDep.value;
}
} else if (isPresent(provider.useFactory)) {
var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps;
transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
} else if (isPresent(provider.useClass)) {
var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps;
transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
}
return _transformProvider(provider, {
useExisting: transformedUseExisting,
useValue: transformedUseValue,
deps: transformedDeps
});
});
transformedProviderAst =
_transformProviderAst(resolvedProvider, {eager: eager, providers: transformedProviders});
this._transformedProviders.add(token, transformedProviderAst);
return transformedProviderAst;
}
private _getLocalDependency(requestingProviderType: ProviderAstType,
dep: CompileDiDependencyMetadata,
eager: boolean = null): CompileDiDependencyMetadata {
if (dep.isAttribute) {
var attrValue = this._attrs[dep.token.value];
return new CompileDiDependencyMetadata({isValue: true, value: normalizeBlank(attrValue)});
}
if (isPresent(dep.query) || isPresent(dep.viewQuery)) {
return dep;
}
if (isPresent(dep.token)) {
// access builtints
if ((requestingProviderType === ProviderAstType.Directive ||
requestingProviderType === ProviderAstType.Component)) {
if (dep.token.equalsTo(identifierToken(Identifiers.Renderer)) ||
dep.token.equalsTo(identifierToken(Identifiers.ElementRef)) ||
dep.token.equalsTo(identifierToken(Identifiers.ChangeDetectorRef)) ||
dep.token.equalsTo(identifierToken(Identifiers.TemplateRef))) {
return dep;
}
if (dep.token.equalsTo(identifierToken(Identifiers.ViewContainerRef))) {
this._hasViewContainer = true;
}
}
// access the injector
if (dep.token.equalsTo(identifierToken(Identifiers.Injector))) {
return dep;
}
// access providers
if (isPresent(this._getOrCreateLocalProvider(requestingProviderType, dep.token, eager))) {
return dep;
}
}
return null;
}
private _getDependency(requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata,
eager: boolean = null): CompileDiDependencyMetadata {
var currElement: ProviderElementContext = this;
var currEager: boolean = eager;
var result: CompileDiDependencyMetadata = null;
if (!dep.isSkipSelf) {
result = this._getLocalDependency(requestingProviderType, dep, eager);
}
if (dep.isSelf) {
if (isBlank(result) && dep.isOptional) {
result = new CompileDiDependencyMetadata({isValue: true, value: null});
}
} else {
// check parent elements
while (isBlank(result) && isPresent(currElement._parent)) {
var prevElement = currElement;
currElement = currElement._parent;
if (prevElement._isViewRoot) {
currEager = false;
}
result = currElement._getLocalDependency(ProviderAstType.PublicService, dep, currEager);
}
// check @Host restriction
if (isBlank(result)) {
if (!dep.isHost || this._viewContext.component.type.isHost ||
identifierToken(this._viewContext.component.type).equalsTo(dep.token) ||
isPresent(this._viewContext.viewProviders.get(dep.token))) {
result = dep;
} else {
result = dep.isOptional ?
result = new CompileDiDependencyMetadata({isValue: true, value: null}) :
null;
}
}
}
if (isBlank(result)) {
this._viewContext.errors.push(
new ProviderError(`No provider for ${dep.token.name}`, this._sourceSpan));
}
return result;
}
}
function _transformProvider(
provider: CompileProviderMetadata,
{useExisting, useValue, deps}:
{useExisting: CompileTokenMetadata, useValue: any, deps: CompileDiDependencyMetadata[]}) {
return new CompileProviderMetadata({
token: provider.token,
useClass: provider.useClass,
useExisting: useExisting,
useFactory: provider.useFactory,
useValue: useValue,
deps: deps,
multi: provider.multi
});
}
function _transformProviderAst(
provider: ProviderAst,
{eager, providers}: {eager: boolean, providers: CompileProviderMetadata[]}): ProviderAst {
return new ProviderAst(provider.token, provider.multiProvider, provider.eager || eager, providers,
provider.providerType, provider.sourceSpan);
}
function _normalizeProviders(
providers: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>,
sourceSpan: ParseSourceSpan, targetErrors: ParseError[],
targetProviders: CompileProviderMetadata[] = null): CompileProviderMetadata[] {
if (isBlank(targetProviders)) {
targetProviders = [];
}
if (isPresent(providers)) {
providers.forEach((provider) => {
if (isArray(provider)) {
_normalizeProviders(<any[]>provider, sourceSpan, targetErrors, targetProviders);
} else {
var normalizeProvider: CompileProviderMetadata;
if (provider instanceof CompileProviderMetadata) {
normalizeProvider = provider;
} else if (provider instanceof CompileTypeMetadata) {
normalizeProvider = new CompileProviderMetadata(
{token: new CompileTokenMetadata({identifier: provider}), useClass: provider});
} else {
targetErrors.push(new ProviderError(`Unknown provider type ${provider}`, sourceSpan));
}
if (isPresent(normalizeProvider)) {
targetProviders.push(normalizeProvider);
}
}
});
}
return targetProviders;
}
function _resolveProvidersFromDirectives(directives: CompileDirectiveMetadata[],
sourceSpan: ParseSourceSpan,
targetErrors: ParseError[]): CompileTokenMap<ProviderAst> {
var providersByToken = new CompileTokenMap<ProviderAst>();
directives.forEach((directive) => {
var dirProvider = new CompileProviderMetadata(
{token: new CompileTokenMetadata({identifier: directive.type}), useClass: directive.type});
_resolveProviders([dirProvider],
directive.isComponent ? ProviderAstType.Component : ProviderAstType.Directive,
true, sourceSpan, targetErrors, providersByToken);
});
// Note: directives need to be able to overwrite providers of a component!
var directivesWithComponentFirst =
directives.filter(dir => dir.isComponent).concat(directives.filter(dir => !dir.isComponent));
directivesWithComponentFirst.forEach((directive) => {
_resolveProviders(_normalizeProviders(directive.providers, sourceSpan, targetErrors),
ProviderAstType.PublicService, false, sourceSpan, targetErrors,
providersByToken);
_resolveProviders(_normalizeProviders(directive.viewProviders, sourceSpan, targetErrors),
ProviderAstType.PrivateService, false, sourceSpan, targetErrors,
providersByToken);
});
return providersByToken;
}
function _resolveProviders(providers: CompileProviderMetadata[], providerType: ProviderAstType,
eager: boolean, sourceSpan: ParseSourceSpan, targetErrors: ParseError[],
targetProvidersByToken: CompileTokenMap<ProviderAst>) {
providers.forEach((provider) => {
var resolvedProvider = targetProvidersByToken.get(provider.token);
if (isPresent(resolvedProvider) && resolvedProvider.multiProvider !== provider.multi) {
targetErrors.push(new ProviderError(
`Mixing multi and non multi provider is not possible for token ${resolvedProvider.token.name}`,
sourceSpan));
}
if (isBlank(resolvedProvider)) {
resolvedProvider = new ProviderAst(provider.token, provider.multi, eager, [provider],
providerType, sourceSpan);
targetProvidersByToken.add(provider.token, resolvedProvider);
} else {
if (!provider.multi) {
ListWrapper.clear(resolvedProvider.providers);
}
resolvedProvider.providers.push(provider);
}
});
}
function _getViewQueries(
component: CompileDirectiveMetadata): CompileTokenMap<CompileQueryMetadata[]> {
var viewQueries = new CompileTokenMap<CompileQueryMetadata[]>();
if (isPresent(component.viewQueries)) {
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
}
component.type.diDeps.forEach((dep) => {
if (isPresent(dep.viewQuery)) {
_addQueryToTokenMap(viewQueries, dep.viewQuery);
}
});
return viewQueries;
}
function _getContentQueries(
directives: CompileDirectiveMetadata[]): CompileTokenMap<CompileQueryMetadata[]> {
var contentQueries = new CompileTokenMap<CompileQueryMetadata[]>();
directives.forEach(directive => {
if (isPresent(directive.queries)) {
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
}
directive.type.diDeps.forEach((dep) => {
if (isPresent(dep.query)) {
_addQueryToTokenMap(contentQueries, dep.query);
}
});
});
return contentQueries;
}
function _addQueryToTokenMap(map: CompileTokenMap<CompileQueryMetadata[]>,
query: CompileQueryMetadata) {
query.selectors.forEach((token: CompileTokenMetadata) => {
var entry = map.get(token);
if (isBlank(entry)) {
entry = [];
map.add(token, entry);
}
entry.push(query);
});
}

View File

@ -0,0 +1,238 @@
import {
IS_DART,
Type,
Json,
isBlank,
isPresent,
stringify,
evalExpression
} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
ListWrapper,
SetWrapper,
MapWrapper,
StringMapWrapper
} from 'angular2/src/facade/collection';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {
createHostComponentMeta,
CompileDirectiveMetadata,
CompileTypeMetadata,
CompileTemplateMetadata,
CompilePipeMetadata,
CompileMetadataWithType,
CompileIdentifierMetadata
} from './compile_metadata';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll
} from './template_ast';
import {Injectable} from 'angular2/src/core/di';
import {StyleCompiler, StylesCompileDependency, StylesCompileResult} from './style_compiler';
import {ViewCompiler} from './view_compiler/view_compiler';
import {TemplateParser} from './template_parser';
import {DirectiveNormalizer} from './directive_normalizer';
import {CompileMetadataResolver} from './metadata_resolver';
import {ComponentFactory} from 'angular2/src/core/linker/component_factory';
import {
ComponentResolver,
ReflectorComponentResolver
} from 'angular2/src/core/linker/component_resolver';
import {CompilerConfig} from './config';
import * as ir from './output/output_ast';
import {jitStatements} from './output/output_jit';
import {interpretStatements} from './output/output_interpreter';
import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view';
import {XHR} from 'angular2/src/compiler/xhr';
/**
* An internal module of the Angular compiler that begins with component types,
* extracts templates, and eventually produces a compiled version of the component
* ready for linking into an application.
*/
@Injectable()
export class RuntimeCompiler implements ComponentResolver {
private _styleCache: Map<string, Promise<string>> = new Map<string, Promise<string>>();
private _hostCacheKeys = new Map<Type, any>();
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
private _compiledTemplateDone = new Map<any, Promise<CompiledTemplate>>();
constructor(private _metadataResolver: CompileMetadataResolver,
private _templateNormalizer: DirectiveNormalizer,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _xhr: XHR,
private _genConfig: CompilerConfig) {}
resolveComponent(componentType: Type): Promise<ComponentFactory<any>> {
var compMeta: CompileDirectiveMetadata =
this._metadataResolver.getDirectiveMetadata(componentType);
var hostCacheKey = this._hostCacheKeys.get(componentType);
if (isBlank(hostCacheKey)) {
hostCacheKey = new Object();
this._hostCacheKeys.set(componentType, hostCacheKey);
assertComponent(compMeta);
var hostMeta: CompileDirectiveMetadata =
createHostComponentMeta(compMeta.type, compMeta.selector);
this._loadAndCompileComponent(hostCacheKey, hostMeta, [compMeta], [], []);
}
return this._compiledTemplateDone.get(hostCacheKey)
.then((compiledTemplate: CompiledTemplate) => new ComponentFactory(
compMeta.selector, compiledTemplate.viewFactory, componentType));
}
clearCache() {
this._styleCache.clear();
this._compiledTemplateCache.clear();
this._compiledTemplateDone.clear();
this._hostCacheKeys.clear();
}
private _loadAndCompileComponent(cacheKey: any, compMeta: CompileDirectiveMetadata,
viewDirectives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[],
compilingComponentsPath: any[]): CompiledTemplate {
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
var done = this._compiledTemplateDone.get(cacheKey);
if (isBlank(compiledTemplate)) {
compiledTemplate = new CompiledTemplate();
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
done =
PromiseWrapper.all(
[<any>this._compileComponentStyles(compMeta)].concat(viewDirectives.map(
dirMeta => this._templateNormalizer.normalizeDirective(dirMeta))))
.then((stylesAndNormalizedViewDirMetas: any[]) => {
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
var styles = stylesAndNormalizedViewDirMetas[0];
var parsedTemplate =
this._templateParser.parse(compMeta, compMeta.template.template,
normalizedViewDirMetas, pipes, compMeta.type.name);
var childPromises = [];
compiledTemplate.init(this._compileComponent(compMeta, parsedTemplate, styles,
pipes, compilingComponentsPath,
childPromises));
return PromiseWrapper.all(childPromises).then((_) => { return compiledTemplate; });
});
this._compiledTemplateDone.set(cacheKey, done);
}
return compiledTemplate;
}
private _compileComponent(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[],
styles: string[], pipes: CompilePipeMetadata[],
compilingComponentsPath: any[],
childPromises: Promise<any>[]): Function {
var compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate,
new ir.ExternalExpr(new CompileIdentifierMetadata({runtime: styles})), pipes);
compileResult.dependencies.forEach((dep) => {
var childCompilingComponentsPath = ListWrapper.clone(compilingComponentsPath);
var childCacheKey = dep.comp.type.runtime;
var childViewDirectives: CompileDirectiveMetadata[] =
this._metadataResolver.getViewDirectivesMetadata(dep.comp.type.runtime);
var childViewPipes: CompilePipeMetadata[] =
this._metadataResolver.getViewPipesMetadata(dep.comp.type.runtime);
var childIsRecursive = ListWrapper.contains(childCompilingComponentsPath, childCacheKey);
childCompilingComponentsPath.push(childCacheKey);
var childComp =
this._loadAndCompileComponent(dep.comp.type.runtime, dep.comp, childViewDirectives,
childViewPipes, childCompilingComponentsPath);
dep.factoryPlaceholder.runtime = childComp.proxyViewFactory;
dep.factoryPlaceholder.name = `viewFactory_${dep.comp.type.name}`;
if (!childIsRecursive) {
// Only wait for a child if it is not a cycle
childPromises.push(this._compiledTemplateDone.get(childCacheKey));
}
});
var factory;
if (IS_DART || !this._genConfig.useJit) {
factory = interpretStatements(compileResult.statements, compileResult.viewFactoryVar,
new InterpretiveAppViewInstanceFactory());
} else {
factory = jitStatements(`${compMeta.type.name}.template.js`, compileResult.statements,
compileResult.viewFactoryVar);
}
return factory;
}
private _compileComponentStyles(compMeta: CompileDirectiveMetadata): Promise<string[]> {
var compileResult = this._styleCompiler.compileComponent(compMeta);
return this._resolveStylesCompileResult(compMeta.type.name, compileResult);
}
private _resolveStylesCompileResult(sourceUrl: string,
result: StylesCompileResult): Promise<string[]> {
var promises = result.dependencies.map((dep) => this._loadStylesheetDep(dep));
return PromiseWrapper.all(promises)
.then((cssTexts) => {
var nestedCompileResultPromises = [];
for (var i = 0; i < result.dependencies.length; i++) {
var dep = result.dependencies[i];
var cssText = cssTexts[i];
var nestedCompileResult =
this._styleCompiler.compileStylesheet(dep.sourceUrl, cssText, dep.isShimmed);
nestedCompileResultPromises.push(
this._resolveStylesCompileResult(dep.sourceUrl, nestedCompileResult));
}
return PromiseWrapper.all(nestedCompileResultPromises);
})
.then((nestedStylesArr) => {
for (var i = 0; i < result.dependencies.length; i++) {
var dep = result.dependencies[i];
dep.valuePlaceholder.runtime = nestedStylesArr[i];
dep.valuePlaceholder.name = `importedStyles${i}`;
}
if (IS_DART || !this._genConfig.useJit) {
return interpretStatements(result.statements, result.stylesVar,
new InterpretiveAppViewInstanceFactory());
} else {
return jitStatements(`${sourceUrl}.css.js`, result.statements, result.stylesVar);
}
});
}
private _loadStylesheetDep(dep: StylesCompileDependency): Promise<string> {
var cacheKey = `${dep.sourceUrl}${dep.isShimmed ? '.shim' : ''}`;
var cssTextPromise = this._styleCache.get(cacheKey);
if (isBlank(cssTextPromise)) {
cssTextPromise = this._xhr.get(dep.sourceUrl);
this._styleCache.set(cacheKey, cssTextPromise);
}
return cssTextPromise;
}
}
class CompiledTemplate {
viewFactory: Function = null;
proxyViewFactory: Function;
constructor() {
this.proxyViewFactory = (viewUtils, childInjector, contextEl) =>
this.viewFactory(viewUtils, childInjector, contextEl);
}
init(viewFactory: Function) { this.viewFactory = viewFactory; }
}
function assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`);
}
}

View File

@ -0,0 +1,44 @@
import {Injectable} from 'angular2/src/core/di';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {splitNsName} from 'angular2/src/compiler/html_tags';
import {ElementSchemaRegistry} from './element_schema_registry';
const NAMESPACE_URIS =
/*@ts2dart_const*/
{'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'};
@Injectable()
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
private _protoElements = new Map<string, Element>();
private _getProtoElement(tagName: string): Element {
var element = this._protoElements.get(tagName);
if (isBlank(element)) {
var nsAndName = splitNsName(tagName);
element = isPresent(nsAndName[0]) ?
DOM.createElementNS(NAMESPACE_URIS[nsAndName[0]], nsAndName[1]) :
DOM.createElement(nsAndName[1]);
this._protoElements.set(tagName, element);
}
return element;
}
hasProperty(tagName: string, propName: string): boolean {
if (tagName.indexOf('-') !== -1) {
// can't tell now as we don't know which properties a custom element will get
// once it is instantiated
return true;
} else {
var elm = this._getProtoElement(tagName);
return DOM.hasProperty(elm, propName);
}
}
getMappedPropName(propName: string): string {
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, propName);
return isPresent(mappedPropName) ? mappedPropName : propName;
}
}

View File

@ -0,0 +1,4 @@
export class ElementSchemaRegistry {
hasProperty(tagName: string, propName: string): boolean { return true; }
getMappedPropName(propName: string): string { return propName; }
}

View File

@ -0,0 +1,383 @@
import {Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {
isPresent,
isBlank,
RegExpWrapper,
RegExpMatcherWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
const _EMPTY_ATTR_VALUE = /*@ts2dart_const*/ '';
// TODO: Can't use `const` here as
// in Dart this is not transpiled into `final` yet...
var _SELECTOR_REGEXP = RegExpWrapper.create(
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)'); // ","
/**
* A css selector contains an element name,
* css classes and attribute/value pairs with the purpose
* of selecting subsets out of them.
*/
export class CssSelector {
element: string = null;
classNames: string[] = [];
attrs: string[] = [];
notSelectors: CssSelector[] = [];
static parse(selector: string): CssSelector[] {
var results: CssSelector[] = [];
var _addResult = (res: CssSelector[], cssSel) => {
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = "*";
}
res.push(cssSel);
};
var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
var current = cssSelector;
var inNot = false;
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
if (isPresent(match[1])) {
if (inNot) {
throw new BaseException('Nesting :not is not allowed in a selector');
}
inNot = true;
current = new CssSelector();
cssSelector.notSelectors.push(current);
}
if (isPresent(match[2])) {
current.setElement(match[2]);
}
if (isPresent(match[3])) {
current.addClassName(match[3]);
}
if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]);
}
if (isPresent(match[6])) {
inNot = false;
current = cssSelector;
}
if (isPresent(match[7])) {
if (inNot) {
throw new BaseException('Multiple selectors in :not are not supported');
}
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
}
_addResult(results, cssSelector);
return results;
}
isElementSelector(): boolean {
return isPresent(this.element) && ListWrapper.isEmpty(this.classNames) &&
ListWrapper.isEmpty(this.attrs) && this.notSelectors.length === 0;
}
setElement(element: string = null) { this.element = element; }
/** Gets a template string for an element that matches the selector. */
getMatchingElementTemplate(): string {
let tagName = isPresent(this.element) ? this.element : 'div';
let classAttr = this.classNames.length > 0 ? ` class="${this.classNames.join(' ')}"` : '';
let attrs = '';
for (let i = 0; i < this.attrs.length; i += 2) {
let attrName = this.attrs[i];
let attrValue = this.attrs[i + 1] !== '' ? `="${this.attrs[i + 1]}"` : '';
attrs += ` ${attrName}${attrValue}`;
}
return `<${tagName}${classAttr}${attrs}></${tagName}>`;
}
addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
this.attrs.push(name);
if (isPresent(value)) {
value = value.toLowerCase();
} else {
value = _EMPTY_ATTR_VALUE;
}
this.attrs.push(value);
}
addClassName(name: string) { this.classNames.push(name.toLowerCase()); }
toString(): string {
var res = '';
if (isPresent(this.element)) {
res += this.element;
}
if (isPresent(this.classNames)) {
for (var i = 0; i < this.classNames.length; i++) {
res += '.' + this.classNames[i];
}
}
if (isPresent(this.attrs)) {
for (var i = 0; i < this.attrs.length;) {
var attrName = this.attrs[i++];
var attrValue = this.attrs[i++];
res += '[' + attrName;
if (attrValue.length > 0) {
res += '=' + attrValue;
}
res += ']';
}
}
this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`);
return res;
}
}
/**
* Reads a list of CssSelectors and allows to calculate which ones
* are contained in a given CssSelector.
*/
export class SelectorMatcher {
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
var notMatcher = new SelectorMatcher();
notMatcher.addSelectables(notSelectors, null);
return notMatcher;
}
private _elementMap = new Map<string, SelectorContext[]>();
private _elementPartialMap = new Map<string, SelectorMatcher>();
private _classMap = new Map<string, SelectorContext[]>();
private _classPartialMap = new Map<string, SelectorMatcher>();
private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>();
private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>();
private _listContexts: SelectorListContext[] = [];
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
var listContext = null;
if (cssSelectors.length > 1) {
listContext = new SelectorListContext(cssSelectors);
this._listContexts.push(listContext);
}
for (var i = 0; i < cssSelectors.length; i++) {
this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
}
}
/**
* Add an object that can be found later on by calling `match`.
* @param cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
*/
private _addSelectable(cssSelector: CssSelector, callbackCtxt: any,
listContext: SelectorListContext) {
var matcher: SelectorMatcher = this;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (isPresent(element)) {
var isTerminal = attrs.length === 0 && classNames.length === 0;
if (isTerminal) {
this._addTerminal(matcher._elementMap, element, selectable);
} else {
matcher = this._addPartial(matcher._elementPartialMap, element);
}
}
if (isPresent(classNames)) {
for (var index = 0; index < classNames.length; index++) {
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
var className = classNames[index];
if (isTerminal) {
this._addTerminal(matcher._classMap, className, selectable);
} else {
matcher = this._addPartial(matcher._classPartialMap, className);
}
}
}
if (isPresent(attrs)) {
for (var index = 0; index < attrs.length;) {
var isTerminal = index === attrs.length - 2;
var attrName = attrs[index++];
var attrValue = attrs[index++];
if (isTerminal) {
var terminalMap = matcher._attrValueMap;
var terminalValuesMap = terminalMap.get(attrName);
if (isBlank(terminalValuesMap)) {
terminalValuesMap = new Map<string, SelectorContext[]>();
terminalMap.set(attrName, terminalValuesMap);
}
this._addTerminal(terminalValuesMap, attrValue, selectable);
} else {
var parttialMap = matcher._attrValuePartialMap;
var partialValuesMap = parttialMap.get(attrName);
if (isBlank(partialValuesMap)) {
partialValuesMap = new Map<string, SelectorMatcher>();
parttialMap.set(attrName, partialValuesMap);
}
matcher = this._addPartial(partialValuesMap, attrValue);
}
}
}
}
private _addTerminal(map: Map<string, SelectorContext[]>, name: string,
selectable: SelectorContext) {
var terminalList = map.get(name);
if (isBlank(terminalList)) {
terminalList = [];
map.set(name, terminalList);
}
terminalList.push(selectable);
}
private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
var matcher = map.get(name);
if (isBlank(matcher)) {
matcher = new SelectorMatcher();
map.set(name, matcher);
}
return matcher;
}
/**
* Find the objects that have been added via `addSelectable`
* whose css selector is contained in the given css selector.
* @param cssSelector A css selector
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
* @return boolean true if a match was found
*/
match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean {
var result = false;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
for (var i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false;
}
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
result;
if (isPresent(classNames)) {
for (var index = 0; index < classNames.length; index++) {
var className = classNames[index];
result =
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
result =
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) ||
result;
}
}
if (isPresent(attrs)) {
for (var index = 0; index < attrs.length;) {
var attrName = attrs[index++];
var attrValue = attrs[index++];
var terminalValuesMap = this._attrValueMap.get(attrName);
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
result = this._matchTerminal(terminalValuesMap, _EMPTY_ATTR_VALUE, cssSelector,
matchedCallback) ||
result;
}
result = this._matchTerminal(terminalValuesMap, attrValue, cssSelector, matchedCallback) ||
result;
var partialValuesMap = this._attrValuePartialMap.get(attrName);
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
result = this._matchPartial(partialValuesMap, _EMPTY_ATTR_VALUE, cssSelector,
matchedCallback) ||
result;
}
result =
this._matchPartial(partialValuesMap, attrValue, cssSelector, matchedCallback) || result;
}
}
return result;
}
/** @internal */
_matchTerminal(map: Map<string, SelectorContext[]>, name, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (isBlank(map) || isBlank(name)) {
return false;
}
var selectables = map.get(name);
var starSelectables = map.get("*");
if (isPresent(starSelectables)) {
selectables = selectables.concat(starSelectables);
}
if (isBlank(selectables)) {
return false;
}
var selectable;
var result = false;
for (var index = 0; index < selectables.length; index++) {
selectable = selectables[index];
result = selectable.finalize(cssSelector, matchedCallback) || result;
}
return result;
}
/** @internal */
_matchPartial(map: Map<string, SelectorMatcher>, name, cssSelector: CssSelector,
matchedCallback /*: (c: CssSelector, a: any) => void*/): boolean {
if (isBlank(map) || isBlank(name)) {
return false;
}
var nestedSelector = map.get(name);
if (isBlank(nestedSelector)) {
return false;
}
// TODO(perf): get rid of recursion and measure again
// TODO(perf): don't pass the whole selector into the recursion,
// but only the not processed parts
return nestedSelector.match(cssSelector, matchedCallback);
}
}
export class SelectorListContext {
alreadyMatched: boolean = false;
constructor(public selectors: CssSelector[]) {}
}
// Store context to pass back selector and context when a selector is matched
export class SelectorContext {
notSelectors: CssSelector[];
constructor(public selector: CssSelector, public cbContext: any,
public listContext: SelectorListContext) {
this.notSelectors = selector.notSelectors;
}
finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean {
var result = true;
if (this.notSelectors.length > 0 &&
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
result = !notMatcher.match(cssSelector, null);
}
if (result && isPresent(callback) &&
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
if (isPresent(this.listContext)) {
this.listContext.alreadyMatched = true;
}
callback(this.selector, this.cbContext);
}
return result;
}
}

View File

@ -0,0 +1,522 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {
StringWrapper,
RegExp,
RegExpWrapper,
RegExpMatcherWrapper,
isPresent,
isBlank
} from 'angular2/src/facade/lang';
/**
* This file is a port of shadowCSS from webcomponents.js to TypeScript.
*
* Please make sure to keep to edits in sync with the source file.
*
* Source:
* https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
*
* The original file level comment is reproduced below
*/
/*
This is a limited shim for ShadowDOM css styling.
https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
The intention here is to support only the styling features which can be
relatively simply implemented. The goal is to allow users to avoid the
most obvious pitfalls and do so without compromising performance significantly.
For ShadowDOM styling that's not covered here, a set of best practices
can be provided that should allow users to accomplish more complex styling.
The following is a list of specific ShadowDOM styling features and a brief
discussion of the approach used to shim.
Shimmed features:
* :host, :host-context: ShadowDOM allows styling of the shadowRoot's host
element using the :host rule. To shim this feature, the :host styles are
reformatted and prefixed with a given scope name and promoted to a
document level stylesheet.
For example, given a scope name of .foo, a rule like this:
:host {
background: red;
}
}
becomes:
.foo {
background: red;
}
* encapsultion: Styles defined within ShadowDOM, apply only to
dom inside the ShadowDOM. Polymer uses one of two techniques to implement
this feature.
By default, rules are prefixed with the host element tag name
as a descendant selector. This ensures styling does not leak out of the 'top'
of the element's ShadowDOM. For example,
div {
font-weight: bold;
}
becomes:
x-foo div {
font-weight: bold;
}
becomes:
Alternatively, if WebComponents.ShadowCSS.strictStyling is set to true then
selectors are scoped by adding an attribute selector suffix to each
simple selector that contains the host element tag name. Each element
in the element's ShadowDOM template is also given the scope attribute.
Thus, these rules match only elements that have the scope attribute.
For example, given a scope name of x-foo, a rule like this:
div {
font-weight: bold;
}
becomes:
div[x-foo] {
font-weight: bold;
}
Note that elements that are dynamically added to a scope must have the scope
selector added to them manually.
* upper/lower bound encapsulation: Styles which are defined outside a
shadowRoot should not cross the ShadowDOM boundary and should not apply
inside a shadowRoot.
This styling behavior is not emulated. Some possible ways to do this that
were rejected due to complexity and/or performance concerns include: (1) reset
every possible property for every possible selector for a given scope name;
(2) re-implement css in javascript.
As an alternative, users should make sure to use selectors
specific to the scope in which they are working.
* ::distributed: This behavior is not emulated. It's often not necessary
to style the contents of a specific insertion point and instead, descendants
of the host element can be styled selectively. Users can also create an
extra node around an insertion point and style that node's contents
via descendent selectors. For example, with a shadowRoot like this:
<style>
::content(div) {
background: red;
}
</style>
<content></content>
could become:
<style>
/ *@polyfill .content-container div * /
::content(div) {
background: red;
}
</style>
<div class="content-container">
<content></content>
</div>
Note the use of @polyfill in the comment above a ShadowDOM specific style
declaration. This is a directive to the styling shim to use the selector
in comments in lieu of the next selector when running under polyfill.
*/
export class ShadowCss {
strictStyling: boolean = true;
constructor() {}
/*
* Shim some cssText with the given selector. Returns cssText that can
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
*
* When strictStyling is true:
* - selector is the attribute added to all elements inside the host,
* - hostSelector is the attribute added to the host itself.
*/
shimCssText(cssText: string, selector: string, hostSelector: string = ''): string {
cssText = stripComments(cssText);
cssText = this._insertDirectives(cssText);
return this._scopeCssText(cssText, selector, hostSelector);
}
private _insertDirectives(cssText: string): string {
cssText = this._insertPolyfillDirectivesInCssText(cssText);
return this._insertPolyfillRulesInCssText(cssText);
}
/*
* Process styles to convert native ShadowDOM rules that will trip
* up the css parser; we rely on decorating the stylesheet with inert rules.
*
* For example, we convert this rule:
*
* polyfill-next-selector { content: ':host menu-item'; }
* ::content menu-item {
*
* to this:
*
* scopeName menu-item {
*
**/
private _insertPolyfillDirectivesInCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments
return StringWrapper.replaceAllMapped(cssText, _cssContentNextSelectorRe,
function(m) { return m[1] + '{'; });
}
/*
* Process styles to add rules which will only apply under the polyfill
*
* For example, we convert this rule:
*
* polyfill-rule {
* content: ':host menu-item';
* ...
* }
*
* to this:
*
* scopeName menu-item {...}
*
**/
private _insertPolyfillRulesInCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments
return StringWrapper.replaceAllMapped(cssText, _cssContentRuleRe, function(m) {
var rule = m[0];
rule = StringWrapper.replace(rule, m[1], '');
rule = StringWrapper.replace(rule, m[2], '');
return m[3] + rule;
});
}
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
*
* .foo {... }
*
* and converts this to
*
* scopeName .foo { ... }
*/
private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
var unscoped = this._extractUnscopedRulesFromCssText(cssText);
cssText = this._insertPolyfillHostInCssText(cssText);
cssText = this._convertColonHost(cssText);
cssText = this._convertColonHostContext(cssText);
cssText = this._convertShadowDOMSelectors(cssText);
if (isPresent(scopeSelector)) {
cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector);
}
cssText = cssText + '\n' + unscoped;
return cssText.trim();
}
/*
* Process styles to add rules which will only apply under the polyfill
* and do not process via CSSOM. (CSSOM is destructive to rules on rare
* occasions, e.g. -webkit-calc on Safari.)
* For example, we convert this rule:
*
* @polyfill-unscoped-rule {
* content: 'menu-item';
* ... }
*
* to this:
*
* menu-item {...}
*
**/
private _extractUnscopedRulesFromCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments
var r = '', m;
var matcher = RegExpWrapper.matcher(_cssContentUnscopedRuleRe, cssText);
while (isPresent(m = RegExpMatcherWrapper.next(matcher))) {
var rule = m[0];
rule = StringWrapper.replace(rule, m[2], '');
rule = StringWrapper.replace(rule, m[1], m[3]);
r += rule + '\n\n';
}
return r;
}
/*
* convert a rule like :host(.foo) > .bar { }
*
* to
*
* scopeName.foo > .bar
*/
private _convertColonHost(cssText: string): string {
return this._convertColonRule(cssText, _cssColonHostRe, this._colonHostPartReplacer);
}
/*
* convert a rule like :host-context(.foo) > .bar { }
*
* to
*
* scopeName.foo > .bar, .foo scopeName > .bar { }
*
* and
*
* :host-context(.foo:host) .bar { ... }
*
* to
*
* scopeName.foo .bar { ... }
*/
private _convertColonHostContext(cssText: string): string {
return this._convertColonRule(cssText, _cssColonHostContextRe,
this._colonHostContextPartReplacer);
}
private _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string {
// p1 = :host, p2 = contents of (), p3 rest of rule
return StringWrapper.replaceAllMapped(cssText, regExp, function(m) {
if (isPresent(m[2])) {
var parts = m[2].split(','), r = [];
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
if (isBlank(p)) break;
p = p.trim();
r.push(partReplacer(_polyfillHostNoCombinator, p, m[3]));
}
return r.join(',');
} else {
return _polyfillHostNoCombinator + m[3];
}
});
}
private _colonHostContextPartReplacer(host: string, part: string, suffix: string): string {
if (StringWrapper.contains(part, _polyfillHost)) {
return this._colonHostPartReplacer(host, part, suffix);
} else {
return host + part + suffix + ', ' + part + ' ' + host + suffix;
}
}
private _colonHostPartReplacer(host: string, part: string, suffix: string): string {
return host + StringWrapper.replace(part, _polyfillHost, '') + suffix;
}
/*
* Convert combinators like ::shadow and pseudo-elements like ::content
* by replacing with space.
*/
private _convertShadowDOMSelectors(cssText: string): string {
for (var i = 0; i < _shadowDOMSelectorsRe.length; i++) {
cssText = StringWrapper.replaceAll(cssText, _shadowDOMSelectorsRe[i], ' ');
}
return cssText;
}
// change a selector like 'div' to 'name div'
private _scopeSelectors(cssText: string, scopeSelector: string, hostSelector: string): string {
return processRules(cssText, (rule: CssRule) => {
var selector = rule.selector;
var content = rule.content;
if (rule.selector[0] != '@' || rule.selector.startsWith('@page')) {
selector =
this._scopeSelector(rule.selector, scopeSelector, hostSelector, this.strictStyling);
} else if (rule.selector.startsWith('@media')) {
content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
}
return new CssRule(selector, content);
});
}
private _scopeSelector(selector: string, scopeSelector: string, hostSelector: string,
strict: boolean): string {
var r = [], parts = selector.split(',');
for (var i = 0; i < parts.length; i++) {
var p = parts[i].trim();
var deepParts = StringWrapper.split(p, _shadowDeepSelectors);
var shallowPart = deepParts[0];
if (this._selectorNeedsScoping(shallowPart, scopeSelector)) {
deepParts[0] = strict && !StringWrapper.contains(shallowPart, _polyfillHostNoCombinator) ?
this._applyStrictSelectorScope(shallowPart, scopeSelector) :
this._applySelectorScope(shallowPart, scopeSelector, hostSelector);
}
// replace /deep/ with a space for child selectors
r.push(deepParts.join(' '));
}
return r.join(', ');
}
private _selectorNeedsScoping(selector: string, scopeSelector: string): boolean {
var re = this._makeScopeMatcher(scopeSelector);
return !isPresent(RegExpWrapper.firstMatch(re, selector));
}
private _makeScopeMatcher(scopeSelector: string): RegExp {
var lre = /\[/g;
var rre = /\]/g;
scopeSelector = StringWrapper.replaceAll(scopeSelector, lre, '\\[');
scopeSelector = StringWrapper.replaceAll(scopeSelector, rre, '\\]');
return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
}
private _applySelectorScope(selector: string, scopeSelector: string,
hostSelector: string): string {
// Difference from webcomponentsjs: scopeSelector could not be an array
return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector);
}
// scope via name and [is=name]
private _applySimpleSelectorScope(selector: string, scopeSelector: string,
hostSelector: string): string {
if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) {
var replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, replaceBy);
return StringWrapper.replaceAll(selector, _polyfillHostRe, replaceBy + ' ');
} else {
return scopeSelector + ' ' + selector;
}
}
// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
private _applyStrictSelectorScope(selector: string, scopeSelector: string): string {
var isRe = /\[is=([^\]]*)\]/g;
scopeSelector = StringWrapper.replaceAllMapped(scopeSelector, isRe, (m) => m[1]);
var splits = [' ', '>', '+', '~'], scoped = selector, attrName = '[' + scopeSelector + ']';
for (var i = 0; i < splits.length; i++) {
var sep = splits[i];
var parts = scoped.split(sep);
scoped = parts.map(p => {
// remove :host since it should be unnecessary
var t = StringWrapper.replaceAll(p.trim(), _polyfillHostRe, '');
if (t.length > 0 && !ListWrapper.contains(splits, t) &&
!StringWrapper.contains(t, attrName)) {
var re = /([^:]*)(:*)(.*)/g;
var m = RegExpWrapper.firstMatch(re, t);
if (isPresent(m)) {
p = m[1] + attrName + m[2] + m[3];
}
}
return p;
})
.join(sep);
}
return scoped;
}
private _insertPolyfillHostInCssText(selector: string): string {
selector = StringWrapper.replaceAll(selector, _colonHostContextRe, _polyfillHostContext);
selector = StringWrapper.replaceAll(selector, _colonHostRe, _polyfillHost);
return selector;
}
}
var _cssContentNextSelectorRe =
/polyfill-next-selector[^}]*content:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim;
var _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim;
var _cssContentUnscopedRuleRe =
/(polyfill-unscoped-rule)[^}]*(content:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim;
var _polyfillHost = '-shadowcsshost';
// note: :host-context pre-processed to -shadowcsshostcontext.
var _polyfillHostContext = '-shadowcsscontext';
var _parenSuffix = ')(?:\\((' +
'(?:\\([^)(]*\\)|[^)(]*)+?' +
')\\))?([^,{]*)';
var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, 'im');
var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
var _shadowDOMSelectorsRe = [
/::shadow/g,
/::content/g,
// Deprecated selectors
// TODO(vicb): see https://github.com/angular/clang-format/issues/16
// clang-format off
/\/shadow-deep\//g, // former /deep/
/\/shadow\//g, // former ::shadow
// clanf-format on
];
var _shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)/g;
var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
var _colonHostRe = /:host/gim;
var _colonHostContextRe = /:host-context/gim;
var _commentRe = /\/\*[\s\S]*?\*\//g;
function stripComments(input:string):string {
return StringWrapper.replaceAllMapped(input, _commentRe, (_) => '');
}
var _ruleRe = /(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g;
var _curlyRe = /([{}])/g;
const OPEN_CURLY = '{';
const CLOSE_CURLY = '}';
const BLOCK_PLACEHOLDER = '%BLOCK%';
export class CssRule {
constructor(public selector:string, public content:string) {}
}
export function processRules(input:string, ruleCallback:Function):string {
var inputWithEscapedBlocks = escapeBlocks(input);
var nextBlockIndex = 0;
return StringWrapper.replaceAllMapped(inputWithEscapedBlocks.escapedString, _ruleRe, function(m) {
var selector = m[2];
var content = '';
var suffix = m[4];
var contentPrefix = '';
if (isPresent(m[4]) && m[4].startsWith('{'+BLOCK_PLACEHOLDER)) {
content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
suffix = m[4].substring(BLOCK_PLACEHOLDER.length+1);
contentPrefix = '{';
}
var rule = ruleCallback(new CssRule(selector, content));
return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`;
});
}
class StringWithEscapedBlocks {
constructor(public escapedString:string, public blocks:string[]) {}
}
function escapeBlocks(input:string):StringWithEscapedBlocks {
var inputParts = StringWrapper.split(input, _curlyRe);
var resultParts = [];
var escapedBlocks = [];
var bracketCount = 0;
var currentBlockParts = [];
for (var partIndex = 0; partIndex<inputParts.length; partIndex++) {
var part = inputParts[partIndex];
if (part == CLOSE_CURLY) {
bracketCount--;
}
if (bracketCount > 0) {
currentBlockParts.push(part);
} else {
if (currentBlockParts.length > 0) {
escapedBlocks.push(currentBlockParts.join(''));
resultParts.push(BLOCK_PLACEHOLDER);
currentBlockParts = [];
}
resultParts.push(part);
}
if (part == OPEN_CURLY) {
bracketCount++;
}
}
if (currentBlockParts.length > 0) {
escapedBlocks.push(currentBlockParts.join(''));
resultParts.push(BLOCK_PLACEHOLDER);
}
return new StringWithEscapedBlocks(resultParts.join(''), escapedBlocks);
}

View File

@ -0,0 +1,77 @@
import {
CompileTemplateMetadata,
CompileIdentifierMetadata,
CompileDirectiveMetadata
} from './compile_metadata';
import * as o from './output/output_ast';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {ShadowCss} from 'angular2/src/compiler/shadow_css';
import {UrlResolver} from 'angular2/src/compiler/url_resolver';
import {extractStyleUrls} from './style_url_resolver';
import {Injectable} from 'angular2/src/core/di';
import {isPresent} from 'angular2/src/facade/lang';
const COMPONENT_VARIABLE = '%COMP%';
const HOST_ATTR = /*@ts2dart_const*/ `_nghost-${COMPONENT_VARIABLE}`;
const CONTENT_ATTR = /*@ts2dart_const*/ `_ngcontent-${COMPONENT_VARIABLE}`;
export class StylesCompileDependency {
constructor(public sourceUrl: string, public isShimmed: boolean,
public valuePlaceholder: CompileIdentifierMetadata) {}
}
export class StylesCompileResult {
constructor(public statements: o.Statement[], public stylesVar: string,
public dependencies: StylesCompileDependency[]) {}
}
@Injectable()
export class StyleCompiler {
private _shadowCss: ShadowCss = new ShadowCss();
constructor(private _urlResolver: UrlResolver) {}
compileComponent(comp: CompileDirectiveMetadata): StylesCompileResult {
var shim = comp.template.encapsulation === ViewEncapsulation.Emulated;
return this._compileStyles(getStylesVarName(comp), comp.template.styles,
comp.template.styleUrls, shim);
}
compileStylesheet(stylesheetUrl: string, cssText: string,
isShimmed: boolean): StylesCompileResult {
var styleWithImports = extractStyleUrls(this._urlResolver, stylesheetUrl, cssText);
return this._compileStyles(getStylesVarName(null), [styleWithImports.style],
styleWithImports.styleUrls, isShimmed);
}
private _compileStyles(stylesVar: string, plainStyles: string[], absUrls: string[],
shim: boolean): StylesCompileResult {
var styleExpressions =
plainStyles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim)));
var dependencies = [];
for (var i = 0; i < absUrls.length; i++) {
var identifier = new CompileIdentifierMetadata({name: getStylesVarName(null)});
dependencies.push(new StylesCompileDependency(absUrls[i], shim, identifier));
styleExpressions.push(new o.ExternalExpr(identifier));
}
// styles variable contains plain strings and arrays of other styles arrays (recursive),
// so we set its type to dynamic.
var stmt = o.variable(stylesVar)
.set(o.literalArr(styleExpressions,
new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]);
return new StylesCompileResult([stmt], stylesVar, dependencies);
}
private _shimIfNeeded(style: string, shim: boolean): string {
return shim ? this._shadowCss.shimCssText(style, CONTENT_ATTR, HOST_ATTR) : style;
}
}
function getStylesVarName(component: CompileDirectiveMetadata): string {
var result = `styles`;
if (isPresent(component)) {
result += `_${component.type.name}`;
}
return result;
}

View File

@ -0,0 +1,39 @@
// Some of the code comes from WebComponents.JS
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
import {RegExp, RegExpWrapper, StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
import {UrlResolver} from 'angular2/src/compiler/url_resolver';
export class StyleWithImports {
constructor(public style: string, public styleUrls: string[]) {}
}
export function isStyleUrlResolvable(url: string): boolean {
if (isBlank(url) || url.length === 0 || url[0] == '/') return false;
var schemeMatch = RegExpWrapper.firstMatch(_urlWithSchemaRe, url);
return isBlank(schemeMatch) || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
}
/**
* Rewrites stylesheets by resolving and removing the @import urls that
* are either relative or don't have a `package:` scheme
*/
export function extractStyleUrls(resolver: UrlResolver, baseUrl: string,
cssText: string): StyleWithImports {
var foundUrls = [];
var modifiedCssText = StringWrapper.replaceAllMapped(cssText, _cssImportRe, (m) => {
var url = isPresent(m[1]) ? m[1] : m[2];
if (!isStyleUrlResolvable(url)) {
// Do not attempt to resolve non-package absolute URLs with URI scheme
return m[0];
}
foundUrls.push(resolver.resolve(baseUrl, url));
return '';
});
return new StyleWithImports(modifiedCssText, foundUrls);
}
var _cssImportRe = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
// TODO: can't use /^[^:/?#.]+:/g due to clang-format bug:
// https://github.com/angular/angular/issues/4596
var _urlWithSchemaRe = /^([a-zA-Z\-\+\.]+):/g;

View File

@ -0,0 +1,250 @@
import {AST} from './expression_parser/ast';
import {isPresent} from 'angular2/src/facade/lang';
import {
CompileDirectiveMetadata,
CompileTokenMetadata,
CompileProviderMetadata,
CompileTokenMap,
CompileQueryMetadata
} from './compile_metadata';
import {ParseSourceSpan} from './parse_util';
/**
* An Abstract Syntax Tree node representing part of a parsed Angular template.
*/
export interface TemplateAst {
/**
* The source span from which this node was parsed.
*/
sourceSpan: ParseSourceSpan;
/**
* Visit this node and possibly transform it.
*/
visit(visitor: TemplateAstVisitor, context: any): any;
}
/**
* A segment of text within the template.
*/
export class TextAst implements TemplateAst {
constructor(public value: string, public ngContentIndex: number,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitText(this, context); }
}
/**
* A bound expression within the text of a template.
*/
export class BoundTextAst implements TemplateAst {
constructor(public value: AST, public ngContentIndex: number,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitBoundText(this, context);
}
}
/**
* A plain attribute on an element.
*/
export class AttrAst implements TemplateAst {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
}
/**
* A binding for an element property (e.g. `[property]="expression"`).
*/
export class BoundElementPropertyAst implements TemplateAst {
constructor(public name: string, public type: PropertyBindingType, public value: AST,
public unit: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElementProperty(this, context);
}
}
/**
* A binding for an element event (e.g. `(event)="handler()"`).
*/
export class BoundEventAst implements TemplateAst {
constructor(public name: string, public target: string, public handler: AST,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context);
}
get fullName() {
if (isPresent(this.target)) {
return `${this.target}:${this.name}`;
} else {
return this.name;
}
}
}
/**
* A reference declaration on an element (e.g. `let someName="expression"`).
*/
export class ReferenceAst implements TemplateAst {
constructor(public name: string, public value: CompileTokenMetadata,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitReference(this, context);
}
}
/**
* A variable declaration on a <template> (e.g. `var-someName="someLocalName"`).
*/
export class VariableAst implements TemplateAst {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitVariable(this, context);
}
}
/**
* An element declaration in a template.
*/
export class ElementAst implements TemplateAst {
constructor(public name: string, public attrs: AttrAst[],
public inputs: BoundElementPropertyAst[], public outputs: BoundEventAst[],
public references: ReferenceAst[], public directives: DirectiveAst[],
public providers: ProviderAst[], public hasViewContainer: boolean,
public children: TemplateAst[], public ngContentIndex: number,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElement(this, context);
}
}
/**
* A `<template>` element included in an Angular template.
*/
export class EmbeddedTemplateAst implements TemplateAst {
constructor(public attrs: AttrAst[], public outputs: BoundEventAst[],
public references: ReferenceAst[], public variables: VariableAst[],
public directives: DirectiveAst[], public providers: ProviderAst[],
public hasViewContainer: boolean, public children: TemplateAst[],
public ngContentIndex: number, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEmbeddedTemplate(this, context);
}
}
/**
* A directive property with a bound value (e.g. `*ngIf="condition").
*/
export class BoundDirectivePropertyAst implements TemplateAst {
constructor(public directiveName: string, public templateName: string, public value: AST,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirectiveProperty(this, context);
}
}
/**
* A directive declared on an element.
*/
export class DirectiveAst implements TemplateAst {
constructor(public directive: CompileDirectiveMetadata,
public inputs: BoundDirectivePropertyAst[],
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirective(this, context);
}
}
/**
* A provider declared on an element
*/
export class ProviderAst implements TemplateAst {
constructor(public token: CompileTokenMetadata, public multiProvider: boolean,
public eager: boolean, public providers: CompileProviderMetadata[],
public providerType: ProviderAstType, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
// No visit method in the visitor for now...
return null;
}
}
export enum ProviderAstType {
PublicService,
PrivateService,
Component,
Directive,
Builtin
}
/**
* Position where content is to be projected (instance of `<ng-content>` in a template).
*/
export class NgContentAst implements TemplateAst {
constructor(public index: number, public ngContentIndex: number,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitNgContent(this, context);
}
}
/**
* Enumeration of types of property bindings.
*/
export enum PropertyBindingType {
/**
* A normal binding to a property (e.g. `[property]="expression"`).
*/
Property,
/**
* A binding to an element attribute (e.g. `[attr.name]="expression"`).
*/
Attribute,
/**
* A binding to a CSS class (e.g. `[class.name]="condition"`).
*/
Class,
/**
* A binding to a style rule (e.g. `[style.rule]="expression"`).
*/
Style
}
/**
* A visitor for {@link TemplateAst} trees that will process each node.
*/
export interface TemplateAstVisitor {
visitNgContent(ast: NgContentAst, context: any): any;
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any;
visitElement(ast: ElementAst, context: any): any;
visitReference(ast: ReferenceAst, context: any): any;
visitVariable(ast: VariableAst, context: any): any;
visitEvent(ast: BoundEventAst, context: any): any;
visitElementProperty(ast: BoundElementPropertyAst, context: any): any;
visitAttr(ast: AttrAst, context: any): any;
visitBoundText(ast: BoundTextAst, context: any): any;
visitText(ast: TextAst, context: any): any;
visitDirective(ast: DirectiveAst, context: any): any;
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any;
}
/**
* Visit every node in a list of {@link TemplateAst}s with the given {@link TemplateAstVisitor}.
*/
export function templateVisitAll(visitor: TemplateAstVisitor, asts: TemplateAst[],
context: any = null): any[] {
var result = [];
asts.forEach(ast => {
var astResult = ast.visit(visitor, context);
if (isPresent(astResult)) {
result.push(astResult);
}
});
return result;
}

View File

@ -0,0 +1,926 @@
import {
ListWrapper,
StringMapWrapper,
SetWrapper,
MapWrapper
} from 'angular2/src/facade/collection';
import {RegExpWrapper, isPresent, StringWrapper, isBlank, isArray} from 'angular2/src/facade/lang';
import {Injectable, Inject, OpaqueToken, Optional} from 'angular2/core';
import {Console} from 'angular2/src/core/console';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
AST,
Interpolation,
ASTWithSource,
TemplateBinding,
RecursiveAstVisitor,
BindingPipe
} from './expression_parser/ast';
import {Parser} from './expression_parser/parser';
import {
CompileTokenMap,
CompileDirectiveMetadata,
CompilePipeMetadata,
CompileMetadataWithType,
CompileProviderMetadata,
CompileTokenMetadata,
CompileTypeMetadata
} from './compile_metadata';
import {HtmlParser} from './html_parser';
import {splitNsName, mergeNsAndName} from './html_tags';
import {ParseSourceSpan, ParseError, ParseLocation, ParseErrorLevel} from './parse_util';
import {MAX_INTERPOLATION_VALUES} from 'angular2/src/core/linker/view_utils';
import {
ElementAst,
BoundElementPropertyAst,
BoundEventAst,
ReferenceAst,
TemplateAst,
TemplateAstVisitor,
templateVisitAll,
TextAst,
BoundTextAst,
EmbeddedTemplateAst,
AttrAst,
NgContentAst,
PropertyBindingType,
DirectiveAst,
BoundDirectivePropertyAst,
ProviderAst,
ProviderAstType,
VariableAst
} from './template_ast';
import {CssSelector, SelectorMatcher} from 'angular2/src/compiler/selector';
import {ElementSchemaRegistry} from 'angular2/src/compiler/schema/element_schema_registry';
import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
import {isStyleUrlResolvable} from './style_url_resolver';
import {
HtmlAstVisitor,
HtmlAst,
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst,
htmlVisitAll
} from './html_ast';
import {splitAtColon} from './util';
import {identifierToken, Identifiers} from './identifiers';
import {ProviderElementContext, ProviderViewContext} from './provider_parser';
// Group 1 = "bind-"
// Group 2 = "var-"
// Group 3 = "let-"
// Group 4 = "ref-/#"
// Group 5 = "on-"
// Group 6 = "bindon-"
// Group 7 = the identifier after "bind-", "var-/#", or "on-"
// Group 8 = identifier inside [()]
// Group 9 = identifier inside []
// Group 10 = identifier inside ()
var BIND_NAME_REGEXP =
/^(?:(?:(?:(bind-)|(var-)|(let-)|(ref-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
const TEMPLATE_ELEMENT = 'template';
const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class';
var PROPERTY_PARTS_SEPARATOR = '.';
const ATTRIBUTE_PREFIX = 'attr';
const CLASS_PREFIX = 'class';
const STYLE_PREFIX = 'style';
var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
/**
* Provides an array of {@link TemplateAstVisitor}s which will be used to transform
* parsed templates before compilation is invoked, allowing custom expression syntax
* and other advanced transformations.
*
* This is currently an internal-only feature and not meant for general use.
*/
export const TEMPLATE_TRANSFORMS: any = /*@ts2dart_const*/ new OpaqueToken('TemplateTransforms');
export class TemplateParseError extends ParseError {
constructor(message: string, span: ParseSourceSpan, level: ParseErrorLevel) {
super(span, message, level);
}
}
export class TemplateParseResult {
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {}
}
@Injectable()
export class TemplateParser {
constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
private _htmlParser: HtmlParser, private _console: Console,
@Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {}
parse(component: CompileDirectiveMetadata, template: string,
directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
templateUrl: string): TemplateAst[] {
var result = this.tryParse(component, template, directives, pipes, templateUrl);
var warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
var errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
if (warnings.length > 0) {
this._console.warn(`Template parse warnings:\n${warnings.join('\n')}`);
}
if (errors.length > 0) {
var errorString = errors.join('\n');
throw new BaseException(`Template parse errors:\n${errorString}`);
}
return result.templateAst;
}
tryParse(component: CompileDirectiveMetadata, template: string,
directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
templateUrl: string): TemplateParseResult {
var htmlAstWithErrors = this._htmlParser.parse(template, templateUrl);
var errors: ParseError[] = htmlAstWithErrors.errors;
var result;
if (htmlAstWithErrors.rootNodes.length > 0) {
var uniqDirectives = <CompileDirectiveMetadata[]>removeDuplicates(directives);
var uniqPipes = <CompilePipeMetadata[]>removeDuplicates(pipes);
var providerViewContext =
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
var parseVisitor = new TemplateParseVisitor(providerViewContext, uniqDirectives, uniqPipes,
this._exprParser, this._schemaRegistry);
result = htmlVisitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors = errors.concat(parseVisitor.errors).concat(providerViewContext.errors);
} else {
result = [];
}
if (errors.length > 0) {
return new TemplateParseResult(result, errors);
}
if (isPresent(this.transforms)) {
this.transforms.forEach(
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
}
return new TemplateParseResult(result, errors);
}
}
class TemplateParseVisitor implements HtmlAstVisitor {
selectorMatcher: SelectorMatcher;
errors: TemplateParseError[] = [];
directivesIndex = new Map<CompileDirectiveMetadata, number>();
ngContentCount: number = 0;
pipesByName: Map<string, CompilePipeMetadata>;
constructor(public providerViewContext: ProviderViewContext,
directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) {
this.selectorMatcher = new SelectorMatcher();
ListWrapper.forEachWithIndex(directives,
(directive: CompileDirectiveMetadata, index: number) => {
var selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index);
});
this.pipesByName = new Map<string, CompilePipeMetadata>();
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
private _reportError(message: string, sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
this.errors.push(new TemplateParseError(message, sourceSpan, level));
}
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString();
try {
var ast = this._exprParser.parseInterpolation(value, sourceInfo);
this._checkPipes(ast, sourceSpan);
if (isPresent(ast) &&
(<Interpolation>ast.ast).expressions.length > MAX_INTERPOLATION_VALUES) {
throw new BaseException(
`Only support at most ${MAX_INTERPOLATION_VALUES} interpolation values!`);
}
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString();
try {
var ast = this._exprParser.parseAction(value, sourceInfo);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
var sourceInfo = sourceSpan.start.toString();
try {
var ast = this._exprParser.parseBinding(value, sourceInfo);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
var sourceInfo = sourceSpan.start.toString();
try {
var bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo);
bindingsResult.templateBindings.forEach((binding) => {
if (isPresent(binding.expression)) {
this._checkPipes(binding.expression, sourceSpan);
}
});
bindingsResult.warnings.forEach(
(warning) => { this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING); });
return bindingsResult.templateBindings;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return [];
}
}
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (isPresent(ast)) {
var collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((pipeName) => {
if (!this.pipesByName.has(pipeName)) {
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
}
});
}
}
visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, parent: ElementContext): any {
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
var expr = this._parseInterpolation(ast.value, ast.sourceSpan);
if (isPresent(expr)) {
return new BoundTextAst(expr, ngContentIndex, ast.sourceSpan);
} else {
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
}
}
visitAttr(ast: HtmlAttrAst, contex: any): any {
return new AttrAst(ast.name, ast.value, ast.sourceSpan);
}
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
visitElement(element: HtmlElementAst, parent: ElementContext): any {
var nodeName = element.name;
var preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE) {
// Skipping <script> for security reasons
// Skipping <style> as we already processed them
// in the StyleCompiler
return null;
}
if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
isStyleUrlResolvable(preparsedElement.hrefAttr)) {
// Skipping stylesheets with either relative urls or package scheme as we already processed
// them in the StyleCompiler
return null;
}
var matchableAttrs: string[][] = [];
var elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
var elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
var elementVars: VariableAst[] = [];
var events: BoundEventAst[] = [];
var templateElementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
var templateMatchableAttrs: string[][] = [];
var templateElementVars: VariableAst[] = [];
var hasInlineTemplates = false;
var attrs = [];
var lcElName = splitNsName(nodeName.toLowerCase())[1];
var isTemplateElement = lcElName == TEMPLATE_ELEMENT;
element.attrs.forEach(attr => {
var hasBinding =
this._parseAttr(isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars);
var hasTemplateBinding = this._parseInlineTemplateBinding(
attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateElementVars);
if (!hasBinding && !hasTemplateBinding) {
// don't include the bindings as attributes as well in the AST
attrs.push(this.visitAttr(attr, null));
matchableAttrs.push([attr.name, attr.value]);
}
if (hasTemplateBinding) {
hasInlineTemplates = true;
}
});
var elementCssSelector = createElementCssSelector(nodeName, matchableAttrs);
var directiveMetas = this._parseDirectives(this.selectorMatcher, elementCssSelector);
var references: ReferenceAst[] = [];
var directiveAsts = this._createDirectiveAsts(isTemplateElement, element.name, directiveMetas,
elementOrDirectiveProps, elementOrDirectiveRefs,
element.sourceSpan, references);
var elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
var isViewRoot = parent.isTemplateElement || hasInlineTemplates;
var providerContext =
new ProviderElementContext(this.providerViewContext, parent.providerContext, isViewRoot,
directiveAsts, attrs, references, element.sourceSpan);
var children = htmlVisitAll(
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create(isTemplateElement, directiveAsts,
isTemplateElement ? parent.providerContext : providerContext));
providerContext.afterElement();
// Override the actual selector when the `ngProjectAs` attribute is provided
var projectionSelector = isPresent(preparsedElement.projectAs) ?
CssSelector.parse(preparsedElement.projectAs)[0] :
elementCssSelector;
var ngContentIndex = parent.findNgContentIndex(projectionSelector);
var parsedElement;
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
if (isPresent(element.children) && element.children.length > 0) {
this._reportError(
`<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content>`,
element.sourceSpan);
}
parsedElement = new NgContentAst(
this.ngContentCount++, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
} else if (isTemplateElement) {
this._assertAllEventsPublishedByDirectives(directiveAsts, events);
this._assertNoComponentsNorElementBindingsOnTemplate(directiveAsts, elementProps,
element.sourceSpan);
parsedElement = new EmbeddedTemplateAst(
attrs, events, references, elementVars, providerContext.transformedDirectiveAsts,
providerContext.transformProviders, providerContext.transformedHasViewContainer, children,
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
} else {
this._assertOnlyOneComponent(directiveAsts, element.sourceSpan);
let ngContentIndex =
hasInlineTemplates ? null : parent.findNgContentIndex(projectionSelector);
parsedElement = new ElementAst(
nodeName, attrs, elementProps, events, references,
providerContext.transformedDirectiveAsts, providerContext.transformProviders,
providerContext.transformedHasViewContainer, children,
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
}
if (hasInlineTemplates) {
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectiveMetas = this._parseDirectives(this.selectorMatcher, templateCssSelector);
var templateDirectiveAsts =
this._createDirectiveAsts(true, element.name, templateDirectiveMetas,
templateElementOrDirectiveProps, [], element.sourceSpan, []);
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
var templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], element.sourceSpan);
templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst([], [], [], templateElementVars,
templateProviderContext.transformedDirectiveAsts,
templateProviderContext.transformProviders,
templateProviderContext.transformedHasViewContainer,
[parsedElement], ngContentIndex, element.sourceSpan);
}
return parsedElement;
}
private _parseInlineTemplateBinding(attr: HtmlAttrAst, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[],
targetVars: VariableAst[]): boolean {
var templateBindingsSource = null;
if (attr.name == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
var key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
}
if (isPresent(templateBindingsSource)) {
var bindings = this._parseTemplateBindings(templateBindingsSource, attr.sourceSpan);
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i];
if (binding.keyIsVar) {
targetVars.push(new VariableAst(binding.key, binding.name, attr.sourceSpan));
} else if (isPresent(binding.expression)) {
this._parsePropertyAst(binding.key, binding.expression, attr.sourceSpan,
targetMatchableAttrs, targetProps);
} else {
targetMatchableAttrs.push([binding.key, '']);
this._parseLiteralAttr(binding.key, null, attr.sourceSpan, targetProps);
}
}
return true;
}
return false;
}
private _parseAttr(isTemplateElement: boolean, attr: HtmlAttrAst,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[], targetEvents: BoundEventAst[],
targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean {
var attrName = this._normalizeAttributeName(attr.name);
var attrValue = attr.value;
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
var hasBinding = false;
if (isPresent(bindParts)) {
hasBinding = true;
if (isPresent(bindParts[1])) { // match: bind-prop
this._parseProperty(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetProps);
} else if (isPresent(bindParts[2])) { // match: var-name / var-name="iden"
var identifier = bindParts[7];
if (isTemplateElement) {
this._reportError(`"var-" on <template> elements is deprecated. Use "let-" instead!`,
attr.sourceSpan, ParseErrorLevel.WARNING);
this._parseVariable(identifier, attrValue, attr.sourceSpan, targetVars);
} else {
this._reportError(`"var-" on non <template> elements is deprecated. Use "ref-" instead!`,
attr.sourceSpan, ParseErrorLevel.WARNING);
this._parseReference(identifier, attrValue, attr.sourceSpan, targetRefs);
}
} else if (isPresent(bindParts[3])) { // match: let-name
if (isTemplateElement) {
var identifier = bindParts[7];
this._parseVariable(identifier, attrValue, attr.sourceSpan, targetVars);
} else {
this._reportError(`"let-" is only supported on template elements.`, attr.sourceSpan);
}
} else if (isPresent(bindParts[4])) { // match: ref- / #iden
var identifier = bindParts[7];
this._parseReference(identifier, attrValue, attr.sourceSpan, targetRefs);
} else if (isPresent(bindParts[5])) { // match: on-event
this._parseEvent(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetEvents);
} else if (isPresent(bindParts[6])) { // match: bindon-prop
this._parseProperty(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetProps);
this._parseAssignmentEvent(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetEvents);
} else if (isPresent(bindParts[8])) { // match: [(expr)]
this._parseProperty(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetProps);
this._parseAssignmentEvent(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetEvents);
} else if (isPresent(bindParts[9])) { // match: [expr]
this._parseProperty(bindParts[9], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetProps);
} else if (isPresent(bindParts[10])) { // match: (event)
this._parseEvent(bindParts[10], attrValue, attr.sourceSpan, targetMatchableAttrs,
targetEvents);
}
} else {
hasBinding = this._parsePropertyInterpolation(attrName, attrValue, attr.sourceSpan,
targetMatchableAttrs, targetProps);
}
if (!hasBinding) {
this._parseLiteralAttr(attrName, attrValue, attr.sourceSpan, targetProps);
}
return hasBinding;
}
private _normalizeAttributeName(attrName: string): string {
return attrName.toLowerCase().startsWith('data-') ? attrName.substring(5) : attrName;
}
private _parseVariable(identifier: string, value: string, sourceSpan: ParseSourceSpan,
targetVars: VariableAst[]) {
if (identifier.indexOf('-') > -1) {
this._reportError(`"-" is not allowed in variable names`, sourceSpan);
}
targetVars.push(new VariableAst(identifier, value, sourceSpan));
}
private _parseReference(identifier: string, value: string, sourceSpan: ParseSourceSpan,
targetRefs: ElementOrDirectiveRef[]) {
if (identifier.indexOf('-') > -1) {
this._reportError(`"-" is not allowed in reference names`, sourceSpan);
}
targetRefs.push(new ElementOrDirectiveRef(identifier, value, sourceSpan));
}
private _parseProperty(name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]) {
this._parsePropertyAst(name, this._parseBinding(expression, sourceSpan), sourceSpan,
targetMatchableAttrs, targetProps);
}
private _parsePropertyInterpolation(name: string, value: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]): boolean {
var expr = this._parseInterpolation(value, sourceSpan);
if (isPresent(expr)) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
return true;
}
return false;
}
private _parsePropertyAst(name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]) {
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new BoundElementOrDirectiveProperty(name, ast, false, sourceSpan));
}
private _parseAssignmentEvent(name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
this._parseEvent(`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs,
targetEvents);
}
private _parseEvent(name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName'
var parts = splitAtColon(name, [null, name]);
var target = parts[0];
var eventName = parts[1];
var ast = this._parseAction(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetEvents.push(new BoundEventAst(eventName, target, ast, sourceSpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
private _parseLiteralAttr(name: string, value: string, sourceSpan: ParseSourceSpan,
targetProps: BoundElementOrDirectiveProperty[]) {
targetProps.push(new BoundElementOrDirectiveProperty(
name, this._exprParser.wrapLiteralPrimitive(value, ''), true, sourceSpan));
}
private _parseDirectives(selectorMatcher: SelectorMatcher,
elementCssSelector: CssSelector): CompileDirectiveMetadata[] {
// Need to sort the directives so that we get consistent results throughout,
// as selectorMatcher uses Maps inside.
// Also dedupe directives as they might match more than one time!
var directives = ListWrapper.createFixedSize(this.directivesIndex.size);
selectorMatcher.match(elementCssSelector, (selector, directive) => {
directives[this.directivesIndex.get(directive)] = directive;
});
return directives.filter(dir => isPresent(dir));
}
private _createDirectiveAsts(isTemplateElement: boolean, elementName: string,
directives: CompileDirectiveMetadata[],
props: BoundElementOrDirectiveProperty[],
elementOrDirectiveRefs: ElementOrDirectiveRef[],
sourceSpan: ParseSourceSpan,
targetReferences: ReferenceAst[]): DirectiveAst[] {
var matchedReferences = new Set<string>();
var component: CompileDirectiveMetadata = null;
var directiveAsts = directives.map((directive: CompileDirectiveMetadata) => {
if (directive.isComponent) {
component = directive;
}
var hostProperties: BoundElementPropertyAst[] = [];
var hostEvents: BoundEventAst[] = [];
var directiveProperties: BoundDirectivePropertyAst[] = [];
this._createDirectiveHostPropertyAsts(elementName, directive.hostProperties, sourceSpan,
hostProperties);
this._createDirectiveHostEventAsts(directive.hostListeners, sourceSpan, hostEvents);
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
(directive.exportAs == elOrDirRef.value)) {
targetReferences.push(new ReferenceAst(elOrDirRef.name, identifierToken(directive.type),
elOrDirRef.sourceSpan));
matchedReferences.add(elOrDirRef.name);
}
});
return new DirectiveAst(directive, directiveProperties, hostProperties, hostEvents,
sourceSpan);
});
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if (elOrDirRef.value.length > 0) {
if (!SetWrapper.has(matchedReferences, elOrDirRef.name)) {
this._reportError(`There is no directive with "exportAs" set to "${elOrDirRef.value}"`,
elOrDirRef.sourceSpan);
};
} else if (isBlank(component)) {
var refToken = null;
if (isTemplateElement) {
refToken = identifierToken(Identifiers.TemplateRef);
}
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
}
});
return directiveAsts;
}
private _createDirectiveHostPropertyAsts(elementName: string, hostProps: {[key: string]: string},
sourceSpan: ParseSourceSpan,
targetPropertyAsts: BoundElementPropertyAst[]) {
if (isPresent(hostProps)) {
StringMapWrapper.forEach(hostProps, (expression: string, propName: string) => {
var exprAst = this._parseBinding(expression, sourceSpan);
targetPropertyAsts.push(
this._createElementPropertyAst(elementName, propName, exprAst, sourceSpan));
});
}
}
private _createDirectiveHostEventAsts(hostListeners: {[key: string]: string},
sourceSpan: ParseSourceSpan,
targetEventAsts: BoundEventAst[]) {
if (isPresent(hostListeners)) {
StringMapWrapper.forEach(hostListeners, (expression: string, propName: string) => {
this._parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
});
}
}
private _createDirectivePropertyAsts(directiveProperties: {[key: string]: string},
boundProps: BoundElementOrDirectiveProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
if (isPresent(directiveProperties)) {
var boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>();
boundProps.forEach(boundProp => {
var prevValue = boundPropsByName.get(boundProp.name);
if (isBlank(prevValue) || prevValue.isLiteral) {
// give [a]="b" a higher precedence than a="b" on the same element
boundPropsByName.set(boundProp.name, boundProp);
}
});
StringMapWrapper.forEach(directiveProperties, (elProp: string, dirProp: string) => {
var boundProp = boundPropsByName.get(elProp);
// Bindings are optional, so this binding only needs to be set up if an expression is given.
if (isPresent(boundProp)) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
}
});
}
}
private _createElementPropertyAsts(elementName: string, props: BoundElementOrDirectiveProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] {
var boundElementProps: BoundElementPropertyAst[] = [];
var boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
directives.forEach((directive: DirectiveAst) => {
directive.inputs.forEach((prop: BoundDirectivePropertyAst) => {
boundDirectivePropsIndex.set(prop.templateName, prop);
});
});
props.forEach((prop: BoundElementOrDirectiveProperty) => {
if (!prop.isLiteral && isBlank(boundDirectivePropsIndex.get(prop.name))) {
boundElementProps.push(this._createElementPropertyAst(elementName, prop.name,
prop.expression, prop.sourceSpan));
}
});
return boundElementProps;
}
private _createElementPropertyAst(elementName: string, name: string, ast: AST,
sourceSpan: ParseSourceSpan): BoundElementPropertyAst {
var unit = null;
var bindingType;
var boundPropertyName;
var parts = name.split(PROPERTY_PARTS_SEPARATOR);
if (parts.length === 1) {
boundPropertyName = this._schemaRegistry.getMappedPropName(parts[0]);
bindingType = PropertyBindingType.Property;
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
this._reportError(
`Can't bind to '${boundPropertyName}' since it isn't a known native property`,
sourceSpan);
}
} else {
if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts[1];
let nsSeparatorIdx = boundPropertyName.indexOf(':');
if (nsSeparatorIdx > -1) {
let ns = boundPropertyName.substring(0, nsSeparatorIdx);
let name = boundPropertyName.substring(nsSeparatorIdx + 1);
boundPropertyName = mergeNsAndName(ns, name);
}
bindingType = PropertyBindingType.Attribute;
} else if (parts[0] == CLASS_PREFIX) {
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Class;
} else if (parts[0] == STYLE_PREFIX) {
unit = parts.length > 2 ? parts[2] : null;
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Style;
} else {
this._reportError(`Invalid property name '${name}'`, sourceSpan);
bindingType = null;
}
}
return new BoundElementPropertyAst(boundPropertyName, bindingType, ast, unit, sourceSpan);
}
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
var componentTypeNames: string[] = [];
directives.forEach(directive => {
var typeName = directive.directive.type.name;
if (directive.directive.isComponent) {
componentTypeNames.push(typeName);
}
});
return componentTypeNames;
}
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
var componentTypeNames = this._findComponentDirectiveNames(directives);
if (componentTypeNames.length > 1) {
this._reportError(`More than one component: ${componentTypeNames.join(',')}`, sourceSpan);
}
}
private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
elementProps: BoundElementPropertyAst[],
sourceSpan: ParseSourceSpan) {
var componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
if (componentTypeNames.length > 0) {
this._reportError(`Components on an embedded template: ${componentTypeNames.join(',')}`,
sourceSpan);
}
elementProps.forEach(prop => {
this._reportError(
`Property binding ${prop.name} not used by any directive on an embedded template`,
sourceSpan);
});
}
private _assertAllEventsPublishedByDirectives(directives: DirectiveAst[],
events: BoundEventAst[]) {
var allDirectiveEvents = new Set<string>();
directives.forEach(directive => {
StringMapWrapper.forEach(directive.directive.outputs,
(eventName: string, _) => { allDirectiveEvents.add(eventName); });
});
events.forEach(event => {
if (isPresent(event.target) || !SetWrapper.has(allDirectiveEvents, event.name)) {
this._reportError(
`Event binding ${event.fullName} not emitted by any directive on an embedded template`,
event.sourceSpan);
}
});
}
}
class NonBindableVisitor implements HtmlAstVisitor {
visitElement(ast: HtmlElementAst, parent: ElementContext): ElementAst {
var preparsedElement = preparseElement(ast);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE ||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
// Skipping <script> for security reasons
// Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler
return null;
}
var attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
var selector = createElementCssSelector(ast.name, attrNameAndValues);
var ngContentIndex = parent.findNgContentIndex(selector);
var children = htmlVisitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
return new ElementAst(ast.name, htmlVisitAll(this, ast.attrs), [], [], [], [], [], false,
children, ngContentIndex, ast.sourceSpan);
}
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
visitAttr(ast: HtmlAttrAst, context: any): AttrAst {
return new AttrAst(ast.name, ast.value, ast.sourceSpan);
}
visitText(ast: HtmlTextAst, parent: ElementContext): TextAst {
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
}
visitExpansion(ast: HtmlExpansionAst, context: any): any { return ast; }
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
}
class BoundElementOrDirectiveProperty {
constructor(public name: string, public expression: AST, public isLiteral: boolean,
public sourceSpan: ParseSourceSpan) {}
}
class ElementOrDirectiveRef {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
}
export function splitClasses(classAttrValue: string): string[] {
return StringWrapper.split(classAttrValue.trim(), /\s+/g);
}
class ElementContext {
static create(isTemplateElement: boolean, directives: DirectiveAst[],
providerContext: ProviderElementContext): ElementContext {
var matcher = new SelectorMatcher();
var wildcardNgContentIndex = null;
var component = directives.find(directive => directive.directive.isComponent);
if (isPresent(component)) {
var ngContentSelectors = component.directive.template.ngContentSelectors;
for (var i = 0; i < ngContentSelectors.length; i++) {
var selector = ngContentSelectors[i];
if (StringWrapper.equals(selector, '*')) {
wildcardNgContentIndex = i;
} else {
matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i);
}
}
}
return new ElementContext(isTemplateElement, matcher, wildcardNgContentIndex, providerContext);
}
constructor(public isTemplateElement: boolean, private _ngContentIndexMatcher: SelectorMatcher,
private _wildcardNgContentIndex: number,
public providerContext: ProviderElementContext) {}
findNgContentIndex(selector: CssSelector): number {
var ngContentIndices = [];
this._ngContentIndexMatcher.match(
selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); });
ListWrapper.sort(ngContentIndices);
if (isPresent(this._wildcardNgContentIndex)) {
ngContentIndices.push(this._wildcardNgContentIndex);
}
return ngContentIndices.length > 0 ? ngContentIndices[0] : null;
}
}
function createElementCssSelector(elementName: string, matchableAttrs: string[][]): CssSelector {
var cssSelector = new CssSelector();
let elNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(elNameNoNs);
for (var i = 0; i < matchableAttrs.length; i++) {
let attrName = matchableAttrs[i][0];
let attrNameNoNs = splitNsName(attrName)[1];
let attrValue = matchableAttrs[i][1];
cssSelector.addAttribute(attrNameNoNs, attrValue);
if (attrName.toLowerCase() == CLASS_ATTR) {
var classes = splitClasses(attrValue);
classes.forEach(className => cssSelector.addClassName(className));
}
}
return cssSelector;
}
var EMPTY_ELEMENT_CONTEXT = new ElementContext(true, new SelectorMatcher(), null, null);
var NON_BINDABLE_VISITOR = new NonBindableVisitor();
export class PipeCollector extends RecursiveAstVisitor {
pipes: Set<string> = new Set<string>();
visitPipe(ast: BindingPipe, context: any): any {
this.pipes.add(ast.name);
ast.exp.visit(this);
this.visitAll(ast.args, context);
return null;
}
}
function removeDuplicates(items: CompileMetadataWithType[]): CompileMetadataWithType[] {
let res = [];
items.forEach(item => {
let hasMatch =
res.filter(r => r.type.name == item.type.name && r.type.moduleUrl == item.type.moduleUrl &&
r.type.runtime == item.type.runtime)
.length > 0;
if (!hasMatch) {
res.push(item);
}
});
return res;
}

View File

@ -0,0 +1,72 @@
import {HtmlElementAst} from './html_ast';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {splitNsName} from './html_tags';
const NG_CONTENT_SELECT_ATTR = 'select';
const NG_CONTENT_ELEMENT = 'ng-content';
const LINK_ELEMENT = 'link';
const LINK_STYLE_REL_ATTR = 'rel';
const LINK_STYLE_HREF_ATTR = 'href';
const LINK_STYLE_REL_VALUE = 'stylesheet';
const STYLE_ELEMENT = 'style';
const SCRIPT_ELEMENT = 'script';
const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
const NG_PROJECT_AS = 'ngProjectAs';
export function preparseElement(ast: HtmlElementAst): PreparsedElement {
var selectAttr = null;
var hrefAttr = null;
var relAttr = null;
var nonBindable = false;
var projectAs: string = null;
ast.attrs.forEach(attr => {
let lcAttrName = attr.name.toLowerCase();
if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
selectAttr = attr.value;
} else if (lcAttrName == LINK_STYLE_HREF_ATTR) {
hrefAttr = attr.value;
} else if (lcAttrName == LINK_STYLE_REL_ATTR) {
relAttr = attr.value;
} else if (attr.name == NG_NON_BINDABLE_ATTR) {
nonBindable = true;
} else if (attr.name == NG_PROJECT_AS) {
if (attr.value.length > 0) {
projectAs = attr.value;
}
}
});
selectAttr = normalizeNgContentSelect(selectAttr);
var nodeName = ast.name.toLowerCase();
var type = PreparsedElementType.OTHER;
if (splitNsName(nodeName)[1] == NG_CONTENT_ELEMENT) {
type = PreparsedElementType.NG_CONTENT;
} else if (nodeName == STYLE_ELEMENT) {
type = PreparsedElementType.STYLE;
} else if (nodeName == SCRIPT_ELEMENT) {
type = PreparsedElementType.SCRIPT;
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
type = PreparsedElementType.STYLESHEET;
}
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable, projectAs);
}
export enum PreparsedElementType {
NG_CONTENT,
STYLE,
STYLESHEET,
SCRIPT,
OTHER
}
export class PreparsedElement {
constructor(public type: PreparsedElementType, public selectAttr: string, public hrefAttr: string,
public nonBindable: boolean, public projectAs: string) {}
}
function normalizeNgContentSelect(selectAttr: string): string {
if (isBlank(selectAttr) || selectAttr.length === 0) {
return '*';
}
return selectAttr;
}

View File

@ -0,0 +1,69 @@
library angular2.src.services.url_resolver;
import 'package:angular2/src/core/di.dart' show Injectable, Inject, Provider;
import 'package:angular2/src/facade/lang.dart' show isPresent, StringWrapper;
import 'package:angular2/src/core/application_tokens.dart' show PACKAGE_ROOT_URL;
const _ASSET_SCHEME = 'asset:';
UrlResolver createUrlResolverWithoutPackagePrefix() {
return new UrlResolver.withUrlPrefix(null);
}
UrlResolver createOfflineCompileUrlResolver() {
return new UrlResolver.withUrlPrefix(_ASSET_SCHEME);
}
const DEFAULT_PACKAGE_URL_PROVIDER = const Provider(PACKAGE_ROOT_URL, useValue: "/packages");
@Injectable()
class UrlResolver {
/// This will be the location where 'package:' Urls will resolve. Default is
/// '/packages'
final String _packagePrefix;
UrlResolver([@Inject(PACKAGE_ROOT_URL) this._packagePrefix]);
/// Creates a UrlResolver that will resolve 'package:' Urls to a different
/// prefixed location.
const UrlResolver.withUrlPrefix(this._packagePrefix);
/**
* Resolves the `url` given the `baseUrl`:
* - when the `url` is null, the `baseUrl` is returned,
* - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
* `baseUrl` and `url`,
* - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
* returned as is (ignoring the `baseUrl`)
*
* @param {string} baseUrl
* @param {string} url
* @returns {string} the resolved URL
*/
String resolve(String baseUrl, String url) {
Uri uri = Uri.parse(url);
if (isPresent(baseUrl) && baseUrl.length > 0) {
Uri baseUri = Uri.parse(baseUrl);
uri = baseUri.resolveUri(uri);
}
var prefix = this._packagePrefix;
if (prefix != null && uri.scheme == 'package') {
if (prefix == _ASSET_SCHEME) {
var pathSegments = uri.pathSegments.toList()..insert(1, 'lib');
return new Uri(scheme: 'asset', pathSegments: pathSegments).toString();
} else {
prefix = StringWrapper.stripRight(prefix, '/');
var path = StringWrapper.stripLeft(uri.path, '/');
return '$prefix/$path';
}
} else {
return uri.toString();
}
}
}
String getUrlScheme(String url) {
return Uri.parse(url).scheme;
}

View File

@ -0,0 +1,357 @@
import {Provider, Injectable, Inject} from 'angular2/src/core/di';
import {
StringWrapper,
isPresent,
isBlank,
RegExpWrapper,
normalizeBlank
} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection';
import {PACKAGE_ROOT_URL} from 'angular2/src/core/application_tokens';
const _ASSET_SCHEME = 'asset:';
/**
* Create a {@link UrlResolver} with no package prefix.
*/
export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
return new UrlResolver();
}
export function createOfflineCompileUrlResolver(): UrlResolver {
return new UrlResolver(_ASSET_SCHEME);
}
/**
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
*/
export var DEFAULT_PACKAGE_URL_PROVIDER = {
provide: PACKAGE_ROOT_URL,
useValue: "/"
};
/**
* Used by the {@link Compiler} when resolving HTML and CSS template URLs.
*
* This class can be overridden by the application developer to create custom behavior.
*
* See {@link Compiler}
*
* ## Example
*
* {@example compiler/ts/url_resolver/url_resolver.ts region='url_resolver'}
*/
@Injectable()
export class UrlResolver {
constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string = null) {}
/**
* Resolves the `url` given the `baseUrl`:
* - when the `url` is null, the `baseUrl` is returned,
* - if `url` is relative ('path/to/here', './path/to/here'), the resolved url is a combination of
* `baseUrl` and `url`,
* - if `url` is absolute (it has a scheme: 'http://', 'https://' or start with '/'), the `url` is
* returned as is (ignoring the `baseUrl`)
*
* @param {string} baseUrl
* @param {string} url
* @returns {string} the resolved URL
*/
resolve(baseUrl: string, url: string): string {
var resolvedUrl = url;
if (isPresent(baseUrl) && baseUrl.length > 0) {
resolvedUrl = _resolveUrl(baseUrl, resolvedUrl);
}
var resolvedParts = _split(resolvedUrl);
var prefix = this._packagePrefix;
if (isPresent(prefix) && isPresent(resolvedParts) &&
resolvedParts[_ComponentIndex.Scheme] == "package") {
var path = resolvedParts[_ComponentIndex.Path];
if (this._packagePrefix === _ASSET_SCHEME) {
var pathSegements = path.split(/\//);
resolvedUrl = `asset:${pathSegements[0]}/lib/${pathSegements.slice(1).join('/')}`;
} else {
prefix = StringWrapper.stripRight(prefix, '/');
path = StringWrapper.stripLeft(path, '/');
return `${prefix}/${path}`;
}
}
return resolvedUrl;
}
}
/**
* Extract the scheme of a URL.
*/
export function getUrlScheme(url: string): string {
var match = _split(url);
return (match && match[_ComponentIndex.Scheme]) || "";
}
// The code below is adapted from Traceur:
// https://github.com/google/traceur-compiler/blob/9511c1dafa972bf0de1202a8a863bad02f0f95a8/src/runtime/url.js
/**
* Builds a URI string from already-encoded parts.
*
* No encoding is performed. Any component may be omitted as either null or
* undefined.
*
* @param {?string=} opt_scheme The scheme such as 'http'.
* @param {?string=} opt_userInfo The user name before the '@'.
* @param {?string=} opt_domain The domain such as 'www.google.com', already
* URI-encoded.
* @param {(string|null)=} opt_port The port number.
* @param {?string=} opt_path The path, already URI-encoded. If it is not
* empty, it must begin with a slash.
* @param {?string=} opt_queryData The URI-encoded query data.
* @param {?string=} opt_fragment The URI-encoded fragment identifier.
* @return {string} The fully combined URI.
*/
function _buildFromEncodedParts(opt_scheme?: string, opt_userInfo?: string, opt_domain?: string,
opt_port?: string, opt_path?: string, opt_queryData?: string,
opt_fragment?: string): string {
var out = [];
if (isPresent(opt_scheme)) {
out.push(opt_scheme + ':');
}
if (isPresent(opt_domain)) {
out.push('//');
if (isPresent(opt_userInfo)) {
out.push(opt_userInfo + '@');
}
out.push(opt_domain);
if (isPresent(opt_port)) {
out.push(':' + opt_port);
}
}
if (isPresent(opt_path)) {
out.push(opt_path);
}
if (isPresent(opt_queryData)) {
out.push('?' + opt_queryData);
}
if (isPresent(opt_fragment)) {
out.push('#' + opt_fragment);
}
return out.join('');
}
/**
* A regular expression for breaking a URI into its component parts.
*
* {@link http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#RFC2234} says
* As the "first-match-wins" algorithm is identical to the "greedy"
* disambiguation method used by POSIX regular expressions, it is natural and
* commonplace to use a regular expression for parsing the potential five
* components of a URI reference.
*
* The following line is the regular expression for breaking-down a
* well-formed URI reference into its components.
*
* <pre>
* ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
* 12 3 4 5 6 7 8 9
* </pre>
*
* The numbers in the second line above are only to assist readability; they
* indicate the reference points for each subexpression (i.e., each paired
* parenthesis). We refer to the value matched for subexpression <n> as $<n>.
* For example, matching the above expression to
* <pre>
* http://www.ics.uci.edu/pub/ietf/uri/#Related
* </pre>
* results in the following subexpression matches:
* <pre>
* $1 = http:
* $2 = http
* $3 = //www.ics.uci.edu
* $4 = www.ics.uci.edu
* $5 = /pub/ietf/uri/
* $6 = <undefined>
* $7 = <undefined>
* $8 = #Related
* $9 = Related
* </pre>
* where <undefined> indicates that the component is not present, as is the
* case for the query component in the above example. Therefore, we can
* determine the value of the five components as
* <pre>
* scheme = $2
* authority = $4
* path = $5
* query = $7
* fragment = $9
* </pre>
*
* The regular expression has been modified slightly to expose the
* userInfo, domain, and port separately from the authority.
* The modified version yields
* <pre>
* $1 = http scheme
* $2 = <undefined> userInfo -\
* $3 = www.ics.uci.edu domain | authority
* $4 = <undefined> port -/
* $5 = /pub/ietf/uri/ path
* $6 = <undefined> query without ?
* $7 = Related fragment without #
* </pre>
* @type {!RegExp}
* @internal
*/
var _splitRe =
RegExpWrapper.create('^' +
'(?:' +
'([^:/?#.]+)' + // scheme - ignore special characters
// used by other URL parts such as :,
// ?, /, #, and .
':)?' +
'(?://' +
'(?:([^/?#]*)@)?' + // userInfo
'([\\w\\d\\-\\u0100-\\uffff.%]*)' + // domain - restrict to letters,
// digits, dashes, dots, percent
// escapes, and unicode characters.
'(?::([0-9]+))?' + // port
')?' +
'([^?#]+)?' + // path
'(?:\\?([^#]*))?' + // query
'(?:#(.*))?' + // fragment
'$');
/**
* The index of each URI component in the return value of goog.uri.utils.split.
* @enum {number}
*/
enum _ComponentIndex {
Scheme = 1,
UserInfo,
Domain,
Port,
Path,
QueryData,
Fragment
}
/**
* Splits a URI into its component parts.
*
* Each component can be accessed via the component indices; for example:
* <pre>
* goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
* </pre>
*
* @param {string} uri The URI string to examine.
* @return {!Array.<string|undefined>} Each component still URI-encoded.
* Each component that is present will contain the encoded value, whereas
* components that are not present will be undefined or empty, depending
* on the browser's regular expression implementation. Never null, since
* arbitrary strings may still look like path names.
*/
function _split(uri: string): Array<string | any> {
return RegExpWrapper.firstMatch(_splitRe, uri);
}
/**
* Removes dot segments in given path component, as described in
* RFC 3986, section 5.2.4.
*
* @param {string} path A non-empty path component.
* @return {string} Path component with removed dot segments.
*/
function _removeDotSegments(path: string): string {
if (path == '/') return '/';
var leadingSlash = path[0] == '/' ? '/' : '';
var trailingSlash = path[path.length - 1] === '/' ? '/' : '';
var segments = path.split('/');
var out: string[] = [];
var up = 0;
for (var pos = 0; pos < segments.length; pos++) {
var segment = segments[pos];
switch (segment) {
case '':
case '.':
break;
case '..':
if (out.length > 0) {
out.pop();
} else {
up++;
}
break;
default:
out.push(segment);
}
}
if (leadingSlash == '') {
while (up-- > 0) {
out.unshift('..');
}
if (out.length === 0) out.push('.');
}
return leadingSlash + out.join('/') + trailingSlash;
}
/**
* Takes an array of the parts from split and canonicalizes the path part
* and then joins all the parts.
* @param {Array.<string?>} parts
* @return {string}
*/
function _joinAndCanonicalizePath(parts: any[]): string {
var path = parts[_ComponentIndex.Path];
path = isBlank(path) ? '' : _removeDotSegments(path);
parts[_ComponentIndex.Path] = path;
return _buildFromEncodedParts(parts[_ComponentIndex.Scheme], parts[_ComponentIndex.UserInfo],
parts[_ComponentIndex.Domain], parts[_ComponentIndex.Port], path,
parts[_ComponentIndex.QueryData], parts[_ComponentIndex.Fragment]);
}
/**
* Resolves a URL.
* @param {string} base The URL acting as the base URL.
* @param {string} to The URL to resolve.
* @return {string}
*/
function _resolveUrl(base: string, url: string): string {
var parts = _split(encodeURI(url));
var baseParts = _split(base);
if (isPresent(parts[_ComponentIndex.Scheme])) {
return _joinAndCanonicalizePath(parts);
} else {
parts[_ComponentIndex.Scheme] = baseParts[_ComponentIndex.Scheme];
}
for (var i = _ComponentIndex.Scheme; i <= _ComponentIndex.Port; i++) {
if (isBlank(parts[i])) {
parts[i] = baseParts[i];
}
}
if (parts[_ComponentIndex.Path][0] == '/') {
return _joinAndCanonicalizePath(parts);
}
var path = baseParts[_ComponentIndex.Path];
if (isBlank(path)) path = '/';
var index = path.lastIndexOf('/');
path = path.substring(0, index + 1) + parts[_ComponentIndex.Path];
parts[_ComponentIndex.Path] = path;
return _joinAndCanonicalizePath(parts);
}

View File

@ -0,0 +1,71 @@
import {
IS_DART,
StringWrapper,
Math,
isBlank,
isArray,
isStrictStringMap,
isPrimitive
} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
export var MODULE_SUFFIX = IS_DART ? '.dart' : '';
var CAMEL_CASE_REGEXP = /([A-Z])/g;
var DASH_CASE_REGEXP = /-([a-z])/g;
export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
(m) => { return '-' + m[1].toLowerCase(); });
}
export function dashCaseToCamelCase(input: string): string {
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
(m) => { return m[1].toUpperCase(); });
}
export function splitAtColon(input: string, defaultValues: string[]): string[] {
var parts = StringWrapper.split(input.trim(), /\s*:\s*/g);
if (parts.length > 1) {
return parts;
} else {
return defaultValues;
}
}
export function sanitizeIdentifier(name: string): string {
return StringWrapper.replaceAll(name, /\W/g, '_');
}
export function visitValue(value: any, visitor: ValueVisitor, context: any): any {
if (isArray(value)) {
return visitor.visitArray(<any[]>value, context);
} else if (isStrictStringMap(value)) {
return visitor.visitStringMap(<{[key: string]: any}>value, context);
} else if (isBlank(value) || isPrimitive(value)) {
return visitor.visitPrimitive(value, context);
} else {
return visitor.visitOther(value, context);
}
}
export interface ValueVisitor {
visitArray(arr: any[], context: any): any;
visitStringMap(map: {[key: string]: any}, context: any): any;
visitPrimitive(value: any, context: any): any;
visitOther(value: any, context: any): any;
}
export class ValueTransformer implements ValueVisitor {
visitArray(arr: any[], context: any): any {
return arr.map(value => visitValue(value, this, context));
}
visitStringMap(map: {[key: string]: any}, context: any): any {
var result = {};
StringMapWrapper.forEach(map,
(value, key) => { result[key] = visitValue(value, this, context); });
return result;
}
visitPrimitive(value: any, context: any): any { return value; }
visitOther(value: any, context: any): any { return value; }
}

View File

@ -0,0 +1,6 @@
import {CompileNode} from './compile_element';
import {TemplateAst} from '../template_ast';
export class CompileBinding {
constructor(public node: CompileNode, public sourceAst: TemplateAst) {}
}

View File

@ -0,0 +1,427 @@
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from '../output/output_ast';
import {Identifiers, identifierToken} from '../identifiers';
import {InjectMethodVars} from './constants';
import {CompileView} from './compile_view';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {TemplateAst, ProviderAst, ProviderAstType, ReferenceAst} from '../template_ast';
import {
CompileTokenMap,
CompileDirectiveMetadata,
CompileTokenMetadata,
CompileQueryMetadata,
CompileProviderMetadata,
CompileDiDependencyMetadata,
CompileIdentifierMetadata,
CompileTypeMetadata,
} from '../compile_metadata';
import {getPropertyInView, createDiTokenExpression, injectFromViewParentInjector} from './util';
import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query';
import {CompileMethod} from './compile_method';
import {ValueTransformer, visitValue} from '../util';
export class CompileNode {
constructor(public parent: CompileElement, public view: CompileView, public nodeIndex: number,
public renderNode: o.Expression, public sourceAst: TemplateAst) {}
isNull(): boolean { return isBlank(this.renderNode); }
isRootElement(): boolean { return this.view != this.parent.view; }
}
export class CompileElement extends CompileNode {
static createNull(): CompileElement {
return new CompileElement(null, null, null, null, null, null, [], [], false, false, []);
}
private _compViewExpr: o.Expression = null;
public appElement: o.ReadPropExpr;
public elementRef: o.Expression;
public injector: o.Expression;
private _instances = new CompileTokenMap<o.Expression>();
private _resolvedProviders: CompileTokenMap<ProviderAst>;
private _queryCount = 0;
private _queries = new CompileTokenMap<CompileQuery[]>();
private _componentConstructorViewQueryLists: o.Expression[] = [];
public contentNodesByNgContentIndex: Array<o.Expression>[] = null;
public embeddedView: CompileView;
public directiveInstances: o.Expression[];
public referenceTokens: {[key: string]: CompileTokenMetadata};
constructor(parent: CompileElement, view: CompileView, nodeIndex: number,
renderNode: o.Expression, sourceAst: TemplateAst,
public component: CompileDirectiveMetadata,
private _directives: CompileDirectiveMetadata[],
private _resolvedProvidersArray: ProviderAst[], public hasViewContainer: boolean,
public hasEmbeddedView: boolean, references: ReferenceAst[]) {
super(parent, view, nodeIndex, renderNode, sourceAst);
this.referenceTokens = {};
references.forEach(ref => this.referenceTokens[ref.name] = ref.value);
this.elementRef = o.importExpr(Identifiers.ElementRef).instantiate([this.renderNode]);
this._instances.add(identifierToken(Identifiers.ElementRef), this.elementRef);
this.injector = o.THIS_EXPR.callMethod('injector', [o.literal(this.nodeIndex)]);
this._instances.add(identifierToken(Identifiers.Injector), this.injector);
this._instances.add(identifierToken(Identifiers.Renderer), o.THIS_EXPR.prop('renderer'));
if (this.hasViewContainer || this.hasEmbeddedView || isPresent(this.component)) {
this._createAppElement();
}
}
private _createAppElement() {
var fieldName = `_appEl_${this.nodeIndex}`;
var parentNodeIndex = this.isRootElement() ? null : this.parent.nodeIndex;
// private is fine here as no child view will reference an AppElement
this.view.fields.push(new o.ClassField(fieldName, o.importType(Identifiers.AppElement),
[o.StmtModifier.Private]));
var statement = o.THIS_EXPR.prop(fieldName)
.set(o.importExpr(Identifiers.AppElement)
.instantiate([
o.literal(this.nodeIndex),
o.literal(parentNodeIndex),
o.THIS_EXPR,
this.renderNode
]))
.toStmt();
this.view.createMethod.addStmt(statement);
this.appElement = o.THIS_EXPR.prop(fieldName);
this._instances.add(identifierToken(Identifiers.AppElement), this.appElement);
}
setComponentView(compViewExpr: o.Expression) {
this._compViewExpr = compViewExpr;
this.contentNodesByNgContentIndex =
ListWrapper.createFixedSize(this.component.template.ngContentSelectors.length);
for (var i = 0; i < this.contentNodesByNgContentIndex.length; i++) {
this.contentNodesByNgContentIndex[i] = [];
}
}
setEmbeddedView(embeddedView: CompileView) {
this.embeddedView = embeddedView;
if (isPresent(embeddedView)) {
var createTemplateRefExpr =
o.importExpr(Identifiers.TemplateRef_)
.instantiate([this.appElement, this.embeddedView.viewFactory]);
var provider = new CompileProviderMetadata(
{token: identifierToken(Identifiers.TemplateRef), useValue: createTemplateRefExpr});
// Add TemplateRef as first provider as it does not have deps on other providers
this._resolvedProvidersArray.unshift(new ProviderAst(provider.token, false, true, [provider],
ProviderAstType.Builtin,
this.sourceAst.sourceSpan));
}
}
beforeChildren(): void {
if (this.hasViewContainer) {
this._instances.add(identifierToken(Identifiers.ViewContainerRef),
this.appElement.prop('vcRef'));
}
this._resolvedProviders = new CompileTokenMap<ProviderAst>();
this._resolvedProvidersArray.forEach(provider =>
this._resolvedProviders.add(provider.token, provider));
// create all the provider instances, some in the view constructor,
// some as getters. We rely on the fact that they are already sorted topologically.
this._resolvedProviders.values().forEach((resolvedProvider) => {
var providerValueExpressions = resolvedProvider.providers.map((provider) => {
if (isPresent(provider.useExisting)) {
return this._getDependency(
resolvedProvider.providerType,
new CompileDiDependencyMetadata({token: provider.useExisting}));
} else if (isPresent(provider.useFactory)) {
var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps;
var depsExpr = deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep));
return o.importExpr(provider.useFactory).callFn(depsExpr);
} else if (isPresent(provider.useClass)) {
var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps;
var depsExpr = deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep));
return o.importExpr(provider.useClass)
.instantiate(depsExpr, o.importType(provider.useClass));
} else {
return _convertValueToOutputAst(provider.useValue);
}
});
var propName = `_${resolvedProvider.token.name}_${this.nodeIndex}_${this._instances.size}`;
var instance =
createProviderProperty(propName, resolvedProvider, providerValueExpressions,
resolvedProvider.multiProvider, resolvedProvider.eager, this);
this._instances.add(resolvedProvider.token, instance);
});
this.directiveInstances =
this._directives.map((directive) => this._instances.get(identifierToken(directive.type)));
for (var i = 0; i < this.directiveInstances.length; i++) {
var directiveInstance = this.directiveInstances[i];
var directive = this._directives[i];
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
}
var queriesWithReads: _QueryWithRead[] = [];
this._resolvedProviders.values().forEach((resolvedProvider) => {
var queriesForProvider = this._getQueriesFor(resolvedProvider.token);
ListWrapper.addAll(
queriesWithReads,
queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
});
StringMapWrapper.forEach(this.referenceTokens, (_, varName) => {
var token = this.referenceTokens[varName];
var varValue;
if (isPresent(token)) {
varValue = this._instances.get(token);
} else {
varValue = this.renderNode;
}
this.view.locals.set(varName, varValue);
var varToken = new CompileTokenMetadata({value: varName});
ListWrapper.addAll(queriesWithReads, this._getQueriesFor(varToken)
.map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
var value: o.Expression;
if (isPresent(queryWithRead.read.identifier)) {
// query for an identifier
value = this._instances.get(queryWithRead.read);
} else {
// query for a reference
var token = this.referenceTokens[queryWithRead.read.value];
if (isPresent(token)) {
value = this._instances.get(token);
} else {
value = this.elementRef;
}
}
if (isPresent(value)) {
queryWithRead.query.addValue(value, this.view);
}
});
if (isPresent(this.component)) {
var componentConstructorViewQueryList =
isPresent(this.component) ? o.literalArr(this._componentConstructorViewQueryLists) :
o.NULL_EXPR;
var compExpr = isPresent(this.getComponent()) ? this.getComponent() : o.NULL_EXPR;
this.view.createMethod.addStmt(
this.appElement.callMethod(
'initComponent',
[compExpr, componentConstructorViewQueryList, this._compViewExpr])
.toStmt());
}
}
afterChildren(childNodeCount: number) {
this._resolvedProviders.values().forEach((resolvedProvider) => {
// Note: afterChildren is called after recursing into children.
// This is good so that an injector match in an element that is closer to a requesting element
// matches first.
var providerExpr = this._instances.get(resolvedProvider.token);
// Note: view providers are only visible on the injector of that element.
// This is not fully correct as the rules during codegen don't allow a directive
// to get hold of a view provdier on the same element. We still do this semantic
// as it simplifies our model to having only one runtime injector per element.
var providerChildNodeCount =
resolvedProvider.providerType === ProviderAstType.PrivateService ? 0 : childNodeCount;
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
});
this._queries.values().forEach(
(queries) =>
queries.forEach((query) => query.afterChildren(this.view.updateContentQueriesMethod)));
}
addContentNode(ngContentIndex: number, nodeExpr: o.Expression) {
this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr);
}
getComponent(): o.Expression {
return isPresent(this.component) ? this._instances.get(identifierToken(this.component.type)) :
null;
}
getProviderTokens(): o.Expression[] {
return this._resolvedProviders.values().map(
(resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
}
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
var result: CompileQuery[] = [];
var currentEl: CompileElement = this;
var distance = 0;
var queries: CompileQuery[];
while (!currentEl.isNull()) {
queries = currentEl._queries.get(token);
if (isPresent(queries)) {
ListWrapper.addAll(result,
queries.filter((query) => query.meta.descendants || distance <= 1));
}
if (currentEl._directives.length > 0) {
distance++;
}
currentEl = currentEl.parent;
}
queries = this.view.componentView.viewQueries.get(token);
if (isPresent(queries)) {
ListWrapper.addAll(result, queries);
}
return result;
}
private _addQuery(queryMeta: CompileQueryMetadata,
directiveInstance: o.Expression): CompileQuery {
var propName = `_query_${queryMeta.selectors[0].name}_${this.nodeIndex}_${this._queryCount++}`;
var queryList = createQueryList(queryMeta, directiveInstance, propName, this.view);
var query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view);
addQueryToTokenMap(this._queries, query);
return query;
}
private _getLocalDependency(requestingProviderType: ProviderAstType,
dep: CompileDiDependencyMetadata): o.Expression {
var result = null;
// constructor content query
if (isBlank(result) && isPresent(dep.query)) {
result = this._addQuery(dep.query, null).queryList;
}
// constructor view query
if (isBlank(result) && isPresent(dep.viewQuery)) {
result = createQueryList(
dep.viewQuery, null,
`_viewQuery_${dep.viewQuery.selectors[0].name}_${this.nodeIndex}_${this._componentConstructorViewQueryLists.length}`,
this.view);
this._componentConstructorViewQueryLists.push(result);
}
if (isPresent(dep.token)) {
// access builtins with special visibility
if (isBlank(result)) {
if (dep.token.equalsTo(identifierToken(Identifiers.ChangeDetectorRef))) {
if (requestingProviderType === ProviderAstType.Component) {
return this._compViewExpr.prop('ref');
} else {
return getPropertyInView(o.THIS_EXPR.prop('ref'), this.view, this.view.componentView);
}
}
}
// access regular providers on the element
if (isBlank(result)) {
result = this._instances.get(dep.token);
}
}
return result;
}
private _getDependency(requestingProviderType: ProviderAstType,
dep: CompileDiDependencyMetadata): o.Expression {
var currElement: CompileElement = this;
var result = null;
if (dep.isValue) {
result = o.literal(dep.value);
}
if (isBlank(result) && !dep.isSkipSelf) {
result = this._getLocalDependency(requestingProviderType, dep);
}
// check parent elements
while (isBlank(result) && !currElement.parent.isNull()) {
currElement = currElement.parent;
result = currElement._getLocalDependency(ProviderAstType.PublicService,
new CompileDiDependencyMetadata({token: dep.token}));
}
if (isBlank(result)) {
result = injectFromViewParentInjector(dep.token, dep.isOptional);
}
if (isBlank(result)) {
result = o.NULL_EXPR;
}
return getPropertyInView(result, this.view, currElement.view);
}
}
function createInjectInternalCondition(nodeIndex: number, childNodeCount: number,
provider: ProviderAst,
providerExpr: o.Expression): o.Statement {
var indexCondition;
if (childNodeCount > 0) {
indexCondition = o.literal(nodeIndex)
.lowerEquals(InjectMethodVars.requestNodeIndex)
.and(InjectMethodVars.requestNodeIndex.lowerEquals(
o.literal(nodeIndex + childNodeCount)));
} else {
indexCondition = o.literal(nodeIndex).identical(InjectMethodVars.requestNodeIndex);
}
return new o.IfStmt(
InjectMethodVars.token.identical(createDiTokenExpression(provider.token)).and(indexCondition),
[new o.ReturnStatement(providerExpr)]);
}
function createProviderProperty(propName: string, provider: ProviderAst,
providerValueExpressions: o.Expression[], isMulti: boolean,
isEager: boolean, compileElement: CompileElement): o.Expression {
var view = compileElement.view;
var resolvedProviderValueExpr;
var type;
if (isMulti) {
resolvedProviderValueExpr = o.literalArr(providerValueExpressions);
type = new o.ArrayType(o.DYNAMIC_TYPE);
} else {
resolvedProviderValueExpr = providerValueExpressions[0];
type = providerValueExpressions[0].type;
}
if (isBlank(type)) {
type = o.DYNAMIC_TYPE;
}
if (isEager) {
view.fields.push(new o.ClassField(propName, type));
view.createMethod.addStmt(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt());
} else {
var internalField = `_${propName}`;
view.fields.push(new o.ClassField(internalField, type));
var getter = new CompileMethod(view);
getter.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
// Note: Equals is important for JS so that it also checks the undefined case!
getter.addStmt(
new o.IfStmt(o.THIS_EXPR.prop(internalField).isBlank(),
[o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]));
getter.addStmt(new o.ReturnStatement(o.THIS_EXPR.prop(internalField)));
view.getters.push(new o.ClassGetter(propName, getter.finish(), type));
}
return o.THIS_EXPR.prop(propName);
}
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = isPresent(query.meta.read) ? query.meta.read : match;
}
}
function _convertValueToOutputAst(value: any): o.Expression {
return visitValue(value, new _ValueOutputAstTransformer(), null);
}
class _ValueOutputAstTransformer extends ValueTransformer {
visitArray(arr: any[], context: any): o.Expression {
return o.literalArr(arr.map(value => visitValue(value, this, context)));
}
visitStringMap(map: {[key: string]: any}, context: any): o.Expression {
var entries = [];
StringMapWrapper.forEach(
map, (value, key) => { entries.push([key, visitValue(value, this, context)]); });
return o.literalMap(entries);
}
visitPrimitive(value: any, context: any): o.Expression { return o.literal(value); }
visitOther(value: any, context: any): o.Expression {
if (value instanceof CompileIdentifierMetadata) {
return o.importExpr(value);
} else if (value instanceof o.Expression) {
return value;
} else {
throw new BaseException(`Illegal state: Don't now how to compile value ${value}`);
}
}
}

View File

@ -0,0 +1,75 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import * as o from '../output/output_ast';
import {TemplateAst} from '../template_ast';
import {CompileView} from './compile_view';
class _DebugState {
constructor(public nodeIndex: number, public sourceAst: TemplateAst) {}
}
var NULL_DEBUG_STATE = new _DebugState(null, null);
export class CompileMethod {
private _newState: _DebugState = NULL_DEBUG_STATE;
private _currState: _DebugState = NULL_DEBUG_STATE;
private _debugEnabled: boolean;
private _bodyStatements: o.Statement[] = [];
constructor(private _view: CompileView) {
this._debugEnabled = this._view.genConfig.genDebugInfo;
}
private _updateDebugContextIfNeeded() {
if (this._newState.nodeIndex !== this._currState.nodeIndex ||
this._newState.sourceAst !== this._currState.sourceAst) {
var expr = this._updateDebugContext(this._newState);
if (isPresent(expr)) {
this._bodyStatements.push(expr.toStmt());
}
}
}
private _updateDebugContext(newState: _DebugState): o.Expression {
this._currState = this._newState = newState;
if (this._debugEnabled) {
var sourceLocation =
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
return o.THIS_EXPR.callMethod('debug', [
o.literal(newState.nodeIndex),
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR,
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR
]);
} else {
return null;
}
}
resetDebugInfoExpr(nodeIndex: number, templateAst: TemplateAst): o.Expression {
var res = this._updateDebugContext(new _DebugState(nodeIndex, templateAst));
return isPresent(res) ? res : o.NULL_EXPR;
}
resetDebugInfo(nodeIndex: number, templateAst: TemplateAst) {
this._newState = new _DebugState(nodeIndex, templateAst);
}
addStmt(stmt: o.Statement) {
this._updateDebugContextIfNeeded();
this._bodyStatements.push(stmt);
}
addStmts(stmts: o.Statement[]) {
this._updateDebugContextIfNeeded();
ListWrapper.addAll(this._bodyStatements, stmts);
}
finish(): o.Statement[] { return this._bodyStatements; }
isEmpty(): boolean { return this._bodyStatements.length === 0; }
}

View File

@ -0,0 +1,75 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from '../output/output_ast';
import {CompileView} from './compile_view';
import {CompilePipeMetadata} from '../compile_metadata';
import {Identifiers, identifierToken} from '../identifiers';
import {injectFromViewParentInjector, createPureProxy, getPropertyInView} from './util';
class _PurePipeProxy {
constructor(public instance: o.ReadPropExpr, public argCount: number) {}
}
export class CompilePipe {
meta: CompilePipeMetadata;
instance: o.ReadPropExpr;
private _purePipeProxies: _PurePipeProxy[] = [];
constructor(public view: CompileView, name: string) {
this.meta = _findPipeMeta(view, name);
this.instance = o.THIS_EXPR.prop(`_pipe_${name}_${view.pipeCount++}`);
}
get pure(): boolean { return this.meta.pure; }
create(): void {
var deps = this.meta.type.diDeps.map((diDep) => {
if (diDep.token.equalsTo(identifierToken(Identifiers.ChangeDetectorRef))) {
return getPropertyInView(o.THIS_EXPR.prop('ref'), this.view, this.view.componentView);
}
return injectFromViewParentInjector(diDep.token, false);
});
this.view.fields.push(new o.ClassField(this.instance.name, o.importType(this.meta.type)));
this.view.createMethod.resetDebugInfo(null, null);
this.view.createMethod.addStmt(o.THIS_EXPR.prop(this.instance.name)
.set(o.importExpr(this.meta.type).instantiate(deps))
.toStmt());
this._purePipeProxies.forEach((purePipeProxy) => {
createPureProxy(
this.instance.prop('transform').callMethod(o.BuiltinMethod.bind, [this.instance]),
purePipeProxy.argCount, purePipeProxy.instance, this.view);
});
}
call(callingView: CompileView, args: o.Expression[]): o.Expression {
if (this.meta.pure) {
var purePipeProxy = new _PurePipeProxy(
o.THIS_EXPR.prop(`${this.instance.name}_${this._purePipeProxies.length}`), args.length);
this._purePipeProxies.push(purePipeProxy);
return getPropertyInView(
o.importExpr(Identifiers.castByValue)
.callFn([purePipeProxy.instance, this.instance.prop('transform')]),
callingView, this.view)
.callFn(args);
} else {
return getPropertyInView(this.instance, callingView, this.view).callMethod('transform', args);
}
}
}
function _findPipeMeta(view: CompileView, name: string): CompilePipeMetadata {
var pipeMeta: CompilePipeMetadata = null;
for (var i = view.pipeMetas.length - 1; i >= 0; i--) {
var localPipeMeta = view.pipeMetas[i];
if (localPipeMeta.name == name) {
pipeMeta = localPipeMeta;
break;
}
}
if (isBlank(pipeMeta)) {
throw new BaseException(
`Illegal state: Could not find pipe ${name} although the parser should have detected this error!`);
}
return pipeMeta;
}

View File

@ -0,0 +1,117 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import * as o from '../output/output_ast';
import {Identifiers} from '../identifiers';
import {
CompileQueryMetadata,
CompileIdentifierMetadata,
CompileTokenMap
} from '../compile_metadata';
import {CompileView} from './compile_view';
import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method';
import {getPropertyInView} from './util';
class ViewQueryValues {
constructor(public view: CompileView, public values: Array<o.Expression | ViewQueryValues>) {}
}
export class CompileQuery {
private _values: ViewQueryValues;
constructor(public meta: CompileQueryMetadata, public queryList: o.Expression,
public ownerDirectiveExpression: o.Expression, public view: CompileView) {
this._values = new ViewQueryValues(view, []);
}
addValue(value: o.Expression, view: CompileView) {
var currentView = view;
var elPath: CompileElement[] = [];
while (isPresent(currentView) && currentView !== this.view) {
var parentEl = currentView.declarationElement;
elPath.unshift(parentEl);
currentView = parentEl.view;
}
var queryListForDirtyExpr = getPropertyInView(this.queryList, view, this.view);
var viewValues = this._values;
elPath.forEach((el) => {
var last =
viewValues.values.length > 0 ? viewValues.values[viewValues.values.length - 1] : null;
if (last instanceof ViewQueryValues && last.view === el.embeddedView) {
viewValues = last;
} else {
var newViewValues = new ViewQueryValues(el.embeddedView, []);
viewValues.values.push(newViewValues);
viewValues = newViewValues;
}
});
viewValues.values.push(value);
if (elPath.length > 0) {
view.dirtyParentQueriesMethod.addStmt(
queryListForDirtyExpr.callMethod('setDirty', []).toStmt());
}
}
afterChildren(targetMethod: CompileMethod) {
var values = createQueryValues(this._values);
var updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (isPresent(this.ownerDirectiveExpression)) {
var valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList;
updateStmts.push(
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
}
if (!this.meta.first) {
updateStmts.push(this.queryList.callMethod('notifyOnChanges', []).toStmt());
}
targetMethod.addStmt(new o.IfStmt(this.queryList.prop('dirty'), updateStmts));
}
}
function createQueryValues(viewValues: ViewQueryValues): o.Expression[] {
return ListWrapper.flatten(viewValues.values.map((entry) => {
if (entry instanceof ViewQueryValues) {
return mapNestedViews(entry.view.declarationElement.appElement, entry.view,
createQueryValues(entry));
} else {
return <o.Expression>entry;
}
}));
}
function mapNestedViews(declarationAppElement: o.Expression, view: CompileView,
expressions: o.Expression[]): o.Expression {
var adjustedExpressions: o.Expression[] = expressions.map((expr) => {
return o.replaceVarInExpression(o.THIS_EXPR.name, o.variable('nestedView'), expr);
});
return declarationAppElement.callMethod('mapNestedViews', [
o.variable(view.className),
o.fn([new o.FnParam('nestedView', view.classType)],
[new o.ReturnStatement(o.literalArr(adjustedExpressions))])
]);
}
export function createQueryList(query: CompileQueryMetadata, directiveInstance: o.Expression,
propertyName: string, compileView: CompileView): o.Expression {
compileView.fields.push(new o.ClassField(propertyName, o.importType(Identifiers.QueryList)));
var expr = o.THIS_EXPR.prop(propertyName);
compileView.createMethod.addStmt(o.THIS_EXPR.prop(propertyName)
.set(o.importExpr(Identifiers.QueryList).instantiate([]))
.toStmt());
return expr;
}
export function addQueryToTokenMap(map: CompileTokenMap<CompileQuery[]>, query: CompileQuery) {
query.meta.selectors.forEach((selector) => {
var entry = map.get(selector);
if (isBlank(entry)) {
entry = [];
map.add(selector, entry);
}
entry.push(query);
});
}

View File

@ -0,0 +1,209 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper, MapWrapper} from 'angular2/src/facade/collection';
import * as o from '../output/output_ast';
import {EventHandlerVars} from './constants';
import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query';
import {NameResolver} from './expression_converter';
import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method';
import {CompilePipe} from './compile_pipe';
import {ViewType} from 'angular2/src/core/linker/view_type';
import {
CompileDirectiveMetadata,
CompilePipeMetadata,
CompileIdentifierMetadata,
CompileTokenMap
} from '../compile_metadata';
import {
getViewFactoryName,
injectFromViewParentInjector,
createDiTokenExpression,
getPropertyInView,
createPureProxy
} from './util';
import {CompilerConfig} from '../config';
import {CompileBinding} from './compile_binding';
import {Identifiers} from '../identifiers';
export class CompileView implements NameResolver {
public viewType: ViewType;
public viewQueries: CompileTokenMap<CompileQuery[]>;
public nodes: CompileNode[] = [];
// root nodes or AppElements for ViewContainers
public rootNodesOrAppElements: o.Expression[] = [];
public bindings: CompileBinding[] = [];
public classStatements: o.Statement[] = [];
public createMethod: CompileMethod;
public injectorGetMethod: CompileMethod;
public updateContentQueriesMethod: CompileMethod;
public dirtyParentQueriesMethod: CompileMethod;
public updateViewQueriesMethod: CompileMethod;
public detectChangesInInputsMethod: CompileMethod;
public detectChangesRenderPropertiesMethod: CompileMethod;
public afterContentLifecycleCallbacksMethod: CompileMethod;
public afterViewLifecycleCallbacksMethod: CompileMethod;
public destroyMethod: CompileMethod;
public eventHandlerMethods: o.ClassMethod[] = [];
public fields: o.ClassField[] = [];
public getters: o.ClassGetter[] = [];
public disposables: o.Expression[] = [];
public subscriptions: o.Expression[] = [];
public componentView: CompileView;
public purePipes = new Map<string, CompilePipe>();
public pipes: CompilePipe[] = [];
public locals = new Map<string, o.Expression>();
public className: string;
public classType: o.Type;
public viewFactory: o.ReadVarExpr;
public literalArrayCount = 0;
public literalMapCount = 0;
public pipeCount = 0;
public componentContext: o.Expression;
constructor(public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
public viewIndex: number, public declarationElement: CompileElement,
public templateVariableBindings: string[][]) {
this.createMethod = new CompileMethod(this);
this.injectorGetMethod = new CompileMethod(this);
this.updateContentQueriesMethod = new CompileMethod(this);
this.dirtyParentQueriesMethod = new CompileMethod(this);
this.updateViewQueriesMethod = new CompileMethod(this);
this.detectChangesInInputsMethod = new CompileMethod(this);
this.detectChangesRenderPropertiesMethod = new CompileMethod(this);
this.afterContentLifecycleCallbacksMethod = new CompileMethod(this);
this.afterViewLifecycleCallbacksMethod = new CompileMethod(this);
this.destroyMethod = new CompileMethod(this);
this.viewType = getViewType(component, viewIndex);
this.className = `_View_${component.type.name}${viewIndex}`;
this.classType = o.importType(new CompileIdentifierMetadata({name: this.className}));
this.viewFactory = o.variable(getViewFactoryName(component, viewIndex));
if (this.viewType === ViewType.COMPONENT || this.viewType === ViewType.HOST) {
this.componentView = this;
} else {
this.componentView = this.declarationElement.view.componentView;
}
this.componentContext =
getPropertyInView(o.THIS_EXPR.prop('context'), this, this.componentView);
var viewQueries = new CompileTokenMap<CompileQuery[]>();
if (this.viewType === ViewType.COMPONENT) {
var directiveInstance = o.THIS_EXPR.prop('context');
ListWrapper.forEachWithIndex(this.component.viewQueries, (queryMeta, queryIndex) => {
var propName = `_viewQuery_${queryMeta.selectors[0].name}_${queryIndex}`;
var queryList = createQueryList(queryMeta, directiveInstance, propName, this);
var query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
addQueryToTokenMap(viewQueries, query);
});
var constructorViewQueryCount = 0;
this.component.type.diDeps.forEach((dep) => {
if (isPresent(dep.viewQuery)) {
var queryList = o.THIS_EXPR.prop('declarationAppElement')
.prop('componentConstructorViewQueries')
.key(o.literal(constructorViewQueryCount++));
var query = new CompileQuery(dep.viewQuery, queryList, null, this);
addQueryToTokenMap(viewQueries, query);
}
});
}
this.viewQueries = viewQueries;
templateVariableBindings.forEach(
(entry) => { this.locals.set(entry[1], o.THIS_EXPR.prop('context').prop(entry[0])); });
if (!this.declarationElement.isNull()) {
this.declarationElement.setEmbeddedView(this);
}
}
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression {
var compView = this.componentView;
var pipe = compView.purePipes.get(name);
if (isBlank(pipe)) {
pipe = new CompilePipe(compView, name);
if (pipe.pure) {
compView.purePipes.set(name, pipe);
}
compView.pipes.push(pipe);
}
return pipe.call(this, [input].concat(args));
}
getLocal(name: string): o.Expression {
if (name == EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
var currView: CompileView = this;
var result = currView.locals.get(name);
while (isBlank(result) && isPresent(currView.declarationElement.view)) {
currView = currView.declarationElement.view;
result = currView.locals.get(name);
}
if (isPresent(result)) {
return getPropertyInView(result, this, currView);
} else {
return null;
}
}
createLiteralArray(values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(Identifiers.EMPTY_ARRAY);
}
var proxyExpr = o.THIS_EXPR.prop(`_arr_${this.literalArrayCount++}`);
var proxyParams: o.FnParam[] = [];
var proxyReturnEntries: o.Expression[] = [];
for (var i = 0; i < values.length; i++) {
var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push(o.variable(paramName));
}
createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))]),
values.length, proxyExpr, this);
return proxyExpr.callFn(values);
}
createLiteralMap(entries: Array<Array<string | o.Expression>>): o.Expression {
if (entries.length === 0) {
return o.importExpr(Identifiers.EMPTY_MAP);
}
var proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`);
var proxyParams: o.FnParam[] = [];
var proxyReturnEntries: Array<Array<string | o.Expression>> = [];
var values: o.Expression[] = [];
for (var i = 0; i < entries.length; i++) {
var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
values.push(<o.Expression>entries[i][1]);
}
createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))]),
entries.length, proxyExpr, this);
return proxyExpr.callFn(values);
}
afterNodes() {
this.pipes.forEach((pipe) => pipe.create());
this.viewQueries.values().forEach(
(queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod)));
}
}
function getViewType(component: CompileDirectiveMetadata, embeddedTemplateIndex: number): ViewType {
if (embeddedTemplateIndex > 0) {
return ViewType.EMBEDDED;
} else if (component.type.isHost) {
return ViewType.HOST;
} else {
return ViewType.COMPONENT;
}
}

View File

@ -0,0 +1,86 @@
import {serializeEnum, isBlank, resolveEnumToken} from 'angular2/src/facade/lang';
import {CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {
ChangeDetectorState,
ChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {ViewType} from 'angular2/src/core/linker/view_type';
import * as o from '../output/output_ast';
import {Identifiers} from '../identifiers';
function _enumExpression(classIdentifier: CompileIdentifierMetadata, value: any): o.Expression {
if (isBlank(value)) return o.NULL_EXPR;
var name = resolveEnumToken(classIdentifier.runtime, value);
return o.importExpr(new CompileIdentifierMetadata({
name: `${classIdentifier.name}.${name}`,
moduleUrl: classIdentifier.moduleUrl,
runtime: value
}));
}
export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression {
return _enumExpression(Identifiers.ViewType, value);
}
static HOST = ViewTypeEnum.fromValue(ViewType.HOST);
static COMPONENT = ViewTypeEnum.fromValue(ViewType.COMPONENT);
static EMBEDDED = ViewTypeEnum.fromValue(ViewType.EMBEDDED);
}
export class ViewEncapsulationEnum {
static fromValue(value: ViewEncapsulation): o.Expression {
return _enumExpression(Identifiers.ViewEncapsulation, value);
}
static Emulated = ViewEncapsulationEnum.fromValue(ViewEncapsulation.Emulated);
static Native = ViewEncapsulationEnum.fromValue(ViewEncapsulation.Native);
static None = ViewEncapsulationEnum.fromValue(ViewEncapsulation.None);
}
export class ChangeDetectorStateEnum {
static fromValue(value: ChangeDetectorState): o.Expression {
return _enumExpression(Identifiers.ChangeDetectorState, value);
}
static NeverChecked = ChangeDetectorStateEnum.fromValue(ChangeDetectorState.NeverChecked);
static CheckedBefore = ChangeDetectorStateEnum.fromValue(ChangeDetectorState.CheckedBefore);
static Errored = ChangeDetectorStateEnum.fromValue(ChangeDetectorState.Errored);
}
export class ChangeDetectionStrategyEnum {
static fromValue(value: ChangeDetectionStrategy): o.Expression {
return _enumExpression(Identifiers.ChangeDetectionStrategy, value);
}
static CheckOnce = ChangeDetectionStrategyEnum.fromValue(ChangeDetectionStrategy.CheckOnce);
static Checked = ChangeDetectionStrategyEnum.fromValue(ChangeDetectionStrategy.Checked);
static CheckAlways = ChangeDetectionStrategyEnum.fromValue(ChangeDetectionStrategy.CheckAlways);
static Detached = ChangeDetectionStrategyEnum.fromValue(ChangeDetectionStrategy.Detached);
static OnPush = ChangeDetectionStrategyEnum.fromValue(ChangeDetectionStrategy.OnPush);
static Default = ChangeDetectionStrategyEnum.fromValue(ChangeDetectionStrategy.Default);
}
export class ViewConstructorVars {
static viewUtils = o.variable('viewUtils');
static parentInjector = o.variable('parentInjector');
static declarationEl = o.variable('declarationEl');
}
export class ViewProperties {
static renderer = o.THIS_EXPR.prop('renderer');
static projectableNodes = o.THIS_EXPR.prop('projectableNodes');
static viewUtils = o.THIS_EXPR.prop('viewUtils');
}
export class EventHandlerVars { static event = o.variable('$event'); }
export class InjectMethodVars {
static token = o.variable('token');
static requestNodeIndex = o.variable('requestNodeIndex');
static notFoundResult = o.variable('notFoundResult');
}
export class DetectChangesVars {
static throwOnChange = o.variable(`throwOnChange`);
static changes = o.variable(`changes`);
static changed = o.variable(`changed`);
static valUnwrapper = o.variable(`valUnwrapper`);
}

View File

@ -0,0 +1,163 @@
import {isBlank, isPresent, StringWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {EventHandlerVars, ViewProperties} from './constants';
import * as o from '../output/output_ast';
import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method';
import {BoundEventAst, DirectiveAst} from '../template_ast';
import {CompileDirectiveMetadata} from '../compile_metadata';
import {convertCdStatementToIr} from './expression_converter';
import {CompileBinding} from './compile_binding';
export class CompileEventListener {
private _method: CompileMethod;
private _hasComponentHostListener: boolean = false;
private _methodName: string;
private _eventParam: o.FnParam;
private _actionResultExprs: o.Expression[] = [];
static getOrCreate(compileElement: CompileElement, eventTarget: string, eventName: string,
targetEventListeners: CompileEventListener[]): CompileEventListener {
var listener = targetEventListeners.find(listener => listener.eventTarget == eventTarget &&
listener.eventName == eventName);
if (isBlank(listener)) {
listener = new CompileEventListener(compileElement, eventTarget, eventName,
targetEventListeners.length);
targetEventListeners.push(listener);
}
return listener;
}
constructor(public compileElement: CompileElement, public eventTarget: string,
public eventName: string, listenerIndex: number) {
this._method = new CompileMethod(compileElement.view);
this._methodName =
`_handle_${santitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
this._eventParam =
new o.FnParam(EventHandlerVars.event.name,
o.importType(this.compileElement.view.genConfig.renderTypes.renderEvent));
}
addAction(hostEvent: BoundEventAst, directive: CompileDirectiveMetadata,
directiveInstance: o.Expression) {
if (isPresent(directive) && directive.isComponent) {
this._hasComponentHostListener = true;
}
this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent);
var context = isPresent(directiveInstance) ? directiveInstance :
this.compileElement.view.componentContext;
var actionStmts = convertCdStatementToIr(this.compileElement.view, context, hostEvent.handler);
var lastIndex = actionStmts.length - 1;
if (lastIndex >= 0) {
var lastStatement = actionStmts[lastIndex];
var returnExpr = convertStmtIntoExpression(lastStatement);
var preventDefaultVar = o.variable(`pd_${this._actionResultExprs.length}`);
this._actionResultExprs.push(preventDefaultVar);
if (isPresent(returnExpr)) {
// Note: We need to cast the result of the method call to dynamic,
// as it might be a void method!
actionStmts[lastIndex] =
preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false)))
.toDeclStmt(null, [o.StmtModifier.Final]);
}
}
this._method.addStmts(actionStmts);
}
finishMethod() {
var markPathToRootStart = this._hasComponentHostListener ?
this.compileElement.appElement.prop('componentView') :
o.THIS_EXPR;
var resultExpr: o.Expression = o.literal(true);
this._actionResultExprs.forEach((expr) => { resultExpr = resultExpr.and(expr); });
var stmts =
(<o.Statement[]>[markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt()])
.concat(this._method.finish())
.concat([new o.ReturnStatement(resultExpr)]);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.eventHandlerMethods.push(new o.ClassMethod(
this._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private]));
}
listenToRenderer() {
var listenExpr;
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.bind, [o.THIS_EXPR])]);
if (isPresent(this.eventTarget)) {
listenExpr = ViewProperties.renderer.callMethod(
'listenGlobal', [o.literal(this.eventTarget), o.literal(this.eventName), eventListener]);
} else {
listenExpr = ViewProperties.renderer.callMethod(
'listen', [this.compileElement.renderNode, o.literal(this.eventName), eventListener]);
}
var disposable = o.variable(`disposable_${this.compileElement.view.disposables.length}`);
this.compileElement.view.disposables.push(disposable);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.createMethod.addStmt(
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
}
listenToDirective(directiveInstance: o.Expression, observablePropName: string) {
var subscription = o.variable(`subscription_${this.compileElement.view.subscriptions.length}`);
this.compileElement.view.subscriptions.push(subscription);
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.bind, [o.THIS_EXPR])]);
this.compileElement.view.createMethod.addStmt(
subscription.set(directiveInstance.prop(observablePropName)
.callMethod(o.BuiltinMethod.SubscribeObservable, [eventListener]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
}
export function collectEventListeners(hostEvents: BoundEventAst[], dirs: DirectiveAst[],
compileElement: CompileElement): CompileEventListener[] {
var eventListeners: CompileEventListener[] = [];
hostEvents.forEach((hostEvent) => {
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
var listener = CompileEventListener.getOrCreate(compileElement, hostEvent.target,
hostEvent.name, eventListeners);
listener.addAction(hostEvent, null, null);
});
ListWrapper.forEachWithIndex(dirs, (directiveAst, i) => {
var directiveInstance = compileElement.directiveInstances[i];
directiveAst.hostEvents.forEach((hostEvent) => {
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
var listener = CompileEventListener.getOrCreate(compileElement, hostEvent.target,
hostEvent.name, eventListeners);
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
});
});
eventListeners.forEach((listener) => listener.finishMethod());
return eventListeners;
}
export function bindDirectiveOutputs(directiveAst: DirectiveAst, directiveInstance: o.Expression,
eventListeners: CompileEventListener[]) {
StringMapWrapper.forEach(directiveAst.directive.outputs, (eventName, observablePropName) => {
eventListeners.filter(listener => listener.eventName == eventName)
.forEach(
(listener) => { listener.listenToDirective(directiveInstance, observablePropName); });
});
}
export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
eventListeners.forEach(listener => listener.listenToRenderer());
}
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
if (stmt instanceof o.ExpressionStatement) {
return stmt.expr;
} else if (stmt instanceof o.ReturnStatement) {
return stmt.value;
}
return null;
}
function santitizeEventName(name: string): string {
return StringWrapper.replaceAll(name, /[^a-zA-Z_]/g, '_');
}

View File

@ -0,0 +1,252 @@
import * as cdAst from '../expression_parser/ast';
import * as o from '../output/output_ast';
import {Identifiers} from '../identifiers';
import {BaseException} from 'angular2/src/facade/exceptions';
import {isBlank, isPresent, isArray} from 'angular2/src/facade/lang';
var IMPLICIT_RECEIVER = o.variable('#implicit');
export interface NameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression;
createLiteralArray(values: o.Expression[]): o.Expression;
createLiteralMap(values: Array<Array<string | o.Expression>>): o.Expression;
}
export class ExpressionWithWrappedValueInfo {
constructor(public expression: o.Expression, public needsValueUnwrapper: boolean) {}
}
export function convertCdExpressionToIr(
nameResolver: NameResolver, implicitReceiver: o.Expression, expression: cdAst.AST,
valueUnwrapper: o.ReadVarExpr): ExpressionWithWrappedValueInfo {
var visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, valueUnwrapper);
var irAst: o.Expression = expression.visit(visitor, _Mode.Expression);
return new ExpressionWithWrappedValueInfo(irAst, visitor.needsValueUnwrapper);
}
export function convertCdStatementToIr(nameResolver: NameResolver, implicitReceiver: o.Expression,
stmt: cdAst.AST): o.Statement[] {
var visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, null);
var statements = [];
flattenStatements(stmt.visit(visitor, _Mode.Statement), statements);
return statements;
}
enum _Mode {
Statement,
Expression
}
function ensureStatementMode(mode: _Mode, ast: cdAst.AST) {
if (mode !== _Mode.Statement) {
throw new BaseException(`Expected a statement, but saw ${ast}`);
}
}
function ensureExpressionMode(mode: _Mode, ast: cdAst.AST) {
if (mode !== _Mode.Expression) {
throw new BaseException(`Expected an expression, but saw ${ast}`);
}
}
function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expression | o.Statement {
if (mode === _Mode.Statement) {
return expr.toStmt();
} else {
return expr;
}
}
class _AstToIrVisitor implements cdAst.AstVisitor {
public needsValueUnwrapper: boolean = false;
constructor(private _nameResolver: NameResolver, private _implicitReceiver: o.Expression,
private _valueUnwrapper: o.ReadVarExpr) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
var op;
switch (ast.operation) {
case '+':
op = o.BinaryOperator.Plus;
break;
case '-':
op = o.BinaryOperator.Minus;
break;
case '*':
op = o.BinaryOperator.Multiply;
break;
case '/':
op = o.BinaryOperator.Divide;
break;
case '%':
op = o.BinaryOperator.Modulo;
break;
case '&&':
op = o.BinaryOperator.And;
break;
case '||':
op = o.BinaryOperator.Or;
break;
case '==':
op = o.BinaryOperator.Equals;
break;
case '!=':
op = o.BinaryOperator.NotEquals;
break;
case '===':
op = o.BinaryOperator.Identical;
break;
case '!==':
op = o.BinaryOperator.NotIdentical;
break;
case '<':
op = o.BinaryOperator.Lower;
break;
case '>':
op = o.BinaryOperator.Bigger;
break;
case '<=':
op = o.BinaryOperator.LowerEquals;
break;
case '>=':
op = o.BinaryOperator.BiggerEquals;
break;
default:
throw new BaseException(`Unsupported operation ${ast.operation}`);
}
return convertToStatementIfNeeded(
mode, new o.BinaryOperatorExpr(op, ast.left.visit(this, _Mode.Expression),
ast.right.visit(this, _Mode.Expression)));
}
visitChain(ast: cdAst.Chain, mode: _Mode): any {
ensureStatementMode(mode, ast);
return this.visitAll(ast.expressions, mode);
}
visitConditional(ast: cdAst.Conditional, mode: _Mode): any {
var value: o.Expression = ast.condition.visit(this, _Mode.Expression);
return convertToStatementIfNeeded(
mode, value.conditional(ast.trueExp.visit(this, _Mode.Expression),
ast.falseExp.visit(this, _Mode.Expression)));
}
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
var input = ast.exp.visit(this, _Mode.Expression);
var args = this.visitAll(ast.args, _Mode.Expression);
var value = this._nameResolver.callPipe(ast.name, input, args);
this.needsValueUnwrapper = true;
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
}
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
return convertToStatementIfNeeded(mode, ast.target.visit(this, _Mode.Expression)
.callFn(this.visitAll(ast.args, _Mode.Expression)));
}
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
ensureExpressionMode(mode, ast);
return IMPLICIT_RECEIVER;
}
visitInterpolation(ast: cdAst.Interpolation, mode: _Mode): any {
ensureExpressionMode(mode, ast);
var args = [o.literal(ast.expressions.length)];
for (var i = 0; i < ast.strings.length - 1; i++) {
args.push(o.literal(ast.strings[i]));
args.push(ast.expressions[i].visit(this, _Mode.Expression));
}
args.push(o.literal(ast.strings[ast.strings.length - 1]));
return o.importExpr(Identifiers.interpolate).callFn(args);
}
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
return convertToStatementIfNeeded(
mode, ast.obj.visit(this, _Mode.Expression).key(ast.key.visit(this, _Mode.Expression)));
}
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
var obj: o.Expression = ast.obj.visit(this, _Mode.Expression);
var key: o.Expression = ast.key.visit(this, _Mode.Expression);
var value: o.Expression = ast.value.visit(this, _Mode.Expression);
return convertToStatementIfNeeded(mode, obj.key(key).set(value));
}
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
return convertToStatementIfNeeded(
mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode)));
}
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
var parts = [];
for (var i = 0; i < ast.keys.length; i++) {
parts.push([ast.keys[i], ast.values[i].visit(this, _Mode.Expression)]);
}
return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts));
}
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value));
}
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
var args = this.visitAll(ast.args, _Mode.Expression);
var result = null;
var receiver = ast.receiver.visit(this, _Mode.Expression);
if (receiver === IMPLICIT_RECEIVER) {
var varExpr = this._nameResolver.getLocal(ast.name);
if (isPresent(varExpr)) {
result = varExpr.callFn(args);
} else {
receiver = this._implicitReceiver;
}
}
if (isBlank(result)) {
result = receiver.callMethod(ast.name, args);
}
return convertToStatementIfNeeded(mode, result);
}
visitPrefixNot(ast: cdAst.PrefixNot, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.not(ast.expression.visit(this, _Mode.Expression)));
}
visitPropertyRead(ast: cdAst.PropertyRead, mode: _Mode): any {
var result = null;
var receiver = ast.receiver.visit(this, _Mode.Expression);
if (receiver === IMPLICIT_RECEIVER) {
result = this._nameResolver.getLocal(ast.name);
if (isBlank(result)) {
receiver = this._implicitReceiver;
}
}
if (isBlank(result)) {
result = receiver.prop(ast.name);
}
return convertToStatementIfNeeded(mode, result);
}
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
var receiver: o.Expression = ast.receiver.visit(this, _Mode.Expression);
if (receiver === IMPLICIT_RECEIVER) {
var varExpr = this._nameResolver.getLocal(ast.name);
if (isPresent(varExpr)) {
throw new BaseException('Cannot assign to a reference or variable!');
}
receiver = this._implicitReceiver;
}
return convertToStatementIfNeeded(
mode, receiver.prop(ast.name).set(ast.value.visit(this, _Mode.Expression)));
}
visitSafePropertyRead(ast: cdAst.SafePropertyRead, mode: _Mode): any {
var receiver = ast.receiver.visit(this, _Mode.Expression);
return convertToStatementIfNeeded(
mode, receiver.isBlank().conditional(o.NULL_EXPR, receiver.prop(ast.name)));
}
visitSafeMethodCall(ast: cdAst.SafeMethodCall, mode: _Mode): any {
var receiver = ast.receiver.visit(this, _Mode.Expression);
var args = this.visitAll(ast.args, _Mode.Expression);
return convertToStatementIfNeeded(
mode, receiver.isBlank().conditional(o.NULL_EXPR, receiver.callMethod(ast.name, args)));
}
visitAll(asts: cdAst.AST[], mode: _Mode): any { return asts.map(ast => ast.visit(this, mode)); }
visitQuote(ast: cdAst.Quote, mode: _Mode): any {
throw new BaseException('Quotes are not supported for evaluation!');
}
}
function flattenStatements(arg: any, output: o.Statement[]) {
if (isArray(arg)) {
(<any[]>arg).forEach((entry) => flattenStatements(entry, output));
} else {
output.push(arg);
}
}

View File

@ -0,0 +1,87 @@
import * as o from '../output/output_ast';
import {DetectChangesVars, ChangeDetectorStateEnum} from './constants';
import {LifecycleHooks} from 'angular2/src/core/metadata/lifecycle_hooks';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {DirectiveAst} from '../template_ast';
import {CompileElement} from './compile_element';
import {CompileView} from './compile_view';
var STATE_IS_NEVER_CHECKED =
o.THIS_EXPR.prop('cdState').identical(ChangeDetectorStateEnum.NeverChecked);
var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange);
export function bindDirectiveDetectChangesLifecycleCallbacks(
directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement) {
var view = compileElement.view;
var detectChangesInInputsMethod = view.detectChangesInInputsMethod;
var lifecycleHooks = directiveAst.directive.lifecycleHooks;
if (lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 && directiveAst.inputs.length > 0) {
detectChangesInInputsMethod.addStmt(new o.IfStmt(
DetectChangesVars.changes.notIdentical(o.NULL_EXPR),
[directiveInstance.callMethod('ngOnChanges', [DetectChangesVars.changes]).toStmt()]));
}
if (lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1) {
detectChangesInInputsMethod.addStmt(
new o.IfStmt(STATE_IS_NEVER_CHECKED.and(NOT_THROW_ON_CHANGES),
[directiveInstance.callMethod('ngOnInit', []).toStmt()]));
}
if (lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1) {
detectChangesInInputsMethod.addStmt(new o.IfStmt(
NOT_THROW_ON_CHANGES, [directiveInstance.callMethod('ngDoCheck', []).toStmt()]));
}
}
export function bindDirectiveAfterContentLifecycleCallbacks(directiveMeta: CompileDirectiveMetadata,
directiveInstance: o.Expression,
compileElement: CompileElement) {
var view = compileElement.view;
var lifecycleHooks = directiveMeta.lifecycleHooks;
var afterContentLifecycleCallbacksMethod = view.afterContentLifecycleCallbacksMethod;
afterContentLifecycleCallbacksMethod.resetDebugInfo(compileElement.nodeIndex,
compileElement.sourceAst);
if (lifecycleHooks.indexOf(LifecycleHooks.AfterContentInit) !== -1) {
afterContentLifecycleCallbacksMethod.addStmt(new o.IfStmt(
STATE_IS_NEVER_CHECKED, [directiveInstance.callMethod('ngAfterContentInit', []).toStmt()]));
}
if (lifecycleHooks.indexOf(LifecycleHooks.AfterContentChecked) !== -1) {
afterContentLifecycleCallbacksMethod.addStmt(
directiveInstance.callMethod('ngAfterContentChecked', []).toStmt());
}
}
export function bindDirectiveAfterViewLifecycleCallbacks(directiveMeta: CompileDirectiveMetadata,
directiveInstance: o.Expression,
compileElement: CompileElement) {
var view = compileElement.view;
var lifecycleHooks = directiveMeta.lifecycleHooks;
var afterViewLifecycleCallbacksMethod = view.afterViewLifecycleCallbacksMethod;
afterViewLifecycleCallbacksMethod.resetDebugInfo(compileElement.nodeIndex,
compileElement.sourceAst);
if (lifecycleHooks.indexOf(LifecycleHooks.AfterViewInit) !== -1) {
afterViewLifecycleCallbacksMethod.addStmt(new o.IfStmt(
STATE_IS_NEVER_CHECKED, [directiveInstance.callMethod('ngAfterViewInit', []).toStmt()]));
}
if (lifecycleHooks.indexOf(LifecycleHooks.AfterViewChecked) !== -1) {
afterViewLifecycleCallbacksMethod.addStmt(
directiveInstance.callMethod('ngAfterViewChecked', []).toStmt());
}
}
export function bindDirectiveDestroyLifecycleCallbacks(directiveMeta: CompileDirectiveMetadata,
directiveInstance: o.Expression,
compileElement: CompileElement) {
var onDestroyMethod = compileElement.view.destroyMethod;
onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
if (directiveMeta.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(directiveInstance.callMethod('ngOnDestroy', []).toStmt());
}
}
export function bindPipeDestroyLifecycleCallbacks(pipeMeta: CompilePipeMetadata,
pipeInstance: o.Expression, view: CompileView) {
var onDestroyMethod = view.destroyMethod;
if (pipeMeta.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(pipeInstance.callMethod('ngOnDestroy', []).toStmt());
}
}

View File

@ -0,0 +1,210 @@
import * as cdAst from '../expression_parser/ast';
import * as o from '../output/output_ast';
import {Identifiers} from '../identifiers';
import {DetectChangesVars} from './constants';
import {
BoundTextAst,
BoundElementPropertyAst,
DirectiveAst,
PropertyBindingType,
TemplateAst
} from '../template_ast';
import {isBlank, isPresent, isArray} from 'angular2/src/facade/lang';
import {CompileView} from './compile_view';
import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method';
import {LifecycleHooks} from 'angular2/src/core/metadata/lifecycle_hooks';
import {isDefaultChangeDetectionStrategy} from 'angular2/src/core/change_detection/constants';
import {camelCaseToDashCase} from '../util';
import {convertCdExpressionToIr} from './expression_converter';
import {CompileBinding} from './compile_binding';
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
}
function createCurrValueExpr(exprIndex: number): o.ReadVarExpr {
return o.variable(`currVal_${exprIndex}`);
}
function bind(view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
method: CompileMethod) {
var checkExpression =
convertCdExpressionToIr(view, context, parsedExpression, DetectChangesVars.valUnwrapper);
if (isBlank(checkExpression.expression)) {
// e.g. an empty expression was given
return;
}
// private is fine here as no child view will reference the cached value...
view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
view.createMethod.addStmt(
o.THIS_EXPR.prop(fieldExpr.name).set(o.importExpr(Identifiers.uninitialized)).toStmt());
if (checkExpression.needsValueUnwrapper) {
var initValueUnwrapperStmt = DetectChangesVars.valUnwrapper.callMethod('reset', []).toStmt();
method.addStmt(initValueUnwrapperStmt);
}
method.addStmt(
currValExpr.set(checkExpression.expression).toDeclStmt(null, [o.StmtModifier.Final]));
var condition: o.Expression =
o.importExpr(Identifiers.checkBinding)
.callFn([DetectChangesVars.throwOnChange, fieldExpr, currValExpr]);
if (checkExpression.needsValueUnwrapper) {
condition = DetectChangesVars.valUnwrapper.prop('hasWrappedValue').or(condition);
}
method.addStmt(new o.IfStmt(
condition,
actions.concat([<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(currValExpr).toStmt()])));
}
export function bindRenderText(boundText: BoundTextAst, compileNode: CompileNode,
view: CompileView) {
var bindingIndex = view.bindings.length;
view.bindings.push(new CompileBinding(compileNode, boundText));
var currValExpr = createCurrValueExpr(bindingIndex);
var valueField = createBindFieldExpr(bindingIndex);
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText);
bind(view, currValExpr, valueField, boundText.value, view.componentContext,
[
o.THIS_EXPR.prop('renderer')
.callMethod('setText', [compileNode.renderNode, currValExpr])
.toStmt()
],
view.detectChangesRenderPropertiesMethod);
}
function bindAndWriteToRenderer(boundProps: BoundElementPropertyAst[], context: o.Expression,
compileElement: CompileElement) {
var view = compileElement.view;
var renderNode = compileElement.renderNode;
boundProps.forEach((boundProp) => {
var bindingIndex = view.bindings.length;
view.bindings.push(new CompileBinding(compileElement, boundProp));
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
var fieldExpr = createBindFieldExpr(bindingIndex);
var currValExpr = createCurrValueExpr(bindingIndex);
var renderMethod: string;
var renderValue: o.Expression = currValExpr;
var updateStmts = [];
switch (boundProp.type) {
case PropertyBindingType.Property:
renderMethod = 'setElementProperty';
if (view.genConfig.logBindingUpdate) {
updateStmts.push(logBindingUpdateStmt(renderNode, boundProp.name, currValExpr));
}
break;
case PropertyBindingType.Attribute:
renderMethod = 'setElementAttribute';
renderValue =
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
break;
case PropertyBindingType.Class:
renderMethod = 'setElementClass';
break;
case PropertyBindingType.Style:
renderMethod = 'setElementStyle';
var strValue: o.Expression = renderValue.callMethod('toString', []);
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
break;
}
updateStmts.push(
o.THIS_EXPR.prop('renderer')
.callMethod(renderMethod, [renderNode, o.literal(boundProp.name), renderValue])
.toStmt());
bind(view, currValExpr, fieldExpr, boundProp.value, context, updateStmts,
view.detectChangesRenderPropertiesMethod);
});
}
export function bindRenderInputs(boundProps: BoundElementPropertyAst[],
compileElement: CompileElement): void {
bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement);
}
export function bindDirectiveHostProps(directiveAst: DirectiveAst, directiveInstance: o.Expression,
compileElement: CompileElement): void {
bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement);
}
export function bindDirectiveInputs(directiveAst: DirectiveAst, directiveInstance: o.Expression,
compileElement: CompileElement) {
if (directiveAst.inputs.length === 0) {
return;
}
var view = compileElement.view;
var detectChangesInInputsMethod = view.detectChangesInInputsMethod;
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
var lifecycleHooks = directiveAst.directive.lifecycleHooks;
var calcChangesMap = lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1;
var isOnPushComp = directiveAst.directive.isComponent &&
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
if (calcChangesMap) {
detectChangesInInputsMethod.addStmt(DetectChangesVars.changes.set(o.NULL_EXPR).toStmt());
}
if (isOnPushComp) {
detectChangesInInputsMethod.addStmt(DetectChangesVars.changed.set(o.literal(false)).toStmt());
}
directiveAst.inputs.forEach((input) => {
var bindingIndex = view.bindings.length;
view.bindings.push(new CompileBinding(compileElement, input));
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
var fieldExpr = createBindFieldExpr(bindingIndex);
var currValExpr = createCurrValueExpr(bindingIndex);
var statements: o.Statement[] =
[directiveInstance.prop(input.directiveName).set(currValExpr).toStmt()];
if (calcChangesMap) {
statements.push(new o.IfStmt(DetectChangesVars.changes.identical(o.NULL_EXPR), [
DetectChangesVars.changes.set(o.literalMap([], new o.MapType(
o.importType(Identifiers.SimpleChange))))
.toStmt()
]));
statements.push(
DetectChangesVars.changes.key(o.literal(input.directiveName))
.set(o.importExpr(Identifiers.SimpleChange).instantiate([fieldExpr, currValExpr]))
.toStmt());
}
if (isOnPushComp) {
statements.push(DetectChangesVars.changed.set(o.literal(true)).toStmt());
}
if (view.genConfig.logBindingUpdate) {
statements.push(
logBindingUpdateStmt(compileElement.renderNode, input.directiveName, currValExpr));
}
bind(view, currValExpr, fieldExpr, input.value, view.componentContext, statements,
detectChangesInInputsMethod);
});
if (isOnPushComp) {
detectChangesInInputsMethod.addStmt(new o.IfStmt(DetectChangesVars.changed, [
compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', [])
.toStmt()
]));
}
}
function logBindingUpdateStmt(renderNode: o.Expression, propName: string,
value: o.Expression): o.Statement {
return o.THIS_EXPR.prop('renderer')
.callMethod('setBindingDebugInfo',
[
renderNode,
o.literal(`ng-reflect-${camelCaseToDashCase(propName)}`),
value.isBlank().conditional(o.NULL_EXPR, value.callMethod('toString', []))
])
.toStmt();
}

View File

@ -0,0 +1,99 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from '../output/output_ast';
import {
CompileTokenMetadata,
CompileDirectiveMetadata,
CompileIdentifierMetadata
} from '../compile_metadata';
import {CompileView} from './compile_view';
import {Identifiers} from '../identifiers';
export function getPropertyInView(property: o.Expression, callingView: CompileView,
definedView: CompileView): o.Expression {
if (callingView === definedView) {
return property;
} else {
var viewProp: o.Expression = o.THIS_EXPR;
var currView: CompileView = callingView;
while (currView !== definedView && isPresent(currView.declarationElement.view)) {
currView = currView.declarationElement.view;
viewProp = viewProp.prop('parent');
}
if (currView !== definedView) {
throw new BaseException(
`Internal error: Could not calculate a property in a parent view: ${property}`);
}
if (property instanceof o.ReadPropExpr) {
let readPropExpr: o.ReadPropExpr = property;
// Note: Don't cast for members of the AppView base class...
if (definedView.fields.some((field) => field.name == readPropExpr.name) ||
definedView.getters.some((field) => field.name == readPropExpr.name)) {
viewProp = viewProp.cast(definedView.classType);
}
}
return o.replaceVarInExpression(o.THIS_EXPR.name, viewProp, property);
}
}
export function injectFromViewParentInjector(token: CompileTokenMetadata,
optional: boolean): o.Expression {
var args = [createDiTokenExpression(token)];
if (optional) {
args.push(o.NULL_EXPR);
}
return o.THIS_EXPR.prop('parentInjector').callMethod('get', args);
}
export function getViewFactoryName(component: CompileDirectiveMetadata,
embeddedTemplateIndex: number): string {
return `viewFactory_${component.type.name}${embeddedTemplateIndex}`;
}
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (isPresent(token.value)) {
return o.literal(token.value);
} else if (token.identifierIsInstance) {
return o.importExpr(token.identifier)
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
} else {
return o.importExpr(token.identifier);
}
}
export function createFlatArray(expressions: o.Expression[]): o.Expression {
var lastNonArrayExpressions = [];
var result: o.Expression = o.literalArr([]);
for (var i = 0; i < expressions.length; i++) {
var expr = expressions[i];
if (expr.type instanceof o.ArrayType) {
if (lastNonArrayExpressions.length > 0) {
result =
result.callMethod(o.BuiltinMethod.ConcatArray, [o.literalArr(lastNonArrayExpressions)]);
lastNonArrayExpressions = [];
}
result = result.callMethod(o.BuiltinMethod.ConcatArray, [expr]);
} else {
lastNonArrayExpressions.push(expr);
}
}
if (lastNonArrayExpressions.length > 0) {
result =
result.callMethod(o.BuiltinMethod.ConcatArray, [o.literalArr(lastNonArrayExpressions)]);
}
return result;
}
export function createPureProxy(fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr,
view: CompileView) {
view.fields.push(new o.ClassField(pureProxyProp.name, null));
var pureProxyId =
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
if (isBlank(pureProxyId)) {
throw new BaseException(`Unsupported number of argument for pure functions: ${argCount}`);
}
view.createMethod.addStmt(
o.THIS_EXPR.prop(pureProxyProp.name).set(o.importExpr(pureProxyId).callFn([fn])).toStmt());
}

View File

@ -0,0 +1,121 @@
import {
ListWrapper,
} from 'angular2/src/facade/collection';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
ReferenceAst,
VariableAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll,
PropertyBindingType,
ProviderAst
} from '../template_ast';
import {
bindRenderText,
bindRenderInputs,
bindDirectiveInputs,
bindDirectiveHostProps
} from './property_binder';
import {bindRenderOutputs, collectEventListeners, bindDirectiveOutputs} from './event_binder';
import {
bindDirectiveAfterContentLifecycleCallbacks,
bindDirectiveAfterViewLifecycleCallbacks,
bindDirectiveDestroyLifecycleCallbacks,
bindPipeDestroyLifecycleCallbacks,
bindDirectiveDetectChangesLifecycleCallbacks
} from './lifecycle_binder';
import {CompileView} from './compile_view';
import {CompileElement, CompileNode} from './compile_element';
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void {
var visitor = new ViewBinderVisitor(view);
templateVisitAll(visitor, parsedTemplate);
view.pipes.forEach(
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
}
class ViewBinderVisitor implements TemplateAstVisitor {
private _nodeIndex: number = 0;
constructor(public view: CompileView) {}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
var node = this.view.nodes[this._nodeIndex++];
bindRenderText(ast, node, this.view);
return null;
}
visitText(ast: TextAst, parent: CompileElement): any {
this._nodeIndex++;
return null;
}
visitNgContent(ast: NgContentAst, parent: CompileElement): any { return null; }
visitElement(ast: ElementAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement);
bindRenderInputs(ast.inputs, compileElement);
bindRenderOutputs(eventListeners);
ListWrapper.forEachWithIndex(ast.directives, (directiveAst, index) => {
var directiveInstance = compileElement.directiveInstances[index];
bindDirectiveInputs(directiveAst, directiveInstance, compileElement);
bindDirectiveDetectChangesLifecycleCallbacks(directiveAst, directiveInstance, compileElement);
bindDirectiveHostProps(directiveAst, directiveInstance, compileElement);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
});
templateVisitAll(this, ast.children, compileElement);
// afterContent and afterView lifecycles need to be called bottom up
// so that children are notified before parents
ListWrapper.forEachWithIndex(ast.directives, (directiveAst, index) => {
var directiveInstance = compileElement.directiveInstances[index];
bindDirectiveAfterContentLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement);
bindDirectiveAfterViewLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement);
bindDirectiveDestroyLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement);
});
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement);
ListWrapper.forEachWithIndex(ast.directives, (directiveAst, index) => {
var directiveInstance = compileElement.directiveInstances[index];
bindDirectiveInputs(directiveAst, directiveInstance, compileElement);
bindDirectiveDetectChangesLifecycleCallbacks(directiveAst, directiveInstance, compileElement);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
bindDirectiveAfterContentLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement);
bindDirectiveAfterViewLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement);
bindDirectiveDestroyLifecycleCallbacks(directiveAst.directive, directiveInstance,
compileElement);
});
bindView(compileElement.embeddedView, ast.children);
return null;
}
visitAttr(ast: AttrAst, ctx: any): any { return null; }
visitDirective(ast: DirectiveAst, ctx: any): any { return null; }
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
return null;
}
visitReference(ast: ReferenceAst, ctx: any): any { return null; }
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
}

View File

@ -0,0 +1,580 @@
import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper, SetWrapper} from 'angular2/src/facade/collection';
import * as o from '../output/output_ast';
import {Identifiers, identifierToken} from '../identifiers';
import {
ViewConstructorVars,
InjectMethodVars,
DetectChangesVars,
ViewTypeEnum,
ViewEncapsulationEnum,
ChangeDetectionStrategyEnum,
ViewProperties
} from './constants';
import {
ChangeDetectionStrategy,
isDefaultChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';
import {CompileView} from './compile_view';
import {CompileElement, CompileNode} from './compile_element';
import {
TemplateAst,
TemplateAstVisitor,
NgContentAst,
EmbeddedTemplateAst,
ElementAst,
ReferenceAst,
VariableAst,
BoundEventAst,
BoundElementPropertyAst,
AttrAst,
BoundTextAst,
TextAst,
DirectiveAst,
BoundDirectivePropertyAst,
templateVisitAll,
PropertyBindingType,
ProviderAst
} from '../template_ast';
import {getViewFactoryName, createFlatArray, createDiTokenExpression} from './util';
import {ViewType} from 'angular2/src/core/linker/view_type';
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
import {
CompileIdentifierMetadata,
CompileDirectiveMetadata,
CompileTokenMetadata
} from '../compile_metadata';
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style';
var parentRenderNodeVar = o.variable('parentRenderNode');
var rootSelectorVar = o.variable('rootSelector');
export class ViewCompileDependency {
constructor(public comp: CompileDirectiveMetadata,
public factoryPlaceholder: CompileIdentifierMetadata) {}
}
export function buildView(view: CompileView, template: TemplateAst[],
targetDependencies: ViewCompileDependency[]): number {
var builderVisitor = new ViewBuilderVisitor(view, targetDependencies);
templateVisitAll(builderVisitor, template, view.declarationElement.isNull() ?
view.declarationElement :
view.declarationElement.parent);
return builderVisitor.nestedViewCount;
}
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
view.afterNodes();
createViewTopLevelStmts(view, targetStatements);
view.nodes.forEach((node) => {
if (node instanceof CompileElement && node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
}
});
}
class ViewBuilderVisitor implements TemplateAstVisitor {
nestedViewCount: number = 0;
constructor(public view: CompileView, public targetDependencies: ViewCompileDependency[]) {}
private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; }
private _addRootNodeAndProject(node: CompileNode, ngContentIndex: number,
parent: CompileElement) {
var vcAppEl =
(node instanceof CompileElement && node.hasViewContainer) ? node.appElement : null;
if (this._isRootNode(parent)) {
// store appElement as root node only for ViewContainers
if (this.view.viewType !== ViewType.COMPONENT) {
this.view.rootNodesOrAppElements.push(isPresent(vcAppEl) ? vcAppEl : node.renderNode);
}
} else if (isPresent(parent.component) && isPresent(ngContentIndex)) {
parent.addContentNode(ngContentIndex, isPresent(vcAppEl) ? vcAppEl : node.renderNode);
}
}
private _getParentRenderNode(parent: CompileElement): o.Expression {
if (this._isRootNode(parent)) {
if (this.view.viewType === ViewType.COMPONENT) {
return parentRenderNodeVar;
} else {
// root node of an embedded/host view
return o.NULL_EXPR;
}
} else {
return isPresent(parent.component) &&
parent.component.template.encapsulation !== ViewEncapsulation.Native ?
o.NULL_EXPR :
parent.renderNode;
}
}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
return this._visitText(ast, '', ast.ngContentIndex, parent);
}
visitText(ast: TextAst, parent: CompileElement): any {
return this._visitText(ast, ast.value, ast.ngContentIndex, parent);
}
private _visitText(ast: TemplateAst, value: string, ngContentIndex: number,
parent: CompileElement): o.Expression {
var fieldName = `_text_${this.view.nodes.length}`;
this.view.fields.push(
new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderText)));
var renderNode = o.THIS_EXPR.prop(fieldName);
var compileNode = new CompileNode(parent, this.view, this.view.nodes.length, renderNode, ast);
var createRenderNode =
o.THIS_EXPR.prop(fieldName)
.set(ViewProperties.renderer.callMethod(
'createText',
[
this._getParentRenderNode(parent),
o.literal(value),
this.view.createMethod.resetDebugInfoExpr(this.view.nodes.length, ast)
]))
.toStmt();
this.view.nodes.push(compileNode);
this.view.createMethod.addStmt(createRenderNode);
this._addRootNodeAndProject(compileNode, ngContentIndex, parent);
return renderNode;
}
visitNgContent(ast: NgContentAst, parent: CompileElement): any {
// the projected nodes originate from a different view, so we don't
// have debug information for them...
this.view.createMethod.resetDebugInfo(null, ast);
var parentRenderNode = this._getParentRenderNode(parent);
var nodesExpression = ViewProperties.projectableNodes.key(
o.literal(ast.index),
new o.ArrayType(o.importType(this.view.genConfig.renderTypes.renderNode)));
if (parentRenderNode !== o.NULL_EXPR) {
this.view.createMethod.addStmt(
ViewProperties.renderer.callMethod(
'projectNodes',
[
parentRenderNode,
o.importExpr(Identifiers.flattenNestedViewRenderNodes)
.callFn([nodesExpression])
])
.toStmt());
} else if (this._isRootNode(parent)) {
if (this.view.viewType !== ViewType.COMPONENT) {
// store root nodes only for embedded/host views
this.view.rootNodesOrAppElements.push(nodesExpression);
}
} else {
if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) {
parent.addContentNode(ast.ngContentIndex, nodesExpression);
}
}
return null;
}
visitElement(ast: ElementAst, parent: CompileElement): any {
var nodeIndex = this.view.nodes.length;
var createRenderNodeExpr;
var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast);
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
createRenderNodeExpr = o.THIS_EXPR.callMethod(
'selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]);
} else {
createRenderNodeExpr = ViewProperties.renderer.callMethod(
'createElement',
[this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]);
}
var fieldName = `_el_${nodeIndex}`;
this.view.fields.push(
new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderElement)));
this.view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName).set(createRenderNodeExpr).toStmt());
var renderNode = o.THIS_EXPR.prop(fieldName);
var directives = ast.directives.map(directiveAst => directiveAst.directive);
var component = directives.find(directive => directive.isComponent);
var htmlAttrs = _readHtmlAttrs(ast.attrs);
var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives);
for (var i = 0; i < attrNameAndValues.length; i++) {
var attrName = attrNameAndValues[i][0];
var attrValue = attrNameAndValues[i][1];
this.view.createMethod.addStmt(
ViewProperties.renderer.callMethod(
'setElementAttribute',
[renderNode, o.literal(attrName), o.literal(attrValue)])
.toStmt());
}
var compileElement =
new CompileElement(parent, this.view, nodeIndex, renderNode, ast, component, directives,
ast.providers, ast.hasViewContainer, false, ast.references);
this.view.nodes.push(compileElement);
var compViewExpr: o.ReadVarExpr = null;
if (isPresent(component)) {
var nestedComponentIdentifier =
new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)});
this.targetDependencies.push(new ViewCompileDependency(component, nestedComponentIdentifier));
compViewExpr = o.variable(`compView_${nodeIndex}`);
compileElement.setComponentView(compViewExpr);
this.view.createMethod.addStmt(compViewExpr.set(o.importExpr(nestedComponentIdentifier)
.callFn([
ViewProperties.viewUtils,
compileElement.injector,
compileElement.appElement
]))
.toDeclStmt());
}
compileElement.beforeChildren();
this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent);
templateVisitAll(this, ast.children, compileElement);
compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1);
if (isPresent(compViewExpr)) {
var codeGenContentNodes;
if (this.view.component.type.isHost) {
codeGenContentNodes = ViewProperties.projectableNodes;
} else {
codeGenContentNodes = o.literalArr(
compileElement.contentNodesByNgContentIndex.map(nodes => createFlatArray(nodes)));
}
this.view.createMethod.addStmt(
compViewExpr.callMethod('create',
[compileElement.getComponent(), codeGenContentNodes, o.NULL_EXPR])
.toStmt());
}
return null;
}
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
var nodeIndex = this.view.nodes.length;
var fieldName = `_anchor_${nodeIndex}`;
this.view.fields.push(
new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderComment)));
this.view.createMethod.addStmt(
o.THIS_EXPR.prop(fieldName)
.set(ViewProperties.renderer.callMethod(
'createTemplateAnchor',
[
this._getParentRenderNode(parent),
this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast)
]))
.toStmt());
var renderNode = o.THIS_EXPR.prop(fieldName);
var templateVariableBindings = ast.variables.map(
varAst => [varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR, varAst.name]);
var directives = ast.directives.map(directiveAst => directiveAst.directive);
var compileElement =
new CompileElement(parent, this.view, nodeIndex, renderNode, ast, null, directives,
ast.providers, ast.hasViewContainer, true, ast.references);
this.view.nodes.push(compileElement);
this.nestedViewCount++;
var embeddedView = new CompileView(
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
this.view.viewIndex + this.nestedViewCount, compileElement, templateVariableBindings);
this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
compileElement.beforeChildren();
this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent);
compileElement.afterChildren(0);
return null;
}
visitAttr(ast: AttrAst, ctx: any): any { return null; }
visitDirective(ast: DirectiveAst, ctx: any): any { return null; }
visitEvent(ast: BoundEventAst, eventTargetAndNames: Map<string, BoundEventAst>): any {
return null;
}
visitReference(ast: ReferenceAst, ctx: any): any { return null; }
visitVariable(ast: VariableAst, ctx: any): any { return null; }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
}
function _mergeHtmlAndDirectiveAttrs(declaredHtmlAttrs: {[key: string]: string},
directives: CompileDirectiveMetadata[]): string[][] {
var result: {[key: string]: string} = {};
StringMapWrapper.forEach(declaredHtmlAttrs, (value, key) => { result[key] = value; });
directives.forEach(directiveMeta => {
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
var prevValue = result[name];
result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
});
});
return mapToKeyValueArray(result);
}
function _readHtmlAttrs(attrs: AttrAst[]): {[key: string]: string} {
var htmlAttrs: {[key: string]: string} = {};
attrs.forEach((ast) => { htmlAttrs[ast.name] = ast.value; });
return htmlAttrs;
}
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
return `${attrValue1} ${attrValue2}`;
} else {
return attrValue2;
}
}
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
var entryArray = [];
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
// We need to sort to get a defined output order
// for tests and for caching generated artifacts...
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
var keyValueArray = [];
entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); });
return keyValueArray;
}
function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statement[]) {
var nodeDebugInfosVar: o.Expression = o.NULL_EXPR;
if (view.genConfig.genDebugInfo) {
nodeDebugInfosVar = o.variable(`nodeDebugInfos_${view.component.type.name}${view.viewIndex}`);
targetStatements.push(
(<o.ReadVarExpr>nodeDebugInfosVar)
.set(o.literalArr(view.nodes.map(createStaticNodeDebugInfo),
new o.ArrayType(new o.ExternalType(Identifiers.StaticNodeDebugInfo),
[o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
var renderCompTypeVar: o.ReadVarExpr = o.variable(`renderType_${view.component.type.name}`);
if (view.viewIndex === 0) {
targetStatements.push(renderCompTypeVar.set(o.NULL_EXPR)
.toDeclStmt(o.importType(Identifiers.RenderComponentType)));
}
var viewClass = createViewClass(view, renderCompTypeVar, nodeDebugInfosVar);
targetStatements.push(viewClass);
targetStatements.push(createViewFactory(view, viewClass, renderCompTypeVar));
}
function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
var compileElement = node instanceof CompileElement ? node : null;
var providerTokens: o.Expression[] = [];
var componentToken: o.Expression = o.NULL_EXPR;
var varTokenEntries = [];
if (isPresent(compileElement)) {
providerTokens = compileElement.getProviderTokens();
if (isPresent(compileElement.component)) {
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
}
StringMapWrapper.forEach(compileElement.referenceTokens, (token, varName) => {
varTokenEntries.push(
[varName, isPresent(token) ? createDiTokenExpression(token) : o.NULL_EXPR]);
});
}
return o.importExpr(Identifiers.StaticNodeDebugInfo)
.instantiate(
[
o.literalArr(providerTokens, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])),
componentToken,
o.literalMap(varTokenEntries, new o.MapType(o.DYNAMIC_TYPE, [o.TypeModifier.Const]))
],
o.importType(Identifiers.StaticNodeDebugInfo, null, [o.TypeModifier.Const]));
}
function createViewClass(view: CompileView, renderCompTypeVar: o.ReadVarExpr,
nodeDebugInfosVar: o.Expression): o.ClassStmt {
var viewConstructorArgs = [
new o.FnParam(ViewConstructorVars.viewUtils.name, o.importType(Identifiers.ViewUtils)),
new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)),
new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement))
];
var superConstructorArgs = [
o.variable(view.className),
renderCompTypeVar,
ViewTypeEnum.fromValue(view.viewType),
ViewConstructorVars.viewUtils,
ViewConstructorVars.parentInjector,
ViewConstructorVars.declarationEl,
ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view))
];
if (view.genConfig.genDebugInfo) {
superConstructorArgs.push(nodeDebugInfosVar);
}
var viewConstructor = new o.ClassMethod(null, viewConstructorArgs,
[o.SUPER_EXPR.callFn(superConstructorArgs).toStmt()]);
var viewMethods = [
new o.ClassMethod('createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
generateCreateMethod(view), o.importType(Identifiers.AppElement)),
new o.ClassMethod(
'injectorGetInternal',
[
new o.FnParam(InjectMethodVars.token.name, o.DYNAMIC_TYPE),
// Note: Can't use o.INT_TYPE here as the method in AppView uses number
new o.FnParam(InjectMethodVars.requestNodeIndex.name, o.NUMBER_TYPE),
new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE)
],
addReturnValuefNotEmpty(view.injectorGetMethod.finish(), InjectMethodVars.notFoundResult),
o.DYNAMIC_TYPE),
new o.ClassMethod('detectChangesInternal',
[new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)],
generateDetectChangesMethod(view)),
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish())
].concat(view.eventHandlerMethods);
var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView;
var viewClass = new o.ClassStmt(view.className, o.importExpr(superClass, [getContextType(view)]),
view.fields, view.getters, viewConstructor,
viewMethods.filter((method) => method.body.length > 0));
return viewClass;
}
function createViewFactory(view: CompileView, viewClass: o.ClassStmt,
renderCompTypeVar: o.ReadVarExpr): o.Statement {
var viewFactoryArgs = [
new o.FnParam(ViewConstructorVars.viewUtils.name, o.importType(Identifiers.ViewUtils)),
new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)),
new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement))
];
var initRenderCompTypeStmts = [];
var templateUrlInfo;
if (view.component.template.templateUrl == view.component.type.moduleUrl) {
templateUrlInfo =
`${view.component.type.moduleUrl} class ${view.component.type.name} - inline template`;
} else {
templateUrlInfo = view.component.template.templateUrl;
}
if (view.viewIndex === 0) {
initRenderCompTypeStmts = [
new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR),
[
renderCompTypeVar.set(ViewConstructorVars
.viewUtils.callMethod('createRenderComponentType',
[
o.literal(templateUrlInfo),
o.literal(view.component
.template.ngContentSelectors.length),
ViewEncapsulationEnum
.fromValue(view.component.template.encapsulation),
view.styles
]))
.toStmt()
])
];
}
return o.fn(viewFactoryArgs, initRenderCompTypeStmts.concat([
new o.ReturnStatement(o.variable(viewClass.name)
.instantiate(viewClass.constructorMethod.params.map(
(param) => o.variable(param.name))))
]),
o.importType(Identifiers.AppView, [getContextType(view)]))
.toDeclStmt(view.viewFactory.name, [o.StmtModifier.Final]);
}
function generateCreateMethod(view: CompileView): o.Statement[] {
var parentRenderNodeExpr: o.Expression = o.NULL_EXPR;
var parentRenderNodeStmts = [];
if (view.viewType === ViewType.COMPONENT) {
parentRenderNodeExpr = ViewProperties.renderer.callMethod(
'createViewRoot', [o.THIS_EXPR.prop('declarationAppElement').prop('nativeElement')]);
parentRenderNodeStmts = [
parentRenderNodeVar.set(parentRenderNodeExpr)
.toDeclStmt(o.importType(view.genConfig.renderTypes.renderNode), [o.StmtModifier.Final])
];
}
var resultExpr: o.Expression;
if (view.viewType === ViewType.HOST) {
resultExpr = (<CompileElement>view.nodes[0]).appElement;
} else {
resultExpr = o.NULL_EXPR;
}
return parentRenderNodeStmts.concat(view.createMethod.finish())
.concat([
o.THIS_EXPR.callMethod('init',
[
createFlatArray(view.rootNodesOrAppElements),
o.literalArr(view.nodes.map(node => node.renderNode)),
o.literalArr(view.disposables),
o.literalArr(view.subscriptions)
])
.toStmt(),
new o.ReturnStatement(resultExpr)
]);
}
function generateDetectChangesMethod(view: CompileView): o.Statement[] {
var stmts = [];
if (view.detectChangesInInputsMethod.isEmpty() && view.updateContentQueriesMethod.isEmpty() &&
view.afterContentLifecycleCallbacksMethod.isEmpty() &&
view.detectChangesRenderPropertiesMethod.isEmpty() &&
view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) {
return stmts;
}
ListWrapper.addAll(stmts, view.detectChangesInInputsMethod.finish());
stmts.push(
o.THIS_EXPR.callMethod('detectContentChildrenChanges', [DetectChangesVars.throwOnChange])
.toStmt());
var afterContentStmts = view.updateContentQueriesMethod.finish().concat(
view.afterContentLifecycleCallbacksMethod.finish());
if (afterContentStmts.length > 0) {
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts));
}
ListWrapper.addAll(stmts, view.detectChangesRenderPropertiesMethod.finish());
stmts.push(o.THIS_EXPR.callMethod('detectViewChildrenChanges', [DetectChangesVars.throwOnChange])
.toStmt());
var afterViewStmts =
view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());
if (afterViewStmts.length > 0) {
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterViewStmts));
}
var varStmts = [];
var readVars = o.findReadVarNames(stmts);
if (SetWrapper.has(readVars, DetectChangesVars.changed.name)) {
varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
}
if (SetWrapper.has(readVars, DetectChangesVars.changes.name)) {
varStmts.push(DetectChangesVars.changes.set(o.NULL_EXPR)
.toDeclStmt(new o.MapType(o.importType(Identifiers.SimpleChange))));
}
if (SetWrapper.has(readVars, DetectChangesVars.valUnwrapper.name)) {
varStmts.push(
DetectChangesVars.valUnwrapper.set(o.importExpr(Identifiers.ValueUnwrapper).instantiate([]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
return varStmts.concat(stmts);
}
function addReturnValuefNotEmpty(statements: o.Statement[], value: o.Expression): o.Statement[] {
if (statements.length > 0) {
return statements.concat([new o.ReturnStatement(value)]);
} else {
return statements;
}
}
function getContextType(view: CompileView): o.Type {
if (view.viewType === ViewType.COMPONENT) {
return o.importType(view.component.type);
}
return o.DYNAMIC_TYPE;
}
function getChangeDetectionMode(view: CompileView): ChangeDetectionStrategy {
var mode: ChangeDetectionStrategy;
if (view.viewType === ViewType.COMPONENT) {
mode = isDefaultChangeDetectionStrategy(view.component.changeDetection) ?
ChangeDetectionStrategy.CheckAlways :
ChangeDetectionStrategy.CheckOnce;
} else {
mode = ChangeDetectionStrategy.CheckAlways;
}
return mode;
}

View File

@ -0,0 +1,37 @@
import {Injectable} from 'angular2/src/core/di';
import * as o from '../output/output_ast';
import {CompileElement} from './compile_element';
import {CompileView} from './compile_view';
import {buildView, finishView, ViewCompileDependency} from './view_builder';
import {bindView} from './view_binder';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {TemplateAst} from '../template_ast';
import {CompilerConfig} from '../config';
export class ViewCompileResult {
constructor(public statements: o.Statement[], public viewFactoryVar: string,
public dependencies: ViewCompileDependency[]) {}
}
@Injectable()
export class ViewCompiler {
constructor(private _genConfig: CompilerConfig) {}
compileComponent(component: CompileDirectiveMetadata, template: TemplateAst[],
styles: o.Expression, pipes: CompilePipeMetadata[]): ViewCompileResult {
var statements = [];
var dependencies = [];
var view = new CompileView(component, this._genConfig, pipes, styles, 0,
CompileElement.createNull(), []);
buildView(view, template, dependencies);
// Need to separate binding from creation to be able to refer to
// variables that have been declared after usage.
bindView(view, template);
finishView(view, statements);
return new ViewCompileResult(statements, view.viewFactory.name, dependencies);
}
}

View File

@ -0,0 +1,111 @@
import {Injectable} from 'angular2/src/core/di';
import {ViewMetadata} from 'angular2/src/core/metadata/view';
import {ComponentMetadata} from 'angular2/src/core/metadata/directives';
import {Type, stringify, isBlank, isPresent} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {Map} from 'angular2/src/facade/collection';
import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
import {reflector} from 'angular2/src/core/reflection/reflection';
/**
* Resolves types to {@link ViewMetadata}.
*/
@Injectable()
export class ViewResolver {
private _reflector: ReflectorReader;
/** @internal */
_cache = new Map<Type, ViewMetadata>();
constructor(_reflector?: ReflectorReader) {
if (isPresent(_reflector)) {
this._reflector = _reflector;
} else {
this._reflector = reflector;
}
}
resolve(component: Type): ViewMetadata {
var view = this._cache.get(component);
if (isBlank(view)) {
view = this._resolve(component);
this._cache.set(component, view);
}
return view;
}
/** @internal */
_resolve(component: Type): ViewMetadata {
var compMeta: ComponentMetadata;
var viewMeta: ViewMetadata;
this._reflector.annotations(component).forEach(m => {
if (m instanceof ViewMetadata) {
viewMeta = m;
}
if (m instanceof ComponentMetadata) {
compMeta = m;
}
});
if (isPresent(compMeta)) {
if (isBlank(compMeta.template) && isBlank(compMeta.templateUrl) && isBlank(viewMeta)) {
throw new BaseException(
`Component '${stringify(component)}' must have either 'template' or 'templateUrl' set.`);
} else if (isPresent(compMeta.template) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("template", component);
} else if (isPresent(compMeta.templateUrl) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("templateUrl", component);
} else if (isPresent(compMeta.directives) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("directives", component);
} else if (isPresent(compMeta.pipes) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("pipes", component);
} else if (isPresent(compMeta.encapsulation) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("encapsulation", component);
} else if (isPresent(compMeta.styles) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("styles", component);
} else if (isPresent(compMeta.styleUrls) && isPresent(viewMeta)) {
this._throwMixingViewAndComponent("styleUrls", component);
} else if (isPresent(viewMeta)) {
return viewMeta;
} else {
return new ViewMetadata({
templateUrl: compMeta.templateUrl,
template: compMeta.template,
directives: compMeta.directives,
pipes: compMeta.pipes,
encapsulation: compMeta.encapsulation,
styles: compMeta.styles,
styleUrls: compMeta.styleUrls
});
}
} else {
if (isBlank(viewMeta)) {
throw new BaseException(
`Could not compile '${stringify(component)}' because it is not a component.`);
} else {
return viewMeta;
}
}
return null;
}
/** @internal */
_throwMixingViewAndComponent(propertyName: string, component: Type): void {
throw new BaseException(
`Component '${stringify(component)}' cannot have both '${propertyName}' and '@View' set at the same time"`);
}
}

View File

@ -0,0 +1,8 @@
// TODO: vsavkin rename it into TemplateLoader
/**
* An interface for retrieving documents by URL that the compiler uses
* to load templates.
*/
export class XHR {
get(url: string): Promise<string> { return null; }
}