Always test replacementSpan, and don't provide if it would just be an identifier (#22918)

This commit is contained in:
Andy 2018-03-27 15:15:50 -07:00 committed by GitHub
parent 0a2c160dd1
commit 6ef4d7774a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 152 additions and 258 deletions

View File

@ -832,7 +832,12 @@ namespace FourSlash {
}
}
public verifyCompletionsAt(markerName: string, expected: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>, options?: FourSlashInterface.CompletionsAtOptions) {
public verifyCompletionsAt(markerName: string | ReadonlyArray<string>, expected: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>, 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<ExpectedCompletionEntry>, options?: CompletionsAtOptions) {
public completionsAt(markerName: string | ReadonlyArray<string>, completions: ReadonlyArray<ExpectedCompletionEntry>, options?: CompletionsAtOptions) {
this.state.verifyCompletionsAt(markerName, completions, options);
}

View File

@ -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<NameAndKind>): ReadonlyArray<PathCompletion> {
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<PathCompletion> {
return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(node, compilerOptions, host, typeChecker));
}
function getStringLiteralCompletionsFromModuleNamesWorker(node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker): ReadonlyArray<NameAndKind> {
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<string>, includeExtensions: boolean, span: TextSpan, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude?: string): PathCompletion[] {
function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, 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<string>, includeExtensions: boolean, span: TextSpan, host: LanguageServiceHost, exclude?: string, result: PathCompletion[] = []): PathCompletion[] {
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, extensions: ReadonlyArray<string>, 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<PathCompletion> | 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<true>();
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 ..)

View File

@ -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 });

View File

@ -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);
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 });

View File

@ -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);
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 });

View File

@ -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);
}
["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 });
});

View File

@ -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 });

View File

@ -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 });

View File

@ -1,7 +1,7 @@
/// <reference path='fourslash.ts' />
// @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 });

View File

@ -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 });

View File

@ -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 });

View File

@ -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 });

View File

@ -1,7 +1,7 @@
/// <reference path="fourslash.ts" />
// @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 });

View File

@ -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 });

View File

@ -1,7 +1,7 @@
/// <reference path="fourslash.ts" />
// @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 });

View File

@ -71,6 +71,10 @@ declare module ts {
}
}
declare namespace ts {
function flatMap<T, U>(array: ReadonlyArray<T>, mapfn: (x: T, i: number) => U | ReadonlyArray<U> | 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<string | { name: string, insertText?: string, replacementSpan?: Range }>, options?: CompletionsAtOptions): void;
completionsAt(markerName: string | ReadonlyArray<string>, completions: ReadonlyArray<string | { name: string, insertText?: string, replacementSpan?: Range }>, options?: CompletionsAtOptions): void;
completionsAndDetailsAt(
markerName: string,
completions: {

View File

@ -15,37 +15,37 @@
//// /// <reference path="/*0*/
//// /// <reference path=".//*1*/
//// /// <reference path=".\/*2*/
//// /// <reference path="./*3*/
//// /// <reference path="[|./*3*/|]
//// /// <reference path="d1//*4*/
//// /// <reference path="d1/.//*5*/
//// /// <reference path="d1/.\/*6*/
//// /// <reference path="d1/./*7*/
//// /// <reference path="d1/[|./*7*/|]
//// /// <reference path="d1\/*8*/
//// /// <reference path="d1\.//*9*/
//// /// <reference path="d1\.\/*10*/
//// /// <reference path="d1\./*11*/
//// /// <reference path="d1\[|./*11*/|]
//// /// <reference path="d1/d2//*12*/
//// /// <reference path="d1/d2/.//*13*/
//// /// <reference path="d1/d2/.\/*14*/
//// /// <reference path="d1/d2/./*15*/
//// /// <reference path="d1/d2/[|./*15*/|]
//// /// <reference path="d1/d2\/*16*/
//// /// <reference path="d1/d2\.//*17*/
//// /// <reference path="d1/d2\.\/*18*/
//// /// <reference path="d1/d2\./*19*/
//// /// <reference path="d1/d2\[|./*19*/|]
//// /// <reference path="d1\d2//*20*/
//// /// <reference path="d1\d2/.//*21*/
//// /// <reference path="d1\d2/.\/*22*/
//// /// <reference path="d1\d2/./*23*/
//// /// <reference path="d1\d2/[|./*23*/|]
//// /// <reference path="d1\d2\/*24*/
//// /// <reference path="d1\d2\.//*25*/
//// /// <reference path="d1\d2\.\/*26*/
//// /// <reference path="d1\d2\./*27*/
//// /// <reference path="d1\d2\[|./*27*/|]
testBlock(0, 'f.ts', "d1");
testBlock(4, 'g.ts', "d2");
@ -54,17 +54,10 @@ testBlock(12, 'h.ts', "d3");
testBlock(16, 'h.ts', "d3");
testBlock(20, 'h.ts', "d3");
testBlock(24, 'h.ts', "d3");
goTo.marker("28");
verify.completionListContains("g.ts");
verify.completionListContains("d2");
verify.not.completionListItemsCountIsGreaterThan(2);
verify.completionsAt("28", ["g.ts", "d2"], { isNewIdentifierLocation: true });
function testBlock(offset: number, fileName: string, dir: string) {
for (let m = offset; m < offset + 4; ++m) {
goTo.marker("" + m);
verify.completionListContains(fileName);
verify.completionListContains(dir);
verify.not.completionListItemsCountIsGreaterThan(2);
}
const names = [fileName, dir];
verify.completionsAt([offset, offset + 1, offset + 2].map(String), names, { isNewIdentifierLocation: true });
verify.completionsAt(String(offset + 3), names.map(name => ({ name, replacementSpan: test.ranges()[offset / 4] })), { isNewIdentifierLocation: true });
}

View File

@ -9,12 +9,9 @@
// @Filename: test.ts
//// /// <reference path="/*0*/
//// /// <reference path="./*1*/
//// /// <reference path="[|./*1*/|]
//// /// <reference path=".//*2*/
//// /// <reference path=".\/*3*/
for(let m of ["0", "1", "2", "3"]) {
goTo.marker(m);
verify.completionListContains("f.ts");
verify.not.completionListItemsCountIsGreaterThan(1);
}
verify.completionsAt(["0", "2", "3"], ["f.ts"], { isNewIdentifierLocation: true });
verify.completionsAt("1", [{ name: "f.ts", replacementSpan: test.ranges()[0] }], { isNewIdentifierLocation: true });

View File

@ -17,18 +17,17 @@
//// /// <reference path="./d/*2*/
//// /// <reference path="f1/*3*/
//// /// <reference path="f1./*4*/
//// /// <reference path="f1.t/*5*/
//// /// <reference path="f1.ts/*6*/
//// /// <reference path="[|f1./*4*/|]
//// /// <reference path="[|f1.t/*5*/|]
//// /// <reference path="[|f1.ts/*6*/|]
//// /// <reference path="./f1/*7*/
//// /// <reference path="./f1./*8*/
//// /// <reference path="./f1.t/*9*/
//// /// <reference path="./f1.ts/*10*/
//// /// <reference path="./[|f1./*8*/|]
//// /// <reference path="./[|f1.t/*9*/|]
//// /// <reference path="./[|f1.ts/*10*/|]
const markersWithReplacementSpan = [4, 5, 6, 8, 9, 10];
for (let m = 0; m < 11; ++m) {
goTo.marker("" + m);
verify.completionListContains("f1.ts");
verify.completionListContains("f2.ts");
verify.completionListContains("d");
verify.not.completionListItemsCountIsGreaterThan(3);
}
const idx = markersWithReplacementSpan.indexOf(m);
const names = ["f1.ts", "f2.ts", "d"];
verify.completionsAt(String(m), idx === -1 ? names : names.map(name => ({ name, replacementSpan: test.ranges()[idx] })), { isNewIdentifierLocation: true });
}

View File

@ -17,8 +17,8 @@
// @Filename: d1/d2/test.ts
//// /// <reference path="/*0*/
//// /// <reference path=".//*1*/
//// /// <reference path="./*2*/
//// /// <reference path="../*3*/
//// /// <reference path="[|./*2*/|]
//// /// <reference path="[|../*3*/|]
//// /// <reference path="d3/*4*/
//// /// <reference path="..//*5*/
@ -32,46 +32,15 @@
//// /// <reference path="d3/d4//*10*/
//// /// <reference path="./d3/d4//*11*/
workingDirCompletions();
parentDirCompletions();
childDirCompletions();
// working dir completions
verify.completionsAt(["0", "1", "4"], ["h.ts", "d3"], { isNewIdentifierLocation: true });
verify.completionsAt("2", ["h.ts", "d3"].map(name => ({ 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);
}
}
// child dir completions
verify.completionsAt(["8", "9"], ["i.ts", "d4"], { isNewIdentifierLocation: true });
verify.completionsAt(["10", "11"], ["j.ts"], { isNewIdentifierLocation: true });