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/drilldown.js
CHANGED
|
@@ -5,6 +5,7 @@ import { JsdocError } from "./errors.js";
|
|
|
5
5
|
import { discoverFiles } from "./file-discovery.js";
|
|
6
6
|
import { parseFileSummaries } from "./jsdoc-parser.js";
|
|
7
7
|
import { generateTypeDeclarations } from "./type-declarations.js";
|
|
8
|
+
|
|
8
9
|
/** Terminal level (1-indexed): 1=summary, 2=description, 3=type declarations, 4=full file. */
|
|
9
10
|
const TERMINAL_LEVEL = 4;
|
|
10
11
|
/**
|
|
@@ -18,33 +19,32 @@ const TERMINAL_LEVEL = 4;
|
|
|
18
19
|
* The array always has 4 entries. Null entries are skipped during processing.
|
|
19
20
|
*/
|
|
20
21
|
function buildLevels(info) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const { summary, description } = info;
|
|
23
|
+
return [
|
|
24
|
+
summary !== null ? { text: () => summary } : null,
|
|
25
|
+
description !== null ? { text: () => description } : null,
|
|
26
|
+
{ text: () => generateTypeDeclarations(info.path) },
|
|
27
|
+
{ text: () => readFileSync(info.path, "utf-8") },
|
|
28
|
+
];
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
31
|
* Compute the display id path for a file. Barrel files (index.ts/index.tsx)
|
|
31
32
|
* use their parent directory path so they represent the directory.
|
|
32
33
|
*/
|
|
33
34
|
function displayPath(filePath, cwd) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const rel = relative(cwd, filePath);
|
|
36
|
+
if (isBarrel(filePath)) {
|
|
37
|
+
const dir = relative(cwd, dirname(filePath));
|
|
38
|
+
return dir || ".";
|
|
39
|
+
}
|
|
40
|
+
return rel;
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Extract the sort key from an output entry (either next_id or id).
|
|
43
44
|
*/
|
|
44
45
|
function sortKey(entry) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return entry.id;
|
|
46
|
+
if ("next_id" in entry) return entry.next_id;
|
|
47
|
+
return entry.id;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
50
|
* Process a single file at a given depth through the drill-down levels.
|
|
@@ -54,97 +54,96 @@ function sortKey(entry) {
|
|
|
54
54
|
* Output contains next_id when more detail is available, or id at terminal level.
|
|
55
55
|
*/
|
|
56
56
|
function processFile(info, depth, cwd) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
57
|
+
const idPath = displayPath(info.path, cwd);
|
|
58
|
+
const levels = buildLevels(info);
|
|
59
|
+
// Clamp depth to [1, TERMINAL_LEVEL], advance past null levels
|
|
60
|
+
let effectiveDepth = Math.max(1, Math.min(depth, TERMINAL_LEVEL));
|
|
61
|
+
while (
|
|
62
|
+
effectiveDepth < TERMINAL_LEVEL &&
|
|
63
|
+
levels[effectiveDepth - 1] === null
|
|
64
|
+
) {
|
|
65
|
+
effectiveDepth++;
|
|
66
|
+
}
|
|
67
|
+
const level = levels[effectiveDepth - 1];
|
|
68
|
+
if (effectiveDepth < TERMINAL_LEVEL) {
|
|
69
|
+
return {
|
|
70
|
+
next_id: `${idPath}@${effectiveDepth + 1}`,
|
|
71
|
+
text: level.text(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
id: `${idPath}@${effectiveDepth}`,
|
|
76
|
+
text: level.text(),
|
|
77
|
+
};
|
|
76
78
|
}
|
|
77
79
|
/**
|
|
78
80
|
* Create an OutputErrorItem for a PARSE_ERROR.
|
|
79
81
|
*/
|
|
80
82
|
function makeParseErrorItem(filePath, error, cwd) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
return {
|
|
84
|
+
id: displayPath(filePath, cwd),
|
|
85
|
+
error: { code: error.code, message: error.message },
|
|
86
|
+
};
|
|
85
87
|
}
|
|
86
88
|
/**
|
|
87
89
|
* Check if an error is a PARSE_ERROR JsdocError.
|
|
88
90
|
*/
|
|
89
91
|
function isParseError(error) {
|
|
90
|
-
|
|
92
|
+
return error instanceof JsdocError && error.code === "PARSE_ERROR";
|
|
91
93
|
}
|
|
92
94
|
/**
|
|
93
95
|
* Process a file safely, returning an OutputErrorItem on PARSE_ERROR.
|
|
94
96
|
* Rethrows non-PARSE_ERROR exceptions.
|
|
95
97
|
*/
|
|
96
98
|
function processFileSafe(filePath, depth, cwd) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
99
|
+
try {
|
|
100
|
+
const info = parseFileSummaries(filePath);
|
|
101
|
+
return processFile(info, depth, cwd);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (isParseError(error)) return makeParseErrorItem(filePath, error, cwd);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
108
|
* Gather barrel info and error entries from a list of barrel file paths.
|
|
109
109
|
* Returns successfully parsed barrel infos and error entries for unparseable barrels.
|
|
110
110
|
*/
|
|
111
111
|
function gatherBarrelInfos(barrelPaths, cwd) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return { infos, errors };
|
|
112
|
+
const infos = [];
|
|
113
|
+
const errors = [];
|
|
114
|
+
for (const barrelPath of barrelPaths) {
|
|
115
|
+
try {
|
|
116
|
+
const info = parseFileSummaries(barrelPath);
|
|
117
|
+
const children = getBarrelChildren(barrelPath, cwd);
|
|
118
|
+
infos.push({
|
|
119
|
+
path: barrelPath,
|
|
120
|
+
hasSummary: info.summary !== null,
|
|
121
|
+
hasDescription: info.description !== null,
|
|
122
|
+
children,
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (isParseError(error)) {
|
|
126
|
+
errors.push(makeParseErrorItem(barrelPath, error, cwd));
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { infos, errors };
|
|
134
133
|
}
|
|
135
134
|
/**
|
|
136
135
|
* Build the set of files gated by barrels that have summaries.
|
|
137
136
|
*/
|
|
138
137
|
function buildGatedFileSet(barrelInfos) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
138
|
+
const gated = new Set();
|
|
139
|
+
for (const barrel of barrelInfos) {
|
|
140
|
+
if (barrel.hasSummary) {
|
|
141
|
+
for (const child of barrel.children) {
|
|
142
|
+
gated.add(child);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return gated;
|
|
148
147
|
}
|
|
149
148
|
/**
|
|
150
149
|
* Process a single barrel at the given depth (1-indexed).
|
|
@@ -153,26 +152,29 @@ function buildGatedFileSet(barrelInfos) {
|
|
|
153
152
|
* then transition at depth 3 where the barrel disappears and children appear.
|
|
154
153
|
*/
|
|
155
154
|
function processBarrelAtDepth(barrel, depth, cwd) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
if (!barrel.hasSummary) return [processFileSafe(barrel.path, depth, cwd)];
|
|
156
|
+
// Null-skip: if depth 2 but no description, advance to transition depth
|
|
157
|
+
let effectiveDepth = depth;
|
|
158
|
+
if (effectiveDepth === 2 && !barrel.hasDescription) {
|
|
159
|
+
effectiveDepth = 3;
|
|
160
|
+
}
|
|
161
|
+
if (effectiveDepth < 3) {
|
|
162
|
+
// Show barrel's own L1 (summary) or L2 (description)
|
|
163
|
+
const entry = processFileSafe(barrel.path, effectiveDepth, cwd);
|
|
164
|
+
if ("next_id" in entry) {
|
|
165
|
+
entry.children = barrel.children.map((c) => relative(cwd, c));
|
|
166
|
+
}
|
|
167
|
+
return [entry];
|
|
168
|
+
}
|
|
169
|
+
// Barrel transitions: barrel disappears, children appear
|
|
170
|
+
const childDepth = effectiveDepth - 2;
|
|
171
|
+
return collectSafeResults(barrel.children, childDepth, cwd);
|
|
170
172
|
}
|
|
171
173
|
/**
|
|
172
174
|
* Process a list of files through processFileSafe.
|
|
173
175
|
*/
|
|
174
176
|
function collectSafeResults(files, depth, cwd) {
|
|
175
|
-
|
|
177
|
+
return files.map((filePath) => processFileSafe(filePath, depth, cwd));
|
|
176
178
|
}
|
|
177
179
|
/**
|
|
178
180
|
* Process files discovered via glob with barrel gating.
|
|
@@ -186,30 +188,31 @@ function collectSafeResults(files, depth, cwd) {
|
|
|
186
188
|
* Non-barrel files that are not gated by any barrel are processed normally.
|
|
187
189
|
*/
|
|
188
190
|
function processGlobWithBarrels(files, depth, cwd) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
191
|
+
const barrelPaths = [];
|
|
192
|
+
const nonBarrelPaths = [];
|
|
193
|
+
for (const filePath of files) {
|
|
194
|
+
if (isBarrel(filePath)) {
|
|
195
|
+
barrelPaths.push(filePath);
|
|
196
|
+
} else {
|
|
197
|
+
nonBarrelPaths.push(filePath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (barrelPaths.length === 0) {
|
|
201
|
+
return collectSafeResults(nonBarrelPaths, depth, cwd);
|
|
202
|
+
}
|
|
203
|
+
const { infos: barrelInfos, errors: barrelErrors } = gatherBarrelInfos(
|
|
204
|
+
barrelPaths,
|
|
205
|
+
cwd,
|
|
206
|
+
);
|
|
207
|
+
const gatedFiles = buildGatedFileSet(barrelInfos);
|
|
208
|
+
const results = [...barrelErrors];
|
|
209
|
+
for (const barrel of barrelInfos) {
|
|
210
|
+
if (gatedFiles.has(barrel.path)) continue;
|
|
211
|
+
results.push(...processBarrelAtDepth(barrel, depth, cwd));
|
|
212
|
+
}
|
|
213
|
+
const ungatedNonBarrels = nonBarrelPaths.filter((f) => !gatedFiles.has(f));
|
|
214
|
+
results.push(...collectSafeResults(ungatedNonBarrels, depth, cwd));
|
|
215
|
+
return results;
|
|
213
216
|
}
|
|
214
217
|
/**
|
|
215
218
|
* Main entry point for normal-mode processing.
|
|
@@ -226,40 +229,45 @@ function processGlobWithBarrels(files, depth, cwd) {
|
|
|
226
229
|
* @throws {JsdocError} PARSE_ERROR for path selector targeting file with syntax errors
|
|
227
230
|
*/
|
|
228
231
|
export function drilldown(selector, cwd, gitignore = true, limit = 100) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
232
|
+
const depth = selector.depth ?? 1;
|
|
233
|
+
if (selector.type === "path") {
|
|
234
|
+
const files = discoverFiles(selector.pattern, cwd, gitignore);
|
|
235
|
+
if (files.length > 1) {
|
|
236
|
+
// Directory expanded to multiple files — route through glob pipeline
|
|
237
|
+
const results = processGlobWithBarrels(files, depth, cwd);
|
|
238
|
+
const sorted = results.sort((a, b) =>
|
|
239
|
+
sortKey(a).localeCompare(sortKey(b)),
|
|
240
|
+
);
|
|
241
|
+
const total = sorted.length;
|
|
242
|
+
const truncated = total > limit;
|
|
243
|
+
return {
|
|
244
|
+
items: sorted.slice(0, limit),
|
|
245
|
+
truncated,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Single file path — errors are fatal, no barrel gating
|
|
249
|
+
const filePath = files[0];
|
|
250
|
+
const info = parseFileSummaries(filePath);
|
|
251
|
+
const items = [processFile(info, depth, cwd)];
|
|
252
|
+
return { items, truncated: false };
|
|
253
|
+
}
|
|
254
|
+
// Glob selector — apply barrel gating
|
|
255
|
+
const files = discoverFiles(selector.pattern, cwd, gitignore);
|
|
256
|
+
if (files.length === 0) {
|
|
257
|
+
throw new JsdocError(
|
|
258
|
+
"NO_FILES_MATCHED",
|
|
259
|
+
`No files matched: ${selector.pattern}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const results = processGlobWithBarrels(files, depth, cwd);
|
|
263
|
+
// Sort alphabetically by key
|
|
264
|
+
const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
265
|
+
const total = sorted.length;
|
|
266
|
+
const truncated = total > limit;
|
|
267
|
+
return {
|
|
268
|
+
items: sorted.slice(0, limit),
|
|
269
|
+
truncated,
|
|
270
|
+
};
|
|
263
271
|
}
|
|
264
272
|
/**
|
|
265
273
|
* Process an explicit list of file paths at a given depth.
|
|
@@ -273,14 +281,16 @@ export function drilldown(selector, cwd, gitignore = true, limit = 100) {
|
|
|
273
281
|
* @returns Array of output entries sorted alphabetically by path
|
|
274
282
|
*/
|
|
275
283
|
export function drilldownFiles(filePaths, depth, cwd, limit = 100) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
const d = depth ?? 1;
|
|
285
|
+
const tsFiles = filePaths.filter(
|
|
286
|
+
(f) => f.endsWith(".ts") || f.endsWith(".tsx"),
|
|
287
|
+
);
|
|
288
|
+
const results = collectSafeResults(tsFiles, d, cwd);
|
|
289
|
+
const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
290
|
+
const total = sorted.length;
|
|
291
|
+
const truncated = total > limit;
|
|
292
|
+
return {
|
|
293
|
+
items: sorted.slice(0, limit),
|
|
294
|
+
truncated,
|
|
295
|
+
};
|
|
286
296
|
}
|
package/dist/errors.js
CHANGED
|
@@ -7,18 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Custom error class that serializes to the documented JSON error contract */
|
|
9
9
|
export class JsdocError extends Error {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
code;
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = "JsdocError";
|
|
15
|
+
}
|
|
16
|
+
toJSON() {
|
|
17
|
+
return {
|
|
18
|
+
error: {
|
|
19
|
+
code: this.code,
|
|
20
|
+
message: this.message,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
24
|
}
|