Skip to content

Commit

Permalink
fix(Compiler): allow "." in attribute selectors (angular#13653)
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb authored and hansl committed Dec 27, 2016
1 parent 0eca960 commit 881eb89
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 66 deletions.
12 changes: 6 additions & 6 deletions modules/@angular/compiler/src/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import {getHtmlTagDefinition} from './ml_parser/html_tags';

const _SELECTOR_REGEXP = new RegExp(
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'g');

/**
Expand Down
36 changes: 21 additions & 15 deletions modules/@angular/compiler/src/template_parser/template_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class TemplateParser {
return new TemplateParseResult(result, errors);
}

if (isPresent(this.transforms)) {
if (this.transforms) {
this.transforms.forEach(
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
}
Expand Down Expand Up @@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
visitText(text: html.Text, parent: ElementContext): any {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
if (isPresent(expr)) {
if (expr) {
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
} else {
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
Expand Down Expand Up @@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
return null;
}

const matchableAttrs: string[][] = [];
const matchableAttrs: [string, string][] = [];
const elementOrDirectiveProps: BoundProperty[] = [];
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
const elementVars: VariableAst[] = [];
const events: BoundEventAst[] = [];

const templateElementOrDirectiveProps: BoundProperty[] = [];
const templateMatchableAttrs: string[][] = [];
const templateMatchableAttrs: [string, string][] = [];
const templateElementVars: VariableAst[] = [];

let hasInlineTemplates = false;
Expand All @@ -268,14 +268,17 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars);

let templateBindingsSource: string|undefined = undefined;
let prefixToken: string|undefined = undefined;
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
let templateBindingsSource: string|undefined;
let prefixToken: string|undefined;
let normalizedName = this._normalizeAttributeName(attr.name);

if (normalizedName == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value;
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
}

const hasTemplateBinding = isPresent(templateBindingsSource);
if (hasTemplateBinding) {
if (hasInlineTemplates) {
Expand Down Expand Up @@ -541,10 +544,12 @@ class TemplateParseVisitor implements html.Visitor {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null;

const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan(
elementSourceSpan.start, elementSourceSpan.end,
`Directive ${identifierName(directive.type)}`);

if (directive.isComponent) {
component = directive;
}
Expand All @@ -567,6 +572,7 @@ class TemplateParseVisitor implements html.Visitor {
return new DirectiveAst(
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
});

elementOrDirectiveRefs.forEach((elOrDirRef) => {
if (elOrDirRef.value.length > 0) {
if (!matchedReferences.has(elOrDirRef.name)) {
Expand All @@ -581,7 +587,7 @@ class TemplateParseVisitor implements html.Visitor {
}
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
}
}); // fix syntax highlighting issue: `
});
return directiveAsts;
}

Expand Down Expand Up @@ -742,7 +748,7 @@ class NonBindableVisitor implements html.Visitor {
return null;
}

const attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
const selector = createElementCssSelector(ast.name, attrNameAndValues);
const ngContentIndex = parent.findNgContentIndex(selector);
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
Expand Down Expand Up @@ -811,16 +817,16 @@ class ElementContext {
}

export function createElementCssSelector(
elementName: string, matchableAttrs: string[][]): CssSelector {
elementName: string, attributes: [string, string][]): CssSelector {
const cssSelector = new CssSelector();
const elNameNoNs = splitNsName(elementName)[1];

cssSelector.setElement(elNameNoNs);

for (let i = 0; i < matchableAttrs.length; i++) {
const attrName = matchableAttrs[i][0];
for (let i = 0; i < attributes.length; i++) {
const attrName = attributes[i][0];
const attrNameNoNs = splitNsName(attrName)[1];
const attrValue = matchableAttrs[i][1];
const attrValue = attributes[i][1];

cssSelector.addAttribute(attrNameNoNs, attrValue);
if (attrName.toLowerCase() == CLASS_ATTR) {
Expand Down
51 changes: 51 additions & 0 deletions modules/@angular/compiler/test/integration_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Component, Directive, Input} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers';

export function main() {
describe('integration tests', () => {
let fixture: ComponentFixture<TestComponent>;


describe('directives', () => {
it('should support dotted selectors', async(() => {
@Directive({selector: '[dot.name]'})
class MyDir {
@Input('dot.name') value: string;
}

TestBed.configureTestingModule({
declarations: [
MyDir,
TestComponent,
],
});

const template = `<div [dot.name]="'foo'"></div>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
expect(myDir.value).toEqual('foo');
}));
});

});
}

@Component({selector: 'test-cmp', template: ''})
class TestComponent {
}

function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}
Loading

0 comments on commit 881eb89

Please sign in to comment.