Skip to content

Commit c26c44d

Browse files
authored
Merge pull request #32266 from fuafa/properties-priorities
Add properties priority for completion
2 parents b3ec4ed + 61551bc commit c26c44d

9 files changed

+142
-21
lines changed

src/services/completions.ts

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
namespace ts.Completions {
33
export enum SortText {
44
LocationPriority = "0",
5-
SuggestedClassMembers = "1",
6-
GlobalsOrKeywords = "2",
7-
AutoImportSuggestions = "3",
8-
JavascriptIdentifiers = "4"
5+
OptionalMember = "1",
6+
MemberDeclaredBySpreadAssignment = "2",
7+
SuggestedClassMembers = "3",
8+
GlobalsOrKeywords = "4",
9+
AutoImportSuggestions = "5",
10+
JavascriptIdentifiers = "6"
911
}
1012
export type Log = (message: string) => void;
1113

@@ -1109,6 +1111,7 @@ namespace ts.Completions {
11091111
const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes);
11101112
if (!attrsType) return GlobalsSearch.Continue;
11111113
symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties);
1114+
setSortTextToOptionalMember();
11121115
completionKind = CompletionKind.MemberLike;
11131116
isNewIdentifierLocation = false;
11141117
return GlobalsSearch.Success;
@@ -1539,6 +1542,8 @@ namespace ts.Completions {
15391542
// Add filtered items to the completion list
15401543
symbols = filterObjectMembersList(typeMembers, Debug.assertDefined(existingMembers));
15411544
}
1545+
setSortTextToOptionalMember();
1546+
15421547
return GlobalsSearch.Success;
15431548
}
15441549

@@ -1910,6 +1915,7 @@ namespace ts.Completions {
19101915
return contextualMemberSymbols;
19111916
}
19121917

1918+
const membersDeclaredBySpreadAssignment = createMap<true>();
19131919
const existingMemberNames = createUnderscoreEscapedMap<boolean>();
19141920
for (const m of existingMembers) {
19151921
// Ignore omitted expressions for missing members
@@ -1918,7 +1924,8 @@ namespace ts.Completions {
19181924
m.kind !== SyntaxKind.BindingElement &&
19191925
m.kind !== SyntaxKind.MethodDeclaration &&
19201926
m.kind !== SyntaxKind.GetAccessor &&
1921-
m.kind !== SyntaxKind.SetAccessor) {
1927+
m.kind !== SyntaxKind.SetAccessor &&
1928+
m.kind !== SyntaxKind.SpreadAssignment) {
19221929
continue;
19231930
}
19241931

@@ -1929,7 +1936,10 @@ namespace ts.Completions {
19291936

19301937
let existingName: __String | undefined;
19311938

1932-
if (isBindingElement(m) && m.propertyName) {
1939+
if (isSpreadAssignment(m)) {
1940+
setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment);
1941+
}
1942+
else if (isBindingElement(m) && m.propertyName) {
19331943
// include only identifiers in completion list
19341944
if (m.propertyName.kind === SyntaxKind.Identifier) {
19351945
existingName = m.propertyName.escapedText;
@@ -1946,7 +1956,43 @@ namespace ts.Completions {
19461956
existingMemberNames.set(existingName!, true); // TODO: GH#18217
19471957
}
19481958

1949-
return contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName));
1959+
const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName));
1960+
setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols);
1961+
1962+
return filteredSymbols;
1963+
}
1964+
1965+
function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Map<true>) {
1966+
const expression = declaration.expression;
1967+
const symbol = typeChecker.getSymbolAtLocation(expression);
1968+
const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression);
1969+
const properties = type && (<ObjectType>type).properties;
1970+
if (properties) {
1971+
properties.forEach(property => {
1972+
membersDeclaredBySpreadAssignment.set(property.name, true);
1973+
});
1974+
}
1975+
}
1976+
1977+
// Set SortText to OptionalMember if it is an optinoal member
1978+
function setSortTextToOptionalMember() {
1979+
symbols.forEach(m => {
1980+
if (m.flags & SymbolFlags.Optional) {
1981+
symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember;
1982+
}
1983+
});
1984+
}
1985+
1986+
// Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment
1987+
function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Map<true>, contextualMemberSymbols: Symbol[]): void {
1988+
if (membersDeclaredBySpreadAssignment.size === 0) {
1989+
return;
1990+
}
1991+
for (const contextualMemberSymbol of contextualMemberSymbols) {
1992+
if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) {
1993+
symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment;
1994+
}
1995+
}
19501996
}
19511997

19521998
/**
@@ -2000,6 +2046,7 @@ namespace ts.Completions {
20002046
*/
20012047
function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>): Symbol[] {
20022048
const seenNames = createUnderscoreEscapedMap<boolean>();
2049+
const membersDeclaredBySpreadAssignment = createMap<true>();
20032050
for (const attr of attributes) {
20042051
// If this is the current item we are editing right now, do not filter it out
20052052
if (isCurrentlyEditingNode(attr)) {
@@ -2009,9 +2056,15 @@ namespace ts.Completions {
20092056
if (attr.kind === SyntaxKind.JsxAttribute) {
20102057
seenNames.set(attr.name.escapedText, true);
20112058
}
2059+
else if (isJsxSpreadAttribute(attr)) {
2060+
setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment);
2061+
}
20122062
}
2063+
const filteredSymbols = symbols.filter(a => !seenNames.get(a.escapedName));
2064+
2065+
setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols);
20132066

2014-
return symbols.filter(a => !seenNames.get(a.escapedName));
2067+
return filteredSymbols;
20152068
}
20162069

20172070
function isCurrentlyEditingNode(node: Node): boolean {

tests/cases/fourslash/completionsAtIncompleteObjectLiteralProperty.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010

1111
verify.completions({
1212
marker: "",
13-
exact: ["abc"],
13+
exact: [{ name: 'abc', kind: 'property', kindModifiers: 'declare,optional', sortText: completion.SortText.OptionalMember }],
1414
});

tests/cases/fourslash/completionsOptionalMethod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
////declare const x: { m?(): void };
66
////x./**/
77

8-
verify.completions({ marker: "", exact: "m" });
8+
verify.completions({ marker: "", exact: "m" });
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference path="fourslash.ts" />
2+
// @strict: true
3+
4+
//// interface I {
5+
//// B?: number;
6+
//// a: number;
7+
//// c?: string;
8+
//// d: string
9+
//// }
10+
11+
//// const foo = {
12+
//// a: 1,
13+
//// B: 2
14+
//// }
15+
16+
//// const i: I = {
17+
//// ...foo,
18+
//// /*a*/
19+
//// }
20+
21+
verify.completions(
22+
{
23+
marker: ['a'],
24+
exact: [
25+
{ name: 'B', kindModifiers: 'optional', sortText: completion.SortText.MemberDeclaredBySpreadAssignment, kind: 'property' },
26+
{ name: 'a', sortText: completion.SortText.MemberDeclaredBySpreadAssignment, kind: 'property' },
27+
{ name: 'c', kindModifiers: 'optional', sortText: completion.SortText.OptionalMember, kind: 'property' },
28+
{ name: 'd', sortText: completion.SortText.LocationPriority, kind: 'property' }
29+
]
30+
}
31+
);

tests/cases/fourslash/completionsWithOptionalProperties.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
verify.completions({
1515
marker: "",
16-
includes: ['world']
16+
exact: [
17+
{ name: "world", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }
18+
]
1719
});
1820

tests/cases/fourslash/fourslash.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -672,10 +672,12 @@ declare namespace completion {
672672
type Entry = FourSlashInterface.ExpectedCompletionEntryObject;
673673
export const enum SortText {
674674
LocationPriority = "0",
675-
SuggestedClassMembers = "1",
676-
GlobalsOrKeywords = "2",
677-
AutoImportSuggestions = "3",
678-
JavascriptIdentifiers = "4"
675+
OptionalMember = "1",
676+
MemberDeclaredBySpreadAssignment = "2",
677+
SuggestedClassMembers = "3",
678+
GlobalsOrKeywords = "4",
679+
AutoImportSuggestions = "5",
680+
JavascriptIdentifiers = "6"
679681
}
680682
export const globalThisEntry: Entry;
681683
export const undefinedVarEntry: Entry;

tests/cases/fourslash/tsxCompletion12.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323
//// let opt4 = <Opt wrong /*5*/ />;
2424

2525
verify.completions(
26-
{ marker: ["1", "2", "5"], exact: ["propx", "propString", "optional"] },
27-
{ marker: "3", exact: ["propString", "optional"] },
26+
{
27+
marker: ["1", "2", "5"],
28+
exact: ["propx", "propString", { name: "optional", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }]
29+
},
30+
{
31+
marker: "3",
32+
exact: ["propString", { name: "optional", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }]
33+
},
2834
{ marker: "4", exact: "propString" },
2935
);

tests/cases/fourslash/tsxCompletion13.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,28 @@
3131
//// let opt = <MainButton wrong /*6*/ />;
3232

3333
verify.completions(
34-
{ marker: ["1", "6"], exact: ["onClick", "children", "className", "goTo"] },
35-
{ marker: "2", exact: ["onClick", "className", "goTo"] },
36-
{ marker: ["3", "4", "5"], exact: ["children", "className"] },
34+
{
35+
marker: ["1", "6"],
36+
exact: [
37+
"onClick",
38+
{ name: "children", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember },
39+
{ name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember },
40+
"goTo"
41+
]
42+
},
43+
{
44+
marker: "2",
45+
exact: [
46+
"onClick",
47+
{ name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember },
48+
"goTo"
49+
]
50+
},
51+
{
52+
marker: ["3", "4", "5"],
53+
exact: [
54+
{ name: "children", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember },
55+
{ name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }
56+
]
57+
},
3758
);

tests/cases/fourslash/tsxCompletion7.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@
1010
//// let y = { ONE: '' };
1111
//// var x = <div {...y} /**/ />;
1212

13-
verify.completions({ marker: "", exact: ["ONE", "TWO"] });
13+
verify.completions({
14+
marker: "",
15+
exact: [
16+
{ name: "ONE", kind: "JSX attribute", kindModifiers: "declare", sortText: completion.SortText.MemberDeclaredBySpreadAssignment },
17+
{ name: "TWO", kind: "JSX attribute", kindModifiers: "declare", sortText: completion.SortText.LocationPriority }
18+
]
19+
});

0 commit comments

Comments
 (0)