Skip to content

Commit e25f04a

Browse files
authored
Enable method signature completion for object literals (#48168)
* skeleton of new feature * working prototype * refactor print and format code into its own function * minor changes; don't support overloads * have two completion entries * get rid of accessor support * add snippet support * add formatting * add trailing comma * add sourcedisplay * support auto-imports via completion details * add user preference option and fix ordering of entries * cleanup * don't return code actions for no import fixes * make sortText lower priority for snippets * get rid of flag * use optional member sort text * update baselines * don't collect method symbols if insert text is not supported * remove comment * return undefined if type is not function type * only slice if needed * use union reduction; more test cases * WIP: modify sort text system * Improve new sort text system * add signature and union type check * re-add flag * fix tests * rename sort text helper * fix test and code for union case * add new flag to protocol type * fix spaces * CR: minor fixes * CR: more fixes * CR: restructure main flow * minor fix
1 parent 7ec7d6d commit e25f04a

31 files changed

+928
-243
lines changed

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8752,6 +8752,7 @@ namespace ts {
87528752
readonly includeAutomaticOptionalChainCompletions?: boolean;
87538753
readonly includeCompletionsWithInsertText?: boolean;
87548754
readonly includeCompletionsWithClassMemberSnippets?: boolean;
8755+
readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean;
87558756
readonly allowIncompleteCompletions?: boolean;
87568757
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
87578758
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */

src/harness/fourslashImpl.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -960,36 +960,36 @@ namespace FourSlash {
960960
expected = typeof expected === "string" ? { name: expected } : expected;
961961

962962
if (actual.insertText !== expected.insertText) {
963-
this.raiseError(`Completion insert text did not match: ${showTextDiff(expected.insertText || "", actual.insertText || "")}`);
963+
this.raiseError(`At entry ${actual.name}: Completion insert text did not match: ${showTextDiff(expected.insertText || "", actual.insertText || "")}`);
964964
}
965965
const convertedReplacementSpan = expected.replacementSpan && ts.createTextSpanFromRange(expected.replacementSpan);
966966
if (convertedReplacementSpan?.length) {
967967
try {
968968
assert.deepEqual(actual.replacementSpan, convertedReplacementSpan);
969969
}
970970
catch {
971-
this.raiseError(`Expected completion replacementSpan to be ${stringify(convertedReplacementSpan)}, got ${stringify(actual.replacementSpan)}`);
971+
this.raiseError(`At entry ${actual.name}: Expected completion replacementSpan to be ${stringify(convertedReplacementSpan)}, got ${stringify(actual.replacementSpan)}`);
972972
}
973973
}
974974

975975
if (expected.kind !== undefined || expected.kindModifiers !== undefined) {
976-
assert.equal(actual.kind, expected.kind, `Expected 'kind' for ${actual.name} to match`);
977-
assert.equal(actual.kindModifiers, expected.kindModifiers || "", `Expected 'kindModifiers' for ${actual.name} to match`);
976+
assert.equal(actual.kind, expected.kind, `At entry ${actual.name}: Expected 'kind' for ${actual.name} to match`);
977+
assert.equal(actual.kindModifiers, expected.kindModifiers || "", `At entry ${actual.name}: Expected 'kindModifiers' for ${actual.name} to match`);
978978
}
979979
if (expected.isFromUncheckedFile !== undefined) {
980-
assert.equal<boolean | undefined>(actual.isFromUncheckedFile, expected.isFromUncheckedFile, "Expected 'isFromUncheckedFile' properties to match");
980+
assert.equal<boolean | undefined>(actual.isFromUncheckedFile, expected.isFromUncheckedFile, `At entry ${actual.name}: Expected 'isFromUncheckedFile' properties to match`);
981981
}
982982
if (expected.isPackageJsonImport !== undefined) {
983-
assert.equal<boolean | undefined>(actual.isPackageJsonImport, expected.isPackageJsonImport, "Expected 'isPackageJsonImport' properties to match");
983+
assert.equal<boolean | undefined>(actual.isPackageJsonImport, expected.isPackageJsonImport, `At entry ${actual.name}: Expected 'isPackageJsonImport' properties to match`);
984984
}
985985

986-
assert.equal(actual.hasAction, expected.hasAction, `Expected 'hasAction' properties to match`);
987-
assert.equal(actual.isRecommended, expected.isRecommended, `Expected 'isRecommended' properties to match'`);
988-
assert.equal(actual.isSnippet, expected.isSnippet, `Expected 'isSnippet' properties to match`);
989-
assert.equal(actual.source, expected.source, `Expected 'source' values to match`);
990-
assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `Expected 'sortText' properties to match`);
986+
assert.equal(actual.hasAction, expected.hasAction, `At entry ${actual.name}: Expected 'hasAction' properties to match`);
987+
assert.equal(actual.isRecommended, expected.isRecommended, `At entry ${actual.name}: Expected 'isRecommended' properties to match'`);
988+
assert.equal(actual.isSnippet, expected.isSnippet, `At entry ${actual.name}: Expected 'isSnippet' properties to match`);
989+
assert.equal(actual.source, expected.source, `At entry ${actual.name}: Expected 'source' values to match`);
990+
assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, `At entry ${actual.name}: Expected 'sortText' properties to match`);
991991
if (expected.sourceDisplay && actual.sourceDisplay) {
992-
assert.equal(ts.displayPartsToString(actual.sourceDisplay), expected.sourceDisplay, `Expected 'sourceDisplay' properties to match`);
992+
assert.equal(ts.displayPartsToString(actual.sourceDisplay), expected.sourceDisplay, `At entry ${actual.name}: Expected 'sourceDisplay' properties to match`);
993993
}
994994

995995
if (expected.text !== undefined) {

src/harness/fourslashInterfaceImpl.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -950,9 +950,36 @@ namespace FourSlashInterface {
950950
}
951951

952952
export namespace Completion {
953-
export import SortText = ts.Completions.SortText;
953+
import SortTextType = ts.Completions.SortText;
954+
export type SortText = SortTextType;
954955
export import CompletionSource = ts.Completions.CompletionSource;
955956

957+
export const SortText = {
958+
// Presets
959+
LocalDeclarationPriority: "10" as SortText,
960+
LocationPriority: "11" as SortText,
961+
OptionalMember: "12" as SortText,
962+
MemberDeclaredBySpreadAssignment: "13" as SortText,
963+
SuggestedClassMembers: "14" as SortText,
964+
GlobalsOrKeywords: "15" as SortText,
965+
AutoImportSuggestions: "16" as SortText,
966+
ClassMemberSnippets: "17" as SortText,
967+
JavascriptIdentifiers: "18" as SortText,
968+
969+
// Transformations
970+
Deprecated(sortText: SortText): SortText {
971+
return "z" + sortText as SortText;
972+
},
973+
974+
ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText {
975+
return `${presetSortText}\0${symbolDisplayName}\0` as SortText;
976+
},
977+
978+
SortBelow(sortText: SortText): SortText {
979+
return sortText + "1" as SortText;
980+
},
981+
};
982+
956983
const functionEntry = (name: string): ExpectedCompletionEntryObject => ({
957984
name,
958985
kind: "function",
@@ -963,7 +990,7 @@ namespace FourSlashInterface {
963990
name,
964991
kind: "function",
965992
kindModifiers: "deprecated,declare",
966-
sortText: SortText.DeprecatedGlobalsOrKeywords
993+
sortText: "z15" as SortText,
967994
});
968995
const varEntry = (name: string): ExpectedCompletionEntryObject => ({
969996
name,
@@ -992,7 +1019,7 @@ namespace FourSlashInterface {
9921019
name,
9931020
kind: "method",
9941021
kindModifiers: "deprecated,declare",
995-
sortText: SortText.DeprecatedLocationPriority
1022+
sortText: "z11" as SortText,
9961023
});
9971024
const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({
9981025
name,

src/server/protocol.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3406,6 +3406,13 @@ namespace ts.server.protocol {
34063406
* `class A { foo }`.
34073407
*/
34083408
readonly includeCompletionsWithClassMemberSnippets?: boolean;
3409+
/**
3410+
* If enabled, object literal methods will have a method declaration completion entry in addition
3411+
* to the regular completion entry containing just the method name.
3412+
* E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`,
3413+
* in addition to `const objectLiteral: T = { foo }`.
3414+
*/
3415+
readonly includeCompletionsWithObjectLiteralMethodSnippets?: boolean;
34093416
readonly allowIncompleteCompletions?: boolean;
34103417
readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
34113418
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */

0 commit comments

Comments
 (0)