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

Commit 25e0108

Browse files
tomv564felixfbecker
authored andcommitted
feat(tsconfig): add support for typeRoots setting (#343)
1 parent 4252207 commit 25e0108

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

src/project-manager.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export class ProjectManager implements Disposable {
7575
*/
7676
private ensuredModuleStructure?: Observable<never>;
7777

78+
/**
79+
* Observable that completes when extra dependencies pointed to by tsconfig.json have been loaded.
80+
*/
81+
private ensuredConfigDependencies?: Observable<never>;
82+
7883
/**
7984
* Observable that completes when `ensureAllFiles` completed
8085
*/
@@ -253,6 +258,7 @@ export class ProjectManager implements Disposable {
253258
*/
254259
invalidateModuleStructure(): void {
255260
this.ensuredModuleStructure = undefined;
261+
this.ensuredConfigDependencies = undefined;
256262
this.ensuredAllFiles = undefined;
257263
this.ensuredOwnFiles = undefined;
258264
}
@@ -322,6 +328,7 @@ export class ProjectManager implements Disposable {
322328
span.addTags({ uri, maxDepth });
323329
ignore.add(uri);
324330
return this.ensureModuleStructure(span)
331+
.concat(Observable.defer(() => this.ensureConfigDependencies()))
325332
// If max depth was reached, don't go any further
326333
.concat(Observable.defer(() => maxDepth === 0 ? Observable.empty<never>() : this.resolveReferencedFiles(uri)))
327334
// Prevent cycles
@@ -338,6 +345,39 @@ export class ProjectManager implements Disposable {
338345
});
339346
}
340347

348+
/**
349+
* Determines if a tsconfig/jsconfig needs additional declaration files loaded.
350+
* @param filePath
351+
*/
352+
isConfigDependency(filePath: string): boolean {
353+
for (const config of this.configurations()) {
354+
config.ensureConfigFile();
355+
if (config.isExpectedDeclarationFile(filePath)) {
356+
return true;
357+
}
358+
}
359+
return false;
360+
}
361+
362+
/**
363+
* Loads files determined by tsconfig to be needed into the file system
364+
*/
365+
ensureConfigDependencies(childOf = new Span()): Observable<never> {
366+
return traceObservable('Ensure config dependencies', childOf, span => {
367+
if (!this.ensuredConfigDependencies) {
368+
this.ensuredConfigDependencies = observableFromIterable(this.inMemoryFs.uris())
369+
.filter(uri => this.isConfigDependency(uri2path(uri)))
370+
.mergeMap(uri => this.updater.ensure(uri))
371+
.do(noop, err => {
372+
this.ensuredConfigDependencies = undefined;
373+
})
374+
.publishReplay()
375+
.refCount() as Observable<never>;
376+
}
377+
return this.ensuredConfigDependencies;
378+
});
379+
}
380+
341381
/**
342382
* Invalidates a cache entry for `resolveReferencedFiles` (e.g. because the file changed)
343383
*
@@ -742,6 +782,11 @@ export class ProjectConfiguration {
742782
*/
743783
private expectedFilePaths = new Set<string>();
744784

785+
/**
786+
* List of resolved extra root directories to allow global type declaration files to be loaded from.
787+
*/
788+
private typeRoots: string[];
789+
745790
/**
746791
* @param fs file system to use
747792
* @param documentRegistry Shared DocumentRegistry that manages SourceFile objects
@@ -846,6 +891,11 @@ export class ProjectConfiguration {
846891
this.expectedFilePaths = new Set(configParseResult.fileNames);
847892

848893
const options = configParseResult.options;
894+
const pathResolver = /^[a-z]:\//i.test(base) ? path.win32 : path.posix;
895+
this.typeRoots = options.typeRoots ?
896+
options.typeRoots.map((r: string) => pathResolver.resolve(this.rootFilePath, r)) :
897+
[];
898+
849899
if (/(^|\/)jsconfig\.json$/.test(this.configFilePath)) {
850900
options.allowJs = true;
851901
}
@@ -872,6 +922,16 @@ export class ProjectConfiguration {
872922

873923
private ensuredBasicFiles = false;
874924

925+
/**
926+
* Determines if a fileName is a declaration file within expected files or type roots
927+
* @param fileName
928+
*/
929+
public isExpectedDeclarationFile(fileName: string) {
930+
return isDeclarationFile(fileName) &&
931+
(this.expectedFilePaths.has(toUnixPath(fileName)) ||
932+
this.typeRoots.some(root => fileName.startsWith(root)));
933+
}
934+
875935
/**
876936
* Ensures we added basic files (global TS files, dependencies, declarations)
877937
*/
@@ -890,7 +950,8 @@ export class ProjectConfiguration {
890950
// Add all global declaration files from the workspace and all declarations from the project
891951
for (const uri of this.fs.uris()) {
892952
const fileName = uri2path(uri);
893-
if (isGlobalTSFile(fileName) || (isDeclarationFile(fileName) && this.expectedFilePaths.has(toUnixPath(fileName)))) {
953+
if (isGlobalTSFile(fileName) ||
954+
this.isExpectedDeclarationFile(fileName)) {
894955
const sourceFile = program.getSourceFile(fileName);
895956
if (!sourceFile) {
896957
this.getHost().addFile(fileName);

src/test/project-manager.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ describe('ProjectManager', () => {
2424
assert.isDefined(configs.find(config => config.configFilePath === '/foo/tsconfig.json'));
2525
});
2626

27+
describe('ensureBasicFiles', () => {
28+
beforeEach(async () => {
29+
memfs = new InMemoryFileSystem('/');
30+
const localfs = new MapFileSystem(new Map([
31+
['file:///project/package.json', '{"name": "package-name-1"}'],
32+
['file:///project/tsconfig.json', '{ "compilerOptions": { "typeRoots": ["../types"]} }'],
33+
['file:///project/file.ts', 'console.log(GLOBALCONSTANT);'],
34+
['file:///types/types.d.ts', 'declare var GLOBALCONSTANT=1;']
35+
36+
]));
37+
const updater = new FileSystemUpdater(localfs, memfs);
38+
projectManager = new ProjectManager('/', memfs, updater, true);
39+
});
40+
it('loads files from typeRoots', async () => {
41+
await projectManager.ensureReferencedFiles('file:///project/file.ts').toPromise();
42+
memfs.getContent('file:///project/file.ts');
43+
memfs.getContent('file:///types/types.d.ts');
44+
});
45+
});
46+
2747
describe('getPackageName()', () => {
2848
beforeEach(async () => {
2949
memfs = new InMemoryFileSystem('/');

0 commit comments

Comments
 (0)