Skip to content

Fix deprecated JSDoc tags not showing for default imports #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/copilot-questions.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Questions I have that I think the developers of this project can help me with:
* How does control flow analysis represent a circular graph? I checked the documentation server for "cfa" and "control flow"
* How do I tell if a symbol is in the global scope? I checked the documentation server for topics referencing "symbol" and "global"
* What is an `EscapedName`, exactly?
Questions I have that I think the developers of this project can help me with:
* How does control flow analysis represent a circular graph? I checked the documentation server for "cfa" and "control flow"
* How do I tell if a symbol is in the global scope? I checked the documentation server for topics referencing "symbol" and "global"
* What is an `EscapedName`, exactly?
* How to extract JSDoc from export assignments for default exports? I searched the documentation server for "export assignment JSDoc", "default export documentation", "getJSDocCommentsAndTags export", and "symbol display alias JSDoc" but couldn't find specific guidance on retrieving JSDoc comments from export assignment nodes when processing default import symbols.
102 changes: 84 additions & 18 deletions src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ import {
getCombinedLocalAndExportSymbolFlags,
getDeclarationOfKind,
getExternalModuleImportEqualsDeclarationExpression,
getMeaningFromLocation,
getNameOfDeclaration,
getNodeModifiers,
getObjectFlags,
getParseTreeNode,
getSourceFileOfNode,
getTextOfConstantValue,
getTextOfIdentifierOrLiteral,
getTextOfNode,
getMeaningFromLocation,
getNameOfDeclaration,
getNodeModifiers,
getObjectFlags,
getParseTreeNode,
getSourceFileOfNode,
getTextOfConstantValue,
getTextOfIdentifierOrLiteral,
getTextOfNode,
getJSDocCommentsAndTags,
hasSyntacticModifier,
idText,
ImportEqualsDeclaration,
Expand All @@ -48,8 +49,10 @@ import {
isFunctionBlock,
isFunctionExpression,
isFunctionLike,
isIdentifier,
isInExpressionContext,
isIdentifier,
isInExpressionContext,
isJSDoc,
isJSDocDeprecatedTag,
isJsxOpeningLikeElement,
isLet,
isModuleWithStringLiteralName,
Expand Down Expand Up @@ -268,7 +271,7 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
alias?: Symbol,
maximumLength?: number,
verbosityLevel?: number,
): SymbolDisplayPartsDocumentationAndSymbolKind {
): SymbolDisplayPartsDocumentationAndSymbolKind {
const displayParts: SymbolDisplayPart[] = [];
let documentation: SymbolDisplayPart[] = [];
let tags: JSDocTagInfo[] = [];
Expand Down Expand Up @@ -583,9 +586,9 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
}
}
}
// don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself
if (symbol.flags & SymbolFlags.Alias) {
prefixNextMeaning();
// don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself
if (symbol.flags & SymbolFlags.Alias) {
prefixNextMeaning();
if (!hasAddedSymbolInfo || documentation.length === 0 && tags.length === 0) {
const resolvedSymbol = typeChecker.getAliasedSymbol(symbol);
if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) {
Expand Down Expand Up @@ -615,9 +618,72 @@ function getSymbolDisplayPartsDocumentationAndSymbolKindWorker(
typeWriterOut.canIncreaseExpansionDepth = true;
}
}
else {
documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker);
tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker);
else {
documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker);
tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker);
}

// For default imports, also try to get JSDoc from the corresponding export assignment
// Default imports have Alias flag but not ExportValue flag
if ((symbol.flags & SymbolFlags.Alias) && !(symbol.flags & SymbolFlags.ExportValue)) {
const sourceFile = getSourceFileOfNode(resolvedNode);
// Find export assignment that exports the resolved symbol as default
for (const statement of sourceFile.statements) {
if (statement.kind === SyntaxKind.ExportAssignment && !(statement as ExportAssignment).isExportEquals) {
const exportAssignment = statement as ExportAssignment;
if (isIdentifier(exportAssignment.expression)) {
const exportedSymbol = typeChecker.getSymbolAtLocation(exportAssignment.expression);
if (exportedSymbol === resolvedSymbol) {
// Found the export assignment, get its JSDoc directly from the node
const jsDocCommentsAndTags = getJSDocCommentsAndTags(exportAssignment);
if (jsDocCommentsAndTags.length > 0) {
const exportDoc: SymbolDisplayPart[] = [];
const exportTags: JSDocTagInfo[] = [];

for (const jsDocOrTag of jsDocCommentsAndTags) {
if (isJSDoc(jsDocOrTag)) {
// Extract documentation from JSDoc comment
if (jsDocOrTag.comment) {
const commentText = typeof jsDocOrTag.comment === "string"
? jsDocOrTag.comment
: jsDocOrTag.comment.map(c => c.text || "").join("");
if (commentText) {
exportDoc.push({ text: commentText, kind: "text" });
}
}

// Extract tags from JSDoc
if (jsDocOrTag.tags) {
for (const tag of jsDocOrTag.tags) {
if (isJSDocDeprecatedTag(tag)) {
const tagText = tag.comment
? (typeof tag.comment === "string"
? tag.comment
: tag.comment.map(c => c.text || "").join(""))
: undefined;
exportTags.push({
name: "deprecated",
text: tagText ? [{ text: tagText, kind: "text" }] : undefined
});
}
}
}
}
}

// Use export assignment JSDoc if it exists, overriding resolved symbol JSDoc
if (exportDoc.length > 0) {
documentationFromAlias = exportDoc;
}
if (exportTags.length > 0) {
tagsFromAlias = exportTags;
}
}
break;
}
}
}
}
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions tests/cases/fourslash/quickInfoDeprecatedDefaultExport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference path="fourslash.ts" />

//// @Filename: mod.ts
//// /** @deprecated please don't use this */
//// export const depr = 'deprecated';
////
//// /** Please use this one */
//// export const notDeprecated = 'not deprecated';
////
//// /** @deprecated please import { notDeprecated } instead */
//// export default notDeprecated;

//// @Filename: index.ts
//// import defaultExport/*0*/, { depr, notDeprecated } from './mod.js';
////
//// console.log(defaultExport/*1*/);
//// console.log(depr/*2*/);
//// console.log(notDeprecated/*3*/);

verify.baselineQuickInfo();