repackaging: all the file moves
This commit is contained in:
3
modules/@angular/compiler/src/assertions.dart
Normal file
3
modules/@angular/compiler/src/assertions.dart
Normal file
@ -0,0 +1,3 @@
|
||||
library angular2.core.util.asserions;
|
||||
|
||||
void assertArrayOfStrings(String identifier, Object value) {}
|
16
modules/@angular/compiler/src/assertions.ts
Normal file
16
modules/@angular/compiler/src/assertions.ts
Normal 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.`);
|
||||
}
|
||||
}
|
||||
}
|
64
modules/@angular/compiler/src/chars.ts
Normal file
64
modules/@angular/compiler/src/chars.ts
Normal 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);
|
||||
}
|
801
modules/@angular/compiler/src/compile_metadata.ts
Normal file
801
modules/@angular/compiler/src/compile_metadata.ts
Normal 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 : [];
|
||||
}
|
62
modules/@angular/compiler/src/compiler.ts
Normal file
62
modules/@angular/compiler/src/compiler.ts
Normal 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
|
||||
];
|
38
modules/@angular/compiler/src/config.ts
Normal file
38
modules/@angular/compiler/src/config.ts
Normal 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;
|
||||
}
|
756
modules/@angular/compiler/src/css/lexer.ts
Normal file
756
modules/@angular/compiler/src/css/lexer.ts
Normal 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;
|
||||
}
|
||||
}
|
740
modules/@angular/compiler/src/css/parser.ts
Normal file
740
modules/@angular/compiler/src/css/parser.ts
Normal 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); }
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
166
modules/@angular/compiler/src/directive_normalizer.ts
Normal file
166
modules/@angular/compiler/src/directive_normalizer.ts
Normal 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; }
|
||||
}
|
169
modules/@angular/compiler/src/directive_resolver.ts
Normal file
169
modules/@angular/compiler/src/directive_resolver.ts
Normal 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);
|
348
modules/@angular/compiler/src/expression_parser/ast.ts
Normal file
348
modules/@angular/compiler/src/expression_parser/ast.ts
Normal 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);
|
||||
}
|
||||
}
|
473
modules/@angular/compiler/src/expression_parser/lexer.ts
Normal file
473
modules/@angular/compiler/src/expression_parser/lexer.ts
Normal 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']);
|
732
modules/@angular/compiler/src/expression_parser/parser.ts
Normal file
732
modules/@angular/compiler/src/expression_parser/parser.ts
Normal 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; }
|
||||
}
|
68
modules/@angular/compiler/src/html_ast.ts
Normal file
68
modules/@angular/compiler/src/html_ast.ts
Normal 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;
|
||||
}
|
728
modules/@angular/compiler/src/html_lexer.ts
Normal file
728
modules/@angular/compiler/src/html_lexer.ts
Normal 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;
|
||||
}
|
369
modules/@angular/compiler/src/html_parser.ts
Normal file
369
modules/@angular/compiler/src/html_parser.ts
Normal 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;
|
||||
}
|
427
modules/@angular/compiler/src/html_tags.ts
Normal file
427
modules/@angular/compiler/src/html_tags.ts
Normal 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 `{` / `ƫ` 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;
|
||||
}
|
116
modules/@angular/compiler/src/i18n/expander.ts
Normal file
116
modules/@angular/compiler/src/i18n/expander.ts
Normal 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);
|
||||
}
|
377
modules/@angular/compiler/src/i18n/i18n_html_parser.ts
Normal file
377
modules/@angular/compiler/src/i18n/i18n_html_parser.ts
Normal 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 ""; }
|
||||
}
|
21
modules/@angular/compiler/src/i18n/message.ts
Normal file
21
modules/@angular/compiler/src/i18n/message.ts
Normal 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}`);
|
||||
}
|
178
modules/@angular/compiler/src/i18n/message_extractor.ts
Normal file
178
modules/@angular/compiler/src/i18n/message_extractor.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
191
modules/@angular/compiler/src/i18n/shared.ts
Normal file
191
modules/@angular/compiler/src/i18n/shared.ts
Normal 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);
|
||||
}
|
||||
}
|
95
modules/@angular/compiler/src/i18n/xmb_serializer.ts
Normal file
95
modules/@angular/compiler/src/i18n/xmb_serializer.ts
Normal 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>`;
|
||||
});
|
||||
}
|
218
modules/@angular/compiler/src/identifiers.ts
Normal file
218
modules/@angular/compiler/src/identifiers.ts
Normal 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});
|
||||
}
|
229
modules/@angular/compiler/src/legacy_template.ts
Normal file
229
modules/@angular/compiler/src/legacy_template.ts
Normal 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;
|
||||
}
|
||||
}
|
433
modules/@angular/compiler/src/metadata_resolver.ts
Normal file
433
modules/@angular/compiler/src/metadata_resolver.ts
Normal 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});
|
||||
}
|
||||
}
|
||||
}
|
141
modules/@angular/compiler/src/offline_compiler.ts
Normal file
141
modules/@angular/compiler/src/offline_compiler.ts
Normal 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.`);
|
||||
}
|
||||
}
|
426
modules/@angular/compiler/src/output/abstract_emitter.ts
Normal file
426
modules/@angular/compiler/src/output/abstract_emitter.ts
Normal 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;
|
||||
}
|
164
modules/@angular/compiler/src/output/abstract_js_emitter.ts
Normal file
164
modules/@angular/compiler/src/output/abstract_js_emitter.ts
Normal 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;
|
||||
}
|
||||
}
|
384
modules/@angular/compiler/src/output/dart_emitter.ts
Normal file
384
modules/@angular/compiler/src/output/dart_emitter.ts
Normal 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);
|
||||
}
|
68
modules/@angular/compiler/src/output/interpretive_view.ts
Normal file
68
modules/@angular/compiler/src/output/interpretive_view.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
77
modules/@angular/compiler/src/output/js_emitter.ts
Normal file
77
modules/@angular/compiler/src/output/js_emitter.ts
Normal 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}; }});`;
|
||||
}
|
870
modules/@angular/compiler/src/output/output_ast.ts
Normal file
870
modules/@angular/compiler/src/output/output_ast.ts
Normal 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);
|
||||
}
|
422
modules/@angular/compiler/src/output/output_interpreter.ts
Normal file
422
modules/@angular/compiler/src/output/output_interpreter.ts
Normal 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';
|
46
modules/@angular/compiler/src/output/output_jit.ts
Normal file
46
modules/@angular/compiler/src/output/output_jit.ts
Normal 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;
|
||||
}
|
||||
}
|
80
modules/@angular/compiler/src/output/path_util.ts
Normal file
80
modules/@angular/compiler/src/output/path_util.ts
Normal 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;
|
||||
}
|
332
modules/@angular/compiler/src/output/ts_emitter.ts
Normal file
332
modules/@angular/compiler/src/output/ts_emitter.ts
Normal 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(`>`);
|
||||
}
|
||||
}
|
||||
}
|
66
modules/@angular/compiler/src/parse_util.ts
Normal file
66
modules/@angular/compiler/src/parse_util.ts
Normal 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}`;
|
||||
}
|
||||
}
|
45
modules/@angular/compiler/src/pipe_resolver.ts
Normal file
45
modules/@angular/compiler/src/pipe_resolver.ts
Normal 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);
|
431
modules/@angular/compiler/src/provider_parser.ts
Normal file
431
modules/@angular/compiler/src/provider_parser.ts
Normal 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);
|
||||
});
|
||||
}
|
238
modules/@angular/compiler/src/runtime_compiler.ts
Normal file
238
modules/@angular/compiler/src/runtime_compiler.ts
Normal 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.`);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export class ElementSchemaRegistry {
|
||||
hasProperty(tagName: string, propName: string): boolean { return true; }
|
||||
getMappedPropName(propName: string): string { return propName; }
|
||||
}
|
383
modules/@angular/compiler/src/selector.ts
Normal file
383
modules/@angular/compiler/src/selector.ts
Normal 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;
|
||||
}
|
||||
}
|
522
modules/@angular/compiler/src/shadow_css.ts
Normal file
522
modules/@angular/compiler/src/shadow_css.ts
Normal 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);
|
||||
}
|
77
modules/@angular/compiler/src/style_compiler.ts
Normal file
77
modules/@angular/compiler/src/style_compiler.ts
Normal 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;
|
||||
}
|
39
modules/@angular/compiler/src/style_url_resolver.ts
Normal file
39
modules/@angular/compiler/src/style_url_resolver.ts
Normal 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;
|
250
modules/@angular/compiler/src/template_ast.ts
Normal file
250
modules/@angular/compiler/src/template_ast.ts
Normal 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;
|
||||
}
|
926
modules/@angular/compiler/src/template_parser.ts
Normal file
926
modules/@angular/compiler/src/template_parser.ts
Normal 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;
|
||||
}
|
72
modules/@angular/compiler/src/template_preparser.ts
Normal file
72
modules/@angular/compiler/src/template_preparser.ts
Normal 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;
|
||||
}
|
69
modules/@angular/compiler/src/url_resolver.dart
Normal file
69
modules/@angular/compiler/src/url_resolver.dart
Normal 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;
|
||||
}
|
357
modules/@angular/compiler/src/url_resolver.ts
Normal file
357
modules/@angular/compiler/src/url_resolver.ts
Normal 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);
|
||||
}
|
71
modules/@angular/compiler/src/util.ts
Normal file
71
modules/@angular/compiler/src/util.ts
Normal 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; }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import {CompileNode} from './compile_element';
|
||||
import {TemplateAst} from '../template_ast';
|
||||
|
||||
export class CompileBinding {
|
||||
constructor(public node: CompileNode, public sourceAst: TemplateAst) {}
|
||||
}
|
427
modules/@angular/compiler/src/view_compiler/compile_element.ts
Normal file
427
modules/@angular/compiler/src/view_compiler/compile_element.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
75
modules/@angular/compiler/src/view_compiler/compile_pipe.ts
Normal file
75
modules/@angular/compiler/src/view_compiler/compile_pipe.ts
Normal 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;
|
||||
}
|
117
modules/@angular/compiler/src/view_compiler/compile_query.ts
Normal file
117
modules/@angular/compiler/src/view_compiler/compile_query.ts
Normal 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);
|
||||
});
|
||||
}
|
209
modules/@angular/compiler/src/view_compiler/compile_view.ts
Normal file
209
modules/@angular/compiler/src/view_compiler/compile_view.ts
Normal 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;
|
||||
}
|
||||
}
|
86
modules/@angular/compiler/src/view_compiler/constants.ts
Normal file
86
modules/@angular/compiler/src/view_compiler/constants.ts
Normal 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`);
|
||||
}
|
163
modules/@angular/compiler/src/view_compiler/event_binder.ts
Normal file
163
modules/@angular/compiler/src/view_compiler/event_binder.ts
Normal 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, '_');
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
210
modules/@angular/compiler/src/view_compiler/property_binder.ts
Normal file
210
modules/@angular/compiler/src/view_compiler/property_binder.ts
Normal 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();
|
||||
}
|
99
modules/@angular/compiler/src/view_compiler/util.ts
Normal file
99
modules/@angular/compiler/src/view_compiler/util.ts
Normal 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());
|
||||
}
|
121
modules/@angular/compiler/src/view_compiler/view_binder.ts
Normal file
121
modules/@angular/compiler/src/view_compiler/view_binder.ts
Normal 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; }
|
||||
}
|
580
modules/@angular/compiler/src/view_compiler/view_builder.ts
Normal file
580
modules/@angular/compiler/src/view_compiler/view_builder.ts
Normal 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;
|
||||
}
|
37
modules/@angular/compiler/src/view_compiler/view_compiler.ts
Normal file
37
modules/@angular/compiler/src/view_compiler/view_compiler.ts
Normal 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);
|
||||
}
|
||||
}
|
111
modules/@angular/compiler/src/view_resolver.ts
Normal file
111
modules/@angular/compiler/src/view_resolver.ts
Normal 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"`);
|
||||
}
|
||||
}
|
8
modules/@angular/compiler/src/xhr.ts
Normal file
8
modules/@angular/compiler/src/xhr.ts
Normal 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; }
|
||||
}
|
Reference in New Issue
Block a user