jsdoczoom 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lint.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Lint mode runs eslint-plugin-jsdoc and custom jsdoczoom rules against
3
+ * discovered files, returning per-file diagnostics with line/column
4
+ * positions, rule names, messages, and severity levels. A single ESLint
5
+ * instance is created per invocation with the correct working directory
6
+ * so that files outside the process cwd are handled correctly. The limit
7
+ * parameter caps the number of files with issues included in the result.
8
+ *
9
+ * @summary Lint files for comprehensive JSDoc quality using ESLint engine
10
+ */
11
+ import { readFileSync } from "node:fs";
12
+ import { relative } from "node:path";
13
+ import { JsdocError } from "./errors.js";
14
+ import { createLintLinter, lintFileForLint } from "./eslint-engine.js";
15
+ import { discoverFiles } from "./file-discovery.js";
16
+ /**
17
+ * Lint a single file and return per-file diagnostics.
18
+ *
19
+ * @param eslint - Reusable ESLint instance with correct cwd
20
+ * @param filePath - Absolute path to the file
21
+ * @param cwd - Working directory for computing relative paths
22
+ * @returns File result with relative path and diagnostics array
23
+ */
24
+ async function lintSingleFile(eslint, filePath, cwd) {
25
+ const sourceText = readFileSync(filePath, "utf-8");
26
+ const diagnostics = await lintFileForLint(eslint, sourceText, filePath);
27
+ return {
28
+ filePath: relative(cwd, filePath),
29
+ diagnostics,
30
+ };
31
+ }
32
+ /**
33
+ * Build a LintResult from per-file results, applying a limit to files with issues.
34
+ *
35
+ * @param fileResults - All per-file lint results
36
+ * @param totalFiles - Total number of files that were linted
37
+ * @param limit - Maximum number of files with issues to include
38
+ * @returns Aggregated lint result with summary statistics
39
+ */
40
+ function buildLintResult(fileResults, totalFiles, limit) {
41
+ const filesWithIssues = fileResults.filter((f) => f.diagnostics.length > 0);
42
+ const totalDiagnostics = filesWithIssues.reduce((sum, f) => sum + f.diagnostics.length, 0);
43
+ const cappedFiles = filesWithIssues.slice(0, limit);
44
+ return {
45
+ files: cappedFiles,
46
+ summary: {
47
+ totalFiles,
48
+ filesWithIssues: filesWithIssues.length,
49
+ totalDiagnostics,
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * Lint files matching a selector pattern for comprehensive JSDoc quality.
55
+ *
56
+ * Discovers files via glob or path selector, creates a single ESLint instance
57
+ * with both jsdoczoom and eslint-plugin-jsdoc rules, and returns per-file
58
+ * diagnostics with summary statistics.
59
+ *
60
+ * @param selector - Selector information (glob or path)
61
+ * @param cwd - Working directory for resolving paths
62
+ * @param limit - Max number of files with issues to include (default 100)
63
+ * @param gitignore - Whether to respect .gitignore rules (default true)
64
+ * @returns Lint result with per-file diagnostics and summary
65
+ * @throws {JsdocError} NO_FILES_MATCHED if glob selector matches no files
66
+ */
67
+ export async function lint(selector, cwd, limit = 100, gitignore = true) {
68
+ const files = discoverFiles(selector.pattern, cwd, gitignore);
69
+ if (files.length === 0 && selector.type === "glob") {
70
+ throw new JsdocError("NO_FILES_MATCHED", `No files matched: ${selector.pattern}`);
71
+ }
72
+ const tsFiles = files.filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
73
+ const eslint = createLintLinter(cwd);
74
+ const fileResults = await Promise.all(tsFiles.map((f) => lintSingleFile(eslint, f, cwd)));
75
+ return buildLintResult(fileResults, tsFiles.length, limit);
76
+ }
77
+ /**
78
+ * Lint an explicit list of file paths for comprehensive JSDoc quality.
79
+ *
80
+ * Filters to .ts/.tsx files only (useful for stdin input), then runs
81
+ * the full lint rule set against each file.
82
+ *
83
+ * @param filePaths - List of absolute file paths to lint
84
+ * @param cwd - Working directory for computing relative paths
85
+ * @param limit - Max number of files with issues to include (default 100)
86
+ * @returns Lint result with per-file diagnostics and summary
87
+ */
88
+ export async function lintFiles(filePaths, cwd, limit = 100) {
89
+ const tsFiles = filePaths.filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
90
+ const eslint = createLintLinter(cwd);
91
+ const fileResults = await Promise.all(tsFiles.map((f) => lintSingleFile(eslint, f, cwd)));
92
+ return buildLintResult(fileResults, tsFiles.length, limit);
93
+ }
@@ -0,0 +1,40 @@
1
+ import { JsdocError } from "./errors.js";
2
+ /**
3
+ * Extracts glob vs. path classification and an optional `@depth` suffix
4
+ * from selector strings. Float depths are rejected; negative or non-digit
5
+ * suffixes are left as part of the pattern string. An empty selector
6
+ * throws INVALID_SELECTOR.
7
+ *
8
+ * @summary Parse selector strings into type, pattern, and optional depth components
9
+ */
10
+ /**
11
+ * Parses a selector string into its components.
12
+ *
13
+ * @param input - Selector string (e.g., "src/star-star/star.ts@3", "file.ts", "../config.js@2")
14
+ * @returns SelectorInfo with type, pattern, and optional depth
15
+ * @throws JsdocError INVALID_SELECTOR if input is empty
16
+ * @throws JsdocError INVALID_DEPTH if depth is negative, non-integer, or float
17
+ */
18
+ export function parseSelector(input) {
19
+ if (!input || input.trim() === "") {
20
+ throw new JsdocError("INVALID_SELECTOR", "Selector cannot be empty");
21
+ }
22
+ // Reject float depths like @2.5 before extracting integer depth
23
+ if (/@\d+\.\d+$/.test(input)) {
24
+ throw new JsdocError("INVALID_DEPTH", "Depth must be an integer, not a float");
25
+ }
26
+ let pattern = input;
27
+ let depth;
28
+ // Match @<digits> at the end of the string
29
+ const depthMatch = input.match(/@(\d+)$/);
30
+ if (depthMatch) {
31
+ depth = parseInt(depthMatch[1], 10);
32
+ pattern = input.substring(0, depthMatch.index);
33
+ }
34
+ const hasGlobChars = /[*?[\]{]/.test(pattern);
35
+ return {
36
+ type: hasGlobChars ? "glob" : "path",
37
+ pattern,
38
+ depth,
39
+ };
40
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Combines the jsdoc skill guidelines with concrete guidance on writing
3
+ * file-level @summary tags and description paragraphs that pass validation.
4
+ * SKILL_TEXT is emitted verbatim to stdout when the CLI receives `--skill` / `-s`.
5
+ * GUIDANCE maps each validation status to a markdown snippet used in validation
6
+ * output to explain how to fix that category.
7
+ *
8
+ * @summary Skill text and per-category guidance constants for JSDoc writing
9
+ */
10
+ /** Markdown guidance text for each validation status category */
11
+ export const GUIDANCE = {
12
+ syntax_error: `### Syntax error
13
+
14
+ The file has TypeScript parse errors that prevent analysis. Fix syntax errors first — no JSDoc validation can run until the file parses cleanly.`,
15
+ missing_jsdoc: `### Missing file-level JSDoc
16
+
17
+ Add a \`/** ... */\` block before the first code statement (after imports). This block is what jsdoczoom reads for orientation and validation.
18
+
19
+ \`\`\`typescript
20
+ import { resolve } from "node:path";
21
+
22
+ /**
23
+ * Description paragraph here.
24
+ *
25
+ * @summary One-line overview here
26
+ */
27
+
28
+ export function example() { ... }
29
+ \`\`\``,
30
+ missing_summary: `### The @summary tag
31
+
32
+ The \`@summary\` tag provides a one-line overview — the first thing someone sees when scanning with jsdoczoom at the shallowest depth.
33
+
34
+ **Important:** Only exact lowercase \`@summary\` is recognized. \`@Summary\`, \`@SUMMARY\`, and other case variants are ignored.
35
+
36
+ **Good summaries:**
37
+ - State what the file *does* or *is responsible for*, not what it contains
38
+ - Are self-contained — understandable without reading other files
39
+ - Use domain vocabulary consistently with the rest of the codebase
40
+ - Fit on a single line (joined if multi-line in source)
41
+
42
+ **Examples:**
43
+ - \`@summary Barrel tree model for hierarchical gating in glob mode\`
44
+ - \`@summary Resolve selector patterns to absolute file paths with gitignore filtering\`
45
+ - \`@summary CLI entry point — argument parsing, mode dispatch, and exit code handling\`
46
+
47
+ **Avoid:**
48
+ - \`@summary This file contains utility functions\` — says what it *contains*, not what it *does*
49
+ - \`@summary Helpers\` — too vague, no domain context
50
+ - \`@summary The main module\` — no information about purpose or scope`,
51
+ multiple_summary: `### Multiple @summary tags
52
+
53
+ Each file must have exactly one \`@summary\` tag. Remove the extra \`@summary\` tags and keep a single one-line overview.`,
54
+ missing_barrel: `### Missing barrel file
55
+
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
+
58
+ Create an \`index.ts\` that re-exports the directory's public API and add a file-level JSDoc block describing the module's capabilities.`,
59
+ missing_description: `### The description paragraph
60
+
61
+ The description is prose that appears before any \`@\` tags. It provides the deeper context that the summary cannot — responsibilities, invariants, trade-offs, and failure modes.
62
+
63
+ **Good descriptions:**
64
+ - Explain *why* this file exists and what problem it solves
65
+ - State invariants and assumptions that callers or maintainers must know
66
+ - Note trade-offs and design decisions
67
+ - Mention failure modes and edge cases relevant to the file as a whole
68
+ - Are 1–4 sentences, not an essay`,
69
+ };
70
+ export const SKILL_TEXT = `# World-Class JSDoc Guidelines for TypeScript
71
+
72
+ These guidelines describe the *properties* of excellent inline JSDoc in TypeScript repositories. They are a target to aim for, not a checklist. Use judgment; clarity beats volume.
73
+
74
+ ## Core principle
75
+
76
+ TypeScript projects already have explicit types. JSDoc should add **intent, behavior, and constraints** rather than repeat what the type system already expresses.
77
+
78
+ ## Would have (high-signal properties)
79
+
80
+ - **Intent and constraints**: Explains the non-obvious decision, constraint, or tradeoff (e.g., why a particular algorithm is used, why a heuristic exists, or what the runtime environment forbids).
81
+ - **Behavioral contract**: Clearly states what inputs are accepted, what outputs represent, and how boundary cases are treated.
82
+ - **Domain vocabulary**: Uses project-specific terms consistently so readers can navigate the codebase by vocabulary.
83
+ - **Examples that disambiguate**: Includes \`@example\` blocks when behavior is otherwise ambiguous (e.g., parsing rules, path formats, or object schemas). Examples are short and realistic.
84
+ - **Runtime effects**: Calls out side effects, temporal dependency (polling vs. event-driven), filesystem reads/writes, or external service behavior when those matter.
85
+ - **Edge-case prompts**: Captures "what happens if..." questions that a reviewer would ask, especially where external APIs or platform behavior has caveats.
86
+ - **Consistency with existing style**: Matches the formatting conventions already established in the codebase (multi-line descriptions, bullet lists where helpful).
87
+ - **Future-proof hints**: Notes invariants and assumptions that must hold if the code evolves.
88
+ - **LLM-friendly structure**: Uses short, self-contained paragraphs written in clear International Business English. Avoids prescriptive headers (e.g., "Why:", "Constraint:") in favor of natural prose that states context, purpose, and caveats directly.
89
+
90
+ ## Would not have (low-signal or risky properties)
91
+
92
+ - **Type restatements**: Repeating TypeScript types in prose (e.g., "@param options - The options object" when the type is already \`Options\`). Keep \`@param\`, \`@returns\`, and \`@throws\` tags—just make the descriptions add meaning beyond the type.
93
+ - **Obvious narration**: Comments that paraphrase the code or parameter name without additional insight.
94
+ - **Incorrect authority**: Claims that are not enforced by code (e.g., "never throws" when it can, or "always" without guardrails).
95
+ - **Redundant verbosity**: Long descriptions that could be expressed more directly, or boilerplate that hides the key idea.
96
+ - **Unbounded examples**: Large blocks or full payloads when a minimal example would do.
97
+ - **Out-of-date operational details**: References to tooling, CLI flags, or config knobs that are not enforced or checked.
98
+ - **Implementation leakage**: Unnecessary internal steps or private details that are likely to change and add churn to docs.
99
+ - **Non-ASCII decoration**: Fancy symbols or emojis that do not already exist in the file; keep ASCII unless needed.
100
+
101
+ ## Tag usage cues (not rules)
102
+
103
+ ### IntelliSense tags (always include)
104
+
105
+ These tags power IDE hover tooltips, autocomplete, and signature help. Always include them for public APIs, constructors, and functions:
106
+
107
+ - **\`@param\`**: Include for every parameter. Describe the parameter's purpose, valid ranges, or constraints—not just its type.
108
+ - **\`@returns\`**: Include when the function returns a value. Describe what the return value represents, especially for edge cases.
109
+ - **\`@throws\`**: Include when the function can throw. Describe the conditions that cause the error.
110
+ - **\`@template\`**: Include for generic functions and types. Describe what the type parameter represents.
111
+
112
+ ### Cross-reference tags (use to aid navigation)
113
+
114
+ - **\`@see\`**: Link to related functions, types, or external documentation.
115
+ - **\`{@link Symbol}\`**: Inline reference within descriptions. Creates a clickable link to another symbol.
116
+
117
+ ### Structural tags (use when appropriate)
118
+
119
+ - Use \`@module\` at the top of files that define a cohesive domain concept.
120
+ - Use \`@example\` when parsing or formatting behavior could be misread, or when a type is complex.
121
+ - Use \`@deprecated\` on exports that are retained for compatibility.
122
+ - Prefer short description + bullets for concepts with multiple facets.
123
+
124
+ ---
125
+
126
+ ## Writing file-level @summary and description
127
+
128
+ Every TypeScript file should have a file-level JSDoc block before the first code statement. This block is what jsdoczoom reads for orientation and validation.
129
+
130
+ ### Structure
131
+
132
+ \`\`\`typescript
133
+ /**
134
+ * Description paragraph goes here. It explains the file's responsibilities,
135
+ * invariants, trade-offs, and failure modes. This is the deepest level of
136
+ * native documentation — enough for someone to understand why this file
137
+ * exists and how it fits into the broader system.
138
+ *
139
+ * @summary Concise one-line overview for quick orientation when scanning a codebase.
140
+ */
141
+ \`\`\`
142
+
143
+ ### The @summary tag
144
+
145
+ The \`@summary\` tag provides a one-line overview — the first thing someone sees when scanning with jsdoczoom at the shallowest depth.
146
+
147
+ **Good summaries:**
148
+ - State what the file *does* or *is responsible for*, not what it contains
149
+ - Are self-contained — understandable without reading other files
150
+ - Use domain vocabulary consistently with the rest of the codebase
151
+ - Fit on a single line (joined if multi-line in source)
152
+
153
+ **Examples:**
154
+ - \`@summary Barrel tree model for hierarchical gating in glob mode\`
155
+ - \`@summary Resolve selector patterns to absolute file paths with gitignore filtering\`
156
+ - \`@summary CLI entry point — argument parsing, mode dispatch, and exit code handling\`
157
+
158
+ **Avoid:**
159
+ - \`@summary This file contains utility functions\` — says what it *contains*, not what it *does*
160
+ - \`@summary Helpers\` — too vague, no domain context
161
+ - \`@summary The main module\` — no information about purpose or scope
162
+
163
+ ### The description paragraph
164
+
165
+ The description is prose that appears before any \`@\` tags. It provides the deeper context that the summary cannot — responsibilities, invariants, trade-offs, and failure modes.
166
+
167
+ **Good descriptions:**
168
+ - Explain *why* this file exists and what problem it solves
169
+ - State invariants and assumptions that callers or maintainers must know
170
+ - Note trade-offs and design decisions (e.g., "uses priority-order fill to keep the limit algorithm simple")
171
+ - Mention failure modes and edge cases relevant to the file as a whole
172
+ - Are 1-4 sentences, not an essay
173
+
174
+ **Examples:**
175
+ \`\`\`typescript
176
+ /**
177
+ * Walks .gitignore files from cwd to filesystem root, building an ignore
178
+ * filter that glob results pass through. Direct-path lookups bypass the
179
+ * filter since the user explicitly named the file. The ignore instance is
180
+ * created per call — no caching — because cwd may differ between invocations.
181
+ *
182
+ * @summary Resolve selector patterns to absolute file paths with gitignore filtering
183
+ */
184
+ \`\`\`
185
+
186
+ \`\`\`typescript
187
+ /**
188
+ * Each file is classified into exactly one status category: the first
189
+ * failing check wins (syntax_error > missing_jsdoc > missing_summary >
190
+ * missing_description). Valid files are omitted from output entirely.
191
+ * The limit parameter caps the total number of invalid paths shown,
192
+ * filled in priority order across groups.
193
+ *
194
+ * @summary Validate file-level JSDoc and group results by status category
195
+ */
196
+ \`\`\`
197
+
198
+ **Avoid:**
199
+ - Restating the summary in longer words
200
+ - Listing every function in the file
201
+ - Implementation details that change frequently (line numbers, internal variable names)
202
+
203
+ ### Barrel files (index.ts / index.tsx)
204
+
205
+ Barrel files represent their directory. The \`@summary\` and description describe the module, not individual files.
206
+
207
+ - **\`@summary\`**: What the module does as a unit
208
+ - **Description**: The module's capabilities and concerns — describe concepts, not child filenames
209
+
210
+ \`\`\`typescript
211
+ // packages/auth/src/index.ts
212
+ /**
213
+ * Provides session lifecycle management, token validation and refresh,
214
+ * and middleware for route-level access control. OAuth2 provider
215
+ * integration is handled here; cryptographic primitives are delegated
216
+ * to the crypto package.
217
+ *
218
+ * @summary Authentication and authorization module
219
+ */
220
+ \`\`\`
221
+
222
+ **Avoid:**
223
+ - Listing child filenames in the description
224
+ - \`@summary Exports for the auth module\` — describes the mechanism, not the purpose
225
+ - \`@summary Index file\` — no information about what the module does
226
+
227
+ ### Placement
228
+
229
+ The file-level JSDoc block must appear **before the first code statement** (imports are fine above it, but the block must precede any \`export\`, \`const\`, \`function\`, \`class\`, etc.). A common pattern is to place it immediately after imports:
230
+
231
+ \`\`\`typescript
232
+ import { resolve } from "node:path";
233
+ import { globSync } from "glob";
234
+
235
+ /**
236
+ * Description paragraph here.
237
+ *
238
+ * @summary One-line overview here
239
+ */
240
+
241
+ export function discoverFiles(...) { ... }
242
+ \`\`\`
243
+
244
+ `;
@@ -0,0 +1,101 @@
1
+ import { readFileSync } from "node:fs";
2
+ import ts from "typescript";
3
+ import { JsdocError } from "./errors.js";
4
+ /**
5
+ * Produces .d.ts-like output from a TypeScript source file using the
6
+ * TypeScript compiler's declaration emit. Preserves JSDoc comments and
7
+ * source order while stripping implementation bodies and non-exported
8
+ * internals.
9
+ *
10
+ * @summary Generate TypeScript declaration output from source files
11
+ */
12
+ /**
13
+ * Generates TypeScript declaration output from a source file.
14
+ *
15
+ * Produces .d.ts-like output containing:
16
+ * - All exported type aliases, interfaces, enums
17
+ * - All exported function signatures (no implementation bodies)
18
+ * - All exported const/let/var declarations (type signatures only)
19
+ * - All exported class declarations (signatures only, no method bodies)
20
+ * - All JSDoc comments preserved
21
+ * - Source order maintained
22
+ *
23
+ * Excludes:
24
+ * - Import statements
25
+ * - Non-exported internals
26
+ *
27
+ * @param filePath - Absolute path to the TypeScript source file
28
+ * @returns The declaration output as a string
29
+ * @throws {JsdocError} If the file cannot be read or parsed
30
+ */
31
+ export function generateTypeDeclarations(filePath) {
32
+ let sourceText;
33
+ try {
34
+ sourceText = readFileSync(filePath, "utf-8");
35
+ }
36
+ catch (_error) {
37
+ throw new JsdocError("FILE_NOT_FOUND", `Failed to read file: ${filePath}`);
38
+ }
39
+ // Create compiler options matching the project setup
40
+ const compilerOptions = {
41
+ declaration: true,
42
+ emitDeclarationOnly: true,
43
+ target: ts.ScriptTarget.Latest,
44
+ module: ts.ModuleKind.NodeNext,
45
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
46
+ skipLibCheck: true,
47
+ removeComments: false, // Preserve JSDoc comments
48
+ };
49
+ // Create a custom compiler host that provides our file
50
+ const host = ts.createCompilerHost(compilerOptions);
51
+ const originalGetSourceFile = host.getSourceFile;
52
+ host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
53
+ if (fileName === filePath) {
54
+ return ts.createSourceFile(fileName, sourceText, languageVersion, true);
55
+ }
56
+ return originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
57
+ };
58
+ // Capture emitted output
59
+ let declarationOutput = "";
60
+ host.writeFile = (fileName, data) => {
61
+ if (fileName.endsWith(".d.ts")) {
62
+ declarationOutput = data;
63
+ }
64
+ };
65
+ // Create program and emit declarations
66
+ const program = ts.createProgram([filePath], compilerOptions, host);
67
+ const sourceFile = program.getSourceFile(filePath);
68
+ if (!sourceFile) {
69
+ throw new JsdocError("PARSE_ERROR", `Failed to parse file: ${filePath}`);
70
+ }
71
+ const emitResult = program.emit(sourceFile, undefined, undefined, true);
72
+ // Check for emit errors
73
+ const diagnostics = ts
74
+ .getPreEmitDiagnostics(program)
75
+ .concat(emitResult.diagnostics);
76
+ if (diagnostics.length > 0) {
77
+ const errors = diagnostics
78
+ .map((diagnostic) => {
79
+ if (diagnostic.file && diagnostic.start !== undefined) {
80
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
81
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
82
+ return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
83
+ }
84
+ return ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
85
+ })
86
+ .join("\n");
87
+ throw new JsdocError("PARSE_ERROR", `TypeScript errors:\n${errors}`);
88
+ }
89
+ if (!declarationOutput) {
90
+ // If no output was generated, the file may have no exports
91
+ // Return empty string in this case
92
+ return "";
93
+ }
94
+ // Clean up the output
95
+ let cleaned = declarationOutput;
96
+ // Remove empty export statement if present and no other exports
97
+ if (cleaned.trim() === "export {};") {
98
+ cleaned = "";
99
+ }
100
+ return cleaned;
101
+ }
package/dist/types.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Defines the output shapes, error codes, validation statuses, and
3
+ * parsed-file structures used across all modules. These types form the
4
+ * public contract for both JSON output and programmatic API consumers.
5
+ *
6
+ * @summary Shared type definitions for output shapes, error codes, and parsed structures
7
+ */
8
+ /** Priority order for validation status categories */
9
+ export const VALIDATION_STATUS_PRIORITY = [
10
+ "syntax_error",
11
+ "missing_jsdoc",
12
+ "missing_summary",
13
+ "multiple_summary",
14
+ "missing_description",
15
+ "missing_barrel",
16
+ ];
@@ -0,0 +1,122 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { isBarrel } from "./barrel.js";
4
+ import { JsdocError } from "./errors.js";
5
+ import { createValidationLinter, lintFileForValidation, mapToValidationStatus, } from "./eslint-engine.js";
6
+ import { discoverFiles } from "./file-discovery.js";
7
+ import { GUIDANCE } from "./skill-text.js";
8
+ import { VALIDATION_STATUS_PRIORITY } from "./types.js";
9
+ /**
10
+ * Classify a single file against validation requirements.
11
+ *
12
+ * Reads the file, lints it with the ESLint engine, and maps the
13
+ * resulting messages to a single ValidationStatus using priority order.
14
+ */
15
+ async function classifyFile(eslint, filePath, cwd) {
16
+ const relativePath = relative(cwd, filePath);
17
+ let sourceText;
18
+ try {
19
+ sourceText = readFileSync(filePath, "utf-8");
20
+ }
21
+ catch {
22
+ throw new JsdocError("FILE_NOT_FOUND", `File not found: ${filePath}`);
23
+ }
24
+ const messages = await lintFileForValidation(eslint, sourceText, filePath);
25
+ const status = mapToValidationStatus(messages);
26
+ return { path: relativePath, status };
27
+ }
28
+ /** Minimum number of .ts/.tsx files in a directory to require a barrel. */
29
+ const BARREL_THRESHOLD = 3;
30
+ /**
31
+ * Find directories with more than BARREL_THRESHOLD .ts/.tsx files
32
+ * that lack a barrel file (index.ts or index.tsx).
33
+ */
34
+ function findMissingBarrels(filePaths, cwd) {
35
+ const dirCounts = new Map();
36
+ for (const filePath of filePaths) {
37
+ if (isBarrel(filePath))
38
+ continue;
39
+ const dir = dirname(filePath);
40
+ dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
41
+ }
42
+ const missing = [];
43
+ for (const [dir, count] of dirCounts) {
44
+ if (count <= BARREL_THRESHOLD)
45
+ continue;
46
+ const hasBarrel = existsSync(join(dir, "index.ts")) || existsSync(join(dir, "index.tsx"));
47
+ if (!hasBarrel) {
48
+ const rel = relative(cwd, dir) || ".";
49
+ missing.push(rel);
50
+ }
51
+ }
52
+ return missing.sort();
53
+ }
54
+ /**
55
+ * Group file statuses into a ValidationResult, applying a limit
56
+ * to the total number of invalid file paths shown.
57
+ */
58
+ function buildGroupedResult(statuses, missingBarrels, limit) {
59
+ const groups = {
60
+ syntax_error: [],
61
+ missing_jsdoc: [],
62
+ missing_summary: [],
63
+ multiple_summary: [],
64
+ missing_description: [],
65
+ missing_barrel: missingBarrels,
66
+ };
67
+ for (const { path, status } of statuses) {
68
+ if (status !== "valid") {
69
+ groups[status].push(path);
70
+ }
71
+ }
72
+ const result = {};
73
+ let remaining = limit;
74
+ for (const status of VALIDATION_STATUS_PRIORITY) {
75
+ const files = groups[status];
76
+ if (files.length === 0)
77
+ continue;
78
+ if (remaining <= 0)
79
+ break;
80
+ const slice = files.slice(0, remaining);
81
+ result[status] = { guidance: GUIDANCE[status], files: slice };
82
+ remaining -= slice.length;
83
+ }
84
+ return result;
85
+ }
86
+ /**
87
+ * Validate files matching a selector pattern.
88
+ *
89
+ * @param selector - Selector information (glob or path)
90
+ * @param cwd - Working directory for resolving paths
91
+ * @param limit - Max number of invalid file paths to include (default 100)
92
+ * @returns Grouped validation results
93
+ * @throws {JsdocError} NO_FILES_MATCHED if glob selector matches no files
94
+ * @throws {JsdocError} FILE_NOT_FOUND if path selector targets nonexistent file
95
+ */
96
+ export async function validate(selector, cwd, limit = 100, gitignore = true) {
97
+ const files = discoverFiles(selector.pattern, cwd, gitignore);
98
+ if (files.length === 0) {
99
+ throw new JsdocError("NO_FILES_MATCHED", `No files matched: ${selector.pattern}`);
100
+ }
101
+ const eslint = createValidationLinter();
102
+ const statuses = await Promise.all(files.map((f) => classifyFile(eslint, f, cwd)));
103
+ const missingBarrels = findMissingBarrels(files, cwd);
104
+ return buildGroupedResult(statuses, missingBarrels, limit);
105
+ }
106
+ /**
107
+ * Validate an explicit list of file paths.
108
+ *
109
+ * Filters to .ts/.tsx files only (useful for stdin input).
110
+ *
111
+ * @param filePaths - List of file paths to validate
112
+ * @param cwd - Working directory for resolving relative paths
113
+ * @param limit - Max number of invalid file paths to include (default 100)
114
+ * @returns Grouped validation results
115
+ */
116
+ export async function validateFiles(filePaths, cwd, limit = 100) {
117
+ const tsFiles = filePaths.filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
118
+ const eslint = createValidationLinter();
119
+ const statuses = await Promise.all(tsFiles.map((f) => classifyFile(eslint, f, cwd)));
120
+ const missingBarrels = findMissingBarrels(tsFiles, cwd);
121
+ return buildGroupedResult(statuses, missingBarrels, limit);
122
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "jsdoczoom",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for extracting JSDoc summaries at configurable depths",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "engines": {
8
+ "node": ">=20.11.0"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "types"
13
+ ],
14
+ "bin": "./dist/cli.js",
15
+ "publishConfig": {
16
+ "bin": {
17
+ "jsdoczoom": "./dist/cli.js"
18
+ }
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./types/index.d.ts"
24
+ },
25
+ "./*": {
26
+ "import": "./dist/*.js",
27
+ "types": "./types/*.d.ts"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "typecheck": "tsc --noEmit",
32
+ "build": "tsc --build tsconfig.build.json",
33
+ "test": "vitest run",
34
+ "lint": "biome check --write --unsafe --max-diagnostics 500 .",
35
+ "release": "bash ../../scripts/release-package.sh jsdoczoom",
36
+ "release:dry-run": "bash ../../scripts/release-package.sh jsdoczoom --dry-run"
37
+ },
38
+ "dependencies": {
39
+ "@typescript-eslint/parser": "^8.21.0",
40
+ "eslint": "^9.20.0",
41
+ "eslint-plugin-jsdoc": "^50.6.4",
42
+ "glob": "^11.0.0",
43
+ "ignore": "^7.0.5",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "devDependencies": {
47
+ "@biomejs/biome": "2.3.14",
48
+ "@types/node": "^24",
49
+ "vitest": "4.0.13"
50
+ }
51
+ }