diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 7f36e326f57..4e1c44ae01c 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -832,7 +832,12 @@ namespace FourSlash { } } - public verifyCompletionsAt(markerName: string, expected: ReadonlyArray, options?: FourSlashInterface.CompletionsAtOptions) { + public verifyCompletionsAt(markerName: string | ReadonlyArray, expected: ReadonlyArray, options?: FourSlashInterface.CompletionsAtOptions) { + if (typeof markerName !== "string") { + for (const m of markerName) this.verifyCompletionsAt(m, expected, options); + return; + } + this.goToMarker(markerName); const actualCompletions = this.getCompletionListAtCaret(options); @@ -3182,9 +3187,7 @@ Actual: ${stringify(fullActual)}`); eq(item.hasAction, hasAction, "hasAction"); eq(item.isRecommended, options && options.isRecommended, "isRecommended"); eq(item.insertText, options && options.insertText, "insertText"); - if (options && options.replacementSpan) { // TODO: GH#21679 - eq(item.replacementSpan, options && options.replacementSpan && ts.createTextSpanFromRange(options.replacementSpan), "replacementSpan"); - } + eq(item.replacementSpan, options && options.replacementSpan && ts.createTextSpanFromRange(options.replacementSpan), "replacementSpan"); } private findFile(indexOrName: string | number) { @@ -3988,7 +3991,7 @@ namespace FourSlashInterface { super(state); } - public completionsAt(markerName: string, completions: ReadonlyArray, options?: CompletionsAtOptions) { + public completionsAt(markerName: string | ReadonlyArray, completions: ReadonlyArray, options?: CompletionsAtOptions) { this.state.verifyCompletionsAt(markerName, completions, options); } diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index cdf940cdc79..976449efee5 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -5,33 +5,40 @@ namespace ts.Completions.PathCompletions { readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName; } export interface PathCompletion extends NameAndKind { - readonly span: TextSpan; - } - function createPathCompletion(name: string, kind: PathCompletion["kind"], span: TextSpan): PathCompletion { - return { name, kind, span }; + readonly span: TextSpan | undefined; } - export function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): PathCompletion[] { + function nameAndKind(name: string, kind: NameAndKind["kind"]): NameAndKind { + return { name, kind }; + } + function addReplacementSpans(text: string, textStart: number, names: ReadonlyArray): ReadonlyArray { + const span = getDirectoryFragmentTextSpan(text, textStart); + return names.map(({ name, kind }): PathCompletion => ({ name, kind, span })); + } + + export function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray { + return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(node, compilerOptions, host, typeChecker)); + } + + function getStringLiteralCompletionsFromModuleNamesWorker(node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray { const literalValue = normalizeSlashes(node.text); const scriptPath = node.getSourceFile().path; const scriptDirectory = getDirectoryPath(scriptPath); - const span = getDirectoryFragmentTextSpan(node.text, node.getStart(sourceFile) + 1); if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) { const extensions = getSupportedExtensions(compilerOptions); if (compilerOptions.rootDirs) { return getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, compilerOptions, host, scriptPath); + compilerOptions.rootDirs, literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, compilerOptions, host, scriptPath); } else { - return getCompletionEntriesForDirectoryFragment( - literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, span, host, scriptPath); + return getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensions, /*includeExtensions*/ false, host, scriptPath); } } else { // Check for node modules - return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span, compilerOptions, host, typeChecker); + return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker); } } @@ -54,15 +61,15 @@ namespace ts.Completions.PathCompletions { compareStringsCaseSensitive); } - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray, includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): PathCompletion[] { + function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray, includeExtensions: boolean, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): NameAndKind[] { const basePath = compilerOptions.project || host.getCurrentDirectory(); const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptPath, ignoreCase); - const result: PathCompletion[] = []; + const result: NameAndKind[] = []; for (const baseDirectory of baseDirectories) { - getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, span, host, exclude, result); + getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensions, includeExtensions, host, exclude, result); } return result; @@ -71,7 +78,7 @@ namespace ts.Completions.PathCompletions { /** * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. */ - function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: ReadonlyArray, includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: PathCompletion[] = []): PathCompletion[] { + function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: ReadonlyArray, includeExtensions: boolean, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] { if (fragment === undefined) { fragment = ""; } @@ -120,7 +127,7 @@ namespace ts.Completions.PathCompletions { } forEachKey(foundFiles, foundFile => { - result.push(createPathCompletion(foundFile, ScriptElementKind.scriptElement, span)); + result.push(nameAndKind(foundFile, ScriptElementKind.scriptElement)); }); } @@ -131,7 +138,7 @@ namespace ts.Completions.PathCompletions { for (const directory of directories) { const directoryName = getBaseFileName(normalizePath(directory)); - result.push(createPathCompletion(directoryName, ScriptElementKind.directory, span)); + result.push(nameAndKind(directoryName, ScriptElementKind.directory)); } } } @@ -146,16 +153,16 @@ namespace ts.Completions.PathCompletions { * Modules from node_modules (i.e. those listed in package.json) * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions */ - function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): PathCompletion[] { + function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): NameAndKind[] { const { baseUrl, paths } = compilerOptions; - const result: PathCompletion[] = []; + const result: NameAndKind[] = []; const fileExtensions = getSupportedExtensions(compilerOptions); if (baseUrl) { const projectDir = compilerOptions.project || host.getCurrentDirectory(); const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(projectDir, baseUrl); - getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, span, host, /*exclude*/ undefined, result); + getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result); for (const path in paths) { const patterns = paths[path]; @@ -163,7 +170,7 @@ namespace ts.Completions.PathCompletions { for (const { name, kind } of getCompletionsForPathMapping(path, patterns, fragment, baseUrl, fileExtensions, host)) { // Path mappings may provide a duplicate way to get to something we've already added, so don't add again. if (!result.some(entry => entry.name === name)) { - result.push(createPathCompletion(name, kind, span)); + result.push(nameAndKind(name, kind)); } } } @@ -174,15 +181,15 @@ namespace ts.Completions.PathCompletions { forEachAncestorDirectory(scriptPath, ancestor => { const nodeModules = combinePaths(ancestor, "node_modules"); if (host.directoryExists(nodeModules)) { - getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, span, host, /*exclude*/ undefined, result); + getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result); } }); } - getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span, result); + getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, result); for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, compilerOptions, typeChecker, host)) { - result.push(createPathCompletion(moduleName, ScriptElementKind.externalModuleName, span)); + result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName)); } return result; @@ -296,7 +303,7 @@ namespace ts.Completions.PathCompletions { return deduplicate(nonRelativeModuleNames, equateStringsCaseSensitive, compareStringsCaseSensitive); } - export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): PathCompletion[] | undefined { + export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): ReadonlyArray | undefined { const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end); @@ -311,23 +318,13 @@ namespace ts.Completions.PathCompletions { const [, prefix, kind, toComplete] = match; const scriptPath = getDirectoryPath(sourceFile.path); - switch (kind) { - case "path": { - // Give completions for a relative path - const span = getDirectoryFragmentTextSpan(toComplete, range.pos + prefix.length); - return getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, span, host, sourceFile.path); - } - case "types": { - // Give completions based on the typings available - const span = createTextSpan(range.pos + prefix.length, match[0].length - prefix.length); - return getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, span); - } - default: - return undefined; - } + const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getSupportedExtensions(compilerOptions), /*includeExtensions*/ true, host, sourceFile.path) + : kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath) + : undefined; + return names && addReplacementSpans(toComplete, range.pos + prefix.length, names); } - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: PathCompletion[] = []): PathCompletion[] { + function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, result: NameAndKind[] = []): NameAndKind[] { // Check for typings specified in compiler options const seen = createMap(); if (options.types) { @@ -375,7 +372,7 @@ namespace ts.Completions.PathCompletions { function pushResult(moduleName: string) { if (!seen.has(moduleName)) { - result.push(createPathCompletion(moduleName, ScriptElementKind.externalModuleName, span)); + result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName)); seen.set(moduleName, true); } } @@ -445,10 +442,12 @@ namespace ts.Completions.PathCompletions { } // Replace everything after the last directory seperator that appears - function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan { - const index = text.lastIndexOf(directorySeparator); + function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined { + const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf("\\")); const offset = index !== -1 ? index + 1 : 0; - return { start: textStart + offset, length: text.length - offset }; + // If the range is an identifier, span is unnecessary. + const length = text.length - offset; + return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length); } // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) diff --git a/tests/cases/fourslash/completionForStringLiteralExport.ts b/tests/cases/fourslash/completionForStringLiteralExport.ts index 649148bb15d..9239f6173fa 100644 --- a/tests/cases/fourslash/completionForStringLiteralExport.ts +++ b/tests/cases/fourslash/completionForStringLiteralExport.ts @@ -7,7 +7,7 @@ // @Filename: test.ts //// export * from "./some/*0*/ //// export * from "./sub/some/*1*/"; -//// export * from "some-/*2*/"; +//// export * from "[|some-/*2*/|]"; //// export * from "..//*3*/"; //// export {} from ".//*4*/"; @@ -21,17 +21,7 @@ // @Filename: my_typings/some-module/index.d.ts //// export var x = 9; -goTo.marker("0"); -verify.completionListContains("someFile1"); - -goTo.marker("1"); -verify.completionListContains("someFile2"); - -goTo.marker("2"); -verify.completionListContains("some-module"); - -goTo.marker("3"); -verify.completionListContains("fourslash"); - -goTo.marker("4"); -verify.completionListContains("someFile1"); +verify.completionsAt(["0", "4"], ["someFile1", "sub", "my_typings"], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["someFile2"], { isNewIdentifierLocation: true }); +verify.completionsAt("2", [{ name: "some-module", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true }); +verify.completionsAt("3", ["fourslash"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionForStringLiteralImport1.ts b/tests/cases/fourslash/completionForStringLiteralImport1.ts index 4cdee889538..c0c20eb3df8 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport1.ts @@ -5,10 +5,10 @@ // @typeRoots: my_typings // @Filename: test.ts -//// import * as foo0 from "./[|some|]/*0*/ -//// import * as foo1 from "./sub/[|some|]/*1*/ +//// import * as foo0 from "./some/*0*/ +//// import * as foo1 from "./sub/some/*1*/ //// import * as foo2 from "[|some-|]/*2*/" -//// import * as foo3 from "../[||]/*3*/"; +//// import * as foo3 from "..//*3*/"; // @Filename: someFile1.ts @@ -20,14 +20,7 @@ // @Filename: my_typings/some-module/index.d.ts //// export var x = 9; -goTo.marker("0"); -verify.completionListContains("someFile1", undefined, undefined, undefined, 0); - -goTo.marker("1"); -verify.completionListContains("someFile2", undefined, undefined, undefined, 1); - -goTo.marker("2"); -verify.completionListContains("some-module", undefined, undefined, undefined, 2); - -goTo.marker("3"); -verify.completionListContains("fourslash", undefined, undefined, undefined, 3); \ No newline at end of file +verify.completionsAt("0", ["someFile1", "sub", "my_typings"], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["someFile2"], { isNewIdentifierLocation: true }); +verify.completionsAt("2", [{ name: "some-module", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true }); +verify.completionsAt("3", ["fourslash"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionForStringLiteralImport2.ts b/tests/cases/fourslash/completionForStringLiteralImport2.ts index 25de3d2240a..6c73becedc5 100644 --- a/tests/cases/fourslash/completionForStringLiteralImport2.ts +++ b/tests/cases/fourslash/completionForStringLiteralImport2.ts @@ -20,14 +20,7 @@ // @Filename: my_typings/some-module/index.d.ts //// export var x = 9; -goTo.marker("0"); -verify.completionListContains("someFile.ts", undefined, undefined, undefined, 0); - -goTo.marker("1"); -verify.completionListContains("some-module", undefined, undefined, undefined, 1); - -goTo.marker("2"); -verify.completionListContains("someOtherFile.ts", undefined, undefined, undefined, 2); - -goTo.marker("3"); -verify.completionListContains("some-module", undefined, undefined, undefined, 3); \ No newline at end of file +verify.completionsAt("0", ["someFile.ts", "sub", "my_typings"], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["some-module"], { isNewIdentifierLocation: true }); +verify.completionsAt("2", ["someOtherFile.ts"], { isNewIdentifierLocation: true }); +verify.completionsAt("3", ["some-module"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts index c0e5ebbcd90..ad45719caea 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport1.ts @@ -5,15 +5,15 @@ // @Filename: tests/test0.ts //// import * as foo1 from "f/*import_as0*/ //// import * as foo2 from "fake-module//*import_as1*/ -//// import * as foo3 from "fake-module/*import_as2*/ +//// import * as foo3 from "[|fake-module/*import_as2*/|] //// import foo4 = require("f/*import_equals0*/ //// import foo5 = require("fake-module//*import_equals1*/ -//// import foo6 = require("fake-module/*import_equals2*/ +//// import foo6 = require("[|fake-module/*import_equals2*/|] //// var foo7 = require("f/*require0*/ //// var foo8 = require("fake-module//*require1*/ -//// var foo9 = require("fake-module/*require2*/ +//// var foo9 = require("[|fake-module/*require2*/|] // @Filename: package.json //// { "dependencies": { "fake-module": "latest" }, "devDependencies": { "fake-module-dev": "latest" } } @@ -41,23 +41,8 @@ // @Filename: node_modules/unlisted-module/index.ts //// /*unlisted-module*/ -const kinds = ["import_as", "import_equals", "require"]; - -for (const kind of kinds) { - goTo.marker(kind + "0"); - verify.completionListContains("fake-module"); - verify.completionListContains("fake-module-dev"); - verify.not.completionListItemsCountIsGreaterThan(2); - - goTo.marker(kind + "1"); - verify.completionListContains("index"); - verify.completionListContains("ts"); - verify.completionListContains("dts"); - verify.completionListContains("tsx"); - verify.not.completionListItemsCountIsGreaterThan(4); - - goTo.marker(kind + "2"); - verify.completionListContains("fake-module"); - verify.completionListContains("fake-module-dev"); - verify.not.completionListItemsCountIsGreaterThan(2); -} \ No newline at end of file +["import_as", "import_equals", "require"].forEach((kind, i) => { + verify.completionsAt(`${kind}0`, ["fake-module", "fake-module-dev"], { isNewIdentifierLocation: true }); + verify.completionsAt(`${kind}1`, ["dts", "index", "ts", "tsx"], { isNewIdentifierLocation: true }); + verify.completionsAt(`${kind}2`, ["fake-module", "fake-module-dev"].map(name => ({ name, replacementSpan: test.ranges()[i] })), { isNewIdentifierLocation: true }); +}); diff --git a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts index 603e8d1104d..9a0b34ab7fb 100644 --- a/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts +++ b/tests/cases/fourslash/completionForStringLiteralNonrelativeImport8.ts @@ -18,17 +18,17 @@ // @Filename: tests/test0.ts -//// import * as foo1 from "0/*import_as0*/ -//// import foo2 = require("0/*import_equals0*/ -//// var foo3 = require("0/*require0*/ +//// import * as foo1 from "f/*import_as0*/ +//// import foo2 = require("f/*import_equals0*/ +//// var foo3 = require("f/*require0*/ -//// import * as foo1 from "1/*import_as1*/ -//// import foo2 = require("1/*import_equals1*/ -//// var foo3 = require("1/*require1*/ +//// import * as foo1 from "f/*import_as1*/ +//// import foo2 = require("f/*import_equals1*/ +//// var foo3 = require("f/*require1*/ -//// import * as foo1 from "2/*import_as2*/ -//// import foo2 = require("2/*import_equals2*/ -//// var foo3 = require("2/*require2*/ +//// import * as foo1 from "f/*import_as2*/ +//// import foo2 = require("f/*import_equals2*/ +//// var foo3 = require("f/*require2*/ // @Filename: modules/prefix/00test/suffix.ts @@ -40,16 +40,5 @@ // @Filename: modules/2test/suffix-only.ts //// export var z = 5; - -const kinds = ["import_as", "import_equals", "require"]; - -for (const kind of kinds) { - goTo.marker(kind + "0"); - verify.completionListContains("0test"); - - goTo.marker(kind + "1"); - verify.completionListContains("1test"); - - goTo.marker(kind + "2"); - verify.completionListContains("2test"); -} +const markers = ts.flatMap(["import_as", "import_equals", "require"], a => [`${a}0`, `${a}1`, `${a}2`]); +verify.completionsAt(markers, ["prefix", "prefix-only", "2test", "0test", "1test"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionForStringLiteralWithDynamicImport.ts b/tests/cases/fourslash/completionForStringLiteralWithDynamicImport.ts index 8e823991c0b..3343eb38749 100644 --- a/tests/cases/fourslash/completionForStringLiteralWithDynamicImport.ts +++ b/tests/cases/fourslash/completionForStringLiteralWithDynamicImport.ts @@ -7,7 +7,7 @@ // @Filename: test.ts //// const a = import("./some/*0*/ //// const a = import("./sub/some/*1*/"); -//// const a = import("some-/*2*/"); +//// const a = import("[|some-/*2*/|]"); //// const a = import("..//*3*/"); @@ -20,14 +20,7 @@ // @Filename: my_typings/some-module/index.d.ts //// export var x = 9; -goTo.marker("0"); -verify.completionListContains("someFile1"); - -goTo.marker("1"); -verify.completionListContains("someFile2"); - -goTo.marker("2"); -verify.completionListContains("some-module"); - -goTo.marker("3"); -verify.completionListContains("fourslash"); +verify.completionsAt("0", ["someFile1", "sub", "my_typings"], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["someFile2"], { isNewIdentifierLocation: true }); +verify.completionsAt("2", [{ name: "some-module", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true }); +verify.completionsAt("3", ["fourslash"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInImportClause05.ts b/tests/cases/fourslash/completionListInImportClause05.ts index 2b5d66621dd..40eda68bb82 100644 --- a/tests/cases/fourslash/completionListInImportClause05.ts +++ b/tests/cases/fourslash/completionListInImportClause05.ts @@ -1,7 +1,7 @@ /// // @Filename: app.ts -////import * as A from "[|/*1*/|]"; +////import * as A from "/*1*/"; // @Filename: /node_modules/@types/a__b/index.d.ts ////declare module "@e/f" { function fun(): string; } @@ -12,9 +12,4 @@ // NOTE: The node_modules folder is in "/", rather than ".", because it requires // less scaffolding to mock. In particular, "/" is where we look for type roots. -const [replacementSpan] = test.ranges(); -verify.completionsAt("1", [ - { name: "@a/b", replacementSpan }, - { name: "@c/d", replacementSpan }, - { name: "@e/f", replacementSpan }, -], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["@a/b", "@c/d", "@e/f"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInImportClause06.ts b/tests/cases/fourslash/completionListInImportClause06.ts index 9336803f7f9..3dfcea3cdbf 100644 --- a/tests/cases/fourslash/completionListInImportClause06.ts +++ b/tests/cases/fourslash/completionListInImportClause06.ts @@ -3,7 +3,7 @@ // @typeRoots: T1,T2 // @Filename: app.ts -////import * as A from "[|/*1*/|]"; +////import * as A from "/*1*/"; // @Filename: T1/a__b/index.d.ts ////export declare let x: number; @@ -12,6 +12,4 @@ ////export declare let x: number; // Confirm that entries are de-dup'd. -verify.completionsAt("1", [ - { name: "@a/b", replacementSpan: test.ranges()[0] }, -], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["@a/b"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionsPaths.ts b/tests/cases/fourslash/completionsPaths.ts index 9b485d60ac3..47e4918b410 100644 --- a/tests/cases/fourslash/completionsPaths.ts +++ b/tests/cases/fourslash/completionsPaths.ts @@ -12,20 +12,17 @@ ////not read // @Filename: /src/a.ts -////import {} from "[|/*1*/|]"; +////import {} from "/*1*/"; // @Filename: /src/folder/b.ts -////import {} from "x/[|/*2*/|]"; +////import {} from "x//*2*/"; // @Filename: /src/folder/c.ts -////const foo = require("x/[|/*3*/|]"); +////const foo = require("x//*3*/"); // @Filename: /src/folder/4.ts -////const foo = require(`x/[|/*4*/|]`); +////const foo = require(`x//*4*/`); const [r0, r1, r2, r3] = test.ranges(); -const options = { isNewIdentifierLocation: true }; -verify.completionsAt("1", [{ name: "y", replacementSpan: r0 }, { name: "x", replacementSpan: r0 }], options); -verify.completionsAt("2", [{ name: "bar", replacementSpan: r1 }, { name: "foo", replacementSpan: r1 }], options); -verify.completionsAt("3", [{ name: "bar", replacementSpan: r2 }, { name: "foo", replacementSpan: r2 }], options); -verify.completionsAt("4", [{ name: "bar", replacementSpan: r3 }, { name: "foo", replacementSpan: r3 }], options); +verify.completionsAt("1", ["y", "x"], { isNewIdentifierLocation: true }); +verify.completionsAt(["2", "3", "4"], ["bar", "foo"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionsPaths_pathMapping.ts b/tests/cases/fourslash/completionsPaths_pathMapping.ts index 200c6d25569..c6c879dc97e 100644 --- a/tests/cases/fourslash/completionsPaths_pathMapping.ts +++ b/tests/cases/fourslash/completionsPaths_pathMapping.ts @@ -7,8 +7,8 @@ /////export const x = 0; // @Filename: /src/a.ts -////import {} from "foo/[|/*0*/|]"; -////import {} from "foo/dir/[|/*1*/|]"; +////import {} from "foo//*0*/"; +////import {} from "foo/dir//*1*/"; // @Filename: /tsconfig.json ////{ @@ -21,5 +21,5 @@ ////} const [r0, r1] = test.ranges(); -verify.completionsAt("0", ["a", "b", "dir"].map(name => ({ name, replacementSpan: r0 })), { isNewIdentifierLocation: true }); -verify.completionsAt("1", ["x"].map(name => ({ name, replacementSpan: r1 })), { isNewIdentifierLocation: true }); +verify.completionsAt("0", ["a", "b", "dir"], { isNewIdentifierLocation: true }); +verify.completionsAt("1", ["x"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionsPaths_pathMapping_parentDirectory.ts b/tests/cases/fourslash/completionsPaths_pathMapping_parentDirectory.ts index 6736256cc0f..71d07052318 100644 --- a/tests/cases/fourslash/completionsPaths_pathMapping_parentDirectory.ts +++ b/tests/cases/fourslash/completionsPaths_pathMapping_parentDirectory.ts @@ -1,7 +1,7 @@ /// // @Filename: /src/a.ts -////import { } from "foo/[|/**/|]"; +////import { } from "foo//**/"; // @Filename: /oof/x.ts ////export const x = 0; @@ -16,5 +16,4 @@ //// } ////} -const [replacementSpan] = test.ranges(); -verify.completionsAt("", [{ name: "x", replacementSpan }], { isNewIdentifierLocation: true }); +verify.completionsAt("", ["x"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionsPaths_pathMapping_relativePath.ts b/tests/cases/fourslash/completionsPaths_pathMapping_relativePath.ts index 659d3352755..a9af29cd6e7 100644 --- a/tests/cases/fourslash/completionsPaths_pathMapping_relativePath.ts +++ b/tests/cases/fourslash/completionsPaths_pathMapping_relativePath.ts @@ -7,7 +7,7 @@ ////export const x = 0; // @Filename: /x/a.ts -////import { } from "foo/[|/**/|]"; +////import { } from "foo//**/"; // @Filename: /x/tsconfig.json ////{ @@ -19,5 +19,4 @@ //// } ////} -const [replacementSpan] = test.ranges(); -verify.completionsAt("", ["a", "b"].map(name => ({ name, replacementSpan })), { isNewIdentifierLocation: true }); +verify.completionsAt("", ["a", "b"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionsPaths_pathMapping_topLevel.ts b/tests/cases/fourslash/completionsPaths_pathMapping_topLevel.ts index b99d6be4ca9..449ff3f65bd 100644 --- a/tests/cases/fourslash/completionsPaths_pathMapping_topLevel.ts +++ b/tests/cases/fourslash/completionsPaths_pathMapping_topLevel.ts @@ -1,7 +1,7 @@ /// // @Filename: /x/src/a.ts -////import {} from "[|/**/|]"; +////import {} from "/**/"; // @Filename: /x/tsconfig.json ////{ @@ -13,5 +13,4 @@ //// } ////} -const [replacementSpan] = test.ranges(); -verify.completionsAt("", ["src", "foo/"].map(name => ({ name, replacementSpan })), { isNewIdentifierLocation: true }); +verify.completionsAt("", ["src", "foo/"], { isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 14d89cc418f..60ccbf8963a 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -71,6 +71,10 @@ declare module ts { } } +declare namespace ts { + function flatMap(array: ReadonlyArray, mapfn: (x: T, i: number) => U | ReadonlyArray | undefined): U[]; +} + declare namespace FourSlashInterface { interface Marker { fileName: string; @@ -196,7 +200,7 @@ declare namespace FourSlashInterface { class verify extends verifyNegatable { assertHasRanges(ranges: Range[]): void; caretAtMarker(markerName?: string): void; - completionsAt(markerName: string, completions: ReadonlyArray, options?: CompletionsAtOptions): void; + completionsAt(markerName: string | ReadonlyArray, completions: ReadonlyArray, options?: CompletionsAtOptions): void; completionsAndDetailsAt( markerName: string, completions: { diff --git a/tests/cases/fourslash/tripleSlashRefPathCompletionBackandForwardSlash.ts b/tests/cases/fourslash/tripleSlashRefPathCompletionBackandForwardSlash.ts index 2ec394e1dc8..e25521ed812 100644 --- a/tests/cases/fourslash/tripleSlashRefPathCompletionBackandForwardSlash.ts +++ b/tests/cases/fourslash/tripleSlashRefPathCompletionBackandForwardSlash.ts @@ -15,37 +15,37 @@ //// /// ({ name, replacementSpan: test.ranges()[offset / 4] })), { isNewIdentifierLocation: true }); } diff --git a/tests/cases/fourslash/tripleSlashRefPathCompletionHiddenFile.ts b/tests/cases/fourslash/tripleSlashRefPathCompletionHiddenFile.ts index fffd310bfeb..2f5792b580d 100644 --- a/tests/cases/fourslash/tripleSlashRefPathCompletionHiddenFile.ts +++ b/tests/cases/fourslash/tripleSlashRefPathCompletionHiddenFile.ts @@ -9,12 +9,9 @@ // @Filename: test.ts //// /// ({ name, replacementSpan: test.ranges()[idx] })), { isNewIdentifierLocation: true }); +} diff --git a/tests/cases/fourslash/tripleSlashRefPathCompletionRelativePaths.ts b/tests/cases/fourslash/tripleSlashRefPathCompletionRelativePaths.ts index ba6af8735f8..a67310d0349 100644 --- a/tests/cases/fourslash/tripleSlashRefPathCompletionRelativePaths.ts +++ b/tests/cases/fourslash/tripleSlashRefPathCompletionRelativePaths.ts @@ -17,8 +17,8 @@ // @Filename: d1/d2/test.ts //// /// ({ name, replacementSpan: test.ranges()[0] })), { isNewIdentifierLocation: true }); +verify.completionsAt("3", ["h.ts", "d3"].map(name => ({ name, replacementSpan: test.ranges()[1] })), { isNewIdentifierLocation: true }); -function workingDirCompletions() { - for (let m = 0; m < 5; ++m) { - goTo.marker("" + m); - verify.completionListContains("h.ts"); - verify.completionListContains("d3"); - verify.not.completionListItemsCountIsGreaterThan(2); - } -} +// parent dir completions +verify.completionsAt(["5", "6"], ["g.ts", "d2"], { isNewIdentifierLocation: true }); +verify.completionsAt("7", ["f.ts", "d1"], { isNewIdentifierLocation: true }); -function parentDirCompletions() { - - for (let m of ["5", "6"]) { - goTo.marker(m); - verify.completionListContains("g.ts"); - verify.completionListContains("d2"); - verify.not.completionListItemsCountIsGreaterThan(2); - } - - goTo.marker("7"); - verify.completionListContains("f.ts"); - verify.completionListContains("d1"); - verify.not.completionListItemsCountIsGreaterThan(2); -} - -function childDirCompletions() { - - for (let m of ["8", "9"]) { - goTo.marker(m); - verify.completionListContains("i.ts"); - verify.completionListContains("d4"); - verify.not.completionListItemsCountIsGreaterThan(2); - } - - for (let m of ["10", "11"]) { - goTo.marker(m); - verify.completionListContains("j.ts"); - verify.not.completionListItemsCountIsGreaterThan(1); - } -} \ No newline at end of file +// child dir completions +verify.completionsAt(["8", "9"], ["i.ts", "d4"], { isNewIdentifierLocation: true }); +verify.completionsAt(["10", "11"], ["j.ts"], { isNewIdentifierLocation: true });