mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler-cli): index pipes in template expressions
Update the template indexer to discover and record pipes used in template expressions. This associates template pipe identifiers with their target pipe class declarations, enabling indexers and language tooling to properly resolve and cross-reference pipes.
This commit is contained in:
committed by
Alex Rickabaugh
parent
3064f3f1dc
commit
74c1716cef
@@ -1160,6 +1160,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
|
|||||||
if (analysis.isPoisoned && !this.usePoisonedData) {
|
if (analysis.isPoisoned && !this.usePoisonedData) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const typeCheckScope = this.typeCheckScopeRegistry.getTypeCheckScope(new Reference(node));
|
||||||
const scope = this.scopeReader.getScopeForComponent(node);
|
const scope = this.scopeReader.getScopeForComponent(node);
|
||||||
const selector = analysis.meta.selector;
|
const selector = analysis.meta.selector;
|
||||||
let matcher: DirectiveMatcher<DirectiveMeta> | null = null;
|
let matcher: DirectiveMatcher<DirectiveMeta> | null = null;
|
||||||
@@ -1210,6 +1211,10 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
|
|||||||
getTemplateAst() {
|
getTemplateAst() {
|
||||||
return boundTemplate.target.template;
|
return boundTemplate.target.template;
|
||||||
},
|
},
|
||||||
|
getPipe(name) {
|
||||||
|
const pipe = typeCheckScope.pipes.get(name);
|
||||||
|
return pipe ? {ref: {node: pipe.ref.node}} : null;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
context.addComponent({
|
context.addComponent({
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export enum IdentifierKind {
|
|||||||
Directive,
|
Directive,
|
||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
|
Pipe,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,6 +157,14 @@ export interface BoundAttributeIdentifier<T = DeclarationNode> extends TemplateI
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Describes a pipe used in a template expression. */
|
||||||
|
export interface PipeIdentifier<T = DeclarationNode> extends TemplateIdentifier {
|
||||||
|
kind: IdentifierKind.Pipe;
|
||||||
|
target: {
|
||||||
|
node: T;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifiers recorded at the top level of the template, without any context about the HTML nodes
|
* Identifiers recorded at the top level of the template, without any context about the HTML nodes
|
||||||
* they were discovered in.
|
* they were discovered in.
|
||||||
@@ -170,7 +179,8 @@ export type TopLevelIdentifier<T = DeclarationNode> =
|
|||||||
| LetDeclarationIdentifier
|
| LetDeclarationIdentifier
|
||||||
| ComponentNodeIdentifier<T>
|
| ComponentNodeIdentifier<T>
|
||||||
| DirectiveNodeIdentifier<T>
|
| DirectiveNodeIdentifier<T>
|
||||||
| BoundAttributeIdentifier<T>;
|
| BoundAttributeIdentifier<T>
|
||||||
|
| PipeIdentifier<T>;
|
||||||
|
|
||||||
/** Identifiers that can bring in directives to the template. */
|
/** Identifiers that can bring in directives to the template. */
|
||||||
export type DirectiveHostIdentifier<T = DeclarationNode> =
|
export type DirectiveHostIdentifier<T = DeclarationNode> =
|
||||||
@@ -227,6 +237,7 @@ export interface AbstractBoundTemplate<T> {
|
|||||||
getExpressionTarget(ast: AST): TmplAstReference | TmplAstVariable | TmplAstLetDeclaration | null;
|
getExpressionTarget(ast: AST): TmplAstReference | TmplAstVariable | TmplAstLetDeclaration | null;
|
||||||
getUsedDirectives(): Array<{ref: {node: T}; isComponent: boolean}>;
|
getUsedDirectives(): Array<{ref: {node: T}; isComponent: boolean}>;
|
||||||
getTemplateAst(): TmplAstNode[] | undefined;
|
getTemplateAst(): TmplAstNode[] | undefined;
|
||||||
|
getPipe(name: string): {ref: {node: T}} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import {
|
import {
|
||||||
AST,
|
AST,
|
||||||
ASTWithSource,
|
ASTWithSource,
|
||||||
|
BindingPipe,
|
||||||
CombinedRecursiveAstVisitor,
|
CombinedRecursiveAstVisitor,
|
||||||
ImplicitReceiver,
|
ImplicitReceiver,
|
||||||
ParseSourceSpan,
|
ParseSourceSpan,
|
||||||
@@ -41,6 +42,7 @@ import {
|
|||||||
IdentifierKind,
|
IdentifierKind,
|
||||||
LetDeclarationIdentifier,
|
LetDeclarationIdentifier,
|
||||||
MethodIdentifier,
|
MethodIdentifier,
|
||||||
|
PipeIdentifier,
|
||||||
PropertyIdentifier,
|
PropertyIdentifier,
|
||||||
ReferenceIdentifier,
|
ReferenceIdentifier,
|
||||||
TemplateNodeIdentifier,
|
TemplateNodeIdentifier,
|
||||||
@@ -148,6 +150,41 @@ class TemplateVisitor<T = DeclarationNode> extends CombinedRecursiveAstVisitor {
|
|||||||
super.visitPropertyRead(ast, null);
|
super.visitPropertyRead(ast, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override visitPipe(ast: BindingPipe): void {
|
||||||
|
this.visitPipeIdentifier(ast);
|
||||||
|
super.visitPipe(ast, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private visitPipeIdentifier(ast: BindingPipe): void {
|
||||||
|
if (this.currentAstWithSource === null || this.currentAstWithSource.source === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {absoluteOffset, source: expressionStr} = this.currentAstWithSource;
|
||||||
|
const identifierStart = ast.nameSpan.start - absoluteOffset;
|
||||||
|
|
||||||
|
if (!expressionStr.startsWith(ast.name, identifierStart)) {
|
||||||
|
this.errors.push(
|
||||||
|
new Error(
|
||||||
|
`Impossible state: "${ast.name}" not found in "${expressionStr}" at location ${identifierStart}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const absoluteStart = absoluteOffset + identifierStart;
|
||||||
|
const span = new AbsoluteSourceSpan(absoluteStart, absoluteStart + ast.name.length);
|
||||||
|
const target = this.boundTemplate.getPipe(ast.name);
|
||||||
|
const identifier: PipeIdentifier<T> = {
|
||||||
|
name: ast.name,
|
||||||
|
span,
|
||||||
|
kind: IdentifierKind.Pipe,
|
||||||
|
target: target ? {node: target.ref.node} : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.identifiers.add(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
override visitBoundAttribute(attribute: TmplAstBoundAttribute): void {
|
override visitBoundAttribute(attribute: TmplAstBoundAttribute): void {
|
||||||
const identifier = this.bindingToIdentifier(attribute, IdentifierKind.Input);
|
const identifier = this.bindingToIdentifier(attribute, IdentifierKind.Input);
|
||||||
if (identifier !== null) {
|
if (identifier !== null) {
|
||||||
|
|||||||
@@ -1326,5 +1326,49 @@ runInEachFileSystem(() => {
|
|||||||
);
|
);
|
||||||
expect(inputOrOutputRefs).toEqual([]);
|
expect(inputOrOutputRefs).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should discover pipes in template expressions', () => {
|
||||||
|
const pipe = `
|
||||||
|
export class MyPipe {
|
||||||
|
transform(v: any) { return v; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const pipeDecl = util.getComponentDeclaration(pipe, 'MyPipe');
|
||||||
|
const template = '{{ foo | myPipe }}';
|
||||||
|
const boundTemplate = util.getBoundTemplate(
|
||||||
|
template,
|
||||||
|
{},
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'myPipe',
|
||||||
|
declaration: pipeDecl,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const refs = Array.from(getTemplateIdentifiers(boundTemplate));
|
||||||
|
|
||||||
|
expect(refs).toContain({
|
||||||
|
name: 'myPipe',
|
||||||
|
kind: IdentifierKind.Pipe,
|
||||||
|
span: new AbsoluteSourceSpan(9, 15),
|
||||||
|
target: {
|
||||||
|
node: pipeDecl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unresolvable pipes gracefully', () => {
|
||||||
|
const template = '{{ foo | unknownPipe }}';
|
||||||
|
const boundTemplate = util.getBoundTemplate(template);
|
||||||
|
const refs = Array.from(getTemplateIdentifiers(boundTemplate));
|
||||||
|
|
||||||
|
expect(refs).toContain({
|
||||||
|
name: 'unknownPipe',
|
||||||
|
kind: IdentifierKind.Pipe,
|
||||||
|
span: new AbsoluteSourceSpan(9, 20),
|
||||||
|
target: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ export function getBoundTemplate(
|
|||||||
inputs?: Record<string, string>;
|
inputs?: Record<string, string>;
|
||||||
outputs?: Record<string, string>;
|
outputs?: Record<string, string>;
|
||||||
}> = [],
|
}> = [],
|
||||||
|
pipes: Array<{
|
||||||
|
name: string;
|
||||||
|
declaration: ClassDeclaration;
|
||||||
|
}> = [],
|
||||||
): AbstractBoundTemplate<DeclarationNode> {
|
): AbstractBoundTemplate<DeclarationNode> {
|
||||||
const componentsMeta = components.map(({selector, declaration, inputs = {}, outputs = {}}) => ({
|
const componentsMeta = components.map(({selector, declaration, inputs = {}, outputs = {}}) => ({
|
||||||
ref: new Reference(declaration),
|
ref: new Reference(declaration),
|
||||||
@@ -130,6 +134,10 @@ export function getBoundTemplate(
|
|||||||
getTemplateAst() {
|
getTemplateAst() {
|
||||||
return boundTemplate.target.template;
|
return boundTemplate.target.template;
|
||||||
},
|
},
|
||||||
|
getPipe(name) {
|
||||||
|
const pipe = pipes.find((p) => p.name === name);
|
||||||
|
return pipe ? {ref: {node: pipe.declaration}} : null;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return abstractBoundTemplate;
|
return abstractBoundTemplate;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user