diff --git a/src/services/completions.ts b/src/services/completions.ts index 3421b5465d..9fe0703647 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -165,7 +165,7 @@ namespace ts.Completions { isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap, - symbolToSortTextMap, + symbolToSortTextMap ); } @@ -926,7 +926,7 @@ namespace ts.Completions { previousToken, isJsxInitializer, insideJsDocTagTypeExpression, - symbolToSortTextMap, + symbolToSortTextMap }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; @@ -985,6 +985,7 @@ namespace ts.Completions { symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node)); } + setSortTextToOptionalMember(); return; } @@ -994,11 +995,13 @@ namespace ts.Completions { if (isMetaProperty(node) && (node.keywordToken === SyntaxKind.NewKeyword || node.keywordToken === SyntaxKind.ImportKeyword)) { const completion = (node.keywordToken === SyntaxKind.NewKeyword) ? "target" : "meta"; symbols.push(typeChecker.createSymbol(SymbolFlags.Property, escapeLeadingUnderscores(completion))); + setSortTextToOptionalMember(); return; } if (!isTypeLocation) { addTypeProperties(typeChecker.getTypeAtLocation(node)); + setSortTextToOptionalMember(); } } @@ -1079,6 +1082,7 @@ namespace ts.Completions { const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); if (!attrsType) return GlobalsSearch.Continue; symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties); + setSortTextToOptionalMember(); completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; return GlobalsSearch.Success; @@ -1586,6 +1590,7 @@ namespace ts.Completions { return type && typeChecker.getPropertiesOfType(classElementModifierFlags & ModifierFlags.Static ? typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl) : type); }); symbols = filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags); + setSortTextToOptionalMember(); } return GlobalsSearch.Success; @@ -1879,7 +1884,7 @@ namespace ts.Completions { return contextualMemberSymbols; } - const fulfilledSymbols: Symbol[] = []; + const membersDeclaredBySpreadAssignment: Symbol[] = []; const existingMemberNames = createUnderscoreEscapedMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members @@ -1906,7 +1911,7 @@ namespace ts.Completions { const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); const properties = type && (type).properties; if (properties) { - fulfilledSymbols.push(...properties); + membersDeclaredBySpreadAssignment.push(...properties); } } else if (isBindingElement(m) && m.propertyName) { @@ -1927,17 +1932,20 @@ namespace ts.Completions { } const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName)); + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, contextualMemberSymbols); - // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment - for (const fulfilledSymbol of fulfilledSymbols) { - for (const contextualMemberSymbol of filteredSymbols) { + return filteredSymbols; + } + + // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment + function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Symbol[], contextualMemberSymbols: Symbol[]): void { + for (const fulfilledSymbol of membersDeclaredBySpreadAssignment) { + for (const contextualMemberSymbol of contextualMemberSymbols) { if (contextualMemberSymbol.name === fulfilledSymbol.name) { symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; } } } - - return filteredSymbols; } /** @@ -1991,6 +1999,7 @@ namespace ts.Completions { */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { const seenNames = createUnderscoreEscapedMap(); + const membersDeclaredBySpreadAssignment: Symbol[] = []; for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out if (isCurrentlyEditingNode(attr)) { @@ -2000,9 +2009,21 @@ namespace ts.Completions { if (attr.kind === SyntaxKind.JsxAttribute) { seenNames.set(attr.name.escapedText, true); } + else if (isJsxSpreadAttribute(attr)) { + const expression = attr.expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + const properties = type && (type).properties; + if (properties) { + membersDeclaredBySpreadAssignment.push(...properties); + } + } } + const filteredSymbols = symbols.filter(a => !seenNames.get(a.escapedName)); - return symbols.filter(a => !seenNames.get(a.escapedName)); + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, symbols); + + return filteredSymbols; } function isCurrentlyEditingNode(node: Node): boolean { diff --git a/tests/cases/fourslash/completionsOptionalKindModifier.ts b/tests/cases/fourslash/completionsOptionalKindModifier.ts index a8c2eb5d63..25bc0e0fde 100644 --- a/tests/cases/fourslash/completionsOptionalKindModifier.ts +++ b/tests/cases/fourslash/completionsOptionalKindModifier.ts @@ -8,7 +8,7 @@ verify.completions({ marker: "a", exact: [ - { name: "a", kind: "property", kindModifiers: "optional" }, - { name: "method", kind: "method", kindModifiers: "optional" }, + { name: "a", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "method", kind: "method", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, ], }); diff --git a/tests/cases/fourslash/completionsPropertiesPriorities.ts b/tests/cases/fourslash/completionsPropertiesPriorities.ts index 9ef22a4903..720a3d9494 100644 --- a/tests/cases/fourslash/completionsPropertiesPriorities.ts +++ b/tests/cases/fourslash/completionsPropertiesPriorities.ts @@ -18,12 +18,38 @@ //// /*a*/ //// } -verify.completions({ - marker: ['a'], - exact: [ - { name: 'B', kindModifiers: 'optional', sortText: completion.SortText.MemberDeclaredBySpreadAssignment, kind: 'property' }, - { name: 'a', sortText: completion.SortText.MemberDeclaredBySpreadAssignment, kind: 'property' }, - { name: 'c', kindModifiers: 'optional', sortText: completion.SortText.OptionalMember, kind: 'property' }, - { name: 'd', sortText: completion.SortText.LocationPriority, kind: 'property' } - ] -}); \ No newline at end of file +//// class A implements I { +//// /*b*/ +//// } + +const keywordEntries = ['private', 'protected', 'public', 'static', 'abstract', 'async', 'constructor', 'get', 'readonly', 'set'].map(keyword => { + return { + name: keyword, + kind: 'keyword', + kindModifiers: '', + sortText: completion.SortText.GlobalsOrKeywords + } +}); + +verify.completions( + { + marker: ['a'], + exact: [ + { name: 'B', kindModifiers: 'optional', sortText: completion.SortText.MemberDeclaredBySpreadAssignment, kind: 'property' }, + { name: 'a', sortText: completion.SortText.MemberDeclaredBySpreadAssignment, kind: 'property' }, + { name: 'c', kindModifiers: 'optional', sortText: completion.SortText.OptionalMember, kind: 'property' }, + { name: 'd', sortText: completion.SortText.LocationPriority, kind: 'property' } + ] + }, + { + marker: ['b'], + isNewIdentifierLocation: true, + exact:[ + { name: 'B', kindModifiers: 'optional', sortText: completion.SortText.OptionalMember, kind: 'property' }, + { name: 'a', sortText: completion.SortText.LocationPriority, kind: 'property' }, + { name: 'c', kindModifiers: 'optional', sortText: completion.SortText.OptionalMember, kind: 'property' }, + { name: 'd', sortText: completion.SortText.LocationPriority, kind: 'property' }, + ...keywordEntries + ] + } +); \ No newline at end of file diff --git a/tests/cases/fourslash/tsxCompletion12.ts b/tests/cases/fourslash/tsxCompletion12.ts index 3b3b3ac547..843ed1532f 100644 --- a/tests/cases/fourslash/tsxCompletion12.ts +++ b/tests/cases/fourslash/tsxCompletion12.ts @@ -23,7 +23,13 @@ //// let opt4 = ; verify.completions( - { marker: ["1", "2", "5"], exact: ["propx", "propString", "optional"] }, - { marker: "3", exact: ["propString", "optional"] }, + { + marker: ["1", "2", "5"], + exact: ["propx", "propString", { name: "optional", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }] + }, + { + marker: "3", + exact: ["propString", { name: "optional", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }] + }, { marker: "4", exact: "propString" }, ); diff --git a/tests/cases/fourslash/tsxCompletion13.ts b/tests/cases/fourslash/tsxCompletion13.ts index b1ac2c7af7..1fec1223d0 100644 --- a/tests/cases/fourslash/tsxCompletion13.ts +++ b/tests/cases/fourslash/tsxCompletion13.ts @@ -31,7 +31,28 @@ //// let opt = ; verify.completions( - { marker: ["1", "6"], exact: ["onClick", "children", "className", "goTo"] }, - { marker: "2", exact: ["onClick", "className", "goTo"] }, - { marker: ["3", "4", "5"], exact: ["children", "className"] }, + { + marker: ["1", "6"], + exact: [ + "onClick", + { name: "children", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + "goTo" + ] + }, + { + marker: "2", + exact: [ + "onClick", + { name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + "goTo" + ] + }, + { + marker: ["3", "4", "5"], + exact: [ + { name: "children", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember } + ] + }, ); diff --git a/tests/cases/fourslash/tsxCompletion7.ts b/tests/cases/fourslash/tsxCompletion7.ts index 87638f38e3..a54e860b35 100644 --- a/tests/cases/fourslash/tsxCompletion7.ts +++ b/tests/cases/fourslash/tsxCompletion7.ts @@ -10,4 +10,10 @@ //// let y = { ONE: '' }; //// var x =
; -verify.completions({ marker: "", exact: ["ONE", "TWO"] }); +verify.completions({ + marker: "", + exact: [ + { name: "ONE", kind: "JSX attribute", kindModifiers: "declare", sortText: completion.SortText.MemberDeclaredBySpreadAssignment }, + { name: "TWO", kind: "JSX attribute", kindModifiers: "declare", sortText: completion.SortText.LocationPriority } + ] +});