From 5bdee45300486ab3facc55b3f82b5e508345a7e3 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 9 Dec 2021 19:51:32 -0800 Subject: [PATCH 1/6] Overhaul detection of JSX attributes and tag names --- src/services/completions.ts | 2 +- src/services/symbolDisplay.ts | 64 ++++++++++++++++- src/services/types.ts | 2 + src/services/utilities.ts | 10 +-- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../jsxTagNameDottedAttributeSnippet.ts | 69 +++++++++++++++++++ .../jsxTagNameDottedAttributeSnippetClosed.ts | 68 ++++++++++++++++++ .../fourslash/jsxTagNameDottedNoSnippet.ts | 53 ++++++++++++++ .../jsxTagNameDottedNoSnippetClosed.ts | 53 ++++++++++++++ tests/cases/fourslash/jsxTagNameNoSnippet.ts | 37 ++++++++++ .../cases/fourslash/tsxFindAllReferences11.ts | 2 +- .../cases/fourslash/tsxFindAllReferences6.ts | 2 +- 13 files changed, 353 insertions(+), 11 deletions(-) create mode 100644 tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts create mode 100644 tests/cases/fourslash/jsxTagNameDottedAttributeSnippetClosed.ts create mode 100644 tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts create mode 100644 tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts create mode 100644 tests/cases/fourslash/jsxTagNameNoSnippet.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index ca265f268f035..53f059b1e77eb 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -739,7 +739,7 @@ namespace ts.Completions { } } - const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location); + const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location, contextToken); if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index f1447b30cd9b5..3151f4ce8eb59 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -3,8 +3,8 @@ namespace ts.SymbolDisplay { const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; // TODO(drosen): use contextual SemanticMeaning. - export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); + export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node, contextToken?: Node): ScriptElementKind { + const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location, contextToken); if (result !== ScriptElementKind.unknown) { return result; } @@ -25,7 +25,7 @@ namespace ts.SymbolDisplay { return result; } - function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { + function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node, contextToken?: Node): ScriptElementKind { const roots = typeChecker.getRootSymbols(symbol); // If this is a method from a mapped type, leave as a method so long as it still has a call signature. if (roots.length === 1 @@ -83,6 +83,20 @@ namespace ts.SymbolDisplay { } return unionPropertyKind; } + + if (contextToken) { + const result = getSymbolKindOfJsxTagNameOrAttribute(location, contextToken); + if (result !== ScriptElementKind.unknown) { + return result; + } + } + + if (isJsxAttribute(location) || location.parent && isJsxAttribute(location.parent) && location.parent.name === location) { + return ScriptElementKind.jsxAttribute; + } + + // TODO(jakebailey): Delete the below code, once the edge cases it handles are handled above. + // If we requested completions after `x.` at the top-level, we may be at a source file location. switch (location.parent && location.parent.kind) { // If we've typed a character of the attribute name, will be 'JsxAttribute', else will be 'JsxOpeningElement'. @@ -100,6 +114,50 @@ namespace ts.SymbolDisplay { return ScriptElementKind.unknown; } + function getSymbolKindOfJsxTagNameOrAttribute(location: Node, contextToken: Node): ScriptElementKind { + const symbolKindFromContext = forEachAncestor(contextToken, (n) => { + if (isJsxAttributeLike(n)) { + return ScriptElementKind.jsxAttribute; + } + + if (isJsxFragment(n) || isJsxOpeningFragment(n) || isJsxClosingFragment(n)) { + return "quit"; + } + + if (isJsxOpeningElement(n) || isJsxSelfClosingElement(n) || isJsxClosingElement(n)) { + if (contextToken.getEnd() <= n.tagName.getFullStart()) { + // Definitely completing part of the tag name. + return ScriptElementKind.jsxTagName; + } + + if (rangeContainsRange(n.tagName, contextToken)) { + // We are to the right of the tag name, as the context is there. + // figure out where we are based on where the location is. + + // TODO(jakebailey): This seems hacky. + if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { + // Unfinished dotted tag name. + return ScriptElementKind.jsxTagName; + } + + if (!rangeContainsRange(n, location)) { + // Unclosed JSX element; location is entirely outside the element. + return ScriptElementKind.jsxAttribute; + } + + if (n.tagName.getEnd() <= location.getFullStart()) { + // After existing attributes, so is another attribute. + return ScriptElementKind.jsxAttribute; + } + } + + return "quit"; + } + }); + + return symbolKindFromContext || ScriptElementKind.unknown; + } + function getNormalizedSymbolModifiers(symbol: Symbol) { if (symbol.declarations && symbol.declarations.length) { const [declaration, ...declarations] = symbol.declarations; diff --git a/src/services/types.ts b/src/services/types.ts index b9bd9f2177d5b..7df40b22c8349 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1457,6 +1457,8 @@ namespace ts { /** * */ + jsxTagName = "JSX tag name", + jsxAttribute = "JSX attribute", /** String literal */ diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 593cbc73913f7..ec8b7f95df903 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1106,16 +1106,16 @@ namespace ts { * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true */ export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); + return getTokenAtPositionWorker(sourceFile, position, () => false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); } /** Returns a token if position is in [start-of-leading-trivia, end) */ export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); + return getTokenAtPositionWorker(sourceFile, position, () => true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); } /** Get the token whose text contains the position */ - function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { + function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: (n: Node) => boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { let current: Node = sourceFile; let foundToken: Node | undefined; outer: while (true) { @@ -1145,7 +1145,7 @@ namespace ts { // position and whose end is greater than the position. - const start = allowPositionInLeadingTrivia ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); + const start = allowPositionInLeadingTrivia(children[middle]) ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { return Comparison.GreaterThan; } @@ -1180,7 +1180,7 @@ namespace ts { } function nodeContainsPosition(node: Node) { - const start = allowPositionInLeadingTrivia ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); + const start = allowPositionInLeadingTrivia(node) ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { // If this child begins after position, then all subsequent children will as well. return false; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4df9e6328ecbd..b620c68b3cce1 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6594,6 +6594,7 @@ declare namespace ts { /** * */ + jsxTagName = "JSX tag name", jsxAttribute = "JSX attribute", /** String literal */ string = "string", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fdd55dab1f2d9..11d25bd2ab52d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6594,6 +6594,7 @@ declare namespace ts { /** * */ + jsxTagName = "JSX tag name", jsxAttribute = "JSX attribute", /** String literal */ string = "string", diff --git a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts new file mode 100644 index 0000000000000..e94e05d1684a0 --- /dev/null +++ b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts @@ -0,0 +1,69 @@ +/// +//@Filename: file.tsx +////interface NestedInterface { +//// Foo: NestedInterface; +//// (props: {className?: string}): any; +////} +//// +////declare const Foo: NestedInterface; +//// +////function fn1() { +//// return +//// +////} +////function fn2() { +//// return +//// +////} +////function fn3() { +//// return +//// +////} +////function fn4() { +//// return +//// +////} +////function fn5() { +//// return +//// +////} +////function fn6() { +//// return +//// +////} +////function fn7() { +//// return +//@Filename: file.tsx +////interface NestedInterface { +//// Foo: NestedInterface; +//// (props: {className?: string}): any; +////} +//// +////declare const Foo: NestedInterface; +//// +////function fn1() { +//// return +//// +//// +////} +////function fn2() { +//// return +//// +//// +////} +////function fn3() { +//// return +//// +//// +////} +////function fn4() { +//// return +//// +//// +////} +////function fn5() { +//// return +//// +//// +////} +////function fn6() { +//// return +//// +//// +////} +////function fn7() { +//// return +////} +////function fn8() { +//// return +////} +////function fn9() { +//// return +////} +////function fn10() { +//// return +////} +////function fn11() { +//// return +////} + +verify.completions( + { + marker: test.markers(), + includes: [ + { name: "className", insertText: 'className={$1}', isSnippet: true, sortText: completion.SortText.OptionalMember } + ], + preferences: { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, + }, + } +) diff --git a/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts b/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts new file mode 100644 index 0000000000000..6b9b589a1bf41 --- /dev/null +++ b/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts @@ -0,0 +1,53 @@ +/// +//@Filename: file.tsx +////interface NestedInterface { +//// Foo: NestedInterface; +//// (props: {}): any; +////} +//// +////declare const Foo: NestedInterface; +//// +////function fn1() { +//// return +//// +////} +////function fn2() { +//// return +//// +////} +////function fn3() { +//// return +//// +////} +////function fn4() { +//// return +//// +////} +////function fn5() { +//// return +//// +////} +////function fn6() { +//// return +//// +////} + +verify.completions( + { + marker: test.markers(), + includes: [ + { name: "Foo", insertText: undefined, isSnippet: undefined } + ], + preferences: { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, + }, + } +) diff --git a/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts b/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts new file mode 100644 index 0000000000000..6d1af353626b7 --- /dev/null +++ b/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts @@ -0,0 +1,53 @@ +/// +//@Filename: file.tsx +////interface NestedInterface { +//// Foo: NestedInterface; +//// (props: {}): any; +////} +//// +////declare const Foo: NestedInterface; +//// +////function fn1() { +//// return +//// +//// +////} +////function fn2() { +//// return +//// +//// +////} +////function fn3() { +//// return +//// +//// +////} +////function fn4() { +//// return +//// +//// +////} +////function fn5() { +//// return +//// +//// +////} +////function fn6() { +//// return +//// +//// +////} + +verify.completions( + { + marker: test.markers(), + includes: [ + { name: "Foo", insertText: undefined, isSnippet: undefined } + ], + preferences: { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, + }, + } +) diff --git a/tests/cases/fourslash/jsxTagNameNoSnippet.ts b/tests/cases/fourslash/jsxTagNameNoSnippet.ts new file mode 100644 index 0000000000000..0f6c07b0d87a4 --- /dev/null +++ b/tests/cases/fourslash/jsxTagNameNoSnippet.ts @@ -0,0 +1,37 @@ +/// +//@Filename: file.tsx +////declare namespace JSX { +//// interface IntrinsicElements { +//// button: any; +//// div: any; +//// } +////} +////function fn() { +//// return <> +//// ; +////} +////function fn2() { +//// return <> +//// preceding junk ; +////} +////function fn3() { +//// return <> +//// ; +////} + +verify.completions( + { + marker: test.markers(), + includes: [ + { name: "button", insertText: undefined, isSnippet: undefined } + ], + preferences: { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, + } + } +); diff --git a/tests/cases/fourslash/tsxFindAllReferences11.ts b/tests/cases/fourslash/tsxFindAllReferences11.ts index 8c7e7a4690937..b965b87d8f831 100644 --- a/tests/cases/fourslash/tsxFindAllReferences11.ts +++ b/tests/cases/fourslash/tsxFindAllReferences11.ts @@ -25,4 +25,4 @@ //// declare function MainButton(props: ButtonProps | LinkProps): JSX.Element; //// let opt = ; -verify.singleReferenceGroup("(property) wrong: true"); +verify.singleReferenceGroup("(JSX attribute) wrong: true"); diff --git a/tests/cases/fourslash/tsxFindAllReferences6.ts b/tests/cases/fourslash/tsxFindAllReferences6.ts index c0e119a4e456e..8d54ddd9537e1 100644 --- a/tests/cases/fourslash/tsxFindAllReferences6.ts +++ b/tests/cases/fourslash/tsxFindAllReferences6.ts @@ -19,4 +19,4 @@ //// declare function Opt(attributes: OptionPropBag): JSX.Element; //// let opt = ; -verify.singleReferenceGroup("(property) wrong: true"); +verify.singleReferenceGroup("(JSX attribute) wrong: true"); From 6a7c27b883f41dbb4ea14742539c935cb2ec7931 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 11 Jan 2022 23:53:07 -0800 Subject: [PATCH 2/6] Undo leftover change --- src/services/utilities.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index ec8b7f95df903..593cbc73913f7 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1106,16 +1106,16 @@ namespace ts { * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true */ export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { - return getTokenAtPositionWorker(sourceFile, position, () => false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); } /** Returns a token if position is in [start-of-leading-trivia, end) */ export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { - return getTokenAtPositionWorker(sourceFile, position, () => true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); } /** Get the token whose text contains the position */ - function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: (n: Node) => boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { + function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { let current: Node = sourceFile; let foundToken: Node | undefined; outer: while (true) { @@ -1145,7 +1145,7 @@ namespace ts { // position and whose end is greater than the position. - const start = allowPositionInLeadingTrivia(children[middle]) ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); + const start = allowPositionInLeadingTrivia ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { return Comparison.GreaterThan; } @@ -1180,7 +1180,7 @@ namespace ts { } function nodeContainsPosition(node: Node) { - const start = allowPositionInLeadingTrivia(node) ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); + const start = allowPositionInLeadingTrivia ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { // If this child begins after position, then all subsequent children will as well. return false; From 2d0a7a3bfb49a33e761742fde99c4d3508860c8c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 12 Jan 2022 10:43:11 -0800 Subject: [PATCH 3/6] Fix symbol display in other places --- src/services/completions.ts | 6 ++--- src/services/symbolDisplay.ts | 8 ++++--- .../jsxTagNameDottedAttributeSnippet.ts | 9 ++++++-- .../jsxTagNameDottedAttributeSnippetClosed.ts | 8 ++++++- .../fourslash/jsxTagNameDottedNoSnippet.ts | 23 ++++++++++--------- .../jsxTagNameDottedNoSnippetClosed.ts | 23 ++++++++++--------- tests/cases/fourslash/jsxTagNameNoSnippet.ts | 22 ++++++++---------- 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 53f059b1e77eb..ce031739fd010 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1402,7 +1402,7 @@ namespace ts.Completions { case "symbol": { const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source); - return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 + return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay, contextToken); // TODO: GH#18217 } case "literal": { const { literal } = symbolCompletion; @@ -1420,10 +1420,10 @@ namespace ts.Completions { return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); } - export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { + export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[], contextToken?: Node): CompletionEntryDetails { const { displayParts, documentation, symbolKind, tags } = checker.runWithCancellationToken(cancellationToken, checker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All, /*alias*/ undefined, contextToken) ); return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 3151f4ce8eb59..7ae1d6367601b 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -202,12 +202,12 @@ namespace ts.SymbolDisplay { // TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, - location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { + location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol, contextToken?: Node): SymbolDisplayPartsDocumentationAndSymbolKind { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[] = []; let tags: JSDocTagInfo[] = []; const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); - let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; + let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location, contextToken) : ScriptElementKind.unknown; let hasAddedSymbolInfo = false; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location); let type: Type | undefined; @@ -289,6 +289,7 @@ namespace ts.SymbolDisplay { switch (symbolKind) { case ScriptElementKind.jsxAttribute: + case ScriptElementKind.jsxTagName: case ScriptElementKind.memberVariableElement: case ScriptElementKind.variableElement: case ScriptElementKind.constElement: @@ -561,6 +562,7 @@ namespace ts.SymbolDisplay { // For properties, variables and local vars: show the type if (symbolKind === ScriptElementKind.memberVariableElement || symbolKind === ScriptElementKind.jsxAttribute || + symbolKind === ScriptElementKind.jsxTagName || symbolFlags & SymbolFlags.Variable || symbolKind === ScriptElementKind.localVariableElement || isThisExpression) { @@ -601,7 +603,7 @@ namespace ts.SymbolDisplay { } } else { - symbolKind = getSymbolKind(typeChecker, symbol, location); + symbolKind = getSymbolKind(typeChecker, symbol, location, contextToken); } } diff --git a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts index e94e05d1684a0..1264ccf79ad5f 100644 --- a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts +++ b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts @@ -53,12 +53,17 @@ //// return ////} +var preferences: FourSlashInterface.UserPreferences = { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, +}; + verify.completions( - { - marker: test.markers(), - includes: [ - { name: "Foo", insertText: undefined, isSnippet: undefined } - ], - preferences: { - jsxAttributeCompletionStyle: "braces", - includeCompletionsWithSnippetText: true, - includeCompletionsWithInsertText: true, - }, - } + { marker: "1", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, + { marker: "2", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, + { marker: "3", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "4", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "5", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "6", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, ) diff --git a/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts b/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts index 6d1af353626b7..fed65eb73f618 100644 --- a/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts +++ b/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts @@ -38,16 +38,17 @@ //// ////} +var preferences: FourSlashInterface.UserPreferences = { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, +}; + verify.completions( - { - marker: test.markers(), - includes: [ - { name: "Foo", insertText: undefined, isSnippet: undefined } - ], - preferences: { - jsxAttributeCompletionStyle: "braces", - includeCompletionsWithSnippetText: true, - includeCompletionsWithInsertText: true, - }, - } + { marker: "1", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, + { marker: "2", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, + { marker: "3", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "4", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "5", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "6", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, ) diff --git a/tests/cases/fourslash/jsxTagNameNoSnippet.ts b/tests/cases/fourslash/jsxTagNameNoSnippet.ts index 0f6c07b0d87a4..c79b359b1eb30 100644 --- a/tests/cases/fourslash/jsxTagNameNoSnippet.ts +++ b/tests/cases/fourslash/jsxTagNameNoSnippet.ts @@ -22,16 +22,14 @@ //// ; ////} +var preferences: FourSlashInterface.UserPreferences = { + jsxAttributeCompletionStyle: "braces", + includeCompletionsWithSnippetText: true, + includeCompletionsWithInsertText: true, +}; + verify.completions( - { - marker: test.markers(), - includes: [ - { name: "button", insertText: undefined, isSnippet: undefined } - ], - preferences: { - jsxAttributeCompletionStyle: "braces", - includeCompletionsWithSnippetText: true, - includeCompletionsWithInsertText: true, - } - } -); + { marker: "1", preferences, includes: { name: "button", text: "(JSX tag name) JSX.IntrinsicElements.button: any" } }, + { marker: "2", preferences, includes: { name: "button", text: "(JSX tag name) JSX.IntrinsicElements.button: any" } }, + { marker: "3", preferences, includes: { name: "button", text: "(JSX tag name) JSX.IntrinsicElements.button: any" } }, +) From c4a7bb1b4e0ae2e19b4590a4c5c1929567070bfe Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 12 Jan 2022 11:51:07 -0800 Subject: [PATCH 4/6] Completely eliminate, deprecate jsxAttribute usage --- src/services/completions.ts | 51 +++++++++-- src/services/symbolDisplay.ts | 84 ++----------------- src/services/types.ts | 3 +- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- tests/cases/fourslash/completionsInJsxTag.ts | 8 +- .../fourslash/completionsJsxAttribute.ts | 4 +- .../completionsJsxAttributeGeneric.ts | 2 +- .../completionsJsxAttributeInitializer.ts | 4 +- tests/cases/fourslash/jsxGenericQuickInfo.tsx | 4 +- .../jsxTagNameDottedAttributeSnippet.ts | 2 +- .../jsxTagNameDottedAttributeSnippetClosed.ts | 2 +- .../fourslash/jsxTagNameDottedNoSnippet.ts | 8 +- .../jsxTagNameDottedNoSnippetClosed.ts | 8 +- tests/cases/fourslash/jsxTagNameNoSnippet.ts | 6 +- tests/cases/fourslash/tsxCompletion12.ts | 4 +- tests/cases/fourslash/tsxCompletion13.ts | 10 +-- tests/cases/fourslash/tsxCompletion7.ts | 4 +- .../cases/fourslash/tsxFindAllReferences11.ts | 2 +- .../cases/fourslash/tsxFindAllReferences6.ts | 2 +- tests/cases/fourslash/tsxQuickInfo3.ts | 4 +- tests/cases/fourslash/tsxQuickInfo4.ts | 4 +- tests/cases/fourslash/tsxQuickInfo5.ts | 4 +- 23 files changed, 96 insertions(+), 128 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index ce031739fd010..290fb8c5a5422 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -739,8 +739,47 @@ namespace ts.Completions { } } - const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, location, contextToken); - if (kind === ScriptElementKind.jsxAttribute && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { + const isJSXAttributeCompletion = contextToken && forEachAncestor(contextToken, (n) => { + if (isJsxAttributeLike(n)) { + return true; + } + + if (isJsxFragment(n) || isJsxOpeningFragment(n) || isJsxClosingFragment(n)) { + return false; + } + + if (isJsxOpeningElement(n) || isJsxSelfClosingElement(n) || isJsxClosingElement(n)) { + if (contextToken.getEnd() <= n.tagName.getFullStart()) { + // Definitely completing part of the tag name. + return false; + } + + if (rangeContainsRange(n.tagName, contextToken)) { + // We are to the right of the tag name, as the context is there. + // figure out where we are based on where the location is. + + // TODO(jakebailey): This seems hacky. + if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { + // Unfinished dotted tag name. + return false; + } + + if (!rangeContainsRange(n, location)) { + // Unclosed JSX element; location is entirely outside the element. + return true; + } + + if (n.tagName.getEnd() <= location.getFullStart()) { + // After existing attributes, so is another attribute. + return true; + } + } + + return false; + } + }); + + if (isJSXAttributeCompletion && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); @@ -785,7 +824,7 @@ namespace ts.Completions { // entries (like JavaScript identifier entries). return { name, - kind, + kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), sortText, source, @@ -1402,7 +1441,7 @@ namespace ts.Completions { case "symbol": { const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source); - return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay, contextToken); // TODO: GH#18217 + return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 } case "literal": { const { literal } = symbolCompletion; @@ -1420,10 +1459,10 @@ namespace ts.Completions { return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); } - export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[], contextToken?: Node): CompletionEntryDetails { + export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { const { displayParts, documentation, symbolKind, tags } = checker.runWithCancellationToken(cancellationToken, checker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All, /*alias*/ undefined, contextToken) + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All, /*alias*/ undefined) ); return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 7ae1d6367601b..c7ee58ff28e63 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -3,8 +3,8 @@ namespace ts.SymbolDisplay { const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; // TODO(drosen): use contextual SemanticMeaning. - export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node, contextToken?: Node): ScriptElementKind { - const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location, contextToken); + export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { + const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); if (result !== ScriptElementKind.unknown) { return result; } @@ -25,7 +25,7 @@ namespace ts.SymbolDisplay { return result; } - function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node, contextToken?: Node): ScriptElementKind { + function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { const roots = typeChecker.getRootSymbols(symbol); // If this is a method from a mapped type, leave as a method so long as it still has a call signature. if (roots.length === 1 @@ -84,80 +84,12 @@ namespace ts.SymbolDisplay { return unionPropertyKind; } - if (contextToken) { - const result = getSymbolKindOfJsxTagNameOrAttribute(location, contextToken); - if (result !== ScriptElementKind.unknown) { - return result; - } - } - - if (isJsxAttribute(location) || location.parent && isJsxAttribute(location.parent) && location.parent.name === location) { - return ScriptElementKind.jsxAttribute; - } - - // TODO(jakebailey): Delete the below code, once the edge cases it handles are handled above. - - // If we requested completions after `x.` at the top-level, we may be at a source file location. - switch (location.parent && location.parent.kind) { - // If we've typed a character of the attribute name, will be 'JsxAttribute', else will be 'JsxOpeningElement'. - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - return location.kind === SyntaxKind.Identifier ? ScriptElementKind.memberVariableElement : ScriptElementKind.jsxAttribute; - case SyntaxKind.JsxAttribute: - return ScriptElementKind.jsxAttribute; - default: - return ScriptElementKind.memberVariableElement; - } + return ScriptElementKind.memberVariableElement; } return ScriptElementKind.unknown; } - function getSymbolKindOfJsxTagNameOrAttribute(location: Node, contextToken: Node): ScriptElementKind { - const symbolKindFromContext = forEachAncestor(contextToken, (n) => { - if (isJsxAttributeLike(n)) { - return ScriptElementKind.jsxAttribute; - } - - if (isJsxFragment(n) || isJsxOpeningFragment(n) || isJsxClosingFragment(n)) { - return "quit"; - } - - if (isJsxOpeningElement(n) || isJsxSelfClosingElement(n) || isJsxClosingElement(n)) { - if (contextToken.getEnd() <= n.tagName.getFullStart()) { - // Definitely completing part of the tag name. - return ScriptElementKind.jsxTagName; - } - - if (rangeContainsRange(n.tagName, contextToken)) { - // We are to the right of the tag name, as the context is there. - // figure out where we are based on where the location is. - - // TODO(jakebailey): This seems hacky. - if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { - // Unfinished dotted tag name. - return ScriptElementKind.jsxTagName; - } - - if (!rangeContainsRange(n, location)) { - // Unclosed JSX element; location is entirely outside the element. - return ScriptElementKind.jsxAttribute; - } - - if (n.tagName.getEnd() <= location.getFullStart()) { - // After existing attributes, so is another attribute. - return ScriptElementKind.jsxAttribute; - } - } - - return "quit"; - } - }); - - return symbolKindFromContext || ScriptElementKind.unknown; - } - function getNormalizedSymbolModifiers(symbol: Symbol) { if (symbol.declarations && symbol.declarations.length) { const [declaration, ...declarations] = symbol.declarations; @@ -202,12 +134,12 @@ namespace ts.SymbolDisplay { // TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, - location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol, contextToken?: Node): SymbolDisplayPartsDocumentationAndSymbolKind { + location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { const displayParts: SymbolDisplayPart[] = []; let documentation: SymbolDisplayPart[] = []; let tags: JSDocTagInfo[] = []; const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); - let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location, contextToken) : ScriptElementKind.unknown; + let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; let hasAddedSymbolInfo = false; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location); let type: Type | undefined; @@ -289,7 +221,6 @@ namespace ts.SymbolDisplay { switch (symbolKind) { case ScriptElementKind.jsxAttribute: - case ScriptElementKind.jsxTagName: case ScriptElementKind.memberVariableElement: case ScriptElementKind.variableElement: case ScriptElementKind.constElement: @@ -562,7 +493,6 @@ namespace ts.SymbolDisplay { // For properties, variables and local vars: show the type if (symbolKind === ScriptElementKind.memberVariableElement || symbolKind === ScriptElementKind.jsxAttribute || - symbolKind === ScriptElementKind.jsxTagName || symbolFlags & SymbolFlags.Variable || symbolKind === ScriptElementKind.localVariableElement || isThisExpression) { @@ -603,7 +533,7 @@ namespace ts.SymbolDisplay { } } else { - symbolKind = getSymbolKind(typeChecker, symbol, location, contextToken); + symbolKind = getSymbolKind(typeChecker, symbol, location); } } diff --git a/src/services/types.ts b/src/services/types.ts index 7df40b22c8349..b30583bb0f4e8 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1456,9 +1456,8 @@ namespace ts { /** * + * @deprecated */ - jsxTagName = "JSX tag name", - jsxAttribute = "JSX attribute", /** String literal */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b620c68b3cce1..cb1d033b1578c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6593,8 +6593,8 @@ declare namespace ts { externalModuleName = "external module name", /** * + * @deprecated */ - jsxTagName = "JSX tag name", jsxAttribute = "JSX attribute", /** String literal */ string = "string", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 11d25bd2ab52d..5985a23e8b361 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6593,8 +6593,8 @@ declare namespace ts { externalModuleName = "external module name", /** * + * @deprecated */ - jsxTagName = "JSX tag name", jsxAttribute = "JSX attribute", /** String literal */ string = "string", diff --git a/tests/cases/fourslash/completionsInJsxTag.ts b/tests/cases/fourslash/completionsInJsxTag.ts index 4f5862e820885..8fd2fcfe4cf5a 100644 --- a/tests/cases/fourslash/completionsInJsxTag.ts +++ b/tests/cases/fourslash/completionsInJsxTag.ts @@ -26,16 +26,16 @@ verify.completions({ exact: [ { name: "aria-label", - text: "(JSX attribute) \"aria-label\": string", + text: "(property) \"aria-label\": string", documentation: "Label docs", - kind: "JSX attribute", + kind: "property", kindModifiers: "declare", }, { name: "foo", - text: "(JSX attribute) foo: string", + text: "(property) foo: string", documentation: "Doc", - kind: "JSX attribute", + kind: "property", kindModifiers: "declare", }, ], diff --git a/tests/cases/fourslash/completionsJsxAttribute.ts b/tests/cases/fourslash/completionsJsxAttribute.ts index 592c2c8ed3c72..3dcf49d487108 100644 --- a/tests/cases/fourslash/completionsJsxAttribute.ts +++ b/tests/cases/fourslash/completionsJsxAttribute.ts @@ -17,8 +17,8 @@ ////
; const exact: ReadonlyArray = [ - { name: "bar", kind: "JSX attribute", kindModifiers: "declare", text: "(JSX attribute) bar: string" }, - { name: "foo", kind: "JSX attribute", kindModifiers: "declare", text: "(JSX attribute) foo: boolean", documentation: "Doc" }, + { name: "bar", kind: "property", kindModifiers: "declare", text: "(property) bar: string" }, + { name: "foo", kind: "property", kindModifiers: "declare", text: "(property) foo: boolean", documentation: "Doc" }, ]; verify.completions({ marker: "", exact }); edit.insert("f"); diff --git a/tests/cases/fourslash/completionsJsxAttributeGeneric.ts b/tests/cases/fourslash/completionsJsxAttributeGeneric.ts index 9180c691f09f4..2e50a8a895e8c 100644 --- a/tests/cases/fourslash/completionsJsxAttributeGeneric.ts +++ b/tests/cases/fourslash/completionsJsxAttributeGeneric.ts @@ -11,7 +11,7 @@ marker, exact: [{ name: 'name', - kind: 'JSX attribute', + kind: 'property', kindModifiers: 'declare' }] }) diff --git a/tests/cases/fourslash/completionsJsxAttributeInitializer.ts b/tests/cases/fourslash/completionsJsxAttributeInitializer.ts index 2076c6588f4d7..f383d52375912 100644 --- a/tests/cases/fourslash/completionsJsxAttributeInitializer.ts +++ b/tests/cases/fourslash/completionsJsxAttributeInitializer.ts @@ -9,8 +9,8 @@ verify.completions({ marker: "", includes: [ { name: "x", text: "(parameter) x: number", kind: "parameter", insertText: "{x}" }, - { name: "p", text: "(JSX attribute) p: number", kind: "JSX attribute", insertText: "{this.p}", sortText: completion.SortText.SuggestedClassMembers, source: completion.CompletionSource.ThisProperty }, - { name: "a b", text: '(JSX attribute) "a b": number', kind: "JSX attribute", insertText: '{this["a b"]}', sortText: completion.SortText.SuggestedClassMembers, source: completion.CompletionSource.ThisProperty }, + { name: "p", text: "(property) p: number", kind: "property", insertText: "{this.p}", sortText: completion.SortText.SuggestedClassMembers, source: completion.CompletionSource.ThisProperty }, + { name: "a b", text: '(property) "a b": number', kind: "property", insertText: '{this["a b"]}', sortText: completion.SortText.SuggestedClassMembers, source: completion.CompletionSource.ThisProperty }, ], preferences: { includeInsertTextCompletions: true, diff --git a/tests/cases/fourslash/jsxGenericQuickInfo.tsx b/tests/cases/fourslash/jsxGenericQuickInfo.tsx index 7566193737651..9e1ff3b84b083 100644 --- a/tests/cases/fourslash/jsxGenericQuickInfo.tsx +++ b/tests/cases/fourslash/jsxGenericQuickInfo.tsx @@ -29,6 +29,6 @@ verify.quickInfoAt("0", "(property) PropsA.renderItem: (item: number) => string"); verify.quickInfoAt("1", "(parameter) item: number"); verify.quickInfoAt("2", `(property) PropsA.name: "A"`, 'comments for A'); -verify.quickInfoAt("3", "(JSX attribute) PropsA.renderItem: (item: number) => string"); +verify.quickInfoAt("3", "(property) PropsA.renderItem: (item: number) => string"); verify.quickInfoAt("4", "(parameter) item: number"); -verify.quickInfoAt("5", `(JSX attribute) PropsA.name: "A"`, 'comments for A'); +verify.quickInfoAt("5", `(property) PropsA.name: "A"`, 'comments for A'); diff --git a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts index 1264ccf79ad5f..98d4c92e20493 100644 --- a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts +++ b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippet.ts @@ -62,7 +62,7 @@ verify.completions( insertText: "className={$1}", isSnippet: true, sortText: completion.SortText.OptionalMember, - text: "(JSX attribute) className?: string" + text: "(property) className?: string" } ], preferences: { diff --git a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippetClosed.ts b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippetClosed.ts index 1a4cfe73886ec..b10279315292f 100644 --- a/tests/cases/fourslash/jsxTagNameDottedAttributeSnippetClosed.ts +++ b/tests/cases/fourslash/jsxTagNameDottedAttributeSnippetClosed.ts @@ -62,7 +62,7 @@ verify.completions( insertText: "className={$1}", isSnippet: true, sortText: completion.SortText.OptionalMember, - text: "(JSX attribute) className?: string" + text: "(property) className?: string" } ], preferences: { diff --git a/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts b/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts index 520021d73dad7..515c631c75ec0 100644 --- a/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts +++ b/tests/cases/fourslash/jsxTagNameDottedNoSnippet.ts @@ -47,8 +47,8 @@ var preferences: FourSlashInterface.UserPreferences = { verify.completions( { marker: "1", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, { marker: "2", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, - { marker: "3", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, - { marker: "4", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, - { marker: "5", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, - { marker: "6", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "3", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, + { marker: "4", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, + { marker: "5", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, + { marker: "6", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, ) diff --git a/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts b/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts index fed65eb73f618..144c53ba60654 100644 --- a/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts +++ b/tests/cases/fourslash/jsxTagNameDottedNoSnippetClosed.ts @@ -47,8 +47,8 @@ var preferences: FourSlashInterface.UserPreferences = { verify.completions( { marker: "1", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, { marker: "2", preferences, includes: { name: "Foo", text: "const Foo: NestedInterface" } }, - { marker: "3", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, - { marker: "4", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, - { marker: "5", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, - { marker: "6", preferences, includes: { name: "Foo", text: "(JSX tag name) NestedInterface.Foo: NestedInterface" } }, + { marker: "3", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, + { marker: "4", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, + { marker: "5", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, + { marker: "6", preferences, includes: { name: "Foo", text: "(property) NestedInterface.Foo: NestedInterface" } }, ) diff --git a/tests/cases/fourslash/jsxTagNameNoSnippet.ts b/tests/cases/fourslash/jsxTagNameNoSnippet.ts index c79b359b1eb30..db243b1102558 100644 --- a/tests/cases/fourslash/jsxTagNameNoSnippet.ts +++ b/tests/cases/fourslash/jsxTagNameNoSnippet.ts @@ -29,7 +29,7 @@ var preferences: FourSlashInterface.UserPreferences = { }; verify.completions( - { marker: "1", preferences, includes: { name: "button", text: "(JSX tag name) JSX.IntrinsicElements.button: any" } }, - { marker: "2", preferences, includes: { name: "button", text: "(JSX tag name) JSX.IntrinsicElements.button: any" } }, - { marker: "3", preferences, includes: { name: "button", text: "(JSX tag name) JSX.IntrinsicElements.button: any" } }, + { marker: "1", preferences, includes: { name: "button", text: "(property) JSX.IntrinsicElements.button: any" } }, + { marker: "2", preferences, includes: { name: "button", text: "(property) JSX.IntrinsicElements.button: any" } }, + { marker: "3", preferences, includes: { name: "button", text: "(property) JSX.IntrinsicElements.button: any" } }, ) diff --git a/tests/cases/fourslash/tsxCompletion12.ts b/tests/cases/fourslash/tsxCompletion12.ts index 4b0b01c2241d0..c4feb1ae51ae7 100644 --- a/tests/cases/fourslash/tsxCompletion12.ts +++ b/tests/cases/fourslash/tsxCompletion12.ts @@ -28,14 +28,14 @@ verify.completions( exact: [ "propString", "propx", - { name: "optional", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "optional", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, ] }, { marker: "3", exact: [ "propString", - { name: "optional", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "optional", kind: "property", 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 b3dc913783a40..5b8a8d7e18b41 100644 --- a/tests/cases/fourslash/tsxCompletion13.ts +++ b/tests/cases/fourslash/tsxCompletion13.ts @@ -36,8 +36,8 @@ verify.completions( exact: [ "goTo", "onClick", - { name: "children", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, - { name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "children", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "className", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, ] }, { @@ -45,14 +45,14 @@ verify.completions( exact: [ "goTo", "onClick", - { name: "className", kind: "JSX attribute", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "className", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, ] }, { 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 }, + { name: "children", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, + { name: "className", kind: "property", kindModifiers: "optional", sortText: completion.SortText.OptionalMember }, ] }, ); diff --git a/tests/cases/fourslash/tsxCompletion7.ts b/tests/cases/fourslash/tsxCompletion7.ts index 90a98c4f5a0d0..1d31441ca1b94 100644 --- a/tests/cases/fourslash/tsxCompletion7.ts +++ b/tests/cases/fourslash/tsxCompletion7.ts @@ -13,7 +13,7 @@ verify.completions({ marker: "", exact: [ - { name: "TWO", kind: "JSX attribute", kindModifiers: "declare", sortText: completion.SortText.LocationPriority }, - { name: "ONE", kind: "JSX attribute", kindModifiers: "declare", sortText: completion.SortText.MemberDeclaredBySpreadAssignment }, + { name: "TWO", kind: "property", kindModifiers: "declare", sortText: completion.SortText.LocationPriority }, + { name: "ONE", kind: "property", kindModifiers: "declare", sortText: completion.SortText.MemberDeclaredBySpreadAssignment }, ] }); diff --git a/tests/cases/fourslash/tsxFindAllReferences11.ts b/tests/cases/fourslash/tsxFindAllReferences11.ts index b965b87d8f831..8c7e7a4690937 100644 --- a/tests/cases/fourslash/tsxFindAllReferences11.ts +++ b/tests/cases/fourslash/tsxFindAllReferences11.ts @@ -25,4 +25,4 @@ //// declare function MainButton(props: ButtonProps | LinkProps): JSX.Element; //// let opt = ; -verify.singleReferenceGroup("(JSX attribute) wrong: true"); +verify.singleReferenceGroup("(property) wrong: true"); diff --git a/tests/cases/fourslash/tsxFindAllReferences6.ts b/tests/cases/fourslash/tsxFindAllReferences6.ts index 8d54ddd9537e1..c0e119a4e456e 100644 --- a/tests/cases/fourslash/tsxFindAllReferences6.ts +++ b/tests/cases/fourslash/tsxFindAllReferences6.ts @@ -19,4 +19,4 @@ //// declare function Opt(attributes: OptionPropBag): JSX.Element; //// let opt = ; -verify.singleReferenceGroup("(JSX attribute) wrong: true"); +verify.singleReferenceGroup("(property) wrong: true"); diff --git a/tests/cases/fourslash/tsxQuickInfo3.ts b/tests/cases/fourslash/tsxQuickInfo3.ts index 614ffa878ef09..068fcab19f51f 100644 --- a/tests/cases/fourslash/tsxQuickInfo3.ts +++ b/tests/cases/fourslash/tsxQuickInfo3.ts @@ -24,7 +24,7 @@ verify.quickInfos({ 1: "class Opt", - 2: "(JSX attribute) propx: number", + 2: "(property) propx: number", 3: "const obj1: OptionProp", - 4: "(JSX attribute) propx: true" + 4: "(property) propx: true" }); diff --git a/tests/cases/fourslash/tsxQuickInfo4.ts b/tests/cases/fourslash/tsxQuickInfo4.ts index 7e1f8e2bc7ba0..f66d4dc4afd9a 100644 --- a/tests/cases/fourslash/tsxQuickInfo4.ts +++ b/tests/cases/fourslash/tsxQuickInfo4.ts @@ -48,8 +48,8 @@ verify.quickInfos({ 1: "function MainButton(linkProps: LinkProps): JSX.Element (+1 overload)", - 2: "(JSX attribute) LinkProps.to: string", + 2: "(property) LinkProps.to: string", 3: "function MainButton(buttonProps: ButtonProps): JSX.Element (+1 overload)", 4: "(method) ButtonProps.onClick(event?: React.MouseEvent): void", - 5: "(JSX attribute) extra-prop: true" + 5: "(property) extra-prop: true" }); diff --git a/tests/cases/fourslash/tsxQuickInfo5.ts b/tests/cases/fourslash/tsxQuickInfo5.ts index 3ebf1bd0bdd04..35f821fe6b28d 100644 --- a/tests/cases/fourslash/tsxQuickInfo5.ts +++ b/tests/cases/fourslash/tsxQuickInfo5.ts @@ -13,6 +13,6 @@ verify.quickInfos({ 1: "function ComponentWithTwoAttributes(l: {\n key1: T;\n value: U;\n}): JSX.Element", - 2: "(JSX attribute) key1: T", - 3: "(JSX attribute) value: U", + 2: "(property) key1: T", + 3: "(property) value: U", }); From e2a958d5c152f11ab9b4a22b6dfe9bc04dbea694 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 12 Jan 2022 12:18:04 -0800 Subject: [PATCH 5/6] Remove leftover --- src/services/completions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 290fb8c5a5422..2dbee0ce1dc2d 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1462,7 +1462,7 @@ namespace ts.Completions { export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { const { displayParts, documentation, symbolKind, tags } = checker.runWithCancellationToken(cancellationToken, checker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All, /*alias*/ undefined) + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) ); return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); } From 73426dd8bec85a3b4c57bdf62f80b0c18542b4aa Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 12 Jan 2022 12:21:54 -0800 Subject: [PATCH 6/6] Remove todo --- src/services/completions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 2dbee0ce1dc2d..e2d56f77dd810 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -758,7 +758,6 @@ namespace ts.Completions { // We are to the right of the tag name, as the context is there. // figure out where we are based on where the location is. - // TODO(jakebailey): This seems hacky. if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { // Unfinished dotted tag name. return false;