feat(ivy): ngcc - add `PrivateDeclarationsAnalyzer` (#26906)
This analyzer searches the source for declared classes that are not exported publicly from the entry-point. PR Close #26906
This commit is contained in:
parent
b55e1c2ba9
commit
bf3ac41e36
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @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} from '../../../ngtsc/host';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {hasNameIdentifier, isDefined} from '../utils';
|
||||
|
||||
export interface ExportInfo {
|
||||
identifier: string;
|
||||
from: string;
|
||||
dtsFrom: string|null;
|
||||
}
|
||||
export type PrivateDeclarationsAnalyses = ExportInfo[];
|
||||
|
||||
/**
|
||||
* This class will analyze a program to find all the declared classes
|
||||
* (i.e. on an NgModule) that are not publicly exported via an entry-point.
|
||||
*/
|
||||
export class PrivateDeclarationsAnalyzer {
|
||||
constructor(private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry) {}
|
||||
|
||||
analyzeProgram(program: ts.Program): PrivateDeclarationsAnalyses {
|
||||
const rootFiles = this.getRootFiles(program);
|
||||
return this.getPrivateDeclarations(rootFiles, this.referencesRegistry.getDeclarationMap());
|
||||
}
|
||||
|
||||
private getRootFiles(program: ts.Program): ts.SourceFile[] {
|
||||
return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined);
|
||||
}
|
||||
|
||||
private getPrivateDeclarations(
|
||||
rootFiles: ts.SourceFile[],
|
||||
declarations: Map<ts.Identifier, Declaration>): PrivateDeclarationsAnalyses {
|
||||
const privateDeclarations: Map<ts.Identifier, Declaration> = new Map(declarations);
|
||||
rootFiles.forEach(f => {
|
||||
const exports = this.host.getExportsOfModule(f);
|
||||
if (exports) {
|
||||
exports.forEach((declaration, exportedName) => {
|
||||
if (hasNameIdentifier(declaration.node) && declaration.node.name.text === exportedName) {
|
||||
privateDeclarations.delete(declaration.node.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return Array.from(privateDeclarations.keys()).map(id => {
|
||||
const from = id.getSourceFile().fileName;
|
||||
const declaration = privateDeclarations.get(id) !;
|
||||
const dtsDeclaration = this.host.getDtsDeclarationOfClass(declaration.node);
|
||||
const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName;
|
||||
return {identifier: id.text, from, dtsFrom};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* @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 {ResolvedReference} from '@angular/compiler-cli/src/ngtsc/metadata';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
|
||||
import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
|
||||
|
||||
const TEST_PROGRAM = [
|
||||
{
|
||||
name: '/src/entry_point.js',
|
||||
isRoot: true,
|
||||
contents: `
|
||||
export {PublicComponent} from './a';
|
||||
export {ModuleA} from './mod';
|
||||
export {ModuleB} from './b';
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/a.js',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
import {Component} from '@angular/core';
|
||||
export class PublicComponent {}
|
||||
PublicComponent.decorators = [
|
||||
{type: Component, args: [{selectors: 'a', template: ''}]}
|
||||
];
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/b.js',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
class PrivateComponent {}
|
||||
PrivateComponent.decorators = [
|
||||
{type: Component, args: [{selectors: 'b', template: ''}]}
|
||||
];
|
||||
export class ModuleB {}
|
||||
ModuleB.decorators = [
|
||||
{type: NgModule, args: [{declarations: [PrivateComponent]}]}
|
||||
];
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/c.js',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
import {Component} from '@angular/core';
|
||||
export class InternalComponent {}
|
||||
InternalComponent.decorators = [
|
||||
{type: Component, args: [{selectors: 'c', template: ''}]}
|
||||
];
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/src/mod.js',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {PublicComponent} from './a';
|
||||
import {ModuleB} from './b';
|
||||
import {InternalComponent} from './c';
|
||||
export class ModuleA {}
|
||||
ModuleA.decorators = [
|
||||
{type: NgModule, args: [{
|
||||
declarations: [PublicComponent, InternalComponent],
|
||||
imports: [ModuleB]
|
||||
}]}
|
||||
];
|
||||
`
|
||||
}
|
||||
];
|
||||
const TEST_DTS_PROGRAM = [
|
||||
{
|
||||
name: '/typings/entry_point.d.ts',
|
||||
isRoot: true,
|
||||
contents: `
|
||||
export {PublicComponent} from './a';
|
||||
export {ModuleA} from './mod';
|
||||
export {ModuleB} from './b';
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/a.d.ts',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
export declare class PublicComponent {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/b.d.ts',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
export declare class ModuleB {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/c.d.ts',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
export declare class InternalComponent {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: '/typings/mod.d.ts',
|
||||
isRoot: false,
|
||||
contents: `
|
||||
import {PublicComponent} from './a';
|
||||
import {ModuleB} from './b';
|
||||
import {InternalComponent} from './c';
|
||||
export declare class ModuleA {}
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
describe('PrivateDeclarationsAnalyzer', () => {
|
||||
describe('analyzeProgram()', () => {
|
||||
it('should find all NgModule declarations that were not publicly exported from the entry-point',
|
||||
() => {
|
||||
const program = makeTestProgram(...TEST_PROGRAM);
|
||||
const dts = makeTestBundleProgram(TEST_DTS_PROGRAM);
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dts);
|
||||
const referencesRegistry = new NgccReferencesRegistry(host);
|
||||
const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);
|
||||
|
||||
// Set up the registry with references - this would normally be done by the
|
||||
// decoration handlers in the `DecorationAnalyzer`.
|
||||
const publicComponentDeclaration =
|
||||
getDeclaration(program, '/src/a.js', 'PublicComponent', ts.isClassDeclaration);
|
||||
referencesRegistry.add(
|
||||
new ResolvedReference(publicComponentDeclaration, publicComponentDeclaration.name !));
|
||||
const privateComponentDeclaration =
|
||||
getDeclaration(program, '/src/b.js', 'PrivateComponent', ts.isClassDeclaration);
|
||||
referencesRegistry.add(new ResolvedReference(
|
||||
privateComponentDeclaration, privateComponentDeclaration.name !));
|
||||
const internalComponentDeclaration =
|
||||
getDeclaration(program, '/src/c.js', 'InternalComponent', ts.isClassDeclaration);
|
||||
referencesRegistry.add(new ResolvedReference(
|
||||
internalComponentDeclaration, internalComponentDeclaration.name !));
|
||||
|
||||
const analyses = analyzer.analyzeProgram(program);
|
||||
expect(analyses.length).toEqual(2);
|
||||
expect(analyses).toEqual([
|
||||
{identifier: 'PrivateComponent', from: '/src/b.js', dtsFrom: null},
|
||||
{identifier: 'InternalComponent', from: '/src/c.js', dtsFrom: '/typings/c.d.ts'},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue