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));
 | 
						|
}
 |