From 7f57e21bc71fa02b9533ebe0fafeb608a5ab7dd9 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Tue, 3 Oct 2017 08:56:36 +0200 Subject: [PATCH 1/9] fix: use unixPath for matching windows paths to configuration paths --- src/project-manager.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/project-manager.ts b/src/project-manager.ts index 8de21e064..89ee626c5 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -405,7 +405,7 @@ export class ProjectConfiguration { */ public isExpectedDeclarationFile(fileName: string): boolean { return isDeclarationFile(fileName) && - (this.expectedFilePaths.has(toUnixPath(fileName)) || + (this.expectedFilePaths.has(fileName) || this.typeRoots.some(root => fileName.startsWith(root))) } @@ -427,8 +427,9 @@ export class ProjectConfiguration { // Add all global declaration files from the workspace and all declarations from the project for (const uri of this.fs.uris()) { const fileName = uri2path(uri) - if (isGlobalTSFile(fileName) || - this.isExpectedDeclarationFile(fileName)) { + const unixPath = toUnixPath(fileName) + if (isGlobalTSFile(unixPath) || + this.isExpectedDeclarationFile(unixPath)) { const sourceFile = program.getSourceFile(fileName) if (!sourceFile) { this.getHost().addFile(fileName) From f0c8e02fdd58465e415adbd673151a45952bdf38 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Tue, 3 Oct 2017 11:01:40 +0200 Subject: [PATCH 2/9] fix: Normalise typeRoots and filePath used against config checks to forward slashes Fixes typeroots support in Windows --- src/project-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/project-manager.ts b/src/project-manager.ts index 89ee626c5..ca209438d 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -343,7 +343,7 @@ export class ProjectConfiguration { const options = configParseResult.options const pathResolver = /^[a-z]:\//i.test(base) ? path.win32 : path.posix this.typeRoots = options.typeRoots ? - options.typeRoots.map((r: string) => pathResolver.resolve(this.rootFilePath, r)) : + options.typeRoots.map((r: string) => toUnixPath(pathResolver.resolve(this.rootFilePath, r))) : [] if (/(^|\/)jsconfig\.json$/.test(this.configFilePath)) { @@ -833,7 +833,7 @@ export class ProjectManager implements Disposable { return traceObservable('Ensure config dependencies', childOf, span => { if (!this.ensuredConfigDependencies) { this.ensuredConfigDependencies = observableFromIterable(this.inMemoryFs.uris()) - .filter(uri => this.isConfigDependency(uri2path(uri))) + .filter(uri => this.isConfigDependency(toUnixPath(uri2path(uri)))) .mergeMap(uri => this.updater.ensure(uri)) .do(noop, err => { this.ensuredConfigDependencies = undefined From 96d6c476a9baabd108c4b86dc88f8ff34ad64939 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Wed, 4 Oct 2017 23:30:30 +0200 Subject: [PATCH 3/9] fix: Add tests, handle windows paths in project-manager --- src/project-manager.ts | 22 ++- src/test/project-manager.test.ts | 245 +++++++++++++++++-------------- 2 files changed, 143 insertions(+), 124 deletions(-) diff --git a/src/project-manager.ts b/src/project-manager.ts index ca209438d..dbef35dc5 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -589,7 +589,7 @@ export class ProjectManager implements Disposable { // Create catch-all fallback configs in case there are no tsconfig.json files // They are removed once at least one tsconfig.json is found - const trimmedRootPath = this.rootPath.replace(/\/+$/, '') + const trimmedRootPath = this.rootPath.replace(/[\\\/]+$/, '') const fallbackConfigs: {js?: ProjectConfiguration, ts?: ProjectConfiguration} = {} for (const configType of ['js', 'ts'] as ConfigType[]) { const configs = this.configs[configType] @@ -622,13 +622,8 @@ export class ProjectManager implements Disposable { .filter(([uri, content]) => !!content && /\/[tj]sconfig\.json/.test(uri) && !uri.includes('/node_modules/')) .subscribe(([uri, content]) => { const filePath = uri2path(uri) - let dir = toUnixPath(filePath) - const pos = dir.lastIndexOf('/') - if (pos <= 0) { - dir = '' - } else { - dir = dir.substring(0, pos) - } + const pos = filePath.includes('\\') ? filePath.lastIndexOf('\\') : filePath.lastIndexOf('/') + const dir = pos <= 0 ? '' : filePath.substring(0, pos) const configType = this.getConfigurationType(filePath) const configs = this.configs[configType] configs.set(dir, new ProjectConfiguration( @@ -930,19 +925,19 @@ export class ProjectManager implements Disposable { * @return closest configuration for a given file path or undefined if there is no such configuration */ public getConfigurationIfExists(filePath: string, configType = this.getConfigurationType(filePath)): ProjectConfiguration | undefined { - let dir = toUnixPath(filePath) + let dir = filePath let config: ProjectConfiguration | undefined const configs = this.configs[configType] if (!configs) { return undefined } - const rootPath = this.rootPath.replace(/\/+$/, '') + const rootPath = this.rootPath.replace(/[\\\/]+$/, '') while (dir && dir !== rootPath) { config = configs.get(dir) if (config) { return config } - const pos = dir.lastIndexOf('/') + const pos = dir.includes('\\') ? dir.lastIndexOf('\\') : dir.lastIndexOf('/') if (pos <= 0) { dir = '' } else { @@ -1030,13 +1025,14 @@ export class ProjectManager implements Disposable { * @return configuration type to use for a given file */ private getConfigurationType(filePath: string): ConfigType { - const name = path.posix.basename(filePath) + const unixPath = toUnixPath(filePath) + const name = path.posix.basename(unixPath) if (name === 'tsconfig.json') { return 'ts' } else if (name === 'jsconfig.json') { return 'js' } - const extension = path.posix.extname(filePath) + const extension = path.posix.extname(unixPath) if (extension === '.js' || extension === '.jsx') { return 'js' } diff --git a/src/test/project-manager.test.ts b/src/test/project-manager.test.ts index db8ea1167..dc4b9f53c 100644 --- a/src/test/project-manager.test.ts +++ b/src/test/project-manager.test.ts @@ -3,134 +3,157 @@ import chaiAsPromised = require('chai-as-promised') import { FileSystemUpdater } from '../fs' import { InMemoryFileSystem } from '../memfs' import { ProjectManager } from '../project-manager' +import { uri2path } from '../util' import { MapFileSystem } from './fs-helpers' chai.use(chaiAsPromised) const assert = chai.assert describe('ProjectManager', () => { + for (const rootUri of ['file:///', 'file:///c:/foo/bar/', 'file:///foo/bar/']) { + testWithRootUri(rootUri) + } +}) - let projectManager: ProjectManager - let memfs: InMemoryFileSystem +function testWithRootUri(rootUri: string): void { + describe(`with rootUri ${rootUri}`, () => { - it('should add a ProjectConfiguration when a tsconfig.json is added to the InMemoryFileSystem', () => { - memfs = new InMemoryFileSystem('/') - const localfs = new MapFileSystem(new Map([ - ['file:///foo/tsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) - memfs.add('file:///foo/tsconfig.json', '{}') - const configs = Array.from(projectManager.configurations()) - assert.isDefined(configs.find(config => config.configFilePath === '/foo/tsconfig.json')) - }) + let projectManager: ProjectManager + let memfs: InMemoryFileSystem - describe('ensureBasicFiles', () => { - beforeEach(async () => { - memfs = new InMemoryFileSystem('/') + it('should add a ProjectConfiguration when a tsconfig.json is added to the InMemoryFileSystem', () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const configFileUri = rootUri + 'foo/tsconfig.json' const localfs = new MapFileSystem(new Map([ - ['file:///project/package.json', '{"name": "package-name-1"}'], - ['file:///project/tsconfig.json', '{ "compilerOptions": { "typeRoots": ["../types"]} }'], - ['file:///project/file.ts', 'console.log(GLOBALCONSTANT);'], - ['file:///types/types.d.ts', 'declare var GLOBALCONSTANT=1;'] - + [configFileUri, '{}'] ])) const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) - }) - it('loads files from typeRoots', async () => { - await projectManager.ensureReferencedFiles('file:///project/file.ts').toPromise() - memfs.getContent('file:///project/file.ts') - memfs.getContent('file:///types/types.d.ts') - }) - }) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + memfs.add(configFileUri, '{}') + const configs = Array.from(projectManager.configurations()) + const expectedConfigFilePath = uri2path(configFileUri) - describe('getPackageName()', () => { - beforeEach(async () => { - memfs = new InMemoryFileSystem('/') - const localfs = new MapFileSystem(new Map([ - ['file:///package.json', '{"name": "package-name-1"}'], - ['file:///subdirectory-with-tsconfig/package.json', '{"name": "package-name-2"}'], - ['file:///subdirectory-with-tsconfig/src/tsconfig.json', '{}'], - ['file:///subdirectory-with-tsconfig/src/dummy.ts', ''] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() - }) - }) - describe('ensureReferencedFiles()', () => { - beforeEach(() => { - memfs = new InMemoryFileSystem('/') - const localfs = new MapFileSystem(new Map([ - ['file:///package.json', '{"name": "package-name-1"}'], - ['file:///node_modules/somelib/index.js', '/// \n/// '], - ['file:///node_modules/somelib/pathref.d.ts', ''], - ['file:///node_modules/%40types/node/index.d.ts', ''], - ['file:///src/dummy.ts', 'import * as somelib from "somelib";'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) + assert.isDefined(configs.find(config => config.configFilePath === expectedConfigFilePath)) }) - it('should ensure content for imports and references is fetched', async () => { - await projectManager.ensureReferencedFiles('file:///src/dummy.ts').toPromise() - memfs.getContent('file:///node_modules/somelib/index.js') - memfs.getContent('file:///node_modules/somelib/pathref.d.ts') - memfs.getContent('file:///node_modules/%40types/node/index.d.ts') - }) - }) - describe('getConfiguration()', () => { - beforeEach(async () => { - memfs = new InMemoryFileSystem('/') - const localfs = new MapFileSystem(new Map([ - ['file:///tsconfig.json', '{}'], - ['file:///src/jsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() + + describe('ensureBasicFiles', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'project/package.json', '{"name": "package-name-1"}'], + [rootUri + 'project/tsconfig.json', '{ "compilerOptions": { "typeRoots": ["../types"]} }'], + [rootUri + 'project/file.ts', 'console.log(GLOBALCONSTANT);'], + [rootUri + 'types/types.d.ts', 'declare var GLOBALCONSTANT=1;'] + + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + }) + it('loads files from typeRoots', async () => { + await projectManager.ensureReferencedFiles(rootUri + 'project/file.ts').toPromise() + memfs.getContent(rootUri + 'project/file.ts') + memfs.getContent(rootUri + 'types/types.d.ts') + }) }) - it('should resolve best configuration based on file name', () => { - const jsConfig = projectManager.getConfiguration('/src/foo.js') - const tsConfig = projectManager.getConfiguration('/src/foo.ts') - assert.equal('/tsconfig.json', tsConfig.configFilePath) - assert.equal('/src/jsconfig.json', jsConfig.configFilePath) + + describe('getPackageName()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'package.json', '{"name": "package-name-1"}'], + [rootUri + 'subdirectory-with-tsconfig/package.json', '{"name": "package-name-2"}'], + [rootUri + 'subdirectory-with-tsconfig/src/tsconfig.json', '{}'], + [rootUri + 'subdirectory-with-tsconfig/src/dummy.ts', ''] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) }) - }) - describe('getParentConfiguration()', () => { - beforeEach(async () => { - memfs = new InMemoryFileSystem('/') - const localfs = new MapFileSystem(new Map([ - ['file:///tsconfig.json', '{}'], - ['file:///src/jsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() + + describe('ensureReferencedFiles()', () => { + beforeEach(() => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'package.json', '{"name": "package-name-1"}'], + [rootUri + 'node_modules/somelib/index.js', '/// \n/// '], + [rootUri + 'node_modules/somelib/pathref.d.ts', ''], + [rootUri + 'node_modules/%40types/node/index.d.ts', ''], + [rootUri + 'src/dummy.ts', 'import * as somelib from "somelib";'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + }) + it('should ensure content for imports and references is fetched', async () => { + await projectManager.ensureReferencedFiles(rootUri + 'src/dummy.ts').toPromise() + memfs.getContent(rootUri + 'node_modules/somelib/index.js') + memfs.getContent(rootUri + 'node_modules/somelib/pathref.d.ts') + memfs.getContent(rootUri + 'node_modules/%40types/node/index.d.ts') + }) }) - it('should resolve best configuration based on file name', () => { - const config = projectManager.getParentConfiguration('file:///src/foo.ts') - assert.isDefined(config) - assert.equal('/tsconfig.json', config!.configFilePath) + describe('getConfiguration()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'tsconfig.json', '{}'], + [rootUri + 'src/jsconfig.json', '{}'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) + it('should resolve best configuration based on file name', () => { + const jsConfig = projectManager.getConfiguration(uri2path(rootUri + 'src/foo.js')) + const tsConfig = projectManager.getConfiguration(uri2path(rootUri + 'src/foo.ts')) + assert.equal(uri2path(rootUri + 'tsconfig.json'), tsConfig.configFilePath) + assert.equal(uri2path(rootUri + 'src/jsconfig.json'), jsConfig.configFilePath) + assert.equal(Array.from(projectManager.configurations()).length, 2) + }) }) - }) - describe('getChildConfigurations()', () => { - beforeEach(async () => { - memfs = new InMemoryFileSystem('/') - const localfs = new MapFileSystem(new Map([ - ['file:///tsconfig.json', '{}'], - ['file:///foo/bar/tsconfig.json', '{}'], - ['file:///foo/baz/tsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager('/', memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() + describe('getParentConfiguration()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'tsconfig.json', '{}'], + [rootUri + 'src/jsconfig.json', '{}'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) + it('should resolve best configuration based on file name', () => { + const config = projectManager.getParentConfiguration(rootUri + 'src/foo.ts') + assert.isDefined(config) + assert.equal(uri2path(rootUri + 'tsconfig.json'), config!.configFilePath) + assert.equal(Array.from(projectManager.configurations()).length, 2) + }) }) - it('should resolve best configuration based on file name', () => { - const configs = Array.from(projectManager.getChildConfigurations('file:///foo')).map(config => config.configFilePath) - assert.deepEqual(configs, [ - '/foo/bar/tsconfig.json', - '/foo/baz/tsconfig.json' - ]) + describe('getChildConfigurations()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'tsconfig.json', '{}'], + [rootUri + 'foo/bar/tsconfig.json', '{}'], + [rootUri + 'foo/baz/tsconfig.json', '{}'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) + it('should resolve best configuration based on file name', () => { + const configs = Array.from(projectManager.getChildConfigurations(rootUri + 'foo')).map(config => config.configFilePath) + assert.deepEqual(configs, [ + uri2path(rootUri + 'foo/bar/tsconfig.json'), + uri2path(rootUri + 'foo/baz/tsconfig.json') + ]) + assert.equal(Array.from(projectManager.configurations()).length, 4) + }) }) }) -}) +} From 6d9d3d0f34d5354e924d25468cbd2a15376fec30 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Sat, 7 Oct 2017 00:53:00 +0200 Subject: [PATCH 4/9] fix: Make typeRoot test more thorough, add global declaration test --- src/test/project-manager.test.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/test/project-manager.test.ts b/src/test/project-manager.test.ts index dc4b9f53c..b411fc12a 100644 --- a/src/test/project-manager.test.ts +++ b/src/test/project-manager.test.ts @@ -43,17 +43,37 @@ function testWithRootUri(rootUri: string): void { const localfs = new MapFileSystem(new Map([ [rootUri + 'project/package.json', '{"name": "package-name-1"}'], [rootUri + 'project/tsconfig.json', '{ "compilerOptions": { "typeRoots": ["../types"]} }'], - [rootUri + 'project/file.ts', 'console.log(GLOBALCONSTANT);'], + [rootUri + 'project/node_modules/%40types/mocha/index.d.ts', 'declare var describe { (description: string, spec: () => void): void; }'], + [rootUri + 'project/file.ts', 'describe("test", () => console.log(GLOBALCONSTANT));'], [rootUri + 'types/types.d.ts', 'declare var GLOBALCONSTANT=1;'] ])) const updater = new FileSystemUpdater(localfs, memfs) projectManager = new ProjectManager(rootPath, memfs, updater, true) }) + it('loads files from typeRoots', async () => { - await projectManager.ensureReferencedFiles(rootUri + 'project/file.ts').toPromise() - memfs.getContent(rootUri + 'project/file.ts') - memfs.getContent(rootUri + 'types/types.d.ts') + const sourceFileUri = rootUri + 'project/file.ts' + const typeRootFileUri = rootUri + 'types/types.d.ts' + await projectManager.ensureReferencedFiles(sourceFileUri).toPromise() + memfs.getContent(typeRootFileUri) + + const config = projectManager.getConfiguration(uri2path(sourceFileUri), 'ts') + const host = config.getHost() + const typeDeclarationPath = uri2path(typeRootFileUri) + assert.includeMembers(host.getScriptFileNames(), [typeDeclarationPath]) + }) + + it('loads mocha global type declarations', async () => { + const sourceFileUri = rootUri + 'project/file.ts' + const mochaDeclarationFileUri = rootUri + 'project/node_modules/%40types/mocha/index.d.ts' + await projectManager.ensureReferencedFiles(sourceFileUri).toPromise() + memfs.getContent(mochaDeclarationFileUri) + + const config = projectManager.getConfiguration(uri2path(sourceFileUri), 'ts') + const host = config.getHost() + const mochaFilePath = uri2path(mochaDeclarationFileUri) + assert.includeMembers(host.getScriptFileNames(), [mochaFilePath]) }) }) From 68c596b3726061be377f5af7810b5c2a168f1506 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Sun, 8 Oct 2017 11:36:44 +0200 Subject: [PATCH 5/9] fix: Put per-path test scenario inline --- src/test/project-manager.test.ts | 286 +++++++++++++++---------------- 1 file changed, 141 insertions(+), 145 deletions(-) diff --git a/src/test/project-manager.test.ts b/src/test/project-manager.test.ts index b411fc12a..0c067ce56 100644 --- a/src/test/project-manager.test.ts +++ b/src/test/project-manager.test.ts @@ -10,170 +10,166 @@ const assert = chai.assert describe('ProjectManager', () => { for (const rootUri of ['file:///', 'file:///c:/foo/bar/', 'file:///foo/bar/']) { - testWithRootUri(rootUri) - } -}) - -function testWithRootUri(rootUri: string): void { - describe(`with rootUri ${rootUri}`, () => { - - let projectManager: ProjectManager - let memfs: InMemoryFileSystem + describe(`with rootUri ${rootUri}`, () => { - it('should add a ProjectConfiguration when a tsconfig.json is added to the InMemoryFileSystem', () => { - const rootPath = uri2path(rootUri) - memfs = new InMemoryFileSystem(rootPath) - const configFileUri = rootUri + 'foo/tsconfig.json' - const localfs = new MapFileSystem(new Map([ - [configFileUri, '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager(rootPath, memfs, updater, true) - memfs.add(configFileUri, '{}') - const configs = Array.from(projectManager.configurations()) - const expectedConfigFilePath = uri2path(configFileUri) + let projectManager: ProjectManager + let memfs: InMemoryFileSystem - assert.isDefined(configs.find(config => config.configFilePath === expectedConfigFilePath)) - }) - - describe('ensureBasicFiles', () => { - beforeEach(async () => { + it('should add a ProjectConfiguration when a tsconfig.json is added to the InMemoryFileSystem', () => { const rootPath = uri2path(rootUri) memfs = new InMemoryFileSystem(rootPath) + const configFileUri = rootUri + 'foo/tsconfig.json' const localfs = new MapFileSystem(new Map([ - [rootUri + 'project/package.json', '{"name": "package-name-1"}'], - [rootUri + 'project/tsconfig.json', '{ "compilerOptions": { "typeRoots": ["../types"]} }'], - [rootUri + 'project/node_modules/%40types/mocha/index.d.ts', 'declare var describe { (description: string, spec: () => void): void; }'], - [rootUri + 'project/file.ts', 'describe("test", () => console.log(GLOBALCONSTANT));'], - [rootUri + 'types/types.d.ts', 'declare var GLOBALCONSTANT=1;'] - + [configFileUri, '{}'] ])) const updater = new FileSystemUpdater(localfs, memfs) projectManager = new ProjectManager(rootPath, memfs, updater, true) + memfs.add(configFileUri, '{}') + const configs = Array.from(projectManager.configurations()) + const expectedConfigFilePath = uri2path(configFileUri) + + assert.isDefined(configs.find(config => config.configFilePath === expectedConfigFilePath)) }) - it('loads files from typeRoots', async () => { - const sourceFileUri = rootUri + 'project/file.ts' - const typeRootFileUri = rootUri + 'types/types.d.ts' - await projectManager.ensureReferencedFiles(sourceFileUri).toPromise() - memfs.getContent(typeRootFileUri) + describe('ensureBasicFiles', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'project/package.json', '{"name": "package-name-1"}'], + [rootUri + 'project/tsconfig.json', '{ "compilerOptions": { "typeRoots": ["../types"]} }'], + [rootUri + 'project/node_modules/%40types/mocha/index.d.ts', 'declare var describe { (description: string, spec: () => void): void; }'], + [rootUri + 'project/file.ts', 'describe("test", () => console.log(GLOBALCONSTANT));'], + [rootUri + 'types/types.d.ts', 'declare var GLOBALCONSTANT=1;'] - const config = projectManager.getConfiguration(uri2path(sourceFileUri), 'ts') - const host = config.getHost() - const typeDeclarationPath = uri2path(typeRootFileUri) - assert.includeMembers(host.getScriptFileNames(), [typeDeclarationPath]) - }) + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + }) - it('loads mocha global type declarations', async () => { - const sourceFileUri = rootUri + 'project/file.ts' - const mochaDeclarationFileUri = rootUri + 'project/node_modules/%40types/mocha/index.d.ts' - await projectManager.ensureReferencedFiles(sourceFileUri).toPromise() - memfs.getContent(mochaDeclarationFileUri) + it('loads files from typeRoots', async () => { + const sourceFileUri = rootUri + 'project/file.ts' + const typeRootFileUri = rootUri + 'types/types.d.ts' + await projectManager.ensureReferencedFiles(sourceFileUri).toPromise() + memfs.getContent(typeRootFileUri) - const config = projectManager.getConfiguration(uri2path(sourceFileUri), 'ts') - const host = config.getHost() - const mochaFilePath = uri2path(mochaDeclarationFileUri) - assert.includeMembers(host.getScriptFileNames(), [mochaFilePath]) - }) - }) + const config = projectManager.getConfiguration(uri2path(sourceFileUri), 'ts') + const host = config.getHost() + const typeDeclarationPath = uri2path(typeRootFileUri) + assert.includeMembers(host.getScriptFileNames(), [typeDeclarationPath]) + }) - describe('getPackageName()', () => { - beforeEach(async () => { - const rootPath = uri2path(rootUri) - memfs = new InMemoryFileSystem(rootPath) - const localfs = new MapFileSystem(new Map([ - [rootUri + 'package.json', '{"name": "package-name-1"}'], - [rootUri + 'subdirectory-with-tsconfig/package.json', '{"name": "package-name-2"}'], - [rootUri + 'subdirectory-with-tsconfig/src/tsconfig.json', '{}'], - [rootUri + 'subdirectory-with-tsconfig/src/dummy.ts', ''] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager(rootPath, memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() - }) - }) + it('loads mocha global type declarations', async () => { + const sourceFileUri = rootUri + 'project/file.ts' + const mochaDeclarationFileUri = rootUri + 'project/node_modules/%40types/mocha/index.d.ts' + await projectManager.ensureReferencedFiles(sourceFileUri).toPromise() + memfs.getContent(mochaDeclarationFileUri) - describe('ensureReferencedFiles()', () => { - beforeEach(() => { - const rootPath = uri2path(rootUri) - memfs = new InMemoryFileSystem(rootPath) - const localfs = new MapFileSystem(new Map([ - [rootUri + 'package.json', '{"name": "package-name-1"}'], - [rootUri + 'node_modules/somelib/index.js', '/// \n/// '], - [rootUri + 'node_modules/somelib/pathref.d.ts', ''], - [rootUri + 'node_modules/%40types/node/index.d.ts', ''], - [rootUri + 'src/dummy.ts', 'import * as somelib from "somelib";'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager(rootPath, memfs, updater, true) - }) - it('should ensure content for imports and references is fetched', async () => { - await projectManager.ensureReferencedFiles(rootUri + 'src/dummy.ts').toPromise() - memfs.getContent(rootUri + 'node_modules/somelib/index.js') - memfs.getContent(rootUri + 'node_modules/somelib/pathref.d.ts') - memfs.getContent(rootUri + 'node_modules/%40types/node/index.d.ts') + const config = projectManager.getConfiguration(uri2path(sourceFileUri), 'ts') + const host = config.getHost() + const mochaFilePath = uri2path(mochaDeclarationFileUri) + assert.includeMembers(host.getScriptFileNames(), [mochaFilePath]) + }) }) - }) - describe('getConfiguration()', () => { - beforeEach(async () => { - const rootPath = uri2path(rootUri) - memfs = new InMemoryFileSystem(rootPath) - const localfs = new MapFileSystem(new Map([ - [rootUri + 'tsconfig.json', '{}'], - [rootUri + 'src/jsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager(rootPath, memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() - }) - it('should resolve best configuration based on file name', () => { - const jsConfig = projectManager.getConfiguration(uri2path(rootUri + 'src/foo.js')) - const tsConfig = projectManager.getConfiguration(uri2path(rootUri + 'src/foo.ts')) - assert.equal(uri2path(rootUri + 'tsconfig.json'), tsConfig.configFilePath) - assert.equal(uri2path(rootUri + 'src/jsconfig.json'), jsConfig.configFilePath) - assert.equal(Array.from(projectManager.configurations()).length, 2) + + describe('getPackageName()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'package.json', '{"name": "package-name-1"}'], + [rootUri + 'subdirectory-with-tsconfig/package.json', '{"name": "package-name-2"}'], + [rootUri + 'subdirectory-with-tsconfig/src/tsconfig.json', '{}'], + [rootUri + 'subdirectory-with-tsconfig/src/dummy.ts', ''] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) }) - }) - describe('getParentConfiguration()', () => { - beforeEach(async () => { - const rootPath = uri2path(rootUri) - memfs = new InMemoryFileSystem(rootPath) - const localfs = new MapFileSystem(new Map([ - [rootUri + 'tsconfig.json', '{}'], - [rootUri + 'src/jsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager(rootPath, memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() + + describe('ensureReferencedFiles()', () => { + beforeEach(() => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'package.json', '{"name": "package-name-1"}'], + [rootUri + 'node_modules/somelib/index.js', '/// \n/// '], + [rootUri + 'node_modules/somelib/pathref.d.ts', ''], + [rootUri + 'node_modules/%40types/node/index.d.ts', ''], + [rootUri + 'src/dummy.ts', 'import * as somelib from "somelib";'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + }) + it('should ensure content for imports and references is fetched', async () => { + await projectManager.ensureReferencedFiles(rootUri + 'src/dummy.ts').toPromise() + memfs.getContent(rootUri + 'node_modules/somelib/index.js') + memfs.getContent(rootUri + 'node_modules/somelib/pathref.d.ts') + memfs.getContent(rootUri + 'node_modules/%40types/node/index.d.ts') + }) }) - it('should resolve best configuration based on file name', () => { - const config = projectManager.getParentConfiguration(rootUri + 'src/foo.ts') - assert.isDefined(config) - assert.equal(uri2path(rootUri + 'tsconfig.json'), config!.configFilePath) - assert.equal(Array.from(projectManager.configurations()).length, 2) + describe('getConfiguration()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'tsconfig.json', '{}'], + [rootUri + 'src/jsconfig.json', '{}'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) + it('should resolve best configuration based on file name', () => { + const jsConfig = projectManager.getConfiguration(uri2path(rootUri + 'src/foo.js')) + const tsConfig = projectManager.getConfiguration(uri2path(rootUri + 'src/foo.ts')) + assert.equal(uri2path(rootUri + 'tsconfig.json'), tsConfig.configFilePath) + assert.equal(uri2path(rootUri + 'src/jsconfig.json'), jsConfig.configFilePath) + assert.equal(Array.from(projectManager.configurations()).length, 2) + }) }) - }) - describe('getChildConfigurations()', () => { - beforeEach(async () => { - const rootPath = uri2path(rootUri) - memfs = new InMemoryFileSystem(rootPath) - const localfs = new MapFileSystem(new Map([ - [rootUri + 'tsconfig.json', '{}'], - [rootUri + 'foo/bar/tsconfig.json', '{}'], - [rootUri + 'foo/baz/tsconfig.json', '{}'] - ])) - const updater = new FileSystemUpdater(localfs, memfs) - projectManager = new ProjectManager(rootPath, memfs, updater, true) - await projectManager.ensureAllFiles().toPromise() + describe('getParentConfiguration()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'tsconfig.json', '{}'], + [rootUri + 'src/jsconfig.json', '{}'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) + it('should resolve best configuration based on file name', () => { + const config = projectManager.getParentConfiguration(rootUri + 'src/foo.ts') + assert.isDefined(config) + assert.equal(uri2path(rootUri + 'tsconfig.json'), config!.configFilePath) + assert.equal(Array.from(projectManager.configurations()).length, 2) + }) }) - it('should resolve best configuration based on file name', () => { - const configs = Array.from(projectManager.getChildConfigurations(rootUri + 'foo')).map(config => config.configFilePath) - assert.deepEqual(configs, [ - uri2path(rootUri + 'foo/bar/tsconfig.json'), - uri2path(rootUri + 'foo/baz/tsconfig.json') - ]) - assert.equal(Array.from(projectManager.configurations()).length, 4) + describe('getChildConfigurations()', () => { + beforeEach(async () => { + const rootPath = uri2path(rootUri) + memfs = new InMemoryFileSystem(rootPath) + const localfs = new MapFileSystem(new Map([ + [rootUri + 'tsconfig.json', '{}'], + [rootUri + 'foo/bar/tsconfig.json', '{}'], + [rootUri + 'foo/baz/tsconfig.json', '{}'] + ])) + const updater = new FileSystemUpdater(localfs, memfs) + projectManager = new ProjectManager(rootPath, memfs, updater, true) + await projectManager.ensureAllFiles().toPromise() + }) + it('should resolve best configuration based on file name', () => { + const configs = Array.from(projectManager.getChildConfigurations(rootUri + 'foo')).map(config => config.configFilePath) + assert.deepEqual(configs, [ + uri2path(rootUri + 'foo/bar/tsconfig.json'), + uri2path(rootUri + 'foo/baz/tsconfig.json') + ]) + assert.equal(Array.from(projectManager.configurations()).length, 4) + }) }) }) - }) -} + } +}) From 0e0b099e467ed5a130166bbde9d1144bbba1d2b1 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 9 Oct 2017 19:05:10 +0200 Subject: [PATCH 6/9] fix: Use string.search instead of includes + lastIndexOf --- src/project-manager.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/project-manager.ts b/src/project-manager.ts index dbef35dc5..31749ae23 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -24,6 +24,8 @@ import { uri2path } from './util' +const LastForwardOrBackwardSlash = /[\\\/][^\\\/]*$/ + /** * Implementaton of LanguageServiceHost that works with in-memory file system. * It takes file content from local cache and provides it to TS compiler on demand @@ -622,7 +624,7 @@ export class ProjectManager implements Disposable { .filter(([uri, content]) => !!content && /\/[tj]sconfig\.json/.test(uri) && !uri.includes('/node_modules/')) .subscribe(([uri, content]) => { const filePath = uri2path(uri) - const pos = filePath.includes('\\') ? filePath.lastIndexOf('\\') : filePath.lastIndexOf('/') + const pos = filePath.search(LastForwardOrBackwardSlash) const dir = pos <= 0 ? '' : filePath.substring(0, pos) const configType = this.getConfigurationType(filePath) const configs = this.configs[configType] @@ -937,7 +939,7 @@ export class ProjectManager implements Disposable { if (config) { return config } - const pos = dir.includes('\\') ? dir.lastIndexOf('\\') : dir.lastIndexOf('/') + const pos = dir.search(LastForwardOrBackwardSlash) if (pos <= 0) { dir = '' } else { From 703d6645caed09c91a6d8d4280cb9c8a5708aa4a Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 9 Oct 2017 21:29:43 +0200 Subject: [PATCH 7/9] fix: Annotate classes and functions that convert paths to forward slashes --- src/project-manager.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/project-manager.ts b/src/project-manager.ts index 31749ae23..3d8c162bc 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -185,6 +185,10 @@ export class InMemoryLanguageServiceHost implements ts.LanguageServiceHost { * made available to the compiler before calling any other methods on * the ProjectConfiguration or its public members. By default, no * files are parsed. + * + * Windows file paths are converted to UNIX-style forward slashes + * when compared with Typescript configuration (isGlobalTSFile, + * expectedFilePaths and typeRoots) */ export class ProjectConfiguration { @@ -227,12 +231,13 @@ export class ProjectConfiguration { /** * List of files that project consist of (based on tsconfig includes/excludes and wildcards). - * Each item is a relative file path + * Each item is a relative UNIX-like file path */ private expectedFilePaths = new Set() /** * List of resolved extra root directories to allow global type declaration files to be loaded from. + * Each item is an absolute UNIX-like file path */ private typeRoots: string[] @@ -403,7 +408,7 @@ export class ProjectConfiguration { /** * Determines if a fileName is a declaration file within expected files or type roots - * @param fileName + * @param fileName A Unix-like absolute file path. */ public isExpectedDeclarationFile(fileName: string): boolean { return isDeclarationFile(fileName) && @@ -811,7 +816,7 @@ export class ProjectManager implements Disposable { /** * Determines if a tsconfig/jsconfig needs additional declaration files loaded. - * @param filePath + * @param filePath A UNIX-like absolute file path */ public isConfigDependency(filePath: string): boolean { for (const config of this.configurations()) { From 34f0c5d4dca2e32efd141b52c28d82779a459f1d Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 9 Oct 2017 21:38:32 +0200 Subject: [PATCH 8/9] fix: Add note about ProjectManager using Windows paths --- src/project-manager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/project-manager.ts b/src/project-manager.ts index 3d8c162bc..6630ab63e 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -495,6 +495,8 @@ export type ConfigType = 'js' | 'ts' * makes one or more LanguageService objects. By default all LanguageService objects contain no files, * they are added on demand - current file for hover or definition, project's files for references and * all files from all projects for workspace symbols. + * + * ProjectManager preserves Windows paths until passed to ProjectConfiguration or TS APIs. */ export class ProjectManager implements Disposable { From 5fc67b6f0d308b0280e5b942aa25f0122ed475a8 Mon Sep 17 00:00:00 2001 From: Tom van Ommeren Date: Mon, 9 Oct 2017 22:04:42 +0200 Subject: [PATCH 9/9] fix: Use CONSTANT_CASE for module constant --- src/project-manager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/project-manager.ts b/src/project-manager.ts index 6630ab63e..faf0a858b 100644 --- a/src/project-manager.ts +++ b/src/project-manager.ts @@ -24,7 +24,7 @@ import { uri2path } from './util' -const LastForwardOrBackwardSlash = /[\\\/][^\\\/]*$/ +const LAST_FORWARD_OR_BACKWARD_SLASH = /[\\\/][^\\\/]*$/ /** * Implementaton of LanguageServiceHost that works with in-memory file system. @@ -631,7 +631,7 @@ export class ProjectManager implements Disposable { .filter(([uri, content]) => !!content && /\/[tj]sconfig\.json/.test(uri) && !uri.includes('/node_modules/')) .subscribe(([uri, content]) => { const filePath = uri2path(uri) - const pos = filePath.search(LastForwardOrBackwardSlash) + const pos = filePath.search(LAST_FORWARD_OR_BACKWARD_SLASH) const dir = pos <= 0 ? '' : filePath.substring(0, pos) const configType = this.getConfigurationType(filePath) const configs = this.configs[configType] @@ -946,7 +946,7 @@ export class ProjectManager implements Disposable { if (config) { return config } - const pos = dir.search(LastForwardOrBackwardSlash) + const pos = dir.search(LAST_FORWARD_OR_BACKWARD_SLASH) if (pos <= 0) { dir = '' } else {