jsdoczoom 0.1.0 → 0.3.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/dist/barrel.js +37 -36
- package/dist/cli.js +248 -163
- package/dist/drilldown.js +173 -163
- package/dist/errors.js +14 -14
- package/dist/eslint-engine.js +214 -96
- package/dist/eslint-plugin.js +312 -167
- package/dist/file-discovery.js +44 -42
- package/dist/index.js +1 -1
- package/dist/jsdoc-parser.js +151 -165
- package/dist/lint.js +44 -29
- package/dist/selector.js +24 -21
- package/dist/skill-text.js +488 -7
- package/dist/type-declarations.js +177 -69
- package/dist/types.js +21 -6
- package/dist/validate.js +84 -69
- package/package.json +7 -1
- package/types/barrel.d.ts +4 -1
- package/types/drilldown.d.ts +12 -2
- package/types/errors.d.ts +8 -8
- package/types/eslint-engine.d.ts +23 -11
- package/types/eslint-plugin.d.ts +6 -5
- package/types/file-discovery.d.ts +5 -1
- package/types/index.d.ts +18 -1
- package/types/jsdoc-parser.d.ts +1 -1
- package/types/lint.d.ts +11 -2
- package/types/skill-text.d.ts +4 -1
- package/types/type-declarations.d.ts +34 -5
- package/types/types.d.ts +65 -43
- package/types/validate.d.ts +11 -2
package/dist/eslint-engine.js
CHANGED
|
@@ -7,6 +7,31 @@ import tsParser from "@typescript-eslint/parser";
|
|
|
7
7
|
import { ESLint } from "eslint";
|
|
8
8
|
import jsdocPlugin from "eslint-plugin-jsdoc";
|
|
9
9
|
import plugin from "./eslint-plugin.js";
|
|
10
|
+
|
|
11
|
+
/** Common invalid JSDoc tags and their recommended replacements */
|
|
12
|
+
const TAG_MIGRATION_HINTS = {
|
|
13
|
+
"@remarks": "Move content to the description paragraph (prose before tags)",
|
|
14
|
+
"@packageDocumentation": "Use @module instead",
|
|
15
|
+
"@concept": "Move content to the description paragraph",
|
|
16
|
+
"@constraint": "Move content to the description paragraph",
|
|
17
|
+
"@vitest-environment":
|
|
18
|
+
"Use a plain comment instead: // @vitest-environment node",
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Enhance a check-tag-names diagnostic message with a migration hint
|
|
22
|
+
* if the invalid tag is a commonly encountered one.
|
|
23
|
+
*
|
|
24
|
+
* @param message - Original ESLint diagnostic message
|
|
25
|
+
* @returns Enhanced message with hint, or original message if no hint available
|
|
26
|
+
*/
|
|
27
|
+
function enhanceTagNameMessage(message) {
|
|
28
|
+
for (const [tag, hint] of Object.entries(TAG_MIGRATION_HINTS)) {
|
|
29
|
+
if (message.includes(tag)) {
|
|
30
|
+
return `${message} (Hint: ${hint})`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return message;
|
|
34
|
+
}
|
|
10
35
|
/**
|
|
11
36
|
* Creates an ESLint instance configured for validation mode.
|
|
12
37
|
*
|
|
@@ -15,26 +40,26 @@ import plugin from "./eslint-plugin.js";
|
|
|
15
40
|
* @returns Configured ESLint instance for validation
|
|
16
41
|
*/
|
|
17
42
|
export function createValidationLinter() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
const eslint = new ESLint({
|
|
44
|
+
overrideConfigFile: true,
|
|
45
|
+
overrideConfig: [
|
|
46
|
+
{
|
|
47
|
+
files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
|
|
48
|
+
plugins: { jsdoczoom: plugin },
|
|
49
|
+
rules: {
|
|
50
|
+
"jsdoczoom/require-file-jsdoc": "error",
|
|
51
|
+
"jsdoczoom/require-file-summary": "error",
|
|
52
|
+
"jsdoczoom/require-file-description": "error",
|
|
53
|
+
},
|
|
54
|
+
languageOptions: {
|
|
55
|
+
parser: tsParser,
|
|
56
|
+
ecmaVersion: "latest",
|
|
57
|
+
sourceType: "module",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
return eslint;
|
|
38
63
|
}
|
|
39
64
|
/**
|
|
40
65
|
* Creates an ESLint instance configured for lint mode.
|
|
@@ -45,40 +70,41 @@ export function createValidationLinter() {
|
|
|
45
70
|
* @returns Configured ESLint instance for lint mode
|
|
46
71
|
*/
|
|
47
72
|
export function createLintLinter(cwd) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
const eslint = new ESLint({
|
|
74
|
+
cwd,
|
|
75
|
+
overrideConfigFile: true,
|
|
76
|
+
overrideConfig: [
|
|
77
|
+
{
|
|
78
|
+
files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
|
|
79
|
+
plugins: { jsdoczoom: plugin, jsdoc: jsdocPlugin },
|
|
80
|
+
rules: {
|
|
81
|
+
"jsdoczoom/require-file-jsdoc": "error",
|
|
82
|
+
"jsdoczoom/require-file-summary": "error",
|
|
83
|
+
"jsdoczoom/require-file-description": "error",
|
|
84
|
+
"jsdoczoom/require-file-ordering": "warn",
|
|
85
|
+
"jsdoc/require-jsdoc": ["error", { publicOnly: true }],
|
|
86
|
+
"jsdoc/require-param": "warn",
|
|
87
|
+
"jsdoc/require-param-description": "warn",
|
|
88
|
+
"jsdoc/require-returns": "warn",
|
|
89
|
+
"jsdoc/require-returns-description": "warn",
|
|
90
|
+
"jsdoc/require-throws": "warn",
|
|
91
|
+
"jsdoc/check-param-names": "error",
|
|
92
|
+
"jsdoc/check-tag-names": "error",
|
|
93
|
+
"jsdoc/no-types": "error",
|
|
94
|
+
"jsdoc/informative-docs": "error",
|
|
95
|
+
"jsdoc/tag-lines": "off",
|
|
96
|
+
"jsdoc/no-blank-blocks": "error",
|
|
97
|
+
"jsdoc/require-description": "error",
|
|
98
|
+
},
|
|
99
|
+
languageOptions: {
|
|
100
|
+
parser: tsParser,
|
|
101
|
+
ecmaVersion: "latest",
|
|
102
|
+
sourceType: "module",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
return eslint;
|
|
82
108
|
}
|
|
83
109
|
/**
|
|
84
110
|
* Lints source text for validation mode and returns simplified messages.
|
|
@@ -89,14 +115,85 @@ export function createLintLinter(cwd) {
|
|
|
89
115
|
* @returns Simplified message list with ruleId, messageId, and fatal flag
|
|
90
116
|
*/
|
|
91
117
|
export async function lintFileForValidation(eslint, sourceText, filePath) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
const results = await eslint.lintText(sourceText, { filePath });
|
|
119
|
+
if (results.length === 0) return [];
|
|
120
|
+
return results[0].messages.map((msg) => ({
|
|
121
|
+
ruleId: msg.ruleId,
|
|
122
|
+
messageId: msg.messageId,
|
|
123
|
+
fatal: msg.fatal,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract the nearest symbol name from source text at a given line.
|
|
128
|
+
* Scans from the diagnostic line downward (up to 3 lines) for
|
|
129
|
+
* function, class, method, getter/setter, interface, type alias,
|
|
130
|
+
* or variable declarations. Skips lines that look like method calls
|
|
131
|
+
* (contain a dot before the identifier) to avoid false matches.
|
|
132
|
+
*
|
|
133
|
+
* @param sourceText - Full file source text
|
|
134
|
+
* @param line - 1-based line number from the diagnostic
|
|
135
|
+
* @returns Symbol name if found, undefined otherwise
|
|
136
|
+
*/
|
|
137
|
+
function extractSymbolName(sourceText, line) {
|
|
138
|
+
const lines = sourceText.split("\n");
|
|
139
|
+
const searchEnd = Math.min(line + 2, lines.length);
|
|
140
|
+
for (let i = line - 1; i <= searchEnd; i++) {
|
|
141
|
+
const text = lines[i];
|
|
142
|
+
if (text === undefined) continue;
|
|
143
|
+
// Skip lines that are clearly method calls (e.g., `obj.method(`)
|
|
144
|
+
if (/\.\w+\s*\(/.test(text)) continue;
|
|
145
|
+
// Function declarations: function foo(, async function foo(, export function foo(
|
|
146
|
+
const funcMatch = text.match(
|
|
147
|
+
/(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)/,
|
|
148
|
+
);
|
|
149
|
+
if (funcMatch?.[1]) return funcMatch[1];
|
|
150
|
+
// Class declarations: class Foo, export class Foo, abstract class Foo
|
|
151
|
+
const classMatch = text.match(
|
|
152
|
+
/(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/,
|
|
153
|
+
);
|
|
154
|
+
if (classMatch?.[1]) return classMatch[1];
|
|
155
|
+
// Interface declarations: interface Foo, export interface Foo
|
|
156
|
+
const ifaceMatch = text.match(/(?:export\s+)?interface\s+(\w+)/);
|
|
157
|
+
if (ifaceMatch?.[1]) return ifaceMatch[1];
|
|
158
|
+
// Type alias declarations: type Foo =, export type Foo =
|
|
159
|
+
const typeMatch = text.match(/(?:export\s+)?type\s+(\w+)\s*[=<]/);
|
|
160
|
+
if (typeMatch?.[1]) return typeMatch[1];
|
|
161
|
+
// Variable declarations: const foo =, let foo =, var foo =
|
|
162
|
+
const varMatch = text.match(
|
|
163
|
+
/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*[=:]/,
|
|
164
|
+
);
|
|
165
|
+
if (varMatch?.[1]) return varMatch[1];
|
|
166
|
+
// Getter/setter: get foo(), set foo()
|
|
167
|
+
const accessorMatch = text.match(/(?:get|set)\s+(\w+)\s*\(/);
|
|
168
|
+
if (accessorMatch?.[1]) return accessorMatch[1];
|
|
169
|
+
// Class method: identifier followed by ( but NOT preceded by a dot
|
|
170
|
+
// Only match if the line starts with optional whitespace + optional modifiers
|
|
171
|
+
const methodMatch = text.match(
|
|
172
|
+
/^\s*(?:(?:public|private|protected|static|readonly|async|override)\s+)*(\w+)\s*\(/,
|
|
173
|
+
);
|
|
174
|
+
if (methodMatch?.[1]) {
|
|
175
|
+
const name = methodMatch[1];
|
|
176
|
+
// Skip common control flow keywords
|
|
177
|
+
if (
|
|
178
|
+
![
|
|
179
|
+
"if",
|
|
180
|
+
"for",
|
|
181
|
+
"while",
|
|
182
|
+
"switch",
|
|
183
|
+
"catch",
|
|
184
|
+
"return",
|
|
185
|
+
"import",
|
|
186
|
+
"from",
|
|
187
|
+
"new",
|
|
188
|
+
"await",
|
|
189
|
+
"throw",
|
|
190
|
+
].includes(name)
|
|
191
|
+
) {
|
|
192
|
+
return name;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
100
197
|
}
|
|
101
198
|
/**
|
|
102
199
|
* Lints source text for lint mode and returns detailed diagnostics.
|
|
@@ -107,16 +204,25 @@ export async function lintFileForValidation(eslint, sourceText, filePath) {
|
|
|
107
204
|
* @returns Array of lint diagnostics with line, column, rule, message, and severity
|
|
108
205
|
*/
|
|
109
206
|
export async function lintFileForLint(eslint, sourceText, filePath) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
207
|
+
const results = await eslint.lintText(sourceText, { filePath });
|
|
208
|
+
if (results.length === 0) return [];
|
|
209
|
+
return results[0].messages.map((msg) => {
|
|
210
|
+
const diagnostic = {
|
|
211
|
+
line: msg.line,
|
|
212
|
+
column: msg.column,
|
|
213
|
+
rule: msg.ruleId ?? "unknown",
|
|
214
|
+
message:
|
|
215
|
+
msg.ruleId === "jsdoc/check-tag-names"
|
|
216
|
+
? enhanceTagNameMessage(msg.message)
|
|
217
|
+
: msg.message,
|
|
218
|
+
severity: msg.severity === 2 ? "error" : "warning",
|
|
219
|
+
};
|
|
220
|
+
const symbol = extractSymbolName(sourceText, msg.line);
|
|
221
|
+
if (symbol) {
|
|
222
|
+
diagnostic.symbol = symbol;
|
|
223
|
+
}
|
|
224
|
+
return diagnostic;
|
|
225
|
+
});
|
|
120
226
|
}
|
|
121
227
|
/**
|
|
122
228
|
* Maps ESLint messages to a single ValidationStatus using priority order.
|
|
@@ -133,28 +239,40 @@ export async function lintFileForLint(eslint, sourceText, filePath) {
|
|
|
133
239
|
* @returns ValidationStatus or "valid"
|
|
134
240
|
*/
|
|
135
241
|
export function mapToValidationStatus(messages) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
242
|
+
// Priority 1: Parse errors
|
|
243
|
+
if (messages.some((msg) => msg.ruleId === null && msg.fatal)) {
|
|
244
|
+
return "syntax_error";
|
|
245
|
+
}
|
|
246
|
+
// Priority 2: Missing JSDoc
|
|
247
|
+
if (messages.some((msg) => msg.ruleId === "jsdoczoom/require-file-jsdoc")) {
|
|
248
|
+
return "missing_jsdoc";
|
|
249
|
+
}
|
|
250
|
+
// Priority 3: Missing summary
|
|
251
|
+
if (
|
|
252
|
+
messages.some(
|
|
253
|
+
(msg) =>
|
|
254
|
+
msg.ruleId === "jsdoczoom/require-file-summary" &&
|
|
255
|
+
msg.messageId === "missingSummary",
|
|
256
|
+
)
|
|
257
|
+
) {
|
|
258
|
+
return "missing_summary";
|
|
259
|
+
}
|
|
260
|
+
// Priority 4: Multiple summary
|
|
261
|
+
if (
|
|
262
|
+
messages.some(
|
|
263
|
+
(msg) =>
|
|
264
|
+
msg.ruleId === "jsdoczoom/require-file-summary" &&
|
|
265
|
+
msg.messageId === "multipleSummary",
|
|
266
|
+
)
|
|
267
|
+
) {
|
|
268
|
+
return "multiple_summary";
|
|
269
|
+
}
|
|
270
|
+
// Priority 5: Missing description
|
|
271
|
+
if (
|
|
272
|
+
messages.some((msg) => msg.ruleId === "jsdoczoom/require-file-description")
|
|
273
|
+
) {
|
|
274
|
+
return "missing_description";
|
|
275
|
+
}
|
|
276
|
+
// No matches
|
|
277
|
+
return "valid";
|
|
160
278
|
}
|