From 12d5063e4ce3dcb84e0a2458196c403d201658e7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 4 Jan 2018 16:36:31 -0800 Subject: [PATCH] Add kindModifier to mark if a completion item is for an optional field (#20839) * Add kindModifier to mark if a completion item is for an optional field For #12458 Adds a new KindModifier for completion items to mark when a property is optional. This can be used by editors to either change the item icon or add a `?` to the item text * Add method test * Baseline-accept --- src/harness/fourslash.ts | 24 ++++++++++++++----- src/services/symbolDisplay.ts | 7 +++++- src/services/types.ts | 1 + .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../completionsOptionalKindModifier.ts | 10 ++++++++ tests/cases/fourslash/fourslash.ts | 2 +- 7 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/completionsOptionalKindModifier.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 99d643ec438..9ab7dd31ffd 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -872,7 +872,7 @@ namespace FourSlash { }); } - public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean, options?: FourSlashInterface.VerifyCompletionListContainsOptions) { + public verifyCompletionListContains(entryId: ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, options?: FourSlashInterface.VerifyCompletionListContainsOptions) { const completions = this.getCompletionListAtCaret(options); if (completions) { this.assertItemInCompletionList(completions.entries, entryId, text, documentation, kind, spanIndex, hasAction, options); @@ -893,7 +893,7 @@ namespace FourSlash { * @param expectedKind the kind of symbol (see ScriptElementKind) * @param spanIndex the index of the range that the completion item's replacement text span should match */ - public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number, options?: FourSlashInterface.CompletionsAtOptions) { + public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, options?: FourSlashInterface.CompletionsAtOptions) { let replacementSpan: ts.TextSpan; if (spanIndex !== undefined) { replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex); @@ -902,7 +902,7 @@ namespace FourSlash { const completions = this.getCompletionListAtCaret(options); if (completions) { let filterCompletions = completions.entries.filter(e => e.name === entryId.name && e.source === entryId.source); - filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind) : filterCompletions; + filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind || (typeof expectedKind === "object" && e.kind === expectedKind.kind)) : filterCompletions; filterCompletions = filterCompletions.filter(entry => { const details = this.getCompletionEntryDetails(entry.name); const documentation = details && ts.displayPartsToString(details.documentation); @@ -3089,7 +3089,7 @@ Actual: ${stringify(fullActual)}`); entryId: ts.Completions.CompletionEntryIdentifier, text: string | undefined, documentation: string | undefined, - kind: string | undefined, + kind: string | undefined | { kind?: string, kindModifiers?: string }, spanIndex: number | undefined, hasAction: boolean | undefined, options: FourSlashInterface.VerifyCompletionListContainsOptions | undefined, @@ -3123,9 +3123,21 @@ Actual: ${stringify(fullActual)}`); } if (kind !== undefined) { - assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId)); + if (typeof kind === "string") { + assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId)); + } + else { + if (kind.kind) { + assert.equal(item.kind, kind.kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + entryId)); + } + if (kind.kindModifiers !== undefined) { + assert.equal(item.kindModifiers, kind.kindModifiers, this.assertionMessageAtLastKnownMarker("completion item kindModifiers for " + entryId)); + } + } } + + if (spanIndex !== undefined) { const span = this.getTextSpanForRangeAtIndex(spanIndex); assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + entryId)); @@ -3842,7 +3854,7 @@ namespace FourSlashInterface { // Verifies the completion list contains the specified symbol. The // completion list is brought up if necessary - public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string, spanIndex?: number, hasAction?: boolean, options?: VerifyCompletionListContainsOptions) { + public completionListContains(entryId: string | ts.Completions.CompletionEntryIdentifier, text?: string, documentation?: string, kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, options?: VerifyCompletionListContainsOptions) { if (typeof entryId === "string") { entryId = { name: entryId, source: undefined }; } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index caf38306bec..e79dde41e69 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -91,9 +91,14 @@ namespace ts.SymbolDisplay { } export function getSymbolModifiers(symbol: Symbol): string { - return symbol && symbol.declarations && symbol.declarations.length > 0 + const nodeModifiers = symbol && symbol.declarations && symbol.declarations.length > 0 ? getNodeModifiers(symbol.declarations[0]) : ScriptElementKindModifier.none; + + const symbolModifiers = symbol && symbol.flags & SymbolFlags.Optional ? + ScriptElementKindModifier.optionalModifier + : ScriptElementKindModifier.none; + return nodeModifiers && symbolModifiers ? nodeModifiers + "," + symbolModifiers : nodeModifiers || symbolModifiers; } interface SymbolDisplayPartsDocumentationAndSymbolKind { diff --git a/src/services/types.ts b/src/services/types.ts index 471ae3b3459..813e8790933 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -954,6 +954,7 @@ namespace ts { ambientModifier = "declare", staticModifier = "static", abstractModifier = "abstract", + optionalModifier = "optional" } export const enum ClassificationTypeNames { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d7d06fd9adc..6d3829d8133 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4505,6 +4505,7 @@ declare namespace ts { ambientModifier = "declare", staticModifier = "static", abstractModifier = "abstract", + optionalModifier = "optional", } enum ClassificationTypeNames { comment = "comment", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4d06e48dc25..54226c4a25f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4505,6 +4505,7 @@ declare namespace ts { ambientModifier = "declare", staticModifier = "static", abstractModifier = "abstract", + optionalModifier = "optional", } enum ClassificationTypeNames { comment = "comment", diff --git a/tests/cases/fourslash/completionsOptionalKindModifier.ts b/tests/cases/fourslash/completionsOptionalKindModifier.ts new file mode 100644 index 00000000000..39ace2342d3 --- /dev/null +++ b/tests/cases/fourslash/completionsOptionalKindModifier.ts @@ -0,0 +1,10 @@ +/// + +////interface A { a?: number; method?(): number; }; +////function f(x: A) { +////x./*a*/; +////} + +goTo.marker("a"); +verify.completionListContains("a", /* text */ undefined, /* documentation */ undefined, { kindModifiers: "optional" }); +verify.completionListContains("method", /* text */ undefined, /* documentation */ undefined, { kindModifiers: "optional" }); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 8b581e62e6c..933e7a88f94 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -146,7 +146,7 @@ declare namespace FourSlashInterface { entryId: string | { name: string, source?: string }, text?: string, documentation?: string, - kind?: string, + kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, options?: { includeExternalModuleExports?: boolean, sourceDisplay?: string, isRecommended?: true },