'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var jsdocTypePrattParser = require('jsdoc-type-pratt-parser'); var esquery = require('esquery'); var commentParser = require('comment-parser'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var esquery__default = /*#__PURE__*/_interopDefaultLegacy(esquery); const stripEncapsulatingBrackets = (container, isArr) => { if (isArr) { const firstItem = container[0]; firstItem.rawType = firstItem.rawType.replace(/^\{/u, ''); const lastItem = container[container.length - 1]; lastItem.rawType = lastItem.rawType.replace(/\}$/u, ''); return; } container.rawType = container.rawType.replace(/^\{/u, '').replace(/\}$/u, ''); }; const commentParserToESTree = (jsdoc, mode) => { const { tokens: { delimiter: delimiterRoot, lineEnd: lineEndRoot, postDelimiter: postDelimiterRoot, end: endRoot, description: descriptionRoot } } = jsdoc.source[0]; const ast = { delimiter: delimiterRoot, description: descriptionRoot, descriptionLines: [], // `end` will be overwritten if there are other entries end: endRoot, postDelimiter: postDelimiterRoot, lineEnd: lineEndRoot, type: 'JsdocBlock' }; const tags = []; let lastDescriptionLine; let lastTag = null; jsdoc.source.slice(1).forEach((info, idx) => { const { tokens } = info; const { delimiter, description, postDelimiter, start, tag, end, type: rawType } = tokens; if (tag || end) { if (lastDescriptionLine === undefined) { lastDescriptionLine = idx; } // Clean-up with last tag before end or new tag if (lastTag) { // Strip out `}` that encapsulates and is not part of // the type stripEncapsulatingBrackets(lastTag); if (lastTag.typeLines.length) { stripEncapsulatingBrackets(lastTag.typeLines, true); } // With even a multiline type now in full, add parsing let parsedType = null; try { parsedType = jsdocTypePrattParser.parse(lastTag.rawType, mode); } catch {// Ignore } lastTag.parsedType = parsedType; } if (end) { ast.end = end; return; } const { end: ed, ...tkns } = tokens; const tagObj = { ...tkns, descriptionLines: [], rawType: '', type: 'JsdocTag', typeLines: [] }; tagObj.tag = tagObj.tag.replace(/^@/u, ''); lastTag = tagObj; tags.push(tagObj); } if (rawType) { // Will strip rawType brackets after this tag lastTag.typeLines.push({ delimiter, postDelimiter, rawType, start, type: 'JsdocTypeLine' }); lastTag.rawType += rawType; } if (description) { const holder = lastTag || ast; holder.descriptionLines.push({ delimiter, description, postDelimiter, start, type: 'JsdocDescriptionLine' }); holder.description += holder.description ? '\n' + description : description; } }); ast.lastDescriptionLine = lastDescriptionLine; ast.tags = tags; return ast; }; const jsdocVisitorKeys = { JsdocBlock: ['tags', 'descriptionLines'], JsdocDescriptionLine: [], JsdocTypeLine: [], JsdocTag: ['descriptionLines', 'typeLines', 'parsedType'] }; /** * @callback CommentHandler * @param {string} commentSelector * @param {Node} jsdoc * @returns {boolean} */ /** * @param {Settings} settings * @returns {CommentHandler} */ const commentHandler = settings => { /** * @type {CommentHandler} */ return (commentSelector, jsdoc) => { const { mode } = settings; const selector = esquery__default['default'].parse(commentSelector); const ast = commentParserToESTree(jsdoc, mode); return esquery__default['default'].matches(ast, selector, null, { visitorKeys: { ...jsdocTypePrattParser.visitorKeys, ...jsdocVisitorKeys } }); }; }; const toCamelCase = str => { return str.toLowerCase().replace(/^[a-z]/gu, init => { return init.toUpperCase(); }).replace(/_(?[a-z])/gu, (_, n1, o, s, { wordInit }) => { return wordInit.toUpperCase(); }); }; /* eslint-disable prefer-named-capture-group -- Temporary */ const { seedBlock, seedTokens } = commentParser.util; const { name: nameTokenizer, tag: tagTokenizer, type: typeTokenizer, description: descriptionTokenizer } = commentParser.tokenizers; const hasSeeWithLink = spec => { return spec.tag === 'see' && /\{@link.+?\}/u.test(spec.source[0].source); }; const defaultNoTypes = ['default', 'defaultvalue', 'see']; const defaultNoNames = ['access', 'author', 'default', 'defaultvalue', 'description', 'example', 'exception', 'license', 'return', 'returns', 'since', 'summary', 'throws', 'version', 'variation']; const getTokenizers = ({ noTypes = defaultNoTypes, noNames = defaultNoNames } = {}) => { // trim return [// Tag tagTokenizer(), // Type spec => { if (noTypes.includes(spec.tag)) { return spec; } return typeTokenizer()(spec); }, // Name spec => { if (spec.tag === 'template') { // const preWS = spec.postTag; const remainder = spec.source[0].tokens.description; const pos = remainder.search(/(? -1) { [, postName, description, lineEnd] = extra.match(/(\s*)([^\r]*)(\r)?/u); } spec.name = name; spec.optional = false; const { tokens } = spec.source[0]; tokens.name = name; tokens.postName = postName; tokens.description = description; tokens.lineEnd = lineEnd || ''; return spec; } if (noNames.includes(spec.tag) || hasSeeWithLink(spec)) { return spec; } return nameTokenizer()(spec); }, // Description spec => { return descriptionTokenizer('preserve')(spec); }]; }; /** * * @param {PlainObject} commentNode * @param {string} indent Whitespace * @returns {PlainObject} */ const parseComment = (commentNode, indent) => { // Preserve JSDoc block start/end indentation. return commentParser.parse(`/*${commentNode.value}*/`, { // @see https://github.com/yavorskiy/comment-parser/issues/21 tokenizers: getTokenizers() })[0] || seedBlock({ source: [{ number: 0, tokens: seedTokens({ delimiter: '/**' }) }, { number: 1, tokens: seedTokens({ end: '*/', start: indent + ' ' }) }] }); }; /** * Obtained originally from {@link https://github.com/eslint/eslint/blob/master/lib/util/source-code.js#L313}. * * @license MIT */ /** * Checks if the given token is a comment token or not. * * @param {Token} token - The token to check. * @returns {boolean} `true` if the token is a comment token. */ const isCommentToken = token => { return token.type === 'Line' || token.type === 'Block' || token.type === 'Shebang'; }; const getDecorator = node => { var _node$declaration, _node$declaration$dec, _node$decorators, _node$parent, _node$parent$decorato; return (node === null || node === void 0 ? void 0 : (_node$declaration = node.declaration) === null || _node$declaration === void 0 ? void 0 : (_node$declaration$dec = _node$declaration.decorators) === null || _node$declaration$dec === void 0 ? void 0 : _node$declaration$dec[0]) || (node === null || node === void 0 ? void 0 : (_node$decorators = node.decorators) === null || _node$decorators === void 0 ? void 0 : _node$decorators[0]) || (node === null || node === void 0 ? void 0 : (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : (_node$parent$decorato = _node$parent.decorators) === null || _node$parent$decorato === void 0 ? void 0 : _node$parent$decorato[0]); }; /** * Check to see if its a ES6 export declaration. * * @param {ASTNode} astNode An AST node. * @returns {boolean} whether the given node represents an export declaration. * @private */ const looksLikeExport = function (astNode) { return astNode.type === 'ExportDefaultDeclaration' || astNode.type === 'ExportNamedDeclaration' || astNode.type === 'ExportAllDeclaration' || astNode.type === 'ExportSpecifier'; }; const getTSFunctionComment = function (astNode) { const { parent } = astNode; const grandparent = parent.parent; const greatGrandparent = grandparent.parent; const greatGreatGrandparent = greatGrandparent && greatGrandparent.parent; // istanbul ignore if if (parent.type !== 'TSTypeAnnotation') { return astNode; } switch (grandparent.type) { case 'ClassProperty': case 'TSDeclareFunction': case 'TSMethodSignature': case 'TSPropertySignature': return grandparent; case 'ArrowFunctionExpression': // istanbul ignore else if (greatGrandparent.type === 'VariableDeclarator' // && greatGreatGrandparent.parent.type === 'VariableDeclaration' ) { return greatGreatGrandparent.parent; } // istanbul ignore next return astNode; case 'FunctionExpression': // istanbul ignore else if (greatGrandparent.type === 'MethodDefinition') { return greatGrandparent; } // Fallthrough default: // istanbul ignore if if (grandparent.type !== 'Identifier') { // istanbul ignore next return astNode; } } // istanbul ignore next switch (greatGrandparent.type) { case 'ArrowFunctionExpression': // istanbul ignore else if (greatGreatGrandparent.type === 'VariableDeclarator' && greatGreatGrandparent.parent.type === 'VariableDeclaration') { return greatGreatGrandparent.parent; } // istanbul ignore next return astNode; case 'FunctionDeclaration': return greatGrandparent; case 'VariableDeclarator': // istanbul ignore else if (greatGreatGrandparent.type === 'VariableDeclaration') { return greatGreatGrandparent; } // Fallthrough default: // istanbul ignore next return astNode; } }; const invokedExpression = new Set(['CallExpression', 'OptionalCallExpression', 'NewExpression']); const allowableCommentNode = new Set(['VariableDeclaration', 'ExpressionStatement', 'MethodDefinition', 'Property', 'ObjectProperty', 'ClassProperty']); /** * Reduces the provided node to the appropriate node for evaluating * JSDoc comment status. * * @param {ASTNode} node An AST node. * @param {SourceCode} sourceCode The ESLint SourceCode. * @returns {ASTNode} The AST node that can be evaluated for appropriate * JSDoc comments. * @private */ const getReducedASTNode = function (node, sourceCode) { let { parent } = node; switch (node.type) { case 'TSFunctionType': return getTSFunctionComment(node); case 'TSInterfaceDeclaration': case 'TSTypeAliasDeclaration': case 'TSEnumDeclaration': case 'ClassDeclaration': case 'FunctionDeclaration': return looksLikeExport(parent) ? parent : node; case 'TSDeclareFunction': case 'ClassExpression': case 'ObjectExpression': case 'ArrowFunctionExpression': case 'TSEmptyBodyFunctionExpression': case 'FunctionExpression': if (!invokedExpression.has(parent.type)) { while (!sourceCode.getCommentsBefore(parent).length && !/Function/u.test(parent.type) && !allowableCommentNode.has(parent.type)) { ({ parent } = parent); if (!parent) { break; } } if (parent && parent.type !== 'FunctionDeclaration' && parent.type !== 'Program') { if (parent.parent && parent.parent.type === 'ExportNamedDeclaration') { return parent.parent; } return parent; } } return node; default: return node; } }; /** * Checks for the presence of a JSDoc comment for the given node and returns it. * * @param {ASTNode} astNode The AST node to get the comment for. * @param {SourceCode} sourceCode * @param {{maxLines: Integer, minLines: Integer}} settings * @returns {Token|null} The Block comment token containing the JSDoc comment * for the given node or null if not found. * @private */ const findJSDocComment = (astNode, sourceCode, settings) => { const { minLines, maxLines } = settings; let currentNode = astNode; let tokenBefore = null; while (currentNode) { const decorator = getDecorator(currentNode); if (decorator) { currentNode = decorator; } tokenBefore = sourceCode.getTokenBefore(currentNode, { includeComments: true }); if (!tokenBefore || !isCommentToken(tokenBefore)) { return null; } if (tokenBefore.type === 'Line') { currentNode = tokenBefore; continue; } break; } if (tokenBefore.type === 'Block' && tokenBefore.value.charAt(0) === '*' && currentNode.loc.start.line - tokenBefore.loc.end.line >= minLines && currentNode.loc.start.line - tokenBefore.loc.end.line <= maxLines) { return tokenBefore; } return null; }; /** * Retrieves the JSDoc comment for a given node. * * @param {SourceCode} sourceCode The ESLint SourceCode * @param {ASTNode} node The AST node to get the comment for. * @param {PlainObject} settings The settings in context * @returns {Token|null} The Block comment token containing the JSDoc comment * for the given node or null if not found. * @public */ const getJSDocComment = function (sourceCode, node, settings) { const reducedNode = getReducedASTNode(node, sourceCode); return findJSDocComment(reducedNode, sourceCode, settings); }; Object.defineProperty(exports, 'jsdocTypeVisitorKeys', { enumerable: true, get: function () { return jsdocTypePrattParser.visitorKeys; } }); exports.commentHandler = commentHandler; exports.commentParserToESTree = commentParserToESTree; exports.defaultNoNames = defaultNoNames; exports.defaultNoTypes = defaultNoTypes; exports.findJSDocComment = findJSDocComment; exports.getDecorator = getDecorator; exports.getJSDocComment = getJSDocComment; exports.getReducedASTNode = getReducedASTNode; exports.getTokenizers = getTokenizers; exports.hasSeeWithLink = hasSeeWithLink; exports.jsdocVisitorKeys = jsdocVisitorKeys; exports.parseComment = parseComment; exports.toCamelCase = toCamelCase;