From 676d9c2c4ba3eb71464e74da7bfc9c93c927e4ac Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Fri, 26 Jan 2018 14:12:46 -0800 Subject: [PATCH] fix(language-service): ensure correct paths are passed to TypeScript (#21812) The 2.6 version of TypeScript's `resolveModuleName` started to require paths passed to be separated by '/' instead of being able to handle '\'. `ngc` and `ng` already do this transformation. Fixes: #21811 PR Close #21812 --- .../language-service/src/reflector_host.ts | 2 +- .../test/reflector_host_spec.ts | 37 +++++++++++++++++++ packages/language-service/test/test_utils.ts | 21 +++++++---- 3 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 packages/language-service/test/reflector_host_spec.ts diff --git a/packages/language-service/src/reflector_host.ts b/packages/language-service/src/reflector_host.ts index 35243f815a..d3443aa4df 100644 --- a/packages/language-service/src/reflector_host.ts +++ b/packages/language-service/src/reflector_host.ts @@ -69,7 +69,7 @@ export class ReflectorHost implements StaticSymbolResolverHost { throw new Error('Resolution of relative paths requires a containing file.'); } // Any containing file gives the same result for absolute imports - containingFile = path.join(this.options.basePath !, 'index.ts'); + containingFile = path.join(this.options.basePath !, 'index.ts').replace(/\\/g, '/'); } const resolved = ts.resolveModuleName(moduleName, containingFile !, this.options, this.hostAdapter) diff --git a/packages/language-service/test/reflector_host_spec.ts b/packages/language-service/test/reflector_host_spec.ts new file mode 100644 index 0000000000..0f58ce06df --- /dev/null +++ b/packages/language-service/test/reflector_host_spec.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as path from 'path'; +import * as ts from 'typescript'; + +import {createLanguageService} from '../src/language_service'; +import {ReflectorHost} from '../src/reflector_host'; +import {Completions, LanguageService} from '../src/types'; +import {TypeScriptServiceHost} from '../src/typescript_host'; + +import {toh} from './test_data'; +import {MockTypescriptHost} from './test_utils'; + +describe('reflector_host_spec', () => { + + // Regression #21811 + it('should be able to find angular under windows', () => { + const originalJoin = path.join; + let mockHost = new MockTypescriptHost( + ['/app/main.ts', '/app/parsing-cases.ts'], toh, 'app/node_modules', + {...path, join: (...args: string[]) => originalJoin.apply(path, args)}); + let service = ts.createLanguageService(mockHost); + let ngHost = new TypeScriptServiceHost(mockHost, service); + let ngService = createLanguageService(ngHost); + const reflectorHost = new ReflectorHost(() => undefined as any, mockHost, {basePath: '\\app'}); + + spyOn(path, 'join').and.callFake((...args: string[]) => { return path.win32.join(...args); }); + const result = reflectorHost.moduleNameToFileName('@angular/core'); + expect(result).not.toBeNull('could not find @angular/core using path.win32'); + }); +}); \ No newline at end of file diff --git a/packages/language-service/test/test_utils.ts b/packages/language-service/test/test_utils.ts index 0f2e246b98..e26b967bcb 100644 --- a/packages/language-service/test/test_utils.ts +++ b/packages/language-service/test/test_utils.ts @@ -71,14 +71,16 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { private options: ts.CompilerOptions; private overrideDirectory = new Set(); - constructor(private scriptNames: string[], private data: MockData) { + constructor( + private scriptNames: string[], private data: MockData, + private node_modules: string = 'node_modules', private myPath: typeof path = path) { const moduleFilename = module.filename.replace(/\\/g, '/'); let angularIndex = moduleFilename.indexOf('@angular'); if (angularIndex >= 0) this.angularPath = moduleFilename.substr(0, angularIndex).replace('/all/', '/all/@angular/'); let distIndex = moduleFilename.indexOf('/dist/all'); if (distIndex >= 0) - this.nodeModulesPath = path.join(moduleFilename.substr(0, distIndex), 'node_modules'); + this.nodeModulesPath = myPath.join(moduleFilename.substr(0, distIndex), 'node_modules'); this.options = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, @@ -141,10 +143,13 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { directoryExists(directoryName: string): boolean { if (this.overrideDirectory.has(directoryName)) return true; let effectiveName = this.getEffectiveName(directoryName); - if (effectiveName === directoryName) + if (effectiveName === directoryName) { return directoryExists(directoryName, this.data); - else + } else if (effectiveName == '/' + this.node_modules) { + return true; + } else { return fs.existsSync(effectiveName); + } } fileExists(fileName: string): boolean { return this.getRawFileContent(fileName) != null; } @@ -175,7 +180,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { let basename = path.basename(fileName); if (/^lib.*\.d\.ts$/.test(basename)) { let libPath = ts.getDefaultLibFilePath(this.getCompilationSettings()); - return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8'); + return fs.readFileSync(this.myPath.join(path.dirname(libPath), basename), 'utf8'); } else { if (missingCache.has(fileName)) { cacheUsed.add(fileName); @@ -199,18 +204,18 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { } private getEffectiveName(name: string): string { - const node_modules = 'node_modules'; + const node_modules = this.node_modules; const at_angular = '/@angular'; if (name.startsWith('/' + node_modules)) { if (this.nodeModulesPath && !name.startsWith('/' + node_modules + at_angular)) { - let result = path.join(this.nodeModulesPath, name.substr(node_modules.length + 1)); + let result = this.myPath.join(this.nodeModulesPath, name.substr(node_modules.length + 1)); if (!name.match(rxjsts)) if (fs.existsSync(result)) { return result; } } if (this.angularPath && name.startsWith('/' + node_modules + at_angular)) { - return path.join( + return this.myPath.join( this.angularPath, name.substr(node_modules.length + at_angular.length + 1)); } }