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/barrel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, resolve } from "node:path";
|
|
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.
|
|
5
5
|
* A barrel is strictly `index.ts` or `index.tsx`; other index variants
|
|
@@ -83,3 +83,28 @@ function findChildBarrel(subdirPath) {
|
|
|
83
83
|
}
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
|
+
/** Minimum number of .ts/.tsx files in a directory to require a barrel. */
|
|
87
|
+
export const BARREL_THRESHOLD = 3;
|
|
88
|
+
/**
|
|
89
|
+
* Find directories with more than BARREL_THRESHOLD .ts/.tsx files
|
|
90
|
+
* that lack a barrel file (index.ts or index.tsx).
|
|
91
|
+
*/
|
|
92
|
+
export function findMissingBarrels(filePaths, cwd) {
|
|
93
|
+
const dirCounts = new Map();
|
|
94
|
+
for (const filePath of filePaths) {
|
|
95
|
+
if (isBarrel(filePath)) continue;
|
|
96
|
+
const dir = dirname(filePath);
|
|
97
|
+
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
const missing = [];
|
|
100
|
+
for (const [dir, count] of dirCounts) {
|
|
101
|
+
if (count <= BARREL_THRESHOLD) continue;
|
|
102
|
+
const hasBarrel =
|
|
103
|
+
existsSync(join(dir, "index.ts")) || existsSync(join(dir, "index.tsx"));
|
|
104
|
+
if (!hasBarrel) {
|
|
105
|
+
const rel = relative(cwd, dir) || ".";
|
|
106
|
+
missing.push(rel);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return missing.sort();
|
|
110
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-hash-based disk caching layer for expensive operations (TypeScript
|
|
3
|
+
* compilation, ESLint linting, JSDoc parsing). Uses SHA-256 hashing to cache
|
|
4
|
+
* results keyed by file content, enabling near-instantaneous repeated runs
|
|
5
|
+
* when files haven't changed. All cache errors degrade gracefully without
|
|
6
|
+
* propagating to callers.
|
|
7
|
+
*
|
|
8
|
+
* @summary Content-hash-based disk cache with graceful error handling
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
/**
|
|
14
|
+
* Compute a SHA-256 content hash for the given string.
|
|
15
|
+
*
|
|
16
|
+
* @param content - The content to hash
|
|
17
|
+
* @returns Hex-encoded SHA-256 digest
|
|
18
|
+
*/
|
|
19
|
+
export function computeContentHash(content) {
|
|
20
|
+
return createHash("sha256").update(content).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Ensure the cache directory exists for the given operation mode.
|
|
24
|
+
* Silently swallows all errors (mkdir failures are handled by read/write ops).
|
|
25
|
+
*
|
|
26
|
+
* @param config - Cache configuration
|
|
27
|
+
* @param mode - Operation mode (drilldown, validate, or lint)
|
|
28
|
+
*/
|
|
29
|
+
export async function ensureCacheDir(config, mode) {
|
|
30
|
+
try {
|
|
31
|
+
await mkdir(join(config.directory, mode), { recursive: true });
|
|
32
|
+
} catch {
|
|
33
|
+
// Silently swallow - subsequent reads will miss, writes will fail silently
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read a cached entry from disk. Returns null on any error (cache miss, ENOENT,
|
|
38
|
+
* corrupted JSON, permission denied, etc.). Never throws.
|
|
39
|
+
*
|
|
40
|
+
* @param config - Cache configuration
|
|
41
|
+
* @param mode - Operation mode namespace
|
|
42
|
+
* @param hash - Content hash key
|
|
43
|
+
* @returns Parsed cached data or null on any error
|
|
44
|
+
*/
|
|
45
|
+
export async function readCacheEntry(config, mode, hash) {
|
|
46
|
+
try {
|
|
47
|
+
const filePath = join(config.directory, mode, `${hash}.json`);
|
|
48
|
+
const content = await readFile(filePath, "utf-8");
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Write a cache entry to disk using atomic write (write to .tmp then rename).
|
|
56
|
+
* Silently swallows all errors (permission denied, disk full, etc.). Never throws.
|
|
57
|
+
*
|
|
58
|
+
* @param config - Cache configuration
|
|
59
|
+
* @param mode - Operation mode namespace
|
|
60
|
+
* @param hash - Content hash key
|
|
61
|
+
* @param data - Data to cache (must be JSON-serializable)
|
|
62
|
+
*/
|
|
63
|
+
export async function writeCacheEntry(config, mode, hash, data) {
|
|
64
|
+
try {
|
|
65
|
+
const filePath = join(config.directory, mode, `${hash}.json`);
|
|
66
|
+
const tmpPath = `${filePath}.tmp`;
|
|
67
|
+
await writeFile(tmpPath, JSON.stringify(data), "utf-8");
|
|
68
|
+
await rename(tmpPath, filePath);
|
|
69
|
+
} catch {
|
|
70
|
+
// Silently swallow - cache write failures should never affect correctness
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Execute a computation with caching. If cache is enabled and entry exists,
|
|
75
|
+
* returns cached result. Otherwise, computes result, stores it (fire-and-forget),
|
|
76
|
+
* and returns it. Degrades gracefully on all cache errors by always computing.
|
|
77
|
+
*
|
|
78
|
+
* @param config - Cache configuration
|
|
79
|
+
* @param mode - Operation mode namespace
|
|
80
|
+
* @param content - File content to hash for cache key
|
|
81
|
+
* @param compute - Function to compute the result on cache miss
|
|
82
|
+
* @returns Cached or computed result
|
|
83
|
+
*/
|
|
84
|
+
export async function processWithCache(config, mode, content, compute) {
|
|
85
|
+
if (!config.enabled) {
|
|
86
|
+
return await compute();
|
|
87
|
+
}
|
|
88
|
+
const hash = computeContentHash(content);
|
|
89
|
+
await ensureCacheDir(config, mode);
|
|
90
|
+
const cached = await readCacheEntry(config, mode, hash);
|
|
91
|
+
if (cached !== null) {
|
|
92
|
+
return cached;
|
|
93
|
+
}
|
|
94
|
+
const result = await compute();
|
|
95
|
+
await writeCacheEntry(config, mode, hash, result);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { drilldown, drilldownFiles } from "./drilldown.js";
|
|
4
6
|
import { JsdocError } from "./errors.js";
|
|
5
|
-
import { lint } from "./lint.js";
|
|
7
|
+
import { lint, lintFiles } from "./lint.js";
|
|
6
8
|
import { parseSelector } from "./selector.js";
|
|
7
9
|
import { RULE_EXPLANATIONS, SKILL_TEXT } from "./skill-text.js";
|
|
8
|
-
import { VALIDATION_STATUS_PRIORITY } from "./types.js";
|
|
9
|
-
import { validate } from "./validate.js";
|
|
10
|
+
import { DEFAULT_CACHE_DIR, VALIDATION_STATUS_PRIORITY } from "./types.js";
|
|
11
|
+
import { validate, validateFiles } from "./validate.js";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
* Parses argv flags (--help, --
|
|
13
|
-
* --no-gitignore), dispatches to drilldown, validation, or lint mode,
|
|
14
|
-
* handles stdin piping. Errors are written to stderr as JSON; validation
|
|
15
|
-
* lint failures use exit code 2 while other errors use exit code 1.
|
|
14
|
+
* Parses argv flags (--help, --version, --check, --lint, --skill, --pretty,
|
|
15
|
+
* --limit, --no-gitignore), dispatches to drilldown, validation, or lint mode,
|
|
16
|
+
* and handles stdin piping. Errors are written to stderr as JSON; validation
|
|
17
|
+
* and lint failures use exit code 2 while other errors use exit code 1.
|
|
16
18
|
*
|
|
17
19
|
* @summary CLI entry point -- argument parsing, mode dispatch, and exit code handling
|
|
18
20
|
*/
|
|
@@ -24,12 +26,15 @@ Each file has four detail levels (1-indexed): @1 summary, @2 description,
|
|
|
24
26
|
|
|
25
27
|
Options:
|
|
26
28
|
-h, --help Show this help text
|
|
27
|
-
-v, --
|
|
29
|
+
-v, --version Show version number
|
|
30
|
+
-c, --check Run validation mode
|
|
28
31
|
-l, --lint Run lint mode (comprehensive JSDoc quality)
|
|
29
32
|
-s, --skill Print JSDoc writing guidelines
|
|
30
33
|
--pretty Format JSON output with 2-space indent
|
|
31
34
|
--limit N Max results shown (default 500)
|
|
32
35
|
--no-gitignore Include files ignored by .gitignore
|
|
36
|
+
--disable-cache Skip all cache operations
|
|
37
|
+
--cache-directory Override cache directory (default: system temp)
|
|
33
38
|
--explain-rule R Explain a lint rule with examples (e.g. jsdoc/informative-docs)
|
|
34
39
|
|
|
35
40
|
Selector:
|
|
@@ -43,18 +48,20 @@ Stdin:
|
|
|
43
48
|
Pipe file paths one per line:
|
|
44
49
|
find . -name "*.ts" | jsdoczoom
|
|
45
50
|
find . -name "*.ts" | jsdoczoom @2
|
|
46
|
-
find . -name "*.ts" | jsdoczoom -
|
|
51
|
+
find . -name "*.ts" | jsdoczoom -c
|
|
47
52
|
|
|
48
53
|
Output:
|
|
49
54
|
JSON items. Items with "next_id" have more detail; use that value as the next
|
|
50
55
|
selector. Items with "id" (no next_id) are at terminal depth.
|
|
51
56
|
|
|
52
57
|
Barrel gating (glob mode):
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
A barrel's @summary and description reflect the cumulative functionality
|
|
59
|
+
of its directory's children, not the barrel file itself. Barrels with a
|
|
60
|
+
@summary gate sibling files at depths 1-2. At depth 3 the barrel
|
|
61
|
+
disappears and its children appear at depth 1.
|
|
55
62
|
|
|
56
63
|
Modes:
|
|
57
|
-
-
|
|
64
|
+
-c Validate file-level structure (has JSDoc block, @summary, description)
|
|
58
65
|
-l Lint comprehensive JSDoc quality (file-level + function-level tags)
|
|
59
66
|
|
|
60
67
|
Exit codes:
|
|
@@ -73,56 +80,95 @@ Workflow:
|
|
|
73
80
|
{ "items": [{ "id": "src/utils@3", "text": "..." }] }
|
|
74
81
|
# "id" instead of "next_id" means terminal depth — stop here
|
|
75
82
|
`;
|
|
83
|
+
/**
|
|
84
|
+
* Parse a flag that requires a value argument.
|
|
85
|
+
* @returns Updated index after consuming the value
|
|
86
|
+
*/
|
|
87
|
+
function parseValueFlag(args, index) {
|
|
88
|
+
return { value: args[index + 1], nextIndex: index + 1 };
|
|
89
|
+
}
|
|
76
90
|
/**
|
|
77
91
|
* Parse CLI arguments into flags and positional args.
|
|
78
92
|
*/
|
|
79
93
|
function parseArgs(args) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
const parsed = {
|
|
95
|
+
help: false,
|
|
96
|
+
version: false,
|
|
97
|
+
checkMode: false,
|
|
98
|
+
lintMode: false,
|
|
99
|
+
skillMode: false,
|
|
100
|
+
pretty: false,
|
|
101
|
+
limit: 500,
|
|
102
|
+
gitignore: true,
|
|
103
|
+
disableCache: false,
|
|
104
|
+
cacheDirectory: undefined,
|
|
105
|
+
explainRule: undefined,
|
|
106
|
+
selectorArg: undefined,
|
|
107
|
+
};
|
|
89
108
|
for (let i = 0; i < args.length; i++) {
|
|
90
109
|
const arg = args[i];
|
|
110
|
+
// Boolean flags
|
|
91
111
|
if (arg === "-h" || arg === "--help") {
|
|
92
|
-
help = true;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
parsed.help = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (arg === "-v" || arg === "--version") {
|
|
116
|
+
parsed.version = true;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (arg === "-c" || arg === "--check") {
|
|
120
|
+
parsed.checkMode = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (arg === "-l" || arg === "--lint") {
|
|
124
|
+
parsed.lintMode = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (arg === "-s" || arg === "--skill") {
|
|
128
|
+
parsed.skillMode = true;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (arg === "--pretty") {
|
|
132
|
+
parsed.pretty = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (arg === "--no-gitignore") {
|
|
136
|
+
parsed.gitignore = false;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (arg === "--disable-cache") {
|
|
140
|
+
parsed.disableCache = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Value flags
|
|
144
|
+
if (arg === "--limit") {
|
|
145
|
+
const { value, nextIndex } = parseValueFlag(args, i);
|
|
146
|
+
parsed.limit = Number(value);
|
|
147
|
+
i = nextIndex;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (arg === "--cache-directory") {
|
|
151
|
+
const { value, nextIndex } = parseValueFlag(args, i);
|
|
152
|
+
parsed.cacheDirectory = value;
|
|
153
|
+
i = nextIndex;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (arg === "--explain-rule") {
|
|
157
|
+
const { value, nextIndex } = parseValueFlag(args, i);
|
|
158
|
+
parsed.explainRule = value;
|
|
159
|
+
i = nextIndex;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Unknown flag or positional arg
|
|
163
|
+
if (arg.startsWith("-")) {
|
|
110
164
|
throw new JsdocError("INVALID_SELECTOR", `Unrecognized option: ${arg}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
165
|
+
}
|
|
166
|
+
// Positional selector arg
|
|
167
|
+
if (parsed.selectorArg === undefined) {
|
|
168
|
+
parsed.selectorArg = arg;
|
|
113
169
|
}
|
|
114
170
|
}
|
|
115
|
-
return
|
|
116
|
-
help,
|
|
117
|
-
validateMode,
|
|
118
|
-
lintMode,
|
|
119
|
-
skillMode,
|
|
120
|
-
pretty,
|
|
121
|
-
limit,
|
|
122
|
-
gitignore,
|
|
123
|
-
explainRule,
|
|
124
|
-
selectorArg,
|
|
125
|
-
};
|
|
171
|
+
return parsed;
|
|
126
172
|
}
|
|
127
173
|
/**
|
|
128
174
|
* Resolve stdin file paths to absolute paths.
|
|
@@ -148,25 +194,30 @@ function extractDepthFromArg(selectorArg) {
|
|
|
148
194
|
async function processStdin(
|
|
149
195
|
stdin,
|
|
150
196
|
selectorArg,
|
|
151
|
-
|
|
197
|
+
checkMode,
|
|
152
198
|
lintMode,
|
|
153
199
|
pretty,
|
|
154
200
|
limit,
|
|
155
201
|
cwd,
|
|
202
|
+
cacheConfig,
|
|
156
203
|
) {
|
|
157
204
|
const stdinPaths = parseStdinPaths(stdin, cwd);
|
|
158
205
|
const depth =
|
|
159
206
|
selectorArg !== undefined ? extractDepthFromArg(selectorArg) : undefined;
|
|
160
207
|
if (lintMode) {
|
|
161
|
-
const
|
|
162
|
-
const result = await lintFiles(stdinPaths, cwd, limit);
|
|
208
|
+
const result = await lintFiles(stdinPaths, cwd, limit, cacheConfig);
|
|
163
209
|
writeLintResult(result, pretty);
|
|
164
|
-
} else if (
|
|
165
|
-
const
|
|
166
|
-
const result = await validateFiles(stdinPaths, cwd, limit);
|
|
210
|
+
} else if (checkMode) {
|
|
211
|
+
const result = await validateFiles(stdinPaths, cwd, limit, cacheConfig);
|
|
167
212
|
writeValidationResult(result, pretty);
|
|
168
213
|
} else {
|
|
169
|
-
const result = drilldownFiles(
|
|
214
|
+
const result = await drilldownFiles(
|
|
215
|
+
stdinPaths,
|
|
216
|
+
depth,
|
|
217
|
+
cwd,
|
|
218
|
+
limit,
|
|
219
|
+
cacheConfig,
|
|
220
|
+
);
|
|
170
221
|
writeResult(result, pretty);
|
|
171
222
|
}
|
|
172
223
|
}
|
|
@@ -175,24 +226,31 @@ async function processStdin(
|
|
|
175
226
|
*/
|
|
176
227
|
async function processSelector(
|
|
177
228
|
selectorArg,
|
|
178
|
-
|
|
229
|
+
checkMode,
|
|
179
230
|
lintMode,
|
|
180
231
|
pretty,
|
|
181
232
|
limit,
|
|
182
233
|
gitignore,
|
|
183
234
|
cwd,
|
|
235
|
+
cacheConfig,
|
|
184
236
|
) {
|
|
185
237
|
const selector = selectorArg
|
|
186
238
|
? parseSelector(selectorArg)
|
|
187
239
|
: { type: "glob", pattern: "**/*.{ts,tsx}", depth: undefined };
|
|
188
240
|
if (lintMode) {
|
|
189
|
-
const result = await lint(selector, cwd, limit, gitignore);
|
|
241
|
+
const result = await lint(selector, cwd, limit, gitignore, cacheConfig);
|
|
190
242
|
writeLintResult(result, pretty);
|
|
191
|
-
} else if (
|
|
192
|
-
const result = await validate(selector, cwd, limit, gitignore);
|
|
243
|
+
} else if (checkMode) {
|
|
244
|
+
const result = await validate(selector, cwd, limit, gitignore, cacheConfig);
|
|
193
245
|
writeValidationResult(result, pretty);
|
|
194
246
|
} else {
|
|
195
|
-
const result = drilldown(
|
|
247
|
+
const result = await drilldown(
|
|
248
|
+
selector,
|
|
249
|
+
cwd,
|
|
250
|
+
gitignore,
|
|
251
|
+
limit,
|
|
252
|
+
cacheConfig,
|
|
253
|
+
);
|
|
196
254
|
writeResult(result, pretty);
|
|
197
255
|
}
|
|
198
256
|
}
|
|
@@ -201,81 +259,114 @@ async function processSelector(
|
|
|
201
259
|
*/
|
|
202
260
|
function writeError(error) {
|
|
203
261
|
if (error instanceof JsdocError) {
|
|
204
|
-
process.stderr.write(`${JSON.stringify(error.toJSON())}\n`);
|
|
262
|
+
void process.stderr.write(`${JSON.stringify(error.toJSON())}\n`);
|
|
205
263
|
process.exitCode = 1;
|
|
206
264
|
return;
|
|
207
265
|
}
|
|
208
266
|
const message = error instanceof Error ? error.message : String(error);
|
|
209
|
-
process.stderr.write(
|
|
267
|
+
void process.stderr.write(
|
|
210
268
|
`${JSON.stringify({ error: { code: "INTERNAL_ERROR", message } })}\n`,
|
|
211
269
|
);
|
|
212
270
|
process.exitCode = 1;
|
|
213
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Handle --help flag by printing help text.
|
|
274
|
+
*/
|
|
275
|
+
function handleHelp() {
|
|
276
|
+
void process.stdout.write(HELP_TEXT);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Handle --version flag by reading and printing version from package.json.
|
|
280
|
+
*/
|
|
281
|
+
function handleVersion() {
|
|
282
|
+
const pkgPath = resolve(
|
|
283
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
284
|
+
"..",
|
|
285
|
+
"package.json",
|
|
286
|
+
);
|
|
287
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
288
|
+
void process.stdout.write(`${pkg.version}\n`);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Handle --skill flag by printing JSDoc writing guidelines.
|
|
292
|
+
*/
|
|
293
|
+
function handleSkill() {
|
|
294
|
+
void process.stdout.write(SKILL_TEXT);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Handle --explain-rule flag by printing rule explanation.
|
|
298
|
+
*/
|
|
299
|
+
function handleExplainRule(ruleName) {
|
|
300
|
+
const explanation = RULE_EXPLANATIONS[ruleName];
|
|
301
|
+
if (explanation) {
|
|
302
|
+
void process.stdout.write(explanation);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const available = Object.keys(RULE_EXPLANATIONS).join(", ");
|
|
306
|
+
writeError(
|
|
307
|
+
new JsdocError(
|
|
308
|
+
"INVALID_SELECTOR",
|
|
309
|
+
`Unknown rule: ${ruleName}. Available rules: ${available}`,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
214
313
|
/**
|
|
215
314
|
* Main CLI entry point. Exported for testability.
|
|
216
315
|
*/
|
|
217
316
|
export async function main(args, stdin) {
|
|
218
317
|
try {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
explainRule,
|
|
228
|
-
selectorArg,
|
|
229
|
-
} = parseArgs(args);
|
|
230
|
-
if (help) {
|
|
231
|
-
process.stdout.write(HELP_TEXT);
|
|
318
|
+
const parsed = parseArgs(args);
|
|
319
|
+
// Handle early-exit flags
|
|
320
|
+
if (parsed.help) {
|
|
321
|
+
handleHelp();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (parsed.version) {
|
|
325
|
+
handleVersion();
|
|
232
326
|
return;
|
|
233
327
|
}
|
|
234
|
-
if (skillMode) {
|
|
235
|
-
|
|
328
|
+
if (parsed.skillMode) {
|
|
329
|
+
handleSkill();
|
|
236
330
|
return;
|
|
237
331
|
}
|
|
238
|
-
if (explainRule !== undefined) {
|
|
239
|
-
|
|
240
|
-
if (explanation) {
|
|
241
|
-
process.stdout.write(explanation);
|
|
242
|
-
} else {
|
|
243
|
-
const available = Object.keys(RULE_EXPLANATIONS).join(", ");
|
|
244
|
-
writeError(
|
|
245
|
-
new JsdocError(
|
|
246
|
-
"INVALID_SELECTOR",
|
|
247
|
-
`Unknown rule: ${explainRule}. Available rules: ${available}`,
|
|
248
|
-
),
|
|
249
|
-
);
|
|
250
|
-
}
|
|
332
|
+
if (parsed.explainRule !== undefined) {
|
|
333
|
+
handleExplainRule(parsed.explainRule);
|
|
251
334
|
return;
|
|
252
335
|
}
|
|
253
|
-
|
|
336
|
+
// Validate mode combinations
|
|
337
|
+
if (parsed.checkMode && parsed.lintMode) {
|
|
254
338
|
writeError(
|
|
255
|
-
new JsdocError("INVALID_SELECTOR", "Cannot use -
|
|
339
|
+
new JsdocError("INVALID_SELECTOR", "Cannot use -c and -l together"),
|
|
256
340
|
);
|
|
257
341
|
return;
|
|
258
342
|
}
|
|
343
|
+
// Build cache config and process files
|
|
344
|
+
const cacheConfig = {
|
|
345
|
+
enabled: !parsed.disableCache,
|
|
346
|
+
directory: parsed.cacheDirectory ?? DEFAULT_CACHE_DIR,
|
|
347
|
+
};
|
|
259
348
|
const cwd = process.cwd();
|
|
260
349
|
if (stdin !== undefined) {
|
|
261
350
|
await processStdin(
|
|
262
351
|
stdin,
|
|
263
|
-
selectorArg,
|
|
264
|
-
|
|
265
|
-
lintMode,
|
|
266
|
-
pretty,
|
|
267
|
-
limit,
|
|
352
|
+
parsed.selectorArg,
|
|
353
|
+
parsed.checkMode,
|
|
354
|
+
parsed.lintMode,
|
|
355
|
+
parsed.pretty,
|
|
356
|
+
parsed.limit,
|
|
268
357
|
cwd,
|
|
358
|
+
cacheConfig,
|
|
269
359
|
);
|
|
270
360
|
} else {
|
|
271
361
|
await processSelector(
|
|
272
|
-
selectorArg,
|
|
273
|
-
|
|
274
|
-
lintMode,
|
|
275
|
-
pretty,
|
|
276
|
-
limit,
|
|
277
|
-
gitignore,
|
|
362
|
+
parsed.selectorArg,
|
|
363
|
+
parsed.checkMode,
|
|
364
|
+
parsed.lintMode,
|
|
365
|
+
parsed.pretty,
|
|
366
|
+
parsed.limit,
|
|
367
|
+
parsed.gitignore,
|
|
278
368
|
cwd,
|
|
369
|
+
cacheConfig,
|
|
279
370
|
);
|
|
280
371
|
}
|
|
281
372
|
} catch (error) {
|
|
@@ -289,7 +380,7 @@ function writeResult(result, pretty) {
|
|
|
289
380
|
const json = pretty
|
|
290
381
|
? JSON.stringify(result, null, 2)
|
|
291
382
|
: JSON.stringify(result);
|
|
292
|
-
process.stdout.write(`${json}\n`);
|
|
383
|
+
void process.stdout.write(`${json}\n`);
|
|
293
384
|
}
|
|
294
385
|
/**
|
|
295
386
|
* Count invalid files across all validation groups.
|
|
@@ -335,7 +426,7 @@ function writeLintResult(result, pretty) {
|
|
|
335
426
|
process.exitCode = 2;
|
|
336
427
|
// Warn if output was truncated
|
|
337
428
|
if (result.summary.filesWithIssues > result.files.length) {
|
|
338
|
-
process.stderr.write(
|
|
429
|
+
void process.stderr.write(
|
|
339
430
|
`Warning: output truncated to ${result.files.length} of ${result.summary.filesWithIssues} files with issues. Use --limit to see more.\n`,
|
|
340
431
|
);
|
|
341
432
|
}
|