refactor(compiler): various cleanups

- use `$implicit` variable value correctly
- handle `ng-non-bindable` correctly
- add some more assertions to `TemplateCompiler`
- make `CompiledTemplate` const
- fix default value for `@Directive.moduleId`
- add new compiler to application bindings

BREAKING CHANGE:
- `Compiler.compileInHost` and all methods of `DynamicComponentLoader` don’t take `Binding` any more, only `Type`s. This is in preparation for the new compiler which does not support this.

Part of #3605

Closes #4346
This commit is contained in:
Tobias Bosch 2015-09-18 10:33:23 -07:00
parent bffa2cb59b
commit 7470ad1bd1
29 changed files with 493 additions and 285 deletions

View File

@ -35,7 +35,8 @@ import {escapeSingleQuoteString} from './util';
import {Injectable} from 'angular2/src/core/di'; import {Injectable} from 'angular2/src/core/di';
export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands'); export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands');
const IMPLICIT_VAR = '%implicit';
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
@Injectable() @Injectable()
export class CommandCompiler { export class CommandCompiler {
@ -207,7 +208,7 @@ class CommandBuilderVisitor<R> implements TemplateAstVisitor {
var variableNameAndValues = []; var variableNameAndValues = [];
ast.vars.forEach((varAst) => { ast.vars.forEach((varAst) => {
variableNameAndValues.push(varAst.name); variableNameAndValues.push(varAst.name);
variableNameAndValues.push(varAst.value); variableNameAndValues.push(varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR);
}); });
var directives = []; var directives = [];
ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
@ -227,7 +228,7 @@ class CommandBuilderVisitor<R> implements TemplateAstVisitor {
if (isBlank(component)) { if (isBlank(component)) {
ast.exportAsVars.forEach((varAst) => { ast.exportAsVars.forEach((varAst) => {
variableNameAndValues.push(varAst.name); variableNameAndValues.push(varAst.name);
variableNameAndValues.push(IMPLICIT_VAR); variableNameAndValues.push(null);
}); });
} }
var directives = []; var directives = [];

View File

@ -5,3 +5,31 @@ export {
CompileTemplateMetadata CompileTemplateMetadata
} from './directive_metadata'; } from './directive_metadata';
export {SourceModule, SourceWithImports} from './source_module'; export {SourceModule, SourceWithImports} from './source_module';
import {assertionsEnabled, Type} from 'angular2/src/core/facade/lang';
import {bind, Binding} from 'angular2/src/core/di';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer';
import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata';
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
import {CommandCompiler} from 'angular2/src/compiler/command_compiler';
import {TemplateCompiler} from 'angular2/src/compiler/template_compiler';
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
export function compilerBindings(): Array<Type | Binding | any[]> {
return [
HtmlParser,
TemplateParser,
TemplateNormalizer,
RuntimeMetadataResolver,
StyleCompiler,
CommandCompiler,
ChangeDetectionCompiler,
bind(ChangeDetectorGenConfig)
.toValue(
new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), false, true)),
TemplateCompiler,
];
}

View File

@ -57,12 +57,7 @@ function parseElement(element: Element, indexInParent: number, parentSourceInfo:
var sourceInfo = `${parentSourceInfo} > ${nodeName}:nth-child(${indexInParent})`; var sourceInfo = `${parentSourceInfo} > ${nodeName}:nth-child(${indexInParent})`;
var attrs = parseAttrs(element, sourceInfo); var attrs = parseAttrs(element, sourceInfo);
var childNodes; var childNodes = parseChildNodes(element, sourceInfo);
if (ignoreChildren(attrs)) {
childNodes = [];
} else {
childNodes = parseChildNodes(element, sourceInfo);
}
return new HtmlElementAst(nodeName, attrs, childNodes, sourceInfo); return new HtmlElementAst(nodeName, attrs, childNodes, sourceInfo);
} }
@ -100,16 +95,6 @@ function parseChildNodes(element: Element, parentSourceInfo: string): HtmlAst[]
return result; return result;
} }
function ignoreChildren(attrs: HtmlAttrAst[]): boolean {
for (var i = 0; i < attrs.length; i++) {
var a = attrs[i];
if (a.name == NG_NON_BINDABLE) {
return true;
}
}
return false;
}
class UnparseVisitor implements HtmlAstVisitor { class UnparseVisitor implements HtmlAstVisitor {
visitElement(ast: HtmlElementAst, parts: string[]): any { visitElement(ast: HtmlElementAst, parts: string[]): any {
parts.push(`<${ast.name}`); parts.push(`<${ast.name}`);

View File

@ -1,4 +1,4 @@
import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata'; import {CompileTypeMetadata, CompileTemplateMetadata} from './directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from './source_module'; import {SourceModule, SourceExpression, moduleRef} from './source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {XHR} from 'angular2/src/core/render/xhr'; import {XHR} from 'angular2/src/core/render/xhr';
@ -29,27 +29,28 @@ export class StyleCompiler {
constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {} constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {}
compileComponentRuntime(component: CompileDirectiveMetadata): Promise<string[]> { compileComponentRuntime(type: CompileTypeMetadata,
var styles = component.template.styles; template: CompileTemplateMetadata): Promise<string[]> {
var styleAbsUrls = component.template.styleUrls; var styles = template.styles;
var styleAbsUrls = template.styleUrls;
return this._loadStyles(styles, styleAbsUrls, return this._loadStyles(styles, styleAbsUrls,
component.template.encapsulation === ViewEncapsulation.Emulated) template.encapsulation === ViewEncapsulation.Emulated)
.then(styles => styles.map(style => StringWrapper.replaceAll(style, COMPONENT_REGEX, .then(styles => styles.map(
`${component.type.id}`))); style => StringWrapper.replaceAll(style, COMPONENT_REGEX, `${type.id}`)));
} }
compileComponentCodeGen(component: CompileDirectiveMetadata): SourceExpression { compileComponentCodeGen(type: CompileTypeMetadata,
var shim = component.template.encapsulation === ViewEncapsulation.Emulated; template: CompileTemplateMetadata): SourceExpression {
var shim = template.encapsulation === ViewEncapsulation.Emulated;
var suffix; var suffix;
if (shim) { if (shim) {
var componentId = `${ component.type.id}`; var componentId = `${ type.id}`;
suffix = suffix =
codeGenMapArray(['style'], `style${codeGenReplaceAll(COMPONENT_VARIABLE, componentId)}`); codeGenMapArray(['style'], `style${codeGenReplaceAll(COMPONENT_VARIABLE, componentId)}`);
} else { } else {
suffix = ''; suffix = '';
} }
return this._styleCodeGen(component.template.styles, component.template.styleUrls, shim, return this._styleCodeGen(template.styles, template.styleUrls, shim, suffix);
suffix);
} }
compileStylesheetCodeGen(moduleId: string, cssText: string): SourceModule[] { compileStylesheetCodeGen(moduleId: string, cssText: string): SourceModule[] {

View File

@ -1,4 +1,5 @@
import {Type, Json, isBlank, stringify} from 'angular2/src/core/facade/lang'; import {Type, Json, isBlank, stringify} from 'angular2/src/core/facade/lang';
import {BaseException} from 'angular2/src/core/facade/exceptions';
import {ListWrapper, SetWrapper} from 'angular2/src/core/facade/collection'; import {ListWrapper, SetWrapper} from 'angular2/src/core/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
import {CompiledTemplate, TemplateCmd} from 'angular2/src/core/compiler/template_commands'; import {CompiledTemplate, TemplateCmd} from 'angular2/src/core/compiler/template_commands';
@ -58,18 +59,12 @@ export class TemplateCompiler {
})); }));
} }
serializeDirectiveMetadata(metadata: CompileDirectiveMetadata): string {
return Json.stringify(metadata.toJson());
}
deserializeDirectiveMetadata(data: string): CompileDirectiveMetadata {
return CompileDirectiveMetadata.fromJson(Json.parse(data));
}
compileHostComponentRuntime(type: Type): Promise<CompiledTemplate> { compileHostComponentRuntime(type: Type): Promise<CompiledTemplate> {
var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type); var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type);
assertComponent(compMeta);
var hostMeta: CompileDirectiveMetadata = var hostMeta: CompileDirectiveMetadata =
createHostComponentMeta(compMeta.type, compMeta.selector); createHostComponentMeta(compMeta.type, compMeta.selector);
this._compileComponentRuntime(hostMeta, [compMeta], new Set()); this._compileComponentRuntime(hostMeta, [compMeta], new Set());
return this._compiledTemplateDone.get(hostMeta.type.id); return this._compiledTemplateDone.get(hostMeta.type.id);
} }
@ -93,9 +88,11 @@ export class TemplateCompiler {
new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactory, commands, styles]); new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactory, commands, styles]);
this._compiledTemplateCache.set(compMeta.type.id, compiledTemplate); this._compiledTemplateCache.set(compMeta.type.id, compiledTemplate);
compilingComponentIds.add(compMeta.type.id); compilingComponentIds.add(compMeta.type.id);
done = PromiseWrapper.all([<any>this._styleCompiler.compileComponentRuntime(compMeta)].concat( done =
viewDirectives.map( PromiseWrapper
dirMeta => this.normalizeDirectiveMetadata(dirMeta)))) .all([
<any>this._styleCompiler.compileComponentRuntime(compMeta.type, compMeta.template)
].concat(viewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
.then((stylesAndNormalizedViewDirMetas: any[]) => { .then((stylesAndNormalizedViewDirMetas: any[]) => {
var childPromises = []; var childPromises = [];
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
@ -106,8 +103,8 @@ export class TemplateCompiler {
compMeta.type, compMeta.changeDetection, parsedTemplate); compMeta.type, compMeta.changeDetection, parsedTemplate);
changeDetectorFactory = changeDetectorFactories[0]; changeDetectorFactory = changeDetectorFactories[0];
styles = stylesAndNormalizedViewDirMetas[0]; styles = stylesAndNormalizedViewDirMetas[0];
commands = this._compileCommandsRuntime(compMeta, parsedTemplate, commands =
changeDetectorFactories, this._compileCommandsRuntime(compMeta, parsedTemplate, changeDetectorFactories,
compilingComponentIds, childPromises); compilingComponentIds, childPromises);
return PromiseWrapper.all(childPromises); return PromiseWrapper.all(childPromises);
}) })
@ -148,6 +145,7 @@ export class TemplateCompiler {
var componentMetas: CompileDirectiveMetadata[] = []; var componentMetas: CompileDirectiveMetadata[] = [];
components.forEach(componentWithDirs => { components.forEach(componentWithDirs => {
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component; var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
assertComponent(compMeta);
componentMetas.push(compMeta); componentMetas.push(compMeta);
this._processTemplateCodeGen(compMeta, this._processTemplateCodeGen(compMeta,
<CompileDirectiveMetadata[]>componentWithDirs.directives, <CompileDirectiveMetadata[]>componentWithDirs.directives,
@ -174,7 +172,7 @@ export class TemplateCompiler {
private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata, private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata,
directives: CompileDirectiveMetadata[], directives: CompileDirectiveMetadata[],
targetDeclarations: string[], targetTemplateArguments: any[][]) { targetDeclarations: string[], targetTemplateArguments: any[][]) {
var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta); var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta.type, compMeta.template);
var parsedTemplate = var parsedTemplate =
this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name); this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name);
var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen( var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen(
@ -197,6 +195,12 @@ export class NormalizedComponentWithViewDirectives {
public directives: CompileDirectiveMetadata[]) {} public directives: CompileDirectiveMetadata[]) {}
} }
function assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new BaseException(`Could not compile '${meta.type.name}' because it is not a component.`);
}
}
function templateVariableName(type: CompileTypeMetadata): string { function templateVariableName(type: CompileTypeMetadata): string {
return `${type.name}Template`; return `${type.name}Template`;
} }

View File

@ -4,6 +4,7 @@ import {
CompileTemplateMetadata CompileTemplateMetadata
} from './directive_metadata'; } from './directive_metadata';
import {isPresent, isBlank} from 'angular2/src/core/facade/lang'; import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
import {BaseException} from 'angular2/src/core/facade/exceptions';
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
import {XHR} from 'angular2/src/core/render/xhr'; import {XHR} from 'angular2/src/core/render/xhr';
@ -34,11 +35,13 @@ export class TemplateNormalizer {
if (isPresent(template.template)) { if (isPresent(template.template)) {
return PromiseWrapper.resolve(this.normalizeLoadedTemplate( return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
directiveType, template, template.template, directiveType.moduleId)); directiveType, template, template.template, directiveType.moduleId));
} else { } else if (isPresent(template.templateUrl)) {
var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleId, template.templateUrl); var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleId, template.templateUrl);
return this._xhr.get(sourceAbsUrl) return this._xhr.get(sourceAbsUrl)
.then(templateContent => this.normalizeLoadedTemplate(directiveType, template, .then(templateContent => this.normalizeLoadedTemplate(directiveType, template,
templateContent, sourceAbsUrl)); templateContent, sourceAbsUrl));
} else {
throw new BaseException(`No template specified for component ${directiveType.name}`);
} }
} }
@ -79,12 +82,15 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
ngContentSelectors: string[] = []; ngContentSelectors: string[] = [];
styles: string[] = []; styles: string[] = [];
styleUrls: string[] = []; styleUrls: string[] = [];
ngNonBindableStackCount: number = 0;
visitElement(ast: HtmlElementAst, context: any): any { visitElement(ast: HtmlElementAst, context: any): any {
var preparsedElement = preparseElement(ast); var preparsedElement = preparseElement(ast);
switch (preparsedElement.type) { switch (preparsedElement.type) {
case PreparsedElementType.NG_CONTENT: case PreparsedElementType.NG_CONTENT:
if (this.ngNonBindableStackCount === 0) {
this.ngContentSelectors.push(preparsedElement.selectAttr); this.ngContentSelectors.push(preparsedElement.selectAttr);
}
break; break;
case PreparsedElementType.STYLE: case PreparsedElementType.STYLE:
var textContent = ''; var textContent = '';
@ -99,8 +105,12 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
this.styleUrls.push(preparsedElement.hrefAttr); this.styleUrls.push(preparsedElement.hrefAttr);
break; break;
} }
if (preparsedElement.type !== PreparsedElementType.NON_BINDABLE) { if (preparsedElement.nonBindable) {
this.ngNonBindableStackCount++;
}
htmlVisitAll(this, ast.children); htmlVisitAll(this, ast.children);
if (preparsedElement.nonBindable) {
this.ngNonBindableStackCount--;
} }
return null; return null;
} }

View File

@ -164,8 +164,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
var preparsedElement = preparseElement(element); var preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT || if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE || preparsedElement.type === PreparsedElementType.STYLE ||
preparsedElement.type === PreparsedElementType.STYLESHEET || preparsedElement.type === PreparsedElementType.STYLESHEET) {
preparsedElement.type === PreparsedElementType.NON_BINDABLE) {
// Skipping <script> for security reasons // Skipping <script> for security reasons
// Skipping <style> and stylesheets as we already processed them // Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler // in the StyleCompiler
@ -196,13 +195,14 @@ class TemplateParseVisitor implements HtmlAstVisitor {
} }
}); });
var isTemplateElement = nodeName == TEMPLATE_ELEMENT; var isTemplateElement = nodeName == TEMPLATE_ELEMENT;
var elementCssSelector = this._createElementCssSelector(nodeName, matchableAttrs); var elementCssSelector = createElementCssSelector(nodeName, matchableAttrs);
var directives = this._createDirectiveAsts( var directives = this._createDirectiveAsts(
element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector), element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector),
elementOrDirectiveProps, isTemplateElement ? [] : vars, element.sourceInfo); elementOrDirectiveProps, isTemplateElement ? [] : vars, element.sourceInfo);
var elementProps: BoundElementPropertyAst[] = var elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives); this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives);
var children = htmlVisitAll(this, element.children, Component.create(directives)); var children = htmlVisitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this,
element.children, Component.create(directives));
var elementNgContentIndex = var elementNgContentIndex =
hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector); hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector);
var parsedElement; var parsedElement;
@ -221,8 +221,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
children, elementNgContentIndex, element.sourceInfo); children, elementNgContentIndex, element.sourceInfo);
} }
if (hasInlineTemplates) { if (hasInlineTemplates) {
var templateCssSelector = var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
this._createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectives = this._createDirectiveAsts( var templateDirectives = this._createDirectiveAsts(
element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector), element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector),
templateElementOrDirectiveProps, [], element.sourceInfo); templateElementOrDirectiveProps, [], element.sourceInfo);
@ -381,22 +380,6 @@ class TemplateParseVisitor implements HtmlAstVisitor {
sourceInfo)); sourceInfo));
} }
private _createElementCssSelector(elementName: string, matchableAttrs: string[][]): CssSelector {
var cssSelector = new CssSelector();
cssSelector.setElement(elementName);
for (var i = 0; i < matchableAttrs.length; i++) {
var attrName = matchableAttrs[i][0].toLowerCase();
var attrValue = matchableAttrs[i][1];
cssSelector.addAttribute(attrName, attrValue);
if (attrName == CLASS_ATTR) {
var classes = splitClasses(attrValue);
classes.forEach(className => cssSelector.addClassName(className));
}
}
return cssSelector;
}
private _parseDirectives(selectorMatcher: SelectorMatcher, private _parseDirectives(selectorMatcher: SelectorMatcher,
elementCssSelector: CssSelector): CompileDirectiveMetadata[] { elementCssSelector: CssSelector): CompileDirectiveMetadata[] {
var directives = []; var directives = [];
@ -480,8 +463,14 @@ class TemplateParseVisitor implements HtmlAstVisitor {
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) { targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
if (isPresent(directiveProperties)) { if (isPresent(directiveProperties)) {
var boundPropsByName: Map<string, BoundElementOrDirectiveProperty> = new Map(); var boundPropsByName: Map<string, BoundElementOrDirectiveProperty> = new Map();
boundProps.forEach(boundProp => boundProps.forEach(boundProp => {
boundPropsByName.set(dashCaseToCamelCase(boundProp.name), boundProp)); var key = dashCaseToCamelCase(boundProp.name);
var prevValue = boundPropsByName.get(boundProp.name);
if (isBlank(prevValue) || prevValue.isLiteral) {
// give [a]="b" a higher precedence thatn a="b" on the same element
boundPropsByName.set(key, boundProp);
}
});
StringMapWrapper.forEach(directiveProperties, (elProp: string, dirProp: string) => { StringMapWrapper.forEach(directiveProperties, (elProp: string, dirProp: string) => {
elProp = dashCaseToCamelCase(elProp); elProp = dashCaseToCamelCase(elProp);
@ -585,6 +574,34 @@ class TemplateParseVisitor implements HtmlAstVisitor {
} }
} }
class NonBindableVisitor implements HtmlAstVisitor {
visitElement(ast: HtmlElementAst, component: Component): 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 = component.findNgContentIndex(selector);
var children = htmlVisitAll(this, ast.children, EMPTY_COMPONENT);
return new ElementAst(ast.name, htmlVisitAll(this, ast.attrs), [], [], [], [], children,
ngContentIndex, ast.sourceInfo);
}
visitAttr(ast: HtmlAttrAst, context: any): AttrAst {
return new AttrAst(ast.name, ast.value, ast.sourceInfo);
}
visitText(ast: HtmlTextAst, component: Component): TextAst {
var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR);
return new TextAst(ast.value, ngContentIndex, ast.sourceInfo);
}
}
class BoundElementOrDirectiveProperty { class BoundElementOrDirectiveProperty {
constructor(public name: string, public expression: AST, public isLiteral: boolean, constructor(public name: string, public expression: AST, public isLiteral: boolean,
public sourceInfo: string) {} public sourceInfo: string) {}
@ -631,4 +648,21 @@ class Component {
} }
} }
function createElementCssSelector(elementName: string, matchableAttrs: string[][]): CssSelector {
var cssSelector = new CssSelector();
cssSelector.setElement(elementName);
for (var i = 0; i < matchableAttrs.length; i++) {
var attrName = matchableAttrs[i][0].toLowerCase();
var attrValue = matchableAttrs[i][1];
cssSelector.addAttribute(attrName, attrValue);
if (attrName == CLASS_ATTR) {
var classes = splitClasses(attrValue);
classes.forEach(className => cssSelector.addClassName(className));
}
}
return cssSelector;
}
var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null); var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null);
var NON_BINDABLE_VISITOR = new NonBindableVisitor();

View File

@ -30,9 +30,7 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement {
selectAttr = normalizeNgContentSelect(selectAttr); selectAttr = normalizeNgContentSelect(selectAttr);
var nodeName = ast.name; var nodeName = ast.name;
var type = PreparsedElementType.OTHER; var type = PreparsedElementType.OTHER;
if (nonBindable) { if (nodeName == NG_CONTENT_ELEMENT) {
type = PreparsedElementType.NON_BINDABLE;
} else if (nodeName == NG_CONTENT_ELEMENT) {
type = PreparsedElementType.NG_CONTENT; type = PreparsedElementType.NG_CONTENT;
} else if (nodeName == STYLE_ELEMENT) { } else if (nodeName == STYLE_ELEMENT) {
type = PreparsedElementType.STYLE; type = PreparsedElementType.STYLE;
@ -41,7 +39,7 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement {
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) { } else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
type = PreparsedElementType.STYLESHEET; type = PreparsedElementType.STYLESHEET;
} }
return new PreparsedElement(type, selectAttr, hrefAttr); return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable);
} }
export enum PreparsedElementType { export enum PreparsedElementType {
@ -49,13 +47,12 @@ export enum PreparsedElementType {
STYLE, STYLE,
STYLESHEET, STYLESHEET,
SCRIPT, SCRIPT,
NON_BINDABLE,
OTHER OTHER
} }
export class PreparsedElement { export class PreparsedElement {
constructor(public type: PreparsedElementType, public selectAttr: string, constructor(public type: PreparsedElementType, public selectAttr: string, public hrefAttr: string,
public hrefAttr: string) {} public nonBindable: boolean) {}
} }

View File

@ -2,8 +2,8 @@ import {StringWrapper, isBlank, isJsObject} from 'angular2/src/core/facade/lang'
var CAMEL_CASE_REGEXP = /([A-Z])/g; var CAMEL_CASE_REGEXP = /([A-Z])/g;
var DASH_CASE_REGEXP = /-([a-z])/g; var DASH_CASE_REGEXP = /-([a-z])/g;
var SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n/g; var SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\$/g;
var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n/g; var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n|\$/g;
export var IS_DART = !isJsObject({}); export var IS_DART = !isJsObject({});
@ -33,7 +33,9 @@ export function escapeDoubleQuoteString(input: string): string {
function escapeString(input: string, re: RegExp): string { function escapeString(input: string, re: RegExp): string {
return StringWrapper.replaceAllMapped(input, re, (match) => { return StringWrapper.replaceAllMapped(input, re, (match) => {
if (match[0] == '\n') { if (match[0] == '$') {
return IS_DART ? '\\$' : '$';
} else if (match[0] == '\n') {
return '\\n'; return '\\n';
} else { } else {
return `\\${match[0]}`; return `\\${match[0]}`;

View File

@ -43,7 +43,7 @@ import {PipeResolver} from './compiler/pipe_resolver';
import {StyleUrlResolver} from 'angular2/src/core/render/dom/compiler/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/core/render/dom/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/core/services/url_resolver'; import {UrlResolver} from 'angular2/src/core/services/url_resolver';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {compilerBindings} from 'angular2/src/compiler/compiler';
/** /**
* Constructs the set of bindings meant for use at the platform level. * Constructs the set of bindings meant for use at the platform level.
@ -95,6 +95,7 @@ export function applicationCommonBindings(): Array<Type | Binding | any[]> {
bestChangeDetection = new JitChangeDetection(); bestChangeDetection = new JitChangeDetection();
} }
return [ return [
compilerBindings(),
ProtoViewFactory, ProtoViewFactory,
AppViewPool, AppViewPool,
bind(APP_VIEW_POOL_CAPACITY).toValue(10000), bind(APP_VIEW_POOL_CAPACITY).toValue(10000),

View File

@ -129,9 +129,7 @@ export class Compiler {
// Create a hostView as if the compiler encountered <hostcmp></hostcmp>. // Create a hostView as if the compiler encountered <hostcmp></hostcmp>.
// Used for bootstrapping. // Used for bootstrapping.
compileInHost(componentTypeOrBinding: Type | Binding): Promise<ProtoViewRef> { compileInHost(componentType: Type): Promise<ProtoViewRef> {
var componentType = isType(componentTypeOrBinding) ? componentTypeOrBinding :
(<Binding>componentTypeOrBinding).token;
var r = wtfStartTimeRange('Compiler#compile()', stringify(componentType)); var r = wtfStartTimeRange('Compiler#compile()', stringify(componentType));
var hostAppProtoView = this._compilerCache.getHost(componentType); var hostAppProtoView = this._compilerCache.getHost(componentType);
@ -139,7 +137,7 @@ export class Compiler {
if (isPresent(hostAppProtoView)) { if (isPresent(hostAppProtoView)) {
hostPvPromise = PromiseWrapper.resolve(hostAppProtoView); hostPvPromise = PromiseWrapper.resolve(hostAppProtoView);
} else { } else {
var componentBinding: DirectiveBinding = this._bindDirective(componentTypeOrBinding); var componentBinding: DirectiveBinding = this._bindDirective(componentType);
Compiler._assertTypeIsComponent(componentBinding); Compiler._assertTypeIsComponent(componentBinding);
var directiveMetadata = componentBinding.metadata; var directiveMetadata = componentBinding.metadata;

View File

@ -6,10 +6,6 @@ import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
import {ViewRef, HostViewRef} from './view_ref'; import {ViewRef, HostViewRef} from './view_ref';
function _asType(typeOrBinding: Type | Binding): Type {
return isType(typeOrBinding) ? typeOrBinding : (<Binding>typeOrBinding).token;
}
/** /**
* Angular's reference to a component instance. * Angular's reference to a component instance.
* *
@ -69,7 +65,7 @@ export class DynamicComponentLoader {
* Loads a root component that is placed at the first element that matches the component's * Loads a root component that is placed at the first element that matches the component's
* selector. * selector.
* *
* - `typeOrBinding` `Type` \ {@link Binding} - representing the component to load. * - `typeOrBinding` `Type` - representing the component to load.
* - `overrideSelector` (optional) selector to load the component at (or use * - `overrideSelector` (optional) selector to load the component at (or use
* `@Component.selector`) The selector can be anywhere (i.e. outside the current component.) * `@Component.selector`) The selector can be anywhere (i.e. outside the current component.)
* - `injector` {@link Injector} - optional injector to use for the component. * - `injector` {@link Injector} - optional injector to use for the component.
@ -120,10 +116,9 @@ export class DynamicComponentLoader {
* </my-app> * </my-app>
* ``` * ```
*/ */
loadAsRoot(typeOrBinding: Type | Binding, overrideSelector: string, injector: Injector, loadAsRoot(type: Type, overrideSelector: string, injector: Injector,
onDispose?: () => void): Promise<ComponentRef> { onDispose?: () => void): Promise<ComponentRef> {
return this._compiler.compileInHost(typeOrBinding) return this._compiler.compileInHost(type).then(hostProtoViewRef => {
.then(hostProtoViewRef => {
var hostViewRef = var hostViewRef =
this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector); this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector);
var newLocation = this._viewManager.getHostElement(hostViewRef); var newLocation = this._viewManager.getHostElement(hostViewRef);
@ -135,8 +130,7 @@ export class DynamicComponentLoader {
onDispose(); onDispose();
} }
}; };
return new ComponentRef(newLocation, component, _asType(typeOrBinding), injector, return new ComponentRef(newLocation, component, type, injector, dispose);
dispose);
}); });
} }
@ -187,11 +181,10 @@ export class DynamicComponentLoader {
* </my-app> * </my-app>
* ``` * ```
*/ */
loadIntoLocation(typeOrBinding: Type | Binding, hostLocation: ElementRef, anchorName: string, loadIntoLocation(type: Type, hostLocation: ElementRef, anchorName: string,
bindings: ResolvedBinding[] = null): Promise<ComponentRef> { bindings: ResolvedBinding[] = null): Promise<ComponentRef> {
return this.loadNextToLocation( return this.loadNextToLocation(
typeOrBinding, this._viewManager.getNamedElementInComponentView(hostLocation, anchorName), type, this._viewManager.getNamedElementInComponentView(hostLocation, anchorName), bindings);
bindings);
} }
/** /**
@ -235,10 +228,9 @@ export class DynamicComponentLoader {
* <child-component>Child</child-component> * <child-component>Child</child-component>
* ``` * ```
*/ */
loadNextToLocation(typeOrBinding: Type | Binding, location: ElementRef, loadNextToLocation(type: Type, location: ElementRef,
bindings: ResolvedBinding[] = null): Promise<ComponentRef> { bindings: ResolvedBinding[] = null): Promise<ComponentRef> {
return this._compiler.compileInHost(typeOrBinding) return this._compiler.compileInHost(type).then(hostProtoViewRef => {
.then(hostProtoViewRef => {
var viewContainer = this._viewManager.getViewContainer(location); var viewContainer = this._viewManager.getViewContainer(location);
var hostViewRef = var hostViewRef =
viewContainer.createHostView(hostProtoViewRef, viewContainer.length, bindings); viewContainer.createHostView(hostProtoViewRef, viewContainer.length, bindings);
@ -251,7 +243,7 @@ export class DynamicComponentLoader {
viewContainer.remove(index); viewContainer.remove(index);
} }
}; };
return new ComponentRef(newLocation, component, _asType(typeOrBinding), null, dispose); return new ComponentRef(newLocation, component, type, null, dispose);
}); });
} }
} }

View File

@ -1,4 +1,4 @@
import {Type, CONST_EXPR, isPresent, isBlank} from 'angular2/src/core/facade/lang'; import {Type, CONST_EXPR, CONST, isPresent, isBlank} from 'angular2/src/core/facade/lang';
import { import {
RenderTemplateCmd, RenderTemplateCmd,
RenderCommandVisitor, RenderCommandVisitor,
@ -9,47 +9,32 @@ import {
RenderEmbeddedTemplateCmd RenderEmbeddedTemplateCmd
} from 'angular2/src/core/render/render'; } from 'angular2/src/core/render/render';
/**
* A compiled template. This is const as we are storing it as annotation
* for the compiled component type.
*/
@CONST()
export class CompiledTemplate { export class CompiledTemplate {
private _changeDetectorFactory: Function = null;
private _styles: string[] = null;
private _commands: TemplateCmd[] = null;
// Note: paramGetter is a function so that we can have cycles between templates! // Note: paramGetter is a function so that we can have cycles between templates!
constructor(public id: number, private _paramGetter: Function) {} // paramGetter returns a tuple with:
// - ChangeDetector factory function
private _init() { // - TemplateCmd[]
if (isBlank(this._commands)) { // - styles
var params = this._paramGetter(); constructor(public id: number,
this._changeDetectorFactory = params[0]; public dataGetter: /*()=>[Function, TemplateCmd[], string[]]*/ Function) {}
this._commands = params[1];
this._styles = params[2];
}
}
get changeDetectorFactory(): Function {
this._init();
return this._changeDetectorFactory;
}
get styles(): string[] {
this._init();
return this._styles;
}
get commands(): TemplateCmd[] {
this._init();
return this._commands;
}
} }
const EMPTY_ARR = CONST_EXPR([]); const EMPTY_ARR = CONST_EXPR([]);
export interface TemplateCmd extends RenderTemplateCmd { export interface TemplateCmd extends RenderTemplateCmd {
visit(visitor: CommandVisitor, context: any): any; visit(visitor: RenderCommandVisitor, context: any): any;
} }
export class TextCmd implements TemplateCmd, RenderTextCmd { export class TextCmd implements TemplateCmd, RenderTextCmd {
constructor(public value: string, public isBound: boolean, public ngContentIndex: number) {} constructor(public value: string, public isBound: boolean, public ngContentIndex: number) {}
visit(visitor: CommandVisitor, context: any): any { return visitor.visitText(this, context); } visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitText(this, context);
}
} }
export function text(value: string, isBound: boolean, ngContentIndex: number): TextCmd { export function text(value: string, isBound: boolean, ngContentIndex: number): TextCmd {
@ -59,7 +44,7 @@ export function text(value: string, isBound: boolean, ngContentIndex: number): T
export class NgContentCmd implements TemplateCmd, RenderNgContentCmd { export class NgContentCmd implements TemplateCmd, RenderNgContentCmd {
isBound: boolean = false; isBound: boolean = false;
constructor(public ngContentIndex: number) {} constructor(public ngContentIndex: number) {}
visit(visitor: CommandVisitor, context: any): any { visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitNgContent(this, context); return visitor.visitNgContent(this, context);
} }
} }
@ -72,7 +57,7 @@ export interface IBeginElementCmd extends TemplateCmd, RenderBeginElementCmd {
variableNameAndValues: Array<string | number>; variableNameAndValues: Array<string | number>;
eventTargetAndNames: string[]; eventTargetAndNames: string[];
directives: Type[]; directives: Type[];
visit(visitor: CommandVisitor, context: any): any; visit(visitor: RenderCommandVisitor, context: any): any;
} }
export class BeginElementCmd implements TemplateCmd, IBeginElementCmd, RenderBeginElementCmd { export class BeginElementCmd implements TemplateCmd, IBeginElementCmd, RenderBeginElementCmd {
@ -80,7 +65,7 @@ export class BeginElementCmd implements TemplateCmd, IBeginElementCmd, RenderBeg
public eventTargetAndNames: string[], public eventTargetAndNames: string[],
public variableNameAndValues: Array<string | number>, public directives: Type[], public variableNameAndValues: Array<string | number>, public directives: Type[],
public isBound: boolean, public ngContentIndex: number) {} public isBound: boolean, public ngContentIndex: number) {}
visit(visitor: CommandVisitor, context: any): any { visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitBeginElement(this, context); return visitor.visitBeginElement(this, context);
} }
} }
@ -94,7 +79,9 @@ export function beginElement(name: string, attrNameAndValues: string[],
} }
export class EndElementCmd implements TemplateCmd { export class EndElementCmd implements TemplateCmd {
visit(visitor: CommandVisitor, context: any): any { return visitor.visitEndElement(context); } visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitEndElement(context);
}
} }
export function endElement(): TemplateCmd { export function endElement(): TemplateCmd {
@ -113,7 +100,7 @@ export class BeginComponentCmd implements TemplateCmd, IBeginElementCmd, RenderB
this.component = directives[0]; this.component = directives[0];
this.templateId = template.id; this.templateId = template.id;
} }
visit(visitor: CommandVisitor, context: any): any { visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitBeginComponent(this, context); return visitor.visitBeginComponent(this, context);
} }
} }
@ -127,7 +114,9 @@ export function beginComponent(
} }
export class EndComponentCmd implements TemplateCmd { export class EndComponentCmd implements TemplateCmd {
visit(visitor: CommandVisitor, context: any): any { return visitor.visitEndComponent(context); } visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitEndComponent(context);
}
} }
export function endComponent(): TemplateCmd { export function endComponent(): TemplateCmd {
@ -142,7 +131,7 @@ export class EmbeddedTemplateCmd implements TemplateCmd, IBeginElementCmd,
constructor(public attrNameAndValues: string[], public variableNameAndValues: string[], constructor(public attrNameAndValues: string[], public variableNameAndValues: string[],
public directives: Type[], public isMerged: boolean, public ngContentIndex: number, public directives: Type[], public isMerged: boolean, public ngContentIndex: number,
public changeDetectorFactory: Function, public children: TemplateCmd[]) {} public changeDetectorFactory: Function, public children: TemplateCmd[]) {}
visit(visitor: CommandVisitor, context: any): any { visit(visitor: RenderCommandVisitor, context: any): any {
return visitor.visitEmbeddedTemplate(this, context); return visitor.visitEmbeddedTemplate(this, context);
} }
} }
@ -155,7 +144,15 @@ export function embeddedTemplate(attrNameAndValues: string[], variableNameAndVal
ngContentIndex, changeDetectorFactory, children); ngContentIndex, changeDetectorFactory, children);
} }
export interface CommandVisitor extends RenderCommandVisitor {} export interface CommandVisitor extends RenderCommandVisitor {
visitText(cmd: TextCmd, context: any): any;
visitNgContent(cmd: NgContentCmd, context: any): any;
visitBeginElement(cmd: BeginElementCmd, context: any): any;
visitEndElement(context: any): any;
visitBeginComponent(cmd: BeginComponentCmd, context: any): any;
visitEndComponent(context: any): any;
visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any;
}
export function visitAllCommands(visitor: CommandVisitor, cmds: TemplateCmd[], export function visitAllCommands(visitor: CommandVisitor, cmds: TemplateCmd[],
context: any = null) { context: any = null) {

View File

@ -45,7 +45,7 @@ class NoReflectionCapabilities implements PlatformReflectionCapabilities {
String importUri(Type type) => './'; String importUri(Type type) => './';
String moduleId(Type type) => null; String moduleId(Type type) => './';
} }
final Reflector reflector = new Reflector(new NoReflectionCapabilities()); final Reflector reflector = new Reflector(new NoReflectionCapabilities());

View File

@ -324,6 +324,9 @@ class ReflectionCapabilities implements PlatformReflectionCapabilities {
} }
String moduleId(Type type) { String moduleId(Type type) {
return '${MirrorSystem.getName((reflectClass(type).owner as LibraryMirror).qualifiedName).replaceAll(DOT_REGEX, "/")}'; var rootUri = currentMirrorSystem().isolate.rootLibrary.uri;
var moduleUri = (reflectClass(type).owner as LibraryMirror).uri;
var relativeUri = new Uri(pathSegments:moduleUri.pathSegments.sublist(rootUri.pathSegments.length-1)).toString();
return relativeUri.substring(0, relativeUri.lastIndexOf('.'));
} }
} }

View File

@ -169,5 +169,5 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
// There is not a concept of import uri in Js, but this is useful in developing Dart applications. // There is not a concept of import uri in Js, but this is useful in developing Dart applications.
importUri(type: Type): string { return './'; } importUri(type: Type): string { return './'; }
moduleId(type: Type): string { return null; } moduleId(type: Type): string { return './'; }
} }

View File

@ -401,7 +401,7 @@ export interface RenderBeginCmd extends RenderTemplateCmd {
export interface RenderTextCmd extends RenderBeginCmd { value: string; } export interface RenderTextCmd extends RenderBeginCmd { value: string; }
export interface RenderNgContentCmd extends RenderBeginCmd { ngContentIndex: number; } export interface RenderNgContentCmd { ngContentIndex: number; }
export interface RenderBeginElementCmd extends RenderBeginCmd { export interface RenderBeginElementCmd extends RenderBeginCmd {
name: string; name: string;
@ -420,13 +420,13 @@ export interface RenderEmbeddedTemplateCmd extends RenderBeginElementCmd {
} }
export interface RenderCommandVisitor { export interface RenderCommandVisitor {
visitText(cmd: any, context: any): any; visitText(cmd: RenderTextCmd, context: any): any;
visitNgContent(cmd: any, context: any): any; visitNgContent(cmd: RenderNgContentCmd, context: any): any;
visitBeginElement(cmd: any, context: any): any; visitBeginElement(cmd: RenderBeginElementCmd, context: any): any;
visitEndElement(context: any): any; visitEndElement(context: any): any;
visitBeginComponent(cmd: any, context: any): any; visitBeginComponent(cmd: RenderBeginComponentCmd, context: any): any;
visitEndComponent(context: any): any; visitEndComponent(context: any): any;
visitEmbeddedTemplate(cmd: any, context: any): any; visitEmbeddedTemplate(cmd: RenderEmbeddedTemplateCmd, context: any): any;
} }

View File

@ -74,6 +74,7 @@ import {
} from 'angular2/src/core/render/dom/schema/dom_element_schema_registry'; } from 'angular2/src/core/render/dom/schema/dom_element_schema_registry';
import {Serializer} from "angular2/src/web_workers/shared/serializer"; import {Serializer} from "angular2/src/web_workers/shared/serializer";
import {Log} from './utils'; import {Log} from './utils';
import {compilerBindings} from 'angular2/src/compiler/compiler';
/** /**
* Returns the root injector bindings. * Returns the root injector bindings.
@ -107,8 +108,8 @@ function _getAppBindings() {
} }
return [ return [
bind(DOCUMENT) compilerBindings(),
.toValue(appDoc), bind(DOCUMENT).toValue(appDoc),
DomRenderer, DomRenderer,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),
bind(APP_ID).toValue('a'), bind(APP_ID).toValue('a'),

View File

@ -171,7 +171,7 @@ export function main() {
'div', 'div',
['a', 'b'], ['a', 'b'],
[null, 'click', 'window', 'scroll'], [null, 'click', 'window', 'scroll'],
['someVar', '%implicit'], ['someVar', null],
[], [],
true, true,
null null
@ -329,29 +329,48 @@ export function main() {
describe('embedded templates', () => { describe('embedded templates', () => {
it('should create embedded template commands', inject([AsyncTestCompleter], (async) => { it('should create embedded template commands', inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({ var rootComp =
type: RootCompTypeMeta, createComp({type: RootCompTypeMeta, template: '<template a="b"></template>'});
template: '<template a="b" #some-var="someValue"></template>'
});
var dir = createDirective(SomeDirTypeMeta, '[a]'); var dir = createDirective(SomeDirTypeMeta, '[a]');
run(rootComp, [dir], 1) run(rootComp, [dir], 1)
.then((data) => { .then((data) => {
expect(data).toEqual([ expect(data).toEqual([
[ [EMBEDDED_TEMPLATE, ['a', 'b'], [], ['SomeDirType'], false, null, 'cd1', []]
EMBEDDED_TEMPLATE,
['a', 'b'],
['someVar', 'someValue'],
['SomeDirType'],
false,
null,
'cd1',
[]
]
]); ]);
async.done(); async.done();
}); });
})); }));
it('should keep variable name and value for <template> elements',
inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({
type: RootCompTypeMeta,
template: '<template #some-var="someValue" #some-empty-var></template>'
});
var dir = createDirective(SomeDirTypeMeta, '[a]');
run(rootComp, [dir], 1)
.then((data) => {
expect(data[0][2])
.toEqual(['someEmptyVar', '$implicit', 'someVar', 'someValue']);
async.done();
});
}));
it('should keep variable name and value for template attributes',
inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({
type: RootCompTypeMeta,
template: '<div template="var someVar=someValue; var someEmptyVar"></div>'
});
var dir = createDirective(SomeDirTypeMeta, '[a]');
run(rootComp, [dir], 1)
.then((data) => {
expect(data[0][2])
.toEqual(['someVar', 'someValue', 'someEmptyVar', '$implicit']);
async.done();
});
}));
it('should created nested nodes', inject([AsyncTestCompleter], (async) => { it('should created nested nodes', inject([AsyncTestCompleter], (async) => {
var rootComp = var rootComp =
createComp({type: RootCompTypeMeta, template: '<template>t</template>'}); createComp({type: RootCompTypeMeta, template: '<template>t</template>'});

View File

@ -66,7 +66,8 @@ export function main() {
it('should use the moduleId from the reflector if none is given', it('should use the moduleId from the reflector if none is given',
inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => { inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => {
var expectedValue = IS_DART ? 'angular2/test/compiler/runtime_metadata_spec' : null; var expectedValue =
IS_DART ? 'base/dist/dart/angular2/test/compiler/runtime_metadata_spec' : './';
expect(resolver.getMetadata(DirectiveWithoutModuleId).type.moduleId) expect(resolver.getMetadata(DirectiveWithoutModuleId).type.moduleId)
.toEqual(expectedValue); .toEqual(expectedValue);
})); }));

View File

@ -42,6 +42,8 @@ const IMPORT_ABS_MODULE_NAME_WITH_IMPORT =
export function main() { export function main() {
describe('StyleCompiler', () => { describe('StyleCompiler', () => {
var xhr: SpyXHR; var xhr: SpyXHR;
var typeMeta;
beforeEachBindings(() => { beforeEachBindings(() => {
xhr = <any>new SpyXHR(); xhr = <any>new SpyXHR();
return [TEST_BINDINGS, bind(XHR).toValue(xhr)]; return [TEST_BINDINGS, bind(XHR).toValue(xhr)];
@ -49,16 +51,10 @@ export function main() {
var compiler: StyleCompiler; var compiler: StyleCompiler;
beforeEach(inject([StyleCompiler], (_compiler) => { compiler = _compiler; })); beforeEach(inject([StyleCompiler], (_compiler) => {
typeMeta = new CompileTypeMetadata({id: 23, moduleId: 'someUrl'});
function comp(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation): compiler = _compiler;
CompileDirectiveMetadata { }));
return CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({id: 23, moduleId: 'someUrl'}),
template: new CompileTemplateMetadata(
{styles: styles, styleUrls: styleAbsUrls, encapsulation: encapsulation})
});
}
describe('compileComponentRuntime', () => { describe('compileComponentRuntime', () => {
var xhrUrlResults; var xhrUrlResults;
@ -84,7 +80,9 @@ export function main() {
} }
return PromiseWrapper.resolve(response); return PromiseWrapper.resolve(response);
}); });
return compiler.compileComponentRuntime(comp(styles, styleAbsUrls, encapsulation)); return compiler.compileComponentRuntime(
typeMeta, new CompileTemplateMetadata(
{styles: styles, styleUrls: styleAbsUrls, encapsulation: encapsulation}));
} }
describe('no shim', () => { describe('no shim', () => {
@ -199,8 +197,9 @@ export function main() {
describe('compileComponentCodeGen', () => { describe('compileComponentCodeGen', () => {
function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation): function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
Promise<string[]> { Promise<string[]> {
var sourceExpression = var sourceExpression = compiler.compileComponentCodeGen(
compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation)); typeMeta, new CompileTemplateMetadata(
{styles: styles, styleUrls: styleAbsUrls, encapsulation: encapsulation}));
var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports(); var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports();
return evalModule(sourceWithImports.source, sourceWithImports.imports, null); return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
}; };

View File

@ -66,6 +66,15 @@ export function main() {
describe('compile templates', () => { describe('compile templates', () => {
function runTests(compile) { function runTests(compile) {
it('should throw for non components', inject([AsyncTestCompleter], (async) => {
PromiseWrapper.catchError(PromiseWrapper.wrap(() => compile([NonComponent])), (error) => {
expect(error.message)
.toEqual(
`Could not compile '${stringify(NonComponent)}' because it is not a component.`);
async.done();
});
}));
it('should compile host components', inject([AsyncTestCompleter], (async) => { it('should compile host components', inject([AsyncTestCompleter], (async) => {
compile([CompWithBindingsAndStyles]) compile([CompWithBindingsAndStyles])
.then((humanizedTemplate) => { .then((humanizedTemplate) => {
@ -202,25 +211,6 @@ export function main() {
}); });
describe('serializeDirectiveMetadata and deserializeDirectiveMetadata', () => {
it('should serialize and deserialize', inject([AsyncTestCompleter], (async) => {
compiler.normalizeDirectiveMetadata(
runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles))
.then((meta: CompileDirectiveMetadata) => {
var json = compiler.serializeDirectiveMetadata(meta);
expect(isString(json)).toBe(true);
// Note: serializing will clear our the runtime type!
var clonedMeta = compiler.deserializeDirectiveMetadata(json);
expect(meta.changeDetection).toEqual(clonedMeta.changeDetection);
expect(meta.template).toEqual(clonedMeta.template);
expect(meta.selector).toEqual(clonedMeta.selector);
expect(meta.exportAs).toEqual(clonedMeta.exportAs);
expect(meta.type.name).toEqual(clonedMeta.type.name);
async.done();
});
}));
});
describe('normalizeDirectiveMetadata', () => { describe('normalizeDirectiveMetadata', () => {
it('should normalize the template', it('should normalize the template',
inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => { inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
@ -297,7 +287,8 @@ class CompWithEmbeddedTemplate {
@Directive({selector: 'plain', moduleId: THIS_MODULE}) @Directive({selector: 'plain', moduleId: THIS_MODULE})
class PlainDirective { @View({template: ''})
class NonComponent {
} }
@Component({selector: 'comp', moduleId: THIS_MODULE}) @Component({selector: 'comp', moduleId: THIS_MODULE})
@ -331,13 +322,11 @@ export function humanizeTemplate(template: CompiledTemplate,
return result; return result;
} }
var commands = []; var commands = [];
result = { var templateData = template.dataGetter();
'styles': template.styles, result =
'commands': commands, {'styles': templateData[2], 'commands': commands, 'cd': testChangeDetector(templateData[0])};
'cd': testChangeDetector(template.changeDetectorFactory)
};
humanizedTemplates.set(template.id, result); humanizedTemplates.set(template.id, result);
visitAllCommands(new CommandHumanizer(commands, humanizedTemplates), template.commands); visitAllCommands(new CommandHumanizer(commands, humanizedTemplates), templateData[1]);
return result; return result;
} }

View File

@ -145,6 +145,14 @@ export function main() {
}); });
it('should throw if no template was specified',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
expect(() => normalizer.normalizeTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: null, styles: [], styleUrls: []})))
.toThrowError('No template specified for component SomeComp');
}));
}); });
describe('normalizeLoadedTemplate', () => { describe('normalizeLoadedTemplate', () => {
@ -274,16 +282,23 @@ export function main() {
expect(template.encapsulation).toEqual(ViewEncapsulation.None); expect(template.encapsulation).toEqual(ViewEncapsulation.None);
})); }));
it('should ignore elements with ng-non-bindable attribute and their children', it('should ignore ng-content in elements with ng-non-bindable',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div ng-non-bindable><ng-content select="a"></ng-content></div><ng-content ng-non-bindable select="b"></ng-content>', '<div ng-non-bindable><ng-content select="a"></ng-content></div>', 'some/module/');
'some/module/');
expect(template.ngContentSelectors).toEqual([]); expect(template.ngContentSelectors).toEqual([]);
})); }));
it('should still collect <style> in elements with ng-non-bindable',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div ng-non-bindable><style>div {color:red}</style></div>', 'some/module/');
expect(template.styles).toEqual(['div {color:red}']);
}));
}); });
}); });
} }

View File

@ -464,6 +464,23 @@ export function main() {
]); ]);
}); });
it('should favor explicit bound properties over literal properties', () => {
var dirA = CompileDirectiveMetadata.create(
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']});
expect(humanizeTemplateAsts(parse('<div a="literal" [a]="\'literal2\'"></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'a', 'literal', 'TestComp > div:nth-child(0)[a=literal]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[
BoundDirectivePropertyAst,
'a',
'"literal2"',
'TestComp > div:nth-child(0)[[a]=\'literal2\']'
]
]);
});
it('should support optional directive properties', () => { it('should support optional directive properties', () => {
var dirA = CompileDirectiveMetadata.create( var dirA = CompileDirectiveMetadata.create(
{selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']}); {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']});
@ -502,7 +519,7 @@ export function main() {
]); ]);
}); });
it('should assign variables with empty value to element', () => { it('should assign variables with empty value to the element', () => {
expect(humanizeTemplateAsts(parse('<div #a></div>', []))) expect(humanizeTemplateAsts(parse('<div #a></div>', [])))
.toEqual([ .toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -743,6 +760,11 @@ There is no directive with "exportAs" set to "dirA" at TestComp > div:nth-child(
.toEqual([['a', null], ['b', 0], ['#text(hello)', 0]]); .toEqual([['a', null], ['b', 0], ['#text(hello)', 0]]);
}); });
it('should project children of components with ng-non-bindable', () => {
expect(humanizeContentProjection(parse('<div ng-non-bindable>{{hello}}<span></span></div>',
[createComp('div', ['*'])])))
.toEqual([['div', null], ['#text({{hello}})', 0], ['span', 0]]);
});
}); });
describe('splitClasses', () => { describe('splitClasses', () => {
@ -838,11 +860,74 @@ Property binding a not used by any directive on an embedded template in TestComp
}); });
it('should ignore elements with ng-non-bindable, including their children, but include them for source info', it('should ignore bindings on children of elements with ng-non-bindable', () => {
() => { expect(humanizeTemplateAsts(parse('<div ng-non-bindable>{{b}}</div>', [])))
expect(humanizeTemplateAsts(parse('<div ng-non-bindable>b</div>a', []))) .toEqual([
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]); [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'ng-non-bindable', '', 'TestComp > div:nth-child(0)[ng-non-bindable=]'],
[TextAst, '{{b}}', 'TestComp > div:nth-child(0) > #text({{b}}):nth-child(0)']
]);
});
it('should keep nested children of elements with ng-non-bindable', () => {
expect(humanizeTemplateAsts(parse('<div ng-non-bindable><span>{{b}}</span></div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'ng-non-bindable', '', 'TestComp > div:nth-child(0)[ng-non-bindable=]'],
[ElementAst, 'span', 'TestComp > div:nth-child(0) > span:nth-child(0)'],
[
TextAst,
'{{b}}',
'TestComp > div:nth-child(0) > span:nth-child(0) > #text({{b}}):nth-child(0)'
]
]);
});
it('should ignore <script> elements inside of elements with ng-non-bindable but include them for source info',
() => {
expect(humanizeTemplateAsts(parse('<div ng-non-bindable><script></script>a</div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'ng-non-bindable', '', 'TestComp > div:nth-child(0)[ng-non-bindable=]'],
[TextAst, 'a', 'TestComp > div:nth-child(0) > #text(a):nth-child(1)']
]);
});
it('should ignore <style> elements inside of elements with ng-non-bindable but include them for source info',
() => {
expect(humanizeTemplateAsts(parse('<div ng-non-bindable><style></style>a</div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'ng-non-bindable', '', 'TestComp > div:nth-child(0)[ng-non-bindable=]'],
[TextAst, 'a', 'TestComp > div:nth-child(0) > #text(a):nth-child(1)']
]);
});
it('should ignore <link rel="stylesheet"> elements inside of elements with ng-non-bindable but include them for source info',
() => {
expect(humanizeTemplateAsts(
parse('<div ng-non-bindable><link rel="stylesheet"></link>a</div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'ng-non-bindable', '', 'TestComp > div:nth-child(0)[ng-non-bindable=]'],
[TextAst, 'a', 'TestComp > div:nth-child(0) > #text(a):nth-child(1)']
]);
});
it('should convert <ng-content> elements into regular elements inside of elements with ng-non-bindable but include them for source info',
() => {
expect(humanizeTemplateAsts(
parse('<div ng-non-bindable><ng-content></ng-content>a</div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'ng-non-bindable', '', 'TestComp > div:nth-child(0)[ng-non-bindable=]'],
[
ElementAst,
'ng-content',
'TestComp > div:nth-child(0) > ng-content:nth-child(0)'
],
[TextAst, 'a', 'TestComp > div:nth-child(0) > #text(a):nth-child(1)']
]);
}); });
}); });

View File

@ -0,0 +1,58 @@
import {
ddescribe,
describe,
xdescribe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
AsyncTestCompleter,
inject,
beforeEachBindings
} from 'angular2/test_lib';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {
preparseElement,
PreparsedElementType,
PreparsedElement
} from 'angular2/src/compiler/template_preparser';
export function main() {
describe('preparseElement', () => {
var htmlParser;
beforeEach(inject([HtmlParser], (_htmlParser: HtmlParser) => { htmlParser = _htmlParser; }));
function preparse(html: string): PreparsedElement {
return preparseElement(htmlParser.parse(html, '')[0]);
}
it('should detect script elements', inject([HtmlParser], (htmlParser: HtmlParser) => {
expect(preparse('<script>').type).toBe(PreparsedElementType.SCRIPT);
}));
it('should detect style elements', inject([HtmlParser], (htmlParser: HtmlParser) => {
expect(preparse('<style>').type).toBe(PreparsedElementType.STYLE);
}));
it('should detect stylesheet elements', inject([HtmlParser], (htmlParser: HtmlParser) => {
expect(preparse('<link rel="stylesheet">').type).toBe(PreparsedElementType.STYLESHEET);
expect(preparse('<link rel="stylesheet" href="someUrl">').hrefAttr).toEqual('someUrl');
expect(preparse('<link rel="someRel">').type).toBe(PreparsedElementType.OTHER);
}));
it('should detect ng-content elements', inject([HtmlParser], (htmlParser: HtmlParser) => {
expect(preparse('<ng-content>').type).toBe(PreparsedElementType.NG_CONTENT);
}));
it('should normalize ng-content.select attribute',
inject([HtmlParser], (htmlParser: HtmlParser) => {
expect(preparse('<ng-content>').selectAttr).toEqual('*');
expect(preparse('<ng-content select>').selectAttr).toEqual('*');
expect(preparse('<ng-content select="*">').selectAttr).toEqual('*');
}));
});
}

View File

@ -1,26 +1,5 @@
import {bind, Binding} from 'angular2/src/core/di'; import {bind, Binding} from 'angular2/src/core/di';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer';
import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata';
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
import {CommandCompiler} from 'angular2/src/compiler/command_compiler';
import {TemplateCompiler} from 'angular2/src/compiler/template_compiler';
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
import {MockSchemaRegistry} from './schema_registry_mock'; import {MockSchemaRegistry} from './schema_registry_mock';
import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry';
// TODO(tbosch): move this into test_injector once the new compiler pipeline is used fully export var TEST_BINDINGS = [bind(ElementSchemaRegistry).toValue(new MockSchemaRegistry({}, {}))];
export var TEST_BINDINGS = [
HtmlParser,
TemplateParser,
TemplateNormalizer,
RuntimeMetadataResolver,
StyleCompiler,
CommandCompiler,
ChangeDetectionCompiler,
bind(ChangeDetectorGenConfig).toValue(new ChangeDetectorGenConfig(true, true, false, false)),
TemplateCompiler,
bind(ElementSchemaRegistry).toValue(new MockSchemaRegistry({}, {}))
];

View File

@ -12,6 +12,7 @@ import {
TestComponentBuilder TestComponentBuilder
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {IS_DART} from '../platform';
import {escapeSingleQuoteString, escapeDoubleQuoteString} from 'angular2/src/compiler/util'; import {escapeSingleQuoteString, escapeDoubleQuoteString} from 'angular2/src/compiler/util';
export function main() { export function main() {
@ -25,6 +26,12 @@ export function main() {
it('should escape newlines', it('should escape newlines',
() => { expect(escapeSingleQuoteString('\n')).toEqual(`'\\n'`); }); () => { expect(escapeSingleQuoteString('\n')).toEqual(`'\\n'`); });
if (IS_DART) {
it('should escape $', () => { expect(escapeSingleQuoteString('$')).toEqual(`'\\$'`); });
} else {
it('should not escape $', () => { expect(escapeSingleQuoteString('$')).toEqual(`'$'`); });
}
}); });
describe('escapeDoubleQuoteString', () => { describe('escapeDoubleQuoteString', () => {
@ -36,6 +43,12 @@ export function main() {
it('should escape newlines', it('should escape newlines',
() => { expect(escapeDoubleQuoteString('\n')).toEqual(`"\\n"`); }); () => { expect(escapeDoubleQuoteString('\n')).toEqual(`"\\n"`); });
if (IS_DART) {
it('should escape $', () => { expect(escapeDoubleQuoteString('$')).toEqual(`"\\$"`); });
} else {
it('should not escape $', () => { expect(escapeDoubleQuoteString('$')).toEqual(`"$"`); });
}
}); });
}); });

View File

@ -384,11 +384,7 @@ export function main() {
tcb.overrideView( tcb.overrideView(
MyComp, new ViewMetadata({ MyComp, new ViewMetadata({
template: '<p no-duplicate></p>', template: '<p no-duplicate></p>',
directives: [ directives: [DuplicateDir, DuplicateDir, [DuplicateDir, [DuplicateDir]]]
DuplicateDir,
DuplicateDir,
[DuplicateDir, [DuplicateDir, bind(DuplicateDir).toClass(DuplicateDir)]]
]
})) }))
.createAsync(MyComp) .createAsync(MyComp)
.then((rootTC) => { .then((rootTC) => {

View File

@ -251,7 +251,7 @@ export function main() {
describe("moduleId", () => { describe("moduleId", () => {
it("should return the moduleId for a type", () => { it("should return the moduleId for a type", () => {
expect(reflector.moduleId(TestObjWith00Args)) expect(reflector.moduleId(TestObjWith00Args))
.toEqual('angular2/test/core/reflection/reflector_spec'); .toEqual('base/dist/dart/angular2/test/core/reflection/reflector_spec');
}); });
it("should return an empty array otherwise", () => { it("should return an empty array otherwise", () => {