| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @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 {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {NgtscTestEnvironment} from './env'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('ngtsc module scopes', () => { | 
					
						
							|  |  |  |   let env !: NgtscTestEnvironment; | 
					
						
							|  |  |  |   beforeEach(() => { | 
					
						
							|  |  |  |     env = NgtscTestEnvironment.setup(); | 
					
						
							|  |  |  |     env.tsconfig(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('diagnostics', () => { | 
					
						
							|  |  |  |     describe('imports', () => { | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |       it('should emit imports in a pure function call', () => { | 
					
						
							|  |  |  |         env.tsconfig(); | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {NgModule} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |           @NgModule({}) | 
					
						
							|  |  |  |           export class OtherModule {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |           @NgModule({imports: [OtherModule]}) | 
					
						
							|  |  |  |           export class TestModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         env.driveMain(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const jsContents = env.getContents('test.js'); | 
					
						
							| 
									
										
										
										
											2019-05-09 11:47:25 -07:00
										 |  |  |         expect(jsContents).toContain('i0.ΔdefineNgModule({ type: TestModule });'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |         expect(jsContents) | 
					
						
							|  |  |  |             .toContain( | 
					
						
							| 
									
										
										
										
											2019-05-09 11:47:25 -07:00
										 |  |  |                 '/*@__PURE__*/ i0.ΔsetNgModuleScope(TestModule, { imports: [OtherModule] });'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const dtsContents = env.getContents('test.d.ts'); | 
					
						
							|  |  |  |         expect(dtsContents) | 
					
						
							|  |  |  |             .toContain( | 
					
						
							| 
									
										
										
										
											2019-05-09 11:47:25 -07:00
										 |  |  |                 'static ngModuleDef: i0.ΔNgModuleDefWithMeta<TestModule, never, [typeof OtherModule], never>'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |       it('should produce an error when an invalid class is imported', () => { | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {NgModule} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           class NotAModule {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @NgModule({imports: [NotAModule]}) | 
					
						
							|  |  |  |           class IsAModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  |         const [error] = env.driveDiagnostics(); | 
					
						
							|  |  |  |         expect(error).not.toBeUndefined(); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('IsAModule'); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('NgModule.imports'); | 
					
						
							|  |  |  |         expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_IMPORT)); | 
					
						
							|  |  |  |         expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAModule'); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should produce an error when a non-class is imported from a .d.ts dependency', () => { | 
					
						
							|  |  |  |         env.write('dep.d.ts', `export declare let NotAClass: Function;`); | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {NgModule} from '@angular/core'; | 
					
						
							|  |  |  |           import {NotAClass} from './dep'; | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @NgModule({imports: [NotAClass]}) | 
					
						
							|  |  |  |           class IsAModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  |         const [error] = env.driveDiagnostics(); | 
					
						
							|  |  |  |         expect(error).not.toBeUndefined(); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('IsAModule'); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('NgModule.imports'); | 
					
						
							|  |  |  |         expect(error.code).toEqual(ngErrorCode(ErrorCode.VALUE_HAS_WRONG_TYPE)); | 
					
						
							|  |  |  |         expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAClass'); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('exports', () => { | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |       it('should emit exports in a pure function call', () => { | 
					
						
							|  |  |  |         env.tsconfig(); | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {NgModule} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |           @NgModule({}) | 
					
						
							|  |  |  |           export class OtherModule {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |           @NgModule({exports: [OtherModule]}) | 
					
						
							|  |  |  |           export class TestModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         env.driveMain(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const jsContents = env.getContents('test.js'); | 
					
						
							| 
									
										
										
										
											2019-05-09 11:47:25 -07:00
										 |  |  |         expect(jsContents).toContain('i0.ΔdefineNgModule({ type: TestModule });'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |         expect(jsContents) | 
					
						
							|  |  |  |             .toContain( | 
					
						
							| 
									
										
										
										
											2019-05-09 11:47:25 -07:00
										 |  |  |                 '/*@__PURE__*/ i0.ΔsetNgModuleScope(TestModule, { exports: [OtherModule] });'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const dtsContents = env.getContents('test.d.ts'); | 
					
						
							|  |  |  |         expect(dtsContents) | 
					
						
							|  |  |  |             .toContain( | 
					
						
							| 
									
										
										
										
											2019-05-09 11:47:25 -07:00
										 |  |  |                 'static ngModuleDef: i0.ΔNgModuleDefWithMeta<TestModule, never, never, [typeof OtherModule]>'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |       it('should produce an error when a non-NgModule class is exported', () => { | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {NgModule} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           class NotAModule {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @NgModule({exports: [NotAModule]}) | 
					
						
							|  |  |  |           class IsAModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  |         const [error] = env.driveDiagnostics(); | 
					
						
							|  |  |  |         expect(error).not.toBeUndefined(); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('IsAModule'); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('NgModule.exports'); | 
					
						
							|  |  |  |         expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_EXPORT)); | 
					
						
							|  |  |  |         expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('NotAModule'); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should produce a transitive error when an invalid NgModule is exported', () => { | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {NgModule} from '@angular/core'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           export class NotAModule {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @NgModule({ | 
					
						
							|  |  |  |             imports: [NotAModule], | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |           class InvalidModule {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @NgModule({exports: [InvalidModule]}) | 
					
						
							|  |  |  |           class IsAModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Find the diagnostic referencing InvalidModule, which should have come from IsAModule.
 | 
					
						
							|  |  |  |         const error = env.driveDiagnostics().find( | 
					
						
							|  |  |  |             error => diagnosticToNode(error, ts.isIdentifier).text === 'InvalidModule'); | 
					
						
							|  |  |  |         if (error === undefined) { | 
					
						
							|  |  |  |           return fail('Expected to find a diagnostic referencing InvalidModule'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         expect(error.messageText).toContain('IsAModule'); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('NgModule.exports'); | 
					
						
							|  |  |  |         expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_EXPORT)); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('re-exports', () => { | 
					
						
							|  |  |  |       it('should produce an error when a non-declared/imported class is re-exported', () => { | 
					
						
							|  |  |  |         env.write('test.ts', `
 | 
					
						
							|  |  |  |           import {Directive, NgModule} from '@angular/core'; | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @Directive({selector: 'test'}) | 
					
						
							|  |  |  |           class Dir {} | 
					
						
							| 
									
										
										
										
											2019-04-04 11:41:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           @NgModule({exports: [Dir]}) | 
					
						
							|  |  |  |           class IsAModule {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  |         const [error] = env.driveDiagnostics(); | 
					
						
							|  |  |  |         expect(error).not.toBeUndefined(); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('IsAModule'); | 
					
						
							|  |  |  |         expect(error.messageText).toContain('NgModule.exports'); | 
					
						
							|  |  |  |         expect(error.code).toEqual(ngErrorCode(ErrorCode.NGMODULE_INVALID_REEXPORT)); | 
					
						
							|  |  |  |         expect(diagnosticToNode(error, ts.isIdentifier).text).toEqual('Dir'); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function diagnosticToNode<T extends ts.Node>( | 
					
						
							|  |  |  |     diag: ts.Diagnostic, guard: (node: ts.Node) => node is T): T { | 
					
						
							|  |  |  |   if (diag.file === undefined) { | 
					
						
							|  |  |  |     throw new Error(`Expected ts.Diagnostic to have a file source`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const node = (ts as any).getTokenAtPosition(diag.file, diag.start) as ts.Node; | 
					
						
							|  |  |  |   expect(guard(node)).toBe(true); | 
					
						
							|  |  |  |   return node as T; | 
					
						
							|  |  |  | } |