Fix symbol display exception when handling incomplete class (#44936)

When a class declaration lacks a name, don't throw an exception when
producing the display parts (e.g. for QuickInfo).

Remaining issues:
 1. The name shows as "__missing", the name of the underlying symbol,
    rather than "(Missing)", as it is for the corresponding function
    declaration case (because the parse constructs a missing identifier
    node for the function declaration).
 2. "(Missing)" is hard-coded, rather than being a localizable resource
    string.
 3. When an anonymous class declaration is a default export, the
    corresponding symbol is named "default", resulting in the confusing
    display string "class default".

Since display parts are built using existing `symbolToString`
functionality, it wasn't clear whether detecting special symbol names
and replacing them with user-friendly strings could be done without
breaking other functionality.

Similarly, changing the shape of the parse tree seemed riskier than the
problem justified (the user experience is just not getting QuickInfo for
the incomplete declaration, which seems acceptable).
This commit is contained in:
Andrew Casey 2021-08-06 15:05:54 -07:00 committed by GitHub
parent 7753efae79
commit 792e6d652a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 452 additions and 1 deletions

View File

@ -758,7 +758,7 @@ namespace ts {
if (isClassDeclaration(node)) {
// for class and function declarations, use the `default` modifier
// when the declaration is unnamed.
const defaultModifier = find(node.modifiers!, isDefaultModifier);
const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier);
if (defaultModifier) return defaultModifier;
}
if (isClassExpression(node)) {

View File

@ -0,0 +1,76 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts",
"position": 0,
"name": "1"
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts",
"position": 7,
"name": "2"
},
"quickInfo": {
"kind": "class",
"kindModifiers": "export",
"textSpan": {
"start": 7,
"length": 7
},
"displayParts": [
{
"text": "class",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "default",
"kind": "className"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts",
"position": 15,
"name": "3"
},
"quickInfo": {
"kind": "class",
"kindModifiers": "export",
"textSpan": {
"start": 15,
"length": 5
},
"displayParts": [
{
"text": "class",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "default",
"kind": "className"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts",
"position": 21,
"name": "4"
}
}
]

View File

@ -0,0 +1,106 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts",
"position": 0,
"name": "1"
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts",
"position": 7,
"name": "2"
},
"quickInfo": {
"kind": "class",
"kindModifiers": "export",
"textSpan": {
"start": 7,
"length": 7
},
"displayParts": [
{
"text": "class",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "C",
"kind": "className"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts",
"position": 15,
"name": "3"
},
"quickInfo": {
"kind": "class",
"kindModifiers": "export",
"textSpan": {
"start": 15,
"length": 5
},
"displayParts": [
{
"text": "class",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "C",
"kind": "className"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts",
"position": 21,
"name": "4"
},
"quickInfo": {
"kind": "class",
"kindModifiers": "export",
"textSpan": {
"start": 21,
"length": 1
},
"displayParts": [
{
"text": "class",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "C",
"kind": "className"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts",
"position": 23,
"name": "5"
}
}
]

View File

@ -0,0 +1,39 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassIncomplete.ts",
"position": 0,
"name": "1"
},
"quickInfo": {
"kind": "class",
"kindModifiers": "",
"textSpan": {
"start": 0,
"length": 5
},
"displayParts": [
{
"text": "class",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "__missing",
"kind": "className"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsClassIncomplete.ts",
"position": 6,
"name": "2"
}
}
]

View File

@ -0,0 +1,204 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts",
"position": 0,
"name": "1"
},
"quickInfo": {
"kind": "function",
"kindModifiers": "",
"textSpan": {
"start": 0,
"length": 8
},
"displayParts": [
{
"text": "function",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "(Missing)",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": "param",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": "+",
"kind": "operator"
},
{
"text": "1",
"kind": "numericLiteral"
},
{
"text": " ",
"kind": "space"
},
{
"text": "overload",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts",
"position": 9,
"name": "2"
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts",
"position": 30,
"name": "3"
},
"quickInfo": {
"kind": "function",
"kindModifiers": "",
"textSpan": {
"start": 30,
"length": 8
},
"displayParts": [
{
"text": "function",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "(Missing)",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": "param",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "string",
"kind": "keyword"
},
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
},
{
"text": " ",
"kind": "space"
},
{
"text": "(",
"kind": "punctuation"
},
{
"text": "+",
"kind": "operator"
},
{
"text": "1",
"kind": "numericLiteral"
},
{
"text": " ",
"kind": "space"
},
{
"text": "overload",
"kind": "text"
},
{
"text": ")",
"kind": "punctuation"
}
],
"documentation": []
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts",
"position": 39,
"name": "4"
}
}
]

View File

@ -0,0 +1,6 @@
/// <reference path='fourslash.ts'/>
/////*1*/export /*2*/default /*3*/class /*4*/ {
////}
verify.baselineQuickInfo();

View File

@ -0,0 +1,6 @@
/// <reference path='fourslash.ts'/>
/////*1*/export /*2*/default /*3*/class /*4*/C /*5*/ {
////}
verify.baselineQuickInfo();

View File

@ -0,0 +1,6 @@
/// <reference path='fourslash.ts'/>
/////*1*/class /*2*/ {
////}
verify.baselineQuickInfo();

View File

@ -0,0 +1,8 @@
/// <reference path='fourslash.ts'/>
/////*1*/function /*2*/(param: string) {
////}\
/////*3*/function /*4*/ {
////}\
verify.baselineQuickInfo();