@forge-ts/enforcer 0.3.0 → 0.4.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/index.d.ts +42 -2
- package/dist/index.js +110 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import { ForgeConfig, ForgeResult } from '@forge-ts/core';
|
|
1
|
+
import { ForgeSymbol, ForgeConfig, ForgeResult } from '@forge-ts/core';
|
|
2
|
+
|
|
3
|
+
/** A detected usage of a deprecated symbol. */
|
|
4
|
+
interface DeprecatedUsage {
|
|
5
|
+
/** The deprecated symbol being consumed. */
|
|
6
|
+
deprecatedSymbol: string;
|
|
7
|
+
/** The package that exports the deprecated symbol. */
|
|
8
|
+
sourcePackage: string;
|
|
9
|
+
/** The file importing the deprecated symbol. */
|
|
10
|
+
consumingFile: string;
|
|
11
|
+
/** Line number of the import. */
|
|
12
|
+
line: number;
|
|
13
|
+
/** The deprecation message. */
|
|
14
|
+
deprecationMessage: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Scans symbols for imports of deprecated exports from other packages.
|
|
18
|
+
*
|
|
19
|
+
* @param symbols - All symbols from the walker across the entire project.
|
|
20
|
+
* @returns Array of deprecated usages found.
|
|
21
|
+
*/
|
|
22
|
+
declare function findDeprecatedUsages(symbols: ForgeSymbol[]): DeprecatedUsage[];
|
|
2
23
|
|
|
3
24
|
/**
|
|
4
25
|
* Runs the TSDoc enforcement pass against a project.
|
|
@@ -25,6 +46,16 @@ import { ForgeConfig, ForgeResult } from '@forge-ts/core';
|
|
|
25
46
|
*
|
|
26
47
|
* @param config - The resolved {@link ForgeConfig} for the project.
|
|
27
48
|
* @returns A {@link ForgeResult} describing which symbols passed or failed.
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { loadConfig } from "@forge-ts/core";
|
|
52
|
+
* import { enforce } from "@forge-ts/enforcer";
|
|
53
|
+
* const config = await loadConfig();
|
|
54
|
+
* const result = await enforce(config);
|
|
55
|
+
* if (!result.success) {
|
|
56
|
+
* console.error(`${result.errors.length} errors found`);
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
28
59
|
* @public
|
|
29
60
|
*/
|
|
30
61
|
declare function enforce(config: ForgeConfig): Promise<ForgeResult>;
|
|
@@ -53,8 +84,17 @@ interface FormatOptions {
|
|
|
53
84
|
* @param result - The result produced by {@link enforce}.
|
|
54
85
|
* @param options - Rendering options (colours, verbosity).
|
|
55
86
|
* @returns A formatted string ready to write to stdout or stderr.
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* import { enforce } from "@forge-ts/enforcer";
|
|
90
|
+
* import { formatResults } from "@forge-ts/enforcer";
|
|
91
|
+
* import { loadConfig } from "@forge-ts/core";
|
|
92
|
+
* const config = await loadConfig();
|
|
93
|
+
* const result = await enforce(config);
|
|
94
|
+
* console.log(formatResults(result, { colors: true, verbose: false }));
|
|
95
|
+
* ```
|
|
56
96
|
* @public
|
|
57
97
|
*/
|
|
58
98
|
declare function formatResults(result: ForgeResult, options: FormatOptions): string;
|
|
59
99
|
|
|
60
|
-
export { type FormatOptions, enforce, formatResults };
|
|
100
|
+
export { type DeprecatedUsage, type FormatOptions, enforce, findDeprecatedUsages, formatResults };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
// src/deprecation-tracker.ts
|
|
2
|
+
function findDeprecatedUsages(symbols) {
|
|
3
|
+
const deprecatedExports = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const symbol of symbols) {
|
|
5
|
+
if (symbol.exported && symbol.documentation?.deprecated) {
|
|
6
|
+
deprecatedExports.set(symbol.name, {
|
|
7
|
+
sourceFile: symbol.filePath,
|
|
8
|
+
message: symbol.documentation.deprecated
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (deprecatedExports.size === 0) return [];
|
|
13
|
+
const usages = [];
|
|
14
|
+
for (const symbol of symbols) {
|
|
15
|
+
const links = symbol.documentation?.links ?? [];
|
|
16
|
+
for (const link of links) {
|
|
17
|
+
const deprecated = deprecatedExports.get(link.target);
|
|
18
|
+
if (deprecated && deprecated.sourceFile !== symbol.filePath) {
|
|
19
|
+
const sourcePackage = extractPackageName(deprecated.sourceFile);
|
|
20
|
+
const consumingPackage = extractPackageName(symbol.filePath);
|
|
21
|
+
if (sourcePackage !== consumingPackage) {
|
|
22
|
+
usages.push({
|
|
23
|
+
deprecatedSymbol: link.target,
|
|
24
|
+
sourcePackage,
|
|
25
|
+
consumingFile: symbol.filePath,
|
|
26
|
+
line: link.line,
|
|
27
|
+
deprecationMessage: deprecated.message
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return usages;
|
|
34
|
+
}
|
|
35
|
+
function extractPackageName(filePath) {
|
|
36
|
+
const match = filePath.match(/packages\/([^/]+)\//);
|
|
37
|
+
return match?.[1] ?? "root";
|
|
38
|
+
}
|
|
39
|
+
|
|
1
40
|
// src/enforcer.ts
|
|
2
41
|
import {
|
|
3
42
|
createWalker,
|
|
@@ -56,6 +95,15 @@ function deprecatedWithoutReason(symbol) {
|
|
|
56
95
|
if (deprecated === void 0) return false;
|
|
57
96
|
return deprecated === "true" || deprecated.trim().length === 0;
|
|
58
97
|
}
|
|
98
|
+
var RULE_MAP = {
|
|
99
|
+
E001: "require-summary",
|
|
100
|
+
E002: "require-param",
|
|
101
|
+
E003: "require-returns",
|
|
102
|
+
E004: "require-example",
|
|
103
|
+
E005: "require-package-doc",
|
|
104
|
+
E006: "require-class-member-doc",
|
|
105
|
+
E007: "require-interface-member-doc"
|
|
106
|
+
};
|
|
59
107
|
async function enforce(config) {
|
|
60
108
|
const start = Date.now();
|
|
61
109
|
const errors = [];
|
|
@@ -63,12 +111,25 @@ async function enforce(config) {
|
|
|
63
111
|
const walker = createWalker(config);
|
|
64
112
|
const allSymbols = walker.walk();
|
|
65
113
|
const symbols = filterByVisibility(allSymbols, config.enforce.minVisibility);
|
|
66
|
-
function emit(
|
|
114
|
+
function emit(code, message, filePath, line, column, guidance) {
|
|
115
|
+
const ruleKey = RULE_MAP[code];
|
|
116
|
+
if (ruleKey !== void 0) {
|
|
117
|
+
const configuredSeverity = config.enforce.rules[ruleKey];
|
|
118
|
+
if (configuredSeverity === "off") return;
|
|
119
|
+
const effectiveSeverity = config.enforce.strict ? "error" : configuredSeverity;
|
|
120
|
+
const diag2 = { code, message, filePath, line, column, ...guidance };
|
|
121
|
+
if (effectiveSeverity === "error") {
|
|
122
|
+
errors.push(diag2);
|
|
123
|
+
} else {
|
|
124
|
+
warnings.push(diag2);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
67
128
|
const diag = { code, message, filePath, line, column, ...guidance };
|
|
68
|
-
if (
|
|
69
|
-
errors.push(diag);
|
|
70
|
-
} else {
|
|
129
|
+
if (code.startsWith("W") && !config.enforce.strict) {
|
|
71
130
|
warnings.push(diag);
|
|
131
|
+
} else {
|
|
132
|
+
errors.push(diag);
|
|
72
133
|
}
|
|
73
134
|
}
|
|
74
135
|
for (const symbol of symbols) {
|
|
@@ -76,7 +137,6 @@ async function enforce(config) {
|
|
|
76
137
|
const isFunctionLike = symbol.kind === "function" || symbol.kind === "method";
|
|
77
138
|
if (!hasSummary(symbol)) {
|
|
78
139
|
emit(
|
|
79
|
-
"error",
|
|
80
140
|
"E001",
|
|
81
141
|
`Exported symbol "${symbol.name}" is missing a TSDoc summary comment.`,
|
|
82
142
|
symbol.filePath,
|
|
@@ -95,7 +155,6 @@ async function enforce(config) {
|
|
|
95
155
|
const missing = undocumentedParams(symbol);
|
|
96
156
|
for (const paramName of missing) {
|
|
97
157
|
emit(
|
|
98
|
-
"error",
|
|
99
158
|
"E002",
|
|
100
159
|
`Parameter "${paramName}" of "${symbol.name}" is not documented with a @param tag.`,
|
|
101
160
|
symbol.filePath,
|
|
@@ -111,7 +170,6 @@ async function enforce(config) {
|
|
|
111
170
|
}
|
|
112
171
|
if (isFunctionLike && missingReturns(symbol)) {
|
|
113
172
|
emit(
|
|
114
|
-
"error",
|
|
115
173
|
"E003",
|
|
116
174
|
`"${symbol.name}" has a non-void return type but is missing a @returns tag.`,
|
|
117
175
|
symbol.filePath,
|
|
@@ -128,7 +186,6 @@ async function enforce(config) {
|
|
|
128
186
|
const hasExample = (symbol.documentation.examples ?? []).length > 0;
|
|
129
187
|
if (!hasExample) {
|
|
130
188
|
emit(
|
|
131
|
-
"error",
|
|
132
189
|
"E004",
|
|
133
190
|
`Exported function "${symbol.name}" is missing an @example block. Add a fenced code block showing usage.`,
|
|
134
191
|
symbol.filePath,
|
|
@@ -152,7 +209,6 @@ async function enforce(config) {
|
|
|
152
209
|
if (child.kind === "property" || child.kind === "method") {
|
|
153
210
|
if (!hasSummary(child)) {
|
|
154
211
|
emit(
|
|
155
|
-
"error",
|
|
156
212
|
errorCode,
|
|
157
213
|
`Member "${child.name}" of ${symbol.kind} "${symbol.name}" is missing a TSDoc comment.`,
|
|
158
214
|
child.filePath,
|
|
@@ -172,7 +228,6 @@ async function enforce(config) {
|
|
|
172
228
|
}
|
|
173
229
|
if (deprecatedWithoutReason(symbol)) {
|
|
174
230
|
emit(
|
|
175
|
-
"warning",
|
|
176
231
|
"W003",
|
|
177
232
|
`"${symbol.name}" is marked @deprecated but provides no explanation.`,
|
|
178
233
|
symbol.filePath,
|
|
@@ -199,7 +254,6 @@ async function enforce(config) {
|
|
|
199
254
|
);
|
|
200
255
|
if (!hasPackageDoc) {
|
|
201
256
|
emit(
|
|
202
|
-
"error",
|
|
203
257
|
"E005",
|
|
204
258
|
`Package entry point "${filePath}" is missing a @packageDocumentation TSDoc comment.`,
|
|
205
259
|
filePath,
|
|
@@ -214,6 +268,50 @@ async function enforce(config) {
|
|
|
214
268
|
);
|
|
215
269
|
}
|
|
216
270
|
}
|
|
271
|
+
const knownSymbols = /* @__PURE__ */ new Set();
|
|
272
|
+
for (const s of allSymbols) {
|
|
273
|
+
knownSymbols.add(s.name);
|
|
274
|
+
if (s.children) {
|
|
275
|
+
for (const child of s.children) {
|
|
276
|
+
knownSymbols.add(`${s.name}.${child.name}`);
|
|
277
|
+
knownSymbols.add(child.name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (const symbol of allSymbols) {
|
|
282
|
+
const docLinks = symbol.documentation?.links ?? [];
|
|
283
|
+
for (const link of docLinks) {
|
|
284
|
+
if (!knownSymbols.has(link.target)) {
|
|
285
|
+
emit(
|
|
286
|
+
"E008",
|
|
287
|
+
`{@link ${link.target}} in "${symbol.name}" references a symbol that does not exist in this project.`,
|
|
288
|
+
symbol.filePath,
|
|
289
|
+
link.line,
|
|
290
|
+
symbol.column,
|
|
291
|
+
{
|
|
292
|
+
suggestedFix: `Remove or update the {@link ${link.target}} reference to point to an existing symbol.`,
|
|
293
|
+
symbolName: symbol.name,
|
|
294
|
+
symbolKind: symbol.kind
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const deprecatedUsages = findDeprecatedUsages(allSymbols);
|
|
301
|
+
for (const usage of deprecatedUsages) {
|
|
302
|
+
emit(
|
|
303
|
+
"W004",
|
|
304
|
+
`Import of deprecated symbol "${usage.deprecatedSymbol}" from package "${usage.sourcePackage}": ${usage.deprecationMessage}`,
|
|
305
|
+
usage.consumingFile,
|
|
306
|
+
usage.line,
|
|
307
|
+
0,
|
|
308
|
+
{
|
|
309
|
+
suggestedFix: `Replace usage of "${usage.deprecatedSymbol}" with its recommended replacement.`,
|
|
310
|
+
symbolName: usage.deprecatedSymbol,
|
|
311
|
+
symbolKind: "variable"
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
217
315
|
const success = errors.length === 0;
|
|
218
316
|
return { success, symbols: allSymbols, errors, warnings, duration: Date.now() - start };
|
|
219
317
|
}
|
|
@@ -292,6 +390,7 @@ function formatResults(result, options) {
|
|
|
292
390
|
}
|
|
293
391
|
export {
|
|
294
392
|
enforce,
|
|
393
|
+
findDeprecatedUsages,
|
|
295
394
|
formatResults
|
|
296
395
|
};
|
|
297
396
|
//# sourceMappingURL=index.js.map
|
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 \"@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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/deprecation-tracker.ts","../src/enforcer.ts","../src/formatter.ts"],"sourcesContent":["import type { ForgeSymbol } from \"@forge-ts/core\";\n\n/** A detected usage of a deprecated symbol. */\nexport interface DeprecatedUsage {\n\t/** The deprecated symbol being consumed. */\n\tdeprecatedSymbol: string;\n\t/** The package that exports the deprecated symbol. */\n\tsourcePackage: string;\n\t/** The file importing the deprecated symbol. */\n\tconsumingFile: string;\n\t/** Line number of the import. */\n\tline: number;\n\t/** The deprecation message. */\n\tdeprecationMessage: string;\n}\n\n/**\n * Scans symbols for imports of deprecated exports from other packages.\n *\n * @param symbols - All symbols from the walker across the entire project.\n * @returns Array of deprecated usages found.\n */\nexport function findDeprecatedUsages(symbols: ForgeSymbol[]): DeprecatedUsage[] {\n\t// Build a set of deprecated symbol names with their source info\n\tconst deprecatedExports = new Map<string, { sourceFile: string; message: string }>();\n\n\tfor (const symbol of symbols) {\n\t\tif (symbol.exported && symbol.documentation?.deprecated) {\n\t\t\tdeprecatedExports.set(symbol.name, {\n\t\t\t\tsourceFile: symbol.filePath,\n\t\t\t\tmessage: symbol.documentation.deprecated,\n\t\t\t});\n\t\t}\n\t}\n\n\tif (deprecatedExports.size === 0) return [];\n\n\t// For each symbol that has a {@link} or references a deprecated name,\n\t// check if it's from a different package\n\tconst usages: DeprecatedUsage[] = [];\n\n\t// Check links\n\tfor (const symbol of symbols) {\n\t\tconst links = symbol.documentation?.links ?? [];\n\t\tfor (const link of links) {\n\t\t\tconst deprecated = deprecatedExports.get(link.target);\n\t\t\tif (deprecated && deprecated.sourceFile !== symbol.filePath) {\n\t\t\t\t// Different file references a deprecated symbol\n\t\t\t\tconst sourcePackage = extractPackageName(deprecated.sourceFile);\n\t\t\t\tconst consumingPackage = extractPackageName(symbol.filePath);\n\n\t\t\t\tif (sourcePackage !== consumingPackage) {\n\t\t\t\t\tusages.push({\n\t\t\t\t\t\tdeprecatedSymbol: link.target,\n\t\t\t\t\t\tsourcePackage,\n\t\t\t\t\t\tconsumingFile: symbol.filePath,\n\t\t\t\t\t\tline: link.line,\n\t\t\t\t\t\tdeprecationMessage: deprecated.message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn usages;\n}\n\n/** Extract package name from file path (e.g., \"packages/core/src/...\" -> \"core\"). */\nfunction extractPackageName(filePath: string): string {\n\tconst match = filePath.match(/packages\\/([^/]+)\\//);\n\treturn match?.[1] ?? \"root\";\n}\n","import {\n\tcreateWalker,\n\ttype EnforceRules,\n\ttype ForgeConfig,\n\ttype ForgeError,\n\ttype ForgeResult,\n\ttype ForgeSymbol,\n\ttype ForgeWarning,\n\tfilterByVisibility,\n} from \"@forge-ts/core\";\nimport { findDeprecatedUsages } from \"./deprecation-tracker.js\";\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// Rule map\n// ---------------------------------------------------------------------------\n\n/**\n * Maps E-code strings to their corresponding {@link EnforceRules} key.\n * @internal\n */\nconst RULE_MAP: Record<string, keyof EnforceRules> = {\n\tE001: \"require-summary\",\n\tE002: \"require-param\",\n\tE003: \"require-returns\",\n\tE004: \"require-example\",\n\tE005: \"require-package-doc\",\n\tE006: \"require-class-member-doc\",\n\tE007: \"require-interface-member-doc\",\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 * @example\n * ```typescript\n * import { loadConfig } from \"@forge-ts/core\";\n * import { enforce } from \"@forge-ts/enforcer\";\n * const config = await loadConfig();\n * const result = await enforce(config);\n * if (!result.success) {\n * console.error(`${result.errors.length} errors found`);\n * }\n * ```\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. The configured per-rule severity determines whether\n\t * the diagnostic is an error or warning; \"off\" suppresses it entirely.\n\t * When `strict` is enabled every warning is promoted to an error.\n\t */\n\tfunction emit(\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 ruleKey = RULE_MAP[code];\n\n\t\t// For rule codes tracked in the map, honour the per-rule severity.\n\t\tif (ruleKey !== undefined) {\n\t\t\tconst configuredSeverity = config.enforce.rules[ruleKey];\n\t\t\tif (configuredSeverity === \"off\") return;\n\t\t\tconst effectiveSeverity = config.enforce.strict ? \"error\" : configuredSeverity;\n\t\t\tconst diag = { code, message, filePath, line, column, ...guidance };\n\t\t\tif (effectiveSeverity === \"error\") {\n\t\t\t\terrors.push(diag);\n\t\t\t} else {\n\t\t\t\twarnings.push(diag);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// For codes not in the map (W003, E008, etc.) fall back to the old\n\t\t// behaviour: always emit, respect strict mode for warnings.\n\t\tconst diag = { code, message, filePath, line, column, ...guidance };\n\t\t// Codes starting with \"W\" are warnings by default.\n\t\tif (code.startsWith(\"W\") && !config.enforce.strict) {\n\t\t\twarnings.push(diag);\n\t\t} else {\n\t\t\terrors.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\"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\"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\"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\"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\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\"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\"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\t// E008 — Dead {@link} references\n\t// Build a set of all known symbol names (simple and qualified).\n\tconst knownSymbols = new Set<string>();\n\tfor (const s of allSymbols) {\n\t\tknownSymbols.add(s.name);\n\t\tif (s.children) {\n\t\t\tfor (const child of s.children) {\n\t\t\t\tknownSymbols.add(`${s.name}.${child.name}`);\n\t\t\t\tknownSymbols.add(child.name);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check all {@link} references across every symbol (not just filtered ones).\n\tfor (const symbol of allSymbols) {\n\t\tconst docLinks = symbol.documentation?.links ?? [];\n\t\tfor (const link of docLinks) {\n\t\t\tif (!knownSymbols.has(link.target)) {\n\t\t\t\temit(\n\t\t\t\t\t\"E008\",\n\t\t\t\t\t`{@link ${link.target}} in \"${symbol.name}\" references a symbol that does not exist in this project.`,\n\t\t\t\t\tsymbol.filePath,\n\t\t\t\t\tlink.line,\n\t\t\t\t\tsymbol.column,\n\t\t\t\t\t{\n\t\t\t\t\t\tsuggestedFix: `Remove or update the {@link ${link.target}} reference to point to an existing symbol.`,\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\t}\n\n\t// W004 — Cross-package deprecated symbol usage\n\tconst deprecatedUsages = findDeprecatedUsages(allSymbols);\n\tfor (const usage of deprecatedUsages) {\n\t\temit(\n\t\t\t\"W004\",\n\t\t\t`Import of deprecated symbol \"${usage.deprecatedSymbol}\" from package \"${usage.sourcePackage}\": ${usage.deprecationMessage}`,\n\t\t\tusage.consumingFile,\n\t\t\tusage.line,\n\t\t\t0,\n\t\t\t{\n\t\t\t\tsuggestedFix: `Replace usage of \"${usage.deprecatedSymbol}\" with its recommended replacement.`,\n\t\t\t\tsymbolName: usage.deprecatedSymbol,\n\t\t\t\tsymbolKind: \"variable\",\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 * @example\n * ```typescript\n * import { enforce } from \"@forge-ts/enforcer\";\n * import { formatResults } from \"@forge-ts/enforcer\";\n * import { loadConfig } from \"@forge-ts/core\";\n * const config = await loadConfig();\n * const result = await enforce(config);\n * console.log(formatResults(result, { colors: true, verbose: false }));\n * ```\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":["diag"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge-ts/enforcer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TSDoc enforcement linter for forge-ts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@forge-ts/core": "0.
|
|
27
|
+
"@forge-ts/core": "0.4.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsup": "^8.3.5",
|