Update the license headers throughout the repository to reference Google LLC rather than Google Inc, for the required license headers. PR Close #37205
		
			
				
	
	
		
			659 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			659 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC 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 chai from 'chai';
 | |
| import * as ts from 'typescript';
 | |
| 
 | |
| import {publicApiInternal, SerializationOptions} from '../lib/serializer';
 | |
| 
 | |
| const classesAndInterfaces = `
 | |
|   export declare class A {
 | |
|       field: string;
 | |
|       method(a: string): number;
 | |
|   }
 | |
|   export interface B {
 | |
|       field: A;
 | |
|   }
 | |
|   export declare class C {
 | |
|       someProp: string;
 | |
|       propWithDefault: number;
 | |
|       private privateProp;
 | |
|       protected protectedProp: number;
 | |
|       constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number);
 | |
|   }
 | |
| `;
 | |
| 
 | |
| describe('unit test', () => {
 | |
|   let _warn: any = null;
 | |
|   let warnings: string[] = [];
 | |
|   beforeEach(() => {
 | |
|     _warn = console.warn;
 | |
|     console.warn = (...args: string[]) => warnings.push(args.join(' '));
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     console.warn = _warn;
 | |
|     warnings = [];
 | |
|     _warn = null;
 | |
|   });
 | |
| 
 | |
|   it('should ignore private methods', () => {
 | |
|     const input = `
 | |
|       export declare class A {
 | |
|           fa(): void;
 | |
|           protected fb(): void;
 | |
|           private fc();
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A {
 | |
|           fa(): void;
 | |
|           protected fb(): void;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should support overloads functions', () => {
 | |
|     const input = `
 | |
|       export declare function group(steps: AnimationMetadata[], options?: AnimationOptions | null): AnimationGroupMetadata;
 | |
| 
 | |
|       export declare function registerLocaleData(data: any, extraData?: any): void;
 | |
|       export declare function registerLocaleData(data: any, localeId?: string, extraData?: any): void;
 | |
|     `;
 | |
| 
 | |
|     const expected = `
 | |
|       export declare function group(steps: AnimationMetadata[], options?: AnimationOptions | null): AnimationGroupMetadata;
 | |
| 
 | |
|       export declare function registerLocaleData(data: any, extraData?: any): void;
 | |
|       export declare function registerLocaleData(data: any, localeId?: string, extraData?: any): void;
 | |
|     `;
 | |
| 
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should ignore private props', () => {
 | |
|     const input = `
 | |
|       export declare class A {
 | |
|           fa: any;
 | |
|           protected fb: any;
 | |
|           private fc;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A {
 | |
|           fa: any;
 | |
|           protected fb: any;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should support imports without capturing imports', () => {
 | |
|     const input = `
 | |
|       import {A} from './classes_and_interfaces';
 | |
|       export declare class C {
 | |
|           field: A;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class C {
 | |
|           field: A;
 | |
|       }
 | |
|     `;
 | |
|     check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should throw on aliased reexports', () => {
 | |
|     const input = `
 | |
|       export { A as Apple } from './classes_and_interfaces';
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input},
 | |
|         'Symbol "A" was aliased as "Apple". Aliases are not supported.');
 | |
|   });
 | |
| 
 | |
|   it('should remove reexported external symbols', () => {
 | |
|     const input = `
 | |
|       export { Foo } from 'some-external-module-that-cannot-be-resolved';
 | |
|     `;
 | |
|     const expected = `
 | |
|     `;
 | |
|     check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected);
 | |
|     chai.assert.deepEqual(
 | |
|         warnings, ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']);
 | |
|   });
 | |
| 
 | |
|   it('should sort exports', () => {
 | |
|     const input = `
 | |
|       export declare type E = string;
 | |
|       export interface D {
 | |
|           e: number;
 | |
|       }
 | |
|       export declare var e: C;
 | |
|       export declare class C {
 | |
|           e: number;
 | |
|           d: string;
 | |
|       }
 | |
|       export declare function b(): boolean;
 | |
|       export declare const a: string;
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare const a: string;
 | |
| 
 | |
|       export declare function b(): boolean;
 | |
| 
 | |
|       export declare class C {
 | |
|           d: string;
 | |
|           e: number;
 | |
|       }
 | |
| 
 | |
|       export interface D {
 | |
|           e: number;
 | |
|       }
 | |
| 
 | |
|       export declare var e: C;
 | |
| 
 | |
|       export declare type E = string;
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should sort class members', () => {
 | |
|     const input = `
 | |
|       export class A {
 | |
|         f: number;
 | |
|         static foo(): void;
 | |
|         c: string;
 | |
|         static a: boolean;
 | |
|         constructor();
 | |
|         static bar(): void;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export class A {
 | |
|         c: string;
 | |
|         f: number;
 | |
|         constructor();
 | |
|         static a: boolean;
 | |
|         static bar(): void;
 | |
|         static foo(): void;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should sort interface members', () => {
 | |
|     const input = `
 | |
|       export interface A {
 | |
|         (): void;
 | |
|         [key: string]: any;
 | |
|         c(): void;
 | |
|         a: number;
 | |
|         new (): Object;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export interface A {
 | |
|         a: number;
 | |
|         (): void;
 | |
|         new (): Object;
 | |
|         [key: string]: any;
 | |
|         c(): void;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should sort class members including readonly', () => {
 | |
|     const input = `
 | |
|         export declare class DebugNode {
 | |
|           private _debugContext;
 | |
|           nativeNode: any;
 | |
|           listeners: any[];
 | |
|           parent: any | null;
 | |
|           constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any);
 | |
|           readonly injector: any;
 | |
|           readonly componentInstance: any;
 | |
|           readonly context: any;
 | |
|           readonly references: {
 | |
|               [key: string]: any;
 | |
|           };
 | |
|           readonly providerTokens: any[];
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|         export declare class DebugNode {
 | |
|           readonly componentInstance: any;
 | |
|           readonly context: any;
 | |
|           readonly injector: any;
 | |
|           listeners: any[];
 | |
|           nativeNode: any;
 | |
|           parent: any | null;
 | |
|           readonly providerTokens: any[];
 | |
|           readonly references: {
 | |
|               [key: string]: any;
 | |
|           };
 | |
|           constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any);
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should sort two call signatures', () => {
 | |
|     const input = `
 | |
|       export interface A {
 | |
|         (b: number): void;
 | |
|         (a: number): void;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export interface A {
 | |
|         (a: number): void;
 | |
|         (b: number): void;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should sort exports including re-exports', () => {
 | |
|     const submodule = `
 | |
|       export declare var e: C;
 | |
|       export declare class C {
 | |
|           e: number;
 | |
|           d: string;
 | |
|       }
 | |
|     `;
 | |
|     const input = `
 | |
|       export * from './submodule';
 | |
|       export declare type E = string;
 | |
|       export interface D {
 | |
|           e: number;
 | |
|       }
 | |
|       export declare function b(): boolean;
 | |
|       export declare const a: string;
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare const a: string;
 | |
| 
 | |
|       export declare function b(): boolean;
 | |
| 
 | |
|       export declare class C {
 | |
|           d: string;
 | |
|           e: number;
 | |
|       }
 | |
| 
 | |
|       export interface D {
 | |
|           e: number;
 | |
|       }
 | |
| 
 | |
|       export declare var e: C;
 | |
| 
 | |
|       export declare type E = string;
 | |
|     `;
 | |
|     check({'submodule.d.ts': submodule, 'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should remove module comments', () => {
 | |
|     const input = `
 | |
|       /**
 | |
|        * An amazing module.
 | |
|        * @module
 | |
|        */
 | |
|       /**
 | |
|        * Foo function.
 | |
|        */
 | |
|       export declare function foo(): boolean;
 | |
|       export declare const bar: number;
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare const bar: number;
 | |
| 
 | |
|       export declare function foo(): boolean;
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should remove class and field comments', () => {
 | |
|     const input = `
 | |
|       /**
 | |
|        * Does something really cool.
 | |
|        */
 | |
|       export declare class A {
 | |
|           /**
 | |
|            * A very interesting getter.
 | |
|            */
 | |
|           b: string;
 | |
|           /**
 | |
|            * A very useful field.
 | |
|            */
 | |
|           name: string;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A {
 | |
|           b: string;
 | |
|           name: string;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should skip symbols matching specified pattern', () => {
 | |
|     const input = `
 | |
|       export const __a__: string;
 | |
|       export class B {
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export class B {
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected, {stripExportPattern: /^__.*/});
 | |
|   });
 | |
| 
 | |
|   it('should throw on using module imports in expression position that were not explicitly allowed',
 | |
|      () => {
 | |
|        const input = `
 | |
|       import * as foo from './foo';
 | |
|       export declare class A extends foo.A {
 | |
|       }
 | |
|     `;
 | |
|        checkThrows(
 | |
|            {'file.d.ts': input},
 | |
|            'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' +
 | |
|                'Remove it from source or allow it via --allowModuleIdentifiers.');
 | |
|      });
 | |
| 
 | |
|   it('should throw on using module imports in type position that were not explicitly allowed',
 | |
|      () => {
 | |
|        const input = `
 | |
|       import * as foo from './foo';
 | |
|       export type A = foo.A;
 | |
|     `;
 | |
|        checkThrows(
 | |
|            {'file.d.ts': input},
 | |
|            'file.d.ts(2,17): error: Module identifier "foo" is not allowed. ' +
 | |
|                'Remove it from source or allow it via --allowModuleIdentifiers.');
 | |
|      });
 | |
| 
 | |
|   it('should not throw on using explicitly allowed module imports', () => {
 | |
|     const input = `
 | |
|       import * as foo from './foo';
 | |
|       export declare class A extends foo.A {
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A extends foo.A {
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected, {allowModuleIdentifiers: ['foo']});
 | |
|   });
 | |
| 
 | |
|   it('should not throw if module imports, that were not explicitly allowed, are not used', () => {
 | |
|     const input = `
 | |
|       import * as foo from './foo';
 | |
|       export declare class A {
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A {
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected);
 | |
|   });
 | |
| 
 | |
|   it('should copy specified jsdoc tags of exports in docstrings', () => {
 | |
|     const input = `
 | |
|       /**
 | |
|        * @deprecated This is useless now
 | |
|        */
 | |
|       export declare class A {
 | |
|       }
 | |
|       /**
 | |
|        * @experimental
 | |
|        */
 | |
|       export declare const b: string;
 | |
|       /**
 | |
|        * @stable
 | |
|        */
 | |
|       export declare var c: number;
 | |
|     `;
 | |
|     const expected = `
 | |
|       /** @deprecated */
 | |
|       export declare class A {
 | |
|       }
 | |
| 
 | |
|       /** @experimental */
 | |
|       export declare const b: string;
 | |
| 
 | |
|       export declare var c: number;
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected, {exportTags: {toCopy: ['deprecated', 'experimental']}});
 | |
|   });
 | |
| 
 | |
|   it('should copy specified jsdoc tags of fields in docstrings', () => {
 | |
|     const input = `
 | |
|       /** @otherTag */
 | |
|       export declare class A {
 | |
|         /**
 | |
|          * @stable
 | |
|          */
 | |
|         value: number;
 | |
|         /**
 | |
|          * @experimental
 | |
|          * @otherTag
 | |
|          */
 | |
|         constructor();
 | |
|         /**
 | |
|          * @deprecated
 | |
|          */
 | |
|         foo(): void;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A {
 | |
|         value: number;
 | |
|         /** @experimental */ constructor();
 | |
|         /** @deprecated */ foo(): void;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected, {memberTags: {toCopy: ['deprecated', 'experimental']}});
 | |
|   });
 | |
| 
 | |
|   it('should copy specified jsdoc tags of parameters in docstrings', () => {
 | |
|     const input = `
 | |
|       export declare class A {
 | |
|         foo(str: string, /** @deprecated */ value: number): void;
 | |
|       }
 | |
|     `;
 | |
|     const expected = `
 | |
|       export declare class A {
 | |
|         foo(str: string, /** @deprecated */ value: number): void;
 | |
|       }
 | |
|     `;
 | |
|     check({'file.d.ts': input}, expected, {paramTags: {toCopy: ['deprecated', 'experimental']}});
 | |
|   });
 | |
| 
 | |
|   it('should throw on using banned jsdoc tags on exports', () => {
 | |
|     const input = `
 | |
|       /**
 | |
|        * @stable
 | |
|        */
 | |
|       export declare class A {
 | |
|         value: number;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(4,1): error: Banned jsdoc tags - "@stable" - were found on `A`.',
 | |
|         {exportTags: {banned: ['stable']}});
 | |
|   });
 | |
| 
 | |
|   it('should throw on using banned jsdoc tags on fields', () => {
 | |
|     const input = `
 | |
|       export declare class A {
 | |
|         /**
 | |
|          * @stable
 | |
|          */
 | |
|         value: number;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(5,3): error: Banned jsdoc tags - "@stable" - were found on `value`.',
 | |
|         {memberTags: {banned: ['stable']}});
 | |
|   });
 | |
| 
 | |
|   it('should throw on using banned jsdoc tags on parameters', () => {
 | |
|     const input = `
 | |
|       export declare class A {
 | |
|         foo(/** @stable */ param: number): void;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(2,22): error: Banned jsdoc tags - "@stable" - were found on `param`.',
 | |
|         {paramTags: {banned: ['stable']}});
 | |
|   });
 | |
| 
 | |
|   it('should throw on missing required jsdoc tags on exports', () => {
 | |
|     const input = `
 | |
|       /** @experimental */
 | |
|       export declare class A {
 | |
|         value: number;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(2,1): error: Required jsdoc tags - One of the tags: "@stable" - must exist on `A`.',
 | |
|         {exportTags: {requireAtLeastOne: ['stable']}});
 | |
|   });
 | |
| 
 | |
|   it('should throw on missing required jsdoc tags on fields', () => {
 | |
|     const input = `
 | |
|       /** @experimental */
 | |
|       export declare class A {
 | |
|         value: number;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(3,3): error: Required jsdoc tags - One of the tags: "@stable" - must exist on `value`.',
 | |
|         {memberTags: {requireAtLeastOne: ['stable']}});
 | |
|   });
 | |
| 
 | |
|   it('should throw on missing required jsdoc tags on parameters', () => {
 | |
|     const input = `
 | |
|       /** @experimental */
 | |
|       export declare class A {
 | |
|         foo(param: number): void;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(3,7): error: Required jsdoc tags - One of the tags: "@stable" - must exist on `param`.',
 | |
|         {paramTags: {requireAtLeastOne: ['stable']}});
 | |
|   });
 | |
| 
 | |
|   it('should require at least one of the requireAtLeastOne tags', () => {
 | |
|     const input = `
 | |
|       /** @experimental */
 | |
|       export declare class A {
 | |
|         foo(param: number): void;
 | |
|       }
 | |
|     `;
 | |
|     checkThrows(
 | |
|         {'file.d.ts': input},
 | |
|         'file.d.ts(3,7): error: Required jsdoc tags - One of the tags: "@stable", "@foo", "@bar" - must exist on `param`.',
 | |
|         {paramTags: {requireAtLeastOne: ['stable', 'foo', 'bar']}});
 | |
|   });
 | |
| 
 | |
|   it('should allow with one of the requireAtLeastOne tags found', () => {
 | |
|     const input = `
 | |
|       /**
 | |
|        * @foo
 | |
|        * @bar
 | |
|        * @stable
 | |
|        */
 | |
|       export declare class A {
 | |
|       }
 | |
|       /**
 | |
|        * @foo
 | |
|        */
 | |
|       export declare const b: string;
 | |
|       /**
 | |
|        * @bar
 | |
|        */
 | |
|       export declare var c: number;
 | |
|       /**
 | |
|        * @stable
 | |
|        */
 | |
|       export declare function d(): void;
 | |
|     `;
 | |
|     const expected = `
 | |
|     export declare class A {
 | |
|     }
 | |
| 
 | |
|     export declare const b: string;
 | |
| 
 | |
|     export declare var c: number;
 | |
| 
 | |
|     export declare function d(): void;
 | |
|     `;
 | |
|     check(
 | |
|         {'file.d.ts': input}, expected,
 | |
|         {exportTags: {requireAtLeastOne: ['stable', 'foo', 'bar']}});
 | |
|   });
 | |
| });
 | |
| 
 | |
| function getMockHost(files: {[name: string]: string}): ts.CompilerHost {
 | |
|   return {
 | |
|     getSourceFile: (sourceName, languageVersion) => {
 | |
|       if (!files[sourceName]) return undefined;
 | |
|       return ts.createSourceFile(
 | |
|           sourceName, stripExtraIndentation(files[sourceName]), languageVersion, true);
 | |
|     },
 | |
|     writeFile: (name, text, writeByteOrderMark) => {},
 | |
|     fileExists: (filename) => !!files[filename],
 | |
|     readFile: (filename) => stripExtraIndentation(files[filename]),
 | |
|     getDefaultLibFileName: () => 'lib.ts',
 | |
|     useCaseSensitiveFileNames: () => true,
 | |
|     getCanonicalFileName: (filename) => filename,
 | |
|     getCurrentDirectory: () => './',
 | |
|     getNewLine: () => '\n',
 | |
|     getDirectories: () => []
 | |
|   };
 | |
| }
 | |
| 
 | |
| function check(
 | |
|     files: {[name: string]: string}, expected: string, options: SerializationOptions = {}) {
 | |
|   const actual = publicApiInternal(getMockHost(files), 'file.d.ts', {}, options);
 | |
|   chai.assert.equal(actual.trim(), stripExtraIndentation(expected).trim());
 | |
| }
 | |
| 
 | |
| function checkThrows(
 | |
|     files: {[name: string]: string}, error: string, options: SerializationOptions = {}) {
 | |
|   chai.assert.throws(() => {
 | |
|     publicApiInternal(getMockHost(files), 'file.d.ts', {}, options);
 | |
|   }, error);
 | |
| }
 | |
| 
 | |
| function stripExtraIndentation(text: string) {
 | |
|   let lines = text.split('\n');
 | |
|   // Ignore first and last new line
 | |
|   lines = lines.slice(1, lines.length - 1);
 | |
|   const commonIndent = lines.reduce((min, line) => {
 | |
|     const indent = /^( *)/.exec(line)![1].length;
 | |
|     // Ignore empty line
 | |
|     return line.length ? Math.min(min, indent) : min;
 | |
|   }, text.length);
 | |
| 
 | |
|   return lines.map(line => line.substr(commonIndent)).join('\n') + '\n';
 | |
| }
 |