Skip to content

Commit 506f3e2

Browse files
fix58438: do not delete comments/code following unterminated string completion (#58742)
Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent 9edddc7 commit 506f3e2

File tree

5 files changed

+86
-14
lines changed

5 files changed

+86
-14
lines changed

src/services/completions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1661,7 +1661,7 @@ function createCompletionEntry(
16611661
): CompletionEntry | undefined {
16621662
let insertText: string | undefined;
16631663
let filterText: string | undefined;
1664-
let replacementSpan = getReplacementSpanForContextToken(replacementToken);
1664+
let replacementSpan = getReplacementSpanForContextToken(replacementToken, position);
16651665
let data: CompletionEntryData | undefined;
16661666
let isSnippet: true | undefined;
16671667
let source = getSourceFromOrigin(origin);

src/services/stringCompletions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ function convertStringLiteralCompletions(
223223
return undefined;
224224
}
225225

226-
const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken);
226+
const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken, position);
227227
switch (completion.kind) {
228228
case StringLiteralCompletionKind.Paths:
229229
return convertPathCompletions(completion.paths);
@@ -270,7 +270,7 @@ function convertStringLiteralCompletions(
270270
kindModifiers: ScriptElementKindModifier.none,
271271
kind: ScriptElementKind.string,
272272
sortText: SortText.LocationPriority,
273-
replacementSpan: getReplacementSpanForContextToken(contextToken),
273+
replacementSpan: getReplacementSpanForContextToken(contextToken, position),
274274
}));
275275
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries };
276276
}

src/services/utilities.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2317,13 +2317,13 @@ function isInReferenceCommentWorker(sourceFile: SourceFile, position: number, sh
23172317
}
23182318

23192319
/** @internal */
2320-
export function getReplacementSpanForContextToken(contextToken: Node | undefined) {
2320+
export function getReplacementSpanForContextToken(contextToken: Node | undefined, position: number) {
23212321
if (!contextToken) return undefined;
23222322

23232323
switch (contextToken.kind) {
23242324
case SyntaxKind.StringLiteral:
23252325
case SyntaxKind.NoSubstitutionTemplateLiteral:
2326-
return createTextSpanFromStringLiteralLikeContent(contextToken as StringLiteralLike);
2326+
return createTextSpanFromStringLiteralLikeContent(contextToken as StringLiteralLike, position);
23272327
default:
23282328
return createTextSpanFromNode(contextToken);
23292329
}
@@ -2335,12 +2335,12 @@ export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endN
23352335
}
23362336

23372337
/** @internal */
2338-
export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) {
2338+
export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike, position: number) {
23392339
let replacementEnd = node.getEnd() - 1;
23402340
if (node.isUnterminated) {
23412341
// we return no replacement range only if unterminated string is empty
23422342
if (node.getStart() === replacementEnd) return undefined;
2343-
replacementEnd = node.getEnd();
2343+
replacementEnd = Math.min(position, node.getEnd());
23442344
}
23452345
return createTextSpanFromBounds(node.getStart() + 1, replacementEnd);
23462346
}

tests/cases/fourslash/stringCompletionsUnterminated.ts

+27-7
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@
1818

1919
// @Filename: file4.ts
2020
//// const a = { "foo.bar": 1 }
21-
//// a["[|foo./*4*/b|]
21+
//// a["[|foo./*4*/|]b
2222

2323
// @Filename: file5.ts
2424
//// const a = { "foo.bar": 1 }
25-
//// a['[|foo./*5*/b|]
25+
//// a['[|foo./*5*/|]b
2626

2727
// @Filename: file6.ts
2828
//// const a = { "foo.bar": 1 }
29-
//// a[`[|foo./*6*/b|]
29+
//// a[`[|foo./*6*/|]b
3030

3131
// @Filename: file7.ts
3232
//// const a = { "foo.bar": 1 }
@@ -44,19 +44,39 @@
4444
//// const a = { "foo.bar": 1 }
4545
//// a["[|foo/*10*/|]"
4646

47+
// @Filename: file11.ts
48+
//// const a = { "foo.bar": 1 }
49+
//// a["[|f/*11*/|]oo.b
50+
51+
// @Filename: file12.ts
52+
//// const a = { "foo.bar": 1 }
53+
//// a["[|f/*12*/oo.b|]"
54+
55+
// @Filename: file13.ts
56+
//// const a = { "foo.bar": 1 }
57+
//// a["[|f/*13*/oo.b|]" // some comment!!
58+
59+
// @Filename: file14.ts
60+
//// const a = { "foo.bar": 1 }
61+
//// a["[|f/*14*/|]oo.b // some comment!!
62+
63+
// @Filename: file15.ts
64+
//// const a = { "foo.bar": 1 }
65+
//// a[`[|foo.b/*15*/|]
66+
4767
// @Filename: empty1.ts
4868
//// const a = { "foo.bar": 1 }
49-
//// a[`/*11*/
69+
//// a[`/*a*/
5070

5171
// @Filename: empty2.ts
5272
//// const a = { "foo.bar": 1 }
53-
//// a["/*12*/
73+
//// a["/*b*/
5474

5575
// @Filename: empty3.ts
5676
//// const a = { "foo.bar": 1 }
57-
//// a['/*13*/
77+
//// a['/*c*/
5878

59-
// tests 11-13 should return no replacementSpan
79+
// tests a,b,c should return no replacementSpan
6080
const markers = test.markers();
6181
const ranges = test.ranges();
6282

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
///<reference path="fourslash.ts" />
2+
3+
// @Filename: test0.ts
4+
//// type Test = 'test';
5+
//// const test0: Test = `[|t/*0*/|]; // this comment should not be deleted
6+
7+
// @Filename: test1.ts
8+
//// type Test = 'test';
9+
//// const test1: Test = '[|t/*1*/|]; // this comment should not be deleted
10+
11+
// @Filename: test2.ts
12+
//// type Test = 'test';
13+
//// const test2: Test = "[|t/*2*/|] other stuff; // this comment should not be deleted
14+
15+
// @Filename: test3.ts
16+
//// type Test = 'test';
17+
//// const test3: Test = "[|t/*3*/|]es // this comment should not be deleted
18+
19+
// @Filename: test4.ts
20+
//// type Test = 'test';
21+
//// const test4: Test = "[|tes/*4*/|] // this comment should not be deleted
22+
23+
// @Filename: file5.ts
24+
//// type Test = 'test';
25+
//// const test5: {prop0: Test, prop1: number, prop2: number, } = { prop0: '[|t/*5*/|] ,prop1: 5, prop2: 2 };
26+
27+
// @Filename: file6.ts
28+
//// type Test = 'test';
29+
//// const test6: {prop0: Test, prop1: number, prop2: number, } = { prop0: '[|t/*6*/|]es ,prop1: 5, prop2: 2 };
30+
31+
// @Filename: test7.ts
32+
//// type Test = 'test asdf';
33+
//// const test7: Test = `[|test as/*7*/|]; // this comment should not be deleted
34+
35+
// @Filename: test8.ts
36+
//// type Test = 'test asdf';
37+
//// const test8: Test = `[|test/*8*/|] as; // this comment should not be deleted
38+
39+
const markers = test.markers();
40+
const ranges = test.ranges();
41+
42+
for (let i = 0; i < markers.length; i++) {
43+
verify.completions({
44+
marker: markers[i],
45+
includes: [{
46+
"name": "test",
47+
"kind": "string",
48+
"kindModifiers": "",
49+
"replacementSpan": ranges[i],
50+
}],
51+
});
52+
}

0 commit comments

Comments
 (0)