@webpieces/nx-webpieces-rules 0.4.863 → 0.4.865
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.865",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/api-doc-model": "0.4.
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
-
"@webpieces/eslint-rules": "0.4.
|
|
26
|
-
"@webpieces/rules-config": "0.4.
|
|
23
|
+
"@webpieces/api-doc-model": "0.4.865",
|
|
24
|
+
"@webpieces/core-util": "0.4.865",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.865",
|
|
26
|
+
"@webpieces/rules-config": "0.4.865",
|
|
27
27
|
"madge": "8.0.0",
|
|
28
|
-
"@webpieces/rules-sdk": "0.4.
|
|
28
|
+
"@webpieces/rules-sdk": "0.4.865"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@nx/devkit": ">=18.0.0"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@webpieces/tooling-testkit": "0.4.
|
|
44
|
+
"@webpieces/tooling-testkit": "0.4.865"
|
|
45
45
|
},
|
|
46
46
|
"types": "./src/index.d.ts",
|
|
47
47
|
"bin": {
|
|
@@ -66,7 +66,7 @@ const api_doc_rules_1 = require("./api-doc-rules");
|
|
|
66
66
|
const api_doc_rules_verdicts_1 = require("./api-doc-rules-verdicts");
|
|
67
67
|
const wire_closure_1 = require("./wire-closure");
|
|
68
68
|
/** `@ApiPath(` at COLUMN ZERO — a docstring that TALKS about a contract declares none. */
|
|
69
|
-
const DECLARES_CONTRACT =
|
|
69
|
+
const DECLARES_CONTRACT = /@/;
|
|
70
70
|
/** ONE contract file, and which of the two rules apply to the project that owns it. */
|
|
71
71
|
class ContractFile {
|
|
72
72
|
absPath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-doc-rules-scan.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-scan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `api-rules-for-openapi` and `api-rules-for-mcp` (#1011) — the CI half of \"is this contract\n * publishable\", on every `@ApiPath` class IN SCOPE, `@ApiType` or not.\n *\n * \"In scope\" is the rule's `mode` (#1017): `AFFECTED_PROJECT` scans the contracts of the projects the\n * diff touched — the granularity nx already builds at, and the mode a consumer normally picks —\n * while `RUN_EVERY_TIME` scans the whole workspace for a migration sweep. `ApiDocRule.coversProject`\n * is the one place that answers it, for both rules.\n *\n * ## The acceptance contract, and why this file drives the generator instead of copying it\n *\n * The rules exist so that ADDING `@ApiType(...)` (and `@WpMcpTool`) to a contract that passes them\n * always works. That is only true if \"expressible\" has exactly ONE definition, so this scan runs the\n * generator's own code — `ApiDocExtractor` / `TypeResolver` from `@webpieces/api-doc-model` for the\n * OpenAPI half, and `McpSchemaRenderer` tool-by-tool for the MCP half. A second implementation of\n * \"what can be published\" would drift from the first on the release that improved either one, and\n * the drift would be silent: the rules would stay green while generation started failing. The two\n * packages ship on the same release train, so the dependency is in lockstep by construction.\n *\n * Two things here are STRICTER than the generator, deliberately, and both are publishing rules\n * rather than expressibility ones (being stricter cannot break the acceptance contract — it can only\n * refuse something that would have generated):\n *\n * - an `unknown` VALUE TYPE anywhere (`Record<string, unknown>`, `unknown[]`, a bare `unknown`\n * field). The extractor maps it to a primitive and the generator publishes `{}`, which in JSON\n * Schema means \"anything\" — a partner-facing field with no shape, which is the defect the\n * unmapped guard exists for, arriving through a door the guard does not watch.\n * - an `rpc` or `external` endpoint whose response is `void`. Fire-and-forget is the CONTRACT of a\n * `cloudtasks` or `cron` endpoint and is allowed there; an endpoint somebody WAITS on that answers\n * nothing can never gain a field without a breaking change, where a named empty response object\n * grows additively forever (#1017 — #1016 read this narrowly as rpc-only because `external` was\n * unstated).\n *\n * ## Why it lives in the rules engine and not in the doc parser\n *\n * `@webpieces/api-doc-model` is only ever pointed at contracts somebody chose to publish. `@ApiType`\n * is a PUBLISHING decision added later, on purpose — so a shape rule that only ran on contracts which\n * had already opted in would let a team discover, six months afterwards, that the type was never\n * expressible, by which time it is in partners' generated clients. So every check below runs on every\n * contract in scope, opted in or not — `@ApiType` narrows nothing here.\n *\n * ## The wire closure (#1064, D4)\n *\n * Every named type a contract reaches must be DECLARED in a `role:api-lib` project and end in the\n * suffix its `required-type-suffix` entry demands — see `wire-closure.ts`. It is a shared defect: a type\n * declared in a general library is wrong on the wire whichever document it would have been in.\n *\n * ## Root-level unions are NOT re-checked here\n *\n * `no-root-union-api-type` (#1009) already refuses them, workspace-wide, with its own config key and\n * its own per-site hatch. One implementation. The MCP half still reports one when it meets it,\n * because `McpSchemaRenderer` refuses it as its own backstop and this scan reports whatever the\n * renderer says — which is the correct division: the OpenAPI document publishes a root union\n * perfectly well, and only a tool schema cannot carry one.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n ApiDocExtractionError,\n ApiDocExtractor,\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { ProjectInfo } from '../project-info';\nimport { collectTsFiles, isTestFile } from './api-ast';\nimport {\n ApiContractDefect,\n ApiDocRule,\n ApiDocRulesFindings,\n ApiRuleFindings,\n DisableComment,\n McpExclusion,\n MCP_RULE,\n OPENAPI_RULE,\n} from './api-doc-rules';\nimport {\n ANSWERING_KINDS,\n ContractLines,\n unknownValueCure,\n VOID_RPC_CURE,\n carriesUnknown,\n classifyUnmapped,\n contractLinesOf,\n contractNamesIn,\n isVoidLike,\n toolFailures,\n} from './api-doc-rules-verdicts';\nimport { WireClosure, WireClosureRule } from './wire-closure';\n\n/** `@ApiPath(` at COLUMN ZERO — a docstring that TALKS about a contract declares none. */\nconst DECLARES_CONTRACT = /^@ApiPath\\(/m;\n\n/** ONE contract file, and which of the two rules apply to the project that owns it. */\nclass ContractFile {\n constructor(\n public readonly absPath: string,\n public readonly openApi: boolean,\n public readonly mcp: boolean,\n ) {}\n}\n\n/** Where one declaration sits, already split out of the extractor's `File.ts:LINE:COL` spelling. */\nclass Site {\n constructor(\n public readonly absPath: string,\n public readonly line: number,\n ) {}\n\n /** `path/to/File.ts:LINE`, workspace-relative — what a refusal prints. */\n relativeTo(workspaceRoot: string): string {\n return `${path.relative(workspaceRoot, this.absPath)}:${this.line}`;\n }\n\n /** `abs/File.ts:12:5` -> a Site. An unparseable one falls back to line 1 of `fallback`. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static parse(location: string, fallback: string): Site {\n const match = location.match(/^(.*):(\\d+):\\d+$/);\n if (match === null) return new Site(fallback, 1);\n return new Site(match[1], Number(match[2]));\n }\n}\n\n/**\n * ONE rule's accumulator. It is what applies the per-site disable, so the disable semantics live in\n * exactly one place and cannot differ between the two rules.\n */\nclass DefectCollector {\n private readonly violations: ApiContractDefect[] = [];\n private readonly reasonless: ApiContractDefect[] = [];\n\n constructor(private readonly ruleName: string) {}\n\n /** The rule this collector reports under — what a shared defect's cure has to name. */\n rule(): string {\n return this.ruleName;\n }\n\n add(defect: ApiContractDefect, site: Site): void {\n const disable = DisableComment.readAt(site.absPath, site.line, this.ruleName);\n if (disable === undefined) {\n this.violations.push(defect);\n return;\n }\n if (!disable.hasReason) this.reasonless.push(defect);\n }\n\n findings(): ApiRuleFindings {\n return new ApiRuleFindings(\n DefectCollector.externalFirst(this.violations),\n DefectCollector.externalFirst(this.reasonless),\n );\n }\n\n /**\n * Partner-facing contracts first. The same defect is a different size depending on who reads the\n * document it would have been in, and a list that buries the `external-customer` ones among\n * thirty internal ones has hidden the only urgent line in it.\n */\n // webpieces-disable no-function-outside-class -- private static ordering of this class\n private static externalFirst(found: readonly ApiContractDefect[]): ApiContractDefect[] {\n return [...found].sort(\n (a: ApiContractDefect, b: ApiContractDefect) =>\n Number(b.isExternal()) - Number(a.isExternal()),\n );\n }\n}\n\n/**\n * Routes a defect to the rule that owns it.\n *\n * Every OpenAPI-level defect ALSO blocks MCP, so it is reported by whichever rule is running —\n * `api-rules-for-openapi` when that one is on, and `api-rules-for-mcp` alone when it is not. It is\n * never reported twice: a team running both would otherwise read every shared defect in two places\n * and have to work out that they are one.\n */\nclass DefectSink {\n constructor(\n private readonly openApi: DefectCollector | undefined,\n private readonly mcp: DefectCollector | undefined,\n ) {}\n\n /**\n * A defect that blocks the OpenAPI document, and therefore every tool on it too.\n *\n * The defect is BUILT from the rule that ends up reporting it, not handed in ready-made, because\n * a shared defect does not know in advance which rule will carry it: `api-rules-for-openapi` when\n * that one runs, and `api-rules-for-mcp` alone when it does not. A cure that named a fixed rule\n * would, on the mcp-only configuration, prescribe a `// webpieces-disable` line the collector\n * reading that site does not look for.\n */\n shared(build: (rule: string) => ApiContractDefect, site: Site): void {\n const target = this.openApi ?? this.mcp;\n if (target === undefined) return;\n target.add(build(target.rule()), site);\n }\n\n /** A defect that blocks ONE tool and nothing else. */\n mcpOnly(defect: ApiContractDefect, site: Site): void {\n this.mcp?.add(defect, site);\n }\n\n anyRuleRuns(): boolean {\n return this.openApi !== undefined || this.mcp !== undefined;\n }\n\n /** True when `api-rules-for-mcp` applies to this file — what the exclusion list is scoped to. */\n mcpRuns(): boolean {\n return this.mcp !== undefined;\n }\n}\n\n/**\n * Walks every project's `src`, extracts every `@ApiPath` contract with the generator's own\n * extractor, and judges the result against the two rules.\n *\n * ONE `ts.Program` over every contract file in the workspace, because a DTO a contract reaches\n * routinely lives in another project and the checker has to be able to follow the import — the same\n * reason the repo sweep in `@webpieces/api-doc-model`'s own spec builds one program rather than one\n * per file.\n */\nexport class ApiDocRulesScan {\n /**\n * Every `@InvalidEndpointForMcp` endpoint met on a file the MCP rule applies to.\n *\n * Collected even on a run with no findings at all, because restating them IS the feature: the\n * alternative considered in #1014 was a one-off warning when somebody adds one, and a warning\n * printed once at the moment of the decision is read by the one person who already knows.\n */\n private readonly exclusions: McpExclusion[] = [];\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** OFF unless a caller read otherwise out of webpieces.config.json, which MUST state it. */\n private readonly openApiRule: ApiDocRule = ApiDocRule.off(OPENAPI_RULE),\n private readonly mcpRule: ApiDocRule = ApiDocRule.off(MCP_RULE),\n /** The suffix half of the wire closure; the role half always runs when either rule does. */\n private readonly wireClosureRule: WireClosureRule = WireClosureRule.withoutSuffixes(),\n ) {}\n\n run(): ApiDocRulesFindings {\n if (!this.openApiRule.enabled && !this.mcpRule.enabled) return ApiDocRulesFindings.empty();\n const files = this.contractFiles();\n if (files.length === 0) return ApiDocRulesFindings.empty();\n\n const openApi = this.openApiRule.enabled ? new DefectCollector(OPENAPI_RULE) : undefined;\n const mcp = this.mcpRule.enabled ? new DefectCollector(MCP_RULE) : undefined;\n const program = ts.createProgram(\n files.map((file: ContractFile) => file.absPath),\n this.compilerOptions(),\n );\n const toolNames = new Map<string, string>();\n const closure = new WireClosure(this.workspaceRoot, this.projectInfos, this.wireClosureRule);\n for (const file of files) {\n const sink = new DefectSink(\n file.openApi ? openApi : undefined,\n file.mcp ? mcp : undefined,\n );\n this.judgeFile(file, program, sink, toolNames, closure);\n }\n return new ApiDocRulesFindings(\n openApi?.findings() ?? new ApiRuleFindings([], []),\n mcp?.findings() ?? new ApiRuleFindings([], []),\n this.exclusions,\n );\n }\n\n /** Every contract in one file, or the ONE refusal that stopped the file being read at all. */\n private judgeFile(\n file: ContractFile,\n program: ts.Program,\n sink: DefectSink,\n toolNames: Map<string, string>,\n closure: WireClosure,\n ): void {\n if (!sink.anyRuleRuns()) return;\n const source = program.getSourceFile(file.absPath);\n if (source === undefined) return;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an extraction refusal IS a finding; it is reported, not propagated\n try {\n for (const model of new ApiDocExtractor().extractAllFrom(program, source)) {\n this.judgeModel(model, source, sink, toolNames);\n this.judgeWireClosure(model, sink, source.fileName, closure);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof ApiDocExtractionError)) throw err;\n this.reportExtractionFailure(err, file, source, sink);\n }\n }\n\n /**\n * An extraction that REFUSED. Reported under the shared list because it stops BOTH documents:\n * `@Endpoint` arguments that cannot be constant-folded, a bound on a non-numeric field, and the\n * `@ApiType(..., MCP)` ⇔ `@WpMcpTool` biconditional all fail here, before a model exists.\n */\n private reportExtractionFailure(\n error: ApiDocExtractionError,\n file: ContractFile,\n source: ts.SourceFile,\n sink: DefectSink,\n ): void {\n const site = Site.parse(error.location, file.absPath);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n contractNamesIn(source)[0] ?? path.basename(file.absPath),\n '',\n `the contract cannot be read at all — ${error.message}`,\n site.relativeTo(this.workspaceRoot),\n error.cure,\n [],\n ),\n site,\n );\n }\n\n /** ONE contract: the shared OpenAPI checks, then the MCP-only ones. */\n private judgeModel(\n model: ApiDocModel,\n source: ts.SourceFile,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const lines = contractLinesOf(source, model.contractName);\n this.judgeUnmapped(model, sink, source.fileName);\n this.judgeUnknownValues(model, sink, source.fileName);\n this.judgeAnsweringResponses(model, source, lines, sink);\n this.judgeTools(model, source, lines, sink, toolNames);\n }\n\n /**\n * D4 — every named type the contract reaches is declared in a `role:api-lib` project and carries its\n * suffix. Shared: a type that should never have left a general library blocks every document.\n */\n private judgeWireClosure(model: ApiDocModel, sink: DefectSink, fallback: string, closure: WireClosure): void {\n for (const type of model.types.values()) {\n const site = Site.parse(type.location, fallback);\n for (const verdict of closure.judge(type, site.absPath)) {\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n '',\n verdict.what,\n site.relativeTo(this.workspaceRoot),\n verdict.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n }\n\n /** Everything `TypeResolver` could not represent — the generator's OWN verdict, re-worded. */\n private judgeUnmapped(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const unmapped of model.unmapped) {\n const site = Site.parse(unmapped.location, fallback);\n const verdict = classifyUnmapped(unmapped);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n '',\n verdict.what,\n site.relativeTo(this.workspaceRoot),\n verdict.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** `Record<string, unknown>`, `unknown[]`, `x: unknown` — by VALUE TYPE, never by spelling. */\n private judgeUnknownValues(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const type of model.types.values()) {\n for (const field of type.fields) {\n if (!carriesUnknown(field.type)) continue;\n const site = Site.parse(field.location, fallback);\n sink.shared(\n (rule: string): ApiContractDefect =>\n new ApiContractDefect(\n model.contractName,\n '',\n `'${type.name}.${field.name}' publishes an 'unknown' value, so the ` +\n 'document states no shape for it at all',\n site.relativeTo(this.workspaceRoot),\n unknownValueCure(rule),\n model.apiTypes,\n ),\n site,\n );\n }\n }\n }\n\n /**\n * An endpoint somebody WAITS ON must NAME a response DTO, even an empty one.\n *\n * `Promise<void>` on a `cloudtasks` or `cron` endpoint is the CONTRACT — fire-and-forget, nothing\n * to shape — and is allowed. On an `rpc` OR an `external` it is a one-way door: both are\n * synchronous request/response, an outside caller reads what comes back, and a `void` response\n * can never gain a field without breaking every generated client where `{}` grows additively\n * forever. This is a contract-EVOLUTION rule, which is why it is here and not in the MCP half: it\n * is worth having on an endpoint that never becomes a tool. See ANSWERING_KINDS (#1017).\n */\n private judgeAnsweringResponses(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n ): void {\n for (const endpoint of model.endpoints) {\n // `.some` and not `.includes`: the model's `kind` is a widened string, and the list is\n // typed EndpointKind so a kind that stops existing is a compile error here.\n if (!ANSWERING_KINDS.some((kind: string): boolean => kind === endpoint.kind)) continue;\n if (endpoint.response !== undefined && !isVoidLike(endpoint.response)) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n `an ${endpoint.kind} endpoint returns nothing a document can name (void, ` +\n 'unknown, or no declared return type)',\n site.relativeTo(this.workspaceRoot),\n VOID_RPC_CURE,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Everything `McpToolRegistry` refuses to boot on, plus whatever the renderer cannot render. */\n private judgeTools(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const renderer = new McpSchemaRenderer(model);\n for (const endpoint of model.endpoints) {\n if (endpoint.invalidForMcp !== undefined) {\n if (!sink.mcpRuns()) continue;\n // @InvalidEndpointForMcp IS the answer to \"could this be a tool\". The decorator\n // carries the argument, so the rule asks nothing further and no webpieces-disable is\n // needed — a suppression would be a second, weaker spelling of the same declaration.\n this.exclusions.push(\n new McpExclusion(\n model.contractName,\n endpoint.methodName,\n endpoint.invalidForMcp,\n new Site(\n source.fileName,\n lines.lineOf(endpoint.methodName),\n ).relativeTo(this.workspaceRoot),\n ),\n );\n continue;\n }\n if (endpoint.mcpTool === undefined) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n for (const failure of toolFailures(endpoint, renderer, toolNames, model)) {\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n failure.what,\n site.relativeTo(this.workspaceRoot),\n failure.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n for (const methodName of lines.toolsWithoutEndpoint) {\n const site = new Site(source.fileName, lines.lineOf(methodName));\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n methodName,\n 'carries @WpMcpTool but is not an @Endpoint, so it is not routed at all',\n site.relativeTo(this.workspaceRoot),\n \"Add @Endpoint(POST, '/path', READ, RPC) to it, or drop the @WpMcpTool.\",\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Every non-test `.ts` under a project's `src` whose text DECLARES an `@ApiPath` contract. */\n private contractFiles(): ContractFile[] {\n const found: ContractFile[] = [];\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n // ONE question, asked of the rule itself: `mode` (OFF / AFFECTED_PROJECT /\n // RUN_EVERY_TIME) and `allowedPaths` are both folded into coversProject, so the\n // affected-project narrowing cannot be honoured in one branch and skipped in the other.\n const openApi = this.openApiRule.coversProject(info.root);\n const mcp = this.mcpRule.coversProject(info.root);\n if (!openApi && !mcp) continue;\n const srcDir = path.join(path.resolve(this.workspaceRoot, info.root), 'src');\n if (!fs.existsSync(srcDir)) continue;\n for (const file of collectTsFiles(srcDir)) {\n if (isTestFile(file)) continue; // a fixture is not a published contract\n if (!DECLARES_CONTRACT.test(fs.readFileSync(file, 'utf8'))) continue;\n found.push(new ContractFile(file, openApi, mcp));\n }\n }\n return found.sort((a: ContractFile, b: ContractFile) => a.absPath.localeCompare(b.absPath));\n }\n\n /**\n * `tsconfig.base.json`'s options when the workspace has one, so an `@webpieces/*` import in a\n * contract RESOLVES and the checker can follow a DTO into another project. Without that the\n * resolver reports every cross-project type as unmapped, which would be a rule failing on its\n * own inability to read rather than on anything the author wrote.\n */\n private compilerOptions(): ts.CompilerOptions {\n const base = path.join(this.workspaceRoot, 'tsconfig.base.json');\n const declared = fs.existsSync(base)\n ? ts.parseJsonConfigFileContent(\n ts.readConfigFile(base, ts.sys.readFile).config,\n ts.sys,\n this.workspaceRoot,\n ).options\n : {};\n return {\n ...declared,\n noEmit: true,\n skipLibCheck: true,\n types: [],\n experimentalDecorators: true,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-doc-rules-scan.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-scan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `api-rules-for-openapi` and `api-rules-for-mcp` (#1011) — the CI half of \"is this contract\n * publishable\", on every `@ApiPath` class IN SCOPE, `@ApiType` or not.\n *\n * \"In scope\" is the rule's `mode` (#1017): `AFFECTED_PROJECT` scans the contracts of the projects the\n * diff touched — the granularity nx already builds at, and the mode a consumer normally picks —\n * while `RUN_EVERY_TIME` scans the whole workspace for a migration sweep. `ApiDocRule.coversProject`\n * is the one place that answers it, for both rules.\n *\n * ## The acceptance contract, and why this file drives the generator instead of copying it\n *\n * The rules exist so that ADDING `@ApiType(...)` (and `@WpMcpTool`) to a contract that passes them\n * always works. That is only true if \"expressible\" has exactly ONE definition, so this scan runs the\n * generator's own code — `ApiDocExtractor` / `TypeResolver` from `@webpieces/api-doc-model` for the\n * OpenAPI half, and `McpSchemaRenderer` tool-by-tool for the MCP half. A second implementation of\n * \"what can be published\" would drift from the first on the release that improved either one, and\n * the drift would be silent: the rules would stay green while generation started failing. The two\n * packages ship on the same release train, so the dependency is in lockstep by construction.\n *\n * Two things here are STRICTER than the generator, deliberately, and both are publishing rules\n * rather than expressibility ones (being stricter cannot break the acceptance contract — it can only\n * refuse something that would have generated):\n *\n * - an `unknown` VALUE TYPE anywhere (`Record<string, unknown>`, `unknown[]`, a bare `unknown`\n * field). The extractor maps it to a primitive and the generator publishes `{}`, which in JSON\n * Schema means \"anything\" — a partner-facing field with no shape, which is the defect the\n * unmapped guard exists for, arriving through a door the guard does not watch.\n * - an `rpc` or `external` endpoint whose response is `void`. Fire-and-forget is the CONTRACT of a\n * `cloudtasks` or `cron` endpoint and is allowed there; an endpoint somebody WAITS on that answers\n * nothing can never gain a field without a breaking change, where a named empty response object\n * grows additively forever (#1017 — #1016 read this narrowly as rpc-only because `external` was\n * unstated).\n *\n * ## Why it lives in the rules engine and not in the doc parser\n *\n * `@webpieces/api-doc-model` is only ever pointed at contracts somebody chose to publish. `@ApiType`\n * is a PUBLISHING decision added later, on purpose — so a shape rule that only ran on contracts which\n * had already opted in would let a team discover, six months afterwards, that the type was never\n * expressible, by which time it is in partners' generated clients. So every check below runs on every\n * contract in scope, opted in or not — `@ApiType` narrows nothing here.\n *\n * ## The wire closure (#1064, D4)\n *\n * Every named type a contract reaches must be DECLARED in a `role:api-lib` project and end in the\n * suffix its `required-type-suffix` entry demands — see `wire-closure.ts`. It is a shared defect: a type\n * declared in a general library is wrong on the wire whichever document it would have been in.\n *\n * ## Root-level unions are NOT re-checked here\n *\n * `no-root-union-api-type` (#1009) already refuses them, workspace-wide, with its own config key and\n * its own per-site hatch. One implementation. The MCP half still reports one when it meets it,\n * because `McpSchemaRenderer` refuses it as its own backstop and this scan reports whatever the\n * renderer says — which is the correct division: the OpenAPI document publishes a root union\n * perfectly well, and only a tool schema cannot carry one.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n ApiDocExtractionError,\n ApiDocExtractor,\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { ProjectInfo } from '../project-info';\nimport { collectTsFiles, isTestFile } from './api-ast';\nimport {\n ApiContractDefect,\n ApiDocRule,\n ApiDocRulesFindings,\n ApiRuleFindings,\n DisableComment,\n McpExclusion,\n MCP_RULE,\n OPENAPI_RULE,\n} from './api-doc-rules';\nimport {\n ANSWERING_KINDS,\n ContractLines,\n unknownValueCure,\n VOID_RPC_CURE,\n carriesUnknown,\n classifyUnmapped,\n contractLinesOf,\n contractNamesIn,\n isVoidLike,\n toolFailures,\n} from './api-doc-rules-verdicts';\nimport { WireClosure, WireClosureRule } from './wire-closure';\n\n/** `@ApiPath(` at COLUMN ZERO — a docstring that TALKS about a contract declares none. */\nconst DECLARES_CONTRACT = /@/;\n\n/** ONE contract file, and which of the two rules apply to the project that owns it. */\nclass ContractFile {\n constructor(\n public readonly absPath: string,\n public readonly openApi: boolean,\n public readonly mcp: boolean,\n ) {}\n}\n\n/** Where one declaration sits, already split out of the extractor's `File.ts:LINE:COL` spelling. */\nclass Site {\n constructor(\n public readonly absPath: string,\n public readonly line: number,\n ) {}\n\n /** `path/to/File.ts:LINE`, workspace-relative — what a refusal prints. */\n relativeTo(workspaceRoot: string): string {\n return `${path.relative(workspaceRoot, this.absPath)}:${this.line}`;\n }\n\n /** `abs/File.ts:12:5` -> a Site. An unparseable one falls back to line 1 of `fallback`. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static parse(location: string, fallback: string): Site {\n const match = location.match(/^(.*):(\\d+):\\d+$/);\n if (match === null) return new Site(fallback, 1);\n return new Site(match[1], Number(match[2]));\n }\n}\n\n/**\n * ONE rule's accumulator. It is what applies the per-site disable, so the disable semantics live in\n * exactly one place and cannot differ between the two rules.\n */\nclass DefectCollector {\n private readonly violations: ApiContractDefect[] = [];\n private readonly reasonless: ApiContractDefect[] = [];\n\n constructor(private readonly ruleName: string) {}\n\n /** The rule this collector reports under — what a shared defect's cure has to name. */\n rule(): string {\n return this.ruleName;\n }\n\n add(defect: ApiContractDefect, site: Site): void {\n const disable = DisableComment.readAt(site.absPath, site.line, this.ruleName);\n if (disable === undefined) {\n this.violations.push(defect);\n return;\n }\n if (!disable.hasReason) this.reasonless.push(defect);\n }\n\n findings(): ApiRuleFindings {\n return new ApiRuleFindings(\n DefectCollector.externalFirst(this.violations),\n DefectCollector.externalFirst(this.reasonless),\n );\n }\n\n /**\n * Partner-facing contracts first. The same defect is a different size depending on who reads the\n * document it would have been in, and a list that buries the `external-customer` ones among\n * thirty internal ones has hidden the only urgent line in it.\n */\n // webpieces-disable no-function-outside-class -- private static ordering of this class\n private static externalFirst(found: readonly ApiContractDefect[]): ApiContractDefect[] {\n return [...found].sort(\n (a: ApiContractDefect, b: ApiContractDefect) =>\n Number(b.isExternal()) - Number(a.isExternal()),\n );\n }\n}\n\n/**\n * Routes a defect to the rule that owns it.\n *\n * Every OpenAPI-level defect ALSO blocks MCP, so it is reported by whichever rule is running —\n * `api-rules-for-openapi` when that one is on, and `api-rules-for-mcp` alone when it is not. It is\n * never reported twice: a team running both would otherwise read every shared defect in two places\n * and have to work out that they are one.\n */\nclass DefectSink {\n constructor(\n private readonly openApi: DefectCollector | undefined,\n private readonly mcp: DefectCollector | undefined,\n ) {}\n\n /**\n * A defect that blocks the OpenAPI document, and therefore every tool on it too.\n *\n * The defect is BUILT from the rule that ends up reporting it, not handed in ready-made, because\n * a shared defect does not know in advance which rule will carry it: `api-rules-for-openapi` when\n * that one runs, and `api-rules-for-mcp` alone when it does not. A cure that named a fixed rule\n * would, on the mcp-only configuration, prescribe a `// webpieces-disable` line the collector\n * reading that site does not look for.\n */\n shared(build: (rule: string) => ApiContractDefect, site: Site): void {\n const target = this.openApi ?? this.mcp;\n if (target === undefined) return;\n target.add(build(target.rule()), site);\n }\n\n /** A defect that blocks ONE tool and nothing else. */\n mcpOnly(defect: ApiContractDefect, site: Site): void {\n this.mcp?.add(defect, site);\n }\n\n anyRuleRuns(): boolean {\n return this.openApi !== undefined || this.mcp !== undefined;\n }\n\n /** True when `api-rules-for-mcp` applies to this file — what the exclusion list is scoped to. */\n mcpRuns(): boolean {\n return this.mcp !== undefined;\n }\n}\n\n/**\n * Walks every project's `src`, extracts every `@ApiPath` contract with the generator's own\n * extractor, and judges the result against the two rules.\n *\n * ONE `ts.Program` over every contract file in the workspace, because a DTO a contract reaches\n * routinely lives in another project and the checker has to be able to follow the import — the same\n * reason the repo sweep in `@webpieces/api-doc-model`'s own spec builds one program rather than one\n * per file.\n */\nexport class ApiDocRulesScan {\n /**\n * Every `@InvalidEndpointForMcp` endpoint met on a file the MCP rule applies to.\n *\n * Collected even on a run with no findings at all, because restating them IS the feature: the\n * alternative considered in #1014 was a one-off warning when somebody adds one, and a warning\n * printed once at the moment of the decision is read by the one person who already knows.\n */\n private readonly exclusions: McpExclusion[] = [];\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** OFF unless a caller read otherwise out of webpieces.config.json, which MUST state it. */\n private readonly openApiRule: ApiDocRule = ApiDocRule.off(OPENAPI_RULE),\n private readonly mcpRule: ApiDocRule = ApiDocRule.off(MCP_RULE),\n /** The suffix half of the wire closure; the role half always runs when either rule does. */\n private readonly wireClosureRule: WireClosureRule = WireClosureRule.withoutSuffixes(),\n ) {}\n\n run(): ApiDocRulesFindings {\n if (!this.openApiRule.enabled && !this.mcpRule.enabled) return ApiDocRulesFindings.empty();\n const files = this.contractFiles();\n if (files.length === 0) return ApiDocRulesFindings.empty();\n\n const openApi = this.openApiRule.enabled ? new DefectCollector(OPENAPI_RULE) : undefined;\n const mcp = this.mcpRule.enabled ? new DefectCollector(MCP_RULE) : undefined;\n const program = ts.createProgram(\n files.map((file: ContractFile) => file.absPath),\n this.compilerOptions(),\n );\n const toolNames = new Map<string, string>();\n const closure = new WireClosure(this.workspaceRoot, this.projectInfos, this.wireClosureRule);\n for (const file of files) {\n const sink = new DefectSink(\n file.openApi ? openApi : undefined,\n file.mcp ? mcp : undefined,\n );\n this.judgeFile(file, program, sink, toolNames, closure);\n }\n return new ApiDocRulesFindings(\n openApi?.findings() ?? new ApiRuleFindings([], []),\n mcp?.findings() ?? new ApiRuleFindings([], []),\n this.exclusions,\n );\n }\n\n /** Every contract in one file, or the ONE refusal that stopped the file being read at all. */\n private judgeFile(\n file: ContractFile,\n program: ts.Program,\n sink: DefectSink,\n toolNames: Map<string, string>,\n closure: WireClosure,\n ): void {\n if (!sink.anyRuleRuns()) return;\n const source = program.getSourceFile(file.absPath);\n if (source === undefined) return;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an extraction refusal IS a finding; it is reported, not propagated\n try {\n for (const model of new ApiDocExtractor().extractAllFrom(program, source)) {\n this.judgeModel(model, source, sink, toolNames);\n this.judgeWireClosure(model, sink, source.fileName, closure);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof ApiDocExtractionError)) throw err;\n this.reportExtractionFailure(err, file, source, sink);\n }\n }\n\n /**\n * An extraction that REFUSED. Reported under the shared list because it stops BOTH documents:\n * `@Endpoint` arguments that cannot be constant-folded, a bound on a non-numeric field, and the\n * `@ApiType(..., MCP)` ⇔ `@WpMcpTool` biconditional all fail here, before a model exists.\n */\n private reportExtractionFailure(\n error: ApiDocExtractionError,\n file: ContractFile,\n source: ts.SourceFile,\n sink: DefectSink,\n ): void {\n const site = Site.parse(error.location, file.absPath);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n contractNamesIn(source)[0] ?? path.basename(file.absPath),\n '',\n `the contract cannot be read at all — ${error.message}`,\n site.relativeTo(this.workspaceRoot),\n error.cure,\n [],\n ),\n site,\n );\n }\n\n /** ONE contract: the shared OpenAPI checks, then the MCP-only ones. */\n private judgeModel(\n model: ApiDocModel,\n source: ts.SourceFile,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const lines = contractLinesOf(source, model.contractName);\n this.judgeUnmapped(model, sink, source.fileName);\n this.judgeUnknownValues(model, sink, source.fileName);\n this.judgeAnsweringResponses(model, source, lines, sink);\n this.judgeTools(model, source, lines, sink, toolNames);\n }\n\n /**\n * D4 — every named type the contract reaches is declared in a `role:api-lib` project and carries its\n * suffix. Shared: a type that should never have left a general library blocks every document.\n */\n private judgeWireClosure(model: ApiDocModel, sink: DefectSink, fallback: string, closure: WireClosure): void {\n for (const type of model.types.values()) {\n const site = Site.parse(type.location, fallback);\n for (const verdict of closure.judge(type, site.absPath)) {\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n '',\n verdict.what,\n site.relativeTo(this.workspaceRoot),\n verdict.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n }\n\n /** Everything `TypeResolver` could not represent — the generator's OWN verdict, re-worded. */\n private judgeUnmapped(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const unmapped of model.unmapped) {\n const site = Site.parse(unmapped.location, fallback);\n const verdict = classifyUnmapped(unmapped);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n '',\n verdict.what,\n site.relativeTo(this.workspaceRoot),\n verdict.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** `Record<string, unknown>`, `unknown[]`, `x: unknown` — by VALUE TYPE, never by spelling. */\n private judgeUnknownValues(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const type of model.types.values()) {\n for (const field of type.fields) {\n if (!carriesUnknown(field.type)) continue;\n const site = Site.parse(field.location, fallback);\n sink.shared(\n (rule: string): ApiContractDefect =>\n new ApiContractDefect(\n model.contractName,\n '',\n `'${type.name}.${field.name}' publishes an 'unknown' value, so the ` +\n 'document states no shape for it at all',\n site.relativeTo(this.workspaceRoot),\n unknownValueCure(rule),\n model.apiTypes,\n ),\n site,\n );\n }\n }\n }\n\n /**\n * An endpoint somebody WAITS ON must NAME a response DTO, even an empty one.\n *\n * `Promise<void>` on a `cloudtasks` or `cron` endpoint is the CONTRACT — fire-and-forget, nothing\n * to shape — and is allowed. On an `rpc` OR an `external` it is a one-way door: both are\n * synchronous request/response, an outside caller reads what comes back, and a `void` response\n * can never gain a field without breaking every generated client where `{}` grows additively\n * forever. This is a contract-EVOLUTION rule, which is why it is here and not in the MCP half: it\n * is worth having on an endpoint that never becomes a tool. See ANSWERING_KINDS (#1017).\n */\n private judgeAnsweringResponses(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n ): void {\n for (const endpoint of model.endpoints) {\n // `.some` and not `.includes`: the model's `kind` is a widened string, and the list is\n // typed EndpointKind so a kind that stops existing is a compile error here.\n if (!ANSWERING_KINDS.some((kind: string): boolean => kind === endpoint.kind)) continue;\n if (endpoint.response !== undefined && !isVoidLike(endpoint.response)) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n `an ${endpoint.kind} endpoint returns nothing a document can name (void, ` +\n 'unknown, or no declared return type)',\n site.relativeTo(this.workspaceRoot),\n VOID_RPC_CURE,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Everything `McpToolRegistry` refuses to boot on, plus whatever the renderer cannot render. */\n private judgeTools(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const renderer = new McpSchemaRenderer(model);\n for (const endpoint of model.endpoints) {\n if (endpoint.invalidForMcp !== undefined) {\n if (!sink.mcpRuns()) continue;\n // @InvalidEndpointForMcp IS the answer to \"could this be a tool\". The decorator\n // carries the argument, so the rule asks nothing further and no webpieces-disable is\n // needed — a suppression would be a second, weaker spelling of the same declaration.\n this.exclusions.push(\n new McpExclusion(\n model.contractName,\n endpoint.methodName,\n endpoint.invalidForMcp,\n new Site(\n source.fileName,\n lines.lineOf(endpoint.methodName),\n ).relativeTo(this.workspaceRoot),\n ),\n );\n continue;\n }\n if (endpoint.mcpTool === undefined) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n for (const failure of toolFailures(endpoint, renderer, toolNames, model)) {\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n failure.what,\n site.relativeTo(this.workspaceRoot),\n failure.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n for (const methodName of lines.toolsWithoutEndpoint) {\n const site = new Site(source.fileName, lines.lineOf(methodName));\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n methodName,\n 'carries @WpMcpTool but is not an @Endpoint, so it is not routed at all',\n site.relativeTo(this.workspaceRoot),\n \"Add @Endpoint(POST, '/path', READ, RPC) to it, or drop the @WpMcpTool.\",\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Every non-test `.ts` under a project's `src` whose text DECLARES an `@ApiPath` contract. */\n private contractFiles(): ContractFile[] {\n const found: ContractFile[] = [];\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n // ONE question, asked of the rule itself: `mode` (OFF / AFFECTED_PROJECT /\n // RUN_EVERY_TIME) and `allowedPaths` are both folded into coversProject, so the\n // affected-project narrowing cannot be honoured in one branch and skipped in the other.\n const openApi = this.openApiRule.coversProject(info.root);\n const mcp = this.mcpRule.coversProject(info.root);\n if (!openApi && !mcp) continue;\n const srcDir = path.join(path.resolve(this.workspaceRoot, info.root), 'src');\n if (!fs.existsSync(srcDir)) continue;\n for (const file of collectTsFiles(srcDir)) {\n if (isTestFile(file)) continue; // a fixture is not a published contract\n if (!DECLARES_CONTRACT.test(fs.readFileSync(file, 'utf8'))) continue;\n found.push(new ContractFile(file, openApi, mcp));\n }\n }\n return found.sort((a: ContractFile, b: ContractFile) => a.absPath.localeCompare(b.absPath));\n }\n\n /**\n * `tsconfig.base.json`'s options when the workspace has one, so an `@webpieces/*` import in a\n * contract RESOLVES and the checker can follow a DTO into another project. Without that the\n * resolver reports every cross-project type as unmapped, which would be a rule failing on its\n * own inability to read rather than on anything the author wrote.\n */\n private compilerOptions(): ts.CompilerOptions {\n const base = path.join(this.workspaceRoot, 'tsconfig.base.json');\n const declared = fs.existsSync(base)\n ? ts.parseJsonConfigFileContent(\n ts.readConfigFile(base, ts.sys.readFile).config,\n ts.sys,\n this.workspaceRoot,\n ).options\n : {};\n return {\n ...declared,\n noEmit: true,\n skipLibCheck: true,\n types: [],\n experimentalDecorators: true,\n };\n }\n}\n"]}
|
|
@@ -225,9 +225,9 @@ function toolFailures(endpoint, renderer, toolNames, model) {
|
|
|
225
225
|
'one, because a tool with no credential is an unauthenticated endpoint an ' +
|
|
226
226
|
'agent can call.'));
|
|
227
227
|
}
|
|
228
|
-
if (endpoint.
|
|
229
|
-
found.push(new Verdict('an MCP tool does not declare @
|
|
230
|
-
'before
|
|
228
|
+
if (endpoint.authorizationText === undefined) {
|
|
229
|
+
found.push(new Verdict('an MCP tool does not declare @WpAuthorization(...)', 'Add @WpAuthorization(...) beside @WpAuth. Common operation authorization is rechecked ' +
|
|
230
|
+
'before tool/schema lookup and at the receiving endpoint.'));
|
|
231
231
|
}
|
|
232
232
|
const rendered = renderFailure(renderer, endpoint);
|
|
233
233
|
if (rendered !== undefined)
|
|
@@ -306,15 +306,14 @@ function contractLinesOf(source, contractName) {
|
|
|
306
306
|
function lineOf(source, node) {
|
|
307
307
|
return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
308
308
|
}
|
|
309
|
-
/**
|
|
309
|
+
/** Match canonical imports, including aliases and namespace calls. */
|
|
310
310
|
// webpieces-disable no-function-outside-class -- pure AST reader
|
|
311
311
|
function hasDecoratorNamed(node, name) {
|
|
312
312
|
const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];
|
|
313
313
|
for (const decorator of decorators) {
|
|
314
314
|
const call = decorator.expression;
|
|
315
315
|
if (ts.isCallExpression(call) &&
|
|
316
|
-
|
|
317
|
-
call.expression.text === name) {
|
|
316
|
+
api_doc_model_1.ApiDocExtractor.canonicalCallName(call.expression) === name) {
|
|
318
317
|
return true;
|
|
319
318
|
}
|
|
320
319
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-doc-rules-verdicts.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pure VERDICTS the api-contract rules print, split out of `api-doc-rules-scan.ts`, which owns\n * the walk and is at its file-size limit.\n *\n * Nothing here touches the filesystem or the project graph. Each function answers one question about\n * ONE declaration the extractor or the renderer has already ruled on, and turns it into the two\n * halves every webpieces refusal carries — what is wrong, and what to do instead. The DECISION is\n * never made here: `model.unmapped` is the extractor's and a render failure is the renderer's. What\n * this file adds is the WORDING, which is the part a generic \"no model representation for this type\n * form\" cannot give an author.\n */\n\nimport * as ts from 'typescript';\nimport {\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { EndpointKind } from './api-relations';\n\n/**\n * The one trigger kind an MCP tool may have. A LITERAL, for the same reason `EndpointKind` beside it\n * is one: this package deliberately does not depend on `@webpieces/core-util` at runtime, and the\n * set the literal belongs to is pinned by that type.\n */\nexport const RPC_KIND: EndpointKind = 'rpc';\n\n/**\n * The endpoint kinds whose response a caller WAITS FOR, and which therefore must name a DTO (#1017).\n *\n * `rpc` and `external` are both synchronous request/response: somebody posts and reads what comes\n * back, so there IS a body and it must be a named type that can gain a field later. `void` on either\n * is a one-way door — a `void` response can never grow without breaking every generated client, where\n * an empty DTO grows additively forever. #1016 refused it on `rpc` only, because `external` was\n * unstated at the time; it is stated now.\n *\n * `cloudtasks` and `cron` are deliberately NOT here: delivery is fire-and-forget, nobody waits for a\n * body, and `Promise<void>` is the CONTRACT rather than an omission.\n */\nexport const ANSWERING_KINDS: readonly EndpointKind[] = ['rpc', 'external'];\n\n/** The scalar keywords a MIXED SCALAR union is made of. */\nconst SCALAR_KEYWORDS = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.StringKeyword,\n ts.SyntaxKind.NumberKeyword,\n ts.SyntaxKind.BooleanKeyword,\n]);\n\n/** ONE reason a declaration is refused, in the two halves every refusal prints. */\nexport class Verdict {\n constructor(\n public readonly what: string,\n public readonly cure: string,\n ) {}\n}\n\n\n/** Method name -> the line its declaration starts on, for one contract class. */\nexport class ContractLines {\n constructor(\n public readonly classLine: number,\n public readonly byMethod: ReadonlyMap<string, number>,\n /** Methods carrying `@WpMcpTool` that do NOT also carry `@Endpoint`. */\n public readonly toolsWithoutEndpoint: readonly string[],\n ) {}\n\n lineOf(methodName: string): number {\n return this.byMethod.get(methodName) ?? this.classLine;\n }\n}\n\n\n/**\n * The cure for an `unknown` value, for whichever rule is REPORTING it.\n *\n * It takes the rule name rather than naming one, because this defect is shared: it blocks the\n * OpenAPI document, so `DefectSink` routes it to `api-rules-for-openapi` when that rule is running\n * and to `api-rules-for-mcp` when it is the only one. A hard-coded rule name would then hand an\n * author, on the mcp-only configuration, a `// webpieces-disable api-rules-for-openapi` line that\n * the collector reading the site does not look for — a cure that does not cure, which is the one\n * failure mode a printed cure must not have.\n */\n// webpieces-disable no-function-outside-class -- pure message builder, beside the verdicts that print it\nexport function unknownValueCure(rule: string): string {\n return (\n 'Name the value type — a DTO, an array of one, a string-literal union, or ' +\n 'Record<string, ThatDto>. If the shape really is unknowable (a transport envelope, ' +\n 'arbitrary SQL rows), write the per-site disable and say so: ' +\n `// webpieces-disable ${rule} -- <why this field is published with no shape>. ` +\n 'An existing no-any-unknown disable does NOT count — that rule asked whether the code is ' +\n 'type-safe, which is a different question from whether a partner is handed a shapeless field.'\n );\n}\n\n/** The one cure for a `void` endpoint that somebody is WAITING on (rpc or external). */\nexport const VOID_RPC_CURE =\n 'Declare a named response DTO and return Promise<That>, even when it has no fields today: an ' +\n 'empty object grows additively forever, and a void response cannot gain a field without ' +\n 'breaking every generated client. An external endpoint is a synchronous call an outside caller ' +\n 'waits on, so it has a body too — an empty one serializes as {} and the failure detail rides on ' +\n 'the thrown error. Promise<void> stays legal on a cloudtasks or cron endpoint, where ' +\n 'fire-and-forget is the contract.';\n\n/**\n * Why the resolver could not map this type, said in the author's vocabulary.\n *\n * The DECISION to refuse is the extractor's and is not second-guessed here — every entry in\n * `model.unmapped` becomes a defect. What this adds is the MESSAGE: an open enum and a mixed scalar\n * union are the two shapes a real repo actually hits (measured: 6 of 98 endpoints, from exactly\n * these two), and \"no model representation for this type form\" tells their author nothing.\n */\n// webpieces-disable no-function-outside-class -- pure classifier over the extractor's own verdict\nexport function classifyUnmapped(unmapped: UnmappedType): Verdict {\n const branches = unionBranchesOf(unmapped.typeText);\n if (isOpenEnum(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is an OPEN ENUM — literals unioned with the wide type`,\n 'Use an enum OR a string, not both. TypeScript COLLAPSES this union to the wide type, ' +\n 'so the literals buy no compile-time safety and a document cannot state them as a ' +\n \"closed set. Either drop the `| string` (`'a' | 'b'` publishes as a real enum), or \" +\n 'drop the literals and list the known values in the JSDoc.',\n );\n }\n if (isMixedScalarUnion(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is a MIXED SCALAR union`,\n 'Give the field ONE type. JSON Schema can spell {\"type\": [\"string\",\"number\"]}, but ' +\n 'this framework\\'s ApiJsonSchema helpers read a type array as \"one real type plus ' +\n 'maybe null\" (baseTypeOf returns the first non-null member), so publishing it ' +\n 'would VALIDATE WRONGLY — which is worse than refusing it. Pick the type, or wrap ' +\n 'the alternatives in a discriminated union of named objects.',\n );\n }\n return new Verdict(\n `'${unmapped.typeText}' has no shape a document can state — ${unmapped.reason}`,\n 'Give it a type a document can carry: a named DTO, an array of one, a string-literal ' +\n 'union, a Record<string, ThatDto>, or a primitive. A field with no shape publishes as ' +\n '\"anything\".',\n );\n}\n\n/** The branches of `typeText` when it parses as a union, else an empty list. */\n// webpieces-disable no-function-outside-class -- pure parser used by classifyUnmapped alone\nfunction unionBranchesOf(typeText: string): readonly ts.TypeNode[] {\n const parsed = ts.createSourceFile(\n '__unmapped.ts',\n `type __X = ${typeText};`,\n ts.ScriptTarget.Latest,\n true,\n );\n const alias = parsed.statements[0];\n if (alias === undefined || !ts.isTypeAliasDeclaration(alias)) return [];\n if (!ts.isUnionTypeNode(alias.type)) return [];\n return alias.type.types.filter((branch: ts.TypeNode) => !isNullishBranch(branch));\n}\n\n/** `'a' | 'b' | string`, and the numeric equivalent. See #1010 and the decision on #1011. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isOpenEnum(branches: readonly ts.TypeNode[]): boolean {\n const wide = branches.some(\n (branch: ts.TypeNode) =>\n branch.kind === ts.SyntaxKind.StringKeyword ||\n branch.kind === ts.SyntaxKind.NumberKeyword,\n );\n const literals = branches.some((branch: ts.TypeNode) => ts.isLiteralTypeNode(branch));\n return wide && literals;\n}\n\n/** `string | number`, `string | boolean | null` — two or more DIFFERENT scalar keywords. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isMixedScalarUnion(branches: readonly ts.TypeNode[]): boolean {\n if (branches.length < 2) return false;\n const kinds = new Set<ts.SyntaxKind>();\n for (const branch of branches) {\n if (!SCALAR_KEYWORDS.has(branch.kind)) return false;\n kinds.add(branch.kind);\n }\n return kinds.size >= 2;\n}\n\n/** `null` / `undefined` branches — nullability, not composition, exactly as the resolver reads it. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isNullishBranch(node: ts.TypeNode): boolean {\n if (node.kind === ts.SyntaxKind.UndefinedKeyword || node.kind === ts.SyntaxKind.NullKeyword) {\n return true;\n }\n return ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword;\n}\n\n/**\n * True when this type PUBLISHES an `unknown` value.\n *\n * It looks at the VALUE TYPE wherever it sits — the item of an array, the value of an open map, the\n * field itself — and never at the `Record<>` spelling, because the real cases in the wild are not\n * all Records: `params?: unknown[]` is the same defect written differently.\n *\n * It does NOT descend through a `ref`: a named DTO is its own entry in the model and its own fields\n * are judged there, so descending would report the same field once per type that points at it.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function carriesUnknown(ref: TypeRef): boolean {\n if (ref.kind === 'primitive') return ref.primitive === 'unknown';\n if (ref.kind === 'array') return carriesUnknown(ref.items!);\n if (ref.kind === 'openMap') return carriesUnknown(ref.values!);\n return false;\n}\n\n/**\n * `void`, `unknown` and `any` all reach the model as the same primitive — the resolver maps the\n * three keywords onto one, because to a document they say the identical thing: nothing.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function isVoidLike(ref: TypeRef): boolean {\n return ref.kind === 'primitive' && ref.primitive === 'unknown';\n}\n\n/**\n * Every reason this tool could not be served, in the order `McpToolRegistry` asks them at boot —\n * unique name, trigger kind, HTTP auth, MCP auth — and then the renderer's own verdict.\n *\n * The registry's four are restated from the model rather than driven, because they are decorator\n * PRESENCE facts the model already carries verbatim; the SCHEMA question is the one with a real\n * implementation behind it, and that one is driven.\n */\n// webpieces-disable no-function-outside-class, max-lines-new-methods -- pure judge over one endpoint, and the four boot checks read as one list\nexport function toolFailures(\n endpoint: DocumentedEndpoint,\n renderer: McpSchemaRenderer,\n toolNames: Map<string, string>,\n model: ApiDocModel,\n): readonly Verdict[] {\n const found: Verdict[] = [];\n const toolName = endpoint.mcpTool!.name;\n const claimed = toolNames.get(toolName);\n if (claimed === undefined) {\n toolNames.set(toolName, `${model.contractName}.${endpoint.methodName}`);\n } else {\n found.push(\n new Verdict(\n `tool name '${toolName}' is already declared by ${claimed}`,\n 'Tool names are the protocol identity and are globally unique — rename one of the ' +\n 'two @WpMcpTool declarations.',\n ),\n );\n }\n if (endpoint.kind !== RPC_KIND) {\n found.push(\n new Verdict(\n `an MCP tool is declared on a '${endpoint.kind}' endpoint`,\n 'Only an RPC endpoint can be a tool — a cloudtasks, cron or external endpoint is ' +\n 'driven by something other than a caller. Make it RPC, or drop the @WpMcpTool.',\n ),\n );\n }\n if (endpoint.auth === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool declares no HTTP auth',\n 'Put a @WpAuth... decorator on the method. The MCP server refuses to boot without ' +\n 'one, because a tool with no credential is an unauthenticated endpoint an ' +\n 'agent can call.',\n ),\n );\n }\n if (endpoint.mcpAuthText === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool does not declare @WpMcpAuthJwt(...)',\n 'Add @WpMcpAuthJwt(...) beside the HTTP auth. MCP authorization is rechecked ' +\n 'before the endpoint boundary and is declared separately on purpose.',\n ),\n );\n }\n const rendered = renderFailure(renderer, endpoint);\n if (rendered !== undefined) found.push(rendered);\n return found;\n}\n\n/**\n * The renderer's own refusal for one tool, or undefined when it renders.\n *\n * This is the whole acceptance contract for the MCP half: \"passes the rule\" and \"renders a tool\" are\n * the SAME statement, because the rule asks the renderer. It covers the method's missing JSDoc,\n * every reachable DTO field's missing JSDoc, a request or response that is not a named object, a\n * root-level union, a recursive DTO, an `unknown` leaf and a malformed `@mcpHeader` — none of which\n * is restated here.\n *\n * One verdict per tool, because the renderer stops at the first thing it cannot draw. A tool blocked\n * by several undocumented fields is therefore fixed a round at a time, which is the price of having\n * exactly one definition of renderable rather than a second walk that could disagree with it.\n */\n// webpieces-disable no-function-outside-class -- pure judge beside toolFailures\nfunction renderFailure(\n renderer: McpSchemaRenderer,\n endpoint: DocumentedEndpoint,\n): Verdict | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the render refusal IS the verdict\n try {\n renderer.tool(endpoint);\n return undefined;\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof McpRenderError)) throw err;\n return new Verdict(`${err.message} — no MCP schema could be built`, err.cure);\n }\n}\n\n/** Every `@ApiPath` class name declared at the top level of a file, in declaration order. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nexport function contractNamesIn(source: ts.SourceFile): string[] {\n const names: string[] = [];\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name === undefined) continue;\n if (hasDecoratorNamed(statement, 'ApiPath')) names.push(statement.name.text);\n }\n return names;\n}\n\n/**\n * Where each method of one contract starts, and which of them carry `@WpMcpTool` without\n * `@Endpoint`.\n *\n * The line numbers exist because the MCP failures come from the RENDERER, whose location is a NAME\n * (`Order.window`) rather than a file offset — it publishes a schema, not a diagnostic. Anchoring\n * every tool failure at its METHOD is also where an author would write the disable: \"this tool is\n * deliberately not renderable\" is a decision about the method, not about a field three DTOs down.\n */\n// webpieces-disable no-function-outside-class -- pure AST reader beside contractNamesIn\nexport function contractLinesOf(source: ts.SourceFile, contractName: string): ContractLines {\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name?.text !== contractName) continue;\n const byMethod = new Map<string, number>();\n const orphanTools: string[] = [];\n for (const member of statement.members) {\n if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) continue;\n byMethod.set(member.name.text, lineOf(source, member));\n if (hasDecoratorNamed(member, 'WpMcpTool') && !hasDecoratorNamed(member, 'Endpoint')) {\n orphanTools.push(member.name.text);\n }\n }\n return new ContractLines(lineOf(source, statement), byMethod, orphanTools);\n }\n return new ContractLines(1, new Map<string, number>(), []);\n}\n\n/** 1-based line of a node's first token, decorators excluded. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction lineOf(source: ts.SourceFile, node: ts.Node): number {\n return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;\n}\n\n/** True when `node` carries `@name(...)`. Matched on the syntax, like every reader in this file. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction hasDecoratorNamed(node: ts.Node, name: string): boolean {\n const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (\n ts.isCallExpression(call) &&\n ts.isIdentifier(call.expression) &&\n call.expression.text === name\n ) {\n return true;\n }\n }\n return false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-doc-rules-verdicts.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pure VERDICTS the api-contract rules print, split out of `api-doc-rules-scan.ts`, which owns\n * the walk and is at its file-size limit.\n *\n * Nothing here touches the filesystem or the project graph. Each function answers one question about\n * ONE declaration the extractor or the renderer has already ruled on, and turns it into the two\n * halves every webpieces refusal carries — what is wrong, and what to do instead. The DECISION is\n * never made here: `model.unmapped` is the extractor's and a render failure is the renderer's. What\n * this file adds is the WORDING, which is the part a generic \"no model representation for this type\n * form\" cannot give an author.\n */\n\nimport * as ts from 'typescript';\nimport {\n ApiDocModel,\n ApiDocExtractor,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { EndpointKind } from './api-relations';\n\n/**\n * The one trigger kind an MCP tool may have. A LITERAL, for the same reason `EndpointKind` beside it\n * is one: this package deliberately does not depend on `@webpieces/core-util` at runtime, and the\n * set the literal belongs to is pinned by that type.\n */\nexport const RPC_KIND: EndpointKind = 'rpc';\n\n/**\n * The endpoint kinds whose response a caller WAITS FOR, and which therefore must name a DTO (#1017).\n *\n * `rpc` and `external` are both synchronous request/response: somebody posts and reads what comes\n * back, so there IS a body and it must be a named type that can gain a field later. `void` on either\n * is a one-way door — a `void` response can never grow without breaking every generated client, where\n * an empty DTO grows additively forever. #1016 refused it on `rpc` only, because `external` was\n * unstated at the time; it is stated now.\n *\n * `cloudtasks` and `cron` are deliberately NOT here: delivery is fire-and-forget, nobody waits for a\n * body, and `Promise<void>` is the CONTRACT rather than an omission.\n */\nexport const ANSWERING_KINDS: readonly EndpointKind[] = ['rpc', 'external'];\n\n/** The scalar keywords a MIXED SCALAR union is made of. */\nconst SCALAR_KEYWORDS = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.StringKeyword,\n ts.SyntaxKind.NumberKeyword,\n ts.SyntaxKind.BooleanKeyword,\n]);\n\n/** ONE reason a declaration is refused, in the two halves every refusal prints. */\nexport class Verdict {\n constructor(\n public readonly what: string,\n public readonly cure: string,\n ) {}\n}\n\n\n/** Method name -> the line its declaration starts on, for one contract class. */\nexport class ContractLines {\n constructor(\n public readonly classLine: number,\n public readonly byMethod: ReadonlyMap<string, number>,\n /** Methods carrying `@WpMcpTool` that do NOT also carry `@Endpoint`. */\n public readonly toolsWithoutEndpoint: readonly string[],\n ) {}\n\n lineOf(methodName: string): number {\n return this.byMethod.get(methodName) ?? this.classLine;\n }\n}\n\n\n/**\n * The cure for an `unknown` value, for whichever rule is REPORTING it.\n *\n * It takes the rule name rather than naming one, because this defect is shared: it blocks the\n * OpenAPI document, so `DefectSink` routes it to `api-rules-for-openapi` when that rule is running\n * and to `api-rules-for-mcp` when it is the only one. A hard-coded rule name would then hand an\n * author, on the mcp-only configuration, a `// webpieces-disable api-rules-for-openapi` line that\n * the collector reading the site does not look for — a cure that does not cure, which is the one\n * failure mode a printed cure must not have.\n */\n// webpieces-disable no-function-outside-class -- pure message builder, beside the verdicts that print it\nexport function unknownValueCure(rule: string): string {\n return (\n 'Name the value type — a DTO, an array of one, a string-literal union, or ' +\n 'Record<string, ThatDto>. If the shape really is unknowable (a transport envelope, ' +\n 'arbitrary SQL rows), write the per-site disable and say so: ' +\n `// webpieces-disable ${rule} -- <why this field is published with no shape>. ` +\n 'An existing no-any-unknown disable does NOT count — that rule asked whether the code is ' +\n 'type-safe, which is a different question from whether a partner is handed a shapeless field.'\n );\n}\n\n/** The one cure for a `void` endpoint that somebody is WAITING on (rpc or external). */\nexport const VOID_RPC_CURE =\n 'Declare a named response DTO and return Promise<That>, even when it has no fields today: an ' +\n 'empty object grows additively forever, and a void response cannot gain a field without ' +\n 'breaking every generated client. An external endpoint is a synchronous call an outside caller ' +\n 'waits on, so it has a body too — an empty one serializes as {} and the failure detail rides on ' +\n 'the thrown error. Promise<void> stays legal on a cloudtasks or cron endpoint, where ' +\n 'fire-and-forget is the contract.';\n\n/**\n * Why the resolver could not map this type, said in the author's vocabulary.\n *\n * The DECISION to refuse is the extractor's and is not second-guessed here — every entry in\n * `model.unmapped` becomes a defect. What this adds is the MESSAGE: an open enum and a mixed scalar\n * union are the two shapes a real repo actually hits (measured: 6 of 98 endpoints, from exactly\n * these two), and \"no model representation for this type form\" tells their author nothing.\n */\n// webpieces-disable no-function-outside-class -- pure classifier over the extractor's own verdict\nexport function classifyUnmapped(unmapped: UnmappedType): Verdict {\n const branches = unionBranchesOf(unmapped.typeText);\n if (isOpenEnum(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is an OPEN ENUM — literals unioned with the wide type`,\n 'Use an enum OR a string, not both. TypeScript COLLAPSES this union to the wide type, ' +\n 'so the literals buy no compile-time safety and a document cannot state them as a ' +\n \"closed set. Either drop the `| string` (`'a' | 'b'` publishes as a real enum), or \" +\n 'drop the literals and list the known values in the JSDoc.',\n );\n }\n if (isMixedScalarUnion(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is a MIXED SCALAR union`,\n 'Give the field ONE type. JSON Schema can spell {\"type\": [\"string\",\"number\"]}, but ' +\n 'this framework\\'s ApiJsonSchema helpers read a type array as \"one real type plus ' +\n 'maybe null\" (baseTypeOf returns the first non-null member), so publishing it ' +\n 'would VALIDATE WRONGLY — which is worse than refusing it. Pick the type, or wrap ' +\n 'the alternatives in a discriminated union of named objects.',\n );\n }\n return new Verdict(\n `'${unmapped.typeText}' has no shape a document can state — ${unmapped.reason}`,\n 'Give it a type a document can carry: a named DTO, an array of one, a string-literal ' +\n 'union, a Record<string, ThatDto>, or a primitive. A field with no shape publishes as ' +\n '\"anything\".',\n );\n}\n\n/** The branches of `typeText` when it parses as a union, else an empty list. */\n// webpieces-disable no-function-outside-class -- pure parser used by classifyUnmapped alone\nfunction unionBranchesOf(typeText: string): readonly ts.TypeNode[] {\n const parsed = ts.createSourceFile(\n '__unmapped.ts',\n `type __X = ${typeText};`,\n ts.ScriptTarget.Latest,\n true,\n );\n const alias = parsed.statements[0];\n if (alias === undefined || !ts.isTypeAliasDeclaration(alias)) return [];\n if (!ts.isUnionTypeNode(alias.type)) return [];\n return alias.type.types.filter((branch: ts.TypeNode) => !isNullishBranch(branch));\n}\n\n/** `'a' | 'b' | string`, and the numeric equivalent. See #1010 and the decision on #1011. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isOpenEnum(branches: readonly ts.TypeNode[]): boolean {\n const wide = branches.some(\n (branch: ts.TypeNode) =>\n branch.kind === ts.SyntaxKind.StringKeyword ||\n branch.kind === ts.SyntaxKind.NumberKeyword,\n );\n const literals = branches.some((branch: ts.TypeNode) => ts.isLiteralTypeNode(branch));\n return wide && literals;\n}\n\n/** `string | number`, `string | boolean | null` — two or more DIFFERENT scalar keywords. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isMixedScalarUnion(branches: readonly ts.TypeNode[]): boolean {\n if (branches.length < 2) return false;\n const kinds = new Set<ts.SyntaxKind>();\n for (const branch of branches) {\n if (!SCALAR_KEYWORDS.has(branch.kind)) return false;\n kinds.add(branch.kind);\n }\n return kinds.size >= 2;\n}\n\n/** `null` / `undefined` branches — nullability, not composition, exactly as the resolver reads it. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isNullishBranch(node: ts.TypeNode): boolean {\n if (node.kind === ts.SyntaxKind.UndefinedKeyword || node.kind === ts.SyntaxKind.NullKeyword) {\n return true;\n }\n return ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword;\n}\n\n/**\n * True when this type PUBLISHES an `unknown` value.\n *\n * It looks at the VALUE TYPE wherever it sits — the item of an array, the value of an open map, the\n * field itself — and never at the `Record<>` spelling, because the real cases in the wild are not\n * all Records: `params?: unknown[]` is the same defect written differently.\n *\n * It does NOT descend through a `ref`: a named DTO is its own entry in the model and its own fields\n * are judged there, so descending would report the same field once per type that points at it.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function carriesUnknown(ref: TypeRef): boolean {\n if (ref.kind === 'primitive') return ref.primitive === 'unknown';\n if (ref.kind === 'array') return carriesUnknown(ref.items!);\n if (ref.kind === 'openMap') return carriesUnknown(ref.values!);\n return false;\n}\n\n/**\n * `void`, `unknown` and `any` all reach the model as the same primitive — the resolver maps the\n * three keywords onto one, because to a document they say the identical thing: nothing.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function isVoidLike(ref: TypeRef): boolean {\n return ref.kind === 'primitive' && ref.primitive === 'unknown';\n}\n\n/**\n * Every reason this tool could not be served, in the order `McpToolRegistry` asks them at boot —\n * unique name, trigger kind, HTTP auth, MCP auth — and then the renderer's own verdict.\n *\n * The registry's four are restated from the model rather than driven, because they are decorator\n * PRESENCE facts the model already carries verbatim; the SCHEMA question is the one with a real\n * implementation behind it, and that one is driven.\n */\n// webpieces-disable no-function-outside-class, max-lines-new-methods -- pure judge over one endpoint, and the four boot checks read as one list\nexport function toolFailures(\n endpoint: DocumentedEndpoint,\n renderer: McpSchemaRenderer,\n toolNames: Map<string, string>,\n model: ApiDocModel,\n): readonly Verdict[] {\n const found: Verdict[] = [];\n const toolName = endpoint.mcpTool!.name;\n const claimed = toolNames.get(toolName);\n if (claimed === undefined) {\n toolNames.set(toolName, `${model.contractName}.${endpoint.methodName}`);\n } else {\n found.push(\n new Verdict(\n `tool name '${toolName}' is already declared by ${claimed}`,\n 'Tool names are the protocol identity and are globally unique — rename one of the ' +\n 'two @WpMcpTool declarations.',\n ),\n );\n }\n if (endpoint.kind !== RPC_KIND) {\n found.push(\n new Verdict(\n `an MCP tool is declared on a '${endpoint.kind}' endpoint`,\n 'Only an RPC endpoint can be a tool — a cloudtasks, cron or external endpoint is ' +\n 'driven by something other than a caller. Make it RPC, or drop the @WpMcpTool.',\n ),\n );\n }\n if (endpoint.auth === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool declares no HTTP auth',\n 'Put a @WpAuth... decorator on the method. The MCP server refuses to boot without ' +\n 'one, because a tool with no credential is an unauthenticated endpoint an ' +\n 'agent can call.',\n ),\n );\n }\n if (endpoint.authorizationText === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool does not declare @WpAuthorization(...)',\n 'Add @WpAuthorization(...) beside @WpAuth. Common operation authorization is rechecked ' +\n 'before tool/schema lookup and at the receiving endpoint.',\n ),\n );\n }\n const rendered = renderFailure(renderer, endpoint);\n if (rendered !== undefined) found.push(rendered);\n return found;\n}\n\n/**\n * The renderer's own refusal for one tool, or undefined when it renders.\n *\n * This is the whole acceptance contract for the MCP half: \"passes the rule\" and \"renders a tool\" are\n * the SAME statement, because the rule asks the renderer. It covers the method's missing JSDoc,\n * every reachable DTO field's missing JSDoc, a request or response that is not a named object, a\n * root-level union, a recursive DTO, an `unknown` leaf and a malformed `@mcpHeader` — none of which\n * is restated here.\n *\n * One verdict per tool, because the renderer stops at the first thing it cannot draw. A tool blocked\n * by several undocumented fields is therefore fixed a round at a time, which is the price of having\n * exactly one definition of renderable rather than a second walk that could disagree with it.\n */\n// webpieces-disable no-function-outside-class -- pure judge beside toolFailures\nfunction renderFailure(\n renderer: McpSchemaRenderer,\n endpoint: DocumentedEndpoint,\n): Verdict | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the render refusal IS the verdict\n try {\n renderer.tool(endpoint);\n return undefined;\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof McpRenderError)) throw err;\n return new Verdict(`${err.message} — no MCP schema could be built`, err.cure);\n }\n}\n\n/** Every `@ApiPath` class name declared at the top level of a file, in declaration order. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nexport function contractNamesIn(source: ts.SourceFile): string[] {\n const names: string[] = [];\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name === undefined) continue;\n if (hasDecoratorNamed(statement, 'ApiPath')) names.push(statement.name.text);\n }\n return names;\n}\n\n/**\n * Where each method of one contract starts, and which of them carry `@WpMcpTool` without\n * `@Endpoint`.\n *\n * The line numbers exist because the MCP failures come from the RENDERER, whose location is a NAME\n * (`Order.window`) rather than a file offset — it publishes a schema, not a diagnostic. Anchoring\n * every tool failure at its METHOD is also where an author would write the disable: \"this tool is\n * deliberately not renderable\" is a decision about the method, not about a field three DTOs down.\n */\n// webpieces-disable no-function-outside-class -- pure AST reader beside contractNamesIn\nexport function contractLinesOf(source: ts.SourceFile, contractName: string): ContractLines {\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name?.text !== contractName) continue;\n const byMethod = new Map<string, number>();\n const orphanTools: string[] = [];\n for (const member of statement.members) {\n if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) continue;\n byMethod.set(member.name.text, lineOf(source, member));\n if (hasDecoratorNamed(member, 'WpMcpTool') && !hasDecoratorNamed(member, 'Endpoint')) {\n orphanTools.push(member.name.text);\n }\n }\n return new ContractLines(lineOf(source, statement), byMethod, orphanTools);\n }\n return new ContractLines(1, new Map<string, number>(), []);\n}\n\n/** 1-based line of a node's first token, decorators excluded. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction lineOf(source: ts.SourceFile, node: ts.Node): number {\n return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;\n}\n\n/** Match canonical imports, including aliases and namespace calls. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction hasDecoratorNamed(node: ts.Node, name: string): boolean {\n const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (\n ts.isCallExpression(call) &&\n ApiDocExtractor.canonicalCallName(call.expression) === name\n ) {\n return true;\n }\n }\n return false;\n}\n"]}
|