Skip to content
This repository has been archived by the owner on Oct 16, 2020. It is now read-only.

Commit

Permalink
feat(tsconfig): add support for typeRoots setting (#343)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomv564 authored and felixfbecker committed Sep 27, 2017
1 parent 4252207 commit 25e0108
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 1 deletion.
63 changes: 62 additions & 1 deletion src/project-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export class ProjectManager implements Disposable {
*/
private ensuredModuleStructure?: Observable<never>;

/**
* Observable that completes when extra dependencies pointed to by tsconfig.json have been loaded.
*/
private ensuredConfigDependencies?: Observable<never>;

/**
* Observable that completes when `ensureAllFiles` completed
*/
Expand Down Expand Up @@ -253,6 +258,7 @@ export class ProjectManager implements Disposable {
*/
invalidateModuleStructure(): void {
this.ensuredModuleStructure = undefined;
this.ensuredConfigDependencies = undefined;
this.ensuredAllFiles = undefined;
this.ensuredOwnFiles = undefined;
}
Expand Down Expand Up @@ -322,6 +328,7 @@ export class ProjectManager implements Disposable {
span.addTags({ uri, maxDepth });
ignore.add(uri);
return this.ensureModuleStructure(span)
.concat(Observable.defer(() => this.ensureConfigDependencies()))
// If max depth was reached, don't go any further
.concat(Observable.defer(() => maxDepth === 0 ? Observable.empty<never>() : this.resolveReferencedFiles(uri)))
// Prevent cycles
Expand All @@ -338,6 +345,39 @@ export class ProjectManager implements Disposable {
});
}

/**
* Determines if a tsconfig/jsconfig needs additional declaration files loaded.
* @param filePath
*/
isConfigDependency(filePath: string): boolean {
for (const config of this.configurations()) {
config.ensureConfigFile();
if (config.isExpectedDeclarationFile(filePath)) {
return true;
}
}
return false;
}

/**
* Loads files determined by tsconfig to be needed into the file system
*/
ensureConfigDependencies(childOf = new Span()): Observable<never> {
return traceObservable('Ensure config dependencies', childOf, span => {
if (!this.ensuredConfigDependencies) {
this.ensuredConfigDependencies = observableFromIterable(this.inMemoryFs.uris())
.filter(uri => this.isConfigDependency(uri2path(uri)))
.mergeMap(uri => this.updater.ensure(uri))
.do(noop, err => {
this.ensuredConfigDependencies = undefined;
})
.publishReplay()
.refCount() as Observable<never>;
}
return this.ensuredConfigDependencies;
});
}

/**
* Invalidates a cache entry for `resolveReferencedFiles` (e.g. because the file changed)
*
Expand Down Expand Up @@ -742,6 +782,11 @@ export class ProjectConfiguration {
*/
private expectedFilePaths = new Set<string>();

/**
* List of resolved extra root directories to allow global type declaration files to be loaded from.
*/
private typeRoots: string[];

/**
* @param fs file system to use
* @param documentRegistry Shared DocumentRegistry that manages SourceFile objects
Expand Down Expand Up @@ -846,6 +891,11 @@ export class ProjectConfiguration {
this.expectedFilePaths = new Set(configParseResult.fileNames);

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)) :
[];

if (/(^|\/)jsconfig\.json$/.test(this.configFilePath)) {
options.allowJs = true;
}
Expand All @@ -872,6 +922,16 @@ export class ProjectConfiguration {

private ensuredBasicFiles = false;

/**
* Determines if a fileName is a declaration file within expected files or type roots
* @param fileName
*/
public isExpectedDeclarationFile(fileName: string) {
return isDeclarationFile(fileName) &&
(this.expectedFilePaths.has(toUnixPath(fileName)) ||
this.typeRoots.some(root => fileName.startsWith(root)));
}

/**
* Ensures we added basic files (global TS files, dependencies, declarations)
*/
Expand All @@ -890,7 +950,8 @@ 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) || (isDeclarationFile(fileName) && this.expectedFilePaths.has(toUnixPath(fileName)))) {
if (isGlobalTSFile(fileName) ||
this.isExpectedDeclarationFile(fileName)) {
const sourceFile = program.getSourceFile(fileName);
if (!sourceFile) {
this.getHost().addFile(fileName);
Expand Down
20 changes: 20 additions & 0 deletions src/test/project-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ describe('ProjectManager', () => {
assert.isDefined(configs.find(config => config.configFilePath === '/foo/tsconfig.json'));
});

describe('ensureBasicFiles', () => {
beforeEach(async () => {
memfs = new InMemoryFileSystem('/');
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;']

]));
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');
});
});

describe('getPackageName()', () => {
beforeEach(async () => {
memfs = new InMemoryFileSystem('/');
Expand Down

0 comments on commit 25e0108

Please sign in to comment.