mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(compiler): Update indexer API to be generic
Rather than requiring TS AST in the indexer API, this update makes it generic with adapters to provide necessary information. This allows other analysis pipelines that don't use TS AST to work with the indexer.
This commit is contained in:
parent
ed333c3992
commit
bc655d006f
10 changed files with 237 additions and 99 deletions
|
|
@ -72,6 +72,8 @@ import {
|
|||
SemanticDepGraphUpdater,
|
||||
} from '../../../incremental/semantic_graph';
|
||||
import {IndexingContext} from '../../../indexer';
|
||||
import {AbstractBoundTemplate} from '../../../indexer/src/api';
|
||||
|
||||
import {
|
||||
DirectiveMeta,
|
||||
extractDirectiveTypeCheckMeta,
|
||||
|
|
@ -1130,10 +1132,31 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
|
|||
const binder = new R3TargetBinder<DirectiveMeta>(matcher);
|
||||
const boundTemplate = binder.bind({template: analysis.template.diagNodes});
|
||||
|
||||
const abstractBoundTemplate: AbstractBoundTemplate<DeclarationNode> = {
|
||||
getDirectivesOfNode(node) {
|
||||
return boundTemplate.getDirectivesOfNode(node);
|
||||
},
|
||||
getReferenceTarget(node) {
|
||||
return boundTemplate.getReferenceTarget(node);
|
||||
},
|
||||
getExpressionTarget(ast) {
|
||||
return boundTemplate.getExpressionTarget(ast);
|
||||
},
|
||||
getUsedDirectives() {
|
||||
return boundTemplate.getUsedDirectives().map((dir) => ({
|
||||
ref: {node: dir.ref.node},
|
||||
isComponent: dir.isComponent,
|
||||
}));
|
||||
},
|
||||
getTemplateAst() {
|
||||
return boundTemplate.target.template;
|
||||
},
|
||||
};
|
||||
|
||||
context.addComponent({
|
||||
declaration: node,
|
||||
selector,
|
||||
boundTemplate,
|
||||
boundTemplate: abstractBoundTemplate,
|
||||
templateMeta: {
|
||||
isInline: analysis.template.declaration.isInline,
|
||||
file: analysis.template.file,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import {
|
|||
} from '../../incremental';
|
||||
import {SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
|
||||
import {NodeAdapter} from '../../indexer/src/api';
|
||||
import {
|
||||
CompoundMetadataReader,
|
||||
CompoundMetadataRegistry,
|
||||
|
|
@ -919,7 +920,20 @@ export class NgCompiler {
|
|||
const compilation = this.ensureAnalyzed();
|
||||
const context = new IndexingContext();
|
||||
compilation.traitCompiler.index(context);
|
||||
return generateAnalysis(context);
|
||||
|
||||
const adapter: NodeAdapter<DeclarationNode> = {
|
||||
getName(node: DeclarationNode): string {
|
||||
return ts.isClassDeclaration(node) && node.name ? node.name.getText() : '';
|
||||
},
|
||||
getFileName(node: DeclarationNode): string {
|
||||
return node.getSourceFile().fileName;
|
||||
},
|
||||
getContent(node: DeclarationNode): string {
|
||||
return node.getSourceFile().getFullText();
|
||||
},
|
||||
};
|
||||
|
||||
return generateAnalysis(context, adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,9 +6,19 @@
|
|||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ParseSourceFile} from '@angular/compiler';
|
||||
|
||||
import {ClassDeclaration, DeclarationNode} from '../../reflection';
|
||||
import {
|
||||
AST,
|
||||
ParseSourceFile,
|
||||
TmplAstComponent,
|
||||
TmplAstDirective,
|
||||
TmplAstElement,
|
||||
TmplAstLetDeclaration,
|
||||
TmplAstNode,
|
||||
TmplAstReference,
|
||||
TmplAstTemplate,
|
||||
TmplAstVariable,
|
||||
} from '@angular/compiler';
|
||||
import {DeclarationNode} from '../../reflection';
|
||||
|
||||
/**
|
||||
* Describes the kind of identifier found in a template.
|
||||
|
|
@ -37,16 +47,16 @@ export interface TemplateIdentifier {
|
|||
}
|
||||
|
||||
/** Describes a template expression, which may have a template reference or variable target. */
|
||||
interface ExpressionIdentifier extends TemplateIdentifier {
|
||||
interface ExpressionIdentifier<T = DeclarationNode> extends TemplateIdentifier {
|
||||
/**
|
||||
* ReferenceIdentifier or VariableIdentifier in the template that this identifier targets, if
|
||||
* any. If the target is `null`, it points to a declaration on the component class.
|
||||
* */
|
||||
target: ReferenceIdentifier | VariableIdentifier | LetDeclarationIdentifier | null;
|
||||
*/
|
||||
target: ReferenceIdentifier<T> | VariableIdentifier | LetDeclarationIdentifier | null;
|
||||
}
|
||||
|
||||
/** Describes a property accessed in a template. */
|
||||
export interface PropertyIdentifier extends ExpressionIdentifier {
|
||||
export interface PropertyIdentifier<T = DeclarationNode> extends ExpressionIdentifier<T> {
|
||||
kind: IdentifierKind.Property;
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +64,7 @@ export interface PropertyIdentifier extends ExpressionIdentifier {
|
|||
* Describes a method accessed in a template.
|
||||
* @deprecated No longer being used. To be removed.
|
||||
*/
|
||||
export interface MethodIdentifier extends ExpressionIdentifier {
|
||||
export interface MethodIdentifier<T = DeclarationNode> extends ExpressionIdentifier<T> {
|
||||
kind: IdentifierKind.Method;
|
||||
}
|
||||
|
||||
|
|
@ -64,57 +74,63 @@ export interface AttributeIdentifier extends TemplateIdentifier {
|
|||
}
|
||||
|
||||
/** A reference to a directive node and its selector. */
|
||||
interface DirectiveReference {
|
||||
node: ClassDeclaration;
|
||||
interface DirectiveReference<T = DeclarationNode> {
|
||||
node: T;
|
||||
selector: string;
|
||||
}
|
||||
|
||||
/** A base interface for element and template identifiers. */
|
||||
interface BaseDirectiveHostIdentifier extends TemplateIdentifier {
|
||||
interface BaseDirectiveHostIdentifier<T = DeclarationNode> extends TemplateIdentifier {
|
||||
/** Attributes on an element or template. */
|
||||
attributes: Set<AttributeIdentifier>;
|
||||
|
||||
/** Directives applied to an element or template. */
|
||||
usedDirectives: Set<DirectiveReference>;
|
||||
usedDirectives: Set<DirectiveReference<T>>;
|
||||
}
|
||||
/**
|
||||
* 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 BaseDirectiveHostIdentifier {
|
||||
export interface ElementIdentifier<T = DeclarationNode> extends BaseDirectiveHostIdentifier<T> {
|
||||
kind: IdentifierKind.Element;
|
||||
}
|
||||
|
||||
/** Describes an indexed template node in a component template file. */
|
||||
export interface TemplateNodeIdentifier extends BaseDirectiveHostIdentifier {
|
||||
export interface TemplateNodeIdentifier<
|
||||
T = DeclarationNode,
|
||||
> extends BaseDirectiveHostIdentifier<T> {
|
||||
kind: IdentifierKind.Template;
|
||||
}
|
||||
|
||||
/** Describes a selectorless component node in a template file. */
|
||||
export interface ComponentNodeIdentifier extends BaseDirectiveHostIdentifier {
|
||||
export interface ComponentNodeIdentifier<
|
||||
T = DeclarationNode,
|
||||
> extends BaseDirectiveHostIdentifier<T> {
|
||||
kind: IdentifierKind.Component;
|
||||
}
|
||||
|
||||
/** Describes a selectorless directive node in a template file. */
|
||||
export interface DirectiveNodeIdentifier extends BaseDirectiveHostIdentifier {
|
||||
export interface DirectiveNodeIdentifier<
|
||||
T = DeclarationNode,
|
||||
> extends BaseDirectiveHostIdentifier<T> {
|
||||
kind: IdentifierKind.Directive;
|
||||
}
|
||||
|
||||
/** Describes a reference in a template like "foo" in `<div #foo></div>`. */
|
||||
export interface ReferenceIdentifier extends TemplateIdentifier {
|
||||
export interface ReferenceIdentifier<T = DeclarationNode> extends TemplateIdentifier {
|
||||
kind: IdentifierKind.Reference;
|
||||
|
||||
/** The target of this reference. If the target is not known, this is `null`. */
|
||||
target: {
|
||||
/** The template AST node that the reference targets. */
|
||||
node: DirectiveHostIdentifier;
|
||||
node: DirectiveHostIdentifier<T>;
|
||||
|
||||
/**
|
||||
* The directive on `node` that the reference targets. If no directive is targeted, this is
|
||||
* `null`.
|
||||
*/
|
||||
directive: ClassDeclaration | null;
|
||||
directive: T | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
|
@ -132,23 +148,23 @@ export interface LetDeclarationIdentifier extends TemplateIdentifier {
|
|||
* Identifiers recorded at the top level of the template, without any context about the HTML nodes
|
||||
* they were discovered in.
|
||||
*/
|
||||
export type TopLevelIdentifier =
|
||||
| PropertyIdentifier
|
||||
| ElementIdentifier
|
||||
| TemplateNodeIdentifier
|
||||
| ReferenceIdentifier
|
||||
export type TopLevelIdentifier<T = DeclarationNode> =
|
||||
| PropertyIdentifier<T>
|
||||
| ElementIdentifier<T>
|
||||
| TemplateNodeIdentifier<T>
|
||||
| ReferenceIdentifier<T>
|
||||
| VariableIdentifier
|
||||
| MethodIdentifier
|
||||
| MethodIdentifier<T>
|
||||
| LetDeclarationIdentifier
|
||||
| ComponentNodeIdentifier
|
||||
| DirectiveNodeIdentifier;
|
||||
| ComponentNodeIdentifier<T>
|
||||
| DirectiveNodeIdentifier<T>;
|
||||
|
||||
/** Identifiers that can bring in directives to the template. */
|
||||
export type DirectiveHostIdentifier =
|
||||
| ElementIdentifier
|
||||
| TemplateNodeIdentifier
|
||||
| ComponentNodeIdentifier
|
||||
| DirectiveNodeIdentifier;
|
||||
export type DirectiveHostIdentifier<T = DeclarationNode> =
|
||||
| ElementIdentifier<T>
|
||||
| TemplateNodeIdentifier<T>
|
||||
| ComponentNodeIdentifier<T>
|
||||
| DirectiveNodeIdentifier<T>;
|
||||
|
||||
/**
|
||||
* Describes the absolute byte offsets of a text anchor in a source code.
|
||||
|
|
@ -163,15 +179,47 @@ export class AbsoluteSourceSpan {
|
|||
/**
|
||||
* Describes an analyzed, indexed component and its template.
|
||||
*/
|
||||
export interface IndexedComponent {
|
||||
export interface IndexedComponent<T = DeclarationNode> {
|
||||
name: string;
|
||||
selector: string | null;
|
||||
file: ParseSourceFile;
|
||||
template: {
|
||||
identifiers: Set<TopLevelIdentifier>;
|
||||
usedComponents: Set<DeclarationNode>;
|
||||
identifiers: Set<TopLevelIdentifier<T>>;
|
||||
usedComponents: Set<T>;
|
||||
isInline: boolean;
|
||||
file: ParseSourceFile;
|
||||
};
|
||||
errors: Error[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract representation of a bound template, providing methods to query
|
||||
* directives and targets in the template.
|
||||
*/
|
||||
export interface AbstractBoundTemplate<T> {
|
||||
getDirectivesOfNode(
|
||||
node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
|
||||
): Array<{ref: {node: T}; selector: string | null}> | null;
|
||||
getReferenceTarget(node: TmplAstReference):
|
||||
| TmplAstElement
|
||||
| TmplAstTemplate
|
||||
| TmplAstComponent
|
||||
| TmplAstDirective
|
||||
| {
|
||||
node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective;
|
||||
directive: {ref: {node: T}};
|
||||
}
|
||||
| null;
|
||||
getExpressionTarget(ast: AST): TmplAstReference | TmplAstVariable | TmplAstLetDeclaration | null;
|
||||
getUsedDirectives(): Array<{ref: {node: T}; isComponent: boolean}>;
|
||||
getTemplateAst(): TmplAstNode[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter to extract information from a node, such as its name and file name.
|
||||
*/
|
||||
export interface NodeAdapter<T> {
|
||||
getName(node: T): string;
|
||||
getFileName(node: T): string;
|
||||
getContent(node: T): string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@
|
|||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {BoundTarget, DirectiveMeta, ParseSourceFile} from '@angular/compiler';
|
||||
import {DirectiveMeta, ParseSourceFile} from '@angular/compiler';
|
||||
import {DeclarationNode} from '../../reflection';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {AbstractBoundTemplate} from './api.js';
|
||||
|
||||
export interface ComponentMeta extends DirectiveMeta {
|
||||
ref: Reference<ClassDeclaration>;
|
||||
/**
|
||||
* Metadata about a component, extending DirectiveMeta to include a reference to the node.
|
||||
*/
|
||||
export interface ComponentMeta<T = DeclarationNode> extends DirectiveMeta {
|
||||
ref: {key: string; node: T};
|
||||
/**
|
||||
* Unparsed selector of the directive, or null if the directive does not have a selector.
|
||||
*/
|
||||
|
|
@ -22,9 +25,9 @@ export interface ComponentMeta extends DirectiveMeta {
|
|||
/**
|
||||
* An intermediate representation of a component.
|
||||
*/
|
||||
export interface ComponentInfo {
|
||||
export interface ComponentInfo<T = DeclarationNode> {
|
||||
/** Component TypeScript class declaration */
|
||||
declaration: ClassDeclaration;
|
||||
declaration: T;
|
||||
|
||||
/** Component template selector if it exists, otherwise null. */
|
||||
selector: string | null;
|
||||
|
|
@ -33,7 +36,7 @@ export interface ComponentInfo {
|
|||
* BoundTarget containing the parsed template. Can also be used to query for directives used in
|
||||
* the template.
|
||||
*/
|
||||
boundTemplate: BoundTarget<ComponentMeta>;
|
||||
boundTemplate: AbstractBoundTemplate<T>;
|
||||
|
||||
/** Metadata about the template */
|
||||
templateMeta: {
|
||||
|
|
@ -51,13 +54,13 @@ export interface ComponentInfo {
|
|||
* An `IndexingContext` collects component and template analysis information from
|
||||
* `DecoratorHandler`s and exposes them to be indexed.
|
||||
*/
|
||||
export class IndexingContext {
|
||||
readonly components = new Set<ComponentInfo>();
|
||||
export class IndexingContext<T = DeclarationNode> {
|
||||
readonly components = new Set<ComponentInfo<T>>();
|
||||
|
||||
/**
|
||||
* Adds a component to the context.
|
||||
*/
|
||||
addComponent(info: ComponentInfo) {
|
||||
addComponent(info: ComponentInfo<T>) {
|
||||
this.components.add(info);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
import {
|
||||
AST,
|
||||
ASTWithSource,
|
||||
BoundTarget,
|
||||
CombinedRecursiveAstVisitor,
|
||||
ImplicitReceiver,
|
||||
ParseSourceSpan,
|
||||
|
|
@ -26,10 +25,11 @@ import {
|
|||
tmplAstVisitAll,
|
||||
} from '@angular/compiler';
|
||||
|
||||
import {ClassDeclaration, DeclarationNode} from '../../reflection';
|
||||
import {DeclarationNode} from '../../reflection';
|
||||
|
||||
import {
|
||||
AbsoluteSourceSpan,
|
||||
AbstractBoundTemplate,
|
||||
AttributeIdentifier,
|
||||
ComponentNodeIdentifier,
|
||||
DirectiveHostIdentifier,
|
||||
|
|
@ -44,30 +44,32 @@ import {
|
|||
TopLevelIdentifier,
|
||||
VariableIdentifier,
|
||||
} from './api';
|
||||
import {ComponentMeta} from './context';
|
||||
|
||||
type ExpressionIdentifier = PropertyIdentifier | MethodIdentifier;
|
||||
type ExpressionIdentifier<T = DeclarationNode> = PropertyIdentifier<T> | MethodIdentifier<T>;
|
||||
type TmplTarget = TmplAstReference | TmplAstVariable | TmplAstLetDeclaration;
|
||||
type TargetIdentifier = ReferenceIdentifier | VariableIdentifier | LetDeclarationIdentifier;
|
||||
type TargetIdentifierMap = Map<TmplTarget, TargetIdentifier>;
|
||||
type TargetIdentifier<T = DeclarationNode> =
|
||||
| ReferenceIdentifier<T>
|
||||
| VariableIdentifier
|
||||
| LetDeclarationIdentifier;
|
||||
type TargetIdentifierMap<T = DeclarationNode> = Map<TmplTarget, TargetIdentifier<T>>;
|
||||
|
||||
/**
|
||||
* Visits the AST of a parsed Angular template. Discovers and stores
|
||||
* identifiers of interest, deferring to an `ExpressionVisitor` as needed.
|
||||
*/
|
||||
class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
||||
class TemplateVisitor<T = DeclarationNode> extends CombinedRecursiveAstVisitor {
|
||||
// Identifiers of interest found in the template.
|
||||
readonly identifiers = new Set<TopLevelIdentifier>();
|
||||
readonly identifiers = new Set<TopLevelIdentifier<T>>();
|
||||
readonly errors: Error[] = [];
|
||||
private currentAstWithSource: {source: string | null; absoluteOffset: number} | null = null;
|
||||
|
||||
// Map of targets in a template to their identifiers.
|
||||
private readonly targetIdentifierCache: TargetIdentifierMap = new Map();
|
||||
private readonly targetIdentifierCache: TargetIdentifierMap<T> = new Map();
|
||||
|
||||
// Map of elements and templates to their identifiers.
|
||||
private readonly directiveHostIdentifierCache = new Map<
|
||||
TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
|
||||
DirectiveHostIdentifier
|
||||
DirectiveHostIdentifier<T>
|
||||
>();
|
||||
|
||||
/**
|
||||
|
|
@ -76,7 +78,7 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
*
|
||||
* @param boundTemplate bound template target
|
||||
*/
|
||||
constructor(private boundTemplate: BoundTarget<ComponentMeta>) {
|
||||
constructor(private boundTemplate: AbstractBoundTemplate<T>) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +160,7 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
/** Creates an identifier for a template element or template node. */
|
||||
private directiveHostToIdentifier(
|
||||
node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective,
|
||||
): DirectiveHostIdentifier | null {
|
||||
): DirectiveHostIdentifier<T> | null {
|
||||
// If this node has already been seen, return the cached result.
|
||||
if (this.directiveHostIdentifierCache.has(node)) {
|
||||
return this.directiveHostIdentifierCache.get(node)!;
|
||||
|
|
@ -229,17 +231,17 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
),
|
||||
// cast b/c pre-TypeScript 3.5 unions aren't well discriminated
|
||||
} as
|
||||
| ElementIdentifier
|
||||
| TemplateNodeIdentifier
|
||||
| ComponentNodeIdentifier
|
||||
| DirectiveNodeIdentifier;
|
||||
| ElementIdentifier<T>
|
||||
| TemplateNodeIdentifier<T>
|
||||
| ComponentNodeIdentifier<T>
|
||||
| DirectiveNodeIdentifier<T>;
|
||||
|
||||
this.directiveHostIdentifierCache.set(node, identifier);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/** Creates an identifier for a template reference or template variable target. */
|
||||
private targetToIdentifier(node: TmplTarget): TargetIdentifier | null {
|
||||
private targetToIdentifier(node: TmplTarget): TargetIdentifier<T> | null {
|
||||
// If this node has already been seen, return the cached result.
|
||||
if (this.targetIdentifierCache.has(node)) {
|
||||
return this.targetIdentifierCache.get(node)!;
|
||||
|
|
@ -252,7 +254,7 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
}
|
||||
|
||||
const span = new AbsoluteSourceSpan(start, start + name.length);
|
||||
let identifier: ReferenceIdentifier | VariableIdentifier | LetDeclarationIdentifier;
|
||||
let identifier: ReferenceIdentifier<T> | VariableIdentifier | LetDeclarationIdentifier;
|
||||
if (node instanceof TmplAstReference) {
|
||||
// If the node is a reference, we care about its target. The target can be an element, a
|
||||
// template, a directive applied on a template or element (in which case the directive field
|
||||
|
|
@ -260,8 +262,8 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
const refTarget = this.boundTemplate.getReferenceTarget(node);
|
||||
let target = null;
|
||||
if (refTarget) {
|
||||
let node: DirectiveHostIdentifier | null = null;
|
||||
let directive: ClassDeclaration<DeclarationNode> | null = null;
|
||||
let node: DirectiveHostIdentifier<T> | null = null;
|
||||
let directive: T | null = null;
|
||||
if (
|
||||
refTarget instanceof TmplAstElement ||
|
||||
refTarget instanceof TmplAstTemplate ||
|
||||
|
|
@ -342,7 +344,7 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
*/
|
||||
private visitIdentifier(
|
||||
ast: AST & {name: string; receiver: AST},
|
||||
kind: ExpressionIdentifier['kind'],
|
||||
kind: ExpressionIdentifier<T>['kind'],
|
||||
) {
|
||||
// Only handle identifiers in expressions that have a source location.
|
||||
if (this.currentAstWithSource === null || this.currentAstWithSource.source === null) {
|
||||
|
|
@ -383,7 +385,7 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
const span = new AbsoluteSourceSpan(absoluteStart, absoluteStart + ast.name.length);
|
||||
const targetAst = this.boundTemplate.getExpressionTarget(ast);
|
||||
const target = targetAst ? this.targetToIdentifier(targetAst) : null;
|
||||
const identifier: ExpressionIdentifier = {
|
||||
const identifier: ExpressionIdentifier<T> = {
|
||||
name: ast.name,
|
||||
span,
|
||||
kind,
|
||||
|
|
@ -400,13 +402,16 @@ class TemplateVisitor extends CombinedRecursiveAstVisitor {
|
|||
* @param boundTemplate bound template target, which can be used for querying expression targets.
|
||||
* @return identifiers in template
|
||||
*/
|
||||
export function getTemplateIdentifiers(boundTemplate: BoundTarget<ComponentMeta>): {
|
||||
identifiers: Set<TopLevelIdentifier>;
|
||||
export function getTemplateIdentifiers<T = DeclarationNode>(
|
||||
boundTemplate: AbstractBoundTemplate<T>,
|
||||
): {
|
||||
identifiers: Set<TopLevelIdentifier<T>>;
|
||||
errors: Error[];
|
||||
} {
|
||||
const visitor = new TemplateVisitor(boundTemplate);
|
||||
if (boundTemplate.target.template !== undefined) {
|
||||
tmplAstVisitAll(visitor, boundTemplate.target.template);
|
||||
const visitor = new TemplateVisitor<T>(boundTemplate);
|
||||
const template = boundTemplate.getTemplateAst();
|
||||
if (template !== undefined) {
|
||||
tmplAstVisitAll(visitor, template);
|
||||
}
|
||||
return {identifiers: visitor.identifiers, errors: visitor.errors};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import {ParseSourceFile} from '@angular/compiler';
|
|||
|
||||
import {DeclarationNode} from '../../reflection';
|
||||
|
||||
import {IndexedComponent} from './api';
|
||||
import {IndexingContext} from './context';
|
||||
import {getTemplateIdentifiers} from './template';
|
||||
import {IndexedComponent, NodeAdapter} from './api.js';
|
||||
import {IndexingContext} from './context.js';
|
||||
import {getTemplateIdentifiers} from './template.js';
|
||||
|
||||
/**
|
||||
* Generates `IndexedComponent` entries from a `IndexingContext`, which has information
|
||||
|
|
@ -20,13 +20,17 @@ import {getTemplateIdentifiers} from './template';
|
|||
*
|
||||
* The context must be populated before `generateAnalysis` is called.
|
||||
*/
|
||||
export function generateAnalysis(context: IndexingContext): Map<DeclarationNode, IndexedComponent> {
|
||||
const analysis = new Map<DeclarationNode, IndexedComponent>();
|
||||
export function generateAnalysis<T = DeclarationNode>(
|
||||
context: IndexingContext<T>,
|
||||
adapter: NodeAdapter<T>,
|
||||
): Map<T, IndexedComponent<T>> {
|
||||
const analysis = new Map<T, IndexedComponent<T>>();
|
||||
|
||||
context.components.forEach(({declaration, selector, boundTemplate, templateMeta}) => {
|
||||
const name = declaration.name.getText();
|
||||
const name = adapter.getName(declaration);
|
||||
const fileName = adapter.getFileName(declaration);
|
||||
|
||||
const usedComponents = new Set<DeclarationNode>();
|
||||
const usedComponents = new Set<T>();
|
||||
const usedDirs = boundTemplate.getUsedDirectives();
|
||||
usedDirs.forEach((dir) => {
|
||||
if (dir.isComponent) {
|
||||
|
|
@ -36,10 +40,7 @@ export function generateAnalysis(context: IndexingContext): Map<DeclarationNode,
|
|||
|
||||
// Get source files for the component and the template. If the template is inline, its source
|
||||
// file is the component's.
|
||||
const componentFile = new ParseSourceFile(
|
||||
declaration.getSourceFile().getFullText(),
|
||||
declaration.getSourceFile().fileName,
|
||||
);
|
||||
const componentFile = new ParseSourceFile(adapter.getContent(declaration), fileName);
|
||||
let templateFile: ParseSourceFile;
|
||||
if (templateMeta.isInline) {
|
||||
templateFile = componentFile;
|
||||
|
|
@ -47,7 +48,7 @@ export function generateAnalysis(context: IndexingContext): Map<DeclarationNode,
|
|||
templateFile = templateMeta.file;
|
||||
}
|
||||
|
||||
const {identifiers, errors} = getTemplateIdentifiers(boundTemplate);
|
||||
const {identifiers, errors} = getTemplateIdentifiers<T>(boundTemplate);
|
||||
analysis.set(declaration, {
|
||||
name,
|
||||
selector,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {ParseSourceFile} from '@angular/compiler';
|
|||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {IndexingContext} from '../src/context';
|
||||
import * as util from './util';
|
||||
import {AbstractBoundTemplate} from '../src/api';
|
||||
import {DeclarationNode} from '../../reflection';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('ComponentAnalysisContext', () => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {BoundTarget} from '@angular/compiler';
|
|||
|
||||
import {
|
||||
AbsoluteSourceSpan,
|
||||
AbstractBoundTemplate,
|
||||
AttributeIdentifier,
|
||||
DirectiveHostIdentifier,
|
||||
ElementIdentifier,
|
||||
|
|
@ -25,6 +26,7 @@ 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, {
|
||||
|
|
@ -34,7 +36,7 @@ function bind(template: string, enableSelectorless = false) {
|
|||
});
|
||||
}
|
||||
|
||||
function getTemplateIdentifiers(boundTemplate: BoundTarget<ComponentMeta>) {
|
||||
function getTemplateIdentifiers(boundTemplate: AbstractBoundTemplate<DeclarationNode>) {
|
||||
return getTemplateIdentifiersAndErrors(boundTemplate).identifiers;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,15 +5,17 @@
|
|||
* 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, ParseSourceFile} from '@angular/compiler';
|
||||
import {ParseSourceFile} from '@angular/compiler';
|
||||
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ComponentMeta, IndexingContext} from '../src/context';
|
||||
import {ClassDeclaration, DeclarationNode} from '../../reflection';
|
||||
import {IndexingContext} from '../src/context';
|
||||
import {getTemplateIdentifiers} from '../src/template';
|
||||
import {generateAnalysis} from '../src/transform';
|
||||
|
||||
import * as util from './util';
|
||||
import {AbstractBoundTemplate, NodeAdapter} from '../src/api';
|
||||
import ts from 'typescript';
|
||||
|
||||
/**
|
||||
* Adds information about a component to a context.
|
||||
|
|
@ -23,7 +25,7 @@ function populateContext(
|
|||
component: ClassDeclaration,
|
||||
selector: string,
|
||||
template: string,
|
||||
boundTemplate: BoundTarget<ComponentMeta>,
|
||||
boundTemplate: AbstractBoundTemplate<DeclarationNode>,
|
||||
isInline: boolean = false,
|
||||
) {
|
||||
context.addComponent({
|
||||
|
|
@ -37,6 +39,18 @@ function populateContext(
|
|||
});
|
||||
}
|
||||
|
||||
const adapter: NodeAdapter<DeclarationNode> = {
|
||||
getName(node: DeclarationNode): string {
|
||||
return ts.isClassDeclaration(node) && node.name ? node.name.getText() : '';
|
||||
},
|
||||
getFileName(node: DeclarationNode): string {
|
||||
return node.getSourceFile().fileName;
|
||||
},
|
||||
getContent(node: DeclarationNode): string {
|
||||
return node.getSourceFile().getFullText();
|
||||
},
|
||||
};
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('generateAnalysis', () => {
|
||||
it('should emit component and template analysis information', () => {
|
||||
|
|
@ -44,7 +58,7 @@ runInEachFileSystem(() => {
|
|||
const decl = util.getComponentDeclaration('class C {}', 'C');
|
||||
const template = '<div>{{foo}}</div>';
|
||||
populateContext(context, decl, 'c-selector', template, util.getBoundTemplate(template));
|
||||
const analysis = generateAnalysis(context);
|
||||
const analysis = generateAnalysis(context, adapter);
|
||||
|
||||
expect(analysis.size).toBe(1);
|
||||
|
||||
|
|
@ -76,7 +90,7 @@ runInEachFileSystem(() => {
|
|||
util.getBoundTemplate(template),
|
||||
/* inline template */ true,
|
||||
);
|
||||
const analysis = generateAnalysis(context);
|
||||
const analysis = generateAnalysis(context, adapter);
|
||||
|
||||
expect(analysis.size).toBe(1);
|
||||
|
||||
|
|
@ -92,7 +106,7 @@ runInEachFileSystem(() => {
|
|||
const decl = util.getComponentDeclaration('class C {}', 'C');
|
||||
const template = '<div>{{foo}}</div>';
|
||||
populateContext(context, decl, 'c-selector', template, util.getBoundTemplate(template));
|
||||
const analysis = generateAnalysis(context);
|
||||
const analysis = generateAnalysis(context, adapter);
|
||||
|
||||
expect(analysis.size).toBe(1);
|
||||
|
||||
|
|
@ -122,7 +136,7 @@ runInEachFileSystem(() => {
|
|||
populateContext(context, declA, 'a-selector', templateA, boundA);
|
||||
populateContext(context, declB, 'b-selector', templateB, boundB);
|
||||
|
||||
const analysis = generateAnalysis(context);
|
||||
const analysis = generateAnalysis(context, adapter);
|
||||
|
||||
expect(analysis.size).toBe(2);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ import ts from 'typescript';
|
|||
|
||||
import {absoluteFrom, AbsoluteFsPath} from '../../file_system';
|
||||
import {Reference} from '../../imports';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {ClassDeclaration, DeclarationNode} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {ComponentMeta} from '../src/context';
|
||||
import {AbstractBoundTemplate} from '../src/api';
|
||||
|
||||
/** Dummy file URL */
|
||||
function getTestFilePath(): AbsoluteFsPath {
|
||||
|
|
@ -57,7 +58,7 @@ export function getBoundTemplate(
|
|||
template: string,
|
||||
options: ParseTemplateOptions = {},
|
||||
components: Array<{selector: string | null; declaration: ClassDeclaration}> = [],
|
||||
): BoundTarget<ComponentMeta> {
|
||||
): AbstractBoundTemplate<DeclarationNode> {
|
||||
const componentsMeta = components.map(({selector, declaration}) => ({
|
||||
ref: new Reference(declaration),
|
||||
selector,
|
||||
|
|
@ -95,5 +96,30 @@ export function getBoundTemplate(
|
|||
|
||||
const binder = new R3TargetBinder(matcher);
|
||||
|
||||
return binder.bind({template: parseTemplate(template, getTestFilePath(), options).nodes});
|
||||
const boundTemplate = binder.bind({
|
||||
template: parseTemplate(template, getTestFilePath(), options).nodes,
|
||||
});
|
||||
const abstractBoundTemplate: AbstractBoundTemplate<DeclarationNode> = {
|
||||
getDirectivesOfNode(node) {
|
||||
return boundTemplate.getDirectivesOfNode(node);
|
||||
},
|
||||
getReferenceTarget(node) {
|
||||
return boundTemplate.getReferenceTarget(node);
|
||||
},
|
||||
getExpressionTarget(ast) {
|
||||
return boundTemplate.getExpressionTarget(ast);
|
||||
},
|
||||
getUsedDirectives() {
|
||||
return boundTemplate.getUsedDirectives().map((dir) => ({
|
||||
ref: {node: dir.ref.node},
|
||||
isComponent: dir.isComponent,
|
||||
}));
|
||||
},
|
||||
getTemplateAst() {
|
||||
return boundTemplate.target.template;
|
||||
},
|
||||
};
|
||||
return abstractBoundTemplate;
|
||||
}
|
||||
|
||||
function createAbstractBoundTemplate() {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue