project-graph-mcp 1.5.0 → 2.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 +171 -31
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/package.json +12 -8
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +7 -0
- package/src/analysis/complexity.js +14 -0
- package/src/analysis/custom-rules.js +36 -0
- package/src/analysis/db-analysis.js +9 -0
- package/src/analysis/dead-code.js +19 -0
- package/src/analysis/full-analysis.js +18 -0
- package/src/analysis/jsdoc-checker.js +24 -0
- package/src/analysis/jsdoc-generator.js +10 -0
- package/src/analysis/large-files.js +11 -0
- package/src/analysis/outdated-patterns.js +12 -0
- package/src/analysis/similar-functions.js +16 -0
- package/src/analysis/test-annotations.js +21 -0
- package/src/analysis/type-checker.js +8 -0
- package/src/analysis/undocumented.js +14 -0
- package/src/cli/cli-handlers.js +4 -0
- package/src/cli/cli.js +5 -0
- package/src/compact/.project-graph-cache.json +1 -0
- package/src/compact/ai-context.js +7 -0
- package/src/compact/compact-migrate.js +17 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +14 -0
- package/src/compact/ctx-to-jsdoc.js +29 -0
- package/src/compact/doc-dialect.js +30 -0
- package/src/compact/expand.js +37 -0
- package/src/compact/framework-references.js +5 -0
- package/src/compact/instructions.js +3 -0
- package/src/compact/mode-config.js +8 -0
- package/src/compact/validate-pipeline.js +9 -0
- package/src/core/event-bus.js +9 -0
- package/src/core/filters.js +14 -0
- package/src/core/graph-builder.js +12 -0
- package/src/core/parser.js +31 -0
- package/src/core/workspace.js +8 -0
- package/src/lang/lang-go.js +17 -0
- package/src/lang/lang-python.js +12 -0
- package/src/lang/lang-sql.js +23 -0
- package/src/lang/lang-typescript.js +9 -0
- package/src/lang/lang-utils.js +4 -0
- package/src/mcp/mcp-server.js +17 -0
- package/src/mcp/tool-defs.js +3 -0
- package/src/mcp/tools.js +25 -0
- package/src/network/backend-lifecycle.js +19 -0
- package/src/network/backend.js +5 -0
- package/src/network/local-gateway.js +23 -0
- package/src/network/mdns.js +13 -0
- package/src/network/server.js +10 -0
- package/src/network/web-server.js +34 -0
- package/web/.project-graph-cache.json +1 -0
- package/web/app.js +17 -0
- package/web/components/code-block.js +3 -0
- package/web/components/quick-open.js +5 -0
- package/web/dashboard-state.js +3 -0
- package/web/dashboard.html +27 -0
- package/web/dashboard.js +8 -0
- package/web/highlight.js +13 -0
- package/web/index.html +35 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +4 -0
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -0
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.js +5 -0
- package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +4 -0
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +5 -0
- package/web/panels/ctx-panel.js +4 -0
- package/web/panels/dep-graph.js +6 -0
- package/web/panels/file-tree.js +188 -0
- package/web/panels/health-panel.js +3 -0
- package/web/panels/live-monitor.js +3 -0
- package/web/state.js +17 -0
- package/web/style.css +157 -0
- package/references/symbiote-3x.md +0 -834
- package/src/ai-context.js +0 -113
- package/src/analysis-cache.js +0 -155
- package/src/cli-handlers.js +0 -271
- package/src/cli.js +0 -95
- package/src/compact.js +0 -207
- package/src/complexity.js +0 -237
- package/src/compress.js +0 -319
- package/src/ctx-to-jsdoc.js +0 -514
- package/src/custom-rules.js +0 -584
- package/src/db-analysis.js +0 -194
- package/src/dead-code.js +0 -468
- package/src/doc-dialect.js +0 -716
- package/src/filters.js +0 -227
- package/src/framework-references.js +0 -177
- package/src/full-analysis.js +0 -470
- package/src/graph-builder.js +0 -299
- package/src/instructions.js +0 -73
- package/src/jsdoc-checker.js +0 -351
- package/src/jsdoc-generator.js +0 -203
- package/src/lang-go.js +0 -285
- package/src/lang-python.js +0 -197
- package/src/lang-sql.js +0 -309
- package/src/lang-typescript.js +0 -190
- package/src/lang-utils.js +0 -124
- package/src/large-files.js +0 -163
- package/src/mcp-server.js +0 -675
- package/src/mode-config.js +0 -127
- package/src/outdated-patterns.js +0 -296
- package/src/parser.js +0 -662
- package/src/server.js +0 -28
- package/src/similar-functions.js +0 -279
- package/src/test-annotations.js +0 -323
- package/src/tool-defs.js +0 -793
- package/src/tools.js +0 -470
- package/src/type-checker.js +0 -188
- package/src/undocumented.js +0 -259
- package/src/workspace.js +0 -70
- /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
- /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
package/src/jsdoc-checker.js
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSDoc Consistency Checker (AST-based)
|
|
3
|
-
* Validates JSDoc annotations against actual function signatures
|
|
4
|
-
*
|
|
5
|
-
* Checks:
|
|
6
|
-
* - Param count mismatch (JSDoc vs AST)
|
|
7
|
-
* - Param name mismatch
|
|
8
|
-
* - Missing @returns on functions with return statements
|
|
9
|
-
* - Type hint inconsistency (default value vs JSDoc type)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
13
|
-
import { join, relative, resolve } from 'path';
|
|
14
|
-
import { parse } from '../vendor/acorn.mjs';
|
|
15
|
-
import * as walk from '../vendor/walk.mjs';
|
|
16
|
-
import { shouldExcludeDir, shouldExcludeFile, parseGitignore } from './filters.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {Object} JSDocIssue
|
|
20
|
-
* @property {string} file
|
|
21
|
-
* @property {number} line
|
|
22
|
-
* @property {string} name - Function or method name
|
|
23
|
-
* @property {'error'|'warning'} severity
|
|
24
|
-
* @property {string} message
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Find all JS files in directory
|
|
29
|
-
* @param {string} dir
|
|
30
|
-
* @param {string} rootDir
|
|
31
|
-
* @returns {string[]}
|
|
32
|
-
*/
|
|
33
|
-
function findJSFiles(dir, rootDir = dir) {
|
|
34
|
-
if (dir === rootDir) parseGitignore(rootDir);
|
|
35
|
-
const files = [];
|
|
36
|
-
try {
|
|
37
|
-
for (const entry of readdirSync(dir)) {
|
|
38
|
-
const fullPath = join(dir, entry);
|
|
39
|
-
const relativePath = relative(rootDir, fullPath);
|
|
40
|
-
const stat = statSync(fullPath);
|
|
41
|
-
if (stat.isDirectory()) {
|
|
42
|
-
if (!shouldExcludeDir(entry, relativePath)) {
|
|
43
|
-
files.push(...findJSFiles(fullPath, rootDir));
|
|
44
|
-
}
|
|
45
|
-
} else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
|
|
46
|
-
if (!shouldExcludeFile(entry, relativePath)) {
|
|
47
|
-
files.push(fullPath);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
} catch (e) { /* dir not found */ }
|
|
52
|
-
return files;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Extract JSDoc comments with positions
|
|
57
|
-
* @param {string} code
|
|
58
|
-
* @returns {Array<{text: string, endLine: number, params: Array<{name: string, type: string}>, hasReturns: boolean}>}
|
|
59
|
-
*/
|
|
60
|
-
function extractJSDocComments(code) {
|
|
61
|
-
const comments = [];
|
|
62
|
-
const regex = /\/\*\*[\s\S]*?\*\//g;
|
|
63
|
-
let match;
|
|
64
|
-
|
|
65
|
-
while ((match = regex.exec(code)) !== null) {
|
|
66
|
-
const text = match[0];
|
|
67
|
-
const endLine = code.slice(0, match.index + text.length).split('\n').length;
|
|
68
|
-
|
|
69
|
-
// Parse @param tags — handle nested braces in types like {Array<{text: string}>}
|
|
70
|
-
const params = [];
|
|
71
|
-
const paramStartRegex = /@param\s+\{/g;
|
|
72
|
-
let paramStart;
|
|
73
|
-
while ((paramStart = paramStartRegex.exec(text)) !== null) {
|
|
74
|
-
// Find matching closing brace (balanced)
|
|
75
|
-
let depth = 1;
|
|
76
|
-
let i = paramStart.index + paramStart[0].length;
|
|
77
|
-
while (i < text.length && depth > 0) {
|
|
78
|
-
if (text[i] === '{') depth++;
|
|
79
|
-
else if (text[i] === '}') depth--;
|
|
80
|
-
i++;
|
|
81
|
-
}
|
|
82
|
-
if (depth !== 0) continue;
|
|
83
|
-
const type = text.slice(paramStart.index + paramStart[0].length, i - 1);
|
|
84
|
-
// Extract param name after the closing brace
|
|
85
|
-
const afterType = text.slice(i);
|
|
86
|
-
const nameMatch = afterType.match(/^\s+(\[?\w+(?:\.\w+)*\]?)/);
|
|
87
|
-
if (!nameMatch) continue;
|
|
88
|
-
let name = nameMatch[1];
|
|
89
|
-
// Strip [] from optional params: [opts] → opts
|
|
90
|
-
if (name.startsWith('[')) name = name.slice(1);
|
|
91
|
-
if (name.endsWith(']')) name = name.slice(0, -1);
|
|
92
|
-
// Strip dotted paths: options.includeTests → skip (nested property)
|
|
93
|
-
if (name.includes('.')) continue;
|
|
94
|
-
params.push({ name, type });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const hasReturns = /@returns?\s/.test(text);
|
|
98
|
-
|
|
99
|
-
comments.push({ text, endLine, params, hasReturns });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return comments;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Find JSDoc comment before a target line
|
|
107
|
-
* @param {Array} comments
|
|
108
|
-
* @param {number} targetLine
|
|
109
|
-
* @returns {Object|null}
|
|
110
|
-
*/
|
|
111
|
-
function findJSDocBefore(comments, targetLine) {
|
|
112
|
-
for (const comment of comments) {
|
|
113
|
-
const gap = targetLine - comment.endLine;
|
|
114
|
-
if (gap >= 0 && gap <= 2) return comment;
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Extract parameter name from AST node
|
|
121
|
-
* @param {Object} param
|
|
122
|
-
* @returns {string}
|
|
123
|
-
*/
|
|
124
|
-
function extractParamName(param) {
|
|
125
|
-
if (param.type === 'Identifier') return param.name;
|
|
126
|
-
if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') return param.left.name;
|
|
127
|
-
if (param.type === 'RestElement' && param.argument.type === 'Identifier') return param.argument.name;
|
|
128
|
-
if (param.type === 'ObjectPattern') return 'options';
|
|
129
|
-
if (param.type === 'ArrayPattern') return 'args';
|
|
130
|
-
return 'param';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Infer expected type from AST default value
|
|
135
|
-
* @param {Object} param
|
|
136
|
-
* @returns {string|null}
|
|
137
|
-
*/
|
|
138
|
-
function inferTypeFromDefault(param) {
|
|
139
|
-
if (param.type !== 'AssignmentPattern') return null;
|
|
140
|
-
const def = param.right;
|
|
141
|
-
if (def.type === 'Literal') {
|
|
142
|
-
if (typeof def.value === 'string') return 'string';
|
|
143
|
-
if (typeof def.value === 'number') return 'number';
|
|
144
|
-
if (typeof def.value === 'boolean') return 'boolean';
|
|
145
|
-
}
|
|
146
|
-
if (def.type === 'ArrayExpression') return 'Array';
|
|
147
|
-
if (def.type === 'ObjectExpression') return 'Object';
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Check if function body has return statements with values
|
|
153
|
-
* @param {Object} node - Function AST node
|
|
154
|
-
* @returns {boolean}
|
|
155
|
-
*/
|
|
156
|
-
function hasReturnValue(node) {
|
|
157
|
-
let found = false;
|
|
158
|
-
try {
|
|
159
|
-
walk.simple(node.body, {
|
|
160
|
-
ReturnStatement(ret) {
|
|
161
|
-
if (ret.argument) found = true;
|
|
162
|
-
},
|
|
163
|
-
// Don't recurse into nested functions
|
|
164
|
-
FunctionDeclaration() { },
|
|
165
|
-
FunctionExpression() { },
|
|
166
|
-
ArrowFunctionExpression() { },
|
|
167
|
-
});
|
|
168
|
-
} catch (e) { /* walk error */ }
|
|
169
|
-
return found;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Validate a function's JSDoc against its AST
|
|
174
|
-
* @param {Object} jsdoc - Parsed JSDoc
|
|
175
|
-
* @param {Object[]} astParams - AST param nodes
|
|
176
|
-
* @param {Object} funcNode - AST function node
|
|
177
|
-
* @param {string} name - Function name
|
|
178
|
-
* @param {string} file - File path
|
|
179
|
-
* @param {number} line - Line number
|
|
180
|
-
* @returns {JSDocIssue[]}
|
|
181
|
-
*/
|
|
182
|
-
function validateFunction(jsdoc, astParams, funcNode, name, file, line) {
|
|
183
|
-
const issues = [];
|
|
184
|
-
|
|
185
|
-
if (!jsdoc) return issues; // No JSDoc = handled by undocumented checker
|
|
186
|
-
|
|
187
|
-
const docParams = jsdoc.params;
|
|
188
|
-
|
|
189
|
-
// 1. Param count mismatch
|
|
190
|
-
if (docParams.length !== astParams.length) {
|
|
191
|
-
issues.push({
|
|
192
|
-
file, line, name,
|
|
193
|
-
severity: 'error',
|
|
194
|
-
message: `Param count mismatch: JSDoc has ${docParams.length}, function has ${astParams.length}`,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 2. Param name mismatch
|
|
199
|
-
const minLen = Math.min(docParams.length, astParams.length);
|
|
200
|
-
for (let i = 0; i < minLen; i++) {
|
|
201
|
-
const docName = docParams[i].name;
|
|
202
|
-
const astName = extractParamName(astParams[i]);
|
|
203
|
-
|
|
204
|
-
if (docName !== astName && astName !== 'options' && astName !== 'args' && astName !== 'param') {
|
|
205
|
-
issues.push({
|
|
206
|
-
file, line, name,
|
|
207
|
-
severity: 'error',
|
|
208
|
-
message: `Param name mismatch at position ${i}: JSDoc says "${docName}", code has "${astName}"`,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 3. Missing @returns on non-void functions
|
|
214
|
-
if (!jsdoc.hasReturns && hasReturnValue(funcNode)) {
|
|
215
|
-
issues.push({
|
|
216
|
-
file, line, name,
|
|
217
|
-
severity: 'warning',
|
|
218
|
-
message: 'Function returns a value but JSDoc has no @returns',
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 4. Type hint inconsistency
|
|
223
|
-
for (let i = 0; i < minLen; i++) {
|
|
224
|
-
const docType = docParams[i].type;
|
|
225
|
-
const inferredType = inferTypeFromDefault(astParams[i]);
|
|
226
|
-
|
|
227
|
-
if (inferredType && docType && docType !== '*') {
|
|
228
|
-
let compatible = docType.includes(inferredType);
|
|
229
|
-
// Union types like 'a'|'b' are valid strings
|
|
230
|
-
if (!compatible && inferredType === 'string' && docType.includes("'") && docType.includes('|')) {
|
|
231
|
-
compatible = true;
|
|
232
|
-
}
|
|
233
|
-
// Type[] shorthand is a valid Array
|
|
234
|
-
if (!compatible && inferredType === 'Array' && docType.includes('[]')) {
|
|
235
|
-
compatible = true;
|
|
236
|
-
}
|
|
237
|
-
if (!compatible) {
|
|
238
|
-
issues.push({
|
|
239
|
-
file, line, name,
|
|
240
|
-
severity: 'warning',
|
|
241
|
-
message: `Type mismatch for "${docParams[i].name}": JSDoc says {${docType}}, default value suggests {${inferredType}}`,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return issues;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Check JSDoc consistency for a single file (per-file export for cache integration)
|
|
252
|
-
* @param {string} code
|
|
253
|
-
* @param {string} filePath
|
|
254
|
-
* @returns {JSDocIssue[]}
|
|
255
|
-
*/
|
|
256
|
-
export function checkJSDocFile(code, filePath) {
|
|
257
|
-
const issues = [];
|
|
258
|
-
|
|
259
|
-
let ast;
|
|
260
|
-
try {
|
|
261
|
-
ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
|
|
262
|
-
} catch (e) {
|
|
263
|
-
return issues;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const comments = extractJSDocComments(code);
|
|
267
|
-
|
|
268
|
-
walk.simple(ast, {
|
|
269
|
-
FunctionDeclaration(node) {
|
|
270
|
-
if (!node.id) return;
|
|
271
|
-
const jsdoc = findJSDocBefore(comments, node.loc.start.line);
|
|
272
|
-
if (jsdoc) {
|
|
273
|
-
issues.push(...validateFunction(jsdoc, node.params, node, node.id.name, filePath, node.loc.start.line));
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
// Exported arrow/const functions
|
|
278
|
-
VariableDeclaration(node) {
|
|
279
|
-
for (const decl of node.declarations) {
|
|
280
|
-
if (!decl.init) continue;
|
|
281
|
-
const func = decl.init.type === 'ArrowFunctionExpression' || decl.init.type === 'FunctionExpression'
|
|
282
|
-
? decl.init : null;
|
|
283
|
-
if (!func || !decl.id?.name) continue;
|
|
284
|
-
|
|
285
|
-
const jsdoc = findJSDocBefore(comments, node.loc.start.line);
|
|
286
|
-
if (jsdoc) {
|
|
287
|
-
issues.push(...validateFunction(jsdoc, func.params, func, decl.id.name, filePath, node.loc.start.line));
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
ClassDeclaration(node) {
|
|
293
|
-
const className = node.id?.name || 'Anonymous';
|
|
294
|
-
for (const element of node.body.body) {
|
|
295
|
-
if (element.type !== 'MethodDefinition') continue;
|
|
296
|
-
const methodName = element.key.name || element.key.value;
|
|
297
|
-
if (!methodName || methodName === 'constructor') continue;
|
|
298
|
-
if (element.kind !== 'method') continue;
|
|
299
|
-
|
|
300
|
-
const funcNode = element.value;
|
|
301
|
-
const jsdoc = findJSDocBefore(comments, element.loc.start.line);
|
|
302
|
-
if (jsdoc) {
|
|
303
|
-
issues.push(...validateFunction(jsdoc, funcNode.params, funcNode, `${className}.${methodName}`, filePath, element.loc.start.line));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
return issues;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Check JSDoc consistency across a directory
|
|
314
|
-
* @param {string} dir
|
|
315
|
-
* @returns {{ issues: JSDocIssue[], summary: { total: number, errors: number, warnings: number, byFile: Object } }}
|
|
316
|
-
*/
|
|
317
|
-
export function checkJSDocConsistency(dir) {
|
|
318
|
-
const resolvedDir = resolve(dir);
|
|
319
|
-
const files = findJSFiles(dir);
|
|
320
|
-
const allIssues = [];
|
|
321
|
-
|
|
322
|
-
for (const file of files) {
|
|
323
|
-
let content;
|
|
324
|
-
try {
|
|
325
|
-
content = readFileSync(file, 'utf-8');
|
|
326
|
-
} catch (e) {
|
|
327
|
-
continue; // File deleted between findJSFiles and read
|
|
328
|
-
}
|
|
329
|
-
const relPath = relative(resolvedDir, file);
|
|
330
|
-
const issues = checkJSDocFile(content, relPath);
|
|
331
|
-
allIssues.push(...issues);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const errors = allIssues.filter(i => i.severity === 'error').length;
|
|
335
|
-
const warnings = allIssues.filter(i => i.severity === 'warning').length;
|
|
336
|
-
|
|
337
|
-
const byFile = {};
|
|
338
|
-
for (const issue of allIssues) {
|
|
339
|
-
byFile[issue.file] = (byFile[issue.file] || 0) + 1;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return {
|
|
343
|
-
issues: allIssues,
|
|
344
|
-
summary: {
|
|
345
|
-
total: allIssues.length,
|
|
346
|
-
errors,
|
|
347
|
-
warnings,
|
|
348
|
-
byFile,
|
|
349
|
-
},
|
|
350
|
-
};
|
|
351
|
-
}
|
package/src/jsdoc-generator.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSDoc Generator
|
|
3
|
-
* Auto-generates JSDoc templates from AST analysis
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync } from 'fs';
|
|
7
|
-
import { relative } from 'path';
|
|
8
|
-
import { parse } from '../vendor/acorn.mjs';
|
|
9
|
-
import * as walk from '../vendor/walk.mjs';
|
|
10
|
-
import { getWorkspaceRoot } from './workspace.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {Object} JSDocTemplate
|
|
14
|
-
* @property {string} name - Function/method name
|
|
15
|
-
* @property {string} type - 'function' | 'method' | 'class'
|
|
16
|
-
* @property {string} file
|
|
17
|
-
* @property {number} line
|
|
18
|
-
* @property {string} jsdoc - Generated JSDoc template
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Generate JSDoc for a single file
|
|
23
|
-
* @param {string} filePath - Absolute path to file
|
|
24
|
-
* @param {Object} [options]
|
|
25
|
-
* @returns {JSDocTemplate[]}
|
|
26
|
-
*/
|
|
27
|
-
export function generateJSDoc(filePath, options = {}) {
|
|
28
|
-
const results = [];
|
|
29
|
-
|
|
30
|
-
const code = readFileSync(filePath, 'utf-8');
|
|
31
|
-
const relPath = relative(getWorkspaceRoot(), filePath);
|
|
32
|
-
|
|
33
|
-
let ast;
|
|
34
|
-
try {
|
|
35
|
-
ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
|
|
36
|
-
} catch (e) {
|
|
37
|
-
return results;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Check if line already has JSDoc
|
|
41
|
-
const hasJSDocAt = (line) => {
|
|
42
|
-
const lines = code.split('\n');
|
|
43
|
-
// Look backwards from function line for JSDoc closing */
|
|
44
|
-
for (let i = line - 2; i >= Math.max(0, line - 15); i--) {
|
|
45
|
-
const trimmed = lines[i]?.trim();
|
|
46
|
-
if (!trimmed) continue; // Skip empty lines
|
|
47
|
-
// Found JSDoc end - look for start
|
|
48
|
-
if (trimmed === '*/' || trimmed.endsWith('*/')) {
|
|
49
|
-
// Now look for /** opening above
|
|
50
|
-
for (let j = i - 1; j >= Math.max(0, i - 20); j--) {
|
|
51
|
-
const upper = lines[j]?.trim();
|
|
52
|
-
if (upper?.startsWith('/**')) return true;
|
|
53
|
-
// If we hit something non-JSDoc, stop
|
|
54
|
-
if (upper && !upper.startsWith('*')) break;
|
|
55
|
-
}
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
// If we hit code, stop
|
|
59
|
-
if (!trimmed.startsWith('*') && !trimmed.startsWith('//')) break;
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
walk.simple(ast, {
|
|
65
|
-
FunctionDeclaration(node) {
|
|
66
|
-
if (!node.id) return;
|
|
67
|
-
if (hasJSDocAt(node.loc.start.line)) return;
|
|
68
|
-
|
|
69
|
-
const jsdoc = buildJSDoc({
|
|
70
|
-
name: node.id.name,
|
|
71
|
-
params: node.params,
|
|
72
|
-
async: node.async,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
results.push({
|
|
76
|
-
name: node.id.name,
|
|
77
|
-
type: 'function',
|
|
78
|
-
file: relPath,
|
|
79
|
-
line: node.loc.start.line,
|
|
80
|
-
jsdoc,
|
|
81
|
-
});
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
ClassDeclaration(node) {
|
|
85
|
-
if (!node.id) return;
|
|
86
|
-
|
|
87
|
-
// Check methods
|
|
88
|
-
for (const element of node.body.body) {
|
|
89
|
-
if (element.type === 'MethodDefinition') {
|
|
90
|
-
const methodName = element.key.name || element.key.value;
|
|
91
|
-
|
|
92
|
-
// Skip constructor, getters, setters, private
|
|
93
|
-
if (element.kind !== 'method') continue;
|
|
94
|
-
if (methodName.startsWith('_')) continue;
|
|
95
|
-
if (hasJSDocAt(element.loc.start.line)) continue;
|
|
96
|
-
|
|
97
|
-
const funcNode = element.value;
|
|
98
|
-
const jsdoc = buildJSDoc({
|
|
99
|
-
name: methodName,
|
|
100
|
-
params: funcNode.params,
|
|
101
|
-
async: funcNode.async,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
results.push({
|
|
105
|
-
name: `${node.id.name}.${methodName}`,
|
|
106
|
-
type: 'method',
|
|
107
|
-
file: relPath,
|
|
108
|
-
line: element.loc.start.line,
|
|
109
|
-
jsdoc,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return results;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Build JSDoc string from function info
|
|
121
|
-
* @param {Object} info
|
|
122
|
-
* @param {string} info.name
|
|
123
|
-
* @param {Array} info.params
|
|
124
|
-
* @param {boolean} info.async
|
|
125
|
-
* @returns {string}
|
|
126
|
-
*/
|
|
127
|
-
function buildJSDoc(info) {
|
|
128
|
-
const lines = ['/**'];
|
|
129
|
-
|
|
130
|
-
// Description placeholder
|
|
131
|
-
lines.push(` * TODO: Add description for ${info.name}`);
|
|
132
|
-
|
|
133
|
-
// Parameters
|
|
134
|
-
for (const param of info.params) {
|
|
135
|
-
const paramName = extractParamName(param);
|
|
136
|
-
const paramType = inferParamType(param);
|
|
137
|
-
lines.push(` * @param {${paramType}} ${paramName}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Return type
|
|
141
|
-
lines.push(` * @returns {${info.async ? 'Promise<*>' : '*'}}`);
|
|
142
|
-
|
|
143
|
-
lines.push(' */');
|
|
144
|
-
return lines.join('\n');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Extract parameter name from AST node
|
|
149
|
-
* @param {Object} param
|
|
150
|
-
* @returns {string}
|
|
151
|
-
*/
|
|
152
|
-
function extractParamName(param) {
|
|
153
|
-
if (param.type === 'Identifier') {
|
|
154
|
-
return param.name;
|
|
155
|
-
}
|
|
156
|
-
if (param.type === 'AssignmentPattern' && param.left.type === 'Identifier') {
|
|
157
|
-
return `[${param.left.name}]`; // Optional param
|
|
158
|
-
}
|
|
159
|
-
if (param.type === 'RestElement' && param.argument.type === 'Identifier') {
|
|
160
|
-
return `...${param.argument.name}`;
|
|
161
|
-
}
|
|
162
|
-
if (param.type === 'ObjectPattern') {
|
|
163
|
-
return 'options';
|
|
164
|
-
}
|
|
165
|
-
if (param.type === 'ArrayPattern') {
|
|
166
|
-
return 'args';
|
|
167
|
-
}
|
|
168
|
-
return 'param';
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Infer parameter type from AST
|
|
173
|
-
* @param {Object} param
|
|
174
|
-
* @returns {string}
|
|
175
|
-
*/
|
|
176
|
-
function inferParamType(param) {
|
|
177
|
-
if (param.type === 'AssignmentPattern') {
|
|
178
|
-
const defaultVal = param.right;
|
|
179
|
-
if (defaultVal.type === 'Literal') {
|
|
180
|
-
if (typeof defaultVal.value === 'string') return 'string';
|
|
181
|
-
if (typeof defaultVal.value === 'number') return 'number';
|
|
182
|
-
if (typeof defaultVal.value === 'boolean') return 'boolean';
|
|
183
|
-
}
|
|
184
|
-
if (defaultVal.type === 'ArrayExpression') return 'Array';
|
|
185
|
-
if (defaultVal.type === 'ObjectExpression') return 'Object';
|
|
186
|
-
}
|
|
187
|
-
if (param.type === 'RestElement') return 'Array';
|
|
188
|
-
if (param.type === 'ObjectPattern') return 'Object';
|
|
189
|
-
if (param.type === 'ArrayPattern') return 'Array';
|
|
190
|
-
return '*';
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Generate JSDoc for specific function by name
|
|
195
|
-
* @param {string} filePath
|
|
196
|
-
* @param {string} functionName
|
|
197
|
-
* @param {Object} [options]
|
|
198
|
-
* @returns {JSDocTemplate|null}
|
|
199
|
-
*/
|
|
200
|
-
export function generateJSDocFor(filePath, functionName, options = {}) {
|
|
201
|
-
const results = generateJSDoc(filePath, options);
|
|
202
|
-
return results.find(r => r.name === functionName || r.name.endsWith(`.${functionName}`)) || null;
|
|
203
|
-
}
|