From 584be33e71c806766363c6c2009f2a527e13b9c3 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Fri, 5 Jul 2019 20:17:17 +0800 Subject: [PATCH 1/8] Add properties priority for completion --- src/harness/fourslash.ts | 14 +++++- src/services/completions.ts | 49 ++++++++++++++++--- src/services/types.ts | 1 + .../completionsPropertiesPriorities.ts | 29 +++++++++++ tests/cases/fourslash/fourslash.ts | 11 +++-- 5 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 tests/cases/fourslash/completionsPropertiesPriorities.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c26b059ced8e5..560017f7c6783 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -841,7 +841,19 @@ namespace FourSlash { assert.equal(actual.hasAction, hasAction); assert.equal(actual.isRecommended, isRecommended); assert.equal(actual.source, source); - assert.equal(actual.sortText, sortText || ts.Completions.SortText.LocationPriority, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`)); + + let isSortTextEqual: boolean; + if (!sortText) { + isSortTextEqual = actual.isFulfilled + ? actual.sortText === ts.Completions.SortText.LocationPriorityFulfilled + : actual.kindModifiers === 'optional' + ? actual.sortText === ts.Completions.SortText.LocationPriorityOptional + : actual.sortText === ts.Completions.SortText.LocationPriority + } + else { + isSortTextEqual = actual.sortText === sortText; + } + assert.equal(isSortTextEqual, true, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`)); if (text !== undefined) { const actualDetails = this.getCompletionEntryDetails(actual.name, actual.source)!; diff --git a/src/services/completions.ts b/src/services/completions.ts index 6d1d48b22b828..cf910df209854 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2,10 +2,12 @@ namespace ts.Completions { export enum SortText { LocationPriority = "0", - SuggestedClassMembers = "1", - GlobalsOrKeywords = "2", - AutoImportSuggestions = "3", - JavascriptIdentifiers = "4" + LocationPriorityOptional = "1", + LocationPriorityFulfilled = "2", + SuggestedClassMembers = "3", + GlobalsOrKeywords = "4", + AutoImportSuggestions = "5", + JavascriptIdentifiers = "6" } export type Log = (message: string) => void; @@ -103,6 +105,7 @@ namespace ts.Completions { isJsxInitializer, insideJsDocTagTypeExpression, symbolToSortTextMap, + fulfilledSymbols, } = completionData; if (location && location.parent && isJsxClosingElement(location.parent)) { @@ -163,7 +166,8 @@ namespace ts.Completions { isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap, - symbolToSortTextMap + symbolToSortTextMap, + fulfilledSymbols ); } @@ -240,6 +244,7 @@ namespace ts.Completions { propertyAccessToConvert: PropertyAccessExpression | undefined, isJsxInitializer: IsJsxInitializer | undefined, preferences: UserPreferences, + isFulfilled: boolean ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; @@ -286,6 +291,7 @@ namespace ts.Completions { isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)), insertText, replacementSpan, + isFulfilled }; } @@ -317,6 +323,7 @@ namespace ts.Completions { recommendedCompletion?: Symbol, symbolToOriginInfoMap?: SymbolOriginInfoMap, symbolToSortTextMap?: SymbolSortTextMap, + fulfilledSymbols?: ReadonlyArray, ): Map { const start = timestamp(); // Tracks unique names. @@ -335,9 +342,23 @@ namespace ts.Completions { continue; } + let sortText = symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)]; + let isFulfilled = false; + if (!sortText) { + fulfilledSymbols && fulfilledSymbols.forEach(fulfilledSymbol => { + if (fulfilledSymbol.name === symbol.name) { + sortText = SortText.LocationPriorityFulfilled; + isFulfilled = true; + } + }); + } + if (!sortText) { + sortText = SymbolDisplay.getSymbolModifiers(symbol) === 'optional' ? SortText.LocationPriorityOptional : SortText.LocationPriority; + } + const entry = createCompletionEntry( symbol, - symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)] || SortText.LocationPriority, + sortText, location, sourceFile, typeChecker, @@ -347,7 +368,8 @@ namespace ts.Completions { recommendedCompletion, propertyAccessToConvert, isJsxInitializer, - preferences + preferences, + isFulfilled ); if (!entry) { continue; @@ -581,6 +603,7 @@ namespace ts.Completions { readonly isJsxInitializer: IsJsxInitializer; readonly insideJsDocTagTypeExpression: boolean; readonly symbolToSortTextMap: SymbolSortTextMap; + readonly fulfilledSymbols?: ReadonlyArray; } type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; @@ -872,6 +895,7 @@ namespace ts.Completions { let isNewIdentifierLocation = false; let keywordFilters = KeywordCompletionFilters.None; let symbols: Symbol[] = []; + let fulfilledSymbols: Symbol[] | undefined = []; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; const symbolToSortTextMap: SymbolSortTextMap = []; @@ -924,7 +948,8 @@ namespace ts.Completions { previousToken, isJsxInitializer, insideJsDocTagTypeExpression, - symbolToSortTextMap + symbolToSortTextMap, + fulfilledSymbols }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; @@ -1494,6 +1519,14 @@ namespace ts.Completions { if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list symbols = filterObjectMembersList(typeMembers, Debug.assertDefined(existingMembers)); + existingMembers && existingMembers.forEach(member => { + if (member.kind === SyntaxKind.SpreadAssignment) { + const expression = (member).expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + fulfilledSymbols = type && (type).properties; + } + }); } return GlobalsSearch.Success; } diff --git a/src/services/types.ts b/src/services/types.ts index b97125734f7c5..ed18f7e0c45e0 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -919,6 +919,7 @@ namespace ts { hasAction?: true; source?: string; isRecommended?: true; + isFulfilled?: boolean; } export interface CompletionEntryDetails { diff --git a/tests/cases/fourslash/completionsPropertiesPriorities.ts b/tests/cases/fourslash/completionsPropertiesPriorities.ts new file mode 100644 index 0000000000000..8e7f7084e6164 --- /dev/null +++ b/tests/cases/fourslash/completionsPropertiesPriorities.ts @@ -0,0 +1,29 @@ +/// +// @strict: true + +//// interface I { +//// B?: number; +//// a: number; +//// c?: string; +//// d: string +//// } + +//// const foo = { +//// a: 1, +//// B: 2 +//// } + +//// const i: I = { +//// ...foo, +//// /*a*/ +//// } + +verify.completions({ + marker: ['a'], + exact: [ + { name: 'B', isFulfilled: true, kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, + { name: 'a', isFulfilled: true, sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, + { name: 'c', isFulfilled: false, kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityOptional, kind: 'property' }, + { name: 'd', isFulfilled: false, sortText: completion.SortText.LocationPriority, kind: 'property' } + ] +}); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 05799a156420a..0a129245e09ae 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -548,6 +548,7 @@ declare namespace FourSlashInterface { readonly kind?: string; readonly kindModifiers?: string; readonly sortText?: completion.SortText; + readonly isFulfilled?: boolean; // details readonly text?: string; @@ -672,10 +673,12 @@ declare namespace completion { type Entry = FourSlashInterface.ExpectedCompletionEntryObject; export const enum SortText { LocationPriority = "0", - SuggestedClassMembers = "1", - GlobalsOrKeywords = "2", - AutoImportSuggestions = "3", - JavascriptIdentifiers = "4" + LocationPriorityOptional = "1", + LocationPriorityFulfilled = "2", + SuggestedClassMembers = "3", + GlobalsOrKeywords = "4", + AutoImportSuggestions = "5", + JavascriptIdentifiers = "6" } export const globalThisEntry: Entry; export const undefinedVarEntry: Entry; From e3b812ad4f59e1011b2f15ba3e8940e3644b858f Mon Sep 17 00:00:00 2001 From: xiaofa Date: Fri, 12 Jul 2019 02:52:29 +0800 Subject: [PATCH 2/8] delete isfulfilled to pass tests --- src/harness/fourslash.ts | 7 +--- src/services/completions.ts | 37 +++++++++---------- src/services/types.ts | 1 - .../completionsPropertiesPriorities.ts | 8 ++-- tests/cases/fourslash/fourslash.ts | 1 - 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 560017f7c6783..2984618cbf3f4 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -844,11 +844,8 @@ namespace FourSlash { let isSortTextEqual: boolean; if (!sortText) { - isSortTextEqual = actual.isFulfilled - ? actual.sortText === ts.Completions.SortText.LocationPriorityFulfilled - : actual.kindModifiers === 'optional' - ? actual.sortText === ts.Completions.SortText.LocationPriorityOptional - : actual.sortText === ts.Completions.SortText.LocationPriority + isSortTextEqual = [ts.Completions.SortText.LocationPriorityFulfilled, ts.Completions.SortText.LocationPriority].indexOf(actual.sortText as ts.Completions.SortText) !== -1 + || (actual.kindModifiers === "optional" && actual.sortText === ts.Completions.SortText.LocationPriorityOptional); } else { isSortTextEqual = actual.sortText === sortText; diff --git a/src/services/completions.ts b/src/services/completions.ts index cf910df209854..0cc9b774c7e5e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -244,7 +244,6 @@ namespace ts.Completions { propertyAccessToConvert: PropertyAccessExpression | undefined, isJsxInitializer: IsJsxInitializer | undefined, preferences: UserPreferences, - isFulfilled: boolean ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; @@ -291,7 +290,6 @@ namespace ts.Completions { isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)), insertText, replacementSpan, - isFulfilled }; } @@ -343,17 +341,17 @@ namespace ts.Completions { } let sortText = symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)]; - let isFulfilled = false; if (!sortText) { - fulfilledSymbols && fulfilledSymbols.forEach(fulfilledSymbol => { - if (fulfilledSymbol.name === symbol.name) { - sortText = SortText.LocationPriorityFulfilled; - isFulfilled = true; - } - }); + if (fulfilledSymbols && fulfilledSymbols.length > 0) { + fulfilledSymbols.forEach(fulfilledSymbol => { + if (fulfilledSymbol.name === symbol.name) { + sortText = SortText.LocationPriorityFulfilled; + } + }); + } } if (!sortText) { - sortText = SymbolDisplay.getSymbolModifiers(symbol) === 'optional' ? SortText.LocationPriorityOptional : SortText.LocationPriority; + sortText = SymbolDisplay.getSymbolModifiers(symbol) === "optional" ? SortText.LocationPriorityOptional : SortText.LocationPriority; } const entry = createCompletionEntry( @@ -369,7 +367,6 @@ namespace ts.Completions { propertyAccessToConvert, isJsxInitializer, preferences, - isFulfilled ); if (!entry) { continue; @@ -1519,14 +1516,16 @@ namespace ts.Completions { if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list symbols = filterObjectMembersList(typeMembers, Debug.assertDefined(existingMembers)); - existingMembers && existingMembers.forEach(member => { - if (member.kind === SyntaxKind.SpreadAssignment) { - const expression = (member).expression; - const symbol = typeChecker.getSymbolAtLocation(expression); - const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); - fulfilledSymbols = type && (type).properties; - } - }); + if (existingMembers && existingMembers.length > 0) { + existingMembers.forEach(member => { + if (member.kind === SyntaxKind.SpreadAssignment) { + const expression = (member).expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + fulfilledSymbols = type && (type).properties; + } + }); + } } return GlobalsSearch.Success; } diff --git a/src/services/types.ts b/src/services/types.ts index ed18f7e0c45e0..b97125734f7c5 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -919,7 +919,6 @@ namespace ts { hasAction?: true; source?: string; isRecommended?: true; - isFulfilled?: boolean; } export interface CompletionEntryDetails { diff --git a/tests/cases/fourslash/completionsPropertiesPriorities.ts b/tests/cases/fourslash/completionsPropertiesPriorities.ts index 8e7f7084e6164..beae7b87a0478 100644 --- a/tests/cases/fourslash/completionsPropertiesPriorities.ts +++ b/tests/cases/fourslash/completionsPropertiesPriorities.ts @@ -21,9 +21,9 @@ verify.completions({ marker: ['a'], exact: [ - { name: 'B', isFulfilled: true, kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, - { name: 'a', isFulfilled: true, sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, - { name: 'c', isFulfilled: false, kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityOptional, kind: 'property' }, - { name: 'd', isFulfilled: false, sortText: completion.SortText.LocationPriority, kind: 'property' } + { name: 'B', kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, + { name: 'a', sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, + { name: 'c', kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityOptional, kind: 'property' }, + { name: 'd', sortText: completion.SortText.LocationPriority, kind: 'property' } ] }); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 0a129245e09ae..8b448d36adb0a 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -548,7 +548,6 @@ declare namespace FourSlashInterface { readonly kind?: string; readonly kindModifiers?: string; readonly sortText?: completion.SortText; - readonly isFulfilled?: boolean; // details readonly text?: string; From 8aa39d6cb85dc60e0229ebf3ca4cc8402006dee1 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Thu, 18 Jul 2019 12:33:21 +0800 Subject: [PATCH 3/8] Set sort text while adding sysmbols --- src/harness/fourslash.ts | 11 +-- src/services/completions.ts | 77 ++++++++++--------- .../completionsPropertiesPriorities.ts | 6 +- .../completionsWithOptionalProperties.ts | 4 +- tests/cases/fourslash/fourslash.ts | 4 +- 5 files changed, 49 insertions(+), 53 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2984618cbf3f4..c26b059ced8e5 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -841,16 +841,7 @@ namespace FourSlash { assert.equal(actual.hasAction, hasAction); assert.equal(actual.isRecommended, isRecommended); assert.equal(actual.source, source); - - let isSortTextEqual: boolean; - if (!sortText) { - isSortTextEqual = [ts.Completions.SortText.LocationPriorityFulfilled, ts.Completions.SortText.LocationPriority].indexOf(actual.sortText as ts.Completions.SortText) !== -1 - || (actual.kindModifiers === "optional" && actual.sortText === ts.Completions.SortText.LocationPriorityOptional); - } - else { - isSortTextEqual = actual.sortText === sortText; - } - assert.equal(isSortTextEqual, true, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`)); + assert.equal(actual.sortText, sortText || ts.Completions.SortText.LocationPriority, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`)); if (text !== undefined) { const actualDetails = this.getCompletionEntryDetails(actual.name, actual.source)!; diff --git a/src/services/completions.ts b/src/services/completions.ts index 0cc9b774c7e5e..3421b5465d5de 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2,8 +2,8 @@ namespace ts.Completions { export enum SortText { LocationPriority = "0", - LocationPriorityOptional = "1", - LocationPriorityFulfilled = "2", + OptionalMember = "1", + MemberDeclaredBySpreadAssignment = "2", SuggestedClassMembers = "3", GlobalsOrKeywords = "4", AutoImportSuggestions = "5", @@ -105,7 +105,6 @@ namespace ts.Completions { isJsxInitializer, insideJsDocTagTypeExpression, symbolToSortTextMap, - fulfilledSymbols, } = completionData; if (location && location.parent && isJsxClosingElement(location.parent)) { @@ -167,7 +166,6 @@ namespace ts.Completions { recommendedCompletion, symbolToOriginInfoMap, symbolToSortTextMap, - fulfilledSymbols ); } @@ -321,7 +319,6 @@ namespace ts.Completions { recommendedCompletion?: Symbol, symbolToOriginInfoMap?: SymbolOriginInfoMap, symbolToSortTextMap?: SymbolSortTextMap, - fulfilledSymbols?: ReadonlyArray, ): Map { const start = timestamp(); // Tracks unique names. @@ -340,23 +337,9 @@ namespace ts.Completions { continue; } - let sortText = symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)]; - if (!sortText) { - if (fulfilledSymbols && fulfilledSymbols.length > 0) { - fulfilledSymbols.forEach(fulfilledSymbol => { - if (fulfilledSymbol.name === symbol.name) { - sortText = SortText.LocationPriorityFulfilled; - } - }); - } - } - if (!sortText) { - sortText = SymbolDisplay.getSymbolModifiers(symbol) === "optional" ? SortText.LocationPriorityOptional : SortText.LocationPriority; - } - const entry = createCompletionEntry( symbol, - sortText, + symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)] || SortText.LocationPriority, location, sourceFile, typeChecker, @@ -366,7 +349,7 @@ namespace ts.Completions { recommendedCompletion, propertyAccessToConvert, isJsxInitializer, - preferences, + preferences ); if (!entry) { continue; @@ -600,7 +583,6 @@ namespace ts.Completions { readonly isJsxInitializer: IsJsxInitializer; readonly insideJsDocTagTypeExpression: boolean; readonly symbolToSortTextMap: SymbolSortTextMap; - readonly fulfilledSymbols?: ReadonlyArray; } type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; @@ -892,7 +874,6 @@ namespace ts.Completions { let isNewIdentifierLocation = false; let keywordFilters = KeywordCompletionFilters.None; let symbols: Symbol[] = []; - let fulfilledSymbols: Symbol[] | undefined = []; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; const symbolToSortTextMap: SymbolSortTextMap = []; @@ -946,7 +927,6 @@ namespace ts.Completions { isJsxInitializer, insideJsDocTagTypeExpression, symbolToSortTextMap, - fulfilledSymbols }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; @@ -1516,20 +1496,21 @@ namespace ts.Completions { if (typeMembers && typeMembers.length > 0) { // Add filtered items to the completion list symbols = filterObjectMembersList(typeMembers, Debug.assertDefined(existingMembers)); - if (existingMembers && existingMembers.length > 0) { - existingMembers.forEach(member => { - if (member.kind === SyntaxKind.SpreadAssignment) { - const expression = (member).expression; - const symbol = typeChecker.getSymbolAtLocation(expression); - const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); - fulfilledSymbols = type && (type).properties; - } - }); - } } + setSortTextToOptionalMember(); + return GlobalsSearch.Success; } + // Set SortText to OptionalMember if it is an optinoal member + function setSortTextToOptionalMember() { + symbols.forEach(m => { + if (SymbolDisplay.getSymbolModifiers(m) === "optional") { + symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember; + } + }); + } + /** * Aggregates relevant symbols for completion in import clauses and export clauses * whose declarations have a module specifier; for instance, symbols will be aggregated for @@ -1898,6 +1879,7 @@ namespace ts.Completions { return contextualMemberSymbols; } + const fulfilledSymbols: Symbol[] = []; const existingMemberNames = createUnderscoreEscapedMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members @@ -1906,7 +1888,8 @@ namespace ts.Completions { m.kind !== SyntaxKind.BindingElement && m.kind !== SyntaxKind.MethodDeclaration && m.kind !== SyntaxKind.GetAccessor && - m.kind !== SyntaxKind.SetAccessor) { + m.kind !== SyntaxKind.SetAccessor && + m.kind !== SyntaxKind.SpreadAssignment) { continue; } @@ -1917,7 +1900,16 @@ namespace ts.Completions { let existingName: __String | undefined; - if (isBindingElement(m) && m.propertyName) { + if (isSpreadAssignment(m)) { + const expression = m.expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + const properties = type && (type).properties; + if (properties) { + fulfilledSymbols.push(...properties); + } + } + else if (isBindingElement(m) && m.propertyName) { // include only identifiers in completion list if (m.propertyName.kind === SyntaxKind.Identifier) { existingName = m.propertyName.escapedText; @@ -1934,7 +1926,18 @@ namespace ts.Completions { existingMemberNames.set(existingName!, true); // TODO: GH#18217 } - return contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName)); + const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName)); + + // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment + for (const fulfilledSymbol of fulfilledSymbols) { + for (const contextualMemberSymbol of filteredSymbols) { + if (contextualMemberSymbol.name === fulfilledSymbol.name) { + symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; + } + } + } + + return filteredSymbols; } /** diff --git a/tests/cases/fourslash/completionsPropertiesPriorities.ts b/tests/cases/fourslash/completionsPropertiesPriorities.ts index beae7b87a0478..9ef22a49034fe 100644 --- a/tests/cases/fourslash/completionsPropertiesPriorities.ts +++ b/tests/cases/fourslash/completionsPropertiesPriorities.ts @@ -21,9 +21,9 @@ verify.completions({ marker: ['a'], exact: [ - { name: 'B', kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, - { name: 'a', sortText: completion.SortText.LocationPriorityFulfilled, kind: 'property' }, - { name: 'c', kindModifiers: 'optional', sortText: completion.SortText.LocationPriorityOptional, kind: 'property' }, + { 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 diff --git a/tests/cases/fourslash/completionsWithOptionalProperties.ts b/tests/cases/fourslash/completionsWithOptionalProperties.ts index e40029ddefac0..78b0564647415 100644 --- a/tests/cases/fourslash/completionsWithOptionalProperties.ts +++ b/tests/cases/fourslash/completionsWithOptionalProperties.ts @@ -13,6 +13,8 @@ verify.completions({ marker: "", - includes: ['world'] + exact: [ + { name: "world", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember } + ] }); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 8b448d36adb0a..d242e51d32b6b 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -672,8 +672,8 @@ declare namespace completion { type Entry = FourSlashInterface.ExpectedCompletionEntryObject; export const enum SortText { LocationPriority = "0", - LocationPriorityOptional = "1", - LocationPriorityFulfilled = "2", + OptionalMember = "1", + MemberDeclaredBySpreadAssignment = "2", SuggestedClassMembers = "3", GlobalsOrKeywords = "4", AutoImportSuggestions = "5", From 6a50507aadfc398c2934c454cb93a8fbf19de377 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Thu, 18 Jul 2019 13:52:51 +0800 Subject: [PATCH 4/8] sort text for class member, JSX attribute, RHS of dot --- src/services/completions.ts | 41 ++++++++++++----- .../completionsOptionalKindModifier.ts | 4 +- .../completionsPropertiesPriorities.ts | 44 +++++++++++++++---- tests/cases/fourslash/tsxCompletion12.ts | 10 ++++- tests/cases/fourslash/tsxCompletion13.ts | 27 ++++++++++-- tests/cases/fourslash/tsxCompletion7.ts | 8 +++- 6 files changed, 107 insertions(+), 27 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 3421b5465d5de..9fe0703647123 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); + + return filteredSymbols; + } - // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment - for (const fulfilledSymbol of fulfilledSymbols) { - for (const contextualMemberSymbol of 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 a8c2eb5d63bd8..25bc0e0fde817 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 9ef22a49034fe..720a3d949459c 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 3b3b3ac547095..843ed1532ffd5 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 b1ac2c7af7a2a..1fec1223d0cc0 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 87638f38e3805..a54e860b35f21 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 } + ] +}); From 615cfaa8915ef70ff65791eefcb5f4444acd3d74 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Fri, 19 Jul 2019 11:37:28 +0800 Subject: [PATCH 5/8] use a map instead of an array --- src/services/completions.ts | 45 ++++++++++--------- ...etionsAtIncompleteObjectLiteralProperty.ts | 2 +- .../fourslash/completionsOptionalMethod.ts | 5 ++- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 9fe0703647123..1206030437c6e 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1506,15 +1506,6 @@ namespace ts.Completions { return GlobalsSearch.Success; } - // Set SortText to OptionalMember if it is an optinoal member - function setSortTextToOptionalMember() { - symbols.forEach(m => { - if (SymbolDisplay.getSymbolModifiers(m) === "optional") { - symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember; - } - }); - } - /** * Aggregates relevant symbols for completion in import clauses and export clauses * whose declarations have a module specifier; for instance, symbols will be aggregated for @@ -1884,7 +1875,7 @@ namespace ts.Completions { return contextualMemberSymbols; } - const membersDeclaredBySpreadAssignment: Symbol[] = []; + const membersDeclaredBySpreadAssignment = createMap(); const existingMemberNames = createUnderscoreEscapedMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members @@ -1911,7 +1902,9 @@ namespace ts.Completions { const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); const properties = type && (type).properties; if (properties) { - membersDeclaredBySpreadAssignment.push(...properties); + properties.forEach(property => { + membersDeclaredBySpreadAssignment.set(property.name, true); + }); } } else if (isBindingElement(m) && m.propertyName) { @@ -1932,18 +1925,28 @@ namespace ts.Completions { } const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName)); - setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, contextualMemberSymbols); + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); return filteredSymbols; } + // Set SortText to OptionalMember if it is an optinoal member + function setSortTextToOptionalMember() { + symbols.forEach(m => { + if (m.flags & SymbolFlags.Optional) { + symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember; + } + }); + } + // 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; - } + function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Map, contextualMemberSymbols: Symbol[]): void { + if (membersDeclaredBySpreadAssignment.size === 0) { + return; + } + for (const contextualMemberSymbol of contextualMemberSymbols) { + if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { + symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; } } } @@ -1999,7 +2002,7 @@ namespace ts.Completions { */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { const seenNames = createUnderscoreEscapedMap(); - const membersDeclaredBySpreadAssignment: Symbol[] = []; + const membersDeclaredBySpreadAssignment = createMap(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out if (isCurrentlyEditingNode(attr)) { @@ -2015,7 +2018,9 @@ namespace ts.Completions { const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); const properties = type && (type).properties; if (properties) { - membersDeclaredBySpreadAssignment.push(...properties); + properties.forEach(property => { + membersDeclaredBySpreadAssignment.set(property.name, true); + }); } } } diff --git a/tests/cases/fourslash/completionsAtIncompleteObjectLiteralProperty.ts b/tests/cases/fourslash/completionsAtIncompleteObjectLiteralProperty.ts index 130b70de94919..3682b31004034 100644 --- a/tests/cases/fourslash/completionsAtIncompleteObjectLiteralProperty.ts +++ b/tests/cases/fourslash/completionsAtIncompleteObjectLiteralProperty.ts @@ -10,5 +10,5 @@ verify.completions({ marker: "", - exact: ["abc"], + exact: [{ name: 'abc', kind: 'property', kindModifiers: 'declare,optional', sortText: completion.SortText.OptionalMember }], }); diff --git a/tests/cases/fourslash/completionsOptionalMethod.ts b/tests/cases/fourslash/completionsOptionalMethod.ts index 4c70e31606299..a08882a2cc94a 100644 --- a/tests/cases/fourslash/completionsOptionalMethod.ts +++ b/tests/cases/fourslash/completionsOptionalMethod.ts @@ -5,4 +5,7 @@ ////declare const x: { m?(): void }; ////x./**/ -verify.completions({ marker: "", exact: "m" }); +verify.completions({ + marker: "", + exact: { name: 'm', kind: 'method', kindModifiers: 'declare,optional', sortText: completion.SortText.OptionalMember } +}); From 3d7bda0ad66e922453ff4dd1494b0911979c2c70 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Thu, 25 Jul 2019 11:49:54 +0800 Subject: [PATCH 6/8] prioritize exclude class or right of dot --- src/services/completions.ts | 6 +---- .../completionsOptionalKindModifier.ts | 4 ++-- .../fourslash/completionsOptionalMethod.ts | 5 +--- .../completionsPropertiesPriorities.ts | 24 ------------------- 4 files changed, 4 insertions(+), 35 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 1206030437c6e..97b67dd670167 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -985,7 +985,6 @@ 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; } @@ -995,13 +994,11 @@ 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(); } } @@ -1581,7 +1578,6 @@ 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; @@ -2026,7 +2022,7 @@ namespace ts.Completions { } const filteredSymbols = symbols.filter(a => !seenNames.get(a.escapedName)); - setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, symbols); + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); return filteredSymbols; } diff --git a/tests/cases/fourslash/completionsOptionalKindModifier.ts b/tests/cases/fourslash/completionsOptionalKindModifier.ts index 25bc0e0fde817..a8c2eb5d63bd8 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", sortText: completion.SortText.OptionalMember }, - { name: "method", kind: "method", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "a", kind: "property", kindModifiers: "optional" }, + { name: "method", kind: "method", kindModifiers: "optional" }, ], }); diff --git a/tests/cases/fourslash/completionsOptionalMethod.ts b/tests/cases/fourslash/completionsOptionalMethod.ts index a08882a2cc94a..b585dca345c04 100644 --- a/tests/cases/fourslash/completionsOptionalMethod.ts +++ b/tests/cases/fourslash/completionsOptionalMethod.ts @@ -5,7 +5,4 @@ ////declare const x: { m?(): void }; ////x./**/ -verify.completions({ - marker: "", - exact: { name: 'm', kind: 'method', kindModifiers: 'declare,optional', sortText: completion.SortText.OptionalMember } -}); +verify.completions({ marker: "", exact: "m" }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsPropertiesPriorities.ts b/tests/cases/fourslash/completionsPropertiesPriorities.ts index 720a3d949459c..f2269243e31ed 100644 --- a/tests/cases/fourslash/completionsPropertiesPriorities.ts +++ b/tests/cases/fourslash/completionsPropertiesPriorities.ts @@ -18,19 +18,6 @@ //// /*a*/ //// } -//// 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'], @@ -40,16 +27,5 @@ verify.completions( { 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 From 2f1effb9936928f2af2b43730ad83af9bd7c9550 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Fri, 26 Jul 2019 12:33:09 +0800 Subject: [PATCH 7/8] scope out duplicate code into setMembersDeclaredBySpreadAssignment function --- src/services/completions.ts | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 97b67dd670167..d86b094d3dc95 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1893,15 +1893,7 @@ namespace ts.Completions { let existingName: __String | undefined; if (isSpreadAssignment(m)) { - const expression = m.expression; - const symbol = typeChecker.getSymbolAtLocation(expression); - const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); - const properties = type && (type).properties; - if (properties) { - properties.forEach(property => { - membersDeclaredBySpreadAssignment.set(property.name, true); - }); - } + setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); } else if (isBindingElement(m) && m.propertyName) { // include only identifiers in completion list @@ -1926,6 +1918,18 @@ namespace ts.Completions { return filteredSymbols; } + function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Map) { + const expression = declaration.expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + const properties = type && (type).properties; + if (properties) { + properties.forEach(property => { + membersDeclaredBySpreadAssignment.set(property.name, true); + }); + } + } + // Set SortText to OptionalMember if it is an optinoal member function setSortTextToOptionalMember() { symbols.forEach(m => { @@ -2009,15 +2013,7 @@ namespace ts.Completions { 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) { - properties.forEach(property => { - membersDeclaredBySpreadAssignment.set(property.name, true); - }); - } + setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); } } const filteredSymbols = symbols.filter(a => !seenNames.get(a.escapedName)); From 61551bc5747e5eea4721b12b4f0afc4dd0abdcf6 Mon Sep 17 00:00:00 2001 From: xiaofa Date: Tue, 20 Aug 2019 00:44:40 +0800 Subject: [PATCH 8/8] change createMap to createMap --- src/services/completions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index d86b094d3dc95..a6fd37fde6880 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1871,7 +1871,7 @@ namespace ts.Completions { return contextualMemberSymbols; } - const membersDeclaredBySpreadAssignment = createMap(); + const membersDeclaredBySpreadAssignment = createMap(); const existingMemberNames = createUnderscoreEscapedMap(); for (const m of existingMembers) { // Ignore omitted expressions for missing members @@ -1918,7 +1918,7 @@ namespace ts.Completions { return filteredSymbols; } - function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Map) { + function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Map) { const expression = declaration.expression; const symbol = typeChecker.getSymbolAtLocation(expression); const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); @@ -1940,7 +1940,7 @@ namespace ts.Completions { } // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment - function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Map, contextualMemberSymbols: Symbol[]): void { + function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Map, contextualMemberSymbols: Symbol[]): void { if (membersDeclaredBySpreadAssignment.size === 0) { return; } @@ -2002,7 +2002,7 @@ namespace ts.Completions { */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { const seenNames = createUnderscoreEscapedMap(); - const membersDeclaredBySpreadAssignment = createMap(); + const membersDeclaredBySpreadAssignment = createMap(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out if (isCurrentlyEditingNode(attr)) {