jsdoczoom 0.3.0 → 0.4.5
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 +26 -1
- package/dist/cache.js +97 -0
- package/dist/cli.js +201 -110
- package/dist/drilldown.js +163 -53
- package/dist/file-discovery.js +1 -2
- package/dist/index.js +7 -5
- package/dist/lint.js +38 -15
- package/dist/skill-text.js +7 -6
- package/dist/type-declarations.js +6 -4
- package/dist/types.js +4 -0
- package/dist/validate.js +31 -38
- package/package.json +13 -9
- package/types/barrel.d.ts +10 -0
- package/types/cache.d.ts +74 -0
- package/types/drilldown.d.ts +5 -3
- package/types/file-discovery.d.ts +14 -0
- package/types/index.d.ts +9 -4
- package/types/lint.d.ts +5 -1
- package/types/skill-text.d.ts +1 -1
- package/types/types.d.ts +10 -0
- package/types/validate.d.ts +6 -1
package/dist/drilldown.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, relative } from "node:path";
|
|
3
3
|
import { getBarrelChildren, isBarrel } from "./barrel.js";
|
|
4
|
+
import { processWithCache } from "./cache.js";
|
|
4
5
|
import { JsdocError } from "./errors.js";
|
|
5
|
-
import { discoverFiles } from "./file-discovery.js";
|
|
6
|
+
import { discoverFiles, loadGitignore } from "./file-discovery.js";
|
|
6
7
|
import { parseFileSummaries } from "./jsdoc-parser.js";
|
|
7
8
|
import { generateTypeDeclarations } from "./type-declarations.js";
|
|
9
|
+
import { DEFAULT_CACHE_DIR } from "./types.js";
|
|
8
10
|
|
|
9
11
|
/** Terminal level (1-indexed): 1=summary, 2=description, 3=type declarations, 4=full file. */
|
|
10
12
|
const TERMINAL_LEVEL = 4;
|
|
13
|
+
/** Default cache configuration used when no config is provided. */
|
|
14
|
+
const DEFAULT_CACHE_CONFIG = {
|
|
15
|
+
enabled: true,
|
|
16
|
+
directory: DEFAULT_CACHE_DIR,
|
|
17
|
+
};
|
|
11
18
|
/**
|
|
12
19
|
* Build the fixed drill-down level array for a file.
|
|
13
20
|
*
|
|
@@ -17,14 +24,16 @@ const TERMINAL_LEVEL = 4;
|
|
|
17
24
|
* - Level 4 (index 3): full file content (always present, terminal)
|
|
18
25
|
*
|
|
19
26
|
* The array always has 4 entries. Null entries are skipped during processing.
|
|
27
|
+
* Type declarations and file content are pre-computed and passed in to support
|
|
28
|
+
* async cache integration.
|
|
20
29
|
*/
|
|
21
|
-
function buildLevels(info) {
|
|
30
|
+
function buildLevels(info, typeDeclarations, fileContent) {
|
|
22
31
|
const { summary, description } = info;
|
|
23
32
|
return [
|
|
24
|
-
summary !== null ? { text:
|
|
25
|
-
description !== null ? { text:
|
|
26
|
-
{ text:
|
|
27
|
-
{ text:
|
|
33
|
+
summary !== null ? { text: summary } : null,
|
|
34
|
+
description !== null ? { text: description } : null,
|
|
35
|
+
{ text: typeDeclarations },
|
|
36
|
+
{ text: fileContent },
|
|
28
37
|
];
|
|
29
38
|
}
|
|
30
39
|
/**
|
|
@@ -53,9 +62,9 @@ function sortKey(entry) {
|
|
|
53
62
|
* If the requested depth level is null (empty), advance to the next non-null level.
|
|
54
63
|
* Output contains next_id when more detail is available, or id at terminal level.
|
|
55
64
|
*/
|
|
56
|
-
function processFile(info, depth, cwd) {
|
|
65
|
+
function processFile(info, typeDeclarations, fileContent, depth, cwd) {
|
|
57
66
|
const idPath = displayPath(info.path, cwd);
|
|
58
|
-
const levels = buildLevels(info);
|
|
67
|
+
const levels = buildLevels(info, typeDeclarations, fileContent);
|
|
59
68
|
// Clamp depth to [1, TERMINAL_LEVEL], advance past null levels
|
|
60
69
|
let effectiveDepth = Math.max(1, Math.min(depth, TERMINAL_LEVEL));
|
|
61
70
|
while (
|
|
@@ -68,12 +77,12 @@ function processFile(info, depth, cwd) {
|
|
|
68
77
|
if (effectiveDepth < TERMINAL_LEVEL) {
|
|
69
78
|
return {
|
|
70
79
|
next_id: `${idPath}@${effectiveDepth + 1}`,
|
|
71
|
-
text: level.text
|
|
80
|
+
text: level.text,
|
|
72
81
|
};
|
|
73
82
|
}
|
|
74
83
|
return {
|
|
75
84
|
id: `${idPath}@${effectiveDepth}`,
|
|
76
|
-
text: level.text
|
|
85
|
+
text: level.text,
|
|
77
86
|
};
|
|
78
87
|
}
|
|
79
88
|
/**
|
|
@@ -91,14 +100,56 @@ function makeParseErrorItem(filePath, error, cwd) {
|
|
|
91
100
|
function isParseError(error) {
|
|
92
101
|
return error instanceof JsdocError && error.code === "PARSE_ERROR";
|
|
93
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Determine the effective depth after null-level advancement.
|
|
105
|
+
* Advances past null summary (L1) and null description (L2) levels.
|
|
106
|
+
*/
|
|
107
|
+
function computeEffectiveDepth(depth, info) {
|
|
108
|
+
let effectiveDepth = Math.max(1, Math.min(depth, TERMINAL_LEVEL));
|
|
109
|
+
if (effectiveDepth < TERMINAL_LEVEL && info.summary === null) {
|
|
110
|
+
effectiveDepth = Math.max(effectiveDepth, 2);
|
|
111
|
+
}
|
|
112
|
+
if (effectiveDepth < TERMINAL_LEVEL && info.description === null) {
|
|
113
|
+
effectiveDepth = Math.max(effectiveDepth, 3);
|
|
114
|
+
}
|
|
115
|
+
return effectiveDepth;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Conditionally compute type declarations if effective depth requires it (L3+).
|
|
119
|
+
* Uses cache to avoid redundant TypeScript compilation.
|
|
120
|
+
*/
|
|
121
|
+
async function computeTypeDeclarationsIfNeeded(
|
|
122
|
+
content,
|
|
123
|
+
filePath,
|
|
124
|
+
effectiveDepth,
|
|
125
|
+
config,
|
|
126
|
+
) {
|
|
127
|
+
if (effectiveDepth < 3) {
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
130
|
+
return processWithCache(config, "drilldown", `${content}\0typedecl`, () =>
|
|
131
|
+
generateTypeDeclarations(filePath),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
94
134
|
/**
|
|
95
135
|
* Process a file safely, returning an OutputErrorItem on PARSE_ERROR.
|
|
96
|
-
* Rethrows non-PARSE_ERROR exceptions.
|
|
136
|
+
* Rethrows non-PARSE_ERROR exceptions. Uses cache for expensive operations
|
|
137
|
+
* (parseFileSummaries and generateTypeDeclarations).
|
|
97
138
|
*/
|
|
98
|
-
function processFileSafe(filePath, depth, cwd) {
|
|
139
|
+
async function processFileSafe(filePath, depth, cwd, config) {
|
|
99
140
|
try {
|
|
100
|
-
const
|
|
101
|
-
|
|
141
|
+
const content = await readFile(filePath, "utf-8");
|
|
142
|
+
const info = await processWithCache(config, "drilldown", content, () =>
|
|
143
|
+
parseFileSummaries(filePath),
|
|
144
|
+
);
|
|
145
|
+
const effectiveDepth = computeEffectiveDepth(depth, info);
|
|
146
|
+
const typeDeclarations = await computeTypeDeclarationsIfNeeded(
|
|
147
|
+
content,
|
|
148
|
+
filePath,
|
|
149
|
+
effectiveDepth,
|
|
150
|
+
config,
|
|
151
|
+
);
|
|
152
|
+
return processFile(info, typeDeclarations, content, depth, cwd);
|
|
102
153
|
} catch (error) {
|
|
103
154
|
if (isParseError(error)) return makeParseErrorItem(filePath, error, cwd);
|
|
104
155
|
throw error;
|
|
@@ -107,26 +158,44 @@ function processFileSafe(filePath, depth, cwd) {
|
|
|
107
158
|
/**
|
|
108
159
|
* Gather barrel info and error entries from a list of barrel file paths.
|
|
109
160
|
* Returns successfully parsed barrel infos and error entries for unparseable barrels.
|
|
161
|
+
* Uses cache for parseFileSummaries calls.
|
|
110
162
|
*/
|
|
111
|
-
function gatherBarrelInfos(barrelPaths, cwd) {
|
|
163
|
+
async function gatherBarrelInfos(barrelPaths, cwd, config) {
|
|
112
164
|
const infos = [];
|
|
113
165
|
const errors = [];
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
166
|
+
const results = await Promise.all(
|
|
167
|
+
barrelPaths.map(async (barrelPath) => {
|
|
168
|
+
try {
|
|
169
|
+
const content = await readFile(barrelPath, "utf-8");
|
|
170
|
+
const info = await processWithCache(config, "drilldown", content, () =>
|
|
171
|
+
parseFileSummaries(barrelPath),
|
|
172
|
+
);
|
|
173
|
+
const children = getBarrelChildren(barrelPath, cwd);
|
|
174
|
+
return {
|
|
175
|
+
type: "info",
|
|
176
|
+
data: {
|
|
177
|
+
path: barrelPath,
|
|
178
|
+
hasSummary: info.summary !== null,
|
|
179
|
+
hasDescription: info.description !== null,
|
|
180
|
+
children,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (isParseError(error)) {
|
|
185
|
+
return {
|
|
186
|
+
type: "error",
|
|
187
|
+
data: makeParseErrorItem(barrelPath, error, cwd),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
128
191
|
}
|
|
129
|
-
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
for (const result of results) {
|
|
195
|
+
if (result.type === "info") {
|
|
196
|
+
infos.push(result.data);
|
|
197
|
+
} else {
|
|
198
|
+
errors.push(result.data);
|
|
130
199
|
}
|
|
131
200
|
}
|
|
132
201
|
return { infos, errors };
|
|
@@ -151,8 +220,9 @@ function buildGatedFileSet(barrelInfos) {
|
|
|
151
220
|
* Barrels with summaries gate children for two depths (L1 summary, L2 description),
|
|
152
221
|
* then transition at depth 3 where the barrel disappears and children appear.
|
|
153
222
|
*/
|
|
154
|
-
function processBarrelAtDepth(barrel, depth, cwd) {
|
|
155
|
-
if (!barrel.hasSummary)
|
|
223
|
+
async function processBarrelAtDepth(barrel, depth, cwd, config) {
|
|
224
|
+
if (!barrel.hasSummary)
|
|
225
|
+
return [await processFileSafe(barrel.path, depth, cwd, config)];
|
|
156
226
|
// Null-skip: if depth 2 but no description, advance to transition depth
|
|
157
227
|
let effectiveDepth = depth;
|
|
158
228
|
if (effectiveDepth === 2 && !barrel.hasDescription) {
|
|
@@ -160,7 +230,12 @@ function processBarrelAtDepth(barrel, depth, cwd) {
|
|
|
160
230
|
}
|
|
161
231
|
if (effectiveDepth < 3) {
|
|
162
232
|
// Show barrel's own L1 (summary) or L2 (description)
|
|
163
|
-
const entry = processFileSafe(
|
|
233
|
+
const entry = await processFileSafe(
|
|
234
|
+
barrel.path,
|
|
235
|
+
effectiveDepth,
|
|
236
|
+
cwd,
|
|
237
|
+
config,
|
|
238
|
+
);
|
|
164
239
|
if ("next_id" in entry) {
|
|
165
240
|
entry.children = barrel.children.map((c) => relative(cwd, c));
|
|
166
241
|
}
|
|
@@ -168,13 +243,15 @@ function processBarrelAtDepth(barrel, depth, cwd) {
|
|
|
168
243
|
}
|
|
169
244
|
// Barrel transitions: barrel disappears, children appear
|
|
170
245
|
const childDepth = effectiveDepth - 2;
|
|
171
|
-
return collectSafeResults(barrel.children, childDepth, cwd);
|
|
246
|
+
return collectSafeResults(barrel.children, childDepth, cwd, config);
|
|
172
247
|
}
|
|
173
248
|
/**
|
|
174
|
-
* Process a list of files through processFileSafe.
|
|
249
|
+
* Process a list of files through processFileSafe in parallel.
|
|
175
250
|
*/
|
|
176
|
-
function collectSafeResults(files, depth, cwd) {
|
|
177
|
-
return
|
|
251
|
+
async function collectSafeResults(files, depth, cwd, config) {
|
|
252
|
+
return Promise.all(
|
|
253
|
+
files.map((filePath) => processFileSafe(filePath, depth, cwd, config)),
|
|
254
|
+
);
|
|
178
255
|
}
|
|
179
256
|
/**
|
|
180
257
|
* Process files discovered via glob with barrel gating.
|
|
@@ -187,7 +264,7 @@ function collectSafeResults(files, depth, cwd) {
|
|
|
187
264
|
* A barrel that is itself gated by a parent barrel is not processed independently.
|
|
188
265
|
* Non-barrel files that are not gated by any barrel are processed normally.
|
|
189
266
|
*/
|
|
190
|
-
function processGlobWithBarrels(files, depth, cwd) {
|
|
267
|
+
async function processGlobWithBarrels(files, depth, cwd, config) {
|
|
191
268
|
const barrelPaths = [];
|
|
192
269
|
const nonBarrelPaths = [];
|
|
193
270
|
for (const filePath of files) {
|
|
@@ -198,20 +275,27 @@ function processGlobWithBarrels(files, depth, cwd) {
|
|
|
198
275
|
}
|
|
199
276
|
}
|
|
200
277
|
if (barrelPaths.length === 0) {
|
|
201
|
-
return collectSafeResults(nonBarrelPaths, depth, cwd);
|
|
278
|
+
return collectSafeResults(nonBarrelPaths, depth, cwd, config);
|
|
202
279
|
}
|
|
203
|
-
const { infos: barrelInfos, errors: barrelErrors } = gatherBarrelInfos(
|
|
280
|
+
const { infos: barrelInfos, errors: barrelErrors } = await gatherBarrelInfos(
|
|
204
281
|
barrelPaths,
|
|
205
282
|
cwd,
|
|
283
|
+
config,
|
|
206
284
|
);
|
|
207
285
|
const gatedFiles = buildGatedFileSet(barrelInfos);
|
|
208
286
|
const results = [...barrelErrors];
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
287
|
+
const barrelResults = await Promise.all(
|
|
288
|
+
barrelInfos
|
|
289
|
+
.filter((barrel) => !gatedFiles.has(barrel.path))
|
|
290
|
+
.map((barrel) => processBarrelAtDepth(barrel, depth, cwd, config)),
|
|
291
|
+
);
|
|
292
|
+
for (const entries of barrelResults) {
|
|
293
|
+
results.push(...entries);
|
|
212
294
|
}
|
|
213
295
|
const ungatedNonBarrels = nonBarrelPaths.filter((f) => !gatedFiles.has(f));
|
|
214
|
-
results.push(
|
|
296
|
+
results.push(
|
|
297
|
+
...(await collectSafeResults(ungatedNonBarrels, depth, cwd, config)),
|
|
298
|
+
);
|
|
215
299
|
return results;
|
|
216
300
|
}
|
|
217
301
|
/**
|
|
@@ -228,13 +312,19 @@ function processGlobWithBarrels(files, depth, cwd) {
|
|
|
228
312
|
* @throws {JsdocError} NO_FILES_MATCHED for empty glob results
|
|
229
313
|
* @throws {JsdocError} PARSE_ERROR for path selector targeting file with syntax errors
|
|
230
314
|
*/
|
|
231
|
-
export function drilldown(
|
|
315
|
+
export async function drilldown(
|
|
316
|
+
selector,
|
|
317
|
+
cwd,
|
|
318
|
+
gitignore = true,
|
|
319
|
+
limit = 100,
|
|
320
|
+
config = DEFAULT_CACHE_CONFIG,
|
|
321
|
+
) {
|
|
232
322
|
const depth = selector.depth ?? 1;
|
|
233
323
|
if (selector.type === "path") {
|
|
234
324
|
const files = discoverFiles(selector.pattern, cwd, gitignore);
|
|
235
325
|
if (files.length > 1) {
|
|
236
326
|
// Directory expanded to multiple files — route through glob pipeline
|
|
237
|
-
const results = processGlobWithBarrels(files, depth, cwd);
|
|
327
|
+
const results = await processGlobWithBarrels(files, depth, cwd, config);
|
|
238
328
|
const sorted = results.sort((a, b) =>
|
|
239
329
|
sortKey(a).localeCompare(sortKey(b)),
|
|
240
330
|
);
|
|
@@ -247,8 +337,18 @@ export function drilldown(selector, cwd, gitignore = true, limit = 100) {
|
|
|
247
337
|
}
|
|
248
338
|
// Single file path — errors are fatal, no barrel gating
|
|
249
339
|
const filePath = files[0];
|
|
250
|
-
const
|
|
251
|
-
const
|
|
340
|
+
const content = await readFile(filePath, "utf-8");
|
|
341
|
+
const info = await processWithCache(config, "drilldown", content, () =>
|
|
342
|
+
parseFileSummaries(filePath),
|
|
343
|
+
);
|
|
344
|
+
const effectiveDepth = computeEffectiveDepth(depth, info);
|
|
345
|
+
const typeDeclarations = await computeTypeDeclarationsIfNeeded(
|
|
346
|
+
content,
|
|
347
|
+
filePath,
|
|
348
|
+
effectiveDepth,
|
|
349
|
+
config,
|
|
350
|
+
);
|
|
351
|
+
const items = [processFile(info, typeDeclarations, content, depth, cwd)];
|
|
252
352
|
return { items, truncated: false };
|
|
253
353
|
}
|
|
254
354
|
// Glob selector — apply barrel gating
|
|
@@ -259,7 +359,7 @@ export function drilldown(selector, cwd, gitignore = true, limit = 100) {
|
|
|
259
359
|
`No files matched: ${selector.pattern}`,
|
|
260
360
|
);
|
|
261
361
|
}
|
|
262
|
-
const results = processGlobWithBarrels(files, depth, cwd);
|
|
362
|
+
const results = await processGlobWithBarrels(files, depth, cwd, config);
|
|
263
363
|
// Sort alphabetically by key
|
|
264
364
|
const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
265
365
|
const total = sorted.length;
|
|
@@ -280,12 +380,22 @@ export function drilldown(selector, cwd, gitignore = true, limit = 100) {
|
|
|
280
380
|
* @param cwd - Working directory for relative path output
|
|
281
381
|
* @returns Array of output entries sorted alphabetically by path
|
|
282
382
|
*/
|
|
283
|
-
export function drilldownFiles(
|
|
383
|
+
export async function drilldownFiles(
|
|
384
|
+
filePaths,
|
|
385
|
+
depth,
|
|
386
|
+
cwd,
|
|
387
|
+
limit = 100,
|
|
388
|
+
config = DEFAULT_CACHE_CONFIG,
|
|
389
|
+
) {
|
|
284
390
|
const d = depth ?? 1;
|
|
391
|
+
const ig = loadGitignore(cwd);
|
|
285
392
|
const tsFiles = filePaths.filter(
|
|
286
|
-
(f) =>
|
|
393
|
+
(f) =>
|
|
394
|
+
(f.endsWith(".ts") || f.endsWith(".tsx")) &&
|
|
395
|
+
!f.endsWith(".d.ts") &&
|
|
396
|
+
!ig.ignores(relative(cwd, f)),
|
|
287
397
|
);
|
|
288
|
-
const results = collectSafeResults(tsFiles, d, cwd);
|
|
398
|
+
const results = await collectSafeResults(tsFiles, d, cwd, config);
|
|
289
399
|
const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
290
400
|
const total = sorted.length;
|
|
291
401
|
const truncated = total > limit;
|
package/dist/file-discovery.js
CHANGED
|
@@ -3,7 +3,6 @@ import { dirname, join, relative, resolve } from "node:path";
|
|
|
3
3
|
import { globSync } from "glob";
|
|
4
4
|
import ignore from "ignore";
|
|
5
5
|
import { JsdocError } from "./errors.js";
|
|
6
|
-
|
|
7
6
|
/**
|
|
8
7
|
* Walks .gitignore files from cwd to filesystem root, building an ignore
|
|
9
8
|
* filter that glob results pass through. Direct-path lookups bypass the
|
|
@@ -16,7 +15,7 @@ import { JsdocError } from "./errors.js";
|
|
|
16
15
|
* Walk from `cwd` up to the filesystem root, collecting .gitignore entries.
|
|
17
16
|
* Returns an Ignore instance loaded with all discovered rules.
|
|
18
17
|
*/
|
|
19
|
-
function loadGitignore(cwd) {
|
|
18
|
+
export function loadGitignore(cwd) {
|
|
20
19
|
const ig = ignore();
|
|
21
20
|
let dir = resolve(cwd);
|
|
22
21
|
while (true) {
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Progressively explores TypeScript codebase documentation through a 4-level
|
|
3
|
+
* drill-down (summary, description, type declarations, full source), validates
|
|
4
|
+
* file-level JSDoc structure, and lints comprehensive JSDoc quality using
|
|
5
|
+
* ESLint. Barrel files gate their children at shallow depths, revealing
|
|
6
|
+
* individual files only at deeper levels.
|
|
5
7
|
*
|
|
6
|
-
* @summary
|
|
8
|
+
* @summary Progressive JSDoc exploration, validation, and linting for TypeScript codebases
|
|
7
9
|
*/
|
|
8
10
|
export { getBarrelChildren, isBarrel } from "./barrel.js";
|
|
9
11
|
export { drilldown, drilldownFiles } from "./drilldown.js";
|
|
@@ -13,5 +15,5 @@ export { extractFileJsdoc, parseFileSummaries } from "./jsdoc-parser.js";
|
|
|
13
15
|
export { lint, lintFiles } from "./lint.js";
|
|
14
16
|
export { parseSelector } from "./selector.js";
|
|
15
17
|
export { generateTypeDeclarations } from "./type-declarations.js";
|
|
16
|
-
export { VALIDATION_STATUS_PRIORITY } from "./types.js";
|
|
18
|
+
export { DEFAULT_CACHE_DIR, VALIDATION_STATUS_PRIORITY } from "./types.js";
|
|
17
19
|
export { validate, validateFiles } from "./validate.js";
|
package/dist/lint.js
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @summary Lint files for comprehensive JSDoc quality using ESLint engine
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { readFile } from "node:fs/promises";
|
|
12
12
|
import { relative } from "node:path";
|
|
13
|
+
import { findMissingBarrels } from "./barrel.js";
|
|
14
|
+
import { processWithCache } from "./cache.js";
|
|
13
15
|
import { JsdocError } from "./errors.js";
|
|
14
16
|
import { createLintLinter, lintFileForLint } from "./eslint-engine.js";
|
|
15
17
|
import { discoverFiles } from "./file-discovery.js";
|
|
18
|
+
import { DEFAULT_CACHE_DIR } from "./types.js";
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Lint a single file and return per-file diagnostics.
|
|
@@ -20,15 +23,18 @@ import { discoverFiles } from "./file-discovery.js";
|
|
|
20
23
|
* @param eslint - Reusable ESLint instance with correct cwd
|
|
21
24
|
* @param filePath - Absolute path to the file
|
|
22
25
|
* @param cwd - Working directory for computing relative paths
|
|
26
|
+
* @param config - Cache configuration
|
|
23
27
|
* @returns File result with relative path and diagnostics array
|
|
24
28
|
*/
|
|
25
|
-
async function lintSingleFile(eslint, filePath, cwd) {
|
|
26
|
-
const sourceText =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
async function lintSingleFile(eslint, filePath, cwd, config) {
|
|
30
|
+
const sourceText = await readFile(filePath, "utf-8");
|
|
31
|
+
return processWithCache(config, "lint", sourceText, async () => {
|
|
32
|
+
const diagnostics = await lintFileForLint(eslint, sourceText, filePath);
|
|
33
|
+
return {
|
|
34
|
+
filePath: relative(cwd, filePath),
|
|
35
|
+
diagnostics,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* Build a LintResult from per-file results, applying a limit to files with issues.
|
|
@@ -36,9 +42,10 @@ async function lintSingleFile(eslint, filePath, cwd) {
|
|
|
36
42
|
* @param fileResults - All per-file lint results
|
|
37
43
|
* @param totalFiles - Total number of files that were linted
|
|
38
44
|
* @param limit - Maximum number of files with issues to include
|
|
45
|
+
* @param missingBarrels - Directories missing barrel files
|
|
39
46
|
* @returns Aggregated lint result with summary statistics
|
|
40
47
|
*/
|
|
41
|
-
function buildLintResult(fileResults, totalFiles, limit) {
|
|
48
|
+
function buildLintResult(fileResults, totalFiles, limit, missingBarrels) {
|
|
42
49
|
const filesWithIssues = fileResults.filter((f) => f.diagnostics.length > 0);
|
|
43
50
|
const totalDiagnostics = filesWithIssues.reduce(
|
|
44
51
|
(sum, f) => sum + f.diagnostics.length,
|
|
@@ -48,6 +55,7 @@ function buildLintResult(fileResults, totalFiles, limit) {
|
|
|
48
55
|
const truncated = filesWithIssues.length > limit;
|
|
49
56
|
return {
|
|
50
57
|
files: cappedFiles,
|
|
58
|
+
...(missingBarrels.length > 0 ? { missingBarrels } : {}),
|
|
51
59
|
summary: {
|
|
52
60
|
totalFiles,
|
|
53
61
|
filesWithIssues: filesWithIssues.length,
|
|
@@ -67,10 +75,17 @@ function buildLintResult(fileResults, totalFiles, limit) {
|
|
|
67
75
|
* @param cwd - Working directory for resolving paths
|
|
68
76
|
* @param limit - Max number of files with issues to include (default 100)
|
|
69
77
|
* @param gitignore - Whether to respect .gitignore rules (default true)
|
|
78
|
+
* @param config - Cache configuration (default enabled with DEFAULT_CACHE_DIR)
|
|
70
79
|
* @returns Lint result with per-file diagnostics and summary
|
|
71
80
|
* @throws {JsdocError} NO_FILES_MATCHED if glob selector matches no files
|
|
72
81
|
*/
|
|
73
|
-
export async function lint(
|
|
82
|
+
export async function lint(
|
|
83
|
+
selector,
|
|
84
|
+
cwd,
|
|
85
|
+
limit = 100,
|
|
86
|
+
gitignore = true,
|
|
87
|
+
config = { enabled: true, directory: DEFAULT_CACHE_DIR },
|
|
88
|
+
) {
|
|
74
89
|
const files = discoverFiles(selector.pattern, cwd, gitignore);
|
|
75
90
|
if (files.length === 0 && selector.type === "glob") {
|
|
76
91
|
throw new JsdocError(
|
|
@@ -81,9 +96,10 @@ export async function lint(selector, cwd, limit = 100, gitignore = true) {
|
|
|
81
96
|
const tsFiles = files.filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
82
97
|
const eslint = createLintLinter(cwd);
|
|
83
98
|
const fileResults = await Promise.all(
|
|
84
|
-
tsFiles.map((f) => lintSingleFile(eslint, f, cwd)),
|
|
99
|
+
tsFiles.map((f) => lintSingleFile(eslint, f, cwd, config)),
|
|
85
100
|
);
|
|
86
|
-
|
|
101
|
+
const missingBarrels = findMissingBarrels(tsFiles, cwd);
|
|
102
|
+
return buildLintResult(fileResults, tsFiles.length, limit, missingBarrels);
|
|
87
103
|
}
|
|
88
104
|
/**
|
|
89
105
|
* Lint an explicit list of file paths for comprehensive JSDoc quality.
|
|
@@ -94,15 +110,22 @@ export async function lint(selector, cwd, limit = 100, gitignore = true) {
|
|
|
94
110
|
* @param filePaths - List of absolute file paths to lint
|
|
95
111
|
* @param cwd - Working directory for computing relative paths
|
|
96
112
|
* @param limit - Max number of files with issues to include (default 100)
|
|
113
|
+
* @param config - Cache configuration (default enabled with DEFAULT_CACHE_DIR)
|
|
97
114
|
* @returns Lint result with per-file diagnostics and summary
|
|
98
115
|
*/
|
|
99
|
-
export async function lintFiles(
|
|
116
|
+
export async function lintFiles(
|
|
117
|
+
filePaths,
|
|
118
|
+
cwd,
|
|
119
|
+
limit = 100,
|
|
120
|
+
config = { enabled: true, directory: DEFAULT_CACHE_DIR },
|
|
121
|
+
) {
|
|
100
122
|
const tsFiles = filePaths.filter(
|
|
101
123
|
(f) => f.endsWith(".ts") || f.endsWith(".tsx"),
|
|
102
124
|
);
|
|
103
125
|
const eslint = createLintLinter(cwd);
|
|
104
126
|
const fileResults = await Promise.all(
|
|
105
|
-
tsFiles.map((f) => lintSingleFile(eslint, f, cwd)),
|
|
127
|
+
tsFiles.map((f) => lintSingleFile(eslint, f, cwd, config)),
|
|
106
128
|
);
|
|
107
|
-
|
|
129
|
+
const missingBarrels = findMissingBarrels(tsFiles, cwd);
|
|
130
|
+
return buildLintResult(fileResults, tsFiles.length, limit, missingBarrels);
|
|
108
131
|
}
|
package/dist/skill-text.js
CHANGED
|
@@ -55,7 +55,7 @@ Each file must have exactly one \`@summary\` tag. Remove the extra \`@summary\`
|
|
|
55
55
|
|
|
56
56
|
Directories with more than 3 TypeScript files should have an \`index.ts\` barrel file. The barrel provides a module-level \`@summary\` and description that helps agents understand what the directory contains before drilling into individual files.
|
|
57
57
|
|
|
58
|
-
Create an \`index.ts\` that re-exports the directory's public API and add a file-level JSDoc block describing the
|
|
58
|
+
Create an \`index.ts\` that re-exports the directory's public API and add a file-level JSDoc block describing what the child files collectively accomplish — not the re-export mechanism itself.`,
|
|
59
59
|
missing_description: `### The description paragraph
|
|
60
60
|
|
|
61
61
|
The description is prose that **must appear before the first \`@\` tag** in the file-level JSDoc block. Text placed after tags is not recognized as description content.
|
|
@@ -220,10 +220,10 @@ The description is prose that appears before any \`@\` tags. It provides the dee
|
|
|
220
220
|
|
|
221
221
|
### Barrel files (index.ts / index.tsx)
|
|
222
222
|
|
|
223
|
-
Barrel files represent their directory.
|
|
223
|
+
Barrel files represent their directory. Their \`@summary\` and description should describe the **cumulative functionality of the directory's children**, not the barrel file itself.
|
|
224
224
|
|
|
225
|
-
- **\`@summary\`**:
|
|
226
|
-
- **Description**: The
|
|
225
|
+
- **\`@summary\`**: The collective purpose of the files in this directory
|
|
226
|
+
- **Description**: The combined capabilities and responsibilities of child modules — what they do together, not their filenames or the re-export mechanism
|
|
227
227
|
|
|
228
228
|
\`\`\`typescript
|
|
229
229
|
// packages/auth/src/index.ts
|
|
@@ -238,9 +238,10 @@ Barrel files represent their directory. The \`@summary\` and description describ
|
|
|
238
238
|
\`\`\`
|
|
239
239
|
|
|
240
240
|
**Avoid:**
|
|
241
|
-
-
|
|
241
|
+
- \`@summary Re-exports all functions and types\` — describes the barrel mechanism, not what the children do
|
|
242
242
|
- \`@summary Exports for the auth module\` — describes the mechanism, not the purpose
|
|
243
|
-
- \`@summary
|
|
243
|
+
- \`@summary Public API barrel\` — names the pattern rather than describing functionality
|
|
244
|
+
- Listing child filenames or saying "This is the entry point"
|
|
244
245
|
|
|
245
246
|
### Placement
|
|
246
247
|
|
|
@@ -48,8 +48,9 @@ export function resolveCompilerOptions(filePath) {
|
|
|
48
48
|
);
|
|
49
49
|
// Use cache key based on tsconfig path (or "__default__" if none found)
|
|
50
50
|
const cacheKey = tsconfigPath ?? "__default__";
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const cached = compilerOptionsCache.get(cacheKey);
|
|
52
|
+
if (cached) {
|
|
53
|
+
return cached;
|
|
53
54
|
}
|
|
54
55
|
let result;
|
|
55
56
|
if (!tsconfigPath) {
|
|
@@ -109,8 +110,9 @@ export function resolveCompilerOptions(filePath) {
|
|
|
109
110
|
*/
|
|
110
111
|
export function getLanguageService(tsconfigPath, compilerOptions) {
|
|
111
112
|
const cacheKey = tsconfigPath ?? "__default__";
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
const cachedService = serviceCache.get(cacheKey);
|
|
114
|
+
if (cachedService) {
|
|
115
|
+
return cachedService;
|
|
114
116
|
}
|
|
115
117
|
// Create a mutable set to track files for this service
|
|
116
118
|
const files = new Set();
|
package/dist/types.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @summary Shared type definitions for output shapes, error codes, and parsed structures
|
|
7
7
|
*/
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
8
10
|
/** Priority order for validation status categories */
|
|
9
11
|
export const VALIDATION_STATUS_PRIORITY = [
|
|
10
12
|
"syntax_error",
|
|
@@ -29,3 +31,5 @@ export function appendText(existing, addition) {
|
|
|
29
31
|
if (existing.length === 0) return addition;
|
|
30
32
|
return `${existing} ${addition}`;
|
|
31
33
|
}
|
|
34
|
+
/** Default cache directory under os.tmpdir() */
|
|
35
|
+
export const DEFAULT_CACHE_DIR = join(tmpdir(), ".jsdoczoom-cache");
|