jsdoczoom 0.4.21 → 1.2.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 +47 -28
- package/dist/cli.js +140 -54
- package/dist/drilldown.js +24 -16
- package/dist/file-discovery.js +15 -10
- package/dist/index.js +7 -1
- package/dist/jsdoc-parser.js +3 -3
- package/dist/lint.js +3 -3
- package/dist/search.js +226 -0
- package/dist/skill-text.js +18 -0
- package/dist/text-format.js +42 -0
- package/dist/type-declarations.js +247 -3
- package/dist/validate.js +3 -3
- package/package.json +1 -1
- package/types/barrel.d.ts +2 -2
- package/types/file-discovery.d.ts +2 -2
- package/types/index.d.ts +7 -1
- package/types/jsdoc-parser.d.ts +3 -1
- package/types/search.d.ts +44 -0
- package/types/skill-text.d.ts +1 -1
- package/types/text-format.d.ts +6 -0
- package/types/type-declarations.d.ts +53 -1
- package/types/types.d.ts +1 -0
package/dist/barrel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { access, readdir } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
/**
|
|
4
4
|
* Barrel detection and child discovery for index.ts/index.tsx files.
|
|
@@ -34,29 +34,34 @@ export function isBarrel(filePath) {
|
|
|
34
34
|
* @param _cwd - Working directory (unused, kept for API consistency)
|
|
35
35
|
* @returns Sorted array of absolute paths to child files
|
|
36
36
|
*/
|
|
37
|
-
export function getBarrelChildren(barrelPath, _cwd) {
|
|
37
|
+
export async function getBarrelChildren(barrelPath, _cwd) {
|
|
38
38
|
const dir = dirname(barrelPath);
|
|
39
39
|
const barrelName = basename(barrelPath);
|
|
40
|
-
let
|
|
40
|
+
let rawEntries;
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
42
|
+
const dirents = await readdir(dir, { withFileTypes: true });
|
|
43
|
+
rawEntries = dirents.map((e) => ({
|
|
44
|
+
name: e.name,
|
|
45
|
+
isDirectory: e.isDirectory(),
|
|
46
|
+
}));
|
|
47
|
+
} catch {
|
|
48
|
+
// Directory unreadable (permissions, deleted, etc.) — no children discoverable
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const entries = (
|
|
52
|
+
await Promise.all(
|
|
53
|
+
rawEntries.map(async (entry) => {
|
|
45
54
|
if (entry.isDirectory) {
|
|
46
|
-
|
|
47
|
-
const childBarrel = findChildBarrel(resolve(dir, entry.name));
|
|
55
|
+
const childBarrel = await findChildBarrel(resolve(dir, entry.name));
|
|
48
56
|
return childBarrel ? [childBarrel] : [];
|
|
49
57
|
}
|
|
50
|
-
// Sibling file: must be .ts/.tsx, not .d.ts, not the barrel itself
|
|
51
58
|
if (isTsFile(entry.name) && entry.name !== barrelName) {
|
|
52
59
|
return [resolve(dir, entry.name)];
|
|
53
60
|
}
|
|
54
61
|
return [];
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return [];
|
|
59
|
-
}
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
64
|
+
).flat();
|
|
60
65
|
return entries.sort();
|
|
61
66
|
}
|
|
62
67
|
/**
|
|
@@ -72,16 +77,21 @@ function isTsFile(name) {
|
|
|
72
77
|
* index.ts takes priority over index.tsx.
|
|
73
78
|
* Returns the absolute path to the barrel, or null if none found.
|
|
74
79
|
*/
|
|
75
|
-
function findChildBarrel(subdirPath) {
|
|
80
|
+
async function findChildBarrel(subdirPath) {
|
|
76
81
|
const tsPath = resolve(subdirPath, "index.ts");
|
|
77
|
-
|
|
82
|
+
try {
|
|
83
|
+
await access(tsPath);
|
|
78
84
|
return tsPath;
|
|
85
|
+
} catch {
|
|
86
|
+
// no index.ts
|
|
79
87
|
}
|
|
80
88
|
const tsxPath = resolve(subdirPath, "index.tsx");
|
|
81
|
-
|
|
89
|
+
try {
|
|
90
|
+
await access(tsxPath);
|
|
82
91
|
return tsxPath;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
83
94
|
}
|
|
84
|
-
return null;
|
|
85
95
|
}
|
|
86
96
|
/** Minimum number of .ts/.tsx files in a directory to require a barrel. */
|
|
87
97
|
export const BARREL_THRESHOLD = 3;
|
|
@@ -89,7 +99,7 @@ export const BARREL_THRESHOLD = 3;
|
|
|
89
99
|
* Find directories with more than BARREL_THRESHOLD .ts/.tsx files
|
|
90
100
|
* that lack a barrel file (index.ts or index.tsx).
|
|
91
101
|
*/
|
|
92
|
-
export function findMissingBarrels(filePaths, cwd) {
|
|
102
|
+
export async function findMissingBarrels(filePaths, cwd) {
|
|
93
103
|
const dirCounts = new Map();
|
|
94
104
|
for (const filePath of filePaths) {
|
|
95
105
|
if (isBarrel(filePath)) continue;
|
|
@@ -97,14 +107,23 @@ export function findMissingBarrels(filePaths, cwd) {
|
|
|
97
107
|
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
98
108
|
}
|
|
99
109
|
const missing = [];
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
await Promise.all(
|
|
111
|
+
[...dirCounts.entries()].map(async ([dir, count]) => {
|
|
112
|
+
if (count <= BARREL_THRESHOLD) return;
|
|
113
|
+
const [tsExists, tsxExists] = await Promise.all([
|
|
114
|
+
access(join(dir, "index.ts")).then(
|
|
115
|
+
() => true,
|
|
116
|
+
() => false,
|
|
117
|
+
),
|
|
118
|
+
access(join(dir, "index.tsx")).then(
|
|
119
|
+
() => true,
|
|
120
|
+
() => false,
|
|
121
|
+
),
|
|
122
|
+
]);
|
|
123
|
+
if (!tsExists && !tsxExists) {
|
|
124
|
+
missing.push(relative(cwd, dir) || ".");
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
109
128
|
return missing.sort();
|
|
110
129
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { drilldown, drilldownFiles } from "./drilldown.js";
|
|
6
6
|
import { JsdocError } from "./errors.js";
|
|
7
7
|
import { lint, lintFiles } from "./lint.js";
|
|
8
|
+
import { search, searchFiles } from "./search.js";
|
|
8
9
|
import { parseSelector } from "./selector.js";
|
|
9
10
|
import { RULE_EXPLANATIONS, SKILL_TEXT } from "./skill-text.js";
|
|
11
|
+
import { formatTextOutput } from "./text-format.js";
|
|
10
12
|
import { DEFAULT_CACHE_DIR, VALIDATION_STATUS_PRIORITY } from "./types.js";
|
|
11
13
|
import { validate, validateFiles } from "./validate.js";
|
|
12
14
|
|
|
@@ -27,32 +29,47 @@ Each file has four detail levels (1-indexed): @1 summary, @2 description,
|
|
|
27
29
|
Options:
|
|
28
30
|
-h, --help Show this help text
|
|
29
31
|
-v, --version Show version number
|
|
30
|
-
-c, --check
|
|
31
|
-
-l, --lint
|
|
32
|
+
-c, --check Validate file-level structure (has JSDoc block, @summary, description)
|
|
33
|
+
-l, --lint Lint comprehensive JSDoc quality (file-level + function-level tags)
|
|
32
34
|
-s, --skill Print JSDoc writing guidelines
|
|
33
|
-
--
|
|
35
|
+
--json Output as JSON (default is plain text)
|
|
36
|
+
--pretty Format JSON output with 2-space indent (use with --json)
|
|
34
37
|
--limit N Max results shown (default 500)
|
|
35
38
|
--no-gitignore Include files ignored by .gitignore
|
|
39
|
+
--search <query> Search files by regex pattern
|
|
36
40
|
--disable-cache Skip all cache operations
|
|
37
41
|
--cache-directory Override cache directory (default: system temp)
|
|
38
42
|
--explain-rule R Explain a lint rule with examples (e.g. jsdoc/informative-docs)
|
|
39
43
|
|
|
40
44
|
Selector:
|
|
41
45
|
A glob pattern or file path, optionally with @depth suffix (1-4).
|
|
46
|
+
|
|
42
47
|
Examples:
|
|
43
48
|
jsdoczoom src/**/*.ts # All .ts files at depth 1 (summary)
|
|
44
|
-
jsdoczoom src/
|
|
49
|
+
jsdoczoom src/foo.ts@2 # Single file at depth 2 (description)
|
|
45
50
|
jsdoczoom **/*.ts@3 # All .ts files at depth 3 (type decls)
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
Search (--search):
|
|
53
|
+
Searches all **/*.{ts,tsx} files (or a selector's file set) by regex.
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
jsdoczoom --search "CacheConfig" # find where CacheConfig is used
|
|
57
|
+
jsdoczoom --search "auth-loader" # searches file name
|
|
58
|
+
jsdoczoom src/*.ts --search "TODO|FIXME" # restrict to a file subset
|
|
52
59
|
|
|
53
60
|
Output:
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
Plain text by default. Each item has a "# path@depth" header followed by
|
|
62
|
+
content. Use the header value as the next selector to drill deeper.
|
|
63
|
+
|
|
64
|
+
Use --json for machine-parseable JSON output, use "next_id" to drill deeper.
|
|
65
|
+
|
|
66
|
+
Type declarations (@3) include source line annotations (// LN or // LN-LM)
|
|
67
|
+
so you can locate the implementation in the source file.
|
|
68
|
+
|
|
69
|
+
Stdin:
|
|
70
|
+
Pipe file paths one per line (useful with -c/-l for targeted validation):
|
|
71
|
+
git diff --name-only | jsdoczoom -c # validate changed files
|
|
72
|
+
cat filelist.txt | jsdoczoom -l # lint a specific set of files
|
|
56
73
|
|
|
57
74
|
Barrel gating (glob mode):
|
|
58
75
|
A barrel's @summary and description reflect the cumulative functionality
|
|
@@ -60,25 +77,16 @@ Barrel gating (glob mode):
|
|
|
60
77
|
@summary gate sibling files at depths 1-2. At depth 3 the barrel
|
|
61
78
|
disappears and its children appear at depth 1.
|
|
62
79
|
|
|
63
|
-
Modes:
|
|
64
|
-
-c Validate file-level structure (has JSDoc block, @summary, description)
|
|
65
|
-
-l Lint comprehensive JSDoc quality (file-level + function-level tags)
|
|
66
|
-
|
|
67
80
|
Exit codes:
|
|
68
81
|
0 Success (all files pass)
|
|
69
82
|
1 Runtime error (invalid arguments, missing files)
|
|
70
83
|
2 Validation or lint failures found
|
|
71
84
|
|
|
72
85
|
Workflow:
|
|
73
|
-
$ jsdoczoom src/**/*.ts
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
$ jsdoczoom src
|
|
77
|
-
{ "items": [{ "next_id": "src/utils@3", "text": "..." }] }
|
|
78
|
-
|
|
79
|
-
$ jsdoczoom src/utils@3 # use next_id again
|
|
80
|
-
{ "items": [{ "id": "src/utils@3", "text": "..." }] }
|
|
81
|
-
# "id" instead of "next_id" means terminal depth — stop here
|
|
86
|
+
$ jsdoczoom src/**/*.ts # list summaries
|
|
87
|
+
$ jsdoczoom src/utils@2 # drill into description
|
|
88
|
+
$ jsdoczoom src/utils@3 # see type declarations
|
|
89
|
+
$ jsdoczoom src/**/*.ts | grep "^#" # list all file headers
|
|
82
90
|
`;
|
|
83
91
|
/**
|
|
84
92
|
* Parse a flag that requires a value argument.
|
|
@@ -97,6 +105,7 @@ function parseArgs(args) {
|
|
|
97
105
|
checkMode: false,
|
|
98
106
|
lintMode: false,
|
|
99
107
|
skillMode: false,
|
|
108
|
+
json: false,
|
|
100
109
|
pretty: false,
|
|
101
110
|
limit: 500,
|
|
102
111
|
gitignore: true,
|
|
@@ -104,6 +113,7 @@ function parseArgs(args) {
|
|
|
104
113
|
cacheDirectory: undefined,
|
|
105
114
|
explainRule: undefined,
|
|
106
115
|
selectorArg: undefined,
|
|
116
|
+
searchQuery: undefined,
|
|
107
117
|
};
|
|
108
118
|
for (let i = 0; i < args.length; i++) {
|
|
109
119
|
const arg = args[i];
|
|
@@ -128,6 +138,10 @@ function parseArgs(args) {
|
|
|
128
138
|
parsed.skillMode = true;
|
|
129
139
|
continue;
|
|
130
140
|
}
|
|
141
|
+
if (arg === "--json") {
|
|
142
|
+
parsed.json = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
131
145
|
if (arg === "--pretty") {
|
|
132
146
|
parsed.pretty = true;
|
|
133
147
|
continue;
|
|
@@ -159,6 +173,15 @@ function parseArgs(args) {
|
|
|
159
173
|
i = nextIndex;
|
|
160
174
|
continue;
|
|
161
175
|
}
|
|
176
|
+
if (arg === "--search") {
|
|
177
|
+
const { value, nextIndex } = parseValueFlag(args, i);
|
|
178
|
+
if (value === undefined) {
|
|
179
|
+
throw new JsdocError("INVALID_SELECTOR", "--search requires a value");
|
|
180
|
+
}
|
|
181
|
+
parsed.searchQuery = value;
|
|
182
|
+
i = nextIndex;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
162
185
|
// Unknown flag or positional arg
|
|
163
186
|
if (arg.startsWith("-")) {
|
|
164
187
|
throw new JsdocError("INVALID_SELECTOR", `Unrecognized option: ${arg}`);
|
|
@@ -196,14 +219,27 @@ async function processStdin(
|
|
|
196
219
|
selectorArg,
|
|
197
220
|
checkMode,
|
|
198
221
|
lintMode,
|
|
222
|
+
json,
|
|
199
223
|
pretty,
|
|
200
224
|
limit,
|
|
201
225
|
cwd,
|
|
202
226
|
cacheConfig,
|
|
227
|
+
searchQuery,
|
|
203
228
|
) {
|
|
204
229
|
const stdinPaths = parseStdinPaths(stdin, cwd);
|
|
205
230
|
const depth =
|
|
206
231
|
selectorArg !== undefined ? extractDepthFromArg(selectorArg) : undefined;
|
|
232
|
+
if (searchQuery !== undefined) {
|
|
233
|
+
const result = await searchFiles(
|
|
234
|
+
stdinPaths,
|
|
235
|
+
searchQuery,
|
|
236
|
+
cwd,
|
|
237
|
+
limit,
|
|
238
|
+
cacheConfig,
|
|
239
|
+
);
|
|
240
|
+
writeDrilldownResult(result, json, pretty);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
207
243
|
if (lintMode) {
|
|
208
244
|
const result = await lintFiles(stdinPaths, cwd, limit, cacheConfig);
|
|
209
245
|
writeLintResult(result, pretty);
|
|
@@ -218,7 +254,7 @@ async function processStdin(
|
|
|
218
254
|
limit,
|
|
219
255
|
cacheConfig,
|
|
220
256
|
);
|
|
221
|
-
|
|
257
|
+
writeDrilldownResult(result, json, pretty);
|
|
222
258
|
}
|
|
223
259
|
}
|
|
224
260
|
/**
|
|
@@ -228,15 +264,29 @@ async function processSelector(
|
|
|
228
264
|
selectorArg,
|
|
229
265
|
checkMode,
|
|
230
266
|
lintMode,
|
|
267
|
+
json,
|
|
231
268
|
pretty,
|
|
232
269
|
limit,
|
|
233
270
|
gitignore,
|
|
234
271
|
cwd,
|
|
235
272
|
cacheConfig,
|
|
273
|
+
searchQuery,
|
|
236
274
|
) {
|
|
237
275
|
const selector = selectorArg
|
|
238
276
|
? parseSelector(selectorArg)
|
|
239
277
|
: { type: "glob", pattern: "**/*.{ts,tsx}", depth: undefined };
|
|
278
|
+
if (searchQuery !== undefined) {
|
|
279
|
+
const result = await search(
|
|
280
|
+
{ type: selector.type, pattern: selector.pattern, depth: undefined },
|
|
281
|
+
searchQuery,
|
|
282
|
+
cwd,
|
|
283
|
+
gitignore,
|
|
284
|
+
limit,
|
|
285
|
+
cacheConfig,
|
|
286
|
+
);
|
|
287
|
+
writeDrilldownResult(result, json, pretty);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
240
290
|
if (lintMode) {
|
|
241
291
|
const result = await lint(selector, cwd, limit, gitignore, cacheConfig);
|
|
242
292
|
writeLintResult(result, pretty);
|
|
@@ -251,7 +301,7 @@ async function processSelector(
|
|
|
251
301
|
limit,
|
|
252
302
|
cacheConfig,
|
|
253
303
|
);
|
|
254
|
-
|
|
304
|
+
writeDrilldownResult(result, json, pretty);
|
|
255
305
|
}
|
|
256
306
|
}
|
|
257
307
|
/**
|
|
@@ -278,13 +328,13 @@ function handleHelp() {
|
|
|
278
328
|
/**
|
|
279
329
|
* Handle --version flag by reading and printing version from package.json.
|
|
280
330
|
*/
|
|
281
|
-
function handleVersion() {
|
|
331
|
+
async function handleVersion() {
|
|
282
332
|
const pkgPath = resolve(
|
|
283
333
|
dirname(fileURLToPath(import.meta.url)),
|
|
284
334
|
"..",
|
|
285
335
|
"package.json",
|
|
286
336
|
);
|
|
287
|
-
const pkg = JSON.parse(
|
|
337
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
288
338
|
void process.stdout.write(`${pkg.version}\n`);
|
|
289
339
|
}
|
|
290
340
|
/**
|
|
@@ -310,37 +360,59 @@ function handleExplainRule(ruleName) {
|
|
|
310
360
|
),
|
|
311
361
|
);
|
|
312
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Handle early-exit flags that print output and return without processing files.
|
|
365
|
+
* Returns true if an early-exit flag was handled.
|
|
366
|
+
*/
|
|
367
|
+
async function handleEarlyExitFlags(parsed) {
|
|
368
|
+
if (parsed.help) {
|
|
369
|
+
handleHelp();
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (parsed.version) {
|
|
373
|
+
await handleVersion();
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (parsed.skillMode) {
|
|
377
|
+
handleSkill();
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
if (parsed.explainRule !== undefined) {
|
|
381
|
+
handleExplainRule(parsed.explainRule);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Validate that mode flags are not used in incompatible combinations.
|
|
388
|
+
* Returns true if validation passed (no conflicts), false if an error was written.
|
|
389
|
+
*/
|
|
390
|
+
function validateModeCombinations(parsed) {
|
|
391
|
+
if (parsed.checkMode && parsed.lintMode) {
|
|
392
|
+
writeError(
|
|
393
|
+
new JsdocError("INVALID_SELECTOR", "Cannot use -c and -l together"),
|
|
394
|
+
);
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
if (
|
|
398
|
+
parsed.searchQuery !== undefined &&
|
|
399
|
+
(parsed.checkMode || parsed.lintMode)
|
|
400
|
+
) {
|
|
401
|
+
writeError(
|
|
402
|
+
new JsdocError("INVALID_SELECTOR", "Cannot use --search with -c or -l"),
|
|
403
|
+
);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
313
408
|
/**
|
|
314
409
|
* Main CLI entry point. Exported for testability.
|
|
315
410
|
*/
|
|
316
411
|
export async function main(args, stdin) {
|
|
317
412
|
try {
|
|
318
413
|
const parsed = parseArgs(args);
|
|
319
|
-
|
|
320
|
-
if (parsed
|
|
321
|
-
handleHelp();
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (parsed.version) {
|
|
325
|
-
handleVersion();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (parsed.skillMode) {
|
|
329
|
-
handleSkill();
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (parsed.explainRule !== undefined) {
|
|
333
|
-
handleExplainRule(parsed.explainRule);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
// Validate mode combinations
|
|
337
|
-
if (parsed.checkMode && parsed.lintMode) {
|
|
338
|
-
writeError(
|
|
339
|
-
new JsdocError("INVALID_SELECTOR", "Cannot use -c and -l together"),
|
|
340
|
-
);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
// Build cache config and process files
|
|
414
|
+
if (await handleEarlyExitFlags(parsed)) return;
|
|
415
|
+
if (!validateModeCombinations(parsed)) return;
|
|
344
416
|
const cacheConfig = {
|
|
345
417
|
enabled: !parsed.disableCache,
|
|
346
418
|
directory: parsed.cacheDirectory ?? DEFAULT_CACHE_DIR,
|
|
@@ -352,27 +424,41 @@ export async function main(args, stdin) {
|
|
|
352
424
|
parsed.selectorArg,
|
|
353
425
|
parsed.checkMode,
|
|
354
426
|
parsed.lintMode,
|
|
427
|
+
parsed.json,
|
|
355
428
|
parsed.pretty,
|
|
356
429
|
parsed.limit,
|
|
357
430
|
cwd,
|
|
358
431
|
cacheConfig,
|
|
432
|
+
parsed.searchQuery,
|
|
359
433
|
);
|
|
360
434
|
} else {
|
|
361
435
|
await processSelector(
|
|
362
436
|
parsed.selectorArg,
|
|
363
437
|
parsed.checkMode,
|
|
364
438
|
parsed.lintMode,
|
|
439
|
+
parsed.json,
|
|
365
440
|
parsed.pretty,
|
|
366
441
|
parsed.limit,
|
|
367
442
|
parsed.gitignore,
|
|
368
443
|
cwd,
|
|
369
444
|
cacheConfig,
|
|
445
|
+
parsed.searchQuery,
|
|
370
446
|
);
|
|
371
447
|
}
|
|
372
448
|
} catch (error) {
|
|
373
449
|
writeError(error);
|
|
374
450
|
}
|
|
375
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Write a drilldown result to stdout as text (default) or JSON.
|
|
454
|
+
*/
|
|
455
|
+
function writeDrilldownResult(result, json, pretty) {
|
|
456
|
+
if (json) {
|
|
457
|
+
writeResult(result, pretty);
|
|
458
|
+
} else {
|
|
459
|
+
void process.stdout.write(formatTextOutput(result));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
376
462
|
/**
|
|
377
463
|
* Write a result to stdout as JSON.
|
|
378
464
|
*/
|
package/dist/drilldown.js
CHANGED
|
@@ -32,8 +32,13 @@ function buildLevels(info, typeDeclarations, fileContent) {
|
|
|
32
32
|
return [
|
|
33
33
|
summary !== null ? { text: summary } : null,
|
|
34
34
|
description !== null ? { text: description } : null,
|
|
35
|
-
{
|
|
36
|
-
|
|
35
|
+
{
|
|
36
|
+
text:
|
|
37
|
+
typeDeclarations.length > 0
|
|
38
|
+
? `\`\`\`typescript\n${typeDeclarations}\n\`\`\``
|
|
39
|
+
: typeDeclarations,
|
|
40
|
+
},
|
|
41
|
+
{ text: `\`\`\`typescript\n${fileContent}\n\`\`\`` },
|
|
37
42
|
];
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
@@ -177,7 +182,7 @@ async function gatherBarrelInfos(barrelPaths, cwd, config) {
|
|
|
177
182
|
const info = await processWithCache(config, "drilldown", content, () =>
|
|
178
183
|
parseFileSummaries(barrelPath),
|
|
179
184
|
);
|
|
180
|
-
const children = getBarrelChildren(barrelPath, cwd);
|
|
185
|
+
const children = await getBarrelChildren(barrelPath, cwd);
|
|
181
186
|
return {
|
|
182
187
|
type: "info",
|
|
183
188
|
data: {
|
|
@@ -328,18 +333,19 @@ export async function drilldown(
|
|
|
328
333
|
) {
|
|
329
334
|
const depth = selector.depth ?? 1;
|
|
330
335
|
if (selector.type === "path") {
|
|
331
|
-
const files = discoverFiles(selector.pattern, cwd, gitignore);
|
|
336
|
+
const files = await discoverFiles(selector.pattern, cwd, gitignore);
|
|
332
337
|
if (files.length > 1) {
|
|
333
338
|
// Directory expanded to multiple files — route through glob pipeline
|
|
334
339
|
const results = await processGlobWithBarrels(files, depth, cwd, config);
|
|
335
340
|
const sorted = results.sort((a, b) =>
|
|
336
341
|
sortKey(a).localeCompare(sortKey(b)),
|
|
337
342
|
);
|
|
338
|
-
const
|
|
339
|
-
const
|
|
343
|
+
const count = sorted.length;
|
|
344
|
+
const isTruncated = count > limit;
|
|
340
345
|
return {
|
|
341
346
|
items: sorted.slice(0, limit),
|
|
342
|
-
truncated,
|
|
347
|
+
truncated: isTruncated,
|
|
348
|
+
...(isTruncated ? { total: count } : {}),
|
|
343
349
|
};
|
|
344
350
|
}
|
|
345
351
|
// Single file path — errors are fatal
|
|
@@ -357,7 +363,7 @@ export async function drilldown(
|
|
|
357
363
|
barrelEffectiveDepth = 3;
|
|
358
364
|
}
|
|
359
365
|
if (barrelEffectiveDepth >= 3) {
|
|
360
|
-
const children = getBarrelChildren(filePath, cwd);
|
|
366
|
+
const children = await getBarrelChildren(filePath, cwd);
|
|
361
367
|
const childDepth = barrelEffectiveDepth - 2;
|
|
362
368
|
const results = await collectSafeResults(
|
|
363
369
|
children,
|
|
@@ -384,7 +390,7 @@ export async function drilldown(
|
|
|
384
390
|
return { items, truncated: false };
|
|
385
391
|
}
|
|
386
392
|
// Glob selector — apply barrel gating
|
|
387
|
-
const files = discoverFiles(selector.pattern, cwd, gitignore);
|
|
393
|
+
const files = await discoverFiles(selector.pattern, cwd, gitignore);
|
|
388
394
|
if (files.length === 0) {
|
|
389
395
|
throw new JsdocError(
|
|
390
396
|
"NO_FILES_MATCHED",
|
|
@@ -394,11 +400,12 @@ export async function drilldown(
|
|
|
394
400
|
const results = await processGlobWithBarrels(files, depth, cwd, config);
|
|
395
401
|
// Sort alphabetically by key
|
|
396
402
|
const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
397
|
-
const
|
|
398
|
-
const
|
|
403
|
+
const count = sorted.length;
|
|
404
|
+
const isTruncated = count > limit;
|
|
399
405
|
return {
|
|
400
406
|
items: sorted.slice(0, limit),
|
|
401
|
-
truncated,
|
|
407
|
+
truncated: isTruncated,
|
|
408
|
+
...(isTruncated ? { total: count } : {}),
|
|
402
409
|
};
|
|
403
410
|
}
|
|
404
411
|
/**
|
|
@@ -420,7 +427,7 @@ export async function drilldownFiles(
|
|
|
420
427
|
config = DEFAULT_CACHE_CONFIG,
|
|
421
428
|
) {
|
|
422
429
|
const d = depth ?? 1;
|
|
423
|
-
const ig = loadGitignore(cwd);
|
|
430
|
+
const ig = await loadGitignore(cwd);
|
|
424
431
|
const tsFiles = filePaths.filter((f) => {
|
|
425
432
|
if (!(f.endsWith(".ts") || f.endsWith(".tsx")) || f.endsWith(".d.ts")) {
|
|
426
433
|
return false;
|
|
@@ -432,10 +439,11 @@ export async function drilldownFiles(
|
|
|
432
439
|
});
|
|
433
440
|
const results = await collectSafeResults(tsFiles, d, cwd, config);
|
|
434
441
|
const sorted = results.sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
435
|
-
const
|
|
436
|
-
const
|
|
442
|
+
const count = sorted.length;
|
|
443
|
+
const isTruncated = count > limit;
|
|
437
444
|
return {
|
|
438
445
|
items: sorted.slice(0, limit),
|
|
439
|
-
truncated,
|
|
446
|
+
truncated: isTruncated,
|
|
447
|
+
...(isTruncated ? { total: count } : {}),
|
|
440
448
|
};
|
|
441
449
|
}
|
package/dist/file-discovery.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { glob } from "glob";
|
|
4
4
|
import ignore from "ignore";
|
|
5
5
|
import { JsdocError } from "./errors.js";
|
|
6
6
|
/**
|
|
@@ -15,13 +15,13 @@ import { JsdocError } from "./errors.js";
|
|
|
15
15
|
* Walk from `cwd` up to the filesystem root, collecting .gitignore entries.
|
|
16
16
|
* Returns an Ignore instance loaded with all discovered rules.
|
|
17
17
|
*/
|
|
18
|
-
export function loadGitignore(cwd) {
|
|
18
|
+
export async function loadGitignore(cwd) {
|
|
19
19
|
const ig = ignore();
|
|
20
20
|
let dir = resolve(cwd);
|
|
21
21
|
while (true) {
|
|
22
22
|
const gitignorePath = join(dir, ".gitignore");
|
|
23
|
-
|
|
24
|
-
const content =
|
|
23
|
+
try {
|
|
24
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
25
25
|
const prefix = relative(cwd, dir);
|
|
26
26
|
const lines = content
|
|
27
27
|
.split("\n")
|
|
@@ -32,6 +32,8 @@ export function loadGitignore(cwd) {
|
|
|
32
32
|
// relative to `cwd`, which is where glob results are anchored.
|
|
33
33
|
ig.add(prefix ? `${prefix}/${line}` : line);
|
|
34
34
|
}
|
|
35
|
+
} catch {
|
|
36
|
+
// No .gitignore at this level, continue walking up
|
|
35
37
|
}
|
|
36
38
|
const parent = dirname(dir);
|
|
37
39
|
if (parent === dir) break;
|
|
@@ -52,25 +54,28 @@ export function loadGitignore(cwd) {
|
|
|
52
54
|
* @returns Array of absolute file paths
|
|
53
55
|
* @throws {JsdocError} FILE_NOT_FOUND when a direct path does not exist
|
|
54
56
|
*/
|
|
55
|
-
export function discoverFiles(pattern, cwd, gitignore = true) {
|
|
57
|
+
export async function discoverFiles(pattern, cwd, gitignore = true) {
|
|
56
58
|
const hasGlobChars = /[*?[\]{]/.test(pattern);
|
|
57
59
|
if (hasGlobChars) {
|
|
58
|
-
const matches =
|
|
60
|
+
const matches = await glob(pattern, { cwd, absolute: true });
|
|
59
61
|
let filtered = matches.filter(
|
|
60
62
|
(f) => (f.endsWith(".ts") || f.endsWith(".tsx")) && !f.endsWith(".d.ts"),
|
|
61
63
|
);
|
|
62
64
|
if (gitignore) {
|
|
63
|
-
const ig = loadGitignore(cwd);
|
|
65
|
+
const ig = await loadGitignore(cwd);
|
|
64
66
|
filtered = filtered.filter((abs) => !ig.ignores(relative(cwd, abs)));
|
|
65
67
|
}
|
|
66
68
|
return filtered.sort();
|
|
67
69
|
}
|
|
68
70
|
// Direct path
|
|
69
71
|
const resolved = resolve(cwd, pattern);
|
|
70
|
-
|
|
72
|
+
let statResult;
|
|
73
|
+
try {
|
|
74
|
+
statResult = await stat(resolved);
|
|
75
|
+
} catch {
|
|
71
76
|
throw new JsdocError("FILE_NOT_FOUND", `File not found: ${pattern}`);
|
|
72
77
|
}
|
|
73
|
-
if (
|
|
78
|
+
if (statResult.isDirectory()) {
|
|
74
79
|
return discoverFiles(`${resolved}/**`, cwd, gitignore);
|
|
75
80
|
}
|
|
76
81
|
return [resolved];
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,13 @@ export { JsdocError } from "./errors.js";
|
|
|
13
13
|
export { discoverFiles } from "./file-discovery.js";
|
|
14
14
|
export { extractFileJsdoc, parseFileSummaries } from "./jsdoc-parser.js";
|
|
15
15
|
export { lint, lintFiles } from "./lint.js";
|
|
16
|
+
export { search, searchFiles } from "./search.js";
|
|
16
17
|
export { parseSelector } from "./selector.js";
|
|
17
|
-
export {
|
|
18
|
+
export { formatTextOutput } from "./text-format.js";
|
|
19
|
+
export {
|
|
20
|
+
extractSourceBlocks,
|
|
21
|
+
generateTypeDeclarations,
|
|
22
|
+
splitDeclarations,
|
|
23
|
+
} from "./type-declarations.js";
|
|
18
24
|
export { DEFAULT_CACHE_DIR, VALIDATION_STATUS_PRIORITY } from "./types.js";
|
|
19
25
|
export { validate, validateFiles } from "./validate.js";
|
package/dist/jsdoc-parser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import { JsdocError } from "./errors.js";
|
|
4
4
|
import { appendText, DESCRIPTION_TAGS } from "./types.js";
|
|
@@ -220,10 +220,10 @@ function parseJsdocContent(jsdocText) {
|
|
|
220
220
|
* Reads the file, extracts the first file-level JSDoc block, and parses the first
|
|
221
221
|
* @summary tag and free-text description.
|
|
222
222
|
*/
|
|
223
|
-
export function parseFileSummaries(filePath) {
|
|
223
|
+
export async function parseFileSummaries(filePath) {
|
|
224
224
|
let sourceText;
|
|
225
225
|
try {
|
|
226
|
-
sourceText =
|
|
226
|
+
sourceText = await readFile(filePath, "utf-8");
|
|
227
227
|
} catch {
|
|
228
228
|
throw new JsdocError("FILE_NOT_FOUND", `File not found: ${filePath}`);
|
|
229
229
|
}
|