Skip to content

Commit 5596ed8

Browse files
authored
Add replacement span for string literal (#37490)
* Add replacement span for string literal * fix change requests * fix lint * Avoid flag * Fix baseline * ADd misising baseline
1 parent 15aff05 commit 5596ed8

32 files changed

+334
-86
lines changed

src/services/completions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ namespace ts.Completions {
245245
const uniqueNames = getCompletionEntriesFromSymbols(
246246
symbols,
247247
entries,
248+
/* contextToken */ undefined,
248249
location,
249250
sourceFile,
250251
typeChecker,
@@ -269,6 +270,7 @@ namespace ts.Completions {
269270
getCompletionEntriesFromSymbols(
270271
symbols,
271272
entries,
273+
/* contextToken */ undefined,
272274
location,
273275
sourceFile,
274276
typeChecker,
@@ -353,6 +355,7 @@ namespace ts.Completions {
353355
function createCompletionEntry(
354356
symbol: Symbol,
355357
sortText: SortText,
358+
contextToken: Node | undefined,
356359
location: Node | undefined,
357360
sourceFile: SourceFile,
358361
typeChecker: TypeChecker,
@@ -365,7 +368,7 @@ namespace ts.Completions {
365368
preferences: UserPreferences,
366369
): CompletionEntry | undefined {
367370
let insertText: string | undefined;
368-
let replacementSpan: TextSpan | undefined;
371+
let replacementSpan = getReplacementSpanForContextToken(contextToken);
369372

370373
const insertQuestionDot = origin && originIsNullableMember(origin);
371374
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
@@ -462,6 +465,7 @@ namespace ts.Completions {
462465
export function getCompletionEntriesFromSymbols(
463466
symbols: readonly Symbol[],
464467
entries: Push<CompletionEntry>,
468+
contextToken: Node | undefined,
465469
location: Node | undefined,
466470
sourceFile: SourceFile,
467471
typeChecker: TypeChecker,
@@ -496,6 +500,7 @@ namespace ts.Completions {
496500
const entry = createCompletionEntry(
497501
symbol,
498502
symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)] || SortText.LocationPriority,
503+
contextToken,
499504
location,
500505
sourceFile,
501506
typeChecker,

src/services/stringCompletions.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ namespace ts.Completions.StringCompletions {
88
if (isInString(sourceFile, position, contextToken)) {
99
if (!contextToken || !isStringLiteralLike(contextToken)) return undefined;
1010
const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host);
11-
return convertStringLiteralCompletions(entries, sourceFile, checker, log, preferences);
11+
return convertStringLiteralCompletions(entries, contextToken, sourceFile, checker, log, preferences);
1212
}
1313
}
1414

15-
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, sourceFile: SourceFile, checker: TypeChecker, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
15+
function convertStringLiteralCompletions(completion: StringLiteralCompletion | undefined, contextToken: Node, sourceFile: SourceFile, checker: TypeChecker, log: Log, preferences: UserPreferences): CompletionInfo | undefined {
1616
if (completion === undefined) {
1717
return undefined;
1818
}
@@ -24,6 +24,7 @@ namespace ts.Completions.StringCompletions {
2424
getCompletionEntriesFromSymbols(
2525
completion.symbols,
2626
entries,
27+
contextToken,
2728
sourceFile,
2829
sourceFile,
2930
checker,
@@ -35,7 +36,13 @@ namespace ts.Completions.StringCompletions {
3536
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, entries };
3637
}
3738
case StringLiteralCompletionKind.Types: {
38-
const entries = completion.types.map(type => ({ name: type.value, kindModifiers: ScriptElementKindModifier.none, kind: ScriptElementKind.string, sortText: "0" }));
39+
const entries = completion.types.map(type => ({
40+
name: type.value,
41+
kindModifiers: ScriptElementKindModifier.none,
42+
kind: ScriptElementKind.string,
43+
sortText: "0",
44+
replacementSpan: getReplacementSpanForContextToken(contextToken)
45+
}));
3946
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, entries };
4047
}
4148
default:

src/services/utilities.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,10 +1600,27 @@ namespace ts {
16001600
return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end));
16011601
}
16021602

1603+
export function getReplacementSpanForContextToken(contextToken: Node | undefined) {
1604+
if (!contextToken) return undefined;
1605+
1606+
switch (contextToken.kind) {
1607+
case SyntaxKind.StringLiteral:
1608+
case SyntaxKind.NoSubstitutionTemplateLiteral:
1609+
return createTextSpanFromStringLiteralLikeContent(<StringLiteralLike>contextToken);
1610+
default:
1611+
return createTextSpanFromNode(contextToken);
1612+
}
1613+
}
1614+
16031615
export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan {
16041616
return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd());
16051617
}
16061618

1619+
export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) {
1620+
if (node.isUnterminated) return undefined;
1621+
return createTextSpanFromBounds(node.getStart() + 1, node.getEnd() - 1);
1622+
}
1623+
16071624
export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange {
16081625
return createRange(node.getStart(sourceFile), node.end);
16091626
}

tests/cases/fourslash/completionForQuotedPropertyInPropertyAssignment1.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
//// let files: Configfiles;
99
//// files = {
1010
//// /*0*/: '',
11-
//// '/*1*/': ''
11+
//// '[|/*1*/|]': ''
1212
//// }
1313

14+
const replacementSpan = test.ranges()[0]
1415
verify.completions(
1516
{ marker: "0", exact: ["jspm", '"jspm:browser"'] },
16-
{ marker: "1", exact: ["jspm", "jspm:browser"] },
17+
{ marker: "1", exact: [
18+
{ name: "jspm", replacementSpan },
19+
{ name: "jspm:browser", replacementSpan }
20+
] },
1721
);

tests/cases/fourslash/completionForQuotedPropertyInPropertyAssignment2.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313
//// config = {
1414
//// files: {
1515
//// /*0*/: '',
16-
//// '/*1*/': ''
16+
//// '[|/*1*/|]': ''
1717
//// }
1818
//// }
1919

20+
const replacementSpan = test.ranges()[0]
2021
verify.completions(
2122
{ marker: "0", exact: ["jspm", '"jspm:browser"'] },
22-
{ marker: "1", exact: ["jspm", "jspm:browser"] },
23+
{ marker: "1", exact: [
24+
{ name: "jspm", replacementSpan },
25+
{ name: "jspm:browser", replacementSpan }
26+
] },
2327
);

tests/cases/fourslash/completionForQuotedPropertyInPropertyAssignment3.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
//// 'jspm:browser': string;
1313
//// } = {
1414
//// jspm: "",
15-
//// '/*1*/': ""
15+
//// '[|/*1*/|]': ""
1616
//// }
1717

18+
const replacementSpan = test.ranges()[0]
1819
verify.completions(
1920
{ marker: "0", exact: ["jspm", '"jspm:browser"'] },
20-
{ marker: "1", exact: ["jspm", "jspm:browser"] },
21+
{ marker: "1", exact: [
22+
{ name: "jspm", replacementSpan },
23+
{ name: "jspm:browser", replacementSpan }
24+
] }
2125
);

tests/cases/fourslash/completionForQuotedPropertyInPropertyAssignment4.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
//// function foo(c: ConfigFiles) {}
99
//// foo({
1010
//// j/*0*/: "",
11-
//// "/*1*/": "",
11+
//// "[|/*1*/|]": "",
1212
//// })
1313

14-
14+
const replacementSpan = test.ranges()[0]
1515
verify.completions(
1616
{ marker: "0", exact: ["jspm", '"jspm:browser"'] },
17-
{ marker: "1", exact: ["jspm", "jspm:browser"] },
17+
{ marker: "1", exact: [
18+
{ name: "jspm", replacementSpan },
19+
{ name: "jspm:browser", replacementSpan }
20+
] }
1821
);
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
/// <reference path='fourslash.ts'/>
22

33
////type Options = "Option 1" | "Option 2" | "Option 3";
4-
////var x: Options = "/*1*/Option 3";
4+
////var x: Options = "[|/*1*/Option 3|]";
55
////
66
////function f(a: Options) { };
77
////f("/*2*/
88

9-
verify.completions({ marker: ["1", "2"], exact: ["Option 1", "Option 2", "Option 3"] });
9+
verify.completions(
10+
{ marker: "1", exact: [
11+
{ name: "Option 1", replacementSpan: test.ranges()[0] },
12+
{ name: "Option 2", replacementSpan: test.ranges()[0] },
13+
{ name: "Option 3", replacementSpan: test.ranges()[0] }
14+
] },
15+
{ marker: "2", exact: [
16+
{ name: "Option 1", replacementSpan: test.ranges()[1] },
17+
{ name: "Option 2", replacementSpan: test.ranges()[1] },
18+
{ name: "Option 3", replacementSpan: test.ranges()[1] }
19+
] }
20+
);

tests/cases/fourslash/completionForStringLiteral10.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
////type As = 'arf' | 'abacus' | 'abaddon';
44
////let a: As;
5-
////if ('/**/' != a
5+
////if ('[|/**/|]' != a
66

7-
verify.completions({ marker: "", exact: ["arf", "abacus", "abaddon"] });
7+
verify.completions({ marker: "", exact: ["arf", "abacus", "abaddon"].map(name => ({
8+
name,
9+
replacementSpan: test.ranges()[0]
10+
})) });

tests/cases/fourslash/completionForStringLiteral11.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
////type As = 'arf' | 'abacus' | 'abaddon';
44
////let a: As;
55
////switch (a) {
6-
//// case '/**/
6+
//// case '[|/**/|]
77
////}
88

99
verify.completions({ marker: "", exact: ["arf", "abacus", "abaddon" ] });

tests/cases/fourslash/completionForStringLiteral12.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
////function foo(x: "bla"): void;
44
////function foo(x: "bla"): void;
55
////function foo(x: string) {}
6-
////foo("/**/")
6+
////foo("[|/**/|]")
77

8-
verify.completions({ marker: "", exact: "bla" });
8+
verify.completions({ marker: "", exact: {
9+
name: "bla",
10+
replacementSpan: test.ranges()[0]
11+
} });

tests/cases/fourslash/completionForStringLiteral2.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,22 @@
77
////};
88
////declare const p: { [s: string]: any, a: number };
99
////
10-
////o["/*1*/bar"];
10+
////o["[|/*1*/bar|]"];
1111
////o["/*2*/ ;
12-
////p["/*3*/"];
12+
////p["[|/*3*/|]"];
13+
14+
const replacementSpan0 = test.ranges()[0]
1315

1416
verify.completions(
15-
{ marker: ["1", "2"], exact: ["foo", "bar", "some other name"] },
16-
{ marker: "3", exact: "a", isNewIdentifierLocation: true },
17+
{ marker: "1", exact: [
18+
{ name: "foo", replacementSpan: replacementSpan0 },
19+
{ name: "bar", replacementSpan: replacementSpan0 },
20+
{ name: "some other name", replacementSpan: replacementSpan0 }
21+
] },
22+
{ marker: "2", exact: [ "foo", "bar", "some other name" ] },
23+
{ marker: "3", exact: {
24+
name: "a",
25+
replacementSpan: test.ranges()[1]
26+
},
27+
isNewIdentifierLocation: true },
1728
);

tests/cases/fourslash/completionForStringLiteral3.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
////declare function f(a: "C", b: number): void;
66
////declare function f(a: string, b: number): void;
77
////
8-
////f("/*1*/C", 2);
8+
////f("[|/*1*/C|]", 2);
99
////
1010
////f("/*2*/
1111

12-
verify.completions({ marker: ["1", "2"], exact: ["A", "B", "C"], isNewIdentifierLocation: true });
12+
verify.completions({ marker: "1", exact: [
13+
{ name: "A", replacementSpan: test.ranges()[0] },
14+
{ name: "B", replacementSpan: test.ranges()[0] },
15+
{ name: "C", replacementSpan: test.ranges()[0] }
16+
], isNewIdentifierLocation: true });
17+
18+
verify.completions({ marker: "2", exact: [ "A", "B", "C"], isNewIdentifierLocation: true });

tests/cases/fourslash/completionForStringLiteral4.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
////function f(p1, p2, p3, p4, p5) {
1212
//// return p1 + p2 + p3 + p4 + p5 + '.';
1313
////}
14-
////f/*1*/('literal', 'literal', "o/*2*/ther1", 12);
14+
////f/*1*/('literal', 'literal', "[|o/*2*/ther1|]", 12);
1515

1616
goTo.marker('1');
1717
verify.quickInfoExists();
1818
verify.quickInfoIs('function f(p1: "literal", p2: "literal", p3: "other1" | "other2", p4: number | "literal", p5: true | 12): string', 'I am documentation');
1919

20-
verify.completions({ marker: "2", exact: ["other1", "other2"] });
20+
const replacementSpan = test.ranges()[0]
21+
verify.completions({ marker: "2", exact: [
22+
{ name: "other1", replacementSpan },
23+
{ name: "other2", replacementSpan }
24+
] });

tests/cases/fourslash/completionForStringLiteral6.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
//// x: "abc" | "def";
55
////}
66
////function bar(f: Foo) { };
7-
////bar({x: "/**/"});
7+
////bar({x: "[|/**/|]"});
88

9-
verify.completions({ marker: "", exact: ["abc", "def"] });
9+
verify.completions({ marker: "", exact: ["abc", "def"].map(name => ({
10+
name,
11+
replacementSpan: test.ranges()[0]
12+
})) });

tests/cases/fourslash/completionForStringLiteral7.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
////type T = "foo" | "bar";
44
////type U = "oof" | "rab";
55
////function f(x: T, ...args: U[]) { };
6-
////f("/*1*/", "/*2*/", "/*3*/");
6+
////f("[|/*1*/|]", "[|/*2*/|]", "[|/*3*/|]");
77

88
verify.completions(
9-
{ marker: "1", exact: ["foo", "bar"] },
10-
{ marker: ["2", "3"], exact: ["oof", "rab"] },
9+
{ marker: "1", exact: ["foo", "bar"].map(name => ({
10+
name, replacementSpan: test.ranges()[0]
11+
})) },
12+
{ marker: "2", exact: ["oof", "rab"].map(name => ({
13+
name, replacementSpan: test.ranges()[1]
14+
})) },
15+
{ marker: "3", exact: ["oof", "rab"].map(name => ({
16+
name, replacementSpan: test.ranges()[2]
17+
})) },
1118
);

tests/cases/fourslash/completionForStringLiteralFromSignature.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
////declare function f(a: "x"): void;
44
////declare function f(a: string): void;
5-
////f("/**/");
5+
////f("[|/**/|]");
66

7-
verify.completions({ marker: "", exact: "x", isNewIdentifierLocation: true });
7+
verify.completions({ marker: "", exact: {
8+
name: "x",
9+
replacementSpan: test.ranges()[0]
10+
}, isNewIdentifierLocation: true });

tests/cases/fourslash/completionForStringLiteralInIndexedAccess01.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
//// bar: string;
66
////}
77
////
8-
////let x: Foo["/*1*/"]
8+
////let x: Foo["[|/*1*/|]"]
99

10-
verify.completions({ marker: "1", exact: ["foo", "bar"] });
10+
const replacementSpan = test.ranges()[0]
11+
verify.completions({ marker: "1", exact: [
12+
{ name: "foo", replacementSpan },
13+
{ name: "bar", replacementSpan }
14+
] });

tests/cases/fourslash/completionForStringLiteral_details.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// @Filename: /a.ts
77
////import {} from ".//*path*/";
88
////
9-
////const x: "a" = "/*type*/";
9+
////const x: "a" = "[|/*type*/|]";
1010
////
1111
////interface I {
1212
//// /** Prop doc */
@@ -15,16 +15,16 @@
1515
//// m(): void;
1616
////}
1717
////declare const o: I;
18-
////o["/*prop*/"];
18+
////o["[|/*prop*/|]"];
1919

2020
verify.completions(
2121
{ marker: "path", includes: { name: "other", text: "other", kind: "script", kindModifiers: ".ts" }, isNewIdentifierLocation: true },
22-
{ marker: "type", exact: { name: "a", text: "a", kind: "string" } },
22+
{ marker: "type", exact: { name: "a", text: "a", kind: "string", replacementSpan: test.ranges()[0] } },
2323
{
2424
marker: "prop",
2525
exact: [
26-
{ name: "x", text: "(property) I.x: number", documentation: "Prop doc", kind: "property" },
27-
{ name: "m", text: "(method) I.m(): void", documentation: "Method doc", kind: "method" },
26+
{ name: "x", text: "(property) I.x: number", documentation: "Prop doc", kind: "property", replacementSpan: test.ranges()[1] },
27+
{ name: "m", text: "(method) I.m(): void", documentation: "Method doc", kind: "method", replacementSpan: test.ranges()[1] },
2828
],
2929
},
3030
);

0 commit comments

Comments
 (0)