@longzai-intelligence-oxlint/tsdoc-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @longzai-intelligence-oxlint/tsdoc-plugin
2
+
3
+ ## 意图
4
+
5
+ 在静态检查阶段守护 TSDoc 注释规范,统一组织内的文档注释写法并拦截遗留 JSDoc 写法。
6
+
7
+ ## 定位
8
+
9
+ Oxlint 生态的 TSDoc 治理插件,规则数量较多,是组织内文档注释治理的核心载体。
10
+
11
+ ## 职能
12
+
13
+ 提供共二十条规则,覆盖:禁用注释内代码、禁用 description 标签、禁用多余 param、禁用遗留 JSDoc 标签、禁用占位描述、param 连字符格式、标签前空行、deprecated 必备替代说明、description 必备、多行属性注释必备、param 必备、param 描述必备、returns 必备、returns 描述必备、throws 必备、throws 类型必备、tsdoc 必备、type param 必备、分隔符格式、TODO 格式等;使用 `defineRule` + `createOnce` 实现以获得最佳性能。
14
+
15
+ ## 实现情况
16
+
17
+ 二十条规则均已实现并按规则文件组织,统一注册为 `@longzai-intelligence/tsdoc`;规则集合稳定且覆盖 TSDoc 主要规范维度。
@@ -0,0 +1,10 @@
1
+ import { OxlintRules } from "@longzai-intelligence-oxlint/core";
2
+ //#region src/configs/recommended.config.d.ts
3
+ /**
4
+ * 推荐规则配置
5
+ *
6
+ * 包含所有 TSDoc 相关的推荐规则配置
7
+ */
8
+ declare const recommended: OxlintRules;
9
+ //#endregion
10
+ export { recommended };
@@ -0,0 +1 @@
1
+ const e={"@longzai-intelligence-oxlint/tsdoc-plugin/require-tsdoc":[`error`,{checkAllVariables:!0}],"@longzai-intelligence-oxlint/tsdoc-plugin/require-param":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-returns":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-description":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-param-description":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-returns-description":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-type-param":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/no-description-tag":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/param-hyphen-format":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-blank-line-before-tags":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/separator-format":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/todo-format":`warn`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-throws":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-throws-type":`warn`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-deprecated-alternative":`warn`,"@longzai-intelligence-oxlint/tsdoc-plugin/no-legacy-jsdoc-tags":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/no-placeholder-description":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/no-commented-code":`warn`,"@longzai-intelligence-oxlint/tsdoc-plugin/no-extra-param":`error`,"@longzai-intelligence-oxlint/tsdoc-plugin/require-multiline-property-comment":`error`};export{e as recommended};
@@ -0,0 +1,12 @@
1
+ import { Rule } from "@oxlint/plugins";
2
+ //#region src/index.d.ts
3
+ /**
4
+ * tsdoc 规则集合
5
+ */
6
+ declare const tsdocRules: Record<string, Rule>;
7
+ /**
8
+ * tsdoc 插件
9
+ */
10
+ declare const tsdocPlugin: import("@oxlint/plugins").Plugin;
11
+ //#endregion
12
+ export { tsdocPlugin as default, tsdocPlugin, tsdocRules };
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";import*as n from"zod/mini";const r=[/^\s*(const|let|var)\s+\w+\s*=/,/^\s*function\s+\w+\s*\(/,/^\s*class\s+\w+/,/^\s*interface\s+\w+/,/^\s*type\s+\w+\s*=/,/^\s*enum\s+\w+/,/^\s*import\s+.*from\s+['"]/,/^\s*export\s+(default\s+)?(function|class|const|let|var|interface|type|enum)/,/^\s*return\s+/,/^\s*if\s*\(/,/^\s*for\s*\(/,/^\s*while\s*\(/,/^\s*switch\s*\(/,/^\s*try\s*\{/,/^\s*catch\s*\(/,/^\s*throw\s+/,/^\s*console\.(log|error|warn|info|debug)\s*\(/,/^\s*\w+\s*\(\s*\)\s*;?\s*$/];function i(e){let t=e.trim();if(t.length===0)return!1;for(let e of r)if(e.test(t))return!0;return!1}function a(e){let t=e.split(`
2
+ `),n=0,r=0;for(let e of t){let t=e.trim();t.length===0||t===`*`||(r++,i(t.replace(/^\*\s*/,``))&&n++)}return r>0&&n/r>=.5}function o(e){return i(e.trim())}const s=e({meta:{type:`suggestion`,docs:{description:`禁止注释掉的代码`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n)r.type===`Block`?a(r.value)&&e.report({node:t,message:`检测到注释掉的代码,请删除或恢复该代码`}):r.type===`Line`&&o(r.value)&&e.report({node:t,message:`检测到注释掉的代码,请删除或恢复该代码`})}}}}),c=new Map,l=1e3;function u(e){let t=e.match(/^\{([^}]*)\}\s*(\S+)?\s*(.*)/);if(t)return{type:t[1]??``,name:t[2]||``,description:t[3]||``};let n=e.match(/^(\S+)\s*(.*)/);return n?{name:n[1]??``,description:n[2]||``,type:``}:{name:``,description:``,type:``}}function ee(e){let t=e.match(/^\{([^}]*)\}\s*(.*)/);return t?{type:t[1]??``,description:t[2]||``}:{description:e,type:``}}function te(e){let t=e.match(/^\{@link\s+([^}]+)\}\s*(.*)/);return t?{type:(t[1]??``).trim(),description:t[2]||``}:{description:e,type:``}}function ne(e,t){if(e===`param`||e===`typeParam`||e===`template`){let n=u(t);return{tag:e,name:n.name,description:n.description,type:n.type}}if(e===`returns`||e===`return`){let n=ee(t);return{tag:e,name:``,description:n.description,type:n.type}}if(e===`throws`||e===`throw`){let n=te(t);return{tag:e,name:``,description:n.description,type:n.type}}return{tag:e,name:``,description:t,type:``}}function re(){if(c.size<=l)return;let e=Array.from(c.entries()).sort((e,t)=>e[1].lastAccess-t[1].lastAccess),t=e.slice(0,e.length-l);for(let[e]of t)c.delete(e)}function d(e){let t=c.get(e);if(t)return t.lastAccess=Date.now(),t.result;let n=ie(e);return c.set(e,{result:n,lastAccess:Date.now()}),re(),n}function ie(e){let t=e.trim();if(!t.startsWith(`*`))return null;let n=t.split(`
3
+ `),r=[],i=[],a=null,o=!0;for(let e=0;e<n.length;e++){let t=n[e];if(t)if(t=t.trim(),t.startsWith(`*`)&&(t=t.slice(1).trim()),t.startsWith(`@`)){o=!1,a&&i.push(a);let e=t.match(/^@(\w+)\s*(.*)/);if(e){let t=e[1];if(!t)continue;a=ne(t,e[2]??``)}}else o?(t||r.length>0)&&r.push(t):a&&(a.description+=(a.description?`
4
+ `:``)+t)}return a&&i.push(a),{description:r.join(`
5
+ `).trim(),tags:i}}function f(e){return e.tags.filter(e=>e.tag===`param`)}function p(e){return e.tags.find(e=>e.tag===`returns`||e.tag===`return`)}function m(e){return e.description.trim().length>0}function h(e){let t=e.description.trim();return!(t.length===0||t===`-`||t===`--`)}function g(e){return f(e).map(e=>e.name.split(`.`)[0]).filter(e=>typeof e==`string`)}const ae=e({meta:{type:`suggestion`,docs:{description:`禁止使用 description 标签`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);i&&i.tags.some(e=>e.tag===`description`)&&e.report({node:t,message:`请勿使用 description 标签,直接书写描述文本即可`})}}}}});n.object({type:n.string(),range:n.tuple([n.number(),n.number()])});const oe=n.object({init:n.unknown()}),se=n.object({type:n.string()});n.object({name:n.unknown()}),n.object({left:n.unknown(),right:n.unknown()}),n.object({id:n.object({name:n.string()})}),n.object({key:n.object({name:n.string()})}),n.object({declarations:n.array(n.object({id:n.optional(n.object({name:n.optional(n.string())}))}))}),n.object({type:n.literal(`ThrowStatement`),argument:n.unknown()}),n.object({type:n.literal(`NewExpression`),callee:n.unknown()}),n.object({type:n.literal(`Identifier`),name:n.unknown()}),n.object({consequent:n.array(n.unknown())}),n.object({consequent:n.unknown(),alternate:n.unknown()}),n.object({block:n.unknown(),handler:n.unknown()}),n.object({cases:n.unknown()}),n.object({body:n.array(n.unknown())});const ce=n.record(n.string(),n.unknown()),le=n.object({type:n.enum([`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`])});n.object({type:n.string(),params:n.array(n.unknown())});const ue=n.object({range:n.tuple([n.number(),n.number()])}),de=n.object({range:n.tuple([n.number(),n.number()])}),fe=n.object({range:n.tuple([n.number(),n.number()])}),pe=n.object({id:n.object({name:n.string()})}),me=n.object({key:n.object({name:n.string()})}),he=n.object({declarations:n.array(n.unknown())}),ge=n.object({typeParameters:n.object({params:n.array(n.record(n.string(),n.unknown()))})}),_e=n.object({params:n.array(n.unknown())}),ve=n.object({value:n.unknown()}),ye=n.object({declarations:n.array(n.unknown())});function _(e){return ue.safeParse(e).success}function be(e){return de.safeParse(e).success}function v(e){return fe.safeParse(e).success}function y(e){if(!_(e))return-1;if(`decorators`in e&&Array.isArray(e.decorators)&&e.decorators.length>0){let t=e.decorators[0];if(v(t))return t.range[0]}if(`parent`in e&&e.parent&&typeof e.parent==`object`){let t=e.parent;if(_(t)&&`type`in t&&typeof t.type==`string`){if(t.type===`MethodDefinition`){if(`decorators`in t&&Array.isArray(t.decorators)&&t.decorators.length>0){let e=t.decorators[0];if(v(e))return e.range[0]}return t.range[0]}if(t.type===`Property`)return t.range[0];if(t.type===`VariableDeclarator`){let e=`parent`in t?t.parent:void 0;if(_(e)&&`type`in e&&typeof e.type==`string`&&e.type===`VariableDeclaration`){if(`parent`in e&&e.parent){let t=e.parent;if(_(t)&&`type`in t&&typeof t.type==`string`&&[`ExportNamedDeclaration`,`ExportDefaultDeclaration`].includes(t.type))return t.range[0]}return e.range[0]}}if([`ExportNamedDeclaration`,`ExportDefaultDeclaration`].includes(t.type))return t.range[0]}}return e.range[0]}function xe(e,t,n,r){let i=e.range[1],a=y(t);if(a<0)return!1;let o=n.filter(e=>e.range[0]>=i&&e.range[1]<=a),s=[];if(_(t)&&`decorators`in t){let e=t.decorators;if(Array.isArray(e))for(let t of e)v(t)&&s.push(t.range)}let c=[...o.map(e=>e.range),...s].sort((e,t)=>e[0]-t[0]),l=[],u=i;for(let e of c)e[0]>u&&l.push(r.slice(u,e[0])),u=Math.max(u,e[1]);u<a&&l.push(r.slice(u,a));for(let e of l)if(e.trim().length>0)return!1;return!0}function b(e,t,n){if(!be(t))return null;let r=y(t),i=r>=0?r:t.range[0],a=null;for(let t of e)t.type===`Block`&&t.value.startsWith(`*`)&&t.range[1]<=i&&(a===null||t.range[1]>a.range[1])&&(a={value:t.value,range:t.range});return a&&n&&!xe(a,t,e,n)?null:a}const x={description:`[待填写描述]`,functionDescription:`[待填写函数描述]`,methodDescription:`[待填写方法描述]`,classDescription:`[待填写类描述]`,variableDescription:`[待填写变量描述]`,paramDescription:`[待填写参数描述]`,returnsDescription:`[待填写返回值描述]`,typeParamDescription:`[待填写类型参数描述]`},Se=[/^\s*\*\s*描述\s*$/m,/^\s*\*\s*函数描述\s*$/m,/^\s*\*\s*方法描述\s*$/m,/^\s*\*\s*类描述\s*$/m,/^\s*\*\s*变量描述\s*$/m,/@param\s+\w+\s*-\s*参数描述/,/@returns\s+返回值描述/,/@typeParam\s+\w+\s*-\s*类型参数描述/];function Ce(e){return Object.values(x).some(t=>e.includes(t))?!0:Se.some(t=>t.test(e))}function S(e,t){let n=t[0];for(;n>0&&e[n-1]!==`
6
+ `;)n--;return e.slice(n,t[0])}function we(e,t,n,r){let i=`\n${r} * @param ${n} - ${x.paramDescription}`,a=t.value.match(/@param[^@]*$/);if(a&&a.index!==void 0){let n=t.range[0]+2+a.index+a[0].length;return e.insertTextAfterRange([t.range[0],n],i)}let o=t.range[1]-2;return e.insertTextBeforeRange([o,t.range[1]],i+`\n${r} `)}function Te(e,t,n){let r=`\n${n} * @returns ${x.returnsDescription}`,i=t.value.match(/@\w+[^@]*$/);if(i&&i.index!==void 0){let n=t.range[0]+2+i.index+i[0].length;return e.insertTextAfterRange([t.range[0],n],r)}let a=t.range[1]-2;return e.insertTextBeforeRange([a,t.range[1]],r+`\n${n} `)}function Ee(e,t,n,r){let i=`\n${r} * @typeParam ${n} - ${x.typeParamDescription}`,a=t.value,o=a.match(/@typeParam[^@]*$/);if(o&&o.index!==void 0){let n=t.range[0]+2+o.index+o[0].length;return e.insertTextAfterRange([t.range[0],n],i)}let s=a.match(/@param[^@]*$/);if(s&&s.index!==void 0)return e.insertTextBeforeRange([t.range[0]+2+s.index,t.range[1]],i+`
7
+ `+r+` *`);let c=t.range[1]-2;return e.insertTextBeforeRange([c,t.range[1]],i+`\n${r} `)}function De(e,t,n){let r=t.value;return r.trim()===`*`?e.replaceTextRange(t.range,`/**\n${n} * ${x.description}\n${n} */`):e.replaceTextRange(t.range,`/*${r}*/`)}function C(e,t){return e.slice(t).includes(`
8
+ `)?``:`
9
+ `}function Oe(e,t,n){let r=t.value,i=RegExp(`@param\\s+${w(n)}\\s*$`,`m`),a=i.exec(r);if(a){let o=C(r,a.index+a[0].length),s=r.replace(i,`@param ${n} - ${x.paramDescription}${o}`);return e.replaceTextRange(t.range,`/*${s}*/`)}return e.replaceTextRange(t.range,`/*${r}*/`)}function ke(e,t){let n=t.value,r=/@returns\s*$/m,i=r.exec(n);if(i){let a=C(n,i.index+i[0].length),o=n.replace(r,`@returns ${x.returnsDescription}${a}`);return e.replaceTextRange(t.range,`/*${o}*/`)}return e.replaceTextRange(t.range,`/*${n}*/`)}function w(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}const T=n.object({name:n.optional(n.string())}),Ae=n.object({type:n.string(),name:n.optional(n.string()),left:n.optional(T),argument:n.optional(T),parameter:n.optional(T)});function je(e){return _e.safeParse(e).success}function E(e){return je(e)?e.params.map(e=>{let t=Ae.safeParse(e);if(!t.success)return``;let n=t.data;switch(n.type){case`Identifier`:return n.name??``;case`AssignmentPattern`:return n.left?.name??``;case`RestElement`:return n.argument?.name??``;case`ObjectPattern`:return Me(n)??`options`;case`ArrayPattern`:return`args`;case`TSParameterProperty`:return n.parameter?.name??``;default:return``}}).filter(e=>!!e):[]}function Me(e){return null}const Ne=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`禁止多余的 @param 标签`}},createOnce(e){let t=[];function n(n){let r=b(t,n,e.sourceCode.text);if(!r)return;let i=d(r.value);if(!i)return;let a=E(n),o=g(i).filter(e=>!a.includes(e));for(let t of o)e.report({node:n,message:`@param "${t}" 在函数签名中不存在`,fix:e=>{let n=r.value,i=RegExp(`\\n\\s*\\*\\s*@param\\s+${w(t)}\\b[^\\n]*`),a=n.replace(i,``);return e.replaceTextRange(r.range,`/*${a}*/`)}})}return{Program(){t=e.sourceCode.getAllComments()},FunctionDeclaration(e){n(e)},FunctionExpression(e){n(e)},ArrowFunctionExpression(e){if(`parent`in e&&e.parent){let t=e.parent.type;if(t===`CallExpression`||t===`Property`)return}n(e)}}}}),Pe=[`file`,`module`,`author`,`since`];function Fe(e){return Pe.includes(e)}function Ie(e,t,n,r){let i=e.split(`
10
+ `),a=-1;for(let e=0;e<i.length;e++){let n=i[e];if(n&&n.trim().startsWith(`* @${t}`)){a=e;break}}if(a===-1)return n.replaceTextRange(r,`/*${e}*/`);let o=i.filter((e,t)=>t!==a).join(`
11
+ `);return n.replaceTextRange(r,`/*${o}*/`)}const Le=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`禁止使用旧版 JSDoc 标签`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);if(i)for(let a of i.tags)Fe(a.tag)&&e.report({node:t,message:`禁止使用旧版 JSDoc 标签 @${a.tag},该标签在 TSDoc 中已不推荐使用`,fix:e=>Ie(n,a.tag,e,r.range)})}}}}}),Re=[...Object.values(x),`参数描述`,`返回值描述`,`函数描述`,`方法描述`,`类描述`,`变量描述`,`类型参数描述`,`描述`];function D(e){let t=e.trim();t.startsWith(`-`)&&(t=t.slice(1).trim());for(let e of Re)if(t===e)return e;return null}function ze(e){let t=D(e);return t?`禁止使用占位描述 "${t}",请提供有意义的描述`:null}function Be(e){let t=[],n=f(e);for(let e of n){let n=D(e.description);n&&t.push(`@param 标签禁止使用占位描述 "${n}",请提供有意义的描述`)}let r=p(e);if(r){let e=D(r.description);e&&t.push(`@returns 标签禁止使用占位描述 "${e}",请提供有意义的描述`)}let i=e.tags.filter(e=>e.tag===`typeParam`||e.tag===`template`);for(let e of i){let n=D(e.description);n&&t.push(`@typeParam 标签禁止使用占位描述 "${n}",请提供有意义的描述`)}return t}const Ve=e({meta:{type:`suggestion`,docs:{description:`禁止使用占位描述文本`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);if(!i)continue;let a=ze(i.description);a&&e.report({node:t,message:a});let o=Be(i);for(let n of o)e.report({node:t,message:n})}}}}});function He(e){return e?e.trim().startsWith(`-`):!1}const Ue=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:"要求参数描述使用 `-` 分隔"}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);if(!i)continue;let a=f(i);for(let i of a)He(i.description)||e.report({node:t,message:`参数 "${i.name}" 的描述应该使用 \`-\` 分隔,格式:@param ${i.name} - 描述`,fix:e=>{let t=RegExp(`(@param\\s+${w(i.name)}\\s+)([^\\-\\s].*?)(\\n|$)`,`g`),a=n.replace(t,`$1- $2$3`);return e.replaceTextRange(r.range,`/*${a}*/`)}})}}}}});function We(e){let t=e.split(`
12
+ `),n=-1;for(let e=0;e<t.length;e++){let r=t[e];if(!r)continue;let i=r.trim();if(i.startsWith(`*`)&&i.slice(1).trim().startsWith(`@`)){n=e;break}}if(n===-1||n===0)return!0;let r=t[n-1];if(!r)return!0;let i=r.trim();return i===`*`||i===``}const Ge=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求标签前有空行`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);i&&i.tags.length>0&&m(i)&&(We(n)||e.report({node:t,message:`标签前应有一个空行`,fix:e=>{let t=n.replace(/(\* [^\n@]+)\n(\s*\* @)/g,`$1
13
+ *
14
+ $2`);return e.replaceTextRange(r.range,`/*${t}*/`)}}))}}}}}),Ke=[/使用\s+\S+\s+代替/,/请使用\s+\S+/,/use\s+\S+\s+instead/i,/replaced\s+by\s+\S+/i];function qe(e){if(!e)return!1;let t=e.trim();if(t.length===0)return!1;for(let e of Ke)if(e.test(t))return!0;return t.length>10}const Je=e({meta:{type:`suggestion`,docs:{description:`要求 @deprecated 标签有替代方案说明`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);if(!i)continue;let a=i.tags.find(e=>e.tag===`deprecated`);a&&(qe(a.description)||e.report({node:t,message:`@deprecated 标签应该包含替代方案说明,例如:@deprecated 使用 newMethod 代替`}))}}}}}),Ye=[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`,`ClassDeclaration`,`VariableDeclaration`,`MethodDefinition`,`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSEnumDeclaration`,`PropertyDefinition`,`TSPropertySignature`,`TSEnumMember`],Xe=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求 TSDoc 注释有描述文本`}},createOnce(e){let t=[];function n(n){let r=b(t,n,e.sourceCode.text);if(!r)return;let i=d(r.value);i&&(m(i)||e.report({node:n,message:`TSDoc 注释缺少描述`,fix:t=>{let n=S(e.sourceCode.text,r.range);return De(t,r,n)}}))}let r={Program(){t=e.sourceCode.getAllComments()}};for(let e of Ye)r[e]=e=>n(e);return r}});function O(e){return pe.safeParse(e).success}function k(e){return me.safeParse(e).success}function A(e){return he.safeParse(e).success}function Ze(e){if(!Array.isArray(e)||e.length===0)return null;let t=e[0];if(!t||typeof t!=`object`)return null;let n=t;if(`type`in n&&n.type===`Property`&&`key`in n){let e=n.key;if(e&&typeof e==`object`&&`name`in e&&typeof e.name==`string`)return e.name}if(`type`in n&&n.type===`RestElement`&&`argument`in n){let e=n.argument;if(e&&typeof e==`object`&&`name`in e&&typeof e.name==`string`)return e.name}return null}function Qe(e){if(!Array.isArray(e)||e.length===0)return null;let t=e[0];return t&&typeof t==`object`&&`name`in t&&typeof t.name==`string`?t.name:null}function $e(e){if(!e||typeof e!=`object`)return null;let t=e;return`type`in t&&t.type===`ObjectPattern`&&`properties`in t?Ze(t.properties):`type`in t&&t.type===`ArrayPattern`&&`elements`in t?Qe(t.elements):null}function et(e){if(!A(e)||e.declarations.length===0)return null;let t=e.declarations[0];if(!t||typeof t!=`object`||!t||!(`id`in t)||!t.id)return null;let n=t.id;if(typeof n==`object`&&`type`in n){let e=n;if(e.type===`Identifier`&&`name`in e)return typeof e.name==`string`?e.name:null;if(e.type===`ObjectPattern`||e.type===`ArrayPattern`)return $e(n)}return O(t)?t.id?.name??null:null}function j(e){if(!e||typeof e!=`object`||!(`type`in e))return null;let t=e.type;if(typeof t!=`string`)return null;switch(t){case`FunctionDeclaration`:case`ClassDeclaration`:case`TSInterfaceDeclaration`:case`TSTypeAliasDeclaration`:case`TSEnumDeclaration`:case`TSEnumMember`:return O(e)?e.id?.name??null:null;case`MethodDefinition`:case`PropertyDefinition`:case`TSPropertySignature`:return k(e)?e.key?.name??null:null;case`VariableDeclaration`:return et(e);default:return null}}const tt={FunctionDeclaration:`函数`,MethodDefinition:`方法`,ClassDeclaration:`类`,VariableDeclaration:`变量`,TSInterfaceDeclaration:`接口`,TSTypeAliasDeclaration:`类型别名`,TSEnumDeclaration:`枚举`,PropertyDefinition:`类属性`,TSPropertySignature:`接口属性`,TSEnumMember:`枚举成员`};function M(e){return tt[e]||e}function N(e){return ge.safeParse(e).success}function nt(e){return N(e)?e.typeParameters.params.map(e=>e.name&&typeof e.name==`object`&&`name`in e.name?String(e.name.name):``).filter(Boolean):[]}function rt(e){return!e.trim().includes(`
15
+ `)}function it(e){return k(e)?e.key.name:O(e)?e.id.name:`(匿名)`}const at=e({meta:{type:`suggestion`,docs:{description:`要求接口属性、类属性、枚举成员使用多行 TSDoc 注释格式`}},createOnce(e){return{TSPropertySignature(t){P(t,e)},PropertyDefinition(t){P(t,e)},TSEnumMember(t){P(t,e)}}}});function P(e,t){let n=b(t.sourceCode.getAllComments(),e,t.sourceCode.text);if(!n)return;let r=n.value;if(rt(r)){let n=it(e);t.report({node:e,message:`"${n}"的注释应使用多行 TSDoc 格式,当前为单行格式`})}}const ot=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求 @param 标签有描述`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);if(!i)continue;let a=f(i);for(let n of a)h(n)||e.report({node:t,message:`参数 "${n.name}" 缺少描述`,fix:e=>Oe(e,r,n.name)})}}}}}),st=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求函数参数有 @param 标签`}},createOnce(e){let t=[];function n(n){let r=b(t,n,e.sourceCode.text);if(!r)return;let i=E(n);if(i.length===0)return;let a=d(r.value);if(!a)return;let o=g(a),s=i.filter(e=>!o.includes(e));for(let t of s)e.report({node:n,message:`参数 "${t}" 缺少 @param 标签`,fix:n=>{let i=S(e.sourceCode.text,r.range);return we(n,r,t,i)}})}return{Program(){t=e.sourceCode.getAllComments()},FunctionDeclaration(e){n(e)},FunctionExpression(e){n(e)},ArrowFunctionExpression(e){if(`parent`in e&&e.parent){let t=e.parent.type;if(t===`CallExpression`||t===`Property`)return}n(e)}}}}),ct=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求 @returns 标签有描述`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Block`)continue;let n=r.value;if(!n.startsWith(`*`))continue;let i=d(n);if(!i)continue;let a=p(i);a&&!h(a)&&e.report({node:t,message:`@returns 标签缺少描述`,fix:e=>ke(e,r)})}}}}});function F(e){return typeof e!=`object`||!e||!(`type`in e)?!1:typeof e.type==`string`}function lt(e){return F(e)&&e.type===`ReturnStatement`&&`argument`in e}function ut(e){return typeof e==`object`&&!!e&&`body`in e}function dt(e){return typeof e==`object`&&!!e&&`returnType`in e}function ft(e){return F(e)?[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`].includes(e.type):!1}function I(e,t){if(!e||typeof e!=`object`)return!1;let n=e;if(t.has(n))return!1;if(t.add(n),lt(n))return!0;if(ft(n))return!1;for(let e of Object.keys(n)){let r=Object.getOwnPropertyDescriptor(n,e);if(!r||!(`value`in r))continue;let i=r.value;if(i&&typeof i==`object`){if(Array.isArray(i)){for(let e of i)if(I(e,t))return!0}else if(`type`in i&&I(i,t))return!0}}return!1}function L(e){if(typeof e!=`object`||!e||!ut(e)||!e.body)return!1;let t=e.body;return typeof t!=`object`||!t?!1:!(`type`in t)||t.type!==`BlockStatement`||I(t,new WeakSet)}function R(e){if(typeof e!=`object`||!e||!dt(e)||!e.returnType)return!1;let t=e.returnType;if(!t||typeof t!=`object`||!(`typeAnnotation`in t)||!t.typeAnnotation)return!1;let n=t.typeAnnotation;if(typeof n!=`object`||!(`type`in n))return!1;if(n.type===`TSVoidKeyword`)return!0;if(n.type===`TSTypeReference`){let e=`typeName`in n?n.typeName:null;if(e&&typeof e==`object`&&`name`in e&&e.name===`Promise`){let e=`typeArguments`in n?n.typeArguments:null;if(e&&typeof e==`object`&&`params`in e&&Array.isArray(e.params)&&e.params.length>0){let t=e.params[0];if(F(t))return t.type===`TSVoidKeyword`}}}return!1}function pt(e){return L(e)?!R(e):!1}const mt=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求有返回值的函数有 @returns 标签`}},createOnce(e){return{FunctionDeclaration(t){z(t,e)},FunctionExpression(t){z(t,e)},ArrowFunctionExpression(t){if(`parent`in t&&t.parent){let e=t.parent.type;if(e===`CallExpression`||e===`Property`)return}z(t,e)}}}});function z(e,t){if(!pt(e))return;let n=b(t.sourceCode.getAllComments(),e,t.sourceCode.text);if(!n)return;let r=d(n.value);r&&(p(r)||t.report({node:e,message:`缺少 @returns 文档`,fix:e=>{let r=S(t.sourceCode.text,n.range);return Te(e,n,r)}}))}function ht(e){if(typeof e!=`object`||!e)return!1;let t=e;return`type`in t&&`range`in t&&Array.isArray(t.range)}const gt=e({meta:{type:`suggestion`,docs:{description:`要求 @throws 标签有异常类型`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let t of n){if(t.type!==`Block`)continue;let n=t.value;if(!n.startsWith(`*`))continue;let r=d(n);if(!r)continue;let i=r.tags.filter(e=>e.tag===`throws`||e.tag===`throw`);for(let n of i)!n.type&&ht(t)&&e.report({node:t,message:`@throws 标签应使用 {@link Type} 格式声明异常类型,例如:@throws {@link Error} 描述`})}}}}});function B(e){if(typeof e!=`object`||!e)return!1;let t=e;return`body`in t&&Array.isArray(t.body)}function _t(e){if(typeof e!=`object`||!e)return!1;let t=e;return`type`in t&&t.type===`ThrowStatement`&&`argument`in t}function vt(e){if(typeof e!=`object`||!e)return!1;let t=e;return`type`in t&&t.type===`NewExpression`&&`callee`in t}function V(e){if(typeof e!=`object`||!e)return!1;let t=e;return`type`in t&&t.type===`Identifier`&&`name`in t}function yt(e){if(typeof e!=`object`||!e)return!1;let t=e;return`consequent`in t&&`alternate`in t}function bt(e){if(typeof e!=`object`||!e)return!1;let t=e;return`block`in t&&`handler`in t}function xt(e){return typeof e!=`object`||!e?!1:`cases`in e}function St(e){if(typeof e!=`object`||!e||!(`consequent`in e))return!1;let t=e;return Array.isArray(t.consequent)}function Ct(e){let t=e.argument;if(vt(t)){let e=t.callee;if(V(e))return e.name}return V(t)?t.name:`Error`}function H(e,t){if(!B(e))return;let n=e;if(Array.isArray(n.body))for(let e of n.body)U(e,t)}function U(e,t){if(!e||typeof e!=`object`)return;let n=e;if(`type`in n&&n.type===`ThrowStatement`&&_t(n)){let e=Ct(n);t.push({typeName:e});return}if(`type`in n&&n.type===`IfStatement`&&yt(n)){U(n.consequent,t),n.alternate&&U(n.alternate,t);return}if(`type`in n&&n.type===`TryStatement`&&bt(n)){wt(n,t);return}if(`type`in n&&n.type===`SwitchStatement`&&xt(n)){Tt(n,t);return}`type`in n&&n.type===`BlockStatement`&&B(n)&&H(n,t)}function wt(e,t){B(e.block)&&H(e.block,t),e.handler&&typeof e.handler==`object`&&`body`in e.handler&&e.handler.body&&B(e.handler.body)&&H(e.handler.body,t)}function Tt(e,t){for(let n of e.cases)if(St(n))for(let e of n.consequent)U(e,t)}function Et(e){if(!e||typeof e!=`object`||!(`body`in e)||!e.body)return[];let t=e.body;if(typeof t!=`object`||!t||!(`type`in t)||t.type!==`BlockStatement`)return[];let n=[];return H(t,n),n}const Dt=e({meta:{type:`suggestion`,docs:{description:`要求抛出异常的函数有 @throws 标签`}},createOnce(e){return{FunctionDeclaration(t){W(t,e)},FunctionExpression(t){W(t,e)},ArrowFunctionExpression(t){if(`parent`in t&&t.parent){let e=t.parent.type;if(e===`CallExpression`||e===`Property`)return}W(t,e)}}}});function W(e,t){let n=Et(e);if(n.length===0)return;let r=b(t.sourceCode.getAllComments(),e,t.sourceCode.text);if(!r){G(e,t,n);return}let i=d(r.value);if(!i){G(e,t,n);return}i.tags.filter(e=>e.tag===`throws`||e.tag===`throw`).length===0&&G(e,t,n)}function G(e,t,n){let r=[...new Set(n.map(e=>e.typeName))],i=r[0]||`Error`;t.report({node:e,message:`函数抛出异常但没有 @throws 标签。抛出的异常类型: ${r.join(`, `)}。正确用法: @throws {@link ${i}} 异常描述`})}function Ot(e){return le.safeParse(e).success}function kt(e){return ce.safeParse(e).success}function At(e){return oe.safeParse(e).success}function jt(e){return se.safeParse(e).success}function Mt(e){return ve.safeParse(e).success}function Nt(e){return ye.safeParse(e).success}function Pt(e,t){return e.type===`FunctionDeclaration`||e.type===`FunctionExpression`||e.type===`ArrowFunctionExpression`?Ft(e,t):e.type===`MethodDefinition`&&Mt(e)&&e.value?It(e.value,t):e.type===`ClassDeclaration`?Lt(t):e.type===`VariableDeclaration`&&Nt(e)?Rt(e.declarations,t):K(t)}function Ft(e,t){let n=E(e),r=L(e),i=R(e),a=[];a.push(`/**`),a.push(` * ${x.functionDescription}`);for(let e of n)a.push(` * @param ${e} - ${x.paramDescription}`);return r&&!i&&a.push(` * @returns ${x.returnsDescription}`),a.push(` */`),`\n${t}`+a.join(`\n${t}`)+`\n${t}`}function It(e,t){if(!e||typeof e!=`object`||!Ot(e))return K(t);let n=e,r=E(n),i=L(n),a=R(n),o=[];o.push(`/**`),o.push(` * ${x.methodDescription}`);for(let e of r)o.push(` * @param ${e} - ${x.paramDescription}`);return i&&!a&&o.push(` * @returns ${x.returnsDescription}`),o.push(` */`),`\n${t}`+o.join(`\n${t}`)+`\n${t}`}function Lt(e){let t=[];return t.push(`/**`),t.push(` * ${x.classDescription}`),t.push(` */`),`\n${e}`+t.join(`\n${e}`)+`\n${e}`}function Rt(e,t){let n=e[0];if(!n||!kt(n))return K(t);let r=At(n)?n.init:void 0;if(r&&typeof r==`object`&&jt(r)){let e=r.type;if(e===`ArrowFunctionExpression`||e===`FunctionExpression`)return K(t)}let i=[];return i.push(`/**`),i.push(` * ${x.variableDescription}`),i.push(` */`),`\n${t}`+i.join(`\n${t}`)+`\n${t}`}function K(e){let t=[];return t.push(`/**`),t.push(` * ${x.description}`),t.push(` */`),`\n${e}`+t.join(`\n${e}`)+`\n${e}`}function q(e){return typeof e==`object`&&!!e&&`type`in e&&typeof e.type==`string`}function J(e){return typeof e==`object`&&!!e&&`parent`in e}function zt(e){return typeof e==`object`&&!!e&&`init`in e}function Y(e){return typeof e==`object`&&!!e}const Bt=[`FunctionDeclaration`,`MethodDefinition`,`ClassDeclaration`,`VariableDeclaration`,`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSEnumDeclaration`,`PropertyDefinition`,`TSEnumMember`];function Vt(e){if(!Y(e)||e.type!==`TSPropertySignature`)return!1;let t=e.key;return Y(t)&&t.type===`Identifier`&&`name`in t&&typeof t.name==`string`?t.name===`null`||t.name===`undefined`:!1}function Ht(e,t){if(J(e)&&e.parent&&typeof e.parent==`object`&&q(e.parent)&&[`ForStatement`,`ForInStatement`,`ForOfStatement`].includes(e.parent.type))return!0;if(e.type===`VariableDeclaration`){if(t)return!1;if(A(e)&&e.declarations.length>0){let t=e.declarations[0];if(!t)return!0;if(zt(t)&&t.init&&typeof t.init==`object`&&q(t.init)&&(t.init.type===`ArrowFunctionExpression`||t.init.type===`FunctionExpression`))return!1}return!0}return!!Vt(e)}function Ut(e){return e.includes(`/**`)}function Wt(e){if(`decorators`in e&&Array.isArray(e.decorators)&&e.decorators.length>0){let t=e.decorators[0];if(Y(t)){let e=t.range;if(Array.isArray(e)&&e.length>=1&&typeof e[0]==`number`)return[e[0],e[0]]}}if(J(e)&&e.parent&&typeof e.parent==`object`){let t=e.parent;if(q(t)&&Y(t)&&[`ExportNamedDeclaration`,`ExportDefaultDeclaration`].includes(t.type)){let e=t.range;if(Array.isArray(e)&&e.length>=1&&typeof e[0]==`number`)return[e[0],e[0]]}}return[e.range[0],e.range[0]]}function Gt(e,t){if(!e)return``;let n=t;for(;n>0&&e[n-1]!==`
16
+ `;)n--;return e.slice(n,t)}function Kt(e,t,n){if(t.skipFile||Ht(e,t.checkAllVariables))return;let r=b(t.commentsCache,e,n.sourceCode.text);if(!r){let t=M(e.type),r=j(e);n.report({node:e,message:`${t}"${r}"缺少 TSDoc 注释`,fix:t=>{let r=Wt(e),i=Pt(e,Gt(n.sourceCode.text,r[0]));return t.insertTextBeforeRange(r,i)}});return}let i=r.value;if(Ce(i)){let t=M(e.type),r=j(e);n.report({node:e,message:`${t}"${r}"的 TSDoc 注释包含占位描述,请填写实际描述`})}}function qt(e,t){let n=e.getAllComments(),r=e.text;if(r!=null){if(!Ut(r))return t.skipFile=!0,n}else if(!n.some(e=>e.type===`Block`&&e.value.startsWith(`*`)))return t.skipFile=!0,n;return n}function Jt(e){if(typeof e==`object`&&e&&!Array.isArray(e)){let t=e;if(`checkAllVariables`in t&&typeof t.checkAllVariables==`boolean`)return t.checkAllVariables}return!1}const Yt=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求 TSDoc 注释存在`},schema:[{type:`object`,properties:{contexts:{type:`array`,items:{type:`string`}},checkAllVariables:{type:`boolean`}},additionalProperties:!1}]},createOnce(e){let t={commentsCache:[],checkAllVariables:!1,skipFile:!1},n={Program(n){t.skipFile=!1,t.checkAllVariables=!1,t.commentsCache=qt(e.sourceCode,t);let r=e.options?.[0];t.checkAllVariables=Jt(r)}};for(let r of Bt)n[r]=n=>Kt(n,t,e);return n}});function Xt(e){return e.tags.filter(e=>e.tag===`typeParam`||e.tag===`template`).map(e=>e.name)}const X=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求泛型有 @typeParam 标签`}},createOnce(e){return{FunctionDeclaration(t){Z(t,e)},FunctionExpression(t){Z(t,e)},ArrowFunctionExpression(t){if(`parent`in t&&t.parent){let e=t.parent.type;if(e===`CallExpression`||e===`Property`)return}Z(t,e)},ClassDeclaration(t){Z(t,e)},TSInterfaceDeclaration(t){Z(t,e)},TSTypeAliasDeclaration(t){Z(t,e)}}}});function Z(e,t){if(!N(e))return;let n=nt(e);if(n.length===0)return;let r=b(t.sourceCode.getAllComments(),e,t.sourceCode.text);if(!r)return;let i=d(r.value);if(!i)return;let a=Xt(i),o=n.filter(e=>!a.includes(e));for(let n of o)t.report({node:e,message:`缺少类型参数 "${n}" 的 @typeParam 文档`,fix:e=>{let i=S(t.sourceCode.text,r.range);return Ee(e,r,n,i)}})}const Zt=/^(=+)\s+(.+?)\s+(=+)$/;function Qt(e){let t=e.trim().match(Zt);return t?{leadingEquals:t[1]??``,text:t[2]??``,trailingEquals:t[3]??``}:null}const $t=e({meta:{type:`suggestion`,fixable:`code`,docs:{description:`要求代码块分隔符使用规范格式`}},createOnce(e){return{Program(t){let n=e.sourceCode.getAllComments();for(let r of n){if(r.type!==`Line`)continue;let n=Qt(r.value.trim());if(n&&(n.leadingEquals.length!==20||n.trailingEquals.length!==20)){let i=`=`.repeat(20);e.report({node:t,message:`代码块分隔符格式不正确,应为:// ${i} ${n.text} ${i}`,fix:e=>{let t=`// ${i} ${n.text} ${i}`;return e.replaceTextRange(r.range,t)}})}}}}}}),en=/\bTODO(?:\(([^)]+)\))?:?\s*(.*)/i;function tn(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}const nn={requireAuthor:!1,requireDescription:!0};function rn(e){return/\bTODO\b/i.test(e)}function an(e){let t={...nn};if(tn(e)){let n=e;typeof n.requireAuthor==`boolean`&&(t.requireAuthor=n.requireAuthor),typeof n.requireDescription==`boolean`&&(t.requireDescription=n.requireDescription)}return t}function on(e){let t=e.match(en);return t?{author:t[1]??``,description:(t[2]??``).trim()}:null}const Q={"no-commented-code":s,"no-description-tag":ae,"no-extra-param":Ne,"no-legacy-jsdoc-tags":Le,"no-placeholder-description":Ve,"param-hyphen-format":Ue,"require-blank-line-before-tags":Ge,"require-deprecated-alternative":Je,"require-description":Xe,"require-multiline-property-comment":at,"require-param":st,"require-param-description":ot,"require-returns":mt,"require-returns-description":ct,"require-throws":Dt,"require-throws-type":gt,"require-tsdoc":Yt,"require-type-param":X,"separator-format":$t,"todo-format":e({meta:{type:`suggestion`,docs:{description:`要求 TODO 注释有描述和作者`}},createOnce(e){let t=an(e.options?.[0]);return{Program(n){let r=e.sourceCode.getAllComments();for(let i of r){let r=i.value;if(!rn(r))continue;let a=on(r);a&&(t.requireAuthor&&!a.author&&e.report({node:n,message:`TODO 注释缺少作者信息,格式:TODO(author): 描述`}),t.requireDescription&&!a.description&&e.report({node:n,message:`TODO 注释缺少描述说明`}))}}}}})},$=t({meta:{name:`@longzai-intelligence-oxlint/tsdoc-plugin`},rules:Q});export{$ as default,$ as tsdocPlugin,Q as tsdocRules};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@longzai-intelligence-oxlint/tsdoc-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Oxlint TSDoc 插件,提供 TSDoc 注释规范检查规则",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./configs/recommended": {
18
+ "types": "./dist/configs/recommended.d.ts",
19
+ "import": "./dist/configs/recommended.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "lzi-builder",
24
+ "build:prod": "NODE_ENV=production lzi-builder",
25
+ "prepublishOnly": "bun run build:prod",
26
+ "dev": "lzi-builder --watch",
27
+ "clean": "rimraf dist",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "test:coverage": "vitest run --coverage",
31
+ "lint": "oxlint && oxfmt --check",
32
+ "lint:fix": "oxlint --fix && oxfmt",
33
+ "typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
34
+ "typecheck:app": "tsgo --noEmit -p tsconfig/app.json",
35
+ "typecheck:node": "tsgo --noEmit -p tsconfig/node.json",
36
+ "typecheck:test": "tsgo --noEmit -p tsconfig/test.json"
37
+ },
38
+ "dependencies": {
39
+ "@longzai-intelligence-oxlint/core": "0.1.0",
40
+ "@longzai-intelligence-logger/node": "^0.1.1",
41
+ "@oxlint/plugins": "^1.74.0",
42
+ "zod": "^4.4.3"
43
+ },
44
+ "devDependencies": {
45
+ "@vitest/coverage-v8": "^4.1.10",
46
+ "acorn": "^8.17.0",
47
+ "typescript": "^6.0.3"
48
+ },
49
+ "peerDependencies": {
50
+ "oxlint": ">=1.0.0"
51
+ }
52
+ }