| 
									
										
										
										
											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
 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2019-04-28 00:26:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import {Diagnostic} from '@angular/compiler-cli'; | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics'; | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  | import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing'; | 
					
						
							| 
									
										
										
										
											2019-04-28 00:26:13 +02:00
										 |  |  | import {getTokenAtPosition} from '../../src/ngtsc/util/src/typescript'; | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  | import {loadStandardTestFiles} from '../helpers/src/mock_file_loading'; | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import {NgtscTestEnvironment} from './env'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  | const testFiles = loadStandardTestFiles(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | runInEachFileSystem(() => { | 
					
						
							|  |  |  |   describe('ngtsc module scopes', () => { | 
					
						
							|  |  |  |     let env !: NgtscTestEnvironment; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     beforeEach(() => { | 
					
						
							|  |  |  |       env = NgtscTestEnvironment.setup(testFiles); | 
					
						
							|  |  |  |       env.tsconfig(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |     describe('diagnostics', () => { | 
					
						
							|  |  |  |       describe('imports', () => { | 
					
						
							|  |  |  |         it('should emit imports in a pure function call', () => { | 
					
						
							|  |  |  |           env.write('test.ts', `
 | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           env.driveMain(); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           const jsContents = env.getContents('test.js'); | 
					
						
							|  |  |  |           expect(jsContents).toContain('i0.ɵɵdefineNgModule({ type: TestModule });'); | 
					
						
							|  |  |  |           expect(jsContents) | 
					
						
							|  |  |  |               .toContain( | 
					
						
							|  |  |  |                   '/*@__PURE__*/ i0.ɵɵsetNgModuleScope(TestModule, { imports: [OtherModule] });'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           const dtsContents = env.getContents('test.d.ts'); | 
					
						
							|  |  |  |           expect(dtsContents) | 
					
						
							|  |  |  |               .toContain( | 
					
						
							| 
									
										
										
										
											2019-10-14 07:20:26 -07:00
										 |  |  |                   'static ɵmod: i0.ɵɵNgModuleDefWithMeta<TestModule, never, [typeof OtherModule], never>'); | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |         it('should produce an error when an invalid class is imported', () => { | 
					
						
							|  |  |  |           env.write('test.ts', `
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           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', `
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           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'); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |       describe('exports', () => { | 
					
						
							|  |  |  |         it('should emit exports in a pure function call', () => { | 
					
						
							|  |  |  |           env.write('test.ts', `
 | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           env.driveMain(); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           const jsContents = env.getContents('test.js'); | 
					
						
							|  |  |  |           expect(jsContents).toContain('i0.ɵɵdefineNgModule({ type: TestModule });'); | 
					
						
							|  |  |  |           expect(jsContents) | 
					
						
							|  |  |  |               .toContain( | 
					
						
							|  |  |  |                   '/*@__PURE__*/ i0.ɵɵsetNgModuleScope(TestModule, { exports: [OtherModule] });'); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           const dtsContents = env.getContents('test.d.ts'); | 
					
						
							|  |  |  |           expect(dtsContents) | 
					
						
							|  |  |  |               .toContain( | 
					
						
							| 
									
										
										
										
											2019-10-14 07:20:26 -07:00
										 |  |  |                   'static ɵmod: i0.ɵɵNgModuleDefWithMeta<TestModule, never, never, [typeof OtherModule]>'); | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-03-29 21:31:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |         it('should produce an error when a non-NgModule class is exported', () => { | 
					
						
							|  |  |  |           env.write('test.ts', `
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           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', `
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           // 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)); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |       describe('re-exports', () => { | 
					
						
							|  |  |  |         it('should produce an error when a non-declared/imported class is re-exported', () => { | 
					
						
							|  |  |  |           env.write('test.ts', `
 | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |           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 {} | 
					
						
							|  |  |  |         `);
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |           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'); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |   function diagnosticToNode<T extends ts.Node>( | 
					
						
							| 
									
										
										
										
											2019-04-28 00:26:13 +02:00
										 |  |  |       diagnostic: ts.Diagnostic | Diagnostic, guard: (node: ts.Node) => node is T): T { | 
					
						
							|  |  |  |     const diag = diagnostic as ts.Diagnostic; | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |     if (diag.file === undefined) { | 
					
						
							|  |  |  |       throw new Error(`Expected ts.Diagnostic to have a file source`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-28 00:26:13 +02:00
										 |  |  |     const node = getTokenAtPosition(diag.file, diag.start !); | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  |     expect(guard(node)).toBe(true); | 
					
						
							|  |  |  |     return node as T; | 
					
						
							| 
									
										
										
										
											2019-03-08 11:32:49 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-06-06 20:22:32 +01:00
										 |  |  | }); |