UNPKG

typescript

Version:

TypeScript is a language for application scale JavaScript development

202 lines • 7.35 kB
import { assertNever } from "../internal/utils.js"; import { SyntaxKind, } from "./ast.js"; import { isIdentifier, isJSDoc, isJSDocOverloadTag, isJSDocParameterTag, isJSDocSatisfiesTag, isJSDocTemplateTag, isJSDocTypeTag, isParenthesizedExpression, isPrivateIdentifier, } from "./is.generated.js"; /** Get all JSDoc tags related to a node, including those on parent nodes. */ export function getJSDocTags(node) { return getJSDocCommentsAndTags(node); } /** Gets all JSDoc tags that match a specified predicate */ export function getAllJSDocTags(node, predicate) { return getJSDocTags(node).filter(predicate); } /** Gets all JSDoc tags of a specified kind */ export function getAllJSDocTagsOfKind(node, kind) { return getJSDocTags(node).filter(doc => doc.kind === kind); } /** Gets the text of a jsdoc comment, flattening links to their text. */ export function getTextOfJSDocComment(comment) { return typeof comment === "string" ? comment : comment?.map(c => c.kind === SyntaxKind.JSDocText ? c.text : formatJSDocLink(c)).join(""); } function isVariableLike(node) { switch (node.kind) { case SyntaxKind.BindingElement: case SyntaxKind.EnumMember: case SyntaxKind.Parameter: case SyntaxKind.PropertyAssignment: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.ShorthandPropertyAssignment: case SyntaxKind.VariableDeclaration: return true; } return false; } function getJSDocNodes(node) { const jsDoc = node.jsDoc; if (!jsDoc || jsDoc.length === 0) { return []; } const result = []; for (const j of jsDoc) { if (isJSDoc(j)) { result.push(j); } } return result; } /** * Determines whether a host node owns a JSDoc tag. A `@type` / `@satisfies` tag * attached to a parenthesized expression belongs only to that expression. */ function ownsJSDocTag(hostNode, tag) { return !(isJSDocTypeTag(tag) || isJSDocSatisfiesTag(tag)) || !tag.parent || !isJSDoc(tag.parent) || !tag.parent.parent || !isParenthesizedExpression(tag.parent.parent) || tag.parent.parent === hostNode; } function filterOwnedJSDocTags(hostNode, comments) { const result = []; const lastJSDoc = comments[comments.length - 1]; for (const jsDoc of comments) { if (!jsDoc.tags) { continue; } if (jsDoc === lastJSDoc) { for (const tag of jsDoc.tags) { if (ownsJSDocTag(hostNode, tag)) { result.push(tag); } } } else { // Tags from earlier comments only contribute their `@overload` tags. for (const tag of jsDoc.tags) { if (isJSDocOverloadTag(tag)) { result.push(tag); } } } } return result; } function getJSDocParameterTags(param) { const result = []; const name = param.name; const parentTags = getJSDocTags(param.parent); if (name && isIdentifier(name)) { for (const tag of parentTags) { if (isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.text === name.text) { result.push(tag); } } } else if (name) { // Binding patterns and JSDoc function syntax match parameter tags by position. const parameters = param.parent.parameters; const i = parameters ? [...parameters].indexOf(param) : -1; if (i > -1) { const paramTags = parentTags.filter(isJSDocParameterTag); if (i < paramTags.length) { result.push(paramTags[i]); } } } return result; } function getJSDocTypeParameterTags(typeParam) { const result = []; const name = typeParam.name.text; for (const tag of getJSDocTags(typeParam.parent)) { if (isJSDocTemplateTag(tag) && [...tag.typeParameters].some(tp => tp.name.text === name)) { result.push(tag); } } return result; } // Keep in sync with ast.GetNextJSDocCommentLocation. function getNextJSDocCommentLocation(node) { const parent = node.parent; if (parent) { switch (parent.kind) { case SyntaxKind.PropertyAssignment: case SyntaxKind.ExportAssignment: case SyntaxKind.PropertyDeclaration: case SyntaxKind.VariableDeclaration: case SyntaxKind.SatisfiesExpression: case SyntaxKind.ReturnStatement: case SyntaxKind.VariableStatement: case SyntaxKind.ExpressionStatement: return parent; case SyntaxKind.VariableDeclarationList: if (parent.declarations[0] === node) { return parent; } break; } } return undefined; } function getJSDocCommentsAndTags(hostNode) { const result = []; // Pull parameter comments from a declaring initializer (e.g. `var x = function () {}`). if (isVariableLike(hostNode)) { const initializer = hostNode.initializer; if (initializer) { const initJSDoc = getJSDocNodes(initializer); if (initJSDoc.length) { result.push(...filterOwnedJSDocTags(hostNode, initJSDoc)); } } } let node = hostNode; while (node && node.parent) { const jsDocNodes = getJSDocNodes(node); if (jsDocNodes.length) { result.push(...filterOwnedJSDocTags(hostNode, jsDocNodes)); } if (node.kind === SyntaxKind.Parameter) { result.push(...getJSDocParameterTags(node)); break; } if (node.kind === SyntaxKind.TypeParameter) { result.push(...getJSDocTypeParameterTags(node)); break; } node = getNextJSDocCommentLocation(node); } return result; } function formatJSDocLink(link) { const kind = link.kind === SyntaxKind.JSDocLink ? "link" : link.kind === SyntaxKind.JSDocLinkCode ? "linkcode" : "linkplain"; const name = link.name ? entityNameToString(link.name) : ""; const space = link.name && (link.text === "" || link.text.startsWith("://")) ? "" : " "; return `{@${kind} ${name}${space}${link.text}}`; } function entityNameToString(name) { switch (name.kind) { case SyntaxKind.ThisKeyword: return "this"; case SyntaxKind.PrivateIdentifier: case SyntaxKind.Identifier: return name.getFullWidth() === 0 ? name.text : name.getText(); case SyntaxKind.QualifiedName: return entityNameToString(name.left) + "." + entityNameToString(name.right); case SyntaxKind.PropertyAccessExpression: if (isIdentifier(name.name) || isPrivateIdentifier(name.name)) { return entityNameToString(name.expression) + "." + entityNameToString(name.name); } else { return assertNever(name.name); } case SyntaxKind.JsxNamespacedName: return entityNameToString(name.namespace) + ":" + entityNameToString(name.name); default: return assertNever(name); } } //# sourceMappingURL=jsdoc.js.map