| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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 {isMetadataGlobalReferenceExpression} from '../../src/metadata/schema'; | 
					
						
							|  |  |  | import {Symbols} from '../../src/metadata/symbols'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-07 12:43:43 -07:00
										 |  |  | import {Directory, expectNoDiagnostics, Host} from './typescript.mocks'; | 
					
						
							| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('Symbols', () => { | 
					
						
							|  |  |  |   let symbols: Symbols; | 
					
						
							|  |  |  |   const someValue = 'some-value'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(() => symbols = new Symbols(null as any as ts.SourceFile)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to add a symbol', () => symbols.define('someSymbol', someValue)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(() => symbols.define('someSymbol', someValue)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to `has` a symbol', () => expect(symbols.has('someSymbol')).toBeTruthy()); | 
					
						
							|  |  |  |   it('should be able to `get` a symbol value', | 
					
						
							|  |  |  |      () => expect(symbols.resolve('someSymbol')).toBe(someValue)); | 
					
						
							|  |  |  |   it('should be able to `get` a symbol value', | 
					
						
							|  |  |  |      () => expect(symbols.resolve('someSymbol')).toBe(someValue)); | 
					
						
							|  |  |  |   it('should be able to determine symbol is missing', | 
					
						
							|  |  |  |      () => expect(symbols.has('missingSymbol')).toBeFalsy()); | 
					
						
							|  |  |  |   it('should return undefined from `get` for a missing symbol', | 
					
						
							|  |  |  |      () => expect(symbols.resolve('missingSymbol')).toBeUndefined()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let host: ts.LanguageServiceHost; | 
					
						
							|  |  |  |   let service: ts.LanguageService; | 
					
						
							|  |  |  |   let program: ts.Program; | 
					
						
							|  |  |  |   let expressions: ts.SourceFile; | 
					
						
							|  |  |  |   let imports: ts.SourceFile; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(() => { | 
					
						
							|  |  |  |     host = new Host(FILES, ['consts.ts', 'expressions.ts', 'imports.ts']); | 
					
						
							|  |  |  |     service = ts.createLanguageService(host); | 
					
						
							| 
									
										
										
										
											2020-04-07 12:43:43 -07:00
										 |  |  |     program = service.getProgram()!; | 
					
						
							|  |  |  |     expressions = program.getSourceFile('expressions.ts')!; | 
					
						
							|  |  |  |     imports = program.getSourceFile('imports.ts')!; | 
					
						
							| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should not have syntax errors in the test sources', () => { | 
					
						
							|  |  |  |     expectNoDiagnostics(service.getCompilerOptionsDiagnostics()); | 
					
						
							|  |  |  |     for (const sourceFile of program.getSourceFiles()) { | 
					
						
							|  |  |  |       expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to find the source files', () => { | 
					
						
							|  |  |  |     expect(expressions).toBeDefined(); | 
					
						
							|  |  |  |     expect(imports).toBeDefined(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to create symbols for a source file', () => { | 
					
						
							|  |  |  |     const symbols = new Symbols(expressions); | 
					
						
							|  |  |  |     expect(symbols).toBeDefined(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to find symbols in expression', () => { | 
					
						
							|  |  |  |     const symbols = new Symbols(expressions); | 
					
						
							|  |  |  |     expect(symbols.has('someName')).toBeTruthy(); | 
					
						
							|  |  |  |     expect(symbols.resolve('someName')) | 
					
						
							|  |  |  |         .toEqual({__symbolic: 'reference', module: './consts', name: 'someName'}); | 
					
						
							|  |  |  |     expect(symbols.has('someBool')).toBeTruthy(); | 
					
						
							|  |  |  |     expect(symbols.resolve('someBool')) | 
					
						
							|  |  |  |         .toEqual({__symbolic: 'reference', module: './consts', name: 'someBool'}); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to detect a * import', () => { | 
					
						
							|  |  |  |     const symbols = new Symbols(imports); | 
					
						
							|  |  |  |     expect(symbols.resolve('b')).toEqual({__symbolic: 'reference', module: 'b'}); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to detect importing a default export', () => { | 
					
						
							|  |  |  |     const symbols = new Symbols(imports); | 
					
						
							|  |  |  |     expect(symbols.resolve('d')).toEqual({__symbolic: 'reference', module: 'd', default: true}); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to import a renamed symbol', () => { | 
					
						
							|  |  |  |     const symbols = new Symbols(imports); | 
					
						
							|  |  |  |     expect(symbols.resolve('g')).toEqual({__symbolic: 'reference', name: 'f', module: 'f'}); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should be able to resolve any symbol in core global scope', () => { | 
					
						
							| 
									
										
										
										
											2017-12-22 09:36:47 -08:00
										 |  |  |     const core = (program.getSourceFiles() as ts.SourceFile[]) | 
					
						
							|  |  |  |                      .find(source => source.fileName.endsWith('lib.d.ts')); | 
					
						
							| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  |     expect(core).toBeDefined(); | 
					
						
							|  |  |  |     const visit = (node: ts.Node): boolean => { | 
					
						
							|  |  |  |       switch (node.kind) { | 
					
						
							|  |  |  |         case ts.SyntaxKind.VariableStatement: | 
					
						
							|  |  |  |         case ts.SyntaxKind.VariableDeclarationList: | 
					
						
							|  |  |  |           return !!ts.forEachChild(node, visit); | 
					
						
							|  |  |  |         case ts.SyntaxKind.VariableDeclaration: | 
					
						
							|  |  |  |           const variableDeclaration = <ts.VariableDeclaration>node; | 
					
						
							|  |  |  |           const nameNode = <ts.Identifier>variableDeclaration.name; | 
					
						
							|  |  |  |           const name = nameNode.text; | 
					
						
							|  |  |  |           const result = symbols.resolve(name); | 
					
						
							|  |  |  |           expect(isMetadataGlobalReferenceExpression(result) && result.name).toEqual(name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Ignore everything after Float64Array as it is IE specific.
 | 
					
						
							|  |  |  |           return name === 'Float64Array'; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-04-07 12:43:43 -07:00
										 |  |  |     ts.forEachChild(core!, visit); | 
					
						
							| 
									
										
										
										
											2017-09-13 16:55:42 -07:00
										 |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const FILES: Directory = { | 
					
						
							|  |  |  |   'consts.ts': `
 | 
					
						
							|  |  |  |     export var someName = 'some-name'; | 
					
						
							|  |  |  |     export var someBool = true; | 
					
						
							|  |  |  |     export var one = 1; | 
					
						
							|  |  |  |     export var two = 2; | 
					
						
							|  |  |  |   `,
 | 
					
						
							|  |  |  |   'expressions.ts': `
 | 
					
						
							|  |  |  |     import {someName, someBool, one, two} from './consts'; | 
					
						
							|  |  |  |   `,
 | 
					
						
							|  |  |  |   'imports.ts': `
 | 
					
						
							|  |  |  |     import * as b from 'b'; | 
					
						
							|  |  |  |     import 'c'; | 
					
						
							|  |  |  |     import d from 'd'; | 
					
						
							|  |  |  |     import {f as g} from 'f'; | 
					
						
							|  |  |  |   `
 | 
					
						
							|  |  |  | }; |