feat(ivy): integrate indexing pipeline with NgtscProgram (#31151)
Add an IndexingContext class to store indexing information and a transformer module to generate indexing analysis. Integrate the indexing module with the rest of NgtscProgram and add integration tests. Closes #30959 PR Close #31151
This commit is contained in:

committed by
Kara Erickson

parent
3fb73ac62b
commit
74f4f5dfab
@ -12,6 +12,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/cycles",
|
||||
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
|
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, InterpolationConfig, LexerRange, ParseError, R3ComponentMetadata, R3TargetBinder, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3TargetBinder, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CycleAnalyzer} from '../../cycles';
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {IndexingContext} from '../../indexer';
|
||||
import {DirectiveMeta, MetadataReader, MetadataRegistry, extractDirectiveGuards} from '../../metadata';
|
||||
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
@ -35,6 +36,7 @@ export interface ComponentHandlerData {
|
||||
meta: R3ComponentMetadata;
|
||||
parsedTemplate: TmplAstNode[];
|
||||
metadataStmt: Statement|null;
|
||||
parseTemplate: (options?: ParseTemplateOptions) => ParsedTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,11 +171,22 @@ export class ComponentDecoratorHandler implements
|
||||
// Parse the template.
|
||||
// If a preanalyze phase was executed, the template may already exist in parsed form, so check
|
||||
// the preanalyzeTemplateCache.
|
||||
let template: ParsedTemplate;
|
||||
// Extract a closure of the template parsing code so that it can be reparsed with different
|
||||
// options if needed, like in the indexing pipeline.
|
||||
let parseTemplate: (options?: ParseTemplateOptions) => ParsedTemplate;
|
||||
if (this.preanalyzeTemplateCache.has(node)) {
|
||||
// The template was parsed in preanalyze. Use it and delete it to save memory.
|
||||
template = this.preanalyzeTemplateCache.get(node) !;
|
||||
const template = this.preanalyzeTemplateCache.get(node) !;
|
||||
this.preanalyzeTemplateCache.delete(node);
|
||||
|
||||
// A pre-analyzed template cannot be reparsed. Pre-analysis is never run with the indexing
|
||||
// pipeline.
|
||||
parseTemplate = (options?: ParseTemplateOptions) => {
|
||||
if (options !== undefined) {
|
||||
throw new Error(`Cannot reparse a pre-analyzed template with new options`);
|
||||
}
|
||||
return template;
|
||||
};
|
||||
} else {
|
||||
// The template was not already parsed. Either there's a templateUrl, or an inline template.
|
||||
if (component.has('templateUrl')) {
|
||||
@ -187,9 +200,9 @@ export class ComponentDecoratorHandler implements
|
||||
const templateStr = this.resourceLoader.load(templateUrl);
|
||||
this.resourceDependencies.recordResourceDependency(node.getSourceFile(), templateUrl);
|
||||
|
||||
template = this._parseTemplate(
|
||||
parseTemplate = (options?: ParseTemplateOptions) => this._parseTemplate(
|
||||
component, templateStr, sourceMapUrl(templateUrl), /* templateRange */ undefined,
|
||||
/* escapedString */ false);
|
||||
/* escapedString */ false, options);
|
||||
} else {
|
||||
// Expect an inline template to be present.
|
||||
const inlineTemplate = this._extractInlineTemplate(component, relativeContextFilePath);
|
||||
@ -199,10 +212,11 @@ export class ComponentDecoratorHandler implements
|
||||
'component is missing a template');
|
||||
}
|
||||
const {templateStr, templateUrl, templateRange, escapedString} = inlineTemplate;
|
||||
template =
|
||||
this._parseTemplate(component, templateStr, templateUrl, templateRange, escapedString);
|
||||
parseTemplate = (options?: ParseTemplateOptions) => this._parseTemplate(
|
||||
component, templateStr, templateUrl, templateRange, escapedString, options);
|
||||
}
|
||||
}
|
||||
const template = parseTemplate();
|
||||
|
||||
if (template.errors !== undefined) {
|
||||
throw new Error(
|
||||
@ -294,7 +308,7 @@ export class ComponentDecoratorHandler implements
|
||||
},
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
parsedTemplate: template.nodes,
|
||||
parsedTemplate: template.nodes, parseTemplate,
|
||||
},
|
||||
typeCheck: true,
|
||||
};
|
||||
@ -304,6 +318,37 @@ export class ComponentDecoratorHandler implements
|
||||
return output;
|
||||
}
|
||||
|
||||
index(context: IndexingContext, node: ClassDeclaration, analysis: ComponentHandlerData) {
|
||||
// The component template may have been previously parsed without preserving whitespace or with
|
||||
// `leadingTriviaChar`s, both of which may manipulate the AST into a form not representative of
|
||||
// the source code, making it unsuitable for indexing. The template is reparsed with preserving
|
||||
// options to remedy this.
|
||||
const template = analysis.parseTemplate({
|
||||
preserveWhitespaces: true,
|
||||
leadingTriviaChars: [],
|
||||
});
|
||||
const scope = this.scopeRegistry.getScopeForComponent(node);
|
||||
const selector = analysis.meta.selector;
|
||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||
if (scope !== null) {
|
||||
for (const directive of scope.compilation.directives) {
|
||||
matcher.addSelectables(CssSelector.parse(directive.selector), directive);
|
||||
}
|
||||
}
|
||||
const binder = new R3TargetBinder(matcher);
|
||||
const boundTemplate = binder.bind({template: template.nodes});
|
||||
|
||||
context.addComponent({
|
||||
declaration: node,
|
||||
selector,
|
||||
boundTemplate,
|
||||
templateMeta: {
|
||||
isInline: template.isInline,
|
||||
file: template.file,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
typeCheck(ctx: TypeCheckContext, node: ClassDeclaration, meta: ComponentHandlerData): void {
|
||||
if (!ts.isClassDeclaration(node)) {
|
||||
return;
|
||||
@ -576,7 +621,8 @@ export class ComponentDecoratorHandler implements
|
||||
|
||||
private _parseTemplate(
|
||||
component: Map<string, ts.Expression>, templateStr: string, templateUrl: string,
|
||||
templateRange: LexerRange|undefined, escapedString: boolean): ParsedTemplate {
|
||||
templateRange: LexerRange|undefined, escapedString: boolean,
|
||||
options: ParseTemplateOptions = {}): ParsedTemplate {
|
||||
let preserveWhitespaces: boolean = this.defaultPreserveWhitespaces;
|
||||
if (component.has('preserveWhitespaces')) {
|
||||
const expr = component.get('preserveWhitespaces') !;
|
||||
@ -602,11 +648,14 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
|
||||
return {
|
||||
interpolation, ...parseTemplate(templateStr, templateUrl, {
|
||||
preserveWhitespaces,
|
||||
interpolationConfig: interpolation,
|
||||
range: templateRange, escapedString
|
||||
}),
|
||||
interpolation,
|
||||
...parseTemplate(templateStr, templateUrl, {
|
||||
preserveWhitespaces,
|
||||
interpolationConfig: interpolation,
|
||||
range: templateRange, escapedString, ...options,
|
||||
}),
|
||||
isInline: component.has('template'),
|
||||
file: new ParseSourceFile(templateStr, templateUrl),
|
||||
};
|
||||
}
|
||||
|
||||
@ -668,4 +717,6 @@ interface ParsedTemplate {
|
||||
nodes: TmplAstNode[];
|
||||
styleUrls: string[];
|
||||
styles: string[];
|
||||
isInline: boolean;
|
||||
file: ParseSourceFile;
|
||||
}
|
||||
|
Reference in New Issue
Block a user