From 25bfd50e9c3dacd0639d17c9170950d3373e2b71 Mon Sep 17 00:00:00 2001 From: Caner Akdas Date: Sat, 16 Nov 2024 23:54:07 +0300 Subject: [PATCH] meta: eslint jsdoc plugin added --- eslint.config.mjs | 31 ++++ package-lock.json | 155 ++++++++++++++++++++ package.json | 1 + src/generators/json-simple/index.mjs | 10 +- src/generators/legacy-html-all/index.mjs | 19 ++- src/generators/legacy-html/index.mjs | 19 ++- src/generators/man-page/index.mjs | 12 ++ src/generators/man-page/utils/converter.mjs | 20 ++- src/loader.mjs | 4 +- src/metadata.mjs | 14 +- src/queries.mjs | 28 ++++ src/utils/remark.mjs | 10 +- src/utils/unist.mjs | 2 +- 13 files changed, 297 insertions(+), 28 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 2863e0f..26689c9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,12 +1,43 @@ import pluginJs from '@eslint/js'; import eslintConfigPrettier from 'eslint-config-prettier'; +import jsdoc from 'eslint-plugin-jsdoc'; import globals from 'globals'; export default [ // @see https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores { files: ['src/**/*.mjs'], + plugins: { + jsdoc: jsdoc, + }, languageOptions: { globals: globals.node }, + rules: { + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-indentation': 'error', + 'jsdoc/require-jsdoc': [ + 'error', + { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: true, + FunctionExpression: true, + }, + }, + ], + 'jsdoc/require-param': 'error', + }, + }, + // Override rules for test files to disable JSDoc rules + { + files: ['src/**/*.test.mjs'], + rules: { + 'jsdoc/check-alignment': 'off', + 'jsdoc/check-indentation': 'off', + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param': 'off', + }, }, // @see https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-and-ignores { diff --git a/package-lock.json b/package-lock.json index bc69cbe..e20b861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@types/node": "^22.9.0", "eslint": "^9.13.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsdoc": "^50.5.0", "globals": "^15.11.0", "husky": "^9.1.6", "lint-staged": "^15.2.10", @@ -44,6 +45,21 @@ "prettier": "3.3.3" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", + "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -334,6 +350,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@shikijs/core": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.2.tgz", @@ -525,6 +554,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -804,6 +843,16 @@ "node": ">=18" } }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -946,6 +995,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1032,6 +1088,32 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "50.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.5.0.tgz", + "integrity": "sha512-xTkshfZrUbiSHXBwZ/9d5ulZ2OcHXxSvm/NPo494H/hadLRJwOq5PMV0EUpMqsb9V+kQo+9BAgi6Z7aJtdBp2A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.49.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.6", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-scope": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", @@ -1660,6 +1742,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3103,6 +3195,20 @@ "node": ">=6" } }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", @@ -3421,6 +3527,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -3480,6 +3593,31 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -3642,6 +3780,23 @@ "node": ">=8" } }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/terser": { "version": "5.31.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", diff --git a/package.json b/package.json index b883063..8d684c3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@types/node": "^22.9.0", "eslint": "^9.13.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsdoc": "^50.5.0", "globals": "^15.11.0", "husky": "^9.1.6", "lint-staged": "^15.2.10", diff --git a/src/generators/json-simple/index.mjs b/src/generators/json-simple/index.mjs index 938e190..f7eb265 100644 --- a/src/generators/json-simple/index.mjs +++ b/src/generators/json-simple/index.mjs @@ -29,6 +29,11 @@ export default { dependsOn: 'ast', + /** + * Generates the simplified JSON version of the API docs + * @param {Input} input + * @param {Partial} options + */ async generate(input, options) { // Gets a remark processor for stringifying the AST tree into JSON const remarkProcessor = getRemark(); @@ -45,7 +50,10 @@ export default { createQueries.UNIST.isHeading, ]); - // For the JSON generate we want to transform the whole content into JSON + /** + * For the JSON generate we want to transform the whole content into JSON + * @returns {string} The stringified JSON version of the content + */ content.toJSON = () => remarkProcessor.stringify(content); return { ...node, content }; diff --git a/src/generators/legacy-html-all/index.mjs b/src/generators/legacy-html-all/index.mjs index 3cbd1ec..f90884a 100644 --- a/src/generators/legacy-html-all/index.mjs +++ b/src/generators/legacy-html-all/index.mjs @@ -12,13 +12,13 @@ import { getRemarkRehype } from '../../utils/remark.mjs'; /** * @typedef {{ - * api: string; - * added: string; - * section: string; - * version: string; - * toc: string; - * nav: string; - * content: string; + * api: string; + * added: string; + * section: string; + * version: string; + * toc: string; + * nav: string; + * content: string; * }} TemplateValues * * This generator generates the legacy HTML pages of the legacy API docs @@ -41,6 +41,11 @@ export default { dependsOn: 'legacy-html', + /** + * Generates the `all.html` file from the `legacy-html` generator + * @param {Input} input + * @param {Partial} options + */ async generate(input, { version, releases, output }) { const inputWithoutIndex = input.filter(entry => entry.api !== 'index'); diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index d36e990..9b88421 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -14,13 +14,13 @@ import { getRemarkRehype } from '../../utils/remark.mjs'; /** * @typedef {{ - * api: string; - * added: string; - * section: string; - * version: string; - * toc: string; - * nav: string; - * content: string; + * api: string; + * added: string; + * section: string; + * version: string; + * toc: string; + * nav: string; + * content: string; * }} TemplateValues * * This generator generates the legacy HTML pages of the legacy API docs @@ -43,6 +43,11 @@ export default { dependsOn: 'ast', + /** + * Generates the legacy version of the API docs in HTML + * @param {Input} input + * @param {Partial} options + */ async generate(input, { releases, version, output }) { // This array holds all the generated values for each module const generatedValues = []; diff --git a/src/generators/man-page/index.mjs b/src/generators/man-page/index.mjs index c36ad5a..8ee045a 100644 --- a/src/generators/man-page/index.mjs +++ b/src/generators/man-page/index.mjs @@ -27,6 +27,11 @@ export default { dependsOn: 'ast', + /** + * Generates the Node.js man-page + * @param {Input} input + * @param {Partial} options + */ async generate(input, options) { // Filter to only 'cli'. const components = input.filter(({ api }) => api === 'cli'); @@ -80,6 +85,13 @@ export default { }, }; +/** + * @param {Array} components + * @param {number} start + * @param {number} end + * @param {(element: ApiDocMetadataEntry) => string} convert + * @returns {string} + */ function extractMandoc(components, start, end, convert) { return components .slice(start, end) diff --git a/src/generators/man-page/utils/converter.mjs b/src/generators/man-page/utils/converter.mjs index 5bc5544..b0b8b69 100644 --- a/src/generators/man-page/utils/converter.mjs +++ b/src/generators/man-page/utils/converter.mjs @@ -9,8 +9,20 @@ * @returns {string} The Mandoc formatted string representing the given node and its children. */ export function convertNodeToMandoc(node, isListItem = false) { - const convertChildren = (sep = '', ili = false) => - node.children.map(child => convertNodeToMandoc(child, ili)).join(sep); + /** + * Converts the children of a node to Mandoc format. + * @param {string} separator + * @param {boolean} isListItem + */ + const convertChildren = (separator = '', isListItem = false) => + node.children + .map(child => convertNodeToMandoc(child, isListItem)) + .join(separator); + + /** + * Escapes special characters in plain text content. + * @returns {string} + */ const escapeText = () => node.value.replace(/\\/g, '\\\\'); switch (node.type) { @@ -97,6 +109,10 @@ export function flagValueToMandoc(flag) { return `${prefix} Ar ${value}`; } +/** + * Formats a command-line flag for Mandoc representation. + * @param {string} flag + */ const formatFlag = flag => // 'Fl' denotes a flag, followed by an optional 'Ar' (argument). `Fl ${flag.split(/\[?[= ]/)[0].slice(1)}${flagValueToMandoc(flag)}`; diff --git a/src/loader.mjs b/src/loader.mjs index b3ae300..12715e0 100644 --- a/src/loader.mjs +++ b/src/loader.mjs @@ -16,8 +16,8 @@ const createLoader = () => { * Loads API Doc files and transforms it into VFiles * * @param {string} searchPath A glob/path for API docs to be loaded - * The input string can be a simple path (relative or absolute) - * The input string can also be any allowed glob string + * The input string can be a simple path (relative or absolute) + * The input string can also be any allowed glob string * * @see https://code.visualstudio.com/docs/editor/glob-patterns */ diff --git a/src/metadata.mjs b/src/metadata.mjs index 3535869..e29733b 100644 --- a/src/metadata.mjs +++ b/src/metadata.mjs @@ -37,9 +37,9 @@ const createMetadata = slugger => { * transformed into NavigationEntries and MetadataEntries * * @type {{ - * heading: ApiDocMetadataEntry['heading'], - * stability: ApiDocMetadataEntry['stability'], - * properties: ApiDocRawMetadataEntry, + * heading: ApiDocMetadataEntry['heading'], + * stability: ApiDocMetadataEntry['stability'], + * properties: ApiDocRawMetadataEntry, * }} */ const internalMetadata = { @@ -130,10 +130,14 @@ const createMetadata = slugger => { internalMetadata.heading.data.type = type ?? internalMetadata.heading.data.type; - // Defines the toJSON method for the Heading AST node to be converted as JSON + /** + * Defines the toJSON method for the Heading AST node to be converted as JSON + */ internalMetadata.heading.toJSON = () => internalMetadata.heading.data; - // Maps the Stability Index AST nodes into a JSON objects from their data properties + /** + * Maps the Stability Index AST nodes into a JSON objects from their data properties + */ internalMetadata.stability.toJSON = () => internalMetadata.stability.children.map(node => node.data); diff --git a/src/queries.mjs b/src/queries.mjs index 8937539..33979a2 100644 --- a/src/queries.mjs +++ b/src/queries.mjs @@ -186,19 +186,47 @@ createQueries.QUERIES = { }; createQueries.UNIST = { + /** + * @param {import('mdast').Blockquote} blockquote + * @returns {boolean} + */ isStabilityNode: ({ type, children }) => type === 'blockquote' && createQueries.QUERIES.stabilityIndex.test(transformNodesToString(children)), + /** + * @param {import('mdast').Html} html + * @returns {boolean} + */ isYamlNode: ({ type, value }) => type === 'html' && createQueries.QUERIES.yamlInnerContent.test(value), + /** + * @param {import('mdast').Text} text + * @returns {boolean} + */ isTextWithType: ({ type, value }) => type === 'text' && createQueries.QUERIES.normalizeTypes.test(value), + /** + * @param {import('mdast').Html} html + * @returns {boolean} + */ isHtmlWithType: ({ type, value }) => type === 'html' && createQueries.QUERIES.linksWithTypes.test(value), + /** + * @param {import('mdast').Link} link + * @returns {boolean} + */ isMarkdownUrl: ({ type, url }) => type === 'link' && createQueries.QUERIES.markdownUrl.test(url), + /** + * @param {import('mdast').Heading} heading + * @returns {boolean} + */ isHeading: ({ type, depth }) => type === 'heading' && depth >= 1 && depth <= 5, + /** + * @param {import('mdast').LinkReference} linkReference + * @returns {boolean} + */ isLinkReference: ({ type, identifier }) => type === 'linkReference' && !!identifier, }; diff --git a/src/utils/remark.mjs b/src/utils/remark.mjs index 9c155ef..0fc4dda 100644 --- a/src/utils/remark.mjs +++ b/src/utils/remark.mjs @@ -11,12 +11,16 @@ import rehypeStringify from 'rehype-stringify'; import syntaxHighlighter from './highlighter.mjs'; -// Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown) +/** + * Retrieves an instance of Remark configured to parse GFM (GitHub Flavored Markdown) + */ export const getRemark = () => unified().use(remarkParse).use(remarkGfm).use(remarkStringify); -// Retrieves an instance of Remark configured to output stringified HTML code -// including parsing Code Boxes with syntax highlighting +/** + * Retrieves an instance of Remark configured to output stringified HTML code + * including parsing Code Boxes with syntax highlighting + */ export const getRemarkRehype = () => unified() .use(remarkParse) diff --git a/src/utils/unist.mjs b/src/utils/unist.mjs index 5b35288..32270bb 100644 --- a/src/utils/unist.mjs +++ b/src/utils/unist.mjs @@ -36,7 +36,7 @@ export const transformNodesToString = nodes => { * NOTE: Not yet used, but probably going to be used by the JSON generator. * * @param {import('unist').Node | undefined} nodeA The Node to be used as a position reference to check against - * the other Node. If the other Node is before this one, the callback will be called. + * the other Node. If the other Node is before this one, the callback will be called. * @param {import('unist').Node | undefined} nodeB The Node to be checked against the position of the first Node * @param {(nodeA: import('unist').Node, nodeB: import('unist').Node) => void} callback The callback to be called */