Skip to content

Commit 0b16180

Browse files
committed
Import completions with rootdirs compiler option
1 parent ed2da32 commit 0b16180

File tree

3 files changed

+109
-5
lines changed

3 files changed

+109
-5
lines changed

src/harness/harnessLanguageService.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,22 @@ namespace Harness.LanguageService {
219219
return snapshot.getText(0, snapshot.getLength());
220220
}
221221
resolvePath(path: string): string {
222-
return ts.normalizePath(ts.isRootedDiskPath(path) ? path : ts.combinePaths(this.getCurrentDirectory(), path));
222+
if (!ts.isRootedDiskPath(path)) {
223+
// An "absolute" path for fourslash is one that is contained within the tests directory
224+
const components = ts.getNormalizedPathComponents(path, this.getCurrentDirectory());
225+
if (components.length) {
226+
// If this is still a relative path after normalization (i.e. currentDirectory is relative), the root will be the empty string
227+
if (!components[0]) {
228+
components.splice(0, 1);
229+
if (components[0] !== "tests") {
230+
// If not contained within test, assume its relative to the directory containing the test files
231+
return ts.normalizePath(ts.combinePaths("tests/cases/fourslash", components.join(ts.directorySeparator)));
232+
}
233+
}
234+
return ts.normalizePath(components.join(ts.directorySeparator));
235+
}
236+
}
237+
return ts.normalizePath(path);
223238
}
224239

225240

src/services/services.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4379,7 +4379,15 @@ namespace ts {
43794379
const isRelativePath = startsWith(literalValue, ".");
43804380
const scriptDir = getDirectoryPath(node.getSourceFile().path);
43814381
if (isRelativePath || isRootedDiskPath(literalValue)) {
4382-
result = getCompletionEntriesForDirectoryFragment(literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false);
4382+
const compilerOptions = program.getCompilerOptions();
4383+
if (compilerOptions.rootDirs) {
4384+
result = getCompletionEntriesForDirectoryFragmentWithRootDirs(
4385+
compilerOptions.rootDirs, literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false);
4386+
}
4387+
else {
4388+
result = getCompletionEntriesForDirectoryFragment(
4389+
literalValue, scriptDir, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false);
4390+
}
43834391
}
43844392
else {
43854393
// Check for node modules
@@ -4393,10 +4401,42 @@ namespace ts {
43934401
};
43944402
}
43954403

4396-
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean): CompletionEntry[] {
4397-
// Complete the path by looking for source files and directories
4404+
/**
4405+
* Takes a script path and returns paths for all potential folders that could be merged with its
4406+
* containing folder via the "rootDirs" compiler option
4407+
*/
4408+
function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptPath: string, ignoreCase: boolean) {
4409+
// Make all paths absolute/normalized if they are not already
4410+
rootDirs = map(rootDirs, rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory)));
4411+
4412+
// Determine the path to the directory containing the script relative to the root directory it is contained within
4413+
let relativeDirectory: string;
4414+
forEach(rootDirs, rootDirectory => {
4415+
if (containsPath(rootDirectory, scriptPath, basePath, ignoreCase)) {
4416+
relativeDirectory = scriptPath.substr(rootDirectory.length);
4417+
return true;
4418+
}
4419+
});
4420+
4421+
// Now find a path for each potential directory that is to be merged with the one containing the script
4422+
return deduplicate(map(rootDirs, rootDirectory => combinePaths(rootDirectory, relativeDirectory)));
4423+
}
4424+
4425+
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean): CompletionEntry[] {
4426+
const basePath = program.getCompilerOptions().project || host.getCurrentDirectory();
4427+
4428+
const baseDirectories = getBaseDirectoriesFromRootDirs(
4429+
rootDirs, basePath, scriptPath, host.useCaseSensitiveFileNames && !host.useCaseSensitiveFileNames());
43984430
const result: CompletionEntry[] = [];
43994431

4432+
for (const baseDirectory of baseDirectories) {
4433+
getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, result);
4434+
}
4435+
4436+
return result;
4437+
}
4438+
4439+
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, result: CompletionEntry[] = []): CompletionEntry[] {
44004440
const toComplete = getBaseFileName(fragment);
44014441
const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)));
44024442
const baseDirectory = toComplete ? getDirectoryPath(absolutePath) : absolutePath;
@@ -4456,7 +4496,8 @@ namespace ts {
44564496

44574497
if (baseUrl) {
44584498
const fileExtensions = getSupportedExtensions(options);
4459-
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(host.getCurrentDirectory(), baseUrl)
4499+
const projectDir = options.project || host.getCurrentDirectory();
4500+
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl);
44604501
result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false);
44614502

44624503
if (paths) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @rootDirs: sub/src1,src2
4+
5+
// @Filename: src2/test0.ts
6+
//// import * as foo1 from "./mo/*import_as0*/
7+
//// import foo2 = require("./mo/*import_equals0*/
8+
//// var foo3 = require("./mo/*require0*/
9+
10+
// @Filename: src2/module0.ts
11+
//// export var w = 0;
12+
13+
// @Filename: sub/src1/module1.ts
14+
//// export var x = 0;
15+
16+
// @Filename: sub/src1/module2.ts
17+
//// export var y = 0;
18+
19+
// @Filename: sub/src1/more/module3.ts
20+
//// export var z = 0;
21+
22+
23+
// @Filename: f1.ts
24+
//// /*f1*/
25+
// @Filename: f2.tsx
26+
//// /*f2*/
27+
// @Filename: folder/f1.ts
28+
//// /*subf1*/
29+
// @Filename: f3.js
30+
//// /*f3*/
31+
// @Filename: f4.jsx
32+
//// /*f4*/
33+
// @Filename: e1.ts
34+
//// /*e1*/
35+
// @Filename: e2.js
36+
//// /*e2*/
37+
38+
const kinds = ["import_as", "import_equals", "require"];
39+
40+
for (const kind of kinds) {
41+
goTo.marker(kind + "0");
42+
43+
verify.completionListContains("module0");
44+
verify.completionListContains("module1");
45+
verify.completionListContains("module2");
46+
verify.completionListContains("more/");
47+
verify.not.completionListItemsCountIsGreaterThan(4);
48+
}

0 commit comments

Comments
 (0)