@forge-ts/enforcer 0.2.0 → 0.3.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/README.md +43 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +103 -7
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @forge-ts/enforcer
|
|
2
|
+
|
|
3
|
+
TSDoc enforcement build gate for the [forge-ts](https://github.com/kryptobaseddev/forge-ts) toolchain.
|
|
4
|
+
|
|
5
|
+
## When to use this package
|
|
6
|
+
|
|
7
|
+
**Most users should install `@forge-ts/cli` instead** and use `npx forge-ts check`. This package is for programmatic use in custom build pipelines.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @forge-ts/enforcer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What it checks
|
|
14
|
+
|
|
15
|
+
| Code | Severity | Rule |
|
|
16
|
+
|------|----------|------|
|
|
17
|
+
| E001 | error | Exported symbol missing TSDoc summary |
|
|
18
|
+
| E002 | error | Function parameter missing `@param` tag |
|
|
19
|
+
| E003 | error | Non-void function missing `@returns` tag |
|
|
20
|
+
| W001 | warning | TSDoc comment has parse errors |
|
|
21
|
+
| W002 | warning | Function throws but missing `@throws` tag |
|
|
22
|
+
| W003 | warning | `@deprecated` without explanation |
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { loadConfig } from "@forge-ts/core";
|
|
28
|
+
import { enforce, formatResults } from "@forge-ts/enforcer";
|
|
29
|
+
|
|
30
|
+
const config = await loadConfig();
|
|
31
|
+
const result = await enforce(config);
|
|
32
|
+
|
|
33
|
+
console.log(formatResults(result, { colors: true, verbose: false }));
|
|
34
|
+
process.exit(result.success ? 0 : 1);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Part of forge-ts
|
|
38
|
+
|
|
39
|
+
See the [main repo](https://github.com/kryptobaseddev/forge-ts) for full documentation.
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ForgeConfig, ForgeResult } from '@
|
|
1
|
+
import { ForgeConfig, ForgeResult } from '@forge-ts/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Runs the TSDoc enforcement pass against a project.
|
|
@@ -13,6 +13,10 @@ import { ForgeConfig, ForgeResult } from '@codluv/forge-core';
|
|
|
13
13
|
* | E001 | error | Exported symbol is missing a TSDoc summary. |
|
|
14
14
|
* | E002 | error | Function/method parameter lacks a `@param` tag. |
|
|
15
15
|
* | E003 | error | Non-void function/method lacks a `@returns` tag. |
|
|
16
|
+
* | E004 | error | Exported function/method is missing an `@example` block. |
|
|
17
|
+
* | E005 | error | Package entry point (index.ts) is missing `@packageDocumentation`. |
|
|
18
|
+
* | E006 | error | Public/protected class member is missing a TSDoc comment. |
|
|
19
|
+
* | E007 | error | Interface/type alias property is missing a TSDoc comment. |
|
|
16
20
|
* | W001 | warning | TSDoc comment contains parse errors. |
|
|
17
21
|
* | W002 | warning | Function body throws but has no `@throws` tag. |
|
|
18
22
|
* | W003 | warning | `@deprecated` tag is present without explanation. |
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createWalker,
|
|
4
4
|
filterByVisibility
|
|
5
|
-
} from "@
|
|
5
|
+
} from "@forge-ts/core";
|
|
6
6
|
function hasSummary(symbol) {
|
|
7
7
|
return symbol.documentation?.summary !== void 0 && symbol.documentation.summary.trim().length > 0;
|
|
8
8
|
}
|
|
@@ -63,8 +63,8 @@ async function enforce(config) {
|
|
|
63
63
|
const walker = createWalker(config);
|
|
64
64
|
const allSymbols = walker.walk();
|
|
65
65
|
const symbols = filterByVisibility(allSymbols, config.enforce.minVisibility);
|
|
66
|
-
function emit(severity, code, message, filePath, line, column) {
|
|
67
|
-
const diag = { code, message, filePath, line, column };
|
|
66
|
+
function emit(severity, code, message, filePath, line, column, guidance) {
|
|
67
|
+
const diag = { code, message, filePath, line, column, ...guidance };
|
|
68
68
|
if (severity === "error" || config.enforce.strict) {
|
|
69
69
|
errors.push(diag);
|
|
70
70
|
} else {
|
|
@@ -81,7 +81,14 @@ async function enforce(config) {
|
|
|
81
81
|
`Exported symbol "${symbol.name}" is missing a TSDoc summary comment.`,
|
|
82
82
|
symbol.filePath,
|
|
83
83
|
symbol.line,
|
|
84
|
-
symbol.column
|
|
84
|
+
symbol.column,
|
|
85
|
+
{
|
|
86
|
+
suggestedFix: `/**
|
|
87
|
+
* [Description of ${symbol.name}]
|
|
88
|
+
*/`,
|
|
89
|
+
symbolName: symbol.name,
|
|
90
|
+
symbolKind: symbol.kind
|
|
91
|
+
}
|
|
85
92
|
);
|
|
86
93
|
}
|
|
87
94
|
if (isFunctionLike) {
|
|
@@ -93,7 +100,12 @@ async function enforce(config) {
|
|
|
93
100
|
`Parameter "${paramName}" of "${symbol.name}" is not documented with a @param tag.`,
|
|
94
101
|
symbol.filePath,
|
|
95
102
|
symbol.line,
|
|
96
|
-
symbol.column
|
|
103
|
+
symbol.column,
|
|
104
|
+
{
|
|
105
|
+
suggestedFix: `@param ${paramName} - [Description of ${paramName}]`,
|
|
106
|
+
symbolName: symbol.name,
|
|
107
|
+
symbolKind: symbol.kind
|
|
108
|
+
}
|
|
97
109
|
);
|
|
98
110
|
}
|
|
99
111
|
}
|
|
@@ -104,9 +116,60 @@ async function enforce(config) {
|
|
|
104
116
|
`"${symbol.name}" has a non-void return type but is missing a @returns tag.`,
|
|
105
117
|
symbol.filePath,
|
|
106
118
|
symbol.line,
|
|
107
|
-
symbol.column
|
|
119
|
+
symbol.column,
|
|
120
|
+
{
|
|
121
|
+
suggestedFix: `@returns [Description of the return value]`,
|
|
122
|
+
symbolName: symbol.name,
|
|
123
|
+
symbolKind: symbol.kind
|
|
124
|
+
}
|
|
108
125
|
);
|
|
109
126
|
}
|
|
127
|
+
if (isFunctionLike && symbol.documentation) {
|
|
128
|
+
const hasExample = (symbol.documentation.examples ?? []).length > 0;
|
|
129
|
+
if (!hasExample) {
|
|
130
|
+
emit(
|
|
131
|
+
"error",
|
|
132
|
+
"E004",
|
|
133
|
+
`Exported function "${symbol.name}" is missing an @example block. Add a fenced code block showing usage.`,
|
|
134
|
+
symbol.filePath,
|
|
135
|
+
symbol.line,
|
|
136
|
+
symbol.column,
|
|
137
|
+
{
|
|
138
|
+
suggestedFix: `@example
|
|
139
|
+
* \`\`\`typescript
|
|
140
|
+
* // Usage of ${symbol.name}
|
|
141
|
+
* ${symbol.name}();
|
|
142
|
+
* \`\`\``,
|
|
143
|
+
symbolName: symbol.name,
|
|
144
|
+
symbolKind: symbol.kind
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (symbol.kind === "class" || symbol.kind === "interface") {
|
|
150
|
+
const errorCode = symbol.kind === "class" ? "E006" : "E007";
|
|
151
|
+
for (const child of symbol.children ?? []) {
|
|
152
|
+
if (child.kind === "property" || child.kind === "method") {
|
|
153
|
+
if (!hasSummary(child)) {
|
|
154
|
+
emit(
|
|
155
|
+
"error",
|
|
156
|
+
errorCode,
|
|
157
|
+
`Member "${child.name}" of ${symbol.kind} "${symbol.name}" is missing a TSDoc comment.`,
|
|
158
|
+
child.filePath,
|
|
159
|
+
child.line,
|
|
160
|
+
child.column,
|
|
161
|
+
{
|
|
162
|
+
suggestedFix: `/**
|
|
163
|
+
* [Description of ${child.name}]
|
|
164
|
+
*/`,
|
|
165
|
+
symbolName: child.name,
|
|
166
|
+
symbolKind: child.kind
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
110
173
|
if (deprecatedWithoutReason(symbol)) {
|
|
111
174
|
emit(
|
|
112
175
|
"warning",
|
|
@@ -114,7 +177,40 @@ async function enforce(config) {
|
|
|
114
177
|
`"${symbol.name}" is marked @deprecated but provides no explanation.`,
|
|
115
178
|
symbol.filePath,
|
|
116
179
|
symbol.line,
|
|
117
|
-
symbol.column
|
|
180
|
+
symbol.column,
|
|
181
|
+
{
|
|
182
|
+
symbolName: symbol.name,
|
|
183
|
+
symbolKind: symbol.kind
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const indexFiles = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const symbol of allSymbols) {
|
|
190
|
+
if (symbol.filePath.endsWith("index.ts")) {
|
|
191
|
+
const bucket = indexFiles.get(symbol.filePath) ?? [];
|
|
192
|
+
bucket.push(symbol);
|
|
193
|
+
indexFiles.set(symbol.filePath, bucket);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const [filePath, fileSymbols] of indexFiles) {
|
|
197
|
+
const hasPackageDoc = fileSymbols.some(
|
|
198
|
+
(s) => s.documentation?.tags?.packageDocumentation !== void 0
|
|
199
|
+
);
|
|
200
|
+
if (!hasPackageDoc) {
|
|
201
|
+
emit(
|
|
202
|
+
"error",
|
|
203
|
+
"E005",
|
|
204
|
+
`Package entry point "${filePath}" is missing a @packageDocumentation TSDoc comment.`,
|
|
205
|
+
filePath,
|
|
206
|
+
1,
|
|
207
|
+
0,
|
|
208
|
+
{
|
|
209
|
+
suggestedFix: `/**
|
|
210
|
+
* @packageDocumentation
|
|
211
|
+
* [Package overview description]
|
|
212
|
+
*/`
|
|
213
|
+
}
|
|
118
214
|
);
|
|
119
215
|
}
|
|
120
216
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/enforcer.ts","../src/formatter.ts"],"sourcesContent":["import {\n\tcreateWalker,\n\ttype ForgeConfig,\n\ttype ForgeError,\n\ttype ForgeResult,\n\ttype ForgeSymbol,\n\ttype ForgeWarning,\n\tfilterByVisibility,\n} from \"@codluv/forge-core\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Checks whether a symbol has at least a summary in its documentation.\n * @internal\n */\nfunction hasSummary(symbol: ForgeSymbol): boolean {\n\treturn (\n\t\tsymbol.documentation?.summary !== undefined && symbol.documentation.summary.trim().length > 0\n\t);\n}\n\n/**\n * Splits a signature parameter list on top-level commas, respecting angle\n * bracket nesting so that `Record<string, string[]>` is not split.\n * @internal\n */\nfunction splitParams(raw: string): string[] {\n\tconst parts: string[] = [];\n\tlet depth = 0;\n\tlet current = \"\";\n\tfor (const ch of raw) {\n\t\tif (ch === \"<\" || ch === \"(\") {\n\t\t\tdepth++;\n\t\t\tcurrent += ch;\n\t\t} else if (ch === \">\" || ch === \")\") {\n\t\t\tdepth--;\n\t\t\tcurrent += ch;\n\t\t} else if (ch === \",\" && depth === 0) {\n\t\t\tparts.push(current);\n\t\t\tcurrent = \"\";\n\t\t} else {\n\t\t\tcurrent += ch;\n\t\t}\n\t}\n\tif (current.trim()) {\n\t\tparts.push(current);\n\t}\n\treturn parts;\n}\n\n/**\n * Returns the names of parameters that are declared on a function/method symbol\n * but lack a corresponding `@param` tag in its documentation.\n *\n * Since the AST walker populates `documentation.params` from parsed TSDoc, we\n * compare the set of documented param names against the names that appear in\n * the symbol's type signature. When no signature is available the check is\n * skipped (returns empty array).\n *\n * @internal\n */\nfunction undocumentedParams(symbol: ForgeSymbol): string[] {\n\tconst sig = symbol.signature;\n\tif (!sig) return [];\n\n\t// Parse parameter names out of the signature string.\n\t// Signatures look like: \"(a: string, b: number) => void\"\n\t// Must handle nested generics: \"(tags: Record<string, string[]>) => void\"\n\tconst parenMatch = sig.match(/^\\(([^)]*)\\)/);\n\tif (!parenMatch || !parenMatch[1].trim()) return [];\n\n\tconst rawParams = splitParams(parenMatch[1])\n\t\t.map((p) =>\n\t\t\tp\n\t\t\t\t.trim()\n\t\t\t\t.split(\":\")[0]\n\t\t\t\t.trim()\n\t\t\t\t.replace(/^\\.{3}/, \"\")\n\t\t\t\t.replace(/\\?$/, \"\")\n\t\t\t\t.trim(),\n\t\t)\n\t\t.filter((p) => p.length > 0 && p !== \"this\");\n\n\tif (rawParams.length === 0) return [];\n\n\tconst documentedNames = new Set((symbol.documentation?.params ?? []).map((p) => p.name));\n\treturn rawParams.filter((name) => !documentedNames.has(name));\n}\n\n/**\n * Returns `true` when a function/method symbol has a non-void return type but\n * no `@returns` block in its documentation.\n * @internal\n */\nfunction missingReturns(symbol: ForgeSymbol): boolean {\n\tconst sig = symbol.signature;\n\tif (!sig) return false;\n\n\t// Extract return type: everything after the last \"=>\"\n\tconst arrowIdx = sig.lastIndexOf(\"=>\");\n\tif (arrowIdx === -1) return false;\n\tconst returnType = sig.slice(arrowIdx + 2).trim();\n\n\tconst isVoidLike =\n\t\treturnType === \"void\" ||\n\t\treturnType === \"never\" ||\n\t\treturnType === \"undefined\" ||\n\t\treturnType.startsWith(\"Promise<void>\") ||\n\t\treturnType.startsWith(\"Promise<never>\") ||\n\t\treturnType.startsWith(\"Promise<undefined>\");\n\n\tif (isVoidLike) return false;\n\treturn symbol.documentation?.returns === undefined;\n}\n\n/**\n * Returns `true` when a `@deprecated` tag is present but carries no\n * explanatory text.\n * @internal\n */\nfunction deprecatedWithoutReason(symbol: ForgeSymbol): boolean {\n\tconst deprecated = symbol.documentation?.deprecated;\n\tif (deprecated === undefined) return false;\n\t// The walker stores `\"true\"` when the tag has no content.\n\treturn deprecated === \"true\" || deprecated.trim().length === 0;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Runs the TSDoc enforcement pass against a project.\n *\n * The enforcer walks all exported symbols that meet the configured minimum\n * visibility threshold and emits diagnostics for any documentation deficiencies\n * it finds.\n *\n * ### Error codes\n * | Code | Severity | Condition |\n * |------|----------|-----------|\n * | E001 | error | Exported symbol is missing a TSDoc summary. |\n * | E002 | error | Function/method parameter lacks a `@param` tag. |\n * | E003 | error | Non-void function/method lacks a `@returns` tag. |\n * | W001 | warning | TSDoc comment contains parse errors. |\n * | W002 | warning | Function body throws but has no `@throws` tag. |\n * | W003 | warning | `@deprecated` tag is present without explanation. |\n *\n * When `config.enforce.strict` is `true` all warnings are promoted to errors.\n *\n * @param config - The resolved {@link ForgeConfig} for the project.\n * @returns A {@link ForgeResult} describing which symbols passed or failed.\n * @public\n */\nexport async function enforce(config: ForgeConfig): Promise<ForgeResult> {\n\tconst start = Date.now();\n\tconst errors: ForgeError[] = [];\n\tconst warnings: ForgeWarning[] = [];\n\n\tconst walker = createWalker(config);\n\tconst allSymbols = walker.walk();\n\tconst symbols = filterByVisibility(allSymbols, config.enforce.minVisibility);\n\n\t/**\n\t * Emit a diagnostic. When `strict` is enabled every warning becomes an\n\t * error so the build gate fails hard.\n\t */\n\tfunction emit(\n\t\tseverity: \"error\" | \"warning\",\n\t\tcode: string,\n\t\tmessage: string,\n\t\tfilePath: string,\n\t\tline: number,\n\t\tcolumn: number,\n\t): void {\n\t\tconst diag = { code, message, filePath, line, column };\n\t\tif (severity === \"error\" || config.enforce.strict) {\n\t\t\terrors.push(diag);\n\t\t} else {\n\t\t\twarnings.push(diag);\n\t\t}\n\t}\n\n\tfor (const symbol of symbols) {\n\t\tif (!symbol.exported) continue;\n\n\t\tconst isFunctionLike = symbol.kind === \"function\" || symbol.kind === \"method\";\n\n\t\t// E001 — Missing summary\n\t\tif (!hasSummary(symbol)) {\n\t\t\temit(\n\t\t\t\t\"error\",\n\t\t\t\t\"E001\",\n\t\t\t\t`Exported symbol \"${symbol.name}\" is missing a TSDoc summary comment.`,\n\t\t\t\tsymbol.filePath,\n\t\t\t\tsymbol.line,\n\t\t\t\tsymbol.column,\n\t\t\t);\n\t\t}\n\n\t\t// E002 — Undocumented parameters\n\t\tif (isFunctionLike) {\n\t\t\tconst missing = undocumentedParams(symbol);\n\t\t\tfor (const paramName of missing) {\n\t\t\t\temit(\n\t\t\t\t\t\"error\",\n\t\t\t\t\t\"E002\",\n\t\t\t\t\t`Parameter \"${paramName}\" of \"${symbol.name}\" is not documented with a @param tag.`,\n\t\t\t\t\tsymbol.filePath,\n\t\t\t\t\tsymbol.line,\n\t\t\t\t\tsymbol.column,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// E003 — Missing @returns\n\t\tif (isFunctionLike && missingReturns(symbol)) {\n\t\t\temit(\n\t\t\t\t\"error\",\n\t\t\t\t\"E003\",\n\t\t\t\t`\"${symbol.name}\" has a non-void return type but is missing a @returns tag.`,\n\t\t\t\tsymbol.filePath,\n\t\t\t\tsymbol.line,\n\t\t\t\tsymbol.column,\n\t\t\t);\n\t\t}\n\n\t\t// W003 — @deprecated without reason\n\t\tif (deprecatedWithoutReason(symbol)) {\n\t\t\temit(\n\t\t\t\t\"warning\",\n\t\t\t\t\"W003\",\n\t\t\t\t`\"${symbol.name}\" is marked @deprecated but provides no explanation.`,\n\t\t\t\tsymbol.filePath,\n\t\t\t\tsymbol.line,\n\t\t\t\tsymbol.column,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst success = errors.length === 0;\n\treturn { success, symbols: allSymbols, errors, warnings, duration: Date.now() - start };\n}\n","import type { ForgeResult } from \"@codluv/forge-core\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Options that control how {@link formatResults} renders its output.\n * @public\n */\nexport interface FormatOptions {\n\t/** Emit ANSI colour escape sequences when `true`. */\n\tcolors: boolean;\n\t/**\n\t * When `true`, include the symbol's type signature alongside each\n\t * diagnostic so the reader has immediate context.\n\t */\n\tverbose: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// ANSI helpers\n// ---------------------------------------------------------------------------\n\n/** @internal */\nconst RESET = \"\\x1b[0m\";\n/** @internal */\nconst RED = \"\\x1b[31m\";\n/** @internal */\nconst YELLOW = \"\\x1b[33m\";\n/** @internal */\nconst BOLD = \"\\x1b[1m\";\n/** @internal */\nconst DIM = \"\\x1b[2m\";\n\n/** @internal */\nfunction colorize(text: string, color: string, useColors: boolean): string {\n\treturn useColors ? `${color}${text}${RESET}` : text;\n}\n\n/** @internal */\nfunction bold(text: string, useColors: boolean): string {\n\treturn useColors ? `${BOLD}${text}${RESET}` : text;\n}\n\n/** @internal */\nfunction dim(text: string, useColors: boolean): string {\n\treturn useColors ? `${DIM}${text}${RESET}` : text;\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\n/** @internal */\ninterface Diagnostic {\n\tcode: string;\n\tmessage: string;\n\tfilePath: string;\n\tline: number;\n\tcolumn: number;\n}\n\n/** @internal */\nfunction isError(code: string): boolean {\n\treturn code.startsWith(\"E\");\n}\n\n/** @internal */\nfunction renderDiagnostic(diag: Diagnostic, opts: FormatOptions): string {\n\tconst label = isError(diag.code)\n\t\t? colorize(`error[${diag.code}]`, RED, opts.colors)\n\t\t: colorize(`warning[${diag.code}]`, YELLOW, opts.colors);\n\n\tconst location = dim(`${diag.line}:${diag.column}`, opts.colors);\n\treturn ` ${label} ${diag.message} ${location}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Formats a {@link ForgeResult} into a human-readable string suitable for\n * printing to a terminal.\n *\n * Diagnostics are grouped by source file. Each file heading shows the\n * relative-ish path, followed by indented error and warning lines. A summary\n * line is appended at the end.\n *\n * @param result - The result produced by {@link enforce}.\n * @param options - Rendering options (colours, verbosity).\n * @returns A formatted string ready to write to stdout or stderr.\n * @public\n */\nexport function formatResults(result: ForgeResult, options: FormatOptions): string {\n\tconst allDiags: Diagnostic[] = [\n\t\t...result.errors.map((e) => ({ ...e })),\n\t\t...result.warnings.map((w) => ({ ...w })),\n\t];\n\n\tif (allDiags.length === 0) {\n\t\tconst msg = `No issues found across ${result.symbols.length} symbol(s).`;\n\t\treturn bold(msg, options.colors);\n\t}\n\n\t// Group by filePath\n\tconst byFile = new Map<string, Diagnostic[]>();\n\tfor (const diag of allDiags) {\n\t\tconst list = byFile.get(diag.filePath);\n\t\tif (list) {\n\t\t\tlist.push(diag);\n\t\t} else {\n\t\t\tbyFile.set(diag.filePath, [diag]);\n\t\t}\n\t}\n\n\tconst lines: string[] = [];\n\n\tfor (const [filePath, diags] of byFile) {\n\t\tlines.push(bold(filePath, options.colors));\n\n\t\t// Sort: errors before warnings, then by line\n\t\tconst sorted = [...diags].sort((a, b) => {\n\t\t\tconst aIsErr = isError(a.code) ? 0 : 1;\n\t\t\tconst bIsErr = isError(b.code) ? 0 : 1;\n\t\t\tif (aIsErr !== bIsErr) return aIsErr - bIsErr;\n\t\t\treturn a.line - b.line;\n\t\t});\n\n\t\tfor (const diag of sorted) {\n\t\t\tlines.push(renderDiagnostic(diag, options));\n\n\t\t\tif (options.verbose) {\n\t\t\t\t// Find the matching symbol to show its signature\n\t\t\t\tconst sym = result.symbols.find(\n\t\t\t\t\t(s) => s.filePath === diag.filePath && s.line === diag.line,\n\t\t\t\t);\n\t\t\t\tif (sym?.signature) {\n\t\t\t\t\tlines.push(dim(` signature: ${sym.signature}`, options.colors));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlines.push(\"\");\n\t}\n\n\t// Summary line\n\tconst errorCount = result.errors.length;\n\tconst warnCount = result.warnings.length;\n\tconst fileCount = byFile.size;\n\n\tconst errorPart =\n\t\terrorCount > 0\n\t\t\t? colorize(`${errorCount} error${errorCount !== 1 ? \"s\" : \"\"}`, RED, options.colors)\n\t\t\t: `0 errors`;\n\tconst warnPart =\n\t\twarnCount > 0\n\t\t\t? colorize(`${warnCount} warning${warnCount !== 1 ? \"s\" : \"\"}`, YELLOW, options.colors)\n\t\t\t: `0 warnings`;\n\tconst filePart = `${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tlines.push(`${errorPart}, ${warnPart} in ${filePart}`);\n\n\treturn lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/enforcer.ts","../src/formatter.ts"],"sourcesContent":["import {\n\tcreateWalker,\n\ttype ForgeConfig,\n\ttype ForgeError,\n\ttype ForgeResult,\n\ttype ForgeSymbol,\n\ttype ForgeWarning,\n\tfilterByVisibility,\n} from \"@forge-ts/core\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Checks whether a symbol has at least a summary in its documentation.\n * @internal\n */\nfunction hasSummary(symbol: ForgeSymbol): boolean {\n\treturn (\n\t\tsymbol.documentation?.summary !== undefined && symbol.documentation.summary.trim().length > 0\n\t);\n}\n\n/**\n * Splits a signature parameter list on top-level commas, respecting angle\n * bracket nesting so that `Record<string, string[]>` is not split.\n * @internal\n */\nfunction splitParams(raw: string): string[] {\n\tconst parts: string[] = [];\n\tlet depth = 0;\n\tlet current = \"\";\n\tfor (const ch of raw) {\n\t\tif (ch === \"<\" || ch === \"(\") {\n\t\t\tdepth++;\n\t\t\tcurrent += ch;\n\t\t} else if (ch === \">\" || ch === \")\") {\n\t\t\tdepth--;\n\t\t\tcurrent += ch;\n\t\t} else if (ch === \",\" && depth === 0) {\n\t\t\tparts.push(current);\n\t\t\tcurrent = \"\";\n\t\t} else {\n\t\t\tcurrent += ch;\n\t\t}\n\t}\n\tif (current.trim()) {\n\t\tparts.push(current);\n\t}\n\treturn parts;\n}\n\n/**\n * Returns the names of parameters that are declared on a function/method symbol\n * but lack a corresponding `@param` tag in its documentation.\n *\n * Since the AST walker populates `documentation.params` from parsed TSDoc, we\n * compare the set of documented param names against the names that appear in\n * the symbol's type signature. When no signature is available the check is\n * skipped (returns empty array).\n *\n * @internal\n */\nfunction undocumentedParams(symbol: ForgeSymbol): string[] {\n\tconst sig = symbol.signature;\n\tif (!sig) return [];\n\n\t// Parse parameter names out of the signature string.\n\t// Signatures look like: \"(a: string, b: number) => void\"\n\t// Must handle nested generics: \"(tags: Record<string, string[]>) => void\"\n\tconst parenMatch = sig.match(/^\\(([^)]*)\\)/);\n\tif (!parenMatch || !parenMatch[1].trim()) return [];\n\n\tconst rawParams = splitParams(parenMatch[1])\n\t\t.map((p) =>\n\t\t\tp\n\t\t\t\t.trim()\n\t\t\t\t.split(\":\")[0]\n\t\t\t\t.trim()\n\t\t\t\t.replace(/^\\.{3}/, \"\")\n\t\t\t\t.replace(/\\?$/, \"\")\n\t\t\t\t.trim(),\n\t\t)\n\t\t.filter((p) => p.length > 0 && p !== \"this\");\n\n\tif (rawParams.length === 0) return [];\n\n\tconst documentedNames = new Set((symbol.documentation?.params ?? []).map((p) => p.name));\n\treturn rawParams.filter((name) => !documentedNames.has(name));\n}\n\n/**\n * Returns `true` when a function/method symbol has a non-void return type but\n * no `@returns` block in its documentation.\n * @internal\n */\nfunction missingReturns(symbol: ForgeSymbol): boolean {\n\tconst sig = symbol.signature;\n\tif (!sig) return false;\n\n\t// Extract return type: everything after the last \"=>\"\n\tconst arrowIdx = sig.lastIndexOf(\"=>\");\n\tif (arrowIdx === -1) return false;\n\tconst returnType = sig.slice(arrowIdx + 2).trim();\n\n\tconst isVoidLike =\n\t\treturnType === \"void\" ||\n\t\treturnType === \"never\" ||\n\t\treturnType === \"undefined\" ||\n\t\treturnType.startsWith(\"Promise<void>\") ||\n\t\treturnType.startsWith(\"Promise<never>\") ||\n\t\treturnType.startsWith(\"Promise<undefined>\");\n\n\tif (isVoidLike) return false;\n\treturn symbol.documentation?.returns === undefined;\n}\n\n/**\n * Returns `true` when a `@deprecated` tag is present but carries no\n * explanatory text.\n * @internal\n */\nfunction deprecatedWithoutReason(symbol: ForgeSymbol): boolean {\n\tconst deprecated = symbol.documentation?.deprecated;\n\tif (deprecated === undefined) return false;\n\t// The walker stores `\"true\"` when the tag has no content.\n\treturn deprecated === \"true\" || deprecated.trim().length === 0;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Runs the TSDoc enforcement pass against a project.\n *\n * The enforcer walks all exported symbols that meet the configured minimum\n * visibility threshold and emits diagnostics for any documentation deficiencies\n * it finds.\n *\n * ### Error codes\n * | Code | Severity | Condition |\n * |------|----------|-----------|\n * | E001 | error | Exported symbol is missing a TSDoc summary. |\n * | E002 | error | Function/method parameter lacks a `@param` tag. |\n * | E003 | error | Non-void function/method lacks a `@returns` tag. |\n * | E004 | error | Exported function/method is missing an `@example` block. |\n * | E005 | error | Package entry point (index.ts) is missing `@packageDocumentation`. |\n * | E006 | error | Public/protected class member is missing a TSDoc comment. |\n * | E007 | error | Interface/type alias property is missing a TSDoc comment. |\n * | W001 | warning | TSDoc comment contains parse errors. |\n * | W002 | warning | Function body throws but has no `@throws` tag. |\n * | W003 | warning | `@deprecated` tag is present without explanation. |\n *\n * When `config.enforce.strict` is `true` all warnings are promoted to errors.\n *\n * @param config - The resolved {@link ForgeConfig} for the project.\n * @returns A {@link ForgeResult} describing which symbols passed or failed.\n * @public\n */\nexport async function enforce(config: ForgeConfig): Promise<ForgeResult> {\n\tconst start = Date.now();\n\tconst errors: ForgeError[] = [];\n\tconst warnings: ForgeWarning[] = [];\n\n\tconst walker = createWalker(config);\n\tconst allSymbols = walker.walk();\n\tconst symbols = filterByVisibility(allSymbols, config.enforce.minVisibility);\n\n\t/**\n\t * Emit a diagnostic. When `strict` is enabled every warning becomes an\n\t * error so the build gate fails hard.\n\t */\n\tfunction emit(\n\t\tseverity: \"error\" | \"warning\",\n\t\tcode: string,\n\t\tmessage: string,\n\t\tfilePath: string,\n\t\tline: number,\n\t\tcolumn: number,\n\t\tguidance?: { suggestedFix?: string; symbolName?: string; symbolKind?: string },\n\t): void {\n\t\tconst diag = { code, message, filePath, line, column, ...guidance };\n\t\tif (severity === \"error\" || config.enforce.strict) {\n\t\t\terrors.push(diag);\n\t\t} else {\n\t\t\twarnings.push(diag);\n\t\t}\n\t}\n\n\tfor (const symbol of symbols) {\n\t\tif (!symbol.exported) continue;\n\n\t\tconst isFunctionLike = symbol.kind === \"function\" || symbol.kind === \"method\";\n\n\t\t// E001 — Missing summary\n\t\tif (!hasSummary(symbol)) {\n\t\t\temit(\n\t\t\t\t\"error\",\n\t\t\t\t\"E001\",\n\t\t\t\t`Exported symbol \"${symbol.name}\" is missing a TSDoc summary comment.`,\n\t\t\t\tsymbol.filePath,\n\t\t\t\tsymbol.line,\n\t\t\t\tsymbol.column,\n\t\t\t\t{\n\t\t\t\t\tsuggestedFix: `/**\\n * [Description of ${symbol.name}]\\n */`,\n\t\t\t\t\tsymbolName: symbol.name,\n\t\t\t\t\tsymbolKind: symbol.kind,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\t// E002 — Undocumented parameters\n\t\tif (isFunctionLike) {\n\t\t\tconst missing = undocumentedParams(symbol);\n\t\t\tfor (const paramName of missing) {\n\t\t\t\temit(\n\t\t\t\t\t\"error\",\n\t\t\t\t\t\"E002\",\n\t\t\t\t\t`Parameter \"${paramName}\" of \"${symbol.name}\" is not documented with a @param tag.`,\n\t\t\t\t\tsymbol.filePath,\n\t\t\t\t\tsymbol.line,\n\t\t\t\t\tsymbol.column,\n\t\t\t\t\t{\n\t\t\t\t\t\tsuggestedFix: `@param ${paramName} - [Description of ${paramName}]`,\n\t\t\t\t\t\tsymbolName: symbol.name,\n\t\t\t\t\t\tsymbolKind: symbol.kind,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// E003 — Missing @returns\n\t\tif (isFunctionLike && missingReturns(symbol)) {\n\t\t\temit(\n\t\t\t\t\"error\",\n\t\t\t\t\"E003\",\n\t\t\t\t`\"${symbol.name}\" has a non-void return type but is missing a @returns tag.`,\n\t\t\t\tsymbol.filePath,\n\t\t\t\tsymbol.line,\n\t\t\t\tsymbol.column,\n\t\t\t\t{\n\t\t\t\t\tsuggestedFix: `@returns [Description of the return value]`,\n\t\t\t\t\tsymbolName: symbol.name,\n\t\t\t\t\tsymbolKind: symbol.kind,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\t// E004 — Missing @example\n\t\tif (isFunctionLike && symbol.documentation) {\n\t\t\tconst hasExample = (symbol.documentation.examples ?? []).length > 0;\n\t\t\tif (!hasExample) {\n\t\t\t\temit(\n\t\t\t\t\t\"error\",\n\t\t\t\t\t\"E004\",\n\t\t\t\t\t`Exported function \"${symbol.name}\" is missing an @example block. Add a fenced code block showing usage.`,\n\t\t\t\t\tsymbol.filePath,\n\t\t\t\t\tsymbol.line,\n\t\t\t\t\tsymbol.column,\n\t\t\t\t\t{\n\t\t\t\t\t\tsuggestedFix: `@example\\n * \\`\\`\\`typescript\\n * // Usage of ${symbol.name}\\n * ${symbol.name}();\\n * \\`\\`\\``,\n\t\t\t\t\t\tsymbolName: symbol.name,\n\t\t\t\t\t\tsymbolKind: symbol.kind,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// E006 — Class member missing documentation\n\t\t// E007 — Interface/type member missing documentation\n\t\tif (symbol.kind === \"class\" || symbol.kind === \"interface\") {\n\t\t\tconst errorCode = symbol.kind === \"class\" ? \"E006\" : \"E007\";\n\t\t\tfor (const child of symbol.children ?? []) {\n\t\t\t\tif (child.kind === \"property\" || child.kind === \"method\") {\n\t\t\t\t\tif (!hasSummary(child)) {\n\t\t\t\t\t\temit(\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\terrorCode,\n\t\t\t\t\t\t\t`Member \"${child.name}\" of ${symbol.kind} \"${symbol.name}\" is missing a TSDoc comment.`,\n\t\t\t\t\t\t\tchild.filePath,\n\t\t\t\t\t\t\tchild.line,\n\t\t\t\t\t\t\tchild.column,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuggestedFix: `/**\\n * [Description of ${child.name}]\\n */`,\n\t\t\t\t\t\t\t\tsymbolName: child.name,\n\t\t\t\t\t\t\t\tsymbolKind: child.kind,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// W003 — @deprecated without reason\n\t\tif (deprecatedWithoutReason(symbol)) {\n\t\t\temit(\n\t\t\t\t\"warning\",\n\t\t\t\t\"W003\",\n\t\t\t\t`\"${symbol.name}\" is marked @deprecated but provides no explanation.`,\n\t\t\t\tsymbol.filePath,\n\t\t\t\tsymbol.line,\n\t\t\t\tsymbol.column,\n\t\t\t\t{\n\t\t\t\t\tsymbolName: symbol.name,\n\t\t\t\t\tsymbolKind: symbol.kind,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// E005 — Missing @packageDocumentation on index.ts entry points\n\t// Group symbols by file to check if any index.ts file lacks @packageDocumentation.\n\tconst indexFiles = new Map<string, ForgeSymbol[]>();\n\tfor (const symbol of allSymbols) {\n\t\tif (symbol.filePath.endsWith(\"index.ts\")) {\n\t\t\tconst bucket = indexFiles.get(symbol.filePath) ?? [];\n\t\t\tbucket.push(symbol);\n\t\t\tindexFiles.set(symbol.filePath, bucket);\n\t\t}\n\t}\n\tfor (const [filePath, fileSymbols] of indexFiles) {\n\t\tconst hasPackageDoc = fileSymbols.some(\n\t\t\t(s) => s.documentation?.tags?.packageDocumentation !== undefined,\n\t\t);\n\t\tif (!hasPackageDoc) {\n\t\t\temit(\n\t\t\t\t\"error\",\n\t\t\t\t\"E005\",\n\t\t\t\t`Package entry point \"${filePath}\" is missing a @packageDocumentation TSDoc comment.`,\n\t\t\t\tfilePath,\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t\t{\n\t\t\t\t\tsuggestedFix: `/**\\n * @packageDocumentation\\n * [Package overview description]\\n */`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tconst success = errors.length === 0;\n\treturn { success, symbols: allSymbols, errors, warnings, duration: Date.now() - start };\n}\n","import type { ForgeResult } from \"@forge-ts/core\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Options that control how {@link formatResults} renders its output.\n * @public\n */\nexport interface FormatOptions {\n\t/** Emit ANSI colour escape sequences when `true`. */\n\tcolors: boolean;\n\t/**\n\t * When `true`, include the symbol's type signature alongside each\n\t * diagnostic so the reader has immediate context.\n\t */\n\tverbose: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// ANSI helpers\n// ---------------------------------------------------------------------------\n\n/** @internal */\nconst RESET = \"\\x1b[0m\";\n/** @internal */\nconst RED = \"\\x1b[31m\";\n/** @internal */\nconst YELLOW = \"\\x1b[33m\";\n/** @internal */\nconst BOLD = \"\\x1b[1m\";\n/** @internal */\nconst DIM = \"\\x1b[2m\";\n\n/** @internal */\nfunction colorize(text: string, color: string, useColors: boolean): string {\n\treturn useColors ? `${color}${text}${RESET}` : text;\n}\n\n/** @internal */\nfunction bold(text: string, useColors: boolean): string {\n\treturn useColors ? `${BOLD}${text}${RESET}` : text;\n}\n\n/** @internal */\nfunction dim(text: string, useColors: boolean): string {\n\treturn useColors ? `${DIM}${text}${RESET}` : text;\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\n/** @internal */\ninterface Diagnostic {\n\tcode: string;\n\tmessage: string;\n\tfilePath: string;\n\tline: number;\n\tcolumn: number;\n}\n\n/** @internal */\nfunction isError(code: string): boolean {\n\treturn code.startsWith(\"E\");\n}\n\n/** @internal */\nfunction renderDiagnostic(diag: Diagnostic, opts: FormatOptions): string {\n\tconst label = isError(diag.code)\n\t\t? colorize(`error[${diag.code}]`, RED, opts.colors)\n\t\t: colorize(`warning[${diag.code}]`, YELLOW, opts.colors);\n\n\tconst location = dim(`${diag.line}:${diag.column}`, opts.colors);\n\treturn ` ${label} ${diag.message} ${location}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Formats a {@link ForgeResult} into a human-readable string suitable for\n * printing to a terminal.\n *\n * Diagnostics are grouped by source file. Each file heading shows the\n * relative-ish path, followed by indented error and warning lines. A summary\n * line is appended at the end.\n *\n * @param result - The result produced by {@link enforce}.\n * @param options - Rendering options (colours, verbosity).\n * @returns A formatted string ready to write to stdout or stderr.\n * @public\n */\nexport function formatResults(result: ForgeResult, options: FormatOptions): string {\n\tconst allDiags: Diagnostic[] = [\n\t\t...result.errors.map((e) => ({ ...e })),\n\t\t...result.warnings.map((w) => ({ ...w })),\n\t];\n\n\tif (allDiags.length === 0) {\n\t\tconst msg = `No issues found across ${result.symbols.length} symbol(s).`;\n\t\treturn bold(msg, options.colors);\n\t}\n\n\t// Group by filePath\n\tconst byFile = new Map<string, Diagnostic[]>();\n\tfor (const diag of allDiags) {\n\t\tconst list = byFile.get(diag.filePath);\n\t\tif (list) {\n\t\t\tlist.push(diag);\n\t\t} else {\n\t\t\tbyFile.set(diag.filePath, [diag]);\n\t\t}\n\t}\n\n\tconst lines: string[] = [];\n\n\tfor (const [filePath, diags] of byFile) {\n\t\tlines.push(bold(filePath, options.colors));\n\n\t\t// Sort: errors before warnings, then by line\n\t\tconst sorted = [...diags].sort((a, b) => {\n\t\t\tconst aIsErr = isError(a.code) ? 0 : 1;\n\t\t\tconst bIsErr = isError(b.code) ? 0 : 1;\n\t\t\tif (aIsErr !== bIsErr) return aIsErr - bIsErr;\n\t\t\treturn a.line - b.line;\n\t\t});\n\n\t\tfor (const diag of sorted) {\n\t\t\tlines.push(renderDiagnostic(diag, options));\n\n\t\t\tif (options.verbose) {\n\t\t\t\t// Find the matching symbol to show its signature\n\t\t\t\tconst sym = result.symbols.find(\n\t\t\t\t\t(s) => s.filePath === diag.filePath && s.line === diag.line,\n\t\t\t\t);\n\t\t\t\tif (sym?.signature) {\n\t\t\t\t\tlines.push(dim(` signature: ${sym.signature}`, options.colors));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlines.push(\"\");\n\t}\n\n\t// Summary line\n\tconst errorCount = result.errors.length;\n\tconst warnCount = result.warnings.length;\n\tconst fileCount = byFile.size;\n\n\tconst errorPart =\n\t\terrorCount > 0\n\t\t\t? colorize(`${errorCount} error${errorCount !== 1 ? \"s\" : \"\"}`, RED, options.colors)\n\t\t\t: `0 errors`;\n\tconst warnPart =\n\t\twarnCount > 0\n\t\t\t? colorize(`${warnCount} warning${warnCount !== 1 ? \"s\" : \"\"}`, YELLOW, options.colors)\n\t\t\t: `0 warnings`;\n\tconst filePart = `${fileCount} file${fileCount !== 1 ? \"s\" : \"\"}`;\n\n\tlines.push(`${errorPart}, ${warnPart} in ${filePart}`);\n\n\treturn lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge-ts/enforcer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TSDoc enforcement linter for forge-ts",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "https://github.com/kryptobaseddev/forge-ts"
|
|
10
10
|
},
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@
|
|
27
|
+
"@forge-ts/core": "0.3.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsup": "^8.3.5",
|