@webpieces/api-doc-model 0.4.803 → 0.4.805
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/package.json +2 -1
- package/src/extract/ApiDocExtractor.d.ts +80 -7
- package/src/extract/ApiDocExtractor.js +272 -40
- package/src/extract/ApiDocExtractor.js.map +1 -1
- package/src/extract/ConstantFolder.d.ts +16 -3
- package/src/extract/ConstantFolder.js +42 -3
- package/src/extract/ConstantFolder.js.map +1 -1
- package/src/extract/JsDoc.d.ts +10 -0
- package/src/extract/JsDoc.js +18 -2
- package/src/extract/JsDoc.js.map +1 -1
- package/src/extract/TypeResolver.d.ts +9 -0
- package/src/extract/TypeResolver.js +32 -7
- package/src/extract/TypeResolver.js.map +1 -1
- package/src/index.d.ts +8 -4
- package/src/index.js +13 -4
- package/src/index.js.map +1 -1
- package/src/model/ApiDocModel.d.ts +162 -13
- package/src/model/ApiDocModel.js +129 -14
- package/src/model/ApiDocModel.js.map +1 -1
- package/src/render/McpRenderError.d.ts +27 -0
- package/src/render/McpRenderError.js +36 -0
- package/src/render/McpRenderError.js.map +1 -0
- package/src/render/McpSchemaRenderer.d.ts +79 -0
- package/src/render/McpSchemaRenderer.js +235 -0
- package/src/render/McpSchemaRenderer.js.map +1 -0
- package/src/render/McpToolDefinition.d.ts +44 -0
- package/src/render/McpToolDefinition.js +44 -0
- package/src/render/McpToolDefinition.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiDocExtractor.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/ApiDocExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["import * as ts from 'typescript';\nimport {\n ApiDocModel,\n DocumentedAuth,\n DocumentedEndpoint,\n DocumentedEndpointOptions,\n DocumentedMcpTool,\n} from '../model/ApiDocModel';\nimport { TypeRef } from '../model/TypeRef';\nimport { ApiDocExtractionError } from './ApiDocExtractionError';\nimport { ConstantFolder } from './ConstantFolder';\nimport { JsDoc } from './JsDoc';\nimport { SourceLocation } from './SourceLocation';\nimport { TypeResolver } from './TypeResolver';\n\n/** The decorator names this extractor reads, by NAME. It imports none of them — see the class doc. */\nconst API_PATH = 'ApiPath';\nconst ENDPOINT = 'Endpoint';\nconst MASK_LOG = 'MaskLog';\nconst MCP_TOOL = 'WpMcpTool';\nconst MCP_AUTH = 'WpMcpAuthJwt';\nconst AUTH_PREFIX = 'WpAuth';\n\n/** The MCP tool hints a renderer surfaces to an agent. */\nconst TOOL_HINTS = ['readOnly', 'destructive', 'idempotent', 'openWorld'];\n\n/**\n * ONE contract file -> ONE {@link ApiDocModel}. The single extraction pass both the OpenAPI documents\n * and the MCP tool list (#982) are rendered from.\n *\n * ## Why the compiler API at all\n *\n * A DTO field's TYPE IS ERASED AT RUNTIME. Reflection can see that `save` takes one argument; it\n * cannot see that the argument has a `deliveryWindow` that is a discriminated union of two shapes,\n * one of which carries an ISO timestamp. Those are precisely the shapes a partner-grade document is\n * made of, so the only place they exist is the source, and the only honest way to read the source is\n * the compiler.\n *\n * ## Why it imports NO webpieces package\n *\n * It depends on `typescript` and nothing else, so it can be pointed at ANY upstream project's\n * contract — which is the whole point of shipping it as a package rather than as a script in this\n * repo. Decorators are therefore matched BY NAME on the syntax, never by importing the real\n * decorator. One `@webpieces/core-util` import here would make the package unusable by anybody whose\n * webpieces version differs from ours, which is everybody.\n *\n * ## What it does NOT do\n *\n * It emits nothing. No OpenAPI, no MCP tool definitions, no file — that is #982, and keeping the\n * render out means the two renderers cannot drift apart about what the contract SAYS.\n */\nexport class ApiDocExtractor {\n /**\n * Extract the contract in `entryFile`.\n *\n * @param entryFile absolute path to the `.ts` file holding the `@ApiPath` class.\n * @param compilerOptions handed straight to `ts.createProgram`; the caller owns them because\n * only the caller knows its own `paths` / `lib` setup.\n * @throws ApiDocExtractionError when the file holds no `@ApiPath` class, or when something that\n * must be exact (a path constant, a numeric bound) cannot be established.\n */\n extractFile(entryFile: string, compilerOptions: ts.CompilerOptions = {}): ApiDocModel {\n const program = ts.createProgram([entryFile], compilerOptions);\n const source = program.getSourceFile(entryFile);\n if (source === undefined) {\n throw new ApiDocExtractionError(\n 'entry file is not part of the program',\n entryFile,\n 'Pass an absolute path to a .ts file that exists.',\n );\n }\n return this.extract(program, source);\n }\n\n /** The same extraction against a program the caller already built. */\n extract(program: ts.Program, source: ts.SourceFile): ApiDocModel {\n const checker = program.getTypeChecker();\n const folder = new ConstantFolder(checker);\n const resolver = new TypeResolver(checker);\n\n const contract = this.findContract(source);\n const pathDecorator = ApiDocExtractor.decoratorCall(contract, API_PATH)!;\n const basePathArgument = pathDecorator.arguments[0];\n const basePath =\n basePathArgument === undefined\n ? ''\n : folder.foldString(basePathArgument, '@ApiPath argument');\n\n const endpoints: DocumentedEndpoint[] = [];\n for (const member of contract.members) {\n const endpoint = this.endpointOf(member, folder, resolver);\n if (endpoint !== undefined) {\n endpoints.push(endpoint);\n }\n }\n\n return new ApiDocModel(\n contract.name?.text ?? '<anonymous>',\n basePath,\n JsDoc.read(contract).description,\n endpoints,\n resolver.collectedTypes(),\n resolver.collectedUnmapped(),\n );\n }\n\n /** The one `@ApiPath` class in the file. Zero is a hard failure; the FIRST wins if there are two. */\n private findContract(source: ts.SourceFile): ts.ClassDeclaration {\n for (const statement of source.statements) {\n if (\n ts.isClassDeclaration(statement) &&\n ApiDocExtractor.decoratorCall(statement, API_PATH) !== undefined\n ) {\n return statement;\n }\n }\n throw new ApiDocExtractionError(\n 'no @ApiPath class in this file',\n source.fileName,\n 'Point the extractor at the contract file — the one whose class carries @ApiPath.',\n );\n }\n\n /** One `@Endpoint` method. Members without the decorator are not part of the contract. */\n private endpointOf(\n member: ts.ClassElement,\n folder: ConstantFolder,\n resolver: TypeResolver,\n ): DocumentedEndpoint | undefined {\n if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) {\n return undefined;\n }\n const call = ApiDocExtractor.decoratorCall(member, ENDPOINT);\n if (call === undefined) {\n return undefined;\n }\n const methodName = member.name.text;\n\n const pathArgument = call.arguments[0];\n if (pathArgument === undefined) {\n throw new ApiDocExtractionError(\n `@Endpoint on '${methodName}' declares no path`,\n SourceLocation.of(call),\n \"Give it a path: @Endpoint('/thing', 'rpc').\",\n );\n }\n const path = folder.foldString(pathArgument, `@Endpoint path on '${methodName}'`);\n\n const kindArgument = call.arguments[1];\n if (kindArgument === undefined) {\n throw new ApiDocExtractionError(\n `@Endpoint on '${methodName}' declares no kind`,\n SourceLocation.of(call),\n \"State what triggers it: 'rpc', 'cloudtasks', 'cron' or 'external'.\",\n );\n }\n const kind = folder.foldString(kindArgument, `@Endpoint kind on '${methodName}'`);\n\n const options = call.arguments[2];\n const literal =\n options !== undefined && ts.isObjectLiteralExpression(options) ? options : undefined;\n\n const doc = JsDoc.read(member);\n return new DocumentedEndpoint(\n methodName,\n path,\n kind,\n ApiDocExtractor.booleanProperty(literal, 'hidden'),\n new DocumentedEndpointOptions(\n ApiDocExtractor.booleanProperty(literal, 'formPost'),\n ApiDocExtractor.stringProperty(literal, 'calledBy', folder),\n ApiDocExtractor.stringProperty(literal, 'callerKind', folder),\n ),\n ApiDocExtractor.authOf(member),\n ApiDocExtractor.mcpToolOf(member, folder),\n ApiDocExtractor.decoratorCall(member, MCP_AUTH)?.arguments[0]?.getText(),\n ApiDocExtractor.maskLogOf(member),\n doc.description,\n doc.mcp,\n this.requestOf(member, methodName, resolver),\n this.responseOf(member, methodName, resolver),\n );\n }\n\n /** The FIRST parameter's declared type. An endpoint with no parameter has no request document. */\n private requestOf(\n member: ts.MethodDeclaration,\n methodName: string,\n resolver: TypeResolver,\n ): TypeRef | undefined {\n const parameter = member.parameters[0];\n if (parameter?.type === undefined) {\n return undefined;\n }\n return resolver.resolve(parameter.type, `${methodName}.request`);\n }\n\n /** The declared return type, unwrapped from `Promise<...>` by the resolver. */\n private responseOf(\n member: ts.MethodDeclaration,\n methodName: string,\n resolver: TypeResolver,\n ): TypeRef | undefined {\n if (member.type === undefined) {\n return undefined;\n }\n return resolver.resolve(member.type, `${methodName}.response`);\n }\n\n /** `@WpAuthPublic()`, `@WpAuthJwt({...})`, … — recorded verbatim; this package rules on nothing. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static authOf(member: ts.Node): DocumentedAuth | undefined {\n const decorators = ts.canHaveDecorators(member) ? (ts.getDecorators(member) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) {\n continue;\n }\n const name = call.expression.text;\n if (name.startsWith(AUTH_PREFIX) && name !== MCP_AUTH) {\n return new DocumentedAuth(name, call.arguments[0]?.getText());\n }\n }\n return undefined;\n }\n\n /** `@WpMcpTool({ name, readOnly, ... })` — the name plus whichever hints were declared. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static mcpToolOf(\n member: ts.Node,\n folder: ConstantFolder,\n ): DocumentedMcpTool | undefined {\n const call = ApiDocExtractor.decoratorCall(member, MCP_TOOL);\n const argument = call?.arguments[0];\n if (argument === undefined || !ts.isObjectLiteralExpression(argument)) {\n return undefined;\n }\n const hints = new Map<string, boolean>();\n for (const hint of TOOL_HINTS) {\n const value = ApiDocExtractor.findProperty(argument, hint);\n if (value !== undefined) {\n hints.set(hint, value.kind === ts.SyntaxKind.TrueKeyword);\n }\n }\n return new DocumentedMcpTool(\n ApiDocExtractor.stringProperty(argument, 'name', folder) ?? '',\n hints,\n );\n }\n\n /** `@MaskLog({ refreshToken: 'full' })` -> field name -> mask mode. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static maskLogOf(member: ts.Node): ReadonlyMap<string, string> {\n const fields = new Map<string, string>();\n const argument = ApiDocExtractor.decoratorCall(member, MASK_LOG)?.arguments[0];\n if (argument === undefined || !ts.isObjectLiteralExpression(argument)) {\n return fields;\n }\n for (const property of argument.properties) {\n if (\n ts.isPropertyAssignment(property) &&\n (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) &&\n ts.isStringLiteralLike(property.initializer)\n ) {\n fields.set(property.name.text, property.initializer.text);\n }\n }\n return fields;\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static booleanProperty(\n literal: ts.ObjectLiteralExpression | undefined,\n name: string,\n ): boolean {\n const value =\n literal === undefined ? undefined : ApiDocExtractor.findProperty(literal, name);\n return value?.kind === ts.SyntaxKind.TrueKeyword;\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static stringProperty(\n literal: ts.ObjectLiteralExpression | undefined,\n name: string,\n folder: ConstantFolder,\n ): string | undefined {\n const value =\n literal === undefined ? undefined : ApiDocExtractor.findProperty(literal, name);\n return value === undefined ? undefined : folder.tryFoldString(value);\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static findProperty(\n literal: ts.ObjectLiteralExpression,\n name: string,\n ): ts.Expression | undefined {\n for (const property of literal.properties) {\n if (\n ts.isPropertyAssignment(property) &&\n (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) &&\n property.name.text === name\n ) {\n return property.initializer;\n }\n }\n return undefined;\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static decoratorCall(\n node: ts.Node,\n decoratorName: string,\n ): ts.CallExpression | undefined {\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 === decoratorName\n ) {\n return call;\n }\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ApiDocExtractor.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/ApiDocExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["import * as ts from 'typescript';\nimport {\n ApiPath,\n CLOUDTASKS,\n CRON,\n Endpoint,\n EXTERNAL,\n GET,\n MaskLog,\n POST,\n READ,\n RPC,\n WRITE,\n WRITE_IDEMPOTENT,\n WpAuthApiKey,\n WpAuthJwt,\n WpAuthLocalOnly,\n WpAuthOidc,\n WpAuthPublic,\n WpAuthSharedSecret,\n WpAuthWebhook,\n ApiType,\n EXTERNAL_CUSTOMER,\n MCP,\n SVC_TO_SVC,\n WpMcpAuthJwt,\n WpMcpTool,\n} from '@webpieces/core-util';\nimport {\n ApiDocModel,\n DocumentedApiKey,\n DocumentedApiKeyCredential,\n DocumentedAuth,\n DocumentedEndpoint,\n DocumentedEndpointOptions,\n DocumentedMcpTool,\n} from '../model/ApiDocModel';\nimport { TypeRef } from '../model/TypeRef';\nimport { ApiDocExtractionError } from './ApiDocExtractionError';\nimport { ConstantFolder } from './ConstantFolder';\nimport { JsDoc } from './JsDoc';\nimport { SourceLocation } from './SourceLocation';\nimport { TypeResolver } from './TypeResolver';\n\n/**\n * The decorator names this extractor matches on, taken from the REAL SYMBOLS rather than re-typed as\n * string literals.\n *\n * ## Why `.name` and not `'Endpoint'`\n *\n * The extractor matches decorators by NAME on the syntax — it must, because it reads a contract with\n * the compiler and never executes it. The question is only where that name comes from, and a literal\n * has a silent failure mode that a symbol does not: rename `@WpMcpTool` in `core-util` and a literal\n * simply stops matching. The extractor then finds zero MCP tools, the MCP document is correctly not\n * written because it has no tools in it, and the BUILD IS GREEN. A partner-facing document silently\n * loses a section with nothing red anywhere (issue #1001).\n *\n * `Endpoint.name` makes that rename a COMPILE ERROR here, which in this repo is the delivery\n * mechanism for a migration rather than an obstacle to one: a compile break names the new spelling\n * and an agent applies it in one pass, where a green build teaches nobody anything.\n *\n * ## Why importing `@webpieces/core-util` is not the coupling it looks like\n *\n * Any contract carrying `@Endpoint` already depends on `core-util` by definition, so there is no\n * upstream project this import could shut out. It is build-time only, it creates no cycle, and it\n * costs a browser bundle nothing because nothing in a bundle imports this package. The direction that\n * WOULD be fatal is `core-util` depending on the TypeScript compiler, and that is not this.\n */\nconst API_PATH = ApiPath.name;\nconst ENDPOINT = Endpoint.name;\nconst MASK_LOG = MaskLog.name;\nconst MCP_TOOL = WpMcpTool.name;\nconst MCP_AUTH = WpMcpAuthJwt.name;\nconst API_KEY_AUTH = WpAuthApiKey.name;\nconst API_TYPE = ApiType.name;\n\n/**\n * The prefix shared by every credential decorator. A PREFIX genuinely has no symbol to take a name\n * from, so it stays a literal — but the set it selects is pinned below, which is what stops it\n * quietly matching nothing.\n */\nconst AUTH_PREFIX = 'WpAuth';\n\n/** Every credential decorator, by real symbol, so a rename of any of them fails to compile here. */\nconst AUTH_DECORATORS = new Set([\n WpAuthPublic.name,\n WpAuthJwt.name,\n WpAuthOidc.name,\n WpAuthSharedSecret.name,\n WpAuthWebhook.name,\n WpAuthApiKey.name,\n WpAuthLocalOnly.name,\n]);\n\n/** The REAL trigger kinds and side-effect contracts, so a contract cannot declare one that is not. */\nconst ENDPOINT_KINDS: readonly string[] = [RPC, CLOUDTASKS, CRON, EXTERNAL];\nconst ENDPOINT_OPERATIONS: readonly string[] = [READ, WRITE_IDEMPOTENT, WRITE];\nconst HTTP_METHODS: readonly string[] = [GET, POST];\n\n/** `@Endpoint`'s four required positions, named so a failure can say which one is missing. */\nconst ARGUMENT_NAMES = ['http method', 'path', 'operation', 'trigger kind'];\n\n/** The allowed values of each required position, in the same order, for the same failure. */\nconst ARGUMENT_VALUES: readonly (readonly string[])[] = [\n HTTP_METHODS,\n [],\n ENDPOINT_OPERATIONS,\n ENDPOINT_KINDS,\n];\n\n/** The REAL document types, so a contract cannot name a document that does not exist. */\nconst API_TYPES: readonly string[] = [SVC_TO_SVC, EXTERNAL_CUSTOMER, MCP];\n\n/** The fail-closed default: a contract that declares nothing feeds only the internal document. */\nconst DEFAULT_API_TYPES: readonly string[] = [SVC_TO_SVC];\n\n/**\n * ONE contract file -> ONE {@link ApiDocModel}. The single extraction pass both the OpenAPI documents\n * and the MCP tool list (#982) are rendered from.\n *\n * ## Why the compiler API at all\n *\n * A DTO field's TYPE IS ERASED AT RUNTIME. Reflection can see that `save` takes one argument; it\n * cannot see that the argument has a `deliveryWindow` that is a discriminated union of two shapes,\n * one of which carries an ISO timestamp. Those are precisely the shapes a partner-grade document is\n * made of, so the only place they exist is the source, and the only honest way to read the source is\n * the compiler.\n *\n * ## Why it IMPORTS `@webpieces/core-util`\n *\n * Decorators are matched BY NAME on the syntax — they must be, because this reads a contract and\n * never executes it. The NAMES come from the real symbols (`Endpoint.name`), so a rename in\n * `core-util` is a compile error here instead of a literal that quietly stops matching. The full\n * argument, and why the import is not the coupling it looks like, is at those constants.\n *\n * ## What it does NOT do\n *\n * It emits nothing. No OpenAPI, no MCP tool definitions, no file — that is #982, and keeping the\n * render out means the two renderers cannot drift apart about what the contract SAYS.\n */\nexport class ApiDocExtractor {\n /**\n * Extract the contract in `entryFile`.\n *\n * @param entryFile absolute path to the `.ts` file holding the `@ApiPath` class.\n * @param compilerOptions handed straight to `ts.createProgram`; the caller owns them because\n * only the caller knows its own `paths` / `lib` setup.\n * @throws ApiDocExtractionError when the file holds no `@ApiPath` class, or when something that\n * must be exact (a path constant, a numeric bound) cannot be established.\n */\n extractFile(entryFile: string, compilerOptions: ts.CompilerOptions = {}): ApiDocModel {\n const program = ts.createProgram([entryFile], compilerOptions);\n const source = program.getSourceFile(entryFile);\n if (source === undefined) {\n throw new ApiDocExtractionError(\n 'entry file is not part of the program',\n entryFile,\n 'Pass an absolute path to a .ts file that exists.',\n );\n }\n return this.extract(program, source);\n }\n\n /**\n * EVERY `@ApiPath` contract in one file, in declaration order.\n *\n * {@link extractFile} answers \"what is THE contract in this file\", which is the shape a manifest\n * entry and a generated document have: one contract, one file. This answers a different question\n * — \"what does this file declare\" — and it exists because a REPO does not obey that convention.\n * `McpRemoteFixtures.ts` in `@webpieces/mcp-server` declares seven contracts, and the runtime\n * registers MCP tools from all seven; a sweep that read only the first would report green while\n * six contracts' worth of tools had never been looked at, which is the exact shape of silent miss\n * this epic exists to remove.\n *\n * A file with no contract yields an EMPTY list rather than throwing: \"this file has none\" is an\n * ordinary answer to this question, where it is a failure to answer {@link extractFile}'s.\n */\n extractAll(\n entryFile: string,\n compilerOptions: ts.CompilerOptions = {},\n ): readonly ApiDocModel[] {\n const program = ts.createProgram([entryFile], compilerOptions);\n const source = program.getSourceFile(entryFile);\n if (source === undefined) {\n throw new ApiDocExtractionError(\n 'entry file is not part of the program',\n entryFile,\n 'Pass an absolute path to a .ts file that exists.',\n );\n }\n return this.extractAllFrom(program, source);\n }\n\n /** {@link extractAll} against a program the caller already built. */\n extractAllFrom(program: ts.Program, source: ts.SourceFile): readonly ApiDocModel[] {\n const models: ApiDocModel[] = [];\n for (const statement of source.statements) {\n if (\n ts.isClassDeclaration(statement) &&\n ApiDocExtractor.decoratorCall(statement, API_PATH) !== undefined\n ) {\n models.push(this.extractContract(program, statement));\n }\n }\n return models;\n }\n\n /** The same extraction against a program the caller already built. */\n extract(program: ts.Program, source: ts.SourceFile): ApiDocModel {\n return this.extractContract(program, this.findContract(source));\n }\n\n /** ONE contract class -> ONE model. The single place the walk actually happens. */\n private extractContract(program: ts.Program, contract: ts.ClassDeclaration): ApiDocModel {\n const checker = program.getTypeChecker();\n const folder = new ConstantFolder(checker);\n const resolver = new TypeResolver(checker);\n\n const pathDecorator = ApiDocExtractor.decoratorCall(contract, API_PATH)!;\n const basePathArgument = pathDecorator.arguments[0];\n const basePath =\n basePathArgument === undefined\n ? ''\n : folder.foldString(basePathArgument, '@ApiPath argument');\n\n const apiTypes = this.declaredApiTypes(contract, folder);\n const endpoints: DocumentedEndpoint[] = [];\n for (const member of contract.members) {\n const endpoint = this.endpointOf(member, folder, resolver);\n if (endpoint !== undefined) {\n endpoints.push(endpoint);\n }\n }\n this.assertApiTypeMatchesMcpTools(contract, apiTypes, endpoints);\n\n return new ApiDocModel(\n contract.name?.text ?? '<anonymous>',\n apiTypes,\n basePath,\n JsDoc.read(contract).description,\n endpoints,\n resolver.collectedTypes(),\n resolver.collectedUnmapped(),\n );\n }\n\n /**\n * Extract ONE named type and everything it reaches, from a file that holds NO contract.\n *\n * This exists for a type nothing in a contract points at but a document still publishes — the\n * document-wide error body a renderer's manifest names. Reading it with the SAME resolver is the\n * point: a second reader would be a second answer to \"what shape is this type\", and the two\n * would drift the first time a field changed.\n *\n * @throws ApiDocExtractionError when the file does not declare that name.\n */\n extractType(\n entryFile: string,\n typeName: string,\n compilerOptions: ts.CompilerOptions = {},\n ): ApiDocModel {\n const program = ts.createProgram([entryFile], compilerOptions);\n const source = program.getSourceFile(entryFile);\n if (source === undefined) {\n throw new ApiDocExtractionError(\n 'entry file is not part of the program',\n entryFile,\n 'Pass an absolute path to a .ts file that exists.',\n );\n }\n const resolver = new TypeResolver(program.getTypeChecker());\n const declaration = ApiDocExtractor.declarationNamed(source, typeName);\n if (declaration === undefined) {\n throw new ApiDocExtractionError(\n `no type named '${typeName}' is declared in this file`,\n entryFile,\n `Declare and export '${typeName}' there, or name the file that does.`,\n );\n }\n resolver.resolveDeclaration(typeName, declaration, typeName);\n return new ApiDocModel(\n typeName,\n DEFAULT_API_TYPES,\n '',\n '',\n [],\n resolver.collectedTypes(),\n resolver.collectedUnmapped(),\n );\n }\n\n /** The interface / class / type alias / enum declared under `name` at the file's top level. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static declarationNamed(\n source: ts.SourceFile,\n name: string,\n ): ts.Declaration | undefined {\n for (const statement of source.statements) {\n const named =\n ts.isInterfaceDeclaration(statement) ||\n ts.isClassDeclaration(statement) ||\n ts.isTypeAliasDeclaration(statement) ||\n ts.isEnumDeclaration(statement);\n if (named && statement.name?.text === name) {\n return statement;\n }\n }\n return undefined;\n }\n\n /** The one `@ApiPath` class in the file. Zero is a hard failure; the FIRST wins if there are two. */\n private findContract(source: ts.SourceFile): ts.ClassDeclaration {\n for (const statement of source.statements) {\n if (\n ts.isClassDeclaration(statement) &&\n ApiDocExtractor.decoratorCall(statement, API_PATH) !== undefined\n ) {\n return statement;\n }\n }\n throw new ApiDocExtractionError(\n 'no @ApiPath class in this file',\n source.fileName,\n 'Point the extractor at the contract file — the one whose class carries @ApiPath.',\n );\n }\n\n /** One `@Endpoint` method. Members without the decorator are not part of the contract. */\n private endpointOf(\n member: ts.ClassElement,\n folder: ConstantFolder,\n resolver: TypeResolver,\n ): DocumentedEndpoint | undefined {\n if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) {\n return undefined;\n }\n const call = ApiDocExtractor.decoratorCall(member, ENDPOINT);\n if (call === undefined) {\n return undefined;\n }\n const methodName = member.name.text;\n\n // `@Endpoint(httpMethod, path, operation, kind, options?)` — METHOD-FIRST, five positions.\n // The positions are read here and nowhere else, so the one place that has to change when the\n // decorator changes is this block plus `ARGUMENT_NAMES` beside it.\n const httpMethod = this.requiredArgument(call, methodName, 0, folder);\n const path = this.requiredArgument(call, methodName, 1, folder);\n const operation = this.requiredArgument(call, methodName, 2, folder);\n const kind = this.requiredArgument(call, methodName, 3, folder);\n\n const options = call.arguments[4];\n const literal =\n options !== undefined && ts.isObjectLiteralExpression(options) ? options : undefined;\n\n const doc = JsDoc.read(member);\n const mcpTool = ApiDocExtractor.mcpToolOf(member, folder);\n return new DocumentedEndpoint(\n methodName,\n httpMethod,\n path,\n operation,\n kind,\n ApiDocExtractor.booleanProperty(literal, 'hidden'),\n ApiDocExtractor.booleanProperty(literal, 'openWorld'),\n new DocumentedEndpointOptions(\n ApiDocExtractor.booleanProperty(literal, 'formPost'),\n ApiDocExtractor.stringProperty(literal, 'calledBy', folder),\n ApiDocExtractor.stringProperty(literal, 'callerKind', folder),\n ),\n ApiDocExtractor.authOf(member, folder),\n mcpTool,\n ApiDocExtractor.decoratorCall(member, MCP_AUTH)?.arguments[0]?.getText(),\n ApiDocExtractor.maskLogOf(member),\n doc.description,\n doc.mcp,\n this.requestOf(member, methodName, resolver),\n this.responseOf(member, methodName, resolver),\n );\n }\n\n /**\n * One REQUIRED positional argument of `@Endpoint`, folded to the string it denotes.\n *\n * Every one of the four is an enum member (`POST`, `READ`, `RPC`) as often as it is a literal,\n * and the folder resolves both — a document that printed `RPC` where the trigger goes would be\n * worse than no document. A missing one is a hard failure naming the position, because the\n * alternative is a document quietly missing a verb.\n */\n private requiredArgument(\n call: ts.CallExpression,\n methodName: string,\n index: number,\n folder: ConstantFolder,\n ): string {\n const argument = call.arguments[index];\n const what = ARGUMENT_NAMES[index];\n if (argument === undefined) {\n throw new ApiDocExtractionError(\n `@Endpoint on '${methodName}' declares no ${what}`,\n SourceLocation.of(call),\n \"Write all four: @Endpoint(POST, '/thing', READ, RPC).\",\n );\n }\n const value = folder.foldString(argument, `@Endpoint ${what} on '${methodName}'`);\n const allowed = ARGUMENT_VALUES[index]!;\n if (allowed.length > 0 && !allowed.includes(value)) {\n throw new ApiDocExtractionError(\n `@Endpoint on '${methodName}' declares ${what} '${value}', which is not one of ` +\n allowed.join(', '),\n SourceLocation.of(argument),\n `Use one of the exported constants: ${allowed.join(', ')}.`,\n );\n }\n return value;\n }\n\n /**\n * `@ApiType(...)` on the contract, folded to the real values.\n *\n * A value that is not one of the three documents is a HARD FAILURE rather than a silent drop: a\n * contract that named `CUSTOMER` by mistake would otherwise feed nothing, which looks exactly\n * like a contract somebody deliberately kept internal.\n */\n private declaredApiTypes(node: ts.Node, folder: ConstantFolder): readonly string[] {\n const call = ApiDocExtractor.decoratorCall(node, API_TYPE);\n if (call === undefined) {\n return DEFAULT_API_TYPES;\n }\n const declared = call.arguments.map((argument: ts.Expression) =>\n folder.foldString(argument, `@${API_TYPE} argument`),\n );\n for (const apiType of declared) {\n if (!API_TYPES.includes(apiType)) {\n throw new ApiDocExtractionError(\n `@${API_TYPE} names '${apiType}', which is not a generated document`,\n SourceLocation.of(call),\n `Use one of the exported constants: ${API_TYPES.join(', ')}.`,\n );\n }\n }\n return declared.length === 0 ? DEFAULT_API_TYPES : declared;\n }\n\n /**\n * MCP membership has exactly ONE spelling, and this is the build-time half of enforcing it\n * (`assertApiTypeMatchesMcpTools` in `@webpieces/core-util` is the wiring-time half).\n *\n * Declared in two places, the two can disagree — and the disagreement is invisible, because each\n * declaration is individually valid. That is the defect this whole epic exists to remove, so it\n * fails the DOCUMENT build rather than producing one that quietly lists the wrong tools.\n */\n private assertApiTypeMatchesMcpTools(\n contract: ts.ClassDeclaration,\n apiTypes: readonly string[],\n endpoints: readonly DocumentedEndpoint[],\n ): void {\n const tools = endpoints.filter((e: DocumentedEndpoint) => e.mcpTool !== undefined);\n const declaresMcp = apiTypes.includes(MCP);\n if (declaresMcp && tools.length === 0) {\n throw new ApiDocExtractionError(\n `@${API_TYPE} names MCP but no method carries @${MCP_TOOL}`,\n SourceLocation.of(contract),\n `Add @${MCP_TOOL}({name, description, openWorldHint}) to the methods agents may ` +\n `call, or drop MCP from the @${API_TYPE} list.`,\n );\n }\n if (!declaresMcp && tools.length > 0) {\n const named = tools.map((e: DocumentedEndpoint) => e.methodName).join(', ');\n throw new ApiDocExtractionError(\n `@${MCP_TOOL} is on ${named} but @${API_TYPE} does not name MCP`,\n SourceLocation.of(contract),\n `Add MCP to the @${API_TYPE} list — membership has ONE spelling, so a tool on a ` +\n 'contract nobody published to agents is a contradiction, not a hint.',\n );\n }\n }\n\n /** The FIRST parameter's declared type. An endpoint with no parameter has no request document. */\n private requestOf(\n member: ts.MethodDeclaration,\n methodName: string,\n resolver: TypeResolver,\n ): TypeRef | undefined {\n const parameter = member.parameters[0];\n if (parameter?.type === undefined) {\n return undefined;\n }\n return resolver.resolve(parameter.type, `${methodName}.request`);\n }\n\n /** The declared return type, unwrapped from `Promise<...>` by the resolver. */\n private responseOf(\n member: ts.MethodDeclaration,\n methodName: string,\n resolver: TypeResolver,\n ): TypeRef | undefined {\n if (member.type === undefined) {\n return undefined;\n }\n return resolver.resolve(member.type, `${methodName}.response`);\n }\n\n /** `@WpAuthPublic()`, `@WpAuthJwt({...})`, … — recorded verbatim; this package rules on nothing. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static authOf(member: ts.Node, folder: ConstantFolder): DocumentedAuth | undefined {\n const decorators = ts.canHaveDecorators(member) ? (ts.getDecorators(member) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) {\n continue;\n }\n const name = call.expression.text;\n if (name.startsWith(AUTH_PREFIX) && AUTH_DECORATORS.has(name)) {\n return new DocumentedAuth(\n name,\n call.arguments.map((argument: ts.Expression) => argument.getText()),\n name === API_KEY_AUTH ? ApiDocExtractor.apiKeyOf(call, folder) : undefined,\n );\n }\n }\n return undefined;\n }\n\n /**\n * `@WpAuthApiKey(regime, [{in: 'header', name: 'x-api-key', description: '…'}, …])`, parsed.\n *\n * A malformed declaration FAILS rather than yielding a half-parsed regime: the credentials are\n * what a published document's security block is made of, and a document that silently omitted\n * one would tell a partner they need fewer credentials than the running hook demands.\n */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static apiKeyOf(call: ts.CallExpression, folder: ConstantFolder): DocumentedApiKey {\n const regimeArgument = call.arguments[0];\n const credentialsArgument = call.arguments[1];\n if (regimeArgument === undefined || credentialsArgument === undefined) {\n throw new ApiDocExtractionError(\n '@WpAuthApiKey needs a regime AND its credentials',\n SourceLocation.of(call),\n \"Write both: @WpAuthApiKey('partner', [{ in: 'header', name: 'x-api-key' }]).\",\n );\n }\n const regime = folder.foldString(regimeArgument, '@WpAuthApiKey regime');\n // FOLLOW a name first: a credential list shared by every method of a contract is written\n // once as a `const` and named per method, which is better source than a copy per method.\n const credentialsLiteral = folder.follow(credentialsArgument);\n if (!ts.isArrayLiteralExpression(credentialsLiteral)) {\n throw new ApiDocExtractionError(\n '@WpAuthApiKey credentials is not an array literal',\n SourceLocation.of(credentialsArgument),\n 'Write the credentials as an array literal, inline or in a `const`; a value ' +\n 'assembled at runtime cannot appear in a published security scheme.',\n );\n }\n const credentials: DocumentedApiKeyCredential[] = [];\n for (const element of credentialsLiteral.elements) {\n credentials.push(ApiDocExtractor.credentialOf(element, folder));\n }\n return new DocumentedApiKey(regime, credentials);\n }\n\n /** ONE `{ in: …, name?: …, description?: … }` credential. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static credentialOf(\n expression: ts.Expression,\n folder: ConstantFolder,\n ): DocumentedApiKeyCredential {\n const element = folder.follow(expression);\n if (!ts.isObjectLiteralExpression(element)) {\n throw new ApiDocExtractionError(\n 'an @WpAuthApiKey credential is not an object literal',\n SourceLocation.of(element),\n \"Write it inline: { in: 'header', name: 'x-api-key' }.\",\n );\n }\n const location = ApiDocExtractor.stringProperty(element, 'in', folder);\n if (location === undefined) {\n throw new ApiDocExtractionError(\n 'an @WpAuthApiKey credential declares no `in`',\n SourceLocation.of(element),\n \"Say where it rides: `in: 'header'` with a name, or `in: 'bearer'`.\",\n );\n }\n return new DocumentedApiKeyCredential(\n location,\n ApiDocExtractor.stringProperty(element, 'name', folder),\n ApiDocExtractor.stringProperty(element, 'description', folder),\n );\n }\n\n /**\n * `@WpMcpTool({ name, openWorldHint })` — the two facts the source cannot otherwise state.\n *\n * `description` is NOT read: the method's JSDoc is the description for the agent and the partner\n * alike. The three side-effect hints are not read either — they are computed from the endpoint's\n * `operation`. See {@link DocumentedMcpTool} for why both of those are deliberate.\n */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static mcpToolOf(\n member: ts.Node,\n folder: ConstantFolder,\n ): DocumentedMcpTool | undefined {\n const call = ApiDocExtractor.decoratorCall(member, MCP_TOOL);\n const argument = call?.arguments[0];\n if (argument === undefined || !ts.isObjectLiteralExpression(argument)) {\n return undefined;\n }\n return new DocumentedMcpTool(\n ApiDocExtractor.stringProperty(argument, 'name', folder) ?? '',\n );\n }\n\n /** `@MaskLog({ refreshToken: 'full' })` -> field name -> mask mode. */\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static maskLogOf(member: ts.Node): ReadonlyMap<string, string> {\n const fields = new Map<string, string>();\n const argument = ApiDocExtractor.decoratorCall(member, MASK_LOG)?.arguments[0];\n if (argument === undefined || !ts.isObjectLiteralExpression(argument)) {\n return fields;\n }\n for (const property of argument.properties) {\n if (\n ts.isPropertyAssignment(property) &&\n (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) &&\n ts.isStringLiteralLike(property.initializer)\n ) {\n fields.set(property.name.text, property.initializer.text);\n }\n }\n return fields;\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static booleanProperty(\n literal: ts.ObjectLiteralExpression | undefined,\n name: string,\n ): boolean {\n const value =\n literal === undefined ? undefined : ApiDocExtractor.findProperty(literal, name);\n return value?.kind === ts.SyntaxKind.TrueKeyword;\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static stringProperty(\n literal: ts.ObjectLiteralExpression | undefined,\n name: string,\n folder: ConstantFolder,\n ): string | undefined {\n const value =\n literal === undefined ? undefined : ApiDocExtractor.findProperty(literal, name);\n return value === undefined ? undefined : folder.tryFoldString(value);\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static findProperty(\n literal: ts.ObjectLiteralExpression,\n name: string,\n ): ts.Expression | undefined {\n for (const property of literal.properties) {\n if (\n ts.isPropertyAssignment(property) &&\n (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) &&\n property.name.text === name\n ) {\n return property.initializer;\n }\n }\n return undefined;\n }\n\n // webpieces-disable no-function-outside-class -- private static reader of this class\n private static decoratorCall(\n node: ts.Node,\n decoratorName: string,\n ): ts.CallExpression | undefined {\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 === decoratorName\n ) {\n return call;\n }\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
/**
|
|
3
|
-
* Decorator arguments are constants as often as they are literals — `@Endpoint(SAVE_PATH,
|
|
4
|
-
* `@ApiPath(PATHS.SAVE)` — and a document that printed `SAVE_PATH` as the path
|
|
5
|
-
* no document.
|
|
3
|
+
* Decorator arguments are constants as often as they are literals — `@Endpoint(POST, SAVE_PATH, READ,
|
|
4
|
+
* RPC)`, `@ApiPath(PATHS.SAVE)` — and a document that printed `SAVE_PATH` as the path, or `RPC` as the
|
|
5
|
+
* trigger, would be worse than no document.
|
|
6
6
|
*
|
|
7
7
|
* So the folder resolves them, and where it CANNOT it FAILS. There is deliberately no fallback to
|
|
8
8
|
* the source text and no "best effort" path: a partner-grade document that is quietly wrong about a
|
|
@@ -24,6 +24,19 @@ export declare class ConstantFolder {
|
|
|
24
24
|
foldString(expression: ts.Expression, what: string): string;
|
|
25
25
|
/** The string this expression denotes, or undefined. No throw — for optional arguments. */
|
|
26
26
|
tryFoldString(expression: ts.Expression): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* The expression a name ultimately DENOTES, following `const` declarations through imports.
|
|
29
|
+
*
|
|
30
|
+
* A decorator argument is a constant as often as it is a literal, and that is true of structures
|
|
31
|
+
* too: a credential list shared by every method of a contract is written once as a `const` and
|
|
32
|
+
* named five times, which is better source than five copies. A reader that only understood a
|
|
33
|
+
* literal would force the copies — so the copies would happen, and one of them would drift.
|
|
34
|
+
*
|
|
35
|
+
* Returns the expression unchanged when it is not a name, so a caller can follow first and then
|
|
36
|
+
* ask what shape it is.
|
|
37
|
+
*/
|
|
38
|
+
follow(expression: ts.Expression): ts.Expression;
|
|
39
|
+
private followThrough;
|
|
27
40
|
private tryFold;
|
|
28
41
|
/** Follow an identifier / `Obj.MEMBER` to its declaration's initializer and fold that. */
|
|
29
42
|
private foldThroughSymbol;
|
|
@@ -6,9 +6,9 @@ const ts = tslib_1.__importStar(require("typescript"));
|
|
|
6
6
|
const ApiDocExtractionError_1 = require("./ApiDocExtractionError");
|
|
7
7
|
const SourceLocation_1 = require("./SourceLocation");
|
|
8
8
|
/**
|
|
9
|
-
* Decorator arguments are constants as often as they are literals — `@Endpoint(SAVE_PATH,
|
|
10
|
-
* `@ApiPath(PATHS.SAVE)` — and a document that printed `SAVE_PATH` as the path
|
|
11
|
-
* no document.
|
|
9
|
+
* Decorator arguments are constants as often as they are literals — `@Endpoint(POST, SAVE_PATH, READ,
|
|
10
|
+
* RPC)`, `@ApiPath(PATHS.SAVE)` — and a document that printed `SAVE_PATH` as the path, or `RPC` as the
|
|
11
|
+
* trigger, would be worse than no document.
|
|
12
12
|
*
|
|
13
13
|
* So the folder resolves them, and where it CANNOT it FAILS. There is deliberately no fallback to
|
|
14
14
|
* the source text and no "best effort" path: a partner-grade document that is quietly wrong about a
|
|
@@ -41,6 +41,45 @@ class ConstantFolder {
|
|
|
41
41
|
tryFoldString(expression) {
|
|
42
42
|
return this.tryFold(expression, new Set());
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The expression a name ultimately DENOTES, following `const` declarations through imports.
|
|
46
|
+
*
|
|
47
|
+
* A decorator argument is a constant as often as it is a literal, and that is true of structures
|
|
48
|
+
* too: a credential list shared by every method of a contract is written once as a `const` and
|
|
49
|
+
* named five times, which is better source than five copies. A reader that only understood a
|
|
50
|
+
* literal would force the copies — so the copies would happen, and one of them would drift.
|
|
51
|
+
*
|
|
52
|
+
* Returns the expression unchanged when it is not a name, so a caller can follow first and then
|
|
53
|
+
* ask what shape it is.
|
|
54
|
+
*/
|
|
55
|
+
follow(expression) {
|
|
56
|
+
return this.followThrough(expression, new Set());
|
|
57
|
+
}
|
|
58
|
+
followThrough(expression, seen) {
|
|
59
|
+
if (seen.has(expression)) {
|
|
60
|
+
return expression;
|
|
61
|
+
}
|
|
62
|
+
seen.add(expression);
|
|
63
|
+
if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)) {
|
|
64
|
+
return this.followThrough(expression.expression, seen);
|
|
65
|
+
}
|
|
66
|
+
if (!ts.isIdentifier(expression) && !ts.isPropertyAccessExpression(expression)) {
|
|
67
|
+
return expression;
|
|
68
|
+
}
|
|
69
|
+
const symbol = this.checker.getSymbolAtLocation(expression);
|
|
70
|
+
const resolved = symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0
|
|
71
|
+
? this.checker.getAliasedSymbol(symbol)
|
|
72
|
+
: symbol;
|
|
73
|
+
for (const declaration of resolved?.declarations ?? []) {
|
|
74
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
75
|
+
return this.followThrough(declaration.initializer, seen);
|
|
76
|
+
}
|
|
77
|
+
if (ts.isPropertyAssignment(declaration) && ts.isExpression(declaration.initializer)) {
|
|
78
|
+
return this.followThrough(declaration.initializer, seen);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return expression;
|
|
82
|
+
}
|
|
44
83
|
tryFold(expression, seen) {
|
|
45
84
|
if (seen.has(expression)) {
|
|
46
85
|
return undefined; // a const initialised from itself; not our problem to diagnose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConstantFolder.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/ConstantFolder.ts"],"names":[],"mappings":";;;;AAAA,uDAAiC;AACjC,mEAAgE;AAChE,qDAAkD;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAa,cAAc;IACM;IAA7B,YAA6B,OAAuB;QAAvB,YAAO,GAAP,OAAO,CAAgB;IAAG,CAAC;IAExD;;;;OAIG;IACH,UAAU,CAAC,UAAyB,EAAE,IAAY;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,GAAG,EAAW,CAAC,CAAC;QAC5D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,IAAI,6CAAqB,CAC3B,GAAG,IAAI,iCAAiC,UAAU,CAAC,OAAO,EAAE,GAAG,EAC/D,+BAAc,CAAC,EAAE,CAAC,UAAU,CAAC,EAC7B,2FAA2F;gBACvF,sEAAsE,CAC7E,CAAC;QACN,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,2FAA2F;IAC3F,aAAa,CAAC,UAAyB;QACnC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,GAAG,EAAW,CAAC,CAAC;IACxD,CAAC;IAEO,OAAO,CAAC,UAAyB,EAAE,IAAkB;QACzD,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC,CAAC,+DAA+D;QACrF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAErB,IAAI,EAAE,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC;YACrC,OAAO,UAAU,CAAC,IAAI,CAAC;QAC3B,CAAC;QAED,mFAAmF;QACnF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,IAAI,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;YACtC,IAAI,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC;gBAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;gBACjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACrB,OAAO,SAAS,CAAC;gBACrB,CAAC;gBACD,GAAG,IAAI,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACpC,CAAC;YACD,OAAO,GAAG,CAAC;QACf,CAAC;QAED,IACI,EAAE,CAAC,kBAAkB,CAAC,UAAU,CAAC;YACjC,UAAU,CAAC,aAAa,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,SAAS,EAC3D,CAAC;YACC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACnD,OAAO,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;QAChF,CAAC;QAED,IAAI,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,0BAA0B,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3E,OAAO,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,EAAE,CAAC,yBAAyB,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5E,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,0FAA0F;IAClF,iBAAiB,CAAC,UAAyB,EAAE,IAAkB;QACnE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAC5D,MAAM,QAAQ,GACV,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;YAC/D,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC;YACvC,CAAC,CAAC,MAAM,CAAC;QACjB,KAAK,MAAM,WAAW,IAAI,QAAQ,EAAE,YAAY,IAAI,EAAE,EAAE,CAAC;YACrD,IAAI,EAAE,CAAC,qBAAqB,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;gBACnE,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACvD,CAAC;YACD,IAAI,EAAE,CAAC,oBAAoB,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;gBACnF,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACvD,CAAC;YACD,IAAI,EAAE,CAAC,qBAAqB,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;gBACnE,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACvD,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ;AA9FD,wCA8FC","sourcesContent":["import * as ts from 'typescript';\nimport { ApiDocExtractionError } from './ApiDocExtractionError';\nimport { SourceLocation } from './SourceLocation';\n\n/**\n * Decorator arguments are constants as often as they are literals — `@Endpoint(SAVE_PATH, 'rpc')`,\n * `@ApiPath(PATHS.SAVE)` — and a document that printed `SAVE_PATH` as the path would be worse than\n * no document.\n *\n * So the folder resolves them, and where it CANNOT it FAILS. There is deliberately no fallback to\n * the source text and no \"best effort\" path: a partner-grade document that is quietly wrong about a\n * URL is the one failure mode nobody catches by reading it.\n *\n * It asks the CHECKER first, which is what makes this robust across re-exports, imports and\n * `as const` objects: a `const` initialised with a string has a string-LITERAL type, and the checker\n * has already done the resolution. The syntactic walk below it exists only for the shapes the\n * checker widens.\n */\nexport class ConstantFolder {\n constructor(private readonly checker: ts.TypeChecker) {}\n\n /**\n * The string this expression denotes.\n *\n * @throws ApiDocExtractionError when it cannot be established — never a guess.\n */\n foldString(expression: ts.Expression, what: string): string {\n const folded = this.tryFold(expression, new Set<ts.Node>());\n if (folded === undefined) {\n throw new ApiDocExtractionError(\n `${what} is not a foldable constant: '${expression.getText()}'`,\n SourceLocation.of(expression),\n 'Declare it as a `const` initialised with a string literal (or write the literal inline). ' +\n 'A value only known at runtime cannot appear in a published document.',\n );\n }\n return folded;\n }\n\n /** The string this expression denotes, or undefined. No throw — for optional arguments. */\n tryFoldString(expression: ts.Expression): string | undefined {\n return this.tryFold(expression, new Set<ts.Node>());\n }\n\n private tryFold(expression: ts.Expression, seen: Set<ts.Node>): string | undefined {\n if (seen.has(expression)) {\n return undefined; // a const initialised from itself; not our problem to diagnose\n }\n seen.add(expression);\n\n if (ts.isStringLiteralLike(expression)) {\n return expression.text;\n }\n\n // The checker's answer, which already resolved imports, re-exports and `as const`.\n const type = this.checker.getTypeAtLocation(expression);\n if (type.isStringLiteral()) {\n return type.value;\n }\n\n if (ts.isTemplateExpression(expression)) {\n let out = expression.head.text;\n for (const span of expression.templateSpans) {\n const part = this.tryFold(span.expression, seen);\n if (part === undefined) {\n return undefined;\n }\n out += part + span.literal.text;\n }\n return out;\n }\n\n if (\n ts.isBinaryExpression(expression) &&\n expression.operatorToken.kind === ts.SyntaxKind.PlusToken\n ) {\n const left = this.tryFold(expression.left, seen);\n const right = this.tryFold(expression.right, seen);\n return left === undefined || right === undefined ? undefined : left + right;\n }\n\n if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression)) {\n return this.foldThroughSymbol(expression, seen);\n }\n\n if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)) {\n return this.tryFold(expression.expression, seen);\n }\n\n return undefined;\n }\n\n /** Follow an identifier / `Obj.MEMBER` to its declaration's initializer and fold that. */\n private foldThroughSymbol(expression: ts.Expression, seen: Set<ts.Node>): string | undefined {\n const symbol = this.checker.getSymbolAtLocation(expression);\n const resolved =\n symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0\n ? this.checker.getAliasedSymbol(symbol)\n : symbol;\n for (const declaration of resolved?.declarations ?? []) {\n if (ts.isVariableDeclaration(declaration) && declaration.initializer) {\n return this.tryFold(declaration.initializer, seen);\n }\n if (ts.isPropertyAssignment(declaration) && ts.isExpression(declaration.initializer)) {\n return this.tryFold(declaration.initializer, seen);\n }\n if (ts.isPropertyDeclaration(declaration) && declaration.initializer) {\n return this.tryFold(declaration.initializer, seen);\n }\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ConstantFolder.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/ConstantFolder.ts"],"names":[],"mappings":"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","sourcesContent":["import * as ts from 'typescript';\nimport { ApiDocExtractionError } from './ApiDocExtractionError';\nimport { SourceLocation } from './SourceLocation';\n\n/**\n * Decorator arguments are constants as often as they are literals — `@Endpoint(POST, SAVE_PATH, READ,\n * RPC)`, `@ApiPath(PATHS.SAVE)` — and a document that printed `SAVE_PATH` as the path, or `RPC` as the\n * trigger, would be worse than no document.\n *\n * So the folder resolves them, and where it CANNOT it FAILS. There is deliberately no fallback to\n * the source text and no \"best effort\" path: a partner-grade document that is quietly wrong about a\n * URL is the one failure mode nobody catches by reading it.\n *\n * It asks the CHECKER first, which is what makes this robust across re-exports, imports and\n * `as const` objects: a `const` initialised with a string has a string-LITERAL type, and the checker\n * has already done the resolution. The syntactic walk below it exists only for the shapes the\n * checker widens.\n */\nexport class ConstantFolder {\n constructor(private readonly checker: ts.TypeChecker) {}\n\n /**\n * The string this expression denotes.\n *\n * @throws ApiDocExtractionError when it cannot be established — never a guess.\n */\n foldString(expression: ts.Expression, what: string): string {\n const folded = this.tryFold(expression, new Set<ts.Node>());\n if (folded === undefined) {\n throw new ApiDocExtractionError(\n `${what} is not a foldable constant: '${expression.getText()}'`,\n SourceLocation.of(expression),\n 'Declare it as a `const` initialised with a string literal (or write the literal inline). ' +\n 'A value only known at runtime cannot appear in a published document.',\n );\n }\n return folded;\n }\n\n /** The string this expression denotes, or undefined. No throw — for optional arguments. */\n tryFoldString(expression: ts.Expression): string | undefined {\n return this.tryFold(expression, new Set<ts.Node>());\n }\n\n /**\n * The expression a name ultimately DENOTES, following `const` declarations through imports.\n *\n * A decorator argument is a constant as often as it is a literal, and that is true of structures\n * too: a credential list shared by every method of a contract is written once as a `const` and\n * named five times, which is better source than five copies. A reader that only understood a\n * literal would force the copies — so the copies would happen, and one of them would drift.\n *\n * Returns the expression unchanged when it is not a name, so a caller can follow first and then\n * ask what shape it is.\n */\n follow(expression: ts.Expression): ts.Expression {\n return this.followThrough(expression, new Set<ts.Node>());\n }\n\n private followThrough(expression: ts.Expression, seen: Set<ts.Node>): ts.Expression {\n if (seen.has(expression)) {\n return expression;\n }\n seen.add(expression);\n if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)) {\n return this.followThrough(expression.expression, seen);\n }\n if (!ts.isIdentifier(expression) && !ts.isPropertyAccessExpression(expression)) {\n return expression;\n }\n const symbol = this.checker.getSymbolAtLocation(expression);\n const resolved =\n symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0\n ? this.checker.getAliasedSymbol(symbol)\n : symbol;\n for (const declaration of resolved?.declarations ?? []) {\n if (ts.isVariableDeclaration(declaration) && declaration.initializer) {\n return this.followThrough(declaration.initializer, seen);\n }\n if (ts.isPropertyAssignment(declaration) && ts.isExpression(declaration.initializer)) {\n return this.followThrough(declaration.initializer, seen);\n }\n }\n return expression;\n }\n\n private tryFold(expression: ts.Expression, seen: Set<ts.Node>): string | undefined {\n if (seen.has(expression)) {\n return undefined; // a const initialised from itself; not our problem to diagnose\n }\n seen.add(expression);\n\n if (ts.isStringLiteralLike(expression)) {\n return expression.text;\n }\n\n // The checker's answer, which already resolved imports, re-exports and `as const`.\n const type = this.checker.getTypeAtLocation(expression);\n if (type.isStringLiteral()) {\n return type.value;\n }\n\n if (ts.isTemplateExpression(expression)) {\n let out = expression.head.text;\n for (const span of expression.templateSpans) {\n const part = this.tryFold(span.expression, seen);\n if (part === undefined) {\n return undefined;\n }\n out += part + span.literal.text;\n }\n return out;\n }\n\n if (\n ts.isBinaryExpression(expression) &&\n expression.operatorToken.kind === ts.SyntaxKind.PlusToken\n ) {\n const left = this.tryFold(expression.left, seen);\n const right = this.tryFold(expression.right, seen);\n return left === undefined || right === undefined ? undefined : left + right;\n }\n\n if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression)) {\n return this.foldThroughSymbol(expression, seen);\n }\n\n if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)) {\n return this.tryFold(expression.expression, seen);\n }\n\n return undefined;\n }\n\n /** Follow an identifier / `Obj.MEMBER` to its declaration's initializer and fold that. */\n private foldThroughSymbol(expression: ts.Expression, seen: Set<ts.Node>): string | undefined {\n const symbol = this.checker.getSymbolAtLocation(expression);\n const resolved =\n symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0\n ? this.checker.getAliasedSymbol(symbol)\n : symbol;\n for (const declaration of resolved?.declarations ?? []) {\n if (ts.isVariableDeclaration(declaration) && declaration.initializer) {\n return this.tryFold(declaration.initializer, seen);\n }\n if (ts.isPropertyAssignment(declaration) && ts.isExpression(declaration.initializer)) {\n return this.tryFold(declaration.initializer, seen);\n }\n if (ts.isPropertyDeclaration(declaration) && declaration.initializer) {\n return this.tryFold(declaration.initializer, seen);\n }\n }\n return undefined;\n }\n}\n"]}
|
package/src/extract/JsDoc.d.ts
CHANGED
|
@@ -19,6 +19,16 @@ export declare class JsDoc {
|
|
|
19
19
|
* the human one" are different facts, and only the first is worth trusting.
|
|
20
20
|
*/
|
|
21
21
|
readonly mcp: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The `@mcpHeader <token>` block tag — the MCP 2026 SEP-2243 header a PRIMITIVE tool
|
|
24
|
+
* parameter is mirrored into (`Mcp-Param-{token}`).
|
|
25
|
+
*
|
|
26
|
+
* It is a JSDoc tag and not a decorator because it is a DOCUMENTATION fact about one field
|
|
27
|
+
* of one wire document, and this epic's rule is that documentation has one source. The
|
|
28
|
+
* runtime spells the same fact as `WpMcpHeader` inside `@WpDtoField`; #984 deletes that
|
|
29
|
+
* spelling, and the equivalence gate (#983) is what proves the two say the same thing first.
|
|
30
|
+
*/
|
|
31
|
+
readonly mcpHeader: string | undefined;
|
|
22
32
|
private constructor();
|
|
23
33
|
/** Read the JSDoc attached to one declaration. */
|
|
24
34
|
static read(node: ts.Node): JsDoc;
|
package/src/extract/JsDoc.js
CHANGED
|
@@ -15,6 +15,7 @@ class JsDoc {
|
|
|
15
15
|
description;
|
|
16
16
|
format;
|
|
17
17
|
mcp;
|
|
18
|
+
mcpHeader;
|
|
18
19
|
constructor(
|
|
19
20
|
/** The body text, links flattened, trimmed. Empty string when undocumented. */
|
|
20
21
|
description,
|
|
@@ -26,10 +27,21 @@ class JsDoc {
|
|
|
26
27
|
* fallback is NOT applied here: "the author wrote an agent-facing sentence" and "we reused
|
|
27
28
|
* the human one" are different facts, and only the first is worth trusting.
|
|
28
29
|
*/
|
|
29
|
-
mcp
|
|
30
|
+
mcp,
|
|
31
|
+
/**
|
|
32
|
+
* The `@mcpHeader <token>` block tag — the MCP 2026 SEP-2243 header a PRIMITIVE tool
|
|
33
|
+
* parameter is mirrored into (`Mcp-Param-{token}`).
|
|
34
|
+
*
|
|
35
|
+
* It is a JSDoc tag and not a decorator because it is a DOCUMENTATION fact about one field
|
|
36
|
+
* of one wire document, and this epic's rule is that documentation has one source. The
|
|
37
|
+
* runtime spells the same fact as `WpMcpHeader` inside `@WpDtoField`; #984 deletes that
|
|
38
|
+
* spelling, and the equivalence gate (#983) is what proves the two say the same thing first.
|
|
39
|
+
*/
|
|
40
|
+
mcpHeader) {
|
|
30
41
|
this.description = description;
|
|
31
42
|
this.format = format;
|
|
32
43
|
this.mcp = mcp;
|
|
44
|
+
this.mcpHeader = mcpHeader;
|
|
33
45
|
}
|
|
34
46
|
/** Read the JSDoc attached to one declaration. */
|
|
35
47
|
// webpieces-disable no-function-outside-class -- static factory; JsDoc has a private constructor so a caller cannot invent prose
|
|
@@ -39,6 +51,7 @@ class JsDoc {
|
|
|
39
51
|
const bodies = [];
|
|
40
52
|
let format;
|
|
41
53
|
let mcp;
|
|
54
|
+
let mcpHeader;
|
|
42
55
|
for (const block of blocks) {
|
|
43
56
|
bodies.push(JsDoc.flatten(block.comment));
|
|
44
57
|
for (const tag of block.tags ?? []) {
|
|
@@ -50,9 +63,12 @@ class JsDoc {
|
|
|
50
63
|
else if (name === 'mcp' && text !== '') {
|
|
51
64
|
mcp = text;
|
|
52
65
|
}
|
|
66
|
+
else if (name === 'mcpHeader' && text !== '') {
|
|
67
|
+
mcpHeader = text;
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
|
-
return new JsDoc(bodies.join('\n').trim(), format, mcp);
|
|
71
|
+
return new JsDoc(bodies.join('\n').trim(), format, mcp, mcpHeader);
|
|
56
72
|
}
|
|
57
73
|
/**
|
|
58
74
|
* A comment's text with every inline tag replaced by its own text: `{@link Foo.bar}` -> `Foo.bar`,
|
package/src/extract/JsDoc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsDoc.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/JsDoc.ts"],"names":[],"mappings":";;;;AAAA,uDAAiC;AAQjC;;;;;;;GAOG;AACH,MAAa,KAAK;IAGD;IAEA;IAOA;
|
|
1
|
+
{"version":3,"file":"JsDoc.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/extract/JsDoc.ts"],"names":[],"mappings":"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","sourcesContent":["import * as ts from 'typescript';\n\n/**\n * A node the compiler has attached JSDoc blocks to. `jsDoc` is internal to the TypeScript API and is\n * therefore not on `ts.Node`, so it is named HERE, once, instead of being cast at the use site.\n */\ntype JsDocCarrier = ts.Node & { jsDoc?: ts.JSDoc[] };\n\n/**\n * The PROSE half of the model: the JSDoc body, the `@format` tag and the `@mcp` override.\n *\n * `{@link Foo.bar}` is flattened to `Foo.bar` HERE, at this boundary, and that placement is the\n * point: a link is a TypeScript editor affordance, and every downstream renderer — OpenAPI\n * `description`, an MCP tool description, an HTML page — would otherwise each have to know the inline\n * tag grammar and each get it slightly differently wrong. One flattening, at extraction.\n */\nexport class JsDoc {\n private constructor(\n /** The body text, links flattened, trimmed. Empty string when undocumented. */\n readonly description: string,\n /** The `@format` block tag's text, e.g. `email` / `date-time`. */\n readonly format: string | undefined,\n /**\n * The `@mcp` block tag's text — the OPTIONAL agent-facing override. Undefined means the\n * author wrote none, which a renderer answers by falling back to {@link description}. The\n * fallback is NOT applied here: \"the author wrote an agent-facing sentence\" and \"we reused\n * the human one\" are different facts, and only the first is worth trusting.\n */\n readonly mcp: string | undefined,\n /**\n * The `@mcpHeader <token>` block tag — the MCP 2026 SEP-2243 header a PRIMITIVE tool\n * parameter is mirrored into (`Mcp-Param-{token}`).\n *\n * It is a JSDoc tag and not a decorator because it is a DOCUMENTATION fact about one field\n * of one wire document, and this epic's rule is that documentation has one source. The\n * runtime spells the same fact as `WpMcpHeader` inside `@WpDtoField`; #984 deletes that\n * spelling, and the equivalence gate (#983) is what proves the two say the same thing first.\n */\n readonly mcpHeader: string | undefined,\n ) {}\n\n /** Read the JSDoc attached to one declaration. */\n // webpieces-disable no-function-outside-class -- static factory; JsDoc has a private constructor so a caller cannot invent prose\n static read(node: ts.Node): JsDoc {\n const symbolLike = node as JsDocCarrier;\n const blocks = symbolLike.jsDoc ?? [];\n const bodies: string[] = [];\n let format: string | undefined;\n let mcp: string | undefined;\n let mcpHeader: string | undefined;\n\n for (const block of blocks) {\n bodies.push(JsDoc.flatten(block.comment));\n for (const tag of block.tags ?? []) {\n const name = tag.tagName.text;\n const text = JsDoc.flatten(tag.comment);\n if (name === 'format' && text !== '') {\n format = text;\n } else if (name === 'mcp' && text !== '') {\n mcp = text;\n } else if (name === 'mcpHeader' && text !== '') {\n mcpHeader = text;\n }\n }\n }\n\n return new JsDoc(bodies.join('\\n').trim(), format, mcp, mcpHeader);\n }\n\n /**\n * A comment's text with every inline tag replaced by its own text: `{@link Foo.bar}` -> `Foo.bar`,\n * `{@link Foo.bar|the widget}` -> `the widget`.\n *\n * TypeScript hands a commented node either a plain string or an array of parts, and the link\n * parts are the ones with structure. Both shapes are handled here so no caller has to.\n */\n // webpieces-disable no-function-outside-class -- private static helper of this class\n private static flatten(comment: string | ts.NodeArray<ts.JSDocComment> | undefined): string {\n if (comment === undefined) {\n return '';\n }\n if (typeof comment === 'string') {\n return comment.trim();\n }\n const parts: string[] = [];\n for (const part of comment) {\n if (ts.isJSDocLink(part) || ts.isJSDocLinkCode(part) || ts.isJSDocLinkPlain(part)) {\n // `text` is whatever followed the target ('|the widget'); the NAME is the target.\n const label = part.text.replace(/^[|\\s]+/, '').trim();\n const target = part.name ? part.name.getText() : '';\n parts.push(label !== '' ? label : target);\n } else {\n parts.push(part.text);\n }\n }\n return parts.join('').trim();\n }\n}\n"]}
|
|
@@ -70,6 +70,15 @@ export declare class TypeResolver {
|
|
|
70
70
|
* rather than guessed at.
|
|
71
71
|
*/
|
|
72
72
|
private resolveReference;
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a type by its DECLARATION rather than by a reference to it.
|
|
75
|
+
*
|
|
76
|
+
* The reference path above is the normal one — a field says `Customer` and the resolver follows
|
|
77
|
+
* it. This entry point exists for a type that is named from OUTSIDE the source: a manifest naming
|
|
78
|
+
* the document-wide error body, which no field in the contract points at. Same registration, same
|
|
79
|
+
* cycle story, so the two cannot disagree about what a type IS.
|
|
80
|
+
*/
|
|
81
|
+
resolveDeclaration(name: string, declaration: ts.Declaration, ownerName: string): TypeRef;
|
|
73
82
|
/** `type X = ...` — registered under X when it has a shape of its own, else transparent. */
|
|
74
83
|
private resolveAlias;
|
|
75
84
|
/** A TS `enum` of string members — the one non-union enum shape a document can carry. */
|
|
@@ -3,16 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TypeResolver = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const ts = tslib_1.__importStar(require("typescript"));
|
|
6
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
6
7
|
const ApiDocModel_1 = require("../model/ApiDocModel");
|
|
7
8
|
const TypeRef_1 = require("../model/TypeRef");
|
|
8
9
|
const ApiDocExtractionError_1 = require("./ApiDocExtractionError");
|
|
9
10
|
const JsDoc_1 = require("./JsDoc");
|
|
10
11
|
const SourceLocation_1 = require("./SourceLocation");
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The decorators this resolver reads off a DTO property, named from the REAL SYMBOLS — a rename in
|
|
14
|
+
* `core-util` is a compile error here rather than a literal that quietly stops matching. See
|
|
15
|
+
* {@link ApiDocExtractor}'s constants for the full argument (issue #1001).
|
|
16
|
+
*/
|
|
17
|
+
const INT_DECORATOR = core_util_1.WpInt.name;
|
|
18
|
+
const MIN_DECORATOR = core_util_1.WpMin.name;
|
|
19
|
+
const MAX_DECORATOR = core_util_1.WpMax.name;
|
|
20
|
+
/**
|
|
21
|
+
* The type-alias name that DECLARES integer-ness.
|
|
22
|
+
*
|
|
23
|
+
* This one is a LITERAL and cannot be anything else: `Integer` is a TYPE ALIAS, so there is no
|
|
24
|
+
* runtime symbol whose `.name` could be read — `.name` is a property of a function, and a type has
|
|
25
|
+
* erased by then. It is the one name in this package that a rename in `core-util` would not break at
|
|
26
|
+
* compile time; the cure if that ever bites is to make integer-ness a decorator here too, not to
|
|
27
|
+
* pretend a type has a runtime identity. See {@link ApiDocExtractor}'s constants for the argument
|
|
28
|
+
* everywhere else (issue #1001).
|
|
29
|
+
*/
|
|
16
30
|
const INTEGER_ALIAS = 'Integer';
|
|
17
31
|
/**
|
|
18
32
|
* Resolve declared TypeScript types into {@link TypeRef}s, registering every NAMED type it meets as
|
|
@@ -231,6 +245,17 @@ class TypeResolver {
|
|
|
231
245
|
if (declaration === undefined) {
|
|
232
246
|
return this.recordUnmapped(node, `no declaration found for '${name}'`);
|
|
233
247
|
}
|
|
248
|
+
return this.resolveDeclaration(name, declaration, ownerName);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resolve a type by its DECLARATION rather than by a reference to it.
|
|
252
|
+
*
|
|
253
|
+
* The reference path above is the normal one — a field says `Customer` and the resolver follows
|
|
254
|
+
* it. This entry point exists for a type that is named from OUTSIDE the source: a manifest naming
|
|
255
|
+
* the document-wide error body, which no field in the contract points at. Same registration, same
|
|
256
|
+
* cycle story, so the two cannot disagree about what a type IS.
|
|
257
|
+
*/
|
|
258
|
+
resolveDeclaration(name, declaration, ownerName) {
|
|
234
259
|
if (ts.isInterfaceDeclaration(declaration) || ts.isClassDeclaration(declaration)) {
|
|
235
260
|
return this.registerNamedObject(name, declaration);
|
|
236
261
|
}
|
|
@@ -240,7 +265,7 @@ class TypeResolver {
|
|
|
240
265
|
if (ts.isEnumDeclaration(declaration)) {
|
|
241
266
|
return this.registerStringEnum(name, declaration);
|
|
242
267
|
}
|
|
243
|
-
return this.recordUnmapped(
|
|
268
|
+
return this.recordUnmapped(declaration, `'${name}' is declared as something with no document shape`);
|
|
244
269
|
}
|
|
245
270
|
/** `type X = ...` — registered under X when it has a shape of its own, else transparent. */
|
|
246
271
|
resolveAlias(name, alias, ownerName) {
|
|
@@ -355,7 +380,7 @@ class TypeResolver {
|
|
|
355
380
|
const min = this.numericArgument(member, MIN_DECORATOR);
|
|
356
381
|
const max = this.numericArgument(member, MAX_DECORATOR);
|
|
357
382
|
this.assertNumericConstraintsFit(member, name, type, min, max);
|
|
358
|
-
return new ApiDocModel_1.DocumentedField(name, type, optional, nullable, doc.description, doc.mcp, doc.format, min, max);
|
|
383
|
+
return new ApiDocModel_1.DocumentedField(name, type, optional, nullable, doc.description, doc.mcp, doc.format, min, max, doc.mcpHeader);
|
|
359
384
|
}
|
|
360
385
|
/**
|
|
361
386
|
* `@WpMin` / `@WpMax` on a non-numeric field is a BUILD FAILURE, not a warning. A minimum on a
|