| 
									
										
										
										
											2016-11-22 09:10:23 -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-09-11 22:08:20 -07:00
										 |  |  | import {CompileNgModuleMetadata, NgAnalyzedModules} from '@angular/compiler'; | 
					
						
							| 
									
										
										
										
											2019-01-25 19:46:11 +01:00
										 |  |  | import {setup} from '@angular/compiler-cli/test/test_support'; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | import * as fs from 'fs'; | 
					
						
							|  |  |  | import * as path from 'path'; | 
					
						
							|  |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  | import {Span} from '../src/types'; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const angularts = /@angular\/(\w|\/|-)+\.tsx?$/; | 
					
						
							|  |  |  | const rxjsts = /rxjs\/(\w|\/)+\.tsx?$/; | 
					
						
							|  |  |  | const rxjsmetadata = /rxjs\/(\w|\/)+\.metadata\.json?$/; | 
					
						
							|  |  |  | const tsxfile = /\.tsx$/; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* The missing cache does two things. First it improves performance of the | 
					
						
							|  |  |  |    tests as it reduces the number of OS calls made during testing. Also it | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |    improves debugging experience as fewer exceptions are raised to allow you | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |    to use stopping on all exceptions. */ | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  | const missingCache = new Set<string>([ | 
					
						
							|  |  |  |   '/node_modules/@angular/core.d.ts', | 
					
						
							|  |  |  |   '/node_modules/@angular/animations.d.ts', | 
					
						
							|  |  |  |   '/node_modules/@angular/platform-browser/animations.d.ts', | 
					
						
							|  |  |  |   '/node_modules/@angular/common.d.ts', | 
					
						
							|  |  |  |   '/node_modules/@angular/forms.d.ts', | 
					
						
							|  |  |  |   '/node_modules/@angular/core/src/di/provider.metadata.json', | 
					
						
							|  |  |  |   '/node_modules/@angular/core/src/change_detection/pipe_transform.metadata.json', | 
					
						
							|  |  |  |   '/node_modules/@angular/core/src/reflection/types.metadata.json', | 
					
						
							|  |  |  |   '/node_modules/@angular/core/src/reflection/platform_reflection_capabilities.metadata.json', | 
					
						
							|  |  |  |   '/node_modules/@angular/forms/src/directives/form_interface.metadata.json', | 
					
						
							|  |  |  | ]); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  | function isFile(path: string) { | 
					
						
							|  |  |  |   return fs.statSync(path).isFile(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Return a Map with key = directory / file path, value = file content. | 
					
						
							|  |  |  |  * [ | 
					
						
							|  |  |  |  *   /app => [[directory]] | 
					
						
							|  |  |  |  *   /app/main.ts => ... | 
					
						
							|  |  |  |  *   /app/app.component.ts => ... | 
					
						
							|  |  |  |  *   /app/expression-cases.ts => ... | 
					
						
							|  |  |  |  *   /app/ng-for-cases.ts => ... | 
					
						
							|  |  |  |  *   /app/ng-if-cases.ts => ... | 
					
						
							|  |  |  |  *   /app/parsing-cases.ts => ... | 
					
						
							|  |  |  |  *   /app/test.css => ... | 
					
						
							|  |  |  |  *   /app/test.ng => ... | 
					
						
							|  |  |  |  * ] | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function loadTourOfHeroes(): ReadonlyMap<string, string> { | 
					
						
							|  |  |  |   const {TEST_SRCDIR} = process.env; | 
					
						
							|  |  |  |   const root = | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       path.join(TEST_SRCDIR!, 'angular', 'packages', 'language-service', 'test', 'project'); | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |   const dirs = [root]; | 
					
						
							|  |  |  |   const files = new Map<string, string>(); | 
					
						
							|  |  |  |   while (dirs.length) { | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |     const dirPath = dirs.pop()!; | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |     for (const filePath of fs.readdirSync(dirPath)) { | 
					
						
							|  |  |  |       const absPath = path.join(dirPath, filePath); | 
					
						
							|  |  |  |       if (isFile(absPath)) { | 
					
						
							|  |  |  |         const key = path.join('/', path.relative(root, absPath)); | 
					
						
							|  |  |  |         const value = fs.readFileSync(absPath, 'utf8'); | 
					
						
							|  |  |  |         files.set(key, value); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         const key = path.join('/', filePath); | 
					
						
							|  |  |  |         files.set(key, '[[directory]]'); | 
					
						
							|  |  |  |         dirs.push(absPath); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return files; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const TOH = loadTourOfHeroes(); | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  | const COMPILER_OPTIONS: Readonly<ts.CompilerOptions> = { | 
					
						
							|  |  |  |   target: ts.ScriptTarget.ES5, | 
					
						
							|  |  |  |   module: ts.ModuleKind.CommonJS, | 
					
						
							|  |  |  |   moduleResolution: ts.ModuleResolutionKind.NodeJs, | 
					
						
							|  |  |  |   emitDecoratorMetadata: true, | 
					
						
							|  |  |  |   experimentalDecorators: true, | 
					
						
							|  |  |  |   removeComments: false, | 
					
						
							|  |  |  |   noImplicitAny: false, | 
					
						
							|  |  |  |   lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'], | 
					
						
							|  |  |  |   strict: true, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export class MockTypescriptHost implements ts.LanguageServiceHost { | 
					
						
							| 
									
										
										
										
											2019-10-16 11:01:31 -07:00
										 |  |  |   private readonly angularPath: string; | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |   private readonly nodeModulesPath: string; | 
					
						
							|  |  |  |   private readonly scriptVersion = new Map<string, number>(); | 
					
						
							|  |  |  |   private readonly overrides = new Map<string, string>(); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   private projectVersion = 0; | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |   private options: ts.CompilerOptions; | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |   private readonly overrideDirectory = new Set<string>(); | 
					
						
							|  |  |  |   private readonly existsCache = new Map<string, boolean>(); | 
					
						
							|  |  |  |   private readonly fileCache = new Map<string, string|undefined>(); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |   constructor( | 
					
						
							| 
									
										
										
										
											2019-09-17 14:33:41 -07:00
										 |  |  |       private readonly scriptNames: string[], | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |       private readonly node_modules: string = 'node_modules', | 
					
						
							|  |  |  |       private readonly myPath: typeof path = path) { | 
					
						
							| 
									
										
										
										
											2019-01-25 19:46:11 +01:00
										 |  |  |     const support = setup(); | 
					
						
							| 
									
										
										
										
											2019-05-22 08:26:05 +02:00
										 |  |  |     this.nodeModulesPath = path.posix.join(support.basePath, 'node_modules'); | 
					
						
							|  |  |  |     this.angularPath = path.posix.join(this.nodeModulesPath, '@angular'); | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     this.options = COMPILER_OPTIONS; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   override(fileName: string, content: string) { | 
					
						
							|  |  |  |     this.scriptVersion.set(fileName, (this.scriptVersion.get(fileName) || 0) + 1); | 
					
						
							| 
									
										
										
										
											2019-11-13 12:21:04 -08:00
										 |  |  |     this.projectVersion++; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     if (content) { | 
					
						
							|  |  |  |       this.overrides.set(fileName, content); | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  |       this.overrideDirectory.add(path.dirname(fileName)); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       this.overrides.delete(fileName); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-15 15:17:00 -07:00
										 |  |  |     return content; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-01 15:49:10 -07:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Override the inline template in `fileName`. | 
					
						
							|  |  |  |    * @param fileName path to component that has inline template | 
					
						
							|  |  |  |    * @param content new template | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return the new content of the file | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   overrideInlineTemplate(fileName: string, content: string): string { | 
					
						
							|  |  |  |     const originalContent = this.getRawFileContent(fileName)!; | 
					
						
							|  |  |  |     const newContent = originalContent.replace(/template: `([\s\S]+)`/, `template: \`${content}\``); | 
					
						
							|  |  |  |     return this.override(fileName, newContent); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-06 17:11:09 -08:00
										 |  |  |   addScript(fileName: string, content: string) { | 
					
						
							| 
									
										
										
										
											2019-10-16 11:01:31 -07:00
										 |  |  |     if (this.scriptVersion.has(fileName)) { | 
					
						
							|  |  |  |       throw new Error(`${fileName} is already in the root files.`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.scriptVersion.set(fileName, 0); | 
					
						
							| 
									
										
										
										
											2016-12-06 17:11:09 -08:00
										 |  |  |     this.projectVersion++; | 
					
						
							|  |  |  |     this.overrides.set(fileName, content); | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  |     this.overrideDirectory.add(path.dirname(fileName)); | 
					
						
							| 
									
										
										
										
											2016-12-06 17:11:09 -08:00
										 |  |  |     this.scriptNames.push(fileName); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |   overrideOptions(options: Partial<ts.CompilerOptions>) { | 
					
						
							|  |  |  |     this.options = {...this.options, ...options}; | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |     this.projectVersion++; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   getCompilationSettings(): ts.CompilerOptions { | 
					
						
							|  |  |  |     return {...this.options}; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   getProjectVersion(): string { | 
					
						
							|  |  |  |     return this.projectVersion.toString(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   getScriptFileNames(): string[] { | 
					
						
							|  |  |  |     return this.scriptNames; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   getScriptVersion(fileName: string): string { | 
					
						
							|  |  |  |     return (this.scriptVersion.get(fileName) || 0).toString(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   getScriptSnapshot(fileName: string): ts.IScriptSnapshot|undefined { | 
					
						
							| 
									
										
										
										
											2019-09-19 15:32:40 -07:00
										 |  |  |     const content = this.readFile(fileName); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     if (content) return ts.ScriptSnapshot.fromString(content); | 
					
						
							|  |  |  |     return undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   getCurrentDirectory(): string { | 
					
						
							|  |  |  |     return '/'; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   getDefaultLibFileName(options: ts.CompilerOptions): string { | 
					
						
							|  |  |  |     return 'lib.d.ts'; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   directoryExists(directoryName: string): boolean { | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  |     if (this.overrideDirectory.has(directoryName)) return true; | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |     const effectiveName = this.getEffectiveName(directoryName); | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |     if (effectiveName === directoryName) { | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |       return TOH.has(directoryName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (effectiveName === '/' + this.node_modules) { | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |     return this.pathExists(effectiveName); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |   fileExists(fileName: string): boolean { | 
					
						
							|  |  |  |     return this.getRawFileContent(fileName) != null; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-09-08 18:40:32 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-19 15:32:40 -07:00
										 |  |  |   readFile(fileName: string): string|undefined { | 
					
						
							|  |  |  |     const content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (content) { | 
					
						
							|  |  |  |       return removeReferenceMarkers(removeLocationMarkers(content)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-02-25 12:58:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-17 14:33:41 -07:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Reset the project to its original state, effectively removing all overrides. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   reset() { | 
					
						
							| 
									
										
										
										
											2019-10-16 11:01:31 -07:00
										 |  |  |     // project version and script version must be monotonically increasing,
 | 
					
						
							|  |  |  |     // they must not be reset to zero.
 | 
					
						
							|  |  |  |     this.projectVersion++; | 
					
						
							|  |  |  |     for (const fileName of this.overrides.keys()) { | 
					
						
							|  |  |  |       const version = this.scriptVersion.get(fileName); | 
					
						
							|  |  |  |       if (version === undefined) { | 
					
						
							|  |  |  |         throw new Error(`No prior version found for ${fileName}`); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this.scriptVersion.set(fileName, version + 1); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-14 15:24:28 -07:00
										 |  |  |     // Remove overrides from scriptNames
 | 
					
						
							|  |  |  |     let length = 0; | 
					
						
							|  |  |  |     for (let i = 0; i < this.scriptNames.length; ++i) { | 
					
						
							|  |  |  |       const fileName = this.scriptNames[i]; | 
					
						
							|  |  |  |       if (!this.overrides.has(fileName)) { | 
					
						
							|  |  |  |         this.scriptNames[length++] = fileName; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.scriptNames.splice(length); | 
					
						
							| 
									
										
										
										
											2019-09-17 14:33:41 -07:00
										 |  |  |     this.overrides.clear(); | 
					
						
							|  |  |  |     this.overrideDirectory.clear(); | 
					
						
							| 
									
										
										
										
											2019-09-19 17:04:02 -07:00
										 |  |  |     this.options = COMPILER_OPTIONS; | 
					
						
							| 
									
										
										
										
											2019-09-17 14:33:41 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   private getRawFileContent(fileName: string): string|undefined { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     if (this.overrides.has(fileName)) { | 
					
						
							|  |  |  |       return this.overrides.get(fileName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     let basename = path.basename(fileName); | 
					
						
							|  |  |  |     if (/^lib.*\.d\.ts$/.test(basename)) { | 
					
						
							|  |  |  |       let libPath = ts.getDefaultLibFilePath(this.getCompilationSettings()); | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |       return fs.readFileSync(this.myPath.join(path.dirname(libPath), basename), 'utf8'); | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (missingCache.has(fileName)) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-05-22 08:26:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-12 15:20:54 -07:00
										 |  |  |     const effectiveName = this.getEffectiveName(fileName); | 
					
						
							|  |  |  |     if (effectiveName === fileName) { | 
					
						
							|  |  |  |       return TOH.get(fileName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!fileName.match(angularts) && !fileName.match(rxjsts) && !fileName.match(rxjsmetadata) && | 
					
						
							|  |  |  |         !fileName.match(tsxfile)) { | 
					
						
							|  |  |  |       if (this.fileCache.has(effectiveName)) { | 
					
						
							|  |  |  |         return this.fileCache.get(effectiveName); | 
					
						
							|  |  |  |       } else if (this.pathExists(effectiveName)) { | 
					
						
							|  |  |  |         const content = fs.readFileSync(effectiveName, 'utf8'); | 
					
						
							|  |  |  |         this.fileCache.set(effectiveName, content); | 
					
						
							|  |  |  |         return content; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         missingCache.add(fileName); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-22 08:26:05 +02:00
										 |  |  |   private pathExists(path: string): boolean { | 
					
						
							|  |  |  |     if (this.existsCache.has(path)) { | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       return this.existsCache.get(path)!; | 
					
						
							| 
									
										
										
										
											2019-05-22 08:26:05 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const exists = fs.existsSync(path); | 
					
						
							|  |  |  |     this.existsCache.set(path, exists); | 
					
						
							|  |  |  |     return exists; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   private getEffectiveName(name: string): string { | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |     const node_modules = this.node_modules; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     const at_angular = '/@angular'; | 
					
						
							|  |  |  |     if (name.startsWith('/' + node_modules)) { | 
					
						
							|  |  |  |       if (this.nodeModulesPath && !name.startsWith('/' + node_modules + at_angular)) { | 
					
						
							| 
									
										
										
										
											2019-05-22 08:26:05 +02:00
										 |  |  |         const result = | 
					
						
							|  |  |  |             this.myPath.posix.join(this.nodeModulesPath, name.substr(node_modules.length + 1)); | 
					
						
							|  |  |  |         if (!name.match(rxjsts) && this.pathExists(result)) { | 
					
						
							|  |  |  |           return result; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-10-16 11:01:31 -07:00
										 |  |  |       if (name.startsWith('/' + node_modules + at_angular)) { | 
					
						
							| 
									
										
										
										
											2019-05-22 08:26:05 +02:00
										 |  |  |         return this.myPath.posix.join( | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |             this.angularPath, name.substr(node_modules.length + at_angular.length + 1)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return name; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Append a snippet of code to `app.component.ts` and return the file name. | 
					
						
							|  |  |  |    * There must not be any name collision with existing code. | 
					
						
							|  |  |  |    * @param code Snippet of code | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   addCode(code: string) { | 
					
						
							|  |  |  |     const fileName = '/app/app.component.ts'; | 
					
						
							| 
									
										
										
										
											2019-09-19 15:32:40 -07:00
										 |  |  |     const originalContent = this.readFile(fileName); | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |     const newContent = originalContent + code; | 
					
						
							|  |  |  |     this.override(fileName, newContent); | 
					
						
							|  |  |  |     return fileName; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |    * Returns the definition marker `ᐱselectorᐱ` for the specified 'selector'. | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |    * Asserts that marker exists. | 
					
						
							|  |  |  |    * @param fileName name of the file | 
					
						
							|  |  |  |    * @param selector name of the marker | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   getDefinitionMarkerFor(fileName: string, selector: string): ts.TextSpan { | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |     const content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (!content) { | 
					
						
							|  |  |  |       throw new Error(`File does not exist: ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const markers = getReferenceMarkers(content); | 
					
						
							|  |  |  |     const definitions = markers.definitions[selector]; | 
					
						
							|  |  |  |     if (!definitions || !definitions.length) { | 
					
						
							|  |  |  |       throw new Error(`Failed to find marker '${selector}' in ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (definitions.length > 1) { | 
					
						
							|  |  |  |       throw new Error(`Multiple positions found for '${selector}' in ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const {start, end} = definitions[0]; | 
					
						
							|  |  |  |     if (start > end) { | 
					
						
							|  |  |  |       throw new Error(`Marker '${selector}' in ${fileName} is invalid: ${start} > ${end}`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |     return { | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |       start, | 
					
						
							|  |  |  |       length: end - start, | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |    * Returns the reference marker `«selector»` for the specified 'selector'. | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |    * Asserts that marker exists. | 
					
						
							|  |  |  |    * @param fileName name of the file | 
					
						
							|  |  |  |    * @param selector name of the marker | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   getReferenceMarkerFor(fileName: string, selector: string): ts.TextSpan { | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |     const content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (!content) { | 
					
						
							|  |  |  |       throw new Error(`File does not exist: ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const markers = getReferenceMarkers(content); | 
					
						
							|  |  |  |     const references = markers.references[selector]; | 
					
						
							|  |  |  |     if (!references || !references.length) { | 
					
						
							|  |  |  |       throw new Error(`Failed to find marker '${selector}' in ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (references.length > 1) { | 
					
						
							|  |  |  |       throw new Error(`Multiple positions found for '${selector}' in ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const {start, end} = references[0]; | 
					
						
							|  |  |  |     if (start > end) { | 
					
						
							|  |  |  |       throw new Error(`Marker '${selector}' in ${fileName} is invalid: ${start} > ${end}`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |     return { | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |       start, | 
					
						
							|  |  |  |       length: end - start, | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |    * Returns the location marker `~{selector}` or the marker pair | 
					
						
							|  |  |  |    * `~{start-selector}` and `~{end-selector}` for the specified 'selector'. | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |    * Asserts that marker exists. | 
					
						
							|  |  |  |    * @param fileName name of the file | 
					
						
							|  |  |  |    * @param selector name of the marker | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   getLocationMarkerFor(fileName: string, selector: string): ts.TextSpan { | 
					
						
							| 
									
										
										
										
											2019-10-11 13:56:33 -07:00
										 |  |  |     const content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (!content) { | 
					
						
							|  |  |  |       throw new Error(`File does not exist: ${fileName}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const markers = getLocationMarkers(content); | 
					
						
							|  |  |  |     // Look for just the selector itself
 | 
					
						
							|  |  |  |     const position = markers[selector]; | 
					
						
							|  |  |  |     if (position !== undefined) { | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         start: position, | 
					
						
							|  |  |  |         length: 0, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // Look for start and end markers for the selector
 | 
					
						
							|  |  |  |     const start = markers[`start-${selector}`]; | 
					
						
							|  |  |  |     const end = markers[`end-${selector}`]; | 
					
						
							|  |  |  |     if (start !== undefined && end !== undefined) { | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         start, | 
					
						
							|  |  |  |         length: end - start, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     throw new Error(`Failed to find marker '${selector}' in ${fileName}`); | 
					
						
							| 
									
										
										
										
											2019-08-28 12:55:12 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const locationMarker = /\~\{(\w+(-\w+)*)\}/g; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function removeLocationMarkers(value: string): string { | 
					
						
							|  |  |  |   return value.replace(locationMarker, ''); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getLocationMarkers(value: string): {[name: string]: number} { | 
					
						
							|  |  |  |   value = removeReferenceMarkers(value); | 
					
						
							|  |  |  |   let result: {[name: string]: number} = {}; | 
					
						
							|  |  |  |   let adjustment = 0; | 
					
						
							|  |  |  |   value.replace(locationMarker, (match: string, name: string, _: any, index: number): string => { | 
					
						
							|  |  |  |     result[name] = index - adjustment; | 
					
						
							|  |  |  |     adjustment += match.length; | 
					
						
							|  |  |  |     return ''; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-08 10:30:54 -07:00
										 |  |  | const referenceMarker = /«(((\w|\-)+)|([^ᐱ]*ᐱ(\w+)ᐱ.[^»]*))»/g; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type ReferenceMarkers = { | 
					
						
							|  |  |  |   [name: string]: Span[] | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | export interface ReferenceResult { | 
					
						
							|  |  |  |   text: string; | 
					
						
							|  |  |  |   definitions: ReferenceMarkers; | 
					
						
							|  |  |  |   references: ReferenceMarkers; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function getReferenceMarkers(value: string): ReferenceResult { | 
					
						
							|  |  |  |   const references: ReferenceMarkers = {}; | 
					
						
							|  |  |  |   const definitions: ReferenceMarkers = {}; | 
					
						
							|  |  |  |   value = removeLocationMarkers(value); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let adjustment = 0; | 
					
						
							|  |  |  |   const text = value.replace( | 
					
						
							| 
									
										
										
										
											2020-04-03 20:57:39 -07:00
										 |  |  |       referenceMarker, | 
					
						
							|  |  |  |       (match: string, text: string, reference: string, _: string, definition: string, | 
					
						
							|  |  |  |        definitionName: string, index: number): string => { | 
					
						
							| 
									
										
										
										
											2019-05-08 10:30:54 -07:00
										 |  |  |         const result = reference ? text : text.replace(/ᐱ/g, ''); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |         const span: Span = {start: index - adjustment, end: index - adjustment + result.length}; | 
					
						
							|  |  |  |         const markers = reference ? references : definitions; | 
					
						
							|  |  |  |         const name = reference || definitionName; | 
					
						
							|  |  |  |         (markers[name] = (markers[name] || [])).push(span); | 
					
						
							|  |  |  |         adjustment += match.length - result.length; | 
					
						
							|  |  |  |         return result; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return {text, definitions, references}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function removeReferenceMarkers(value: string): string { | 
					
						
							| 
									
										
										
										
											2019-05-08 10:30:54 -07:00
										 |  |  |   return value.replace(referenceMarker, (match, text) => text.replace(/ᐱ/g, '')); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-09-11 22:08:20 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Find the StaticSymbol that has the specified `directiveName` and return its | 
					
						
							|  |  |  |  * Angular metadata, if any. | 
					
						
							|  |  |  |  * @param ngModules analyzed modules | 
					
						
							|  |  |  |  * @param directiveName | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function findDirectiveMetadataByName( | 
					
						
							|  |  |  |     ngModules: NgAnalyzedModules, directiveName: string): CompileNgModuleMetadata|undefined { | 
					
						
							|  |  |  |   for (const [key, value] of ngModules.ngModuleByPipeOrDirective) { | 
					
						
							|  |  |  |     if (key.name === directiveName) { | 
					
						
							|  |  |  |       return value; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |