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:
Ayaz Hafiz
2019-06-19 17:23:59 -07:00
committed by Kara Erickson
parent 3fb73ac62b
commit 74f4f5dfab
19 changed files with 749 additions and 209 deletions

View File

@ -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",

View File

@ -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;
}