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' ;
2019-03-05 23:29:28 +01:00
import { ClassDeclaration , Declaration } from '../../../src/ngtsc/reflection' ;
2019-03-20 13:47:58 +00:00
import { ModuleWithProvidersFunction , NgccReflectionHost } from '../host/ngcc_host' ;
2019-03-05 23:29:28 +01:00
import { hasNameIdentifier , isDefined } from '../utils' ;
2018-12-07 13:10:52 +00:00
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 .
* /
2019-03-05 23:29:28 +01:00
ngModule : Declaration < ClassDeclaration > ;
2018-12-07 13:10:52 +00:00
}
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`.
2019-03-05 23:29:28 +01:00
let ngModule = fn . ngModule ;
2018-12-07 13:10:52 +00:00
// 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 ( ) } . ` ) ;
}
2019-03-05 23:29:28 +01:00
if ( ! ts . isClassDeclaration ( dtsNgModule ) || ! hasNameIdentifier ( dtsNgModule ) ) {
2018-12-07 13:10:52 +00:00
throw new Error (
2019-03-05 23:29:28 +01:00
` The referenced NgModule in ${ fn . declaration . getText ( ) } is not a named class declaration in the typings program; instead we get ${ dtsNgModule . getText ( ) } ` ) ;
2018-12-07 13:10:52 +00:00
}
// Record the usage of the internal module as it needs to become an exported symbol
2019-03-05 23:29:28 +01:00
this . referencesRegistry . add ( ngModule . node , new Reference ( ngModule . node ) ) ;
2018-12-07 13:10:52 +00:00
ngModule = { node : dtsNgModule , viaModule : null } ;
}
const dtsFile = dtsFn . getSourceFile ( ) ;
2019-05-14 08:36:57 +01:00
const analysis = analyses . has ( dtsFile ) ? analyses . get ( dtsFile ) : [ ] ;
2018-12-07 13:10:52 +00:00
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 ;
}