| 
									
										
										
										
											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
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-21 16:59:04 -07:00
										 |  |  | import {isInBazel, 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'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-14 17:49:47 -08:00
										 |  |  | import {Diagnostic, DiagnosticMessageChain, Diagnostics, Span} from '../src/types'; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type MockData = string | MockDirectory; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type MockDirectory = { | 
					
						
							|  |  |  |   [name: string]: MockData | undefined; | 
					
						
							| 
									
										
										
										
											2016-12-27 14:55:58 -08:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |    improves debugging experience as fewer exceptions are raised allow you | 
					
						
							|  |  |  |    to use stopping on all exceptions. */ | 
					
						
							|  |  |  | const missingCache = new Map<string, boolean>(); | 
					
						
							|  |  |  | const cacheUsed = new Set<string>(); | 
					
						
							|  |  |  | const reportedMissing = new Set<string>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The cache is valid if all the returned entries are empty. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function validateCache(): {exists: string[], unused: string[], reported: string[]} { | 
					
						
							|  |  |  |   const exists: string[] = []; | 
					
						
							|  |  |  |   const unused: string[] = []; | 
					
						
							|  |  |  |   for (const fileName of iterableToArray(missingCache.keys())) { | 
					
						
							|  |  |  |     if (fs.existsSync(fileName)) { | 
					
						
							|  |  |  |       exists.push(fileName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!cacheUsed.has(fileName)) { | 
					
						
							|  |  |  |       unused.push(fileName); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return {exists, unused, reported: iterableToArray(reportedMissing.keys())}; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | missingCache.set('/node_modules/@angular/core.d.ts', true); | 
					
						
							| 
									
										
										
										
											2017-02-22 15:14:49 -08:00
										 |  |  | missingCache.set('/node_modules/@angular/animations.d.ts', true); | 
					
						
							|  |  |  | missingCache.set('/node_modules/@angular/platform-browser/animations.d.ts', true); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | missingCache.set('/node_modules/@angular/common.d.ts', true); | 
					
						
							|  |  |  | missingCache.set('/node_modules/@angular/forms.d.ts', true); | 
					
						
							|  |  |  | missingCache.set('/node_modules/@angular/core/src/di/provider.metadata.json', true); | 
					
						
							|  |  |  | missingCache.set( | 
					
						
							|  |  |  |     '/node_modules/@angular/core/src/change_detection/pipe_transform.metadata.json', true); | 
					
						
							|  |  |  | missingCache.set('/node_modules/@angular/core/src/reflection/types.metadata.json', true); | 
					
						
							|  |  |  | missingCache.set( | 
					
						
							|  |  |  |     '/node_modules/@angular/core/src/reflection/platform_reflection_capabilities.metadata.json', | 
					
						
							|  |  |  |     true); | 
					
						
							|  |  |  | missingCache.set('/node_modules/@angular/forms/src/directives/form_interface.metadata.json', true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class MockTypescriptHost implements ts.LanguageServiceHost { | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   private angularPath: string|undefined; | 
					
						
							| 
									
										
										
										
											2018-06-18 16:38:33 -07:00
										 |  |  |   // TODO(issue/24571): remove '!'.
 | 
					
						
							|  |  |  |   private nodeModulesPath !: string; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   private scriptVersion = new Map<string, number>(); | 
					
						
							|  |  |  |   private overrides = new Map<string, string>(); | 
					
						
							|  |  |  |   private projectVersion = 0; | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |   private options: ts.CompilerOptions; | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  |   private overrideDirectory = new Set<string>(); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |   constructor( | 
					
						
							|  |  |  |       private scriptNames: string[], private data: MockData, | 
					
						
							|  |  |  |       private node_modules: string = 'node_modules', private myPath: typeof path = path) { | 
					
						
							| 
									
										
										
										
											2017-01-06 20:43:17 -08:00
										 |  |  |     const moduleFilename = module.filename.replace(/\\/g, '/'); | 
					
						
							| 
									
										
										
										
											2018-03-21 16:59:04 -07:00
										 |  |  |     if (isInBazel()) { | 
					
						
							|  |  |  |       const support = setup(); | 
					
						
							|  |  |  |       this.nodeModulesPath = path.join(support.basePath, 'node_modules'); | 
					
						
							|  |  |  |       this.angularPath = path.join(this.nodeModulesPath, '@angular'); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       const angularIndex = moduleFilename.indexOf('@angular'); | 
					
						
							|  |  |  |       if (angularIndex >= 0) | 
					
						
							|  |  |  |         this.angularPath = | 
					
						
							|  |  |  |             moduleFilename.substr(0, angularIndex).replace('/all/', '/all/@angular/'); | 
					
						
							|  |  |  |       const distIndex = moduleFilename.indexOf('/dist/all'); | 
					
						
							|  |  |  |       if (distIndex >= 0) | 
					
						
							|  |  |  |         this.nodeModulesPath = myPath.join(moduleFilename.substr(0, distIndex), 'node_modules'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |     this.options = { | 
					
						
							|  |  |  |       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'], | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   override(fileName: string, content: string) { | 
					
						
							|  |  |  |     this.scriptVersion.set(fileName, (this.scriptVersion.get(fileName) || 0) + 1); | 
					
						
							|  |  |  |     if (fileName.endsWith('.ts')) { | 
					
						
							|  |  |  |       this.projectVersion++; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-06 17:11:09 -08:00
										 |  |  |   addScript(fileName: string, content: string) { | 
					
						
							|  |  |  |     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); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 12:42:48 -08:00
										 |  |  |   forgetAngular() { this.angularPath = undefined; } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |   overrideOptions(cb: (options: ts.CompilerOptions) => ts.CompilerOptions) { | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  |     this.options = cb((Object as any).assign({}, this.options)); | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |     this.projectVersion++; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 15:10:34 -07:00
										 |  |  |   getCompilationSettings(): ts.CompilerOptions { return this.options; } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   getProjectVersion(): string { return this.projectVersion.toString(); } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getScriptFileNames(): string[] { return this.scriptNames; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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 { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     const content = this.getFileContent(fileName); | 
					
						
							|  |  |  |     if (content) return ts.ScriptSnapshot.fromString(content); | 
					
						
							|  |  |  |     return undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getCurrentDirectory(): string { return '/'; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   directoryExists(directoryName: string): boolean { | 
					
						
							| 
									
										
										
										
											2017-04-13 16:19:59 -07:00
										 |  |  |     if (this.overrideDirectory.has(directoryName)) return true; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     let effectiveName = this.getEffectiveName(directoryName); | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |     if (effectiveName === directoryName) { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       return directoryExists(directoryName, this.data); | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |     } else if (effectiveName == '/' + this.node_modules) { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       return fs.existsSync(effectiveName); | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-08 18:40:32 -07:00
										 |  |  |   fileExists(fileName: string): boolean { return this.getRawFileContent(fileName) != null; } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |   getMarkerLocations(fileName: string): {[name: string]: number}|undefined { | 
					
						
							|  |  |  |     let content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (content) { | 
					
						
							|  |  |  |       return getLocationMarkers(content); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   getReferenceMarkers(fileName: string): ReferenceResult|undefined { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     let content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (content) { | 
					
						
							|  |  |  |       return getReferenceMarkers(content); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   getFileContent(fileName: string): string|undefined { | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     const content = this.getRawFileContent(fileName); | 
					
						
							|  |  |  |     if (content) return removeReferenceMarkers(removeLocationMarkers(content)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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'); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       if (missingCache.has(fileName)) { | 
					
						
							|  |  |  |         cacheUsed.add(fileName); | 
					
						
							|  |  |  |         return undefined; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       let effectiveName = this.getEffectiveName(fileName); | 
					
						
							|  |  |  |       if (effectiveName === fileName) | 
					
						
							|  |  |  |         return open(fileName, this.data); | 
					
						
							|  |  |  |       else if ( | 
					
						
							|  |  |  |           !fileName.match(angularts) && !fileName.match(rxjsts) && !fileName.match(rxjsmetadata) && | 
					
						
							|  |  |  |           !fileName.match(tsxfile)) { | 
					
						
							|  |  |  |         if (fs.existsSync(effectiveName)) { | 
					
						
							|  |  |  |           return fs.readFileSync(effectiveName, 'utf8'); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           missingCache.set(fileName, true); | 
					
						
							|  |  |  |           reportedMissing.add(fileName); | 
					
						
							|  |  |  |           cacheUsed.add(fileName); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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)) { | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |         let result = this.myPath.join(this.nodeModulesPath, name.substr(node_modules.length + 1)); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |         if (!name.match(rxjsts)) | 
					
						
							|  |  |  |           if (fs.existsSync(result)) { | 
					
						
							|  |  |  |             return result; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (this.angularPath && name.startsWith('/' + node_modules + at_angular)) { | 
					
						
							| 
									
										
										
										
											2018-01-26 14:12:46 -08:00
										 |  |  |         return this.myPath.join( | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |             this.angularPath, name.substr(node_modules.length + at_angular.length + 1)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return name; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function iterableToArray<T>(iterator: IterableIterator<T>) { | 
					
						
							|  |  |  |   const result: T[] = []; | 
					
						
							|  |  |  |   while (true) { | 
					
						
							|  |  |  |     const next = iterator.next(); | 
					
						
							|  |  |  |     if (next.done) break; | 
					
						
							|  |  |  |     result.push(next.value); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function find(fileName: string, data: MockData): MockData|undefined { | 
					
						
							|  |  |  |   let names = fileName.split('/'); | 
					
						
							|  |  |  |   if (names.length && !names[0].length) names.shift(); | 
					
						
							|  |  |  |   let current = data; | 
					
						
							|  |  |  |   for (let name of names) { | 
					
						
							|  |  |  |     if (typeof current === 'string') | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     else | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |       current = (<MockDirectory>current)[name] !; | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     if (!current) return undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return current; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function open(fileName: string, data: MockData): string|undefined { | 
					
						
							|  |  |  |   let result = find(fileName, data); | 
					
						
							|  |  |  |   if (typeof result === 'string') { | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return undefined; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function directoryExists(dirname: string, data: MockData): boolean { | 
					
						
							|  |  |  |   let result = find(dirname, data); | 
					
						
							| 
									
										
										
										
											2017-03-24 09:57:32 -07:00
										 |  |  |   return !!result && typeof result !== 'string'; | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const referenceMarker = /«(((\w|\-)+)|([^∆]*∆(\w+)∆.[^»]*))»/g; | 
					
						
							|  |  |  | const definitionMarkerGroup = 1; | 
					
						
							|  |  |  | const nameMarkerGroup = 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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( | 
					
						
							|  |  |  |       referenceMarker, (match: string, text: string, reference: string, _: string, | 
					
						
							|  |  |  |                         definition: string, definitionName: string, index: number): string => { | 
					
						
							|  |  |  |         const result = reference ? text : text.replace(/∆/g, ''); | 
					
						
							|  |  |  |         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 { | 
					
						
							|  |  |  |   return value.replace(referenceMarker, (match, text) => text.replace(/∆/g, '')); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function noDiagnostics(diagnostics: Diagnostics) { | 
					
						
							|  |  |  |   if (diagnostics && diagnostics.length) { | 
					
						
							|  |  |  |     throw new Error(`Unexpected diagnostics: \n  ${diagnostics.map(d => d.message).join('\n  ')}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-14 17:49:47 -08:00
										 |  |  | export function diagnosticMessageContains( | 
					
						
							|  |  |  |     message: string | DiagnosticMessageChain, messageFragment: string): boolean { | 
					
						
							|  |  |  |   if (typeof message == 'string') { | 
					
						
							|  |  |  |     return message.indexOf(messageFragment) >= 0; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (message.message.indexOf(messageFragment) >= 0) { | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (message.next) { | 
					
						
							|  |  |  |     return diagnosticMessageContains(message.next, messageFragment); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function findDiagnostic(diagnostics: Diagnostic[], messageFragment: string): Diagnostic| | 
					
						
							|  |  |  |     undefined { | 
					
						
							|  |  |  |   return diagnostics.find(d => diagnosticMessageContains(d.message, messageFragment)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  | export function includeDiagnostic( | 
					
						
							|  |  |  |     diagnostics: Diagnostics, message: string, text?: string, len?: string): void; | 
					
						
							|  |  |  | export function includeDiagnostic( | 
					
						
							|  |  |  |     diagnostics: Diagnostics, message: string, at?: number, len?: number): void; | 
					
						
							|  |  |  | export function includeDiagnostic(diagnostics: Diagnostics, message: string, p1?: any, p2?: any) { | 
					
						
							|  |  |  |   expect(diagnostics).toBeDefined(); | 
					
						
							|  |  |  |   if (diagnostics) { | 
					
						
							| 
									
										
										
										
											2017-11-14 17:49:47 -08:00
										 |  |  |     const diagnostic = findDiagnostic(diagnostics, message); | 
					
						
							|  |  |  |     expect(diagnostic).toBeDefined(`no diagnostic contains '${message}`); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |     if (diagnostic && p1 != null) { | 
					
						
							|  |  |  |       const at = typeof p1 === 'number' ? p1 : p2.indexOf(p1); | 
					
						
							|  |  |  |       const len = typeof p2 === 'number' ? p2 : p1.length; | 
					
						
							| 
									
										
										
										
											2017-11-14 17:49:47 -08:00
										 |  |  |       expect(diagnostic.span.start) | 
					
						
							|  |  |  |           .toEqual( | 
					
						
							|  |  |  |               at, | 
					
						
							|  |  |  |               `expected message '${message}' was reported at ${diagnostic.span.start} but should be ${at}`); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       if (len != null) { | 
					
						
							| 
									
										
										
										
											2017-11-14 17:49:47 -08:00
										 |  |  |         expect(diagnostic.span.end - diagnostic.span.start) | 
					
						
							|  |  |  |             .toEqual(len, `expected '${message}'s span length to be ${len}`); | 
					
						
							| 
									
										
										
										
											2016-11-22 09:10:23 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-01-26 11:16:51 -08:00
										 |  |  | } |