`TemplateVisitor#visitBoundAttribute` currently has to invoke visiting expressions manually (this is fixed in #31813). Previously, it did not bind `targetToIdentifier` to the visitor before deferring to the expression visitor, which breaks the `targetToIdentifier` code. This fixes that and adds a test to ensure the closure processed correctly. This change is urgent; without it, many indexing targets in g3 are broken. PR Close #31861
761 lines
25 KiB
TypeScript
761 lines
25 KiB
TypeScript
/**
|
|
* @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 {AbsoluteSourceSpan, AttributeIdentifier, ElementIdentifier, IdentifierKind, ReferenceIdentifier, TemplateNodeIdentifier, TopLevelIdentifier, VariableIdentifier} from '..';
|
|
import {runInEachFileSystem} from '../../file_system/testing';
|
|
import {getTemplateIdentifiers} from '../src/template';
|
|
import * as util from './util';
|
|
|
|
function bind(template: string) {
|
|
return util.getBoundTemplate(template, {
|
|
preserveWhitespaces: true,
|
|
leadingTriviaChars: [],
|
|
});
|
|
}
|
|
|
|
runInEachFileSystem(() => {
|
|
describe('getTemplateIdentifiers', () => {
|
|
it('should generate nothing in empty template', () => {
|
|
const template = '';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
expect(refs.size).toBe(0);
|
|
});
|
|
|
|
it('should ignore comments', () => {
|
|
const template = '<!-- {{comment}} -->';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
expect(refs.size).toBe(0);
|
|
});
|
|
|
|
it('should handle arbitrary whitespace', () => {
|
|
const template = '\n\n {{foo}}';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref).toEqual({
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(7, 10),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should resist collisions', () => {
|
|
const template = '<div [bar]="bar ? bar : bar"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
{
|
|
name: 'bar',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(12, 15),
|
|
target: null,
|
|
},
|
|
{
|
|
name: 'bar',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(18, 21),
|
|
target: null,
|
|
},
|
|
{
|
|
name: 'bar',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(24, 27),
|
|
target: null,
|
|
},
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
describe('generates identifiers for PropertyReads', () => {
|
|
it('should discover component properties', () => {
|
|
const template = '{{foo}}';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref).toEqual({
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(2, 5),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should discover nested properties', () => {
|
|
const template = '<div><span>{{foo}}</span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(13, 16),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should ignore identifiers that are not implicitly received by the template', () => {
|
|
const template = '{{foo.bar.baz}}';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref.name).toBe('foo');
|
|
});
|
|
|
|
it('should discover properties in bound attributes', () => {
|
|
const template = '<div [bar]="bar"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'bar',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(12, 15),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should discover variables in bound attributes', () => {
|
|
const template = '<div #div [value]="div.innerText"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const elementReference: ElementIdentifier = {
|
|
name: 'div',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 4),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
};
|
|
const reference: ReferenceIdentifier = {
|
|
name: 'div',
|
|
kind: IdentifierKind.Reference,
|
|
span: new AbsoluteSourceSpan(6, 9),
|
|
target: {node: elementReference, directive: null},
|
|
};
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'div',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(19, 22),
|
|
target: reference,
|
|
});
|
|
});
|
|
|
|
it('should discover properties in template expressions', () => {
|
|
const template = '<div [bar]="bar ? bar1 : bar2"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
{
|
|
name: 'bar',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(12, 15),
|
|
target: null,
|
|
},
|
|
{
|
|
name: 'bar1',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(18, 22),
|
|
target: null,
|
|
},
|
|
{
|
|
name: 'bar2',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(25, 29),
|
|
target: null,
|
|
},
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover properties in template expressions', () => {
|
|
const template = '<div *ngFor="let foo of foos"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'foos',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(24, 28),
|
|
target: null,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('generates identifiers for PropertyWrites', () => {
|
|
it('should discover property writes in bound events', () => {
|
|
const template = '<div (click)="foo=bar"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(14, 17),
|
|
target: null,
|
|
},
|
|
{
|
|
name: 'bar',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(18, 21),
|
|
target: null,
|
|
}
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover nested property writes', () => {
|
|
const template = '<div><span (click)="foo=bar"></span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(20, 23),
|
|
target: null,
|
|
}] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should ignore property writes that are not implicitly received by the template', () => {
|
|
const template = '<div><span (click)="foo.bar=baz"></span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
const bar = refArr.find(ref => ref.name.includes('bar'));
|
|
expect(bar).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('generates identifiers for MethodCalls', () => {
|
|
it('should discover component method calls', () => {
|
|
const template = '{{foo()}}';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref).toEqual({
|
|
name: 'foo',
|
|
kind: IdentifierKind.Method,
|
|
span: new AbsoluteSourceSpan(2, 5),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should discover nested properties', () => {
|
|
const template = '<div><span>{{foo()}}</span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'foo',
|
|
kind: IdentifierKind.Method,
|
|
span: new AbsoluteSourceSpan(13, 16),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should ignore identifiers that are not implicitly received by the template', () => {
|
|
const template = '{{foo().bar().baz()}}';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref.name).toBe('foo');
|
|
});
|
|
|
|
it('should discover method calls in bound attributes', () => {
|
|
const template = '<div [bar]="bar()"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'bar',
|
|
kind: IdentifierKind.Method,
|
|
span: new AbsoluteSourceSpan(12, 15),
|
|
target: null,
|
|
});
|
|
});
|
|
|
|
it('should discover method calls in template expressions', () => {
|
|
const template = '<div *ngFor="let foo of foos()"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'foos',
|
|
kind: IdentifierKind.Method,
|
|
span: new AbsoluteSourceSpan(24, 28),
|
|
target: null,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('generates identifiers for template reference variables', () => {
|
|
it('should discover references', () => {
|
|
const template = '<div #foo>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const elementReference: ElementIdentifier = {
|
|
name: 'div',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 4),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
};
|
|
|
|
const refArray = Array.from(refs);
|
|
expect(refArray).toEqual(jasmine.arrayContaining([{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Reference,
|
|
span: new AbsoluteSourceSpan(6, 9),
|
|
target: {node: elementReference, directive: null},
|
|
}] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover nested references', () => {
|
|
const template = '<div><span #foo></span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const elementReference: ElementIdentifier = {
|
|
name: 'span',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(6, 10),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
};
|
|
|
|
const refArray = Array.from(refs);
|
|
expect(refArray).toEqual(jasmine.arrayContaining([{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Reference,
|
|
span: new AbsoluteSourceSpan(12, 15),
|
|
target: {node: elementReference, directive: null},
|
|
}] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover references to references', () => {
|
|
const template = `<div #foo>{{foo.className}}</div>`;
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const elementIdentifier: ElementIdentifier = {
|
|
name: 'div',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 4),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
};
|
|
const referenceIdentifier: ReferenceIdentifier = {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Reference,
|
|
span: new AbsoluteSourceSpan(6, 9),
|
|
target: {node: elementIdentifier, directive: null},
|
|
};
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
elementIdentifier, referenceIdentifier, {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(12, 15),
|
|
target: referenceIdentifier,
|
|
}
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover forward references', () => {
|
|
const template = `{{foo}}<div #foo></div>`;
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const elementIdentifier: ElementIdentifier = {
|
|
name: 'div',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(8, 11),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
};
|
|
const referenceIdentifier: ReferenceIdentifier = {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Reference,
|
|
span: new AbsoluteSourceSpan(13, 16),
|
|
target: {node: elementIdentifier, directive: null},
|
|
};
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
elementIdentifier, referenceIdentifier, {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(2, 5),
|
|
target: referenceIdentifier,
|
|
}
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should generate information directive targets', () => {
|
|
const declB = util.getComponentDeclaration('class B {}', 'B');
|
|
const template = '<div #foo b-selector>';
|
|
const boundTemplate = util.getBoundTemplate(template, {}, [
|
|
{selector: '[b-selector]', declaration: declB},
|
|
]);
|
|
|
|
const refs = getTemplateIdentifiers(boundTemplate);
|
|
const refArr = Array.from(refs);
|
|
let fooRef = refArr.find(id => id.name === 'foo');
|
|
expect(fooRef).toBeDefined();
|
|
expect(fooRef !.kind).toBe(IdentifierKind.Reference);
|
|
|
|
fooRef = fooRef as ReferenceIdentifier;
|
|
expect(fooRef.target).toBeDefined();
|
|
expect(fooRef.target !.node.kind).toBe(IdentifierKind.Element);
|
|
expect(fooRef.target !.node.name).toBe('div');
|
|
expect(fooRef.target !.node.span).toEqual(new AbsoluteSourceSpan(1, 4));
|
|
expect(fooRef.target !.directive).toEqual(declB);
|
|
});
|
|
|
|
it('should discover references to references', () => {
|
|
const template = `<div #foo (ngSubmit)="do(foo)"></div>`;
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const elementIdentifier: ElementIdentifier = {
|
|
name: 'div',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 4),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
};
|
|
const referenceIdentifier: ReferenceIdentifier = {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Reference,
|
|
span: new AbsoluteSourceSpan(6, 9),
|
|
target: {node: elementIdentifier, directive: null},
|
|
};
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
elementIdentifier, referenceIdentifier, {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(25, 28),
|
|
target: referenceIdentifier,
|
|
}
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
});
|
|
|
|
describe('generates identifiers for template variables', () => {
|
|
it('should discover variables', () => {
|
|
const template = '<div *ngFor="let foo of foos">';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArray = Array.from(refs);
|
|
expect(refArray).toEqual(jasmine.arrayContaining([{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Variable,
|
|
span: new AbsoluteSourceSpan(17, 20),
|
|
}] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover variables with let- syntax', () => {
|
|
const template = '<ng-template let-var="classVar">';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArray = Array.from(refs);
|
|
expect(refArray).toEqual(jasmine.arrayContaining([{
|
|
name: 'var',
|
|
kind: IdentifierKind.Variable,
|
|
span: new AbsoluteSourceSpan(17, 20),
|
|
}] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover nested variables', () => {
|
|
const template = '<div><span *ngFor="let foo of foos"></span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArray = Array.from(refs);
|
|
expect(refArray).toEqual(jasmine.arrayContaining([{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Variable,
|
|
span: new AbsoluteSourceSpan(23, 26),
|
|
}] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover references to variables', () => {
|
|
const template = `<div *ngFor="let foo of foos; let i = index">{{foo + i}}</div>`;
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const fooIdentifier: VariableIdentifier = {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Variable,
|
|
span: new AbsoluteSourceSpan(17, 20),
|
|
};
|
|
const iIdentifier: VariableIdentifier = {
|
|
name: 'i',
|
|
kind: IdentifierKind.Variable,
|
|
span: new AbsoluteSourceSpan(34, 35),
|
|
};
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
fooIdentifier,
|
|
{
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(47, 50),
|
|
target: fooIdentifier,
|
|
},
|
|
iIdentifier,
|
|
{
|
|
name: 'i',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(53, 54),
|
|
target: iIdentifier,
|
|
},
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
|
|
it('should discover references to variables', () => {
|
|
const template = `<div *ngFor="let foo of foos" (click)="do(foo)"></div>`;
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
const variableIdentifier: VariableIdentifier = {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Variable,
|
|
span: new AbsoluteSourceSpan(17, 20),
|
|
};
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toEqual(jasmine.arrayContaining([
|
|
variableIdentifier, {
|
|
name: 'foo',
|
|
kind: IdentifierKind.Property,
|
|
span: new AbsoluteSourceSpan(42, 45),
|
|
target: variableIdentifier,
|
|
}
|
|
] as TopLevelIdentifier[]));
|
|
});
|
|
});
|
|
|
|
describe('generates identifiers for elements', () => {
|
|
it('should record elements as ElementIdentifiers', () => {
|
|
const template = '<test-selector>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref.kind).toBe(IdentifierKind.Element);
|
|
});
|
|
|
|
it('should record element names as their selector', () => {
|
|
const template = '<test-selector>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref as ElementIdentifier).toEqual({
|
|
name: 'test-selector',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 14),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should discover selectors in self-closing elements', () => {
|
|
const template = '<img />';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref as ElementIdentifier).toEqual({
|
|
name: 'img',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 4),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should discover selectors in elements with adjacent open and close tags', () => {
|
|
const template = '<test-selector></test-selector>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref as ElementIdentifier).toEqual({
|
|
name: 'test-selector',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 14),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should discover selectors in elements with non-adjacent open and close tags', () => {
|
|
const template = '<test-selector> text </test-selector>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref as ElementIdentifier).toEqual({
|
|
name: 'test-selector',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(1, 14),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should discover nested selectors', () => {
|
|
const template = '<div><span></span></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'span',
|
|
kind: IdentifierKind.Element,
|
|
span: new AbsoluteSourceSpan(6, 10),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should generate information about attributes', () => {
|
|
const template = '<div attrA attrB="val"></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const [ref] = Array.from(refs);
|
|
const attrs = (ref as ElementIdentifier).attributes;
|
|
expect(attrs).toEqual(new Set<AttributeIdentifier>([
|
|
{
|
|
name: 'attrA',
|
|
kind: IdentifierKind.Attribute,
|
|
span: new AbsoluteSourceSpan(5, 10),
|
|
},
|
|
{
|
|
name: 'attrB',
|
|
kind: IdentifierKind.Attribute,
|
|
span: new AbsoluteSourceSpan(11, 22),
|
|
}
|
|
]));
|
|
});
|
|
|
|
it('should generate information about used directives', () => {
|
|
const declA = util.getComponentDeclaration('class A {}', 'A');
|
|
const declB = util.getComponentDeclaration('class B {}', 'B');
|
|
const declC = util.getComponentDeclaration('class C {}', 'C');
|
|
const template = '<a-selector b-selector></a-selector>';
|
|
const boundTemplate = util.getBoundTemplate(template, {}, [
|
|
{selector: 'a-selector', declaration: declA},
|
|
{selector: '[b-selector]', declaration: declB},
|
|
{selector: ':not(never-selector)', declaration: declC},
|
|
]);
|
|
|
|
const refs = getTemplateIdentifiers(boundTemplate);
|
|
const [ref] = Array.from(refs);
|
|
const usedDirectives = (ref as ElementIdentifier).usedDirectives;
|
|
expect(usedDirectives).toEqual(new Set([
|
|
{
|
|
node: declA,
|
|
selector: 'a-selector',
|
|
},
|
|
{
|
|
node: declB,
|
|
selector: '[b-selector]',
|
|
},
|
|
{
|
|
node: declC,
|
|
selector: ':not(never-selector)',
|
|
}
|
|
]));
|
|
});
|
|
});
|
|
|
|
describe('generates identifiers for templates', () => {
|
|
it('should record templates as TemplateNodeIdentifiers', () => {
|
|
const template = '<ng-template>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref.kind).toBe(IdentifierKind.Template);
|
|
});
|
|
|
|
it('should record template names as their tag name', () => {
|
|
const template = '<ng-template>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
expect(refs.size).toBe(1);
|
|
|
|
const [ref] = Array.from(refs);
|
|
expect(ref as TemplateNodeIdentifier).toEqual({
|
|
name: 'ng-template',
|
|
kind: IdentifierKind.Template,
|
|
span: new AbsoluteSourceSpan(1, 12),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should discover nested templates', () => {
|
|
const template = '<div><ng-template></ng-template></div>';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const refArr = Array.from(refs);
|
|
expect(refArr).toContain({
|
|
name: 'ng-template',
|
|
kind: IdentifierKind.Template,
|
|
span: new AbsoluteSourceSpan(6, 17),
|
|
attributes: new Set(),
|
|
usedDirectives: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should generate information about attributes', () => {
|
|
const template = '<ng-template attrA attrB="val">';
|
|
const refs = getTemplateIdentifiers(bind(template));
|
|
|
|
const [ref] = Array.from(refs);
|
|
const attrs = (ref as TemplateNodeIdentifier).attributes;
|
|
expect(attrs).toEqual(new Set<AttributeIdentifier>([
|
|
{
|
|
name: 'attrA',
|
|
kind: IdentifierKind.Attribute,
|
|
span: new AbsoluteSourceSpan(13, 18),
|
|
},
|
|
{
|
|
name: 'attrB',
|
|
kind: IdentifierKind.Attribute,
|
|
span: new AbsoluteSourceSpan(19, 30),
|
|
}
|
|
]));
|
|
});
|
|
|
|
it('should generate information about used directives', () => {
|
|
const declB = util.getComponentDeclaration('class B {}', 'B');
|
|
const declC = util.getComponentDeclaration('class C {}', 'C');
|
|
const template = '<ng-template b-selector>';
|
|
const boundTemplate = util.getBoundTemplate(template, {}, [
|
|
{selector: '[b-selector]', declaration: declB},
|
|
{selector: ':not(never-selector)', declaration: declC},
|
|
]);
|
|
|
|
const refs = getTemplateIdentifiers(boundTemplate);
|
|
const [ref] = Array.from(refs);
|
|
const usedDirectives = (ref as ElementIdentifier).usedDirectives;
|
|
expect(usedDirectives).toEqual(new Set([
|
|
{
|
|
node: declB,
|
|
selector: '[b-selector]',
|
|
},
|
|
{
|
|
node: declC,
|
|
selector: ':not(never-selector)',
|
|
}
|
|
]));
|
|
});
|
|
});
|
|
});
|