refactor(compiler): add getEntitiesInTemplateScope to template binder (#39048)
The template binding API in @angular/compiler exposes information about a template that is synthesized from the template structure and its scope (associated directives and pipes). This commit introduces a new API, `getEntitiesInTemplateScope`, which accepts a `Template` object (or `null` to indicate the root template) and returns all `Reference` and `Variable` nodes that are visible at that level of the template, including those declared in parent templates. This API is needed by the template type-checker to support autocompletion APIs for the Language Service. PR Close #39048
This commit is contained in:
parent
3975dd90a6
commit
72755eadd2
|
@ -149,6 +149,12 @@ export interface BoundTarget<DirectiveT extends DirectiveMeta> {
|
|||
*/
|
||||
getNestingLevel(template: Template): number;
|
||||
|
||||
/**
|
||||
* Get all `Reference`s and `Variables` visible within the given `Template` (or at the top level,
|
||||
* if `null` is passed).
|
||||
*/
|
||||
getEntitiesInTemplateScope(template: Template|null): ReadonlySet<Reference|Variable>;
|
||||
|
||||
/**
|
||||
* Get a list of all the directives used by the target.
|
||||
*/
|
||||
|
|
|
@ -37,6 +37,10 @@ export class R3TargetBinder<DirectiveT extends DirectiveMeta> implements TargetB
|
|||
// scopes in the template and makes them available for later use.
|
||||
const scope = Scope.apply(target.template);
|
||||
|
||||
|
||||
// Use the `Scope` to extract the entities present at every level of the template.
|
||||
const templateEntities = extractTemplateEntities(scope);
|
||||
|
||||
// Next, perform directive matching on the template using the `DirectiveBinder`. This returns:
|
||||
// - directives: Map of nodes (elements & ng-templates) to the directives on them.
|
||||
// - bindings: Map of inputs, outputs, and attributes to the directive/element that claims
|
||||
|
@ -49,7 +53,8 @@ export class R3TargetBinder<DirectiveT extends DirectiveMeta> implements TargetB
|
|||
const {expressions, symbols, nestingLevel, usedPipes} =
|
||||
TemplateBinder.apply(target.template, scope);
|
||||
return new R3BoundTarget(
|
||||
target, directives, bindings, references, expressions, symbols, nestingLevel, usedPipes);
|
||||
target, directives, bindings, references, expressions, symbols, nestingLevel,
|
||||
templateEntities, usedPipes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,14 +76,18 @@ class Scope implements Visitor {
|
|||
*/
|
||||
readonly childScopes = new Map<Template, Scope>();
|
||||
|
||||
private constructor(readonly parentScope?: Scope) {}
|
||||
private constructor(readonly parentScope: Scope|null, readonly template: Template|null) {}
|
||||
|
||||
static newRootScope(): Scope {
|
||||
return new Scope(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a template (either as a `Template` sub-template with variables, or a plain array of
|
||||
* template `Node`s) and construct its `Scope`.
|
||||
*/
|
||||
static apply(template: Template|Node[]): Scope {
|
||||
const scope = new Scope();
|
||||
static apply(template: Node[]): Scope {
|
||||
const scope = Scope.newRootScope();
|
||||
scope.ingest(template);
|
||||
return scope;
|
||||
}
|
||||
|
@ -113,7 +122,7 @@ class Scope implements Visitor {
|
|||
template.references.forEach(node => this.visitReference(node));
|
||||
|
||||
// Next, create an inner scope and process the template within it.
|
||||
const scope = new Scope(this);
|
||||
const scope = new Scope(this, template);
|
||||
scope.ingest(template);
|
||||
this.childScopes.set(template, scope);
|
||||
}
|
||||
|
@ -153,7 +162,7 @@ class Scope implements Visitor {
|
|||
if (this.namedEntities.has(name)) {
|
||||
// Found in the local scope.
|
||||
return this.namedEntities.get(name)!;
|
||||
} else if (this.parentScope !== undefined) {
|
||||
} else if (this.parentScope !== null) {
|
||||
// Not in the local scope, but there's a parent scope so check there.
|
||||
return this.parentScope.lookup(name);
|
||||
} else {
|
||||
|
@ -517,7 +526,13 @@ export class R3BoundTarget<DirectiveT extends DirectiveMeta> implements BoundTar
|
|||
{directive: DirectiveT, node: Element|Template}|Element|Template>,
|
||||
private exprTargets: Map<AST, Reference|Variable>,
|
||||
private symbols: Map<Reference|Variable, Template>,
|
||||
private nestingLevel: Map<Template, number>, private usedPipes: Set<string>) {}
|
||||
private nestingLevel: Map<Template, number>,
|
||||
private templateEntities: Map<Template|null, ReadonlySet<Reference|Variable>>,
|
||||
private usedPipes: Set<string>) {}
|
||||
|
||||
getEntitiesInTemplateScope(template: Template|null): ReadonlySet<Reference|Variable> {
|
||||
return this.templateEntities.get(template) ?? new Set();
|
||||
}
|
||||
|
||||
getDirectivesOfNode(node: Element|Template): DirectiveT[]|null {
|
||||
return this.directives.get(node) || null;
|
||||
|
@ -555,3 +570,40 @@ export class R3BoundTarget<DirectiveT extends DirectiveMeta> implements BoundTar
|
|||
return Array.from(this.usedPipes);
|
||||
}
|
||||
}
|
||||
|
||||
function extractTemplateEntities(rootScope: Scope): Map<Template|null, Set<Reference|Variable>> {
|
||||
const entityMap = new Map<Template|null, Map<string, Reference|Variable>>();
|
||||
|
||||
function extractScopeEntities(scope: Scope): Map<string, Reference|Variable> {
|
||||
if (entityMap.has(scope.template)) {
|
||||
return entityMap.get(scope.template)!;
|
||||
}
|
||||
|
||||
const currentEntities = scope.namedEntities;
|
||||
|
||||
let templateEntities: Map<string, Reference|Variable>;
|
||||
if (scope.parentScope !== null) {
|
||||
templateEntities = new Map([...extractScopeEntities(scope.parentScope), ...currentEntities]);
|
||||
} else {
|
||||
templateEntities = new Map(currentEntities);
|
||||
}
|
||||
|
||||
entityMap.set(scope.template, templateEntities);
|
||||
return templateEntities;
|
||||
}
|
||||
|
||||
const scopesToProcess: Scope[] = [rootScope];
|
||||
while (scopesToProcess.length > 0) {
|
||||
const scope = scopesToProcess.pop()!;
|
||||
for (const childScope of scope.childScopes.values()) {
|
||||
scopesToProcess.push(childScope);
|
||||
}
|
||||
extractScopeEntities(scope);
|
||||
}
|
||||
|
||||
const templateEntities = new Map<Template|null, Set<Reference|Variable>>();
|
||||
for (const [template, entities] of entityMap) {
|
||||
templateEntities.set(template, new Set(entities.values()));
|
||||
}
|
||||
return templateEntities;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue