Skip to content

Commit 7261866

Browse files
committed
Cleaning up the completion code and tests
1 parent b9b79af commit 7261866

27 files changed

+206
-283
lines changed

src/harness/fourslash.ts

Lines changed: 47 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -598,38 +598,6 @@ namespace FourSlash {
598598
}
599599
}
600600

601-
public verifyImportModuleCompletionListItemsCountIsGreaterThan(count: number, negative: boolean) {
602-
const completions = this.getImportModuleCompletionListAtCaret();
603-
const itemsCount = completions.entries.length;
604-
605-
if (negative) {
606-
if (itemsCount > count) {
607-
this.raiseError(`Expected import module completion list items count to not be greater than ${count}, but is actually ${itemsCount}`);
608-
}
609-
}
610-
else {
611-
if (itemsCount <= count) {
612-
this.raiseError(`Expected import module completion list items count to be greater than ${count}, but is actually ${itemsCount}`);
613-
}
614-
}
615-
}
616-
617-
public verifyImportModuleCompletionListIsEmpty(negative: boolean) {
618-
const completions = this.getImportModuleCompletionListAtCaret();
619-
if ((!completions || completions.entries.length === 0) && negative) {
620-
this.raiseError("Completion list is empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition);
621-
}
622-
else if (completions && completions.entries.length !== 0 && !negative) {
623-
let errorMsg = "\n" + "Completion List contains: [" + completions.entries[0].name;
624-
for (let i = 1; i < completions.entries.length; i++) {
625-
errorMsg += ", " + completions.entries[i].name;
626-
}
627-
errorMsg += "]\n";
628-
629-
this.raiseError("Completion list is not empty at caret at position " + this.activeFile.fileName + " " + this.currentCaretPosition + errorMsg);
630-
}
631-
}
632-
633601
public verifyCompletionListStartsWithItemsInOrder(items: string[]): void {
634602
if (items.length === 0) {
635603
return;
@@ -701,10 +669,10 @@ namespace FourSlash {
701669
}
702670
}
703671

704-
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
672+
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
705673
const completions = this.getCompletionListAtCaret();
706674
if (completions) {
707-
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind);
675+
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex);
708676
}
709677
else {
710678
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
@@ -720,25 +688,32 @@ namespace FourSlash {
720688
* @param expectedText the text associated with the symbol
721689
* @param expectedDocumentation the documentation text associated with the symbol
722690
* @param expectedKind the kind of symbol (see ScriptElementKind)
691+
* @param spanIndex the index of the range that the completion item's replacement text span should match
723692
*/
724-
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string) {
693+
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
725694
const that = this;
695+
let replacementSpan: ts.TextSpan;
696+
if (spanIndex !== undefined) {
697+
replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex);
698+
}
699+
726700
function filterByTextOrDocumentation(entry: ts.CompletionEntry) {
727701
const details = that.getCompletionEntryDetails(entry.name);
728702
const documentation = ts.displayPartsToString(details.documentation);
729703
const text = ts.displayPartsToString(details.displayParts);
730-
if (expectedText && expectedDocumentation) {
731-
return (documentation === expectedDocumentation && text === expectedText) ? true : false;
704+
705+
// If any of the expected values are undefined, assume that users don't
706+
// care about them.
707+
if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) {
708+
return false;
732709
}
733-
else if (expectedText && !expectedDocumentation) {
734-
return text === expectedText ? true : false;
710+
else if (expectedText && text !== expectedText) {
711+
return false;
735712
}
736-
else if (expectedDocumentation && !expectedText) {
737-
return documentation === expectedDocumentation ? true : false;
713+
else if (expectedDocumentation && documentation !== expectedDocumentation) {
714+
return false;
738715
}
739-
// Because expectedText and expectedDocumentation are undefined, we assume that
740-
// users don"t care to compare them so we will treat that entry as if the entry has matching text and documentation
741-
// and keep it in the list of filtered entry.
716+
742717
return true;
743718
}
744719

@@ -762,45 +737,11 @@ namespace FourSlash {
762737
if (expectedKind) {
763738
error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions[0].kind + ".";
764739
}
765-
this.raiseError(error);
766-
}
767-
}
768-
}
769-
770-
public verifyImportModuleCompletionListContains(symbol: string, rangeIndex?: number) {
771-
const completions = this.getImportModuleCompletionListAtCaret();
772-
if (completions) {
773-
const completion = ts.forEach(completions.entries, completion => completion.name === symbol ? completion : undefined);
774-
if (!completion) {
775-
const itemsString = completions.entries.map(item => item.name).join(",\n");
776-
this.raiseError(`Expected "${symbol}" to be in list [${itemsString}]`);
777-
}
778-
else if (rangeIndex !== undefined) {
779-
const ranges = this.getRanges();
780-
if (ranges && ranges.length > rangeIndex) {
781-
const range = ranges[rangeIndex];
782-
783-
const start = completion.replacementSpan.start;
784-
const end = start + completion.replacementSpan.length;
785-
if (range.start !== start || range.end !== end) {
786-
this.raiseError(`Expected completion span for '${symbol}', ${stringify(completion.replacementSpan)}, to cover range ${stringify(range)}`);
787-
}
740+
if (replacementSpan) {
741+
const spanText = filterCompletions[0].replacementSpan ? stringify(filterCompletions[0].replacementSpan) : undefined;
742+
error += "Expected replacement span: " + stringify(replacementSpan) + " to equal: " + spanText + ".";
788743
}
789-
else {
790-
this.raiseError(`Expected completion span for '${symbol}' to cover range at index ${rangeIndex}, but no range was found at that index`);
791-
}
792-
}
793-
}
794-
else {
795-
this.raiseError(`No import module completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
796-
}
797-
}
798-
799-
public verifyImportModuleCompletionListDoesNotContain(symbol: string) {
800-
const completions = this.getImportModuleCompletionListAtCaret();
801-
if (completions) {
802-
if (ts.forEach(completions.entries, completion => completion.name === symbol)) {
803-
this.raiseError(`Import module completion list did contain ${symbol}`);
744+
this.raiseError(error);
804745
}
805746
}
806747
}
@@ -891,10 +832,6 @@ namespace FourSlash {
891832
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition);
892833
}
893834

894-
private getImportModuleCompletionListAtCaret() {
895-
return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition);
896-
}
897-
898835
private getCompletionEntryDetails(entryName: string) {
899836
return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName);
900837
}
@@ -2241,7 +2178,7 @@ namespace FourSlash {
22412178
return text.substring(startPos, endPos);
22422179
}
22432180

2244-
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string) {
2181+
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
22452182
for (let i = 0; i < items.length; i++) {
22462183
const item = items[i];
22472184
if (item.name === name) {
@@ -2260,6 +2197,11 @@ namespace FourSlash {
22602197
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name));
22612198
}
22622199

2200+
if (spanIndex !== undefined) {
2201+
const span = this.getTextSpanForRangeAtIndex(spanIndex);
2202+
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name));
2203+
}
2204+
22632205
return;
22642206
}
22652207
}
@@ -2316,6 +2258,17 @@ namespace FourSlash {
23162258
return `line ${(pos.line + 1)}, col ${pos.character}`;
23172259
}
23182260

2261+
private getTextSpanForRangeAtIndex(index: number): ts.TextSpan {
2262+
const ranges = this.getRanges();
2263+
if (ranges && ranges.length > index) {
2264+
const range = ranges[index];
2265+
return { start: range.start, length: range.end - range.start };
2266+
}
2267+
else {
2268+
this.raiseError("Supplied span index: " + index + " does not exist in range list of size: " + (ranges ? 0 : ranges.length));
2269+
}
2270+
}
2271+
23192272
public getMarkerByName(markerName: string) {
23202273
const markerPos = this.testData.markerPositions[markerName];
23212274
if (markerPos === undefined) {
@@ -2339,6 +2292,10 @@ namespace FourSlash {
23392292
public resetCancelled(): void {
23402293
this.cancellationToken.resetCancelled();
23412294
}
2295+
2296+
private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) {
2297+
return a && b && a.start === b.start && a.length === b.length;
2298+
}
23422299
}
23432300

23442301
export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
@@ -2909,12 +2866,12 @@ namespace FourSlashInterface {
29092866

29102867
// Verifies the completion list contains the specified symbol. The
29112868
// completion list is brought up if necessary
2912-
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
2869+
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
29132870
if (this.negative) {
2914-
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind);
2871+
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex);
29152872
}
29162873
else {
2917-
this.state.verifyCompletionListContains(symbol, text, documentation, kind);
2874+
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex);
29182875
}
29192876
}
29202877

@@ -2924,23 +2881,6 @@ namespace FourSlashInterface {
29242881
this.state.verifyCompletionListItemsCountIsGreaterThan(count, this.negative);
29252882
}
29262883

2927-
public importModuleCompletionListContains(symbol: string, rangeIndex?: number): void {
2928-
if (this.negative) {
2929-
this.state.verifyImportModuleCompletionListDoesNotContain(symbol);
2930-
}
2931-
else {
2932-
this.state.verifyImportModuleCompletionListContains(symbol, rangeIndex);
2933-
}
2934-
}
2935-
2936-
public importModuleCompletionListItemsCountIsGreaterThan(count: number): void {
2937-
this.state.verifyImportModuleCompletionListItemsCountIsGreaterThan(count, this.negative);
2938-
}
2939-
2940-
public importModuleCompletionListIsEmpty(): void {
2941-
this.state.verifyImportModuleCompletionListIsEmpty(this.negative);
2942-
}
2943-
29442884
public assertHasRanges(ranges: FourSlash.Range[]) {
29452885
assert(ranges.length !== 0, "Array of ranges is expected to be non-empty");
29462886
}

src/services/services.ts

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4236,9 +4236,8 @@ namespace ts {
42364236

42374237
const sourceFile = getValidSourceFile(fileName);
42384238

4239-
const importCompletionInfo = getImportModuleCompletionsAtPosition(fileName, position);
4240-
if (importCompletionInfo && importCompletionInfo.entries.length !== 0) {
4241-
return importCompletionInfo;
4239+
if (isInReferenceComment(sourceFile, position)) {
4240+
return getTripleSlashReferenceCompletion(sourceFile, position);
42424241
}
42434242

42444243
if (isInString(sourceFile, position)) {
@@ -4410,6 +4409,13 @@ namespace ts {
44104409
// a['/*completion position*/']
44114410
return getStringLiteralCompletionEntriesFromElementAccess(node.parent);
44124411
}
4412+
else if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) {
4413+
// Get all known external module names or complete a path to a module
4414+
// i.e. import * as ns from "/*completion position*/";
4415+
// import x = require("/*completion position*/");
4416+
// var y = require("/*completion position*/");
4417+
return getStringLiteralCompletionEntriesFromModuleNames(<StringLiteral>node);
4418+
}
44134419
else {
44144420
const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile);
44154421
if (argumentInfo) {
@@ -4502,55 +4508,35 @@ namespace ts {
45024508
}
45034509
}
45044510
}
4505-
}
4506-
4507-
function getImportModuleCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
4508-
synchronizeHostData();
4509-
4510-
const sourceFile = getValidSourceFile(fileName);
4511-
if (isInReferenceComment(sourceFile, position)) {
4512-
return getTripleSlashReferenceCompletion(sourceFile, position);
4513-
}
4514-
else if (isInString(sourceFile, position)) {
4515-
const node = findPrecedingToken(position, sourceFile);
4516-
if (!node || node.kind !== SyntaxKind.StringLiteral) {
4517-
return undefined;
4518-
}
4519-
4520-
if (node.parent.kind === SyntaxKind.ImportDeclaration || isExpressionOfExternalModuleImportEqualsDeclaration(node) || isRequireCall(node.parent, false)) {
4521-
// Get all known external module names or complete a path to a module
4522-
return {
4523-
isMemberCompletion: false,
4524-
isNewIdentifierLocation: true,
4525-
entries: getStringLiteralCompletionEntriesFromModuleNames(<StringLiteral>node)
4526-
};
4527-
}
4528-
}
45294511

4530-
return undefined;
4531-
4532-
function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral) {
4512+
function getStringLiteralCompletionEntriesFromModuleNames(node: StringLiteral): CompletionInfo {
45334513
const literalValue = normalizeSlashes(node.text);
45344514

45354515
const scriptPath = node.getSourceFile().path;
45364516
const scriptDirectory = getDirectoryPath(scriptPath);
45374517

45384518
const span = getDirectoryFragmentTextSpan((<StringLiteral>node).text, node.getStart() + 1);
4519+
let entries: CompletionEntry[];
45394520
if (isPathRelativeToScript(literalValue) || isRootedDiskPath(literalValue)) {
45404521
const compilerOptions = program.getCompilerOptions();
45414522
if (compilerOptions.rootDirs) {
4542-
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
4523+
entries = getCompletionEntriesForDirectoryFragmentWithRootDirs(
45434524
compilerOptions.rootDirs, literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath);
45444525
}
45454526
else {
4546-
return getCompletionEntriesForDirectoryFragment(
4527+
entries = getCompletionEntriesForDirectoryFragment(
45474528
literalValue, scriptDirectory, getSupportedExtensions(program.getCompilerOptions()), /*includeExtensions*/false, span, scriptPath);
45484529
}
45494530
}
45504531
else {
45514532
// Check for node modules
4552-
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span);
4533+
entries = getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, span);
45534534
}
4535+
return {
4536+
isMemberCompletion: false,
4537+
isNewIdentifierLocation: true,
4538+
entries
4539+
};
45544540
}
45554541

45564542
/**

tests/cases/fourslash/completionForStringLiteralImport1.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
//// export var x = 9;
2222

2323
goTo.marker("0");
24-
verify.importModuleCompletionListContains("someFile1", 0);
24+
verify.completionListContains("someFile1", undefined, undefined, undefined, 0);
2525

2626
goTo.marker("1");
27-
verify.importModuleCompletionListContains("someFile2", 1);
27+
verify.completionListContains("someFile2", undefined, undefined, undefined, 1);
2828

2929
goTo.marker("2");
30-
verify.importModuleCompletionListContains("some-module", 2);
30+
verify.completionListContains("some-module", undefined, undefined, undefined, 2);
3131

3232
goTo.marker("3");
33-
verify.importModuleCompletionListContains("fourslash", 3);
33+
verify.completionListContains("fourslash", undefined, undefined, undefined, 3);

tests/cases/fourslash/completionForStringLiteralImport2.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
//// export var x = 9;
2222

2323
goTo.marker("0");
24-
verify.importModuleCompletionListContains("someFile.ts", 0);
24+
verify.completionListContains("someFile.ts", undefined, undefined, undefined, 0);
2525

2626
goTo.marker("1");
27-
verify.importModuleCompletionListContains("some-module", 1);
27+
verify.completionListContains("some-module", undefined, undefined, undefined, 1);
2828

2929
goTo.marker("2");
30-
verify.importModuleCompletionListContains("someOtherFile.ts", 2);
30+
verify.completionListContains("someOtherFile.ts", undefined, undefined, undefined, 2);
3131

3232
goTo.marker("3");
33-
verify.importModuleCompletionListContains("some-module", 3);
33+
verify.completionListContains("some-module", undefined, undefined, undefined, 3);

0 commit comments

Comments
 (0)