From 792e6d652a0143cf5a7db5cb139d25e78a55d1ef Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 6 Aug 2021 15:05:54 -0700 Subject: [PATCH] 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). --- src/services/utilities.ts | 2 +- ...DisplayPartsClassDefaultAnonymous.baseline | 76 +++++++ ...InfoDisplayPartsClassDefaultNamed.baseline | 106 +++++++++ ...ckInfoDisplayPartsClassIncomplete.baseline | 39 ++++ ...nfoDisplayPartsFunctionIncomplete.baseline | 204 ++++++++++++++++++ ...ckInfoDisplayPartsClassDefaultAnonymous.ts | 6 + .../quickInfoDisplayPartsClassDefaultNamed.ts | 6 + .../quickInfoDisplayPartsClassIncomplete.ts | 6 + ...quickInfoDisplayPartsFunctionIncomplete.ts | 8 + 9 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/quickInfoDisplayPartsClassDefaultAnonymous.baseline create mode 100644 tests/baselines/reference/quickInfoDisplayPartsClassDefaultNamed.baseline create mode 100644 tests/baselines/reference/quickInfoDisplayPartsClassIncomplete.baseline create mode 100644 tests/baselines/reference/quickInfoDisplayPartsFunctionIncomplete.baseline create mode 100644 tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts create mode 100644 tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts create mode 100644 tests/cases/fourslash/quickInfoDisplayPartsClassIncomplete.ts create mode 100644 tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b67c8da5420..3d503bff659 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -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)) { diff --git a/tests/baselines/reference/quickInfoDisplayPartsClassDefaultAnonymous.baseline b/tests/baselines/reference/quickInfoDisplayPartsClassDefaultAnonymous.baseline new file mode 100644 index 00000000000..6f0e960d874 --- /dev/null +++ b/tests/baselines/reference/quickInfoDisplayPartsClassDefaultAnonymous.baseline @@ -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" + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoDisplayPartsClassDefaultNamed.baseline b/tests/baselines/reference/quickInfoDisplayPartsClassDefaultNamed.baseline new file mode 100644 index 00000000000..cdaaee1e1c2 --- /dev/null +++ b/tests/baselines/reference/quickInfoDisplayPartsClassDefaultNamed.baseline @@ -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" + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoDisplayPartsClassIncomplete.baseline b/tests/baselines/reference/quickInfoDisplayPartsClassIncomplete.baseline new file mode 100644 index 00000000000..93919d9b5bf --- /dev/null +++ b/tests/baselines/reference/quickInfoDisplayPartsClassIncomplete.baseline @@ -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" + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoDisplayPartsFunctionIncomplete.baseline b/tests/baselines/reference/quickInfoDisplayPartsFunctionIncomplete.baseline new file mode 100644 index 00000000000..829dd84f076 --- /dev/null +++ b/tests/baselines/reference/quickInfoDisplayPartsFunctionIncomplete.baseline @@ -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" + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts b/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts new file mode 100644 index 00000000000..01aadf05c81 --- /dev/null +++ b/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultAnonymous.ts @@ -0,0 +1,6 @@ +/// + +/////*1*/export /*2*/default /*3*/class /*4*/ { +////} + +verify.baselineQuickInfo(); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts b/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts new file mode 100644 index 00000000000..d8ea23dd073 --- /dev/null +++ b/tests/cases/fourslash/quickInfoDisplayPartsClassDefaultNamed.ts @@ -0,0 +1,6 @@ +/// + +/////*1*/export /*2*/default /*3*/class /*4*/C /*5*/ { +////} + +verify.baselineQuickInfo(); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoDisplayPartsClassIncomplete.ts b/tests/cases/fourslash/quickInfoDisplayPartsClassIncomplete.ts new file mode 100644 index 00000000000..4313c7345fb --- /dev/null +++ b/tests/cases/fourslash/quickInfoDisplayPartsClassIncomplete.ts @@ -0,0 +1,6 @@ +/// + +/////*1*/class /*2*/ { +////} + +verify.baselineQuickInfo(); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts b/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts new file mode 100644 index 00000000000..0eafe8f9e6f --- /dev/null +++ b/tests/cases/fourslash/quickInfoDisplayPartsFunctionIncomplete.ts @@ -0,0 +1,8 @@ +/// + +/////*1*/function /*2*/(param: string) { +////}\ +/////*3*/function /*4*/ { +////}\ + +verify.baselineQuickInfo(); \ No newline at end of file