/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/compiler-cli/src/ngtsc/indexer/test/template_spec.ts
1 200 строк
37 KB
Andrew Scott
refactor(compiler): Update indexer API to be generic
08 май 2026, 01:16
08 май 2026, 01:16
bc655d0
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC 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.dev/license */ import {BoundTarget} from '@angular/compiler'; import { AbsoluteSourceSpan, AbstractBoundTemplate, AttributeIdentifier, DirectiveHostIdentifier, ElementIdentifier, IdentifierKind, LetDeclarationIdentifier, ReferenceIdentifier, TemplateNodeIdentifier, TopLevelIdentifier, VariableIdentifier, } from '..'; import {runInEachFileSystem} from '../../file_system/testing'; import {ComponentMeta} from '../src/context'; import {getTemplateIdentifiers as getTemplateIdentifiersAndErrors} from '../src/template'; import * as util from './util'; import {DeclarationNode} from '../../reflection'; function bind(template: string, enableSelectorless = false) { return util.getBoundTemplate(template, { preserveWhitespaces: true, leadingTriviaChars: [], enableSelectorless, }); } function getTemplateIdentifiers(boundTemplate: AbstractBoundTemplate<DeclarationNode>) { return getTemplateIdentifiersAndErrors(boundTemplate).identifiers; } runInEachFileSystem(() => { describe('getTemplateIdentifiers', () => { it('should handle svg elements', () => { const template = '<svg></svg>'; const refs = getTemplateIdentifiers(bind(template)); const [ref] = Array.from(refs); expect(ref).toEqual({ kind: IdentifierKind.Element, name: 'svg', span: new AbsoluteSourceSpan(1, 4), usedDirectives: new Set(), attributes: new Set(), }); }); it('should handle svg elements on templates', () => { const template = '<svg *ngIf="true"></svg>'; const refs = getTemplateIdentifiers(bind(template)); const [ref] = Array.from(refs); expect(ref).toEqual({ kind: IdentifierKind.Template, name: 'svg', span: new AbsoluteSourceSpan(1, 4), usedDirectives: new Set(), attributes: new Set(), }); }); it('should handle comments in interpolations', () => { const template = '{{foo // comment}}'; const refs = getTemplateIdentifiers(bind(template)); const [ref] = Array.from(refs); expect(ref).toEqual({ name: 'foo', kind: IdentifierKind.Property, span: new AbsoluteSourceSpan(2, 5), target: null, }); }); it('should handle whitespace and comments in interpolations', () => { const template = '{{ foo // comment }}'; const refs = getTemplateIdentifiers(bind(template)); const [ref] = Array.from(refs); expect(ref).toEqual({ name: 'foo', kind: IdentifierKind.Property, span: new AbsoluteSourceSpan(5, 8), target: null, }); }); it('works when structural directives are on templates', () => { const template = '<ng-template *ngIf="true">'; const refs = getTemplateIdentifiers(bind(template)); const [ref] = Array.from(refs); expect(ref).toEqual({ kind: IdentifierKind.Template, name: 'ng-template', span: new AbsoluteSourceSpan(1, 12), usedDirectives: new Set(), attributes: new Set(), }); }); 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 component properties read using "this" as a receiver', () => { const template = '{{this.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(7, 10), 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 handle bound attributes with no value', () => { const template = '<div [bar]></div>'; const refs = getTemplateIdentifiers(bind(template)); const refArr = Array.from(refs); expect(refArr).toEqual([ { name: 'div', kind: IdentifierKind.Element, span: new AbsoluteSourceSpan(1, 4), attributes: new Set(), usedDirectives: new Set(), }, ]); }); 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 structural directive', () => { 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, }); }); it('should discover properties in template expressions and resist collisions', () => { const template = '<div *ngFor="let foo of (foos ? foos : foos)"></div>'; const refs = getTemplateIdentifiers(bind(template)); const refArr = Array.from(refs); expect(refArr).toEqual( jasmine.arrayContaining([ { name: 'foos', kind: IdentifierKind.Property, span: new AbsoluteSourceSpan(25, 29), target: null, }, { name: 'foos', kind: IdentifierKind.Property, span: new AbsoluteSourceSpan(32, 36), target: null, }, { name: 'foos', kind: IdentifierKind.Property, span: new AbsoluteSourceSpan(39, 43), target: null, }, ]), ); }); }); describe('generates identifiers for property writes', () => { 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 method calls', () => { 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.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 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.Property, 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.Property, 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 used in an interpolation', () => { 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 used in an event binding', () => { 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 used in an event binding', () => { 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('let declarations', () => { it('should discover references to let declaration', () => { const template = `@let foo = 123; <div [someInput]="foo"></div>`; const refs = getTemplateIdentifiers(bind(template)); const letIdentifier: LetDeclarationIdentifier = { name: 'foo', kind: IdentifierKind.LetDeclaration, span: new AbsoluteSourceSpan(5, 8), }; expect(Array.from(refs)).toEqual( jasmine.arrayContaining([ letIdentifier, { name: 'foo', kind: IdentifierKind.Property, span: new AbsoluteSourceSpan(34, 37), target: letIdentifier, }, ]), ); }); }); 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)', }, ]), ); }); it('should handle interpolations in attributes, preceded by HTML entity', () => { const template = `<img src=" {{foo}}" />`; const refs = getTemplateIdentifiers(bind(template)); expect(Array.from(refs)).toEqual([ { kind: IdentifierKind.Element, name: 'img', span: new AbsoluteSourceSpan(1, 4), usedDirectives: new Set(), attributes: new Set(), }, { kind: IdentifierKind.Property, name: 'foo', span: new AbsoluteSourceSpan(18, 21), target: null, }, ]); }); }); describe('selectorless', () => { it('should generate information about selectorless component nodes', () => { const compDecl = util.getComponentDeclaration('class Comp {}', 'Comp'); const fooDecl = util.getComponentDeclaration('class Foo {}', 'Foo'); const barDecl = util.getComponentDeclaration('class Bar {}', 'Bar'); const template = '<Comp @Foo @Bar([input]="value")/>'; const boundTemplate = util.getBoundTemplate( template, { enableSelectorless: true, }, [ {selector: null, declaration: compDecl}, {selector: null, declaration: fooDecl}, {selector: null, declaration: barDecl}, ], ); const refs = getTemplateIdentifiers(boundTemplate); expect(Array.from(refs)).toEqual([ { name: 'Comp', span: new AbsoluteSourceSpan(1, 5), kind: IdentifierKind.Component, attributes: new Set(), usedDirectives: new Set([ { node: compDecl, selector: null, }, ]), }, { name: 'Foo', span: new AbsoluteSourceSpan(7, 10), kind: IdentifierKind.Directive, attributes: new Set(), usedDirectives: new Set([ { node: fooDecl, selector: null, }, ]), }, { name: 'Bar', span: new AbsoluteSourceSpan(12, 15), kind: IdentifierKind.Directive, attributes: new Set(), usedDirectives: new Set([ { node: barDecl, selector: null, }, ]), }, { name: 'value', span: new AbsoluteSourceSpan(25, 30), kind: IdentifierKind.Property, target: null, }, ]); }); it('should generate information about selectorless directives used on a plain element', () => { const fooDecl = util.getComponentDeclaration('class Foo {}', 'Foo'); const barDecl = util.getComponentDeclaration('class Bar {}', 'Bar'); const template = '<div @Foo @Bar([input]="value")></div>'; const boundTemplate = util.getBoundTemplate( template, { enableSelectorless: true, }, [ {selector: null, declaration: fooDecl}, {selector: null, declaration: barDecl}, ], ); const refs = getTemplateIdentifiers(boundTemplate); expect(Array.from(refs)).toEqual([ { name: 'div', span: new AbsoluteSourceSpan(1, 4), kind: IdentifierKind.Element, attributes: new Set(), usedDirectives: new Set(), }, { name: 'Foo', span: new AbsoluteSourceSpan(6, 9), kind: IdentifierKind.Directive, attributes: new Set(), usedDirectives: new Set([ { node: fooDecl, selector: null, }, ]), }, { name: 'Bar', span: new AbsoluteSourceSpan(11, 14), kind: IdentifierKind.Directive, attributes: new Set(), usedDirectives: new Set([ { node: barDecl, selector: null, }, ]), }, { name: 'value', span: new AbsoluteSourceSpan(24, 29), kind: IdentifierKind.Property, target: null, }, ]); }); it('should discover references to selectorless components and directives', () => { const compDecl = util.getComponentDeclaration('class Comp {}', 'Comp'); const fooDecl = util.getComponentDeclaration('class Foo {}', 'Foo'); const template = '<Comp #comp @Foo(#foo)/>'; const boundTemplate = util.getBoundTemplate( template, { enableSelectorless: true, }, [ {selector: null, declaration: compDecl}, {selector: null, declaration: fooDecl}, ], ); const refs = Array.from(getTemplateIdentifiers(boundTemplate)); const [compRef, fooRef] = refs as [ DirectiveHostIdentifier, DirectiveHostIdentifier, ...unknown[], ]; expect(refs).toEqual([ { name: 'Comp', span: new AbsoluteSourceSpan(1, 5), kind: IdentifierKind.Component, attributes: new Set(), usedDirectives: new Set([ { node: compDecl, selector: null, }, ]), }, { name: 'Foo', span: new AbsoluteSourceSpan(13, 16), kind: IdentifierKind.Directive, attributes: new Set(), usedDirectives: new Set([ { node: fooDecl, selector: null, }, ]), }, { name: 'foo', span: new AbsoluteSourceSpan(18, 21), kind: IdentifierKind.Reference, target: { node: fooRef, directive: fooDecl, }, }, { name: 'comp', span: new AbsoluteSourceSpan(7, 11), kind: IdentifierKind.Reference, target: { node: compRef, directive: compDecl, }, }, ]); }); }); });