This allows CLI usage to filter excessive log messages and integrations like webpack plugins to provide their own logger. // FW-1198 PR Close #29591
		
			
				
	
	
		
			243 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @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} from '../../../src/ngtsc/imports';
 | |
| import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
 | |
| import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
 | |
| import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
 | |
| import {MockLogger} from '../helpers/mock_logger';
 | |
| import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
 | |
| 
 | |
| describe('PrivateDeclarationsAnalyzer', () => {
 | |
|   describe('analyzeProgram()', () => {
 | |
| 
 | |
|     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 PrivateComponent1 {}
 | |
|     PrivateComponent1.decorators = [
 | |
|       {type: Component, args: [{selectors: 'b', template: ''}]}
 | |
|     ];
 | |
|     class PrivateComponent2 {}
 | |
|     PrivateComponent2.decorators = [
 | |
|       {type: Component, args: [{selectors: 'c', template: ''}]}
 | |
|     ];
 | |
|     export class ModuleB {}
 | |
|     ModuleB.decorators = [
 | |
|       {type: NgModule, args: [{declarations: [PrivateComponent1]}]}
 | |
|     ];
 | |
|   `
 | |
|       },
 | |
|       {
 | |
|         name: '/src/c.js',
 | |
|         isRoot: false,
 | |
|         contents: `
 | |
|     import {Component} from '@angular/core';
 | |
|     export class InternalComponent1 {}
 | |
|     InternalComponent1.decorators = [
 | |
|       {type: Component, args: [{selectors: 'd', template: ''}]}
 | |
|     ];
 | |
|     export class InternalComponent2 {}
 | |
|     InternalComponent2.decorators = [
 | |
|       {type: Component, args: [{selectors: 'e', template: ''}]}
 | |
|     ];
 | |
|   `
 | |
|       },
 | |
|       {
 | |
|         name: '/src/mod.js',
 | |
|         isRoot: false,
 | |
|         contents: `
 | |
|     import {Component, NgModule} from '@angular/core';
 | |
|     import {PublicComponent} from './a';
 | |
|     import {ModuleB} from './b';
 | |
|     import {InternalComponent1} from './c';
 | |
|     export class ModuleA {}
 | |
|     ModuleA.decorators = [
 | |
|       {type: NgModule, args: [{
 | |
|         declarations: [PublicComponent, InternalComponent1],
 | |
|         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 InternalComponent1 {}
 | |
|   `
 | |
|       },
 | |
|       {
 | |
|         name: '/typings/mod.d.ts',
 | |
|         isRoot: false,
 | |
|         contents: `
 | |
|     import {PublicComponent} from './a';
 | |
|     import {ModuleB} from './b';
 | |
|     import {InternalComponent1} from './c';
 | |
|     export declare class ModuleA {}
 | |
|   `
 | |
|       },
 | |
|     ];
 | |
| 
 | |
|     it('should find all NgModule declarations that were not publicly exported from the entry-point',
 | |
|        () => {
 | |
|          const {program, referencesRegistry, analyzer} = setup(TEST_PROGRAM, TEST_DTS_PROGRAM);
 | |
| 
 | |
|          addToReferencesRegistry(program, referencesRegistry, '/src/a.js', 'PublicComponent');
 | |
|          addToReferencesRegistry(program, referencesRegistry, '/src/b.js', 'PrivateComponent1');
 | |
|          addToReferencesRegistry(program, referencesRegistry, '/src/c.js', 'InternalComponent1');
 | |
| 
 | |
|          const analyses = analyzer.analyzeProgram(program);
 | |
|          // Note that `PrivateComponent2` and `InternalComponent2` are not found because they are
 | |
|          // not added to the ReferencesRegistry (i.e. they were not declared in an NgModule).
 | |
|          expect(analyses.length).toEqual(2);
 | |
|          expect(analyses).toEqual([
 | |
|            {identifier: 'PrivateComponent1', from: '/src/b.js', dtsFrom: null, alias: null},
 | |
|            {
 | |
|              identifier: 'InternalComponent1',
 | |
|              from: '/src/c.js',
 | |
|              dtsFrom: '/typings/c.d.ts',
 | |
|              alias: null
 | |
|            },
 | |
|          ]);
 | |
|        });
 | |
| 
 | |
|     const ALIASED_EXPORTS_PROGRAM = [
 | |
|       {
 | |
|         name: '/src/entry_point.js',
 | |
|         isRoot: true,
 | |
|         contents: `
 | |
|         // This component is only exported as an alias.
 | |
|         export {ComponentOne as aliasedComponentOne} from './a';
 | |
|         // This component is exported both as itself and an alias.
 | |
|         export {ComponentTwo as aliasedComponentTwo, ComponentTwo} from './a';
 | |
|       `
 | |
|       },
 | |
|       {
 | |
|         name: '/src/a.js',
 | |
|         isRoot: false,
 | |
|         contents: `
 | |
|       import {Component} from '@angular/core';
 | |
|       export class ComponentOne {}
 | |
|       ComponentOne.decorators = [
 | |
|         {type: Component, args: [{selectors: 'a', template: ''}]}
 | |
|       ];
 | |
| 
 | |
|       export class ComponentTwo {}
 | |
|       Component.decorators = [
 | |
|         {type: Component, args: [{selectors: 'a', template: ''}]}
 | |
|       ];
 | |
|     `
 | |
|       }
 | |
|     ];
 | |
|     const ALIASED_EXPORTS_DTS_PROGRAM = [
 | |
|       {
 | |
|         name: '/typings/entry_point.d.ts',
 | |
|         isRoot: true,
 | |
|         contents: `
 | |
|         export declare class aliasedComponentOne {}
 | |
|         export declare class ComponentTwo {}
 | |
|         export {ComponentTwo as aliasedComponentTwo}
 | |
|       `
 | |
|       },
 | |
|     ];
 | |
| 
 | |
|     it('should find all non-public declarations that were aliased', () => {
 | |
|       const {program, referencesRegistry, analyzer} =
 | |
|           setup(ALIASED_EXPORTS_PROGRAM, ALIASED_EXPORTS_DTS_PROGRAM);
 | |
| 
 | |
|       addToReferencesRegistry(program, referencesRegistry, '/src/a.js', 'ComponentOne');
 | |
|       addToReferencesRegistry(program, referencesRegistry, '/src/a.js', 'ComponentTwo');
 | |
| 
 | |
|       const analyses = analyzer.analyzeProgram(program);
 | |
|       expect(analyses).toEqual([{
 | |
|         identifier: 'ComponentOne',
 | |
|         from: '/src/a.js',
 | |
|         dtsFrom: null,
 | |
|         alias: 'aliasedComponentOne',
 | |
|       }]);
 | |
|     });
 | |
|   });
 | |
| });
 | |
| 
 | |
| type Files = {
 | |
|   name: string,
 | |
|   contents: string, isRoot?: boolean | undefined
 | |
| }[];
 | |
| 
 | |
| function setup(jsProgram: Files, dtsProgram: Files) {
 | |
|   const program = makeTestProgram(...jsProgram);
 | |
|   const dts = makeTestBundleProgram(dtsProgram);
 | |
|   const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
 | |
|   const referencesRegistry = new NgccReferencesRegistry(host);
 | |
|   const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);
 | |
|   return {program, referencesRegistry, analyzer};
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add up the named component to the references registry.
 | |
|  *
 | |
|  * This would normally be done by the decoration handlers in the `DecorationAnalyzer`.
 | |
|  */
 | |
| function addToReferencesRegistry(
 | |
|     program: ts.Program, registry: NgccReferencesRegistry, fileName: string,
 | |
|     componentName: string) {
 | |
|   const declaration = getDeclaration(program, fileName, componentName, ts.isClassDeclaration);
 | |
|   registry.add(null !, new Reference(declaration));
 | |
| }
 |