angular-cn/packages/compiler-cli/ngcc/src/analysis/module_with_providers_analyzer.ts
Joey Perrott d1ea1f4c7f build: update license headers to reference Google LLC (#37205)
Update the license headers throughout the repository to reference Google LLC
rather than Google Inc, for the required license headers.

PR Close #37205
2020-05-26 14:26:58 -04:00

232 lines
8.9 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ReferencesRegistry} from '../../../src/ngtsc/annotations';
import {Reference} from '../../../src/ngtsc/imports';
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
import {ClassDeclaration, isNamedClassDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {NgccReflectionHost} from '../host/ngcc_host';
import {hasNameIdentifier, isDefined} from '../utils';
/**
* A structure returned from `getModuleWithProvidersFunctions()` that describes functions
* that return ModuleWithProviders objects.
*/
export interface ModuleWithProvidersInfo {
/**
* The name of the declared function.
*/
name: string;
/**
* The declaration of the function that returns the `ModuleWithProviders` object.
*/
declaration: ts.SignatureDeclaration;
/**
* Declaration of the containing class (if this is a method)
*/
container: ts.Declaration|null;
/**
* The declaration of the class that the `ngModule` property on the `ModuleWithProviders` object
* refers to.
*/
ngModule: Reference<ClassDeclaration>;
}
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
export const ModuleWithProvidersAnalyses = Map;
export class ModuleWithProvidersAnalyzer {
private evaluator = new PartialEvaluator(this.host, this.typeChecker, null);
constructor(
private host: NgccReflectionHost, private typeChecker: ts.TypeChecker,
private referencesRegistry: ReferencesRegistry, private processDts: boolean) {}
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
const analyses: ModuleWithProvidersAnalyses = new ModuleWithProvidersAnalyses();
const rootFiles = this.getRootFiles(program);
rootFiles.forEach(f => {
const fns = this.getModuleWithProvidersFunctions(f);
fns && fns.forEach(fn => {
if (fn.ngModule.bestGuessOwningModule === null) {
// Record the usage of an internal module as it needs to become an exported symbol
this.referencesRegistry.add(fn.ngModule.node, new Reference(fn.ngModule.node));
}
// Only when processing the dts files do we need to determine which declaration to update.
if (this.processDts) {
const dtsFn = this.getDtsModuleWithProvidersFunction(fn);
const dtsFnType = dtsFn.declaration.type;
const typeParam = dtsFnType && ts.isTypeReferenceNode(dtsFnType) &&
dtsFnType.typeArguments && dtsFnType.typeArguments[0] ||
null;
if (!typeParam || isAnyKeyword(typeParam)) {
const dtsFile = dtsFn.declaration.getSourceFile();
const analysis = analyses.has(dtsFile) ? analyses.get(dtsFile)! : [];
analysis.push(dtsFn);
analyses.set(dtsFile, analysis);
}
}
});
});
return analyses;
}
private getRootFiles(program: ts.Program): ts.SourceFile[] {
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
}
private getModuleWithProvidersFunctions(f: ts.SourceFile): ModuleWithProvidersInfo[] {
const exports = this.host.getExportsOfModule(f);
if (!exports) return [];
const infos: ModuleWithProvidersInfo[] = [];
exports.forEach((declaration) => {
if (declaration.node === null) {
return;
}
if (this.host.isClass(declaration.node)) {
this.host.getMembersOfClass(declaration.node).forEach(member => {
if (member.isStatic) {
const info = this.parseForModuleWithProviders(
member.name, member.node, member.implementation, declaration.node);
if (info) {
infos.push(info);
}
}
});
} else {
if (hasNameIdentifier(declaration.node)) {
const info =
this.parseForModuleWithProviders(declaration.node.name.text, declaration.node);
if (info) {
infos.push(info);
}
}
}
});
return infos;
}
/**
* Parse a function/method node (or its implementation), to see if it returns a
* `ModuleWithProviders` object.
* @param name The name of the function.
* @param node the node to check - this could be a function, a method or a variable declaration.
* @param implementation the actual function expression if `node` is a variable declaration.
* @param container the class that contains the function, if it is a method.
* @returns info about the function if it does return a `ModuleWithProviders` object; `null`
* otherwise.
*/
private parseForModuleWithProviders(
name: string, node: ts.Node|null, implementation: ts.Node|null = node,
container: ts.Declaration|null = null): ModuleWithProvidersInfo|null {
if (implementation === null ||
(!ts.isFunctionDeclaration(implementation) && !ts.isMethodDeclaration(implementation) &&
!ts.isFunctionExpression(implementation))) {
return null;
}
const declaration = implementation;
const definition = this.host.getDefinitionOfFunction(declaration);
if (definition === null) {
return null;
}
const body = definition.body;
if (body === null || body.length === 0) {
return null;
}
// Get hold of the return statement expression for the function
const lastStatement = body[body.length - 1];
if (!ts.isReturnStatement(lastStatement) || lastStatement.expression === undefined) {
return null;
}
// Evaluate this expression and extract the `ngModule` reference
const result = this.evaluator.evaluate(lastStatement.expression);
if (!(result instanceof Map) || !result.has('ngModule')) {
return null;
}
const ngModuleRef = result.get('ngModule')!;
if (!(ngModuleRef instanceof Reference)) {
return null;
}
if (!isNamedClassDeclaration(ngModuleRef.node) &&
!isNamedVariableDeclaration(ngModuleRef.node)) {
throw new Error(`The identity given by ${ngModuleRef.debugName} referenced in "${
declaration!.getText()}" doesn't appear to be a "class" declaration.`);
}
const ngModule = ngModuleRef as Reference<ClassDeclaration>;
return {name, ngModule, declaration, container};
}
private getDtsModuleWithProvidersFunction(fn: ModuleWithProvidersInfo): ModuleWithProvidersInfo {
let dtsFn: ts.Declaration|null = null;
const containerClass = fn.container && this.host.getClassSymbol(fn.container);
if (containerClass) {
const dtsClass = this.host.getDtsDeclaration(containerClass.declaration.valueDeclaration);
// Get the declaration of the matching static method
dtsFn = dtsClass && ts.isClassDeclaration(dtsClass) ?
dtsClass.members.find(
member => ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) &&
member.name.text === fn.name) as ts.Declaration :
null;
} else {
dtsFn = this.host.getDtsDeclaration(fn.declaration);
}
if (!dtsFn) {
throw new Error(`Matching type declaration for ${fn.declaration.getText()} is missing`);
}
if (!isFunctionOrMethod(dtsFn)) {
throw new Error(`Matching type declaration for ${
fn.declaration.getText()} is not a function: ${dtsFn.getText()}`);
}
const container = containerClass ? containerClass.declaration.valueDeclaration : null;
const ngModule = this.resolveNgModuleReference(fn);
return {name: fn.name, container, declaration: dtsFn, ngModule};
}
private resolveNgModuleReference(fn: ModuleWithProvidersInfo): Reference<ClassDeclaration> {
const ngModule = fn.ngModule;
// For external module references, use the declaration as is.
if (ngModule.bestGuessOwningModule !== null) {
return ngModule;
}
// For internal (non-library) module references, redirect the module's value declaration
// to its type declaration.
const dtsNgModule = this.host.getDtsDeclaration(ngModule.node);
if (!dtsNgModule) {
throw new Error(`No typings declaration can be found for the referenced NgModule class in ${
fn.declaration.getText()}.`);
}
if (!isNamedClassDeclaration(dtsNgModule)) {
throw new Error(`The referenced NgModule in ${
fn.declaration
.getText()} is not a named class declaration in the typings program; instead we get ${
dtsNgModule.getText()}`);
}
return new Reference(dtsNgModule, null);
}
}
function isFunctionOrMethod(declaration: ts.Declaration): declaration is ts.FunctionDeclaration|
ts.MethodDeclaration {
return ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration);
}
function isAnyKeyword(typeParam: ts.TypeNode): typeParam is ts.KeywordTypeNode {
return typeParam.kind === ts.SyntaxKind.AnyKeyword;
}