Adding import completions for typings

This commit is contained in:
Richard Knoll 2016-07-28 11:52:15 -07:00
parent 0b16180174
commit dbf19f18af
7 changed files with 183 additions and 45 deletions

View File

@ -260,8 +260,8 @@ namespace FourSlash {
// Extend our existing compiler options so that we can also support tsconfig only options
if (configJson.config.compilerOptions) {
let baseDir = ts.normalizePath(ts.getDirectoryPath(file.fileName));
let tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDir, file.fileName);
const baseDirectory = ts.normalizePath(ts.getDirectoryPath(file.fileName));
const tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDirectory, file.fileName);
if (!tsConfig.errors || !tsConfig.errors.length) {
compilationOptions = ts.extend(compilationOptions, tsConfig.options);

View File

@ -1968,7 +1968,7 @@ namespace ts {
* for completions.
* For example, this matches /// <reference path="fragment
*/
const tripleSlashDirectiveFragmentRegex = /^\/\/\/\s*<reference\s+path\s*=\s*(?:'|")([^'"]+)$/;
const tripleSlashDirectiveFragmentRegex = /^\/\/\/\s*<reference\s+(path|types)\s*=\s*(?:'|")([^'"]+)$/;
let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[];
@ -4501,7 +4501,7 @@ namespace ts {
result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false);
if (paths) {
for (var path in paths) {
for (const path in paths) {
if (paths.hasOwnProperty(path)) {
if (path === "*") {
if (paths[path]) {
@ -4526,7 +4526,7 @@ namespace ts {
result = [];
}
getCompletionEntriesFromTypings(host, options, scriptPath, result);
forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath), moduleName => {
result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName));
@ -4558,7 +4558,7 @@ namespace ts {
// If we have a suffix, then we need to read the directory all the way down. We could create a glob
// that encodes the suffix, but we would have to escape the character "?" which readDirectory
// doesn't support. For now, this is safer but slower
const includeGlob = normalizedSuffix ? "**/*" : "./*"
const includeGlob = normalizedSuffix ? "**/*" : "./*";
const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]);
const result: string[] = [];
@ -4629,19 +4629,97 @@ namespace ts {
const text = sourceFile.text.substr(node.pos, position);
const match = tripleSlashDirectiveFragmentRegex.exec(text);
if (match) {
const fragment = match[1];
const scriptPath = getDirectoryPath(sourceFile.path);
return {
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true)
};
const kind= match[1];
const fragment = match[2];
if (kind === "path") {
// Give completions for a relative path
const scriptPath = getDirectoryPath(sourceFile.path);
return {
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: getCompletionEntriesForDirectoryFragment(fragment, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true)
};
}
else {
// Give completions based on what is available in the types directory
}
}
return undefined;
}
}
function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: CompletionEntry[]): CompletionEntry[] {
// Check for typings specified in compiler options
if (options.types) {
forEach(options.types, moduleName => {
result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName));
});
}
else if (options.typeRoots) {
const absoluteRoots = map(options.typeRoots, rootDirectory => getAbsoluteProjectPath(rootDirectory, host, options.project));
forEach(absoluteRoots, absoluteRoot => getCompletionEntriesFromDirectory(host, options, absoluteRoot, result));
}
// Also get all @types typings installed in visible node_modules directories
forEach(findPackageJsons(scriptPath), package => {
const typesDir = combinePaths(getDirectoryPath(package), "node_modules/@types");
getCompletionEntriesFromDirectory(host, options, typesDir, result);
});
return result;
}
function getAbsoluteProjectPath(path: string, host: LanguageServiceHost, projectDir?: string) {
if (isRootedDiskPath(path)) {
return normalizePath(path);
}
if (projectDir) {
return normalizePath(combinePaths(projectDir, path));
}
return normalizePath(host.resolvePath(path));
}
function getCompletionEntriesFromDirectory(host: LanguageServiceHost, options: CompilerOptions, directory: string, result: CompletionEntry[]) {
if (directoryProbablyExists(directory, host)) {
const typeDirectories = host.readDirectory(directory, getSupportedExtensions(options), /*exclude*/undefined, /*include*/["./*/*"]);
const seen: {[index: string]: boolean} = {};
forEach(typeDirectories, typeFile => {
const typeDirectory = getDirectoryPath(typeFile);
if (!hasProperty(seen, typeDirectory)) {
seen[typeDirectory] = true;
result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName));
}
});
}
}
function findPackageJsons(currentDir: string): string[] {
const paths: string[] = [];
let currentConfigPath: string;
while (true) {
currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json");
if (currentConfigPath) {
paths.push(currentConfigPath);
currentDir = getDirectoryPath(currentConfigPath);
const parent = getDirectoryPath(currentDir);
if (currentDir === parent) {
break;
}
currentDir = parent;
}
else {
break;
}
}
return paths;
}
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) {
const result: VisibleModuleInfo[] = [];
findPackageJsons(scriptPath).forEach((packageJson) => {
@ -4672,29 +4750,6 @@ namespace ts {
return result;
function findPackageJsons(currentDir: string): string[] {
const paths: string[] = [];
let currentConfigPath: string;
while (true) {
currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json");
if (currentConfigPath) {
paths.push(currentConfigPath);
currentDir = getDirectoryPath(currentConfigPath);
const parent = getDirectoryPath(currentDir);
if (currentDir === parent) {
break;
}
currentDir = parent;
}
else {
break;
}
}
return paths;
}
function tryReadingPackageJson(filePath: string) {
try {
const fileText = host.readFile(filePath);
@ -4745,7 +4800,7 @@ namespace ts {
kind,
kindModifiers: ScriptElementKindModifier.none,
sortText: name
}
};
}
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {

View File

@ -22,11 +22,8 @@
// @Filename: node_modules/unlisted-module/index.js
//// /*unlisted-module*/
// @Filename: node_modules/@types/fake-module/other.d.ts
//// declare module "fake-module/other" {}
// @Filename: node_modules/@types/unlisted-module/index.d.ts
//// /*unlisted-types*/
// @Filename: ambient.ts
//// declare module "fake-module/other"
const kinds = ["import_as", "import_equals", "require"];

View File

@ -20,7 +20,7 @@
// @Filename: dir1/package.json
//// { "dependencies": { "fake-module2": "latest" } }
// @Filename: dir1/node_modules/@types/fake-module2/js.d.ts
// @Filename: dir1/node_modules/fake-module2/index.ts
//// declare module "ambient-module-test" {}
// @Filename: dir1/dir2/dir3/package.json
@ -34,7 +34,7 @@ for (const kind of kinds) {
goTo.marker(kind + "0");
verify.completionListContains("fake-module/");
verify.completionListContains("fake-module2/");
verify.completionListContains("fake-module2");
verify.completionListContains("fake-module3/");
verify.not.completionListItemsCountIsGreaterThan(3);
@ -46,7 +46,7 @@ for (const kind of kinds) {
goTo.marker(kind + "2");
verify.completionListContains("fake-module/");
verify.completionListContains("fake-module2/");
verify.completionListContains("fake-module2");
verify.completionListContains("fake-module3/");
verify.not.completionListItemsCountIsGreaterThan(3);
}

View File

@ -0,0 +1,32 @@
/// <reference path='fourslash.ts' />
// @typeRoots: my_typings,my_other_typings
// @Filename: tests/test0.ts
//// import * as foo1 from "m/*import_as0*/
//// import foo2 = require("m/*import_equals0*/
//// var foo3 = require("m/*require0*/
// @Filename: my_typings/module-x/index.d.ts
//// export var x = 9;
// @Filename: my_typings/module-x/whatever.d.ts
//// export var w = 9;
// @Filename: my_typings/module-y/index.d.ts
//// export var y = 9;
// @Filename: my_other_typings/module-z/index.d.ts
//// export var z = 9;
const kinds = ["import_as", "import_equals", "require"];
for (const kind of kinds) {
goTo.marker(kind + "0");
verify.completionListContains("module-x");
verify.completionListContains("module-y");
verify.completionListContains("module-z");
verify.not.completionListItemsCountIsGreaterThan(3);
}

View File

@ -0,0 +1,29 @@
/// <reference path='fourslash.ts' />
// @typeRoots: my_typings,my_other_typings
// @types: module-x,module-z
// @Filename: tests/test0.ts
//// import * as foo1 from "m/*import_as0*/
//// import foo2 = require("m/*import_equals0*/
//// var foo3 = require("m/*require0*/
// @Filename: my_typings/module-x/index.d.ts
//// export var x = 9;
// @Filename: my_typings/module-y/index.d.ts
//// export var y = 9;
// @Filename: my_other_typings/module-z/index.d.ts
//// export var z = 9;
const kinds = ["import_as", "import_equals", "require"];
for (const kind of kinds) {
goTo.marker(kind + "0");
verify.completionListContains("module-x");
verify.completionListContains("module-z");
verify.not.completionListItemsCountIsGreaterThan(2);
}

View File

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts' />
// @Filename: subdirectory/test0.ts
//// import * as foo1 from "m/*import_as0*/
//// import foo2 = require("m/*import_equals0*/
//// var foo3 = require("m/*require0*/
// @Filename: subdirectory/node_modules/@types/module-x/index.d.ts
//// export var x = 9;
// @Filename: subdirectory/package.json
//// { "dependencies": { "@types/module-x": "latest" } }
// @Filename: node_modules/@types/module-y/index.d.ts
//// export var y = 9;
// @Filename: package.json
//// { "dependencies": { "@types/module-y": "latest" } }
const kinds = ["import_as", "import_equals", "require"];
for (const kind of kinds) {
goTo.marker(kind + "0");
verify.completionListContains("module-x");
verify.completionListContains("module-y");
verify.not.completionListItemsCountIsGreaterThan(2);
}