feat(ivy): index template elements for selectors, attributes, directives (#31240)
Add support for indexing elements in the indexing module. Opening and self-closing HTML tags have their selector indexed, as well as the attributes on the element and the directives applied to an element. PR Close #31240
This commit is contained in:
parent
c4c340a7c4
commit
604d9063c5
|
@ -15,13 +15,8 @@ import * as ts from 'typescript';
|
|||
export enum IdentifierKind {
|
||||
Property,
|
||||
Method,
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the absolute byte offsets of a text anchor in a source code.
|
||||
*/
|
||||
export class AbsoluteSourceSpan {
|
||||
constructor(public start: number, public end: number) {}
|
||||
Element,
|
||||
Attribute,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,6 +29,43 @@ export interface TemplateIdentifier {
|
|||
kind: IdentifierKind;
|
||||
}
|
||||
|
||||
/** Describes a property accessed in a template. */
|
||||
export interface PropertyIdentifier extends TemplateIdentifier { kind: IdentifierKind.Property; }
|
||||
|
||||
/** Describes a method accessed in a template. */
|
||||
export interface MethodIdentifier extends TemplateIdentifier { kind: IdentifierKind.Method; }
|
||||
|
||||
/** Describes an element attribute in a template. */
|
||||
export interface AttributeIdentifier extends TemplateIdentifier { kind: IdentifierKind.Attribute; }
|
||||
|
||||
/**
|
||||
* Describes an indexed element in a template. The name of an `ElementIdentifier` is the entire
|
||||
* element tag, which can be parsed by an indexer to determine where used directives should be
|
||||
* referenced.
|
||||
*/
|
||||
export interface ElementIdentifier extends TemplateIdentifier {
|
||||
kind: IdentifierKind.Element;
|
||||
|
||||
/** Attributes on an element. */
|
||||
attributes: Set<AttributeIdentifier>;
|
||||
|
||||
/** Directives applied to an element. */
|
||||
usedDirectives: Set<ts.Declaration>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifiers recorded at the top level of the template, without any context about the HTML nodes
|
||||
* they were discovered in.
|
||||
*/
|
||||
export type TopLevelIdentifier = PropertyIdentifier | MethodIdentifier | ElementIdentifier;
|
||||
|
||||
/**
|
||||
* Describes the absolute byte offsets of a text anchor in a source code.
|
||||
*/
|
||||
export class AbsoluteSourceSpan {
|
||||
constructor(public start: number, public end: number) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an analyzed, indexed component and its template.
|
||||
*/
|
||||
|
@ -42,7 +74,7 @@ export interface IndexedComponent {
|
|||
selector: string|null;
|
||||
file: ParseSourceFile;
|
||||
template: {
|
||||
identifiers: Set<TemplateIdentifier>,
|
||||
identifiers: Set<TopLevelIdentifier>,
|
||||
usedComponents: Set<ts.Declaration>,
|
||||
isInline: boolean,
|
||||
file: ParseSourceFile;
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, BoundTarget, DirectiveMeta, ImplicitReceiver, MethodCall, PropertyRead, RecursiveAstVisitor} from '@angular/compiler';
|
||||
import {AST, BoundTarget, ImplicitReceiver, MethodCall, PropertyRead, RecursiveAstVisitor} from '@angular/compiler';
|
||||
import {BoundText, Element, Node, RecursiveVisitor as RecursiveTemplateVisitor, Template} from '@angular/compiler/src/render3/r3_ast';
|
||||
import {AbsoluteSourceSpan, IdentifierKind, TemplateIdentifier} from './api';
|
||||
import {AbsoluteSourceSpan, AttributeIdentifier, ElementIdentifier, IdentifierKind, MethodIdentifier, PropertyIdentifier, TemplateIdentifier, TopLevelIdentifier} from './api';
|
||||
import {ComponentMeta} from './context';
|
||||
|
||||
/**
|
||||
* A parsed node in a template, which may have a name (if it is a selector) or
|
||||
|
@ -19,20 +20,22 @@ interface HTMLNode extends Node {
|
|||
name?: string;
|
||||
}
|
||||
|
||||
type ExpressionIdentifier = PropertyIdentifier | MethodIdentifier;
|
||||
|
||||
/**
|
||||
* Visits the AST of an Angular template syntax expression, finding interesting
|
||||
* entities (variable references, etc.). Creates an array of Entities found in
|
||||
* the expression, with the location of the Entities being relative to the
|
||||
* expression.
|
||||
*
|
||||
* Visiting `text {{prop}}` will return `[TemplateIdentifier {name: 'prop', span: {start: 7, end:
|
||||
* 11}}]`.
|
||||
* Visiting `text {{prop}}` will return
|
||||
* `[TopLevelIdentifier {name: 'prop', span: {start: 7, end: 11}}]`.
|
||||
*/
|
||||
class ExpressionVisitor extends RecursiveAstVisitor {
|
||||
readonly identifiers: TemplateIdentifier[] = [];
|
||||
readonly identifiers: ExpressionIdentifier[] = [];
|
||||
|
||||
private constructor(
|
||||
context: Node, private readonly boundTemplate: BoundTarget<DirectiveMeta>,
|
||||
context: Node, private readonly boundTemplate: BoundTarget<ComponentMeta>,
|
||||
private readonly expressionStr = context.sourceSpan.toString(),
|
||||
private readonly absoluteOffset = context.sourceSpan.start.offset) {
|
||||
super();
|
||||
|
@ -46,8 +49,8 @@ class ExpressionVisitor extends RecursiveAstVisitor {
|
|||
* @param boundTemplate bound target of the entire template, which can be used to query for the
|
||||
* entities expressions target.
|
||||
*/
|
||||
static getIdentifiers(ast: AST, context: Node, boundTemplate: BoundTarget<DirectiveMeta>):
|
||||
TemplateIdentifier[] {
|
||||
static getIdentifiers(ast: AST, context: Node, boundTemplate: BoundTarget<ComponentMeta>):
|
||||
TopLevelIdentifier[] {
|
||||
const visitor = new ExpressionVisitor(context, boundTemplate);
|
||||
visitor.visit(ast);
|
||||
return visitor.identifiers;
|
||||
|
@ -71,7 +74,8 @@ class ExpressionVisitor extends RecursiveAstVisitor {
|
|||
* @param ast expression AST the identifier is in
|
||||
* @param kind identifier kind
|
||||
*/
|
||||
private visitIdentifier(ast: AST&{name: string, receiver: AST}, kind: IdentifierKind) {
|
||||
private visitIdentifier(
|
||||
ast: AST&{name: string, receiver: AST}, kind: ExpressionIdentifier['kind']) {
|
||||
// The definition of a non-top-level property such as `bar` in `{{foo.bar}}` is currently
|
||||
// impossible to determine by an indexer and unsupported by the indexing module.
|
||||
// The indexing module also does not currently support references to identifiers declared in the
|
||||
|
@ -86,6 +90,9 @@ class ExpressionVisitor extends RecursiveAstVisitor {
|
|||
// useful to the indexer. For example, a `MethodCall` `foo(a, b)` will record the span of the
|
||||
// entire method call, but the indexer is interested only in the method identifier.
|
||||
const localExpression = this.expressionStr.substr(ast.span.start, ast.span.end);
|
||||
if (!localExpression.includes(ast.name)) {
|
||||
throw new Error(`Impossible state: "${ast.name}" not found in "${localExpression}"`);
|
||||
}
|
||||
const identifierStart = ast.span.start + localExpression.indexOf(ast.name);
|
||||
|
||||
// Join the relative position of the expression within a node with the absolute position
|
||||
|
@ -93,11 +100,7 @@ class ExpressionVisitor extends RecursiveAstVisitor {
|
|||
const absoluteStart = this.absoluteOffset + identifierStart;
|
||||
const span = new AbsoluteSourceSpan(absoluteStart, absoluteStart + ast.name.length);
|
||||
|
||||
this.identifiers.push({
|
||||
name: ast.name,
|
||||
span,
|
||||
kind,
|
||||
});
|
||||
this.identifiers.push({ name: ast.name, span, kind, } as ExpressionIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +110,7 @@ class ExpressionVisitor extends RecursiveAstVisitor {
|
|||
*/
|
||||
class TemplateVisitor extends RecursiveTemplateVisitor {
|
||||
// identifiers of interest found in the template
|
||||
readonly identifiers = new Set<TemplateIdentifier>();
|
||||
readonly identifiers = new Set<TopLevelIdentifier>();
|
||||
|
||||
/**
|
||||
* Creates a template visitor for a bound template target. The bound target can be used when
|
||||
|
@ -115,7 +118,7 @@ class TemplateVisitor extends RecursiveTemplateVisitor {
|
|||
*
|
||||
* @param boundTemplate bound template target
|
||||
*/
|
||||
constructor(private boundTemplate: BoundTarget<DirectiveMeta>) { super(); }
|
||||
constructor(private boundTemplate: BoundTarget<ComponentMeta>) { super(); }
|
||||
|
||||
/**
|
||||
* Visits a node in the template.
|
||||
|
@ -126,8 +129,40 @@ class TemplateVisitor extends RecursiveTemplateVisitor {
|
|||
|
||||
visitAll(nodes: Node[]) { nodes.forEach(node => this.visit(node)); }
|
||||
|
||||
/**
|
||||
* Add an identifier for an HTML element and visit its children recursively.
|
||||
*
|
||||
* @param element
|
||||
*/
|
||||
visitElement(element: Element) {
|
||||
this.visitAll(element.attributes);
|
||||
// Record the element's attributes, which an indexer can later traverse to see if any of them
|
||||
// specify a used directive on the element.
|
||||
const attributes = element.attributes.map(({name, value, sourceSpan}): AttributeIdentifier => {
|
||||
return {
|
||||
name,
|
||||
span: new AbsoluteSourceSpan(sourceSpan.start.offset, sourceSpan.end.offset),
|
||||
kind: IdentifierKind.Attribute,
|
||||
};
|
||||
});
|
||||
const usedDirectives = this.boundTemplate.getDirectivesOfNode(element) || [];
|
||||
const {name, sourceSpan} = element;
|
||||
// An element's source span can be of the form `<element>`, `<element />`, or
|
||||
// `<element></element>`. Only the selector is interesting to the indexer, so the source is
|
||||
// searched for the first occurrence of the element (selector) name.
|
||||
const localStr = sourceSpan.toString();
|
||||
if (!localStr.includes(name)) {
|
||||
throw new Error(`Impossible state: "${name}" not found in "${localStr}"`);
|
||||
}
|
||||
const start = sourceSpan.start.offset + localStr.indexOf(name);
|
||||
const elId: ElementIdentifier = {
|
||||
name,
|
||||
span: new AbsoluteSourceSpan(start, start + name.length),
|
||||
kind: IdentifierKind.Element,
|
||||
attributes: new Set(attributes),
|
||||
usedDirectives: new Set(usedDirectives.map(dir => dir.ref.node)),
|
||||
};
|
||||
this.identifiers.add(elId);
|
||||
|
||||
this.visitAll(element.children);
|
||||
this.visitAll(element.references);
|
||||
}
|
||||
|
@ -142,7 +177,7 @@ class TemplateVisitor extends RecursiveTemplateVisitor {
|
|||
/**
|
||||
* Visits a node's expression and adds its identifiers, if any, to the visitor's state.
|
||||
*
|
||||
* @param curretNode node whose expression to visit
|
||||
* @param node node whose expression to visit
|
||||
*/
|
||||
private visitExpression(node: Node&{value: AST}) {
|
||||
const identifiers = ExpressionVisitor.getIdentifiers(node.value, node, this.boundTemplate);
|
||||
|
@ -156,8 +191,8 @@ class TemplateVisitor extends RecursiveTemplateVisitor {
|
|||
* @param boundTemplate bound template target, which can be used for querying expression targets.
|
||||
* @return identifiers in template
|
||||
*/
|
||||
export function getTemplateIdentifiers(boundTemplate: BoundTarget<DirectiveMeta>):
|
||||
Set<TemplateIdentifier> {
|
||||
export function getTemplateIdentifiers(boundTemplate: BoundTarget<ComponentMeta>):
|
||||
Set<TopLevelIdentifier> {
|
||||
const visitor = new TemplateVisitor(boundTemplate);
|
||||
if (boundTemplate.target.template !== undefined) {
|
||||
visitor.visitAll(boundTemplate.target.template);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbsoluteSourceSpan, IdentifierKind} from '..';
|
||||
import {AbsoluteSourceSpan, AttributeIdentifier, ElementIdentifier, IdentifierKind} from '..';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {getTemplateIdentifiers} from '../src/template';
|
||||
import * as util from './util';
|
||||
|
@ -20,8 +20,8 @@ function bind(template: string) {
|
|||
|
||||
runInEachFileSystem(() => {
|
||||
describe('getTemplateIdentifiers', () => {
|
||||
it('should generate nothing in HTML-only template', () => {
|
||||
const refs = getTemplateIdentifiers(bind('<div></div>'));
|
||||
it('should generate nothing in empty template', () => {
|
||||
const refs = getTemplateIdentifiers(bind(''));
|
||||
|
||||
expect(refs.size).toBe(0);
|
||||
});
|
||||
|
@ -33,14 +33,14 @@ runInEachFileSystem(() => {
|
|||
});
|
||||
|
||||
it('should handle arbitrary whitespace', () => {
|
||||
const template = '<div>\n\n {{foo}}</div>';
|
||||
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(12, 15),
|
||||
span: new AbsoluteSourceSpan(7, 10),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -75,11 +75,11 @@ runInEachFileSystem(() => {
|
|||
const refs = getTemplateIdentifiers(bind(template));
|
||||
|
||||
const refArr = Array.from(refs);
|
||||
expect(refArr).toEqual(jasmine.arrayContaining([{
|
||||
expect(refArr).toContain({
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(13, 16),
|
||||
}]));
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore identifiers that are not implicitly received by the template', () => {
|
||||
|
@ -111,11 +111,11 @@ runInEachFileSystem(() => {
|
|||
const refs = getTemplateIdentifiers(bind(template));
|
||||
|
||||
const refArr = Array.from(refs);
|
||||
expect(refArr).toEqual(jasmine.arrayContaining([{
|
||||
expect(refArr).toContain({
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Method,
|
||||
span: new AbsoluteSourceSpan(13, 16),
|
||||
}]));
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore identifiers that are not implicitly received by the template', () => {
|
||||
|
@ -127,5 +127,127 @@ runInEachFileSystem(() => {
|
|||
expect(ref.name).toBe('foo');
|
||||
});
|
||||
});
|
||||
|
||||
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([declA, declB, declC]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
*/
|
||||
import {BoundTarget, ParseSourceFile} from '@angular/compiler';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {DirectiveMeta} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {IndexingContext} from '../src/context';
|
||||
import {ComponentMeta, IndexingContext} from '../src/context';
|
||||
import {getTemplateIdentifiers} from '../src/template';
|
||||
import {generateAnalysis} from '../src/transform';
|
||||
import * as util from './util';
|
||||
|
@ -19,7 +18,7 @@ import * as util from './util';
|
|||
*/
|
||||
function populateContext(
|
||||
context: IndexingContext, component: ClassDeclaration, selector: string, template: string,
|
||||
boundTemplate: BoundTarget<DirectiveMeta>, isInline: boolean = false) {
|
||||
boundTemplate: BoundTarget<ComponentMeta>, isInline: boolean = false) {
|
||||
context.addComponent({
|
||||
declaration: component,
|
||||
selector,
|
||||
|
|
|
@ -10,9 +10,9 @@ import {BoundTarget, CssSelector, ParseTemplateOptions, R3TargetBinder, Selector
|
|||
import * as ts from 'typescript';
|
||||
import {AbsoluteFsPath, absoluteFrom} from '../../file_system';
|
||||
import {Reference} from '../../imports';
|
||||
import {DirectiveMeta} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {ComponentMeta} from '../src/context';
|
||||
|
||||
/** Dummy file URL */
|
||||
export function getTestFilePath(): AbsoluteFsPath {
|
||||
|
@ -41,16 +41,12 @@ export function getComponentDeclaration(componentStr: string, className: string)
|
|||
export function getBoundTemplate(
|
||||
template: string, options: ParseTemplateOptions = {},
|
||||
components: Array<{selector: string, declaration: ClassDeclaration}> =
|
||||
[]): BoundTarget<DirectiveMeta> {
|
||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||
[]): BoundTarget<ComponentMeta> {
|
||||
const matcher = new SelectorMatcher<ComponentMeta>();
|
||||
components.forEach(({selector, declaration}) => {
|
||||
matcher.addSelectables(CssSelector.parse(selector), {
|
||||
ref: new Reference(declaration),
|
||||
selector,
|
||||
queries: [],
|
||||
ngTemplateGuards: [],
|
||||
hasNgTemplateContextGuard: false,
|
||||
baseClass: null,
|
||||
name: declaration.name.getText(),
|
||||
isComponent: true,
|
||||
inputs: {},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
import {AbsoluteFsPath, resolve} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
||||
import {AbsoluteSourceSpan, IdentifierKind} from '@angular/compiler-cli/src/ngtsc/indexer';
|
||||
import {AbsoluteSourceSpan, IdentifierKind, TopLevelIdentifier} from '@angular/compiler-cli/src/ngtsc/indexer';
|
||||
import {ParseSourceFile} from '@angular/compiler/src/compiler';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
|
@ -66,7 +66,7 @@ runInEachFileSystem(() => {
|
|||
const template = indexedComp.template;
|
||||
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
identifiers: new Set<TopLevelIdentifier>([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(127, 130),
|
||||
|
@ -93,7 +93,7 @@ runInEachFileSystem(() => {
|
|||
const template = indexedComp.template;
|
||||
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
identifiers: new Set<TopLevelIdentifier>([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(2, 5),
|
||||
|
@ -118,20 +118,20 @@ runInEachFileSystem(() => {
|
|||
})
|
||||
export class TestCmp { foo = 0; }
|
||||
`);
|
||||
env.write(testTemplateFile, '<div> \n {{foo}}</div>');
|
||||
env.write(testTemplateFile, ' \n {{foo}}');
|
||||
const indexed = env.driveIndexer();
|
||||
const [[_, indexedComp]] = Array.from(indexed.entries());
|
||||
const template = indexedComp.template;
|
||||
|
||||
expect(template).toEqual({
|
||||
identifiers: new Set([{
|
||||
identifiers: new Set<TopLevelIdentifier>([{
|
||||
name: 'foo',
|
||||
kind: IdentifierKind.Property,
|
||||
span: new AbsoluteSourceSpan(12, 15),
|
||||
span: new AbsoluteSourceSpan(7, 10),
|
||||
}]),
|
||||
usedComponents: new Set(),
|
||||
isInline: false,
|
||||
file: new ParseSourceFile('<div> \n {{foo}}</div>', testTemplateFile),
|
||||
file: new ParseSourceFile(' \n {{foo}}', testTemplateFile),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue