diff --git a/src/services/services.ts b/src/services/services.ts index 66b894bb428..e8ff3847804 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2069,7 +2069,7 @@ namespace ts { * for completions. * For example, this matches /// combinePaths(rootDirectory, relativeDirectory))); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan): ImportCompletionEntry[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string): ImportCompletionEntry[] { const basePath = program.getCompilerOptions().project || host.getCurrentDirectory(); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); - const baseDirectories = getBaseDirectoriesFromRootDirs( - rootDirs, basePath, scriptPath, host.useCaseSensitiveFileNames && !host.useCaseSensitiveFileNames()); const result: ImportCompletionEntry[] = []; for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, result); + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, exclude, result); } return result; } - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { - const toComplete = getBaseFileName(fragment); - const absolutePath = normalizeSlashes(host.resolvePath(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment))); - const baseDirectory = toComplete ? getDirectoryPath(absolutePath) : absolutePath; + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: string[], includeExtensions: boolean, span: TextSpan, exclude?: string, result: ImportCompletionEntry[] = []): ImportCompletionEntry[] { + fragment = getDirectoryPath(fragment); + if (!fragment) { + fragment = "./" + } + else { + fragment = ensureTrailingDirectorySeparator(fragment); + } + + const absolutePath = normalizeAndPreserveTrailingSlash(isRootedDiskPath(fragment) ? fragment : combinePaths(scriptPath, fragment)); + const baseDirectory = host.resolvePath(getDirectoryPath(absolutePath)); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); if (directoryProbablyExists(baseDirectory, host)) { // Enumerate the available files const files = host.readDirectory(baseDirectory, extensions, /*exclude*/undefined, /*include*/["./*"]); - forEach(files, f => { - const fileName = includeExtensions ? getBaseFileName(f) : removeFileExtension(getBaseFileName(f)); + forEach(files, filePath => { + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + return false; + } - const duplicate = includeExtensions ? false : forEach(result, entry => entry.name === fileName); + const fileName = includeExtensions ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath)); + const duplicate = !includeExtensions && forEach(result, entry => entry.name === fileName); - if (startsWith(fileName, toComplete) && !duplicate) { + if (!duplicate) { result.push({ name: fileName, kind: ScriptElementKind.directory, @@ -4605,14 +4612,12 @@ namespace ts { forEach(directories, d => { const directoryName = getBaseFileName(removeTrailingDirectorySeparator(d)); - if (startsWith(directoryName, toComplete)) { - result.push({ - name: ensureTrailingDirectorySeparator(directoryName), - kind: ScriptElementKind.directory, - sortText: directoryName, - span - }); - } + result.push({ + name: ensureTrailingDirectorySeparator(directoryName), + kind: ScriptElementKind.directory, + sortText: directoryName, + span + }); }); } } @@ -4679,18 +4684,17 @@ namespace ts { if (parsed) { // The prefix has two effective parts: the directory path and the base component after the filepath that is not a // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = hasTrailingDirectorySeparator(parsed.prefix) ? - ensureTrailingDirectorySeparator(normalizePath(parsed.prefix)) : normalizePath(parsed.prefix); + const normalizedPrefix = normalizeAndPreserveTrailingSlash(parsed.prefix); const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); const normalizedPrefixBase = getBaseFileName(normalizedPrefix); const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1; // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDir = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; const normalizedSuffix = normalizePath(parsed.suffix); - const baseDirectory = combinePaths(baseUrl, expandedPrefixDir); + const baseDirectory = combinePaths(baseUrl, expandedPrefixDirectory); const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; // If we have a suffix, then we need to read the directory all the way down. We could create a glob @@ -4720,17 +4724,8 @@ namespace ts { } function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions): string[] { - const trailingSeperator = hasTrailingDirectorySeparator(fragment); - fragment = normalizePath(fragment); - - if (trailingSeperator) { - fragment = ensureTrailingDirectorySeparator(fragment); - } - - // If this is a nested module, get the module name - const firstSeparator = fragment.indexOf(directorySeparator); - const moduleNameFragment = firstSeparator !== -1 ? fragment.substr(0, firstSeparator) : fragment; - const isNestedModule = fragment !== moduleNameFragment; + // Check If this is a nested module + const isNestedModule = fragment.indexOf(directorySeparator) !== -1 ; // Get modules that the type checker picked up const ambientModules = ts.map(program.getTypeChecker().getAmbientModules(), sym => stripQuotes(sym.name)); @@ -4749,7 +4744,7 @@ namespace ts { }); if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { - forEach(enumerateNodeModulesVisibleToScript(host, scriptPath, moduleNameFragment), visibleModule => { + forEach(enumerateNodeModulesVisibleToScript(host, scriptPath), visibleModule => { if (!isNestedModule) { nonRelativeModules.push(visibleModule.canBeImported ? visibleModule.moduleName : ensureTrailingDirectorySeparator(visibleModule.moduleName)); } @@ -4796,7 +4791,7 @@ namespace ts { if (kind === "path") { // Give completions for a relative path const span: TextSpan = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); - return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span); + return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/true, span, sourceFile.path); } else { // Give completions based on the typings available @@ -4876,7 +4871,7 @@ namespace ts { } - function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) { + function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string) { const result: VisibleModuleInfo[] = []; findPackageJsons(scriptPath).forEach((packageJson) => { const package = tryReadingPackageJson(packageJson); @@ -4888,10 +4883,10 @@ namespace ts { const foundModuleNames: string[] = []; if (package.dependencies) { - addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames); + addPotentialPackageNames(package.dependencies, foundModuleNames); } if (package.devDependencies) { - addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames); + addPotentialPackageNames(package.devDependencies, foundModuleNames); } forEach(foundModuleNames, (moduleName) => { @@ -4916,9 +4911,10 @@ namespace ts { } } - function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) { + function addPotentialPackageNames(dependencies: any, result: string[]) { for (const dep in dependencies) { - if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) { + if (dependencies.hasOwnProperty(dep) && !startsWith(dep, "@types") && + dep.charCodeAt(6) !== CharacterCodes.slash && dep.charCodeAt(6) !== CharacterCodes.backslash) { result.push(dep); } } @@ -4961,6 +4957,20 @@ namespace ts { const offset = index !== -1 ? index + 1 : 0; return { start: textStart + offset, length: text.length - offset } } + + // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) + function isPathRelativeToScript(path: string) { + if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { + const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; + const slashCharCode = path.charCodeAt(slashIndex); + return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; + } + return false; + } + + function normalizeAndPreserveTrailingSlash(path: string) { + return hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalizePath(path)) : normalizePath(path); + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts index 28170754456..f4a9ae2d537 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport4.ts @@ -2,16 +2,8 @@ // @Filename: dir1/dir2/dir3/dir4/test0.ts //// import * as foo1 from "f/*import_as0*/ -//// import * as foo2 from "a/*import_as1*/ -//// import * as foo3 from "fake-module/*import_as2*/ - //// import foo4 = require("f/*import_equals0*/ -//// import foo5 = require("a/*import_equals1*/ -//// import foo6 = require("fake-module/*import_equals2*/ - //// var foo7 = require("f/*require0*/ -//// var foo8 = require("a/*require1*/ -//// var foo9 = require("fake-module/*require2*/ // @Filename: package.json //// { "dependencies": { "fake-module": "latest" } } @@ -21,7 +13,7 @@ // @Filename: dir1/package.json //// { "dependencies": { "fake-module2": "latest" } } // @Filename: dir1/node_modules/fake-module2/index.ts -//// declare module "ambient-module-test" {} +//// /*module2*/ // @Filename: dir1/dir2/dir3/package.json //// { "dependencies": { "fake-module3": "latest" } } @@ -37,16 +29,4 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("fake-module2"); verify.importModuleCompletionListContains("fake-module3/"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); - - goTo.marker(kind + "1"); - - verify.importModuleCompletionListContains("ambient-module-test"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); - - goTo.marker(kind + "2"); - - verify.importModuleCompletionListContains("fake-module/"); - verify.importModuleCompletionListContains("fake-module2"); - verify.importModuleCompletionListContains("fake-module3/"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts index f3607d70c28..52d62f3b4f8 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport1.ts @@ -3,28 +3,21 @@ // @Filename: test0.ts //// import * as foo1 from "./*import_as0*/ //// import * as foo2 from ".//*import_as1*/ -//// import * as foo3 from "./f/*import_as2*/ -//// import * as foo4 from "./folder//*import_as3*/ -//// import * as foo5 from "./folder/h/*import_as4*/ +//// import * as foo4 from "./folder//*import_as2*/ //// import foo6 = require("./*import_equals0*/ //// import foo7 = require(".//*import_equals1*/ -//// import foo8 = require("./f/*import_equals2*/ -//// import foo9 = require("./folder//*import_equals3*/ -//// import foo10 = require("./folder/h/*import_equals4*/ +//// import foo9 = require("./folder//*import_equals2*/ //// var foo11 = require("./*require0*/ //// var foo12 = require(".//*require1*/ -//// var foo13 = require("./f/*require2*/ -//// var foo14 = require("./folder//*require3*/ -//// var foo15 = require("./folder/h/*require4*/ +//// var foo14 = require("./folder//*require2*/ // @Filename: parentTest/sub/test5.ts -//// import * as foo16 from "../g/*import_as5*/ +//// import * as foo16 from "../g/*import_as3*/ +//// import foo17 = require("../g/*import_equals3*/ +//// var foo18 = require("../g/*require3*/ -//// import foo17 = require("../g/*import_equals5*/ - -//// var foo18 = require("../g/*require5*/ // @Filename: f1.ts //// /*f1*/ @@ -40,11 +33,11 @@ //// /*f4*/ // @Filename: e1.ts //// /*e1*/ -// @Filename: folder/f1.ts +// @Filename: folder/f3.ts //// /*subf1*/ // @Filename: folder/h1.ts //// /*subh1*/ -// @Filename: parentTest/f1.ts +// @Filename: parentTest/f4.ts //// /*parentf1*/ // @Filename: parentTest/g1.ts //// /*parentg1*/ @@ -58,27 +51,18 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("f1"); verify.importModuleCompletionListContains("f2"); verify.importModuleCompletionListContains("e1"); - verify.importModuleCompletionListContains("test0"); verify.importModuleCompletionListContains("folder/"); verify.importModuleCompletionListContains("parentTest/"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); goTo.marker(kind + "2"); - verify.importModuleCompletionListContains("f1"); - verify.importModuleCompletionListContains("f2"); - verify.importModuleCompletionListContains("folder/"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); - - goTo.marker(kind + "3"); - verify.importModuleCompletionListContains("f1"); + verify.importModuleCompletionListContains("f3"); verify.importModuleCompletionListContains("h1"); verify.not.importModuleCompletionListItemsCountIsGreaterThan(2); - goTo.marker(kind + "4"); - verify.importModuleCompletionListContains("h1"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); - - goTo.marker(kind + "5"); + goTo.marker(kind + "3"); + verify.importModuleCompletionListContains("f4"); verify.importModuleCompletionListContains("g1"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(1); + verify.importModuleCompletionListContains("sub/"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(3); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts index eeb24d591f0..72b28d39b87 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport2.ts @@ -37,13 +37,14 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("f4"); verify.importModuleCompletionListContains("e1"); verify.importModuleCompletionListContains("e2"); - verify.importModuleCompletionListContains("test0"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(7); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); goTo.marker(kind + "1"); verify.importModuleCompletionListContains("f1"); verify.importModuleCompletionListContains("f2"); verify.importModuleCompletionListContains("f3"); verify.importModuleCompletionListContains("f4"); - verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); + verify.importModuleCompletionListContains("e1"); + verify.importModuleCompletionListContains("e2"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts index 5f4d7bb7fc0..773c4d90d75 100644 --- a/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts +++ b/tests/cases/fourslash/completionForStringLiteralRelativeImport4.ts @@ -44,5 +44,7 @@ for (const kind of kinds) { verify.importModuleCompletionListContains("module1"); verify.importModuleCompletionListContains("module2"); verify.importModuleCompletionListContains("more/"); + + // Should not contain itself verify.not.importModuleCompletionListItemsCountIsGreaterThan(4); } \ No newline at end of file diff --git a/tests/cases/fourslash/completionForTripleSlashReference1.ts b/tests/cases/fourslash/completionForTripleSlashReference1.ts index 284d5ff66a4..8c6188ac90c 100644 --- a/tests/cases/fourslash/completionForTripleSlashReference1.ts +++ b/tests/cases/fourslash/completionForTripleSlashReference1.ts @@ -1,22 +1,15 @@ /// // @Filename: test0.ts -//// /// +//// /// - -// @Filename: test3.ts -//// /// // @Filename: f1.ts -//// /f1*/ +//// /*f1*/ // @Filename: f1.js //// /*f1j*/ // @Filename: f1.d.ts //// /*f1d*/ // @Filename: f2.tsx -//// /*f2*/ -// @Filename: f3.js -//// /*f3*/ +//// /f2*/ // @Filename: f4.jsx //// /*f4*/ -// @Filename: e1.ts -//// /*e1*/ -// @Filename: e2.js -//// /*e2*/ -goTo.marker("0"); -verify.importModuleCompletionListContains("f1.ts"); -verify.importModuleCompletionListContains("f1.js"); -verify.importModuleCompletionListContains("f1.d.ts"); -verify.importModuleCompletionListContains("f2.tsx"); -verify.importModuleCompletionListContains("f3.js"); -verify.importModuleCompletionListContains("f4.jsx"); -verify.importModuleCompletionListContains("e1.ts"); -verify.importModuleCompletionListContains("e2.js"); -verify.importModuleCompletionListContains("test0.ts"); -verify.importModuleCompletionListContains("test1.ts"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(10); - -goTo.marker("1"); -verify.importModuleCompletionListContains("f1.ts"); -verify.importModuleCompletionListContains("f1.js"); -verify.importModuleCompletionListContains("f1.d.ts"); -verify.importModuleCompletionListContains("f2.tsx"); -verify.importModuleCompletionListContains("f3.js"); -verify.importModuleCompletionListContains("f4.jsx"); -verify.not.importModuleCompletionListItemsCountIsGreaterThan(6); \ No newline at end of file +for (let i = 0; i < 5; i++) { + goTo.marker("" + i); + verify.importModuleCompletionListContains("f1.ts"); + verify.importModuleCompletionListContains("f1.js"); + verify.importModuleCompletionListContains("f1.d.ts"); + verify.importModuleCompletionListContains("f2.tsx"); + verify.importModuleCompletionListContains("f4.jsx"); + verify.not.importModuleCompletionListItemsCountIsGreaterThan(5); +} \ No newline at end of file