Skip to content

Commit 29ffdfd

Browse files
vicbIgorMinar
authored andcommitted
fix(Compiler): allow "." in attribute selectors (angular#13653)
fixes angular#13645
1 parent 5754ecc commit 29ffdfd

6 files changed

Lines changed: 190 additions & 66 deletions

File tree

‎modules/@angular/compiler/src/selector.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
import {getHtmlTagDefinition} from './ml_parser/html_tags';
1010

1111
const _SELECTOR_REGEXP = new RegExp(
12-
'(\\:not\\()|' + //":not("
13-
'([-\\w]+)|' + // "tag"
14-
'(?:\\.([-\\w]+))|' + // ".class"
15-
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
16-
'(\\))|' + // ")"
17-
'(\\s*,\\s*)', // ","
12+
'(\\:not\\()|' + //":not("
13+
'([-\\w]+)|' + // "tag"
14+
'(?:\\.([-\\w]+))|' + // ".class"
15+
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
16+
'(\\))|' + // ")"
17+
'(\\s*,\\s*)', // ","
1818
'g');
1919

2020
/**

‎modules/@angular/compiler/src/template_parser/template_parser.ts‎

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export class TemplateParser {
149149
return new TemplateParseResult(result, errors);
150150
}
151151

152-
if (isPresent(this.transforms)) {
152+
if (this.transforms) {
153153
this.transforms.forEach(
154154
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
155155
}
@@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
218218
visitText(text: html.Text, parent: ElementContext): any {
219219
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
220220
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
221-
if (isPresent(expr)) {
221+
if (expr) {
222222
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
223223
} else {
224224
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
@@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
248248
return null;
249249
}
250250

251-
const matchableAttrs: string[][] = [];
251+
const matchableAttrs: [string, string][] = [];
252252
const elementOrDirectiveProps: BoundProperty[] = [];
253253
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
254254
const elementVars: VariableAst[] = [];
255255
const events: BoundEventAst[] = [];
256256

257257
const templateElementOrDirectiveProps: BoundProperty[] = [];
258-
const templateMatchableAttrs: string[][] = [];
258+
const templateMatchableAttrs: [string, string][] = [];
259259
const templateElementVars: VariableAst[] = [];
260260

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

271-
let templateBindingsSource: string|undefined = undefined;
272-
let prefixToken: string|undefined = undefined;
273-
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
271+
let templateBindingsSource: string|undefined;
272+
let prefixToken: string|undefined;
273+
let normalizedName = this._normalizeAttributeName(attr.name);
274+
275+
if (normalizedName == TEMPLATE_ATTR) {
274276
templateBindingsSource = attr.value;
275-
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
277+
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
276278
templateBindingsSource = attr.value;
277-
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
279+
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
278280
}
281+
279282
const hasTemplateBinding = isPresent(templateBindingsSource);
280283
if (hasTemplateBinding) {
281284
if (hasInlineTemplates) {
@@ -541,10 +544,12 @@ class TemplateParseVisitor implements html.Visitor {
541544
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
542545
const matchedReferences = new Set<string>();
543546
let component: CompileDirectiveSummary = null;
547+
544548
const directiveAsts = directives.map((directive) => {
545549
const sourceSpan = new ParseSourceSpan(
546550
elementSourceSpan.start, elementSourceSpan.end,
547551
`Directive ${identifierName(directive.type)}`);
552+
548553
if (directive.isComponent) {
549554
component = directive;
550555
}
@@ -567,6 +572,7 @@ class TemplateParseVisitor implements html.Visitor {
567572
return new DirectiveAst(
568573
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
569574
});
575+
570576
elementOrDirectiveRefs.forEach((elOrDirRef) => {
571577
if (elOrDirRef.value.length > 0) {
572578
if (!matchedReferences.has(elOrDirRef.name)) {
@@ -581,7 +587,7 @@ class TemplateParseVisitor implements html.Visitor {
581587
}
582588
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
583589
}
584-
}); // fix syntax highlighting issue: `
590+
});
585591
return directiveAsts;
586592
}
587593

@@ -742,7 +748,7 @@ class NonBindableVisitor implements html.Visitor {
742748
return null;
743749
}
744750

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

813819
export function createElementCssSelector(
814-
elementName: string, matchableAttrs: string[][]): CssSelector {
820+
elementName: string, attributes: [string, string][]): CssSelector {
815821
const cssSelector = new CssSelector();
816822
const elNameNoNs = splitNsName(elementName)[1];
817823

818824
cssSelector.setElement(elNameNoNs);
819825

820-
for (let i = 0; i < matchableAttrs.length; i++) {
821-
const attrName = matchableAttrs[i][0];
826+
for (let i = 0; i < attributes.length; i++) {
827+
const attrName = attributes[i][0];
822828
const attrNameNoNs = splitNsName(attrName)[1];
823-
const attrValue = matchableAttrs[i][1];
829+
const attrValue = attributes[i][1];
824830

825831
cssSelector.addAttribute(attrNameNoNs, attrValue);
826832
if (attrName.toLowerCase() == CLASS_ATTR) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Component, Directive, Input} from '@angular/core';
10+
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
11+
import {By} from '@angular/platform-browser/src/dom/debug/by';
12+
import {expect} from '@angular/platform-browser/testing/matchers';
13+
14+
export function main() {
15+
describe('integration tests', () => {
16+
let fixture: ComponentFixture<TestComponent>;
17+
18+
19+
describe('directives', () => {
20+
it('should support dotted selectors', async(() => {
21+
@Directive({selector: '[dot.name]'})
22+
class MyDir {
23+
@Input('dot.name') value: string;
24+
}
25+
26+
TestBed.configureTestingModule({
27+
declarations: [
28+
MyDir,
29+
TestComponent,
30+
],
31+
});
32+
33+
const template = `<div [dot.name]="'foo'"></div>`;
34+
fixture = createTestComponent(template);
35+
fixture.detectChanges();
36+
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
37+
expect(myDir.value).toEqual('foo');
38+
}));
39+
});
40+
41+
});
42+
}
43+
44+
@Component({selector: 'test-cmp', template: ''})
45+
class TestComponent {
46+
}
47+
48+
function createTestComponent(template: string): ComponentFixture<TestComponent> {
49+
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
50+
.createComponent(TestComponent);
51+
}

0 commit comments

Comments
 (0)