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/barrel.js +84 -0
- package/dist/cli.js +287 -0
- package/dist/drilldown.js +286 -0
- package/dist/errors.js +24 -0
- package/dist/eslint-engine.js +160 -0
- package/dist/eslint-plugin.js +205 -0
- package/dist/file-discovery.js +76 -0
- package/dist/index.js +17 -0
- package/dist/jsdoc-parser.js +238 -0
- package/dist/lint.js +93 -0
- package/dist/selector.js +40 -0
- package/dist/skill-text.js +244 -0
- package/dist/type-declarations.js +101 -0
- package/dist/types.js +16 -0
- package/dist/validate.js +122 -0
- package/package.json +51 -0
- package/types/barrel.d.ts +32 -0
- package/types/cli.d.ts +5 -0
- package/types/drilldown.d.ts +28 -0
- package/types/errors.d.ts +19 -0
- package/types/eslint-engine.d.ts +65 -0
- package/types/eslint-plugin.d.ts +9 -0
- package/types/file-discovery.d.ts +14 -0
- package/types/index.d.ts +17 -0
- package/types/jsdoc-parser.d.ts +24 -0
- package/types/lint.d.ts +38 -0
- package/types/selector.d.ts +18 -0
- package/types/skill-text.d.ts +13 -0
- package/types/type-declarations.d.ts +28 -0
- package/types/types.d.ts +91 -0
- package/types/validate.d.ts +23 -0
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
|
+
}
|
package/dist/selector.js
ADDED
|
@@ -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
|
+
];
|
package/dist/validate.js
ADDED
|
@@ -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
|
+
}
|