diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index d973b9360ec..4a4a1d9eed9 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -66,17 +66,37 @@ class MyCompletionItem extends vscode.CompletionItem { this.useCodeSnippet = useCodeSnippetsOnMethodSuggest && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method); if (tsEntry.replacementSpan) { - this.range = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); + let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); // Make sure we only replace a single line at most - if (!this.range.isSingleLine) { - this.range = new vscode.Range(this.range.start.line, this.range.start.character, this.range.start.line, line.length); + if (!replaceRange.isSingleLine) { + replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, line.length); } + this.range = { + inserting: new vscode.Range(replaceRange.start, position), + replacing: replaceRange, + }; } - this.insertText = tsEntry.insertText; - // Set filterText for intelliCode and bracket accessors , but not for `this.` completions since it results in - // them being overly prioritized. #74164 - this.filterText = tsEntry.insertText && !/^this\./.test(tsEntry.insertText) ? tsEntry.insertText : undefined; + if (tsEntry.insertText) { + this.insertText = tsEntry.insertText; + + // Set filterText for intelliCode and bracket accessors , but not for `this.` completions since it results in + // them being overly prioritized. #74164 + this.filterText = !(/^this\./).test(tsEntry.insertText) ? tsEntry.insertText : undefined; + + // Handle the case: + // + // ``` + // const xyz = { 'ab c': 1 }; + // xyz.ab| + // ``` + // + // In which case we want to insert a bracket accessor but should use `.abc` as the filter text instead of + // the bracketed insert text. + if (tsEntry.insertText[0] === '[') { + this.filterText = tsEntry.insertText.replace(/^\[['"](.+)[['"]\]$/, '.$1'); + } + } if (completionContext.isMemberCompletion && completionContext.dotAccessorContext) { this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.label); diff --git a/extensions/typescript-language-features/src/test/completions.test.ts b/extensions/typescript-language-features/src/test/completions.test.ts index f00fbb530f2..46c021d4489 100644 --- a/extensions/typescript-language-features/src/test/completions.test.ts +++ b/extensions/typescript-language-features/src/test/completions.test.ts @@ -293,7 +293,7 @@ suite('TypeScript Completions', () => { }); }); - test('Member completions for string properties should insert `this.` and use brackets', async () => { + test('Member completions for string property name should insert `this.` and use brackets', async () => { await enumerateConfig(Config.insertMode, insertModes, async config => { await createTestEditor(testDocumentUri, `class A {`, @@ -319,7 +319,33 @@ suite('TypeScript Completions', () => { }); }); - test('Accepting a completion in word using insert mode should insert', async () => { + test('Member completions for string property name already using `this.` should add brackets', async () => { + await enumerateConfig(Config.insertMode, insertModes, async config => { + await createTestEditor(testDocumentUri, + `class A {`, + ` ['xyz 123'] = 1`, + ` foo() {`, + ` this.xyz$0`, + ` }`, + `}`, + ); + + const document = await acceptFirstSuggestion(testDocumentUri, _disposables); + assert.strictEqual( + document.getText(), + joinLines( + `class A {`, + ` ['xyz 123'] = 1`, + ` foo() {`, + ` this["xyz 123"]`, + ` }`, + `}`, + ), + `Config: ${config}`); + }); + }); + + test('Accepting a completion in word using `insert` mode should insert', async () => { await updateConfig({ [Config.insertMode]: 'insert' }); await createTestEditor(testDocumentUri, @@ -336,7 +362,7 @@ suite('TypeScript Completions', () => { )); }); - test('Accepting a completion in word using replace mode should replace', async () => { + test('Accepting a completion in word using `replace` mode should replace', async () => { await updateConfig({ [Config.insertMode]: 'replace' }); await createTestEditor(testDocumentUri, @@ -353,7 +379,7 @@ suite('TypeScript Completions', () => { )); }); - test('Accepting a member completion in word using insert mode add `this.` and insert', async () => { + test('Accepting a member completion in word using `insert` mode add `this.` and insert', async () => { await updateConfig({ [Config.insertMode]: 'insert' }); await createTestEditor(testDocumentUri, @@ -378,7 +404,7 @@ suite('TypeScript Completions', () => { )); }); - test('Accepting a member completion in word using replace mode should add `this.` and replace', async () => { + test('Accepting a member completion in word using `replace` mode should add `this.` and replace', async () => { await updateConfig({ [Config.insertMode]: 'replace' }); await createTestEditor(testDocumentUri, @@ -403,7 +429,7 @@ suite('TypeScript Completions', () => { )); }); - test('Accepting string completion inside string using insert mode should insert', async () => { + test('Accepting string completion inside string using `insert` mode should insert', async () => { await updateConfig({ [Config.insertMode]: 'insert' }); await createTestEditor(testDocumentUri,