feat(ivy): register references from NgModule annotations (#26906)
The `NgModuleDecoratorHandler` can now register all the references that it finds in the `NgModule` metadata, such as `declarations`, `imports`, `exports` etc. This information can then be used by ngcc to work out if any of these references are internal only and need to be manually exported from a library's entry-point. PR Close #26906
This commit is contained in:
parent
b93c1dffa1
commit
4a70b669be
|
@ -9,9 +9,8 @@ import {ConstantPool} from '@angular/compiler';
|
|||
import * as fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from '../../../ngtsc/annotations';
|
||||
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader, SelectorScopeRegistry} from '../../../ngtsc/annotations';
|
||||
import {CompileResult, DecoratorHandler} from '../../../ngtsc/transform';
|
||||
|
||||
import {DecoratedClass} from '../host/decorated_class';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {isDefined} from '../utils';
|
||||
|
@ -63,13 +62,15 @@ export class DecorationAnalyzer {
|
|||
this.rootDirs, /* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true),
|
||||
new DirectiveDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, this.isCore),
|
||||
new InjectableDecoratorHandler(this.host, this.isCore),
|
||||
new NgModuleDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, this.isCore),
|
||||
new NgModuleDecoratorHandler(
|
||||
this.typeChecker, this.host, this.scopeRegistry, this.referencesRegistry, this.isCore),
|
||||
new PipeDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, this.isCore),
|
||||
];
|
||||
|
||||
constructor(
|
||||
private typeChecker: ts.TypeChecker, private host: NgccReflectionHost,
|
||||
private rootDirs: string[], private isCore: boolean) {}
|
||||
private referencesRegistry: ReferencesRegistry, private rootDirs: string[],
|
||||
private isCore: boolean) {}
|
||||
|
||||
/**
|
||||
* Analyze a program to find all the decorated files should be transformed.
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @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';
|
||||
import {ReferencesRegistry} from '../../../ngtsc/annotations';
|
||||
import {Declaration, ReflectionHost} from '../../../ngtsc/host';
|
||||
import {Reference, ResolvedReference} from '../../../ngtsc/metadata';
|
||||
import {hasNameIdentifier} from '../utils';
|
||||
|
||||
/**
|
||||
* This is a place for DecoratorHandlers to register references that they
|
||||
* find in their analysis of the code.
|
||||
*
|
||||
* This registry is used to ensure that these references are publicly exported
|
||||
* from libraries that are compiled by ngcc.
|
||||
*/
|
||||
export class NgccReferencesRegistry implements ReferencesRegistry {
|
||||
private map = new Map<ts.Identifier, Declaration>();
|
||||
|
||||
constructor(private host: ReflectionHost) {}
|
||||
|
||||
/**
|
||||
* Register one or more references in the registry.
|
||||
* Only `ResolveReference` references are stored. Other types are ignored.
|
||||
* @param references A collection of references to register.
|
||||
*/
|
||||
add(...references: Reference<ts.Declaration>[]): void {
|
||||
references.forEach(ref => {
|
||||
// Only store resolved references. We are not interested in literals.
|
||||
if (ref instanceof ResolvedReference && hasNameIdentifier(ref.node)) {
|
||||
const declaration = this.host.getDeclarationOfIdentifier(ref.node.name);
|
||||
if (declaration && hasNameIdentifier(declaration.node)) {
|
||||
this.map.set(declaration.node.name, declaration);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a mapping for the registered resolved references.
|
||||
* @returns A map of reference identifiers to reference declarations.
|
||||
*/
|
||||
getDeclarationMap(): Map<ts.Identifier, Declaration> { return this.map; }
|
||||
}
|
|
@ -62,7 +62,7 @@ interface EntryPointPackageJson {
|
|||
* @param packageJsonPath the absolute path to the package.json file.
|
||||
* @returns JSON from the package.json file if it is valid, `null` otherwise.
|
||||
*/
|
||||
function loadEntryPointPackage(packageJsonPath: string): {[key: string]: any}|null {
|
||||
function loadEntryPointPackage(packageJsonPath: string): EntryPointPackageJson|null {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
} catch (e) {
|
||||
|
@ -109,7 +109,8 @@ export function getEntryPointInfo(packagePath: string, entryPointPath: string):
|
|||
}
|
||||
|
||||
// Also there must exist a `metadata.json` file next to the typings entry-point.
|
||||
const metadataPath = path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
|
||||
const metadataPath =
|
||||
path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
|
||||
if (!fs.existsSync(metadataPath)) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {mkdir, mv} from 'shelljs';
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
||||
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
|
||||
import {SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
||||
import {Esm2015ReflectionHost} from '../host/esm2015_host';
|
||||
import {Esm5ReflectionHost} from '../host/esm5_host';
|
||||
|
@ -156,8 +157,10 @@ export class Transformer {
|
|||
analyzeProgram(
|
||||
program: ts.Program, reflectionHost: NgccReflectionHost, rootDirs: string[],
|
||||
isCore: boolean) {
|
||||
const typeChecker = bundle.program.getTypeChecker();
|
||||
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
|
||||
const decorationAnalyzer =
|
||||
new DecorationAnalyzer(program.getTypeChecker(), reflectionHost, rootDirs, isCore);
|
||||
new DecorationAnalyzer(typeChecker, reflectionHost, referencesRegistry, rootDirs, isCore);
|
||||
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
|
||||
return {
|
||||
decorationAnalyses: decorationAnalyzer.analyzeProgram(program),
|
||||
|
|
|
@ -40,3 +40,13 @@ export function findAll<T>(node: ts.Node, test: (node: ts.Node) => node is ts.No
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given declaration have a name which is an identifier?
|
||||
* @param declaration The declaration to test.
|
||||
* @returns true if the declaration has an identifer for a name.
|
||||
*/
|
||||
export function hasNameIdentifier(declaration: ts.Declaration): declaration is ts.Declaration&
|
||||
{name: ts.Identifier} {
|
||||
return ts.isIdentifier((declaration as any).name);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import * as ts from 'typescript';
|
|||
import {Decorator} from '../../../ngtsc/host';
|
||||
import {DecoratorHandler} from '../../../ngtsc/transform';
|
||||
import {DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
|
||||
import {makeProgram} from '../helpers/utils';
|
||||
|
@ -84,9 +85,10 @@ describe('DecorationAnalyzer', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
program = makeProgram(TEST_PROGRAM);
|
||||
const analyzer = new DecorationAnalyzer(
|
||||
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
|
||||
[''], false);
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const analyzer =
|
||||
new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false);
|
||||
testHandler = createTestHandler();
|
||||
analyzer.handlers = [testHandler];
|
||||
result = analyzer.analyzeProgram(program);
|
||||
|
@ -126,9 +128,10 @@ describe('DecorationAnalyzer', () => {
|
|||
it('should analyze an internally imported component, which is not publicly exported from the entry-point',
|
||||
() => {
|
||||
const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM);
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const analyzer = new DecorationAnalyzer(
|
||||
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
|
||||
[''], false);
|
||||
program.getTypeChecker(), host, referencesRegistry, [''], false);
|
||||
const testHandler = createTestHandler();
|
||||
analyzer.handlers = [testHandler];
|
||||
const result = analyzer.analyzeProgram(program);
|
||||
|
@ -142,9 +145,10 @@ describe('DecorationAnalyzer', () => {
|
|||
|
||||
it('should analyze an internally defined component, which is not exported at all', () => {
|
||||
const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM);
|
||||
const analyzer = new DecorationAnalyzer(
|
||||
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
|
||||
[''], false);
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const analyzer =
|
||||
new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false);
|
||||
const testHandler = createTestHandler();
|
||||
analyzer.handlers = [testHandler];
|
||||
const result = analyzer.analyzeProgram(program);
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @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';
|
||||
|
||||
import {Reference, TypeScriptReflectionHost, staticallyResolve} from '../../../ngtsc/metadata';
|
||||
import {getDeclaration, makeProgram} from '../../../ngtsc/testing/in_memory_typescript';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
|
||||
describe('NgccReferencesRegistry', () => {
|
||||
it('should return a mapping from resolved reference identifiers to their declarations', () => {
|
||||
const {program} = makeProgram([{
|
||||
name: 'index.ts',
|
||||
contents: `
|
||||
export class SomeClass {}
|
||||
export function someFunction() {}
|
||||
export const someVariable = 42;
|
||||
|
||||
export const testArray = [SomeClass, someFunction, someVariable];
|
||||
`
|
||||
}]);
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
const testArrayDeclaration =
|
||||
getDeclaration(program, 'index.ts', 'testArray', ts.isVariableDeclaration);
|
||||
const someClassDecl = getDeclaration(program, 'index.ts', 'SomeClass', ts.isClassDeclaration);
|
||||
const someFunctionDecl =
|
||||
getDeclaration(program, 'index.ts', 'someFunction', ts.isFunctionDeclaration);
|
||||
const someVariableDecl =
|
||||
getDeclaration(program, 'index.ts', 'someVariable', ts.isVariableDeclaration);
|
||||
const testArrayExpression = testArrayDeclaration.initializer !;
|
||||
|
||||
const host = new TypeScriptReflectionHost(checker);
|
||||
const registry = new NgccReferencesRegistry(host);
|
||||
|
||||
const references =
|
||||
staticallyResolve(testArrayExpression, host, checker) as Reference<ts.Declaration>[];
|
||||
registry.add(...references);
|
||||
|
||||
const map = registry.getDeclarationMap();
|
||||
expect(map.size).toEqual(2);
|
||||
expect(map.get(someClassDecl.name !) !.node).toBe(someClassDecl);
|
||||
expect(map.get(someFunctionDecl.name !) !.node).toBe(someFunctionDecl);
|
||||
expect(map.has(someVariableDecl.name as ts.Identifier)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -12,6 +12,7 @@ import MagicString from 'magic-string';
|
|||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||
import {makeProgram} from '../helpers/utils';
|
||||
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {BundleInfo, createBundleInfo} from '../../src/packages/bundle';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
|
@ -46,8 +47,10 @@ function createTestRenderer(
|
|||
options.rewriteCoreImportsTo ? program.getSourceFile(options.rewriteCoreImportsTo) ! : null;
|
||||
const bundle = createBundleInfo(options.isCore || false, rewriteCoreImportsTo, null);
|
||||
const host = new Esm2015ReflectionHost(bundle.isCore, program.getTypeChecker());
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const decorationAnalyses =
|
||||
new DecorationAnalyzer(program.getTypeChecker(), host, [''], bundle.isCore)
|
||||
new DecorationAnalyzer(
|
||||
program.getTypeChecker(), host, referencesRegistry, [''], bundle.isCore)
|
||||
.analyzeProgram(program);
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
|
||||
const renderer = new TestRenderer(host, bundle);
|
||||
|
|
|
@ -15,4 +15,5 @@ export {DirectiveDecoratorHandler} from './src/directive';
|
|||
export {InjectableDecoratorHandler} from './src/injectable';
|
||||
export {NgModuleDecoratorHandler} from './src/ng_module';
|
||||
export {PipeDecoratorHandler} from './src/pipe';
|
||||
export {NoopReferencesRegistry, ReferencesRegistry} from './src/references_registry';
|
||||
export {CompilationScope, SelectorScopeRegistry} from './src/selector_scope';
|
||||
|
|
|
@ -15,6 +15,7 @@ import {Reference, ResolvedReference, ResolvedValue, reflectObjectLiteral, stati
|
|||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
import {ReferencesRegistry} from './references_registry';
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util';
|
||||
|
||||
|
@ -32,7 +33,8 @@ export interface NgModuleAnalysis {
|
|||
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis, Decorator> {
|
||||
constructor(
|
||||
private checker: ts.TypeChecker, private reflector: ReflectionHost,
|
||||
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
|
||||
private scopeRegistry: SelectorScopeRegistry, private referencesRegistry: ReferencesRegistry,
|
||||
private isCore: boolean) {}
|
||||
|
||||
detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined {
|
||||
if (!decorators) {
|
||||
|
@ -72,6 +74,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
const expr = ngModule.get('declarations') !;
|
||||
const declarationMeta = staticallyResolve(expr, this.reflector, this.checker);
|
||||
declarations = this.resolveTypeList(expr, declarationMeta, 'declarations');
|
||||
this.referencesRegistry.add(...declarations);
|
||||
}
|
||||
let imports: Reference<ts.Declaration>[] = [];
|
||||
if (ngModule.has('imports')) {
|
||||
|
@ -80,6 +83,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
expr, this.reflector, this.checker,
|
||||
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
||||
imports = this.resolveTypeList(expr, importsMeta, 'imports');
|
||||
this.referencesRegistry.add(...imports);
|
||||
}
|
||||
let exports: Reference<ts.Declaration>[] = [];
|
||||
if (ngModule.has('exports')) {
|
||||
|
@ -88,12 +92,14 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
expr, this.reflector, this.checker,
|
||||
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
|
||||
exports = this.resolveTypeList(expr, exportsMeta, 'exports');
|
||||
this.referencesRegistry.add(...exports);
|
||||
}
|
||||
let bootstrap: Reference<ts.Declaration>[] = [];
|
||||
if (ngModule.has('bootstrap')) {
|
||||
const expr = ngModule.get('bootstrap') !;
|
||||
const bootstrapMeta = staticallyResolve(expr, this.reflector, this.checker);
|
||||
bootstrap = this.resolveTypeList(expr, bootstrapMeta, 'bootstrap');
|
||||
this.referencesRegistry.add(...bootstrap);
|
||||
}
|
||||
|
||||
// Register this module's information with the SelectorScopeRegistry. This ensures that during
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @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';
|
||||
import {Declaration} from '../../host';
|
||||
import {Reference} from '../../metadata';
|
||||
|
||||
/**
|
||||
* Implement this interface if you want DecoratorHandlers to register
|
||||
* references that they find in their analysis of the code.
|
||||
*/
|
||||
export interface ReferencesRegistry {
|
||||
/**
|
||||
* Register one or more references in the registry.
|
||||
* Only `ResolveReference` references are stored. Other types are ignored.
|
||||
* @param references A collection of references to register.
|
||||
*/
|
||||
add(...references: Reference<ts.Declaration>[]): void;
|
||||
/**
|
||||
* Create and return a mapping for the registered resolved references.
|
||||
* @returns A map of reference identifiers to reference declarations.
|
||||
*/
|
||||
getDeclarationMap(): Map<ts.Identifier, Declaration>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This registry does nothing, since ngtsc does not currently need
|
||||
* this functionality.
|
||||
* The ngcc tool implements a working version for its purposes.
|
||||
*/
|
||||
export class NoopReferencesRegistry implements ReferencesRegistry {
|
||||
add(...references: Reference<ts.Declaration>[]): void {}
|
||||
getDeclarationMap(): Map<ts.Identifier, Declaration> { return new Map(); }
|
||||
}
|
|
@ -13,7 +13,7 @@ import * as ts from 'typescript';
|
|||
import * as api from '../transformers/api';
|
||||
import {nocollapseHack} from '../transformers/nocollapse_hack';
|
||||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations';
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations';
|
||||
import {BaseDefDecoratorHandler} from './annotations/src/base_def';
|
||||
import {TypeScriptReflectionHost} from './metadata';
|
||||
import {FileResourceLoader, HostResourceLoader} from './resource_loader';
|
||||
|
@ -214,6 +214,7 @@ export class NgtscProgram implements api.Program {
|
|||
private makeCompilation(): IvyCompilation {
|
||||
const checker = this.tsProgram.getTypeChecker();
|
||||
const scopeRegistry = new SelectorScopeRegistry(checker, this.reflector);
|
||||
const referencesRegistry = new NoopReferencesRegistry();
|
||||
|
||||
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
||||
const handlers = [
|
||||
|
@ -223,7 +224,8 @@ export class NgtscProgram implements api.Program {
|
|||
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false),
|
||||
new DirectiveDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore),
|
||||
new InjectableDecoratorHandler(this.reflector, this.isCore),
|
||||
new NgModuleDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore),
|
||||
new NgModuleDecoratorHandler(
|
||||
checker, this.reflector, scopeRegistry, referencesRegistry, this.isCore),
|
||||
new PipeDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore),
|
||||
];
|
||||
|
||||
|
|
Loading…
Reference in New Issue