2018-12-07 13:10:52 +00:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright Google Inc. 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';
|
|
|
|
|
|
2019-03-20 13:47:58 +00:00
|
|
|
import {ReferencesRegistry} from '../../../src/ngtsc/annotations';
|
|
|
|
|
import {Reference} from '../../../src/ngtsc/imports';
|
|
|
|
|
import {Declaration} from '../../../src/ngtsc/reflection';
|
2019-03-20 13:47:58 +00:00
|
|
|
import {ModuleWithProvidersFunction, NgccReflectionHost} from '../host/ngcc_host';
|
2018-12-07 13:10:52 +00:00
|
|
|
import {isDefined} from '../utils';
|
|
|
|
|
|
|
|
|
|
export interface ModuleWithProvidersInfo {
|
|
|
|
|
/**
|
|
|
|
|
* The declaration (in the .d.ts file) of the function that returns
|
|
|
|
|
* a `ModuleWithProviders object, but has a signature that needs
|
|
|
|
|
* a type parameter adding.
|
|
|
|
|
*/
|
|
|
|
|
declaration: ts.MethodDeclaration|ts.FunctionDeclaration;
|
|
|
|
|
/**
|
|
|
|
|
* The NgModule class declaration (in the .d.ts file) to add as a type parameter.
|
|
|
|
|
*/
|
|
|
|
|
ngModule: Declaration;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ModuleWithProvidersAnalyses = Map<ts.SourceFile, ModuleWithProvidersInfo[]>;
|
|
|
|
|
export const ModuleWithProvidersAnalyses = Map;
|
|
|
|
|
|
|
|
|
|
export class ModuleWithProvidersAnalyzer {
|
|
|
|
|
constructor(private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry) {}
|
|
|
|
|
|
|
|
|
|
analyzeProgram(program: ts.Program): ModuleWithProvidersAnalyses {
|
|
|
|
|
const analyses = new ModuleWithProvidersAnalyses();
|
|
|
|
|
const rootFiles = this.getRootFiles(program);
|
|
|
|
|
rootFiles.forEach(f => {
|
|
|
|
|
const fns = this.host.getModuleWithProvidersFunctions(f);
|
|
|
|
|
fns && fns.forEach(fn => {
|
2019-03-20 13:47:58 +00:00
|
|
|
const dtsFn = this.getDtsDeclarationForFunction(fn);
|
2018-12-07 13:10:52 +00:00
|
|
|
const typeParam = dtsFn.type && ts.isTypeReferenceNode(dtsFn.type) &&
|
|
|
|
|
dtsFn.type.typeArguments && dtsFn.type.typeArguments[0] ||
|
|
|
|
|
null;
|
|
|
|
|
if (!typeParam || isAnyKeyword(typeParam)) {
|
|
|
|
|
// Either we do not have a parameterized type or the type is `any`.
|
|
|
|
|
let ngModule = this.host.getDeclarationOfIdentifier(fn.ngModule);
|
|
|
|
|
if (!ngModule) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Cannot find a declaration for NgModule ${fn.ngModule.text} referenced in ${fn.declaration.getText()}`);
|
|
|
|
|
}
|
|
|
|
|
// For internal (non-library) module references, redirect the module's value declaration
|
|
|
|
|
// to its type declaration.
|
|
|
|
|
if (ngModule.viaModule === null) {
|
|
|
|
|
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 (!ts.isClassDeclaration(dtsNgModule)) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`The referenced NgModule in ${fn.declaration.getText()} is not a class declaration in the typings program; instead we get ${dtsNgModule.getText()}`);
|
|
|
|
|
}
|
|
|
|
|
// Record the usage of the internal module as it needs to become an exported symbol
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
const reference = new Reference(ngModule.node);
|
|
|
|
|
reference.addIdentifier(fn.ngModule);
|
|
|
|
|
this.referencesRegistry.add(ngModule.node, reference);
|
2018-12-07 13:10:52 +00:00
|
|
|
|
|
|
|
|
ngModule = {node: dtsNgModule, viaModule: null};
|
|
|
|
|
}
|
|
|
|
|
const dtsFile = dtsFn.getSourceFile();
|
|
|
|
|
const analysis = analyses.get(dtsFile) || [];
|
|
|
|
|
analysis.push({declaration: dtsFn, ngModule});
|
|
|
|
|
analyses.set(dtsFile, analysis);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return analyses;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getRootFiles(program: ts.Program): ts.SourceFile[] {
|
|
|
|
|
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-20 13:47:58 +00:00
|
|
|
private getDtsDeclarationForFunction(fn: ModuleWithProvidersFunction) {
|
2018-12-07 13:10:52 +00:00
|
|
|
let dtsFn: ts.Declaration|null = null;
|
2019-03-20 13:47:58 +00:00
|
|
|
const containerClass = fn.container && this.host.getClassSymbol(fn.container);
|
|
|
|
|
if (containerClass) {
|
2018-12-07 13:10:52 +00:00
|
|
|
const dtsClass = this.host.getDtsDeclaration(containerClass.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) &&
|
2019-03-20 13:47:58 +00:00
|
|
|
member.name.text === fn.name) as ts.Declaration :
|
2018-12-07 13:10:52 +00:00
|
|
|
null;
|
|
|
|
|
} else {
|
2019-03-20 13:47:58 +00:00
|
|
|
dtsFn = this.host.getDtsDeclaration(fn.declaration);
|
2018-12-07 13:10:52 +00:00
|
|
|
}
|
|
|
|
|
if (!dtsFn) {
|
2019-03-20 13:47:58 +00:00
|
|
|
throw new Error(`Matching type declaration for ${fn.declaration.getText()} is missing`);
|
2018-12-07 13:10:52 +00:00
|
|
|
}
|
|
|
|
|
if (!isFunctionOrMethod(dtsFn)) {
|
|
|
|
|
throw new Error(
|
2019-03-20 13:47:58 +00:00
|
|
|
`Matching type declaration for ${fn.declaration.getText()} is not a function: ${dtsFn.getText()}`);
|
2018-12-07 13:10:52 +00:00
|
|
|
}
|
|
|
|
|
return dtsFn;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|