oas 38.5.0 → 38.5.1

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.
@@ -7,10 +7,12 @@ var _chunkCOHEPMUPcjs = require('../chunk-COHEPMUP.cjs');
7
7
 
8
8
 
9
9
 
10
+
10
11
  var _chunk6GTPCWO5cjs = require('../chunk-6GTPCWO5.cjs');
11
12
  require('../chunk-EARDDQSB.cjs');
12
13
 
13
14
 
15
+
14
16
  var _chunk7PWF3F2Wcjs = require('../chunk-7PWF3F2W.cjs');
15
17
 
16
18
  // src/analyzer/dereference.ts
@@ -113,9 +115,58 @@ function queryCached(queries, definition) {
113
115
 
114
116
  // src/analyzer/scope.ts
115
117
  var _jsonpointer = require('jsonpointer'); var _jsonpointer2 = _interopRequireDefault(_jsonpointer);
118
+ function canonicalizeRefPointer($ref) {
119
+ const pointer = _chunk6GTPCWO5cjs.toPointer.call(void 0, $ref);
120
+ try {
121
+ return decodeURIComponent(pointer);
122
+ } catch (e) {
123
+ return pointer;
124
+ }
125
+ }
126
+ function resolvePathItemTargetPointer(raw, definition) {
127
+ if (!_chunk7PWF3F2Wcjs.isRef.call(void 0, raw)) {
128
+ return void 0;
129
+ }
130
+ let current = raw;
131
+ let lastRef = raw.$ref;
132
+ const seen = /* @__PURE__ */ new Set();
133
+ while (_chunk7PWF3F2Wcjs.isRef.call(void 0, current)) {
134
+ const ref = current.$ref;
135
+ if (seen.has(ref)) {
136
+ break;
137
+ }
138
+ seen.add(ref);
139
+ lastRef = ref;
140
+ let next;
141
+ try {
142
+ next = _jsonpointer2.default.get(definition, canonicalizeRefPointer(ref));
143
+ } catch (e2) {
144
+ break;
145
+ }
146
+ if (next === void 0 || !_chunk7PWF3F2Wcjs.isRef.call(void 0, next)) {
147
+ break;
148
+ }
149
+ current = next;
150
+ }
151
+ return canonicalizeRefPointer(lastRef);
152
+ }
116
153
  function resolveKey(keys, target) {
117
154
  return keys.find((key) => key === target) || keys.find((key) => key.toLowerCase() === target.toLowerCase());
118
155
  }
156
+ function resolvePathItem(raw, definition) {
157
+ const resolved = _chunk6GTPCWO5cjs.dereferenceRef.call(void 0, raw, definition);
158
+ if (!resolved || typeof resolved !== "object" || _chunk7PWF3F2Wcjs.isRef.call(void 0, resolved)) {
159
+ return void 0;
160
+ }
161
+ return resolved;
162
+ }
163
+ function resolveMethodKey(container, method) {
164
+ const methodKey = resolveKey(Object.keys(container), method);
165
+ if (!methodKey || !_chunk6GTPCWO5cjs.supportedMethods.includes(methodKey.toLowerCase())) {
166
+ return void 0;
167
+ }
168
+ return methodKey;
169
+ }
119
170
  function accumulateReachableRefs(definition, seeds) {
120
171
  const reachable = /* @__PURE__ */ new Set();
121
172
  const queue = [...seeds];
@@ -128,7 +179,7 @@ function accumulateReachableRefs(definition, seeds) {
128
179
  let resolved;
129
180
  try {
130
181
  resolved = _jsonpointer2.default.get(definition, _chunk6GTPCWO5cjs.toPointer.call(void 0, ref));
131
- } catch (e) {
182
+ } catch (e3) {
132
183
  continue;
133
184
  }
134
185
  if (resolved === void 0) {
@@ -166,17 +217,28 @@ function computeOperationScope(definition, path, method) {
166
217
  if (!pathKey) {
167
218
  throw new Error(`Path \`${path}\` not found.`);
168
219
  }
169
- const pathItem = definition.paths[pathKey] || {};
170
- const methodKey = resolveKey(Object.keys(pathItem), method);
171
- if (!methodKey || !_chunk6GTPCWO5cjs.supportedMethods.includes(methodKey.toLowerCase())) {
220
+ const rawPathItem = definition.paths[pathKey];
221
+ const pathItem = resolvePathItem(rawPathItem, definition);
222
+ if (!pathItem) {
223
+ throw new Error(`Operation \`${method} ${path}\` not found.`);
224
+ }
225
+ const methodKey = resolveMethodKey(pathItem, method);
226
+ if (!methodKey) {
172
227
  throw new Error(`Operation \`${method} ${path}\` not found.`);
173
228
  }
174
229
  const operation = pathItem[methodKey];
230
+ if (!operation || typeof operation !== "object") {
231
+ throw new Error(`Operation \`${method} ${path}\` not found.`);
232
+ }
175
233
  const rootPointer = `/paths/${_chunk6GTPCWO5cjs.encodePointer.call(void 0, pathKey)}/${methodKey}`;
176
234
  const extraPointers = [];
177
235
  const seeds = new Set(_chunk6GTPCWO5cjs.collectRefsInSchema.call(void 0, operation));
236
+ const targetPointer = resolvePathItemTargetPointer(rawPathItem, definition);
237
+ if (targetPointer) {
238
+ extraPointers.push(`${targetPointer}/${methodKey}`);
239
+ }
178
240
  if (pathItem.parameters) {
179
- extraPointers.push(`/paths/${_chunk6GTPCWO5cjs.encodePointer.call(void 0, pathKey)}/parameters`);
241
+ extraPointers.push(targetPointer ? `${targetPointer}/parameters` : `/paths/${_chunk6GTPCWO5cjs.encodePointer.call(void 0, pathKey)}/parameters`);
180
242
  _chunk6GTPCWO5cjs.collectRefsInSchema.call(void 0, pathItem.parameters).forEach((ref) => seeds.add(ref));
181
243
  }
182
244
  const security = "security" in operation ? operation.security : definition.security;
@@ -190,17 +252,30 @@ function computeWebhookScope(definition, webhookName, method) {
190
252
  if (!webhookKey) {
191
253
  throw new Error(`Webhook \`${webhookName}\` not found.`);
192
254
  }
193
- const webhook = webhooks2[webhookKey] || {};
194
- const methodKey = resolveKey(Object.keys(webhook), method);
195
- if (!methodKey || !_chunk6GTPCWO5cjs.supportedMethods.includes(methodKey.toLowerCase())) {
255
+ const rawWebhook = webhooks2[webhookKey];
256
+ const webhook = resolvePathItem(rawWebhook, definition);
257
+ if (!webhook) {
258
+ throw new Error(`Webhook operation \`${method} ${webhookName}\` not found.`);
259
+ }
260
+ const methodKey = resolveMethodKey(webhook, method);
261
+ if (!methodKey) {
196
262
  throw new Error(`Webhook operation \`${method} ${webhookName}\` not found.`);
197
263
  }
198
264
  const operation = webhook[methodKey];
265
+ if (!operation || typeof operation !== "object") {
266
+ throw new Error(`Webhook operation \`${method} ${webhookName}\` not found.`);
267
+ }
199
268
  const rootPointer = `/webhooks/${_chunk6GTPCWO5cjs.encodePointer.call(void 0, webhookKey)}/${methodKey}`;
200
269
  const extraPointers = [];
201
270
  const seeds = new Set(_chunk6GTPCWO5cjs.collectRefsInSchema.call(void 0, operation));
271
+ const targetPointer = resolvePathItemTargetPointer(rawWebhook, definition);
272
+ if (targetPointer) {
273
+ extraPointers.push(`${targetPointer}/${methodKey}`);
274
+ }
202
275
  if (webhook.parameters) {
203
- extraPointers.push(`/webhooks/${_chunk6GTPCWO5cjs.encodePointer.call(void 0, webhookKey)}/parameters`);
276
+ extraPointers.push(
277
+ targetPointer ? `${targetPointer}/parameters` : `/webhooks/${_chunk6GTPCWO5cjs.encodePointer.call(void 0, webhookKey)}/parameters`
278
+ );
204
279
  _chunk6GTPCWO5cjs.collectRefsInSchema.call(void 0, webhook.parameters).forEach((ref) => seeds.add(ref));
205
280
  }
206
281
  const security = "security" in operation ? operation.security : definition.security;
@@ -468,7 +543,7 @@ async function buildAnalysis(definition, query2, scope) {
468
543
  locations: xmlSchemas2
469
544
  };
470
545
  }
471
- if (!query2 || query2.includes("xmlSchemas")) {
546
+ if (!query2 || query2.includes("webhooks")) {
472
547
  const webhooks2 = webhooks(definition, scope);
473
548
  analysis.webhooks = {
474
549
  present: !!webhooks2.length,
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/erunion/code/readme/oas/packages/oas/dist/analyzer/index.cjs","../../src/analyzer/dereference.ts","../../src/analyzer/query-cache.ts","../../src/analyzer/scope.ts","../../src/analyzer/queries/openapi.ts","../../src/analyzer/index.ts"],"names":["webhooks","query","additionalProperties","callbacks","commonParameters","discriminators","links","parameterSerialization","polymorphism","references","serverVariables","xmlRequests","xmlResponses","xmlSchemas"],"mappings":"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","file":"/Users/erunion/code/readme/oas/packages/oas/dist/analyzer/index.cjs","sourcesContent":[null,"import type { OASDocument } from '../types.js';\nimport type { ParserOptions } from '@readme/openapi-parser';\n\nimport { dereference } from '@readme/openapi-parser';\n\n/**\n * Retrive our dereferencing configuration for `@readme/openapi-parser`.\n *\n */\nfunction getDereferencingOptions(circularRefs: Set<string>): Pick<ParserOptions, 'resolve' | 'dereference'> {\n return {\n resolve: {\n // We shouldn't be resolving external pointers at this point so just ignore them.\n external: false,\n },\n dereference: {\n // If circular `$refs` are ignored they'll remain in the schema as `$ref: String`, otherwise\n // `$ref` just won't exist. This, in tandem with `onCircular`, allows us to do easy and\n // accumulate a list of circular references.\n circular: 'ignore',\n\n onCircular: (path: string) => {\n // The circular references that are coming out of `json-schema-ref-parser` are prefixed\n // with the schema path (file path, URL, whatever) that the schema exists in. Because we\n // don't care about this information for this reporting mechanism, and only the `$ref`\n // pointer, we're removing it.\n circularRefs.add(`#${path.split('#')[1]}`);\n },\n },\n };\n}\n\ninterface DereferenceOasResult {\n api: OASDocument;\n circularRefs: string[];\n}\n\ninterface DereferenceOasState {\n circularRefs: string[];\n complete: boolean;\n processing: boolean;\n promises: {\n reject: (err: unknown) => void;\n resolve: (result: DereferenceOasResult) => void;\n }[];\n result?: DereferenceOasResult;\n}\n\nconst dereferenceStates = new WeakMap<OASDocument, DereferenceOasState>();\n\nfunction getDereferenceState(definition: OASDocument): DereferenceOasState {\n let state = dereferenceStates.get(definition);\n if (!state) {\n state = {\n processing: false,\n complete: false,\n circularRefs: [],\n promises: [],\n };\n dereferenceStates.set(definition, state);\n }\n\n return state;\n}\n\n/**\n * Dereference a given OpenAPI definition so it can be parsed free from the hassle of resolving\n * `$ref` schemas and circular structures.\n *\n */\nexport async function dereferenceOas(\n definition: OASDocument,\n opts?: {\n /**\n * A callback method can be supplied to be called when dereferencing is complete. Used for\n * debugging that the multi-promise handling within this method works.\n *\n * @private\n */\n cb?: () => void;\n },\n): Promise<DereferenceOasResult> {\n const state = getDereferenceState(definition);\n\n if (state.complete && state.result) {\n return state.result;\n }\n\n if (state.processing) {\n return new Promise((resolve, reject) => {\n state.promises.push({ resolve, reject });\n });\n }\n\n state.processing = true;\n\n const circularRefs: Set<string> = new Set();\n const dereferencingOptions = getDereferencingOptions(circularRefs);\n const { promises } = state;\n\n return dereference<OASDocument>(definition, dereferencingOptions)\n .then((dereferenced: OASDocument) => {\n const result: DereferenceOasResult = {\n api: dereferenced,\n circularRefs: [...circularRefs],\n };\n\n state.result = result;\n state.circularRefs = result.circularRefs;\n state.processing = false;\n state.complete = true;\n\n // Used for debugging that dereferencing promise awaiting works.\n if (opts?.cb) {\n opts.cb();\n }\n\n return result;\n })\n .then(result => {\n promises.forEach(deferred => deferred.resolve(result));\n state.promises = [];\n return result;\n })\n .catch(err => {\n state.processing = false;\n promises.forEach(deferred => deferred.reject(err));\n state.promises = [];\n throw err;\n });\n}\n\n/**\n * `dereferenceOas()` caches its result against whatever object reference it's given, but callers\n * that care about not mutating their original definition (like the analyzer) have historically had\n * to pass in a fresh `structuredClone()` on every call — which means that cache is only ever a hit\n * within a single call, never across separate analyses of the same definition.\n *\n * This wraps that up: it clones the given definition exactly once and reuses that same clone (and\n * therefore `dereferenceOas()`'s cached result for it) for every subsequent call with the same\n * original definition reference. This is what lets the analyzer dereference a large API definition\n * a single time and reuse that work across an analysis of every operation within it, instead of\n * re-dereferencing the whole thing per-operation.\n *\n * @param definition An OpenAPI definition to dereference.\n */\nexport function dereferenceOasShared(definition: OASDocument): Promise<DereferenceOasResult> {\n let clone = sharedClones.get(definition);\n if (!clone) {\n clone = structuredClone(definition);\n sharedClones.set(definition, clone);\n }\n\n return dereferenceOas(clone);\n}\n\nconst sharedClones = new WeakMap<OASDocument, OASDocument>();\n","import type { JSONPathResult } from './util.js';\n\nimport { query } from './util.js';\n\nconst cache = new WeakMap<object, Map<string, JSONPathResult[]>>();\n\n/**\n * Every analyzer query in `queries/openapi.ts` runs one or more `$..`-style JSONPath scans across\n * the *entire* API definition, regardless of which operation (if any) we're ultimately interested\n * in. When analyzing a single operation that's cheap to do once, but when analyzing hundreds of\n * operations out of the same definition — which is the whole point of being able to scope analysis\n * to an operation instead of reducing the definition down first — re-running those same full-document\n * scans for every single operation throws away all of that shared work.\n *\n * This cache runs each unique set of JSONPath queries against a given definition exactly once and\n * hands back the same result array on every subsequent call, so callers can cheaply filter it down\n * per-operation instead of re-scanning the whole document each time.\n *\n * Cache entries are keyed by object identity (via `WeakMap`), so this is only effective when\n * callers reuse the same `definition` reference across calls — if you're handed a fresh clone every\n * time there's nothing to reuse. It also means definitions are never manually evicted from the\n * cache; they simply fall out of it once nothing else references them.\n *\n * @param queries JSONPath queries to run.\n * @param definition The object to run them against.\n */\nexport function queryCached(queries: string[], definition: object): JSONPathResult[] {\n let byQuery = cache.get(definition);\n if (!byQuery) {\n byQuery = new Map();\n cache.set(definition, byQuery);\n }\n\n // A space can't appear within a JSONPath query, so it's a safe delimiter to join on for a cache key.\n const cacheKey = queries.join(' ');\n\n let results = byQuery.get(cacheKey);\n if (!results) {\n results = query(queries, definition);\n byQuery.set(cacheKey, results);\n }\n\n return results;\n}\n","import type { OAS31Document, OASDocument } from '../types.js';\n\nimport jsonPointer from 'jsonpointer';\n\nimport { collectRefsInSchema, encodePointer, toPointer } from '../lib/refs.js';\nimport { supportedMethods } from '../utils.js';\n\n/**\n * The set of JSON pointers (in plain `/foo/bar` form, without the leading `#`) that describe\n * everything an operation or webhook touches: itself, any path-level (or webhook-level) common\n * parameters, and every `$ref` pointer that's reachable from either of those.\n *\n * This is what allows the analyzer to be run against a full, undreduced API definition and still\n * only report on what a single operation actually uses.\n */\nexport interface OperationScope {\n /**\n * `anchors`, each with a trailing `/` appended, precomputed once so `isPointerInScope()` isn't\n * concatenating a new string per anchor on every single pointer it checks. Analyzing an\n * operation can mean checking thousands of result pointers against this list, so avoiding a\n * string allocation per comparison meaningfully adds up.\n */\n anchorPrefixes: string[];\n\n /**\n * A flattened, precomputed list of every pointer prefix that's considered \"in scope\". This is\n * derived from `rootPointer`, `extraPointers`, and `reachableRefs` and exists purely so we don't\n * have to rebuild it for every pointer comparison we do.\n */\n anchors: string[];\n\n /**\n * Pointers, beyond `rootPointer`, that should be treated as belonging to this operation (e.g. a\n * path item's common `parameters`).\n */\n extraPointers: string[];\n\n /**\n * Every `$ref` pointer (in `#/...` form) that's transitively reachable from this operation.\n */\n reachableRefs: Set<string>;\n\n /**\n * The pointer to the operation (or webhook operation) itself, e.g. `/paths/~1pet/get` or\n * `/webhooks/newBooking/post`.\n */\n rootPointer: string;\n}\n\n/**\n * Case-insensitively find the real key for a given target within a list of keys. OpenAPI paths and\n * HTTP methods aren't case-sensitive as far as most tooling (including this) is concerned, but the\n * pointers we build need to match the casing that's actually in the document.\n *\n */\nfunction resolveKey(keys: string[], target: string): string | undefined {\n return keys.find(key => key === target) || keys.find(key => key.toLowerCase() === target.toLowerCase());\n}\n\n/**\n * Starting from a seed set of `$ref` pointers, recursively follow every `$ref` that they, or\n * anything they point to, contain and return the full set of pointers that are reachable.\n *\n * This intentionally mirrors the ref-walking that `OpenAPIReducer` does, but is read-only (no\n * cloning, no mutation) and resolves each `$ref` lazily against the original definition instead of\n * requiring a prebuilt map of every possible reference.\n *\n */\nfunction accumulateReachableRefs(definition: OASDocument, seeds: Iterable<string>): Set<string> {\n const reachable = new Set<string>();\n const queue = [...seeds];\n\n while (queue.length) {\n const ref = queue.shift() as string;\n if (reachable.has(ref)) {\n continue;\n }\n\n reachable.add(ref);\n\n let resolved: unknown;\n try {\n resolved = jsonPointer.get(definition, toPointer(ref));\n } catch {\n // If the `$ref` doesn't resolve to anything (a malformed pointer, an external ref we didn't\n // bundle, etc.) there's nothing further to walk from it.\n continue;\n }\n\n if (resolved === undefined) {\n continue;\n }\n\n collectRefsInSchema(resolved).forEach(nestedRef => {\n if (!reachable.has(nestedRef)) {\n queue.push(nestedRef);\n }\n });\n }\n\n return reachable;\n}\n\nfunction buildAnchors(\n rootPointer: string,\n extraPointers: string[],\n reachableRefs: Set<string>,\n): Pick<OperationScope, 'anchorPrefixes' | 'anchors'> {\n const anchors = [rootPointer, ...extraPointers];\n reachableRefs.forEach(ref => anchors.push(toPointer(ref)));\n\n return { anchors, anchorPrefixes: anchors.map(anchor => `${anchor}/`) };\n}\n\n/**\n * Accumulate the `#/components/securitySchemes/*` refs that a given set of security requirements\n * make use of.\n *\n */\nfunction collectSecuritySchemeRefs(security: unknown): string[] {\n if (!Array.isArray(security)) {\n return [];\n }\n\n const refs: string[] = [];\n security.forEach(requirement => {\n if (requirement && typeof requirement === 'object') {\n Object.keys(requirement).forEach(scheme => {\n refs.push(`#/components/securitySchemes/${scheme}`);\n });\n }\n });\n\n return refs;\n}\n\n/**\n * Compute the `OperationScope` for a single operation within an API definition, so that the\n * analyzer can report only on what that operation (and anything it references) actually uses,\n * without requiring the definition to be reduced down first.\n *\n */\nexport function computeOperationScope(definition: OASDocument, path: string, method: string): OperationScope {\n const pathKey = resolveKey(Object.keys(definition.paths || {}), path);\n if (!pathKey) {\n throw new Error(`Path \\`${path}\\` not found.`);\n }\n\n const pathItem = (definition.paths as Record<string, any>)[pathKey] || {};\n const methodKey = resolveKey(Object.keys(pathItem), method);\n if (!methodKey || !supportedMethods.includes(methodKey.toLowerCase() as (typeof supportedMethods)[number])) {\n throw new Error(`Operation \\`${method} ${path}\\` not found.`);\n }\n\n const operation = pathItem[methodKey];\n const rootPointer = `/paths/${encodePointer(pathKey)}/${methodKey}`;\n const extraPointers: string[] = [];\n const seeds = new Set<string>(collectRefsInSchema(operation));\n\n if (pathItem.parameters) {\n extraPointers.push(`/paths/${encodePointer(pathKey)}/parameters`);\n collectRefsInSchema(pathItem.parameters).forEach(ref => seeds.add(ref));\n }\n\n const security = 'security' in operation ? operation.security : definition.security;\n collectSecuritySchemeRefs(security).forEach(ref => seeds.add(ref));\n\n const reachableRefs = accumulateReachableRefs(definition, seeds);\n\n return { rootPointer, extraPointers, reachableRefs, ...buildAnchors(rootPointer, extraPointers, reachableRefs) };\n}\n\n/**\n * Compute the `OperationScope` for a single webhook operation within an OpenAPI 3.1 definition.\n *\n */\nexport function computeWebhookScope(definition: OAS31Document, webhookName: string, method: string): OperationScope {\n const webhooks = ('webhooks' in definition ? definition.webhooks : {}) as NonNullable<OAS31Document['webhooks']>;\n const webhookKey = resolveKey(Object.keys(webhooks || {}), webhookName);\n if (!webhookKey) {\n throw new Error(`Webhook \\`${webhookName}\\` not found.`);\n }\n\n const webhook = (webhooks as Record<string, any>)[webhookKey] || {};\n const methodKey = resolveKey(Object.keys(webhook), method);\n if (!methodKey || !supportedMethods.includes(methodKey.toLowerCase() as (typeof supportedMethods)[number])) {\n throw new Error(`Webhook operation \\`${method} ${webhookName}\\` not found.`);\n }\n\n const operation = webhook[methodKey];\n const rootPointer = `/webhooks/${encodePointer(webhookKey)}/${methodKey}`;\n const extraPointers: string[] = [];\n const seeds = new Set<string>(collectRefsInSchema(operation));\n\n if (webhook.parameters) {\n extraPointers.push(`/webhooks/${encodePointer(webhookKey)}/parameters`);\n collectRefsInSchema(webhook.parameters).forEach(ref => seeds.add(ref));\n }\n\n const security = 'security' in operation ? operation.security : definition.security;\n collectSecuritySchemeRefs(security).forEach(ref => seeds.add(ref));\n\n const reachableRefs = accumulateReachableRefs(definition, seeds);\n\n return { rootPointer, extraPointers, reachableRefs, ...buildAnchors(rootPointer, extraPointers, reachableRefs) };\n}\n\n/**\n * Determine if a given JSON pointer (as returned by `jsonpath-plus`, without a leading `#`) falls\n * within a given `OperationScope`.\n *\n */\nexport function isPointerInScope(pointer: string, scope: OperationScope): boolean {\n const { anchors, anchorPrefixes } = scope;\n for (let i = 0; i < anchors.length; i += 1) {\n if (pointer === anchors[i] || pointer.startsWith(anchorPrefixes[i])) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Determine if a given `OperationScope`'s root is nested *within* a given pointer. This is the\n * inverse of `isPointerInScope()` and exists for the handful of queries (like our `webhooks` one)\n * that report on a coarser pointer than the specific operation we're scoped to, e.g. reporting on\n * `/webhooks/newBooking` as a whole when we're scoped to `/webhooks/newBooking/post`.\n *\n */\nexport function isAncestorOfScope(pointer: string, scope: OperationScope): boolean {\n return scope.rootPointer === pointer || scope.rootPointer.startsWith(`${pointer}/`);\n}\n","import type { OASDocument } from '../../types.js';\nimport type { OperationScope } from '../scope.js';\nimport type { JSONPathResult } from '../util.js';\n\nimport { toPointer } from '../../lib/refs.js';\nimport { dereferenceOas } from '../dereference.js';\nimport { queryCached } from '../query-cache.js';\nimport { isAncestorOfScope, isPointerInScope } from '../scope.js';\nimport { refizePointer } from '../util.js';\n\n/**\n * Narrow a set of JSONPath results down to only the ones that fall within a given operation's\n * scope. When no scope is supplied every result is considered in scope, which preserves the\n * whole-document behavior these queries have always had.\n *\n */\nfunction filterByScope(results: JSONPathResult[], scope?: OperationScope): JSONPathResult[] {\n if (!scope) {\n return results;\n }\n\n return results.filter(res => isPointerInScope(res.pointer, scope));\n}\n\n/**\n * Determine if a given API definition uses the `additionalProperties` schema property.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object}\n */\nexport function additionalProperties(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..additionalProperties'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine if a given API definition utilizes `callbacks`.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#callback-object}\n */\nexport function callbacks(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(['$.components.callbacks', '$.paths.*[?(@.callbacks)].callbacks'], definition),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition has circular refs.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object}\n */\nexport async function circularRefs(definition: OASDocument, scope?: OperationScope): Promise<string[]> {\n // Dereferencing will update the passed in variable, which we don't want to do, so we\n // instantiated `Oas` with a clone.\n const { circularRefs: refs } = await dereferenceOas(structuredClone(definition));\n\n const results = scope ? refs.filter(ref => isPointerInScope(toPointer(ref), scope)) : [...refs];\n results.sort();\n return results;\n}\n\n/**\n * Determine if a given API definition utilizes common parameters.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-item-object}\n */\nexport function commonParameters(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..paths[*].parameters'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine if a given API definition utilizes discriminators.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#discriminator-object}\n */\nexport function discriminators(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..discriminator'], definition), scope).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition utilizes `links`.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#link-object}\n */\nexport function links(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$.components.links', '$.paths..responses.*.links'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine all of the available media types used within an API definition.\n *\n * @todo This query currently picks up false positives if there is an object named `content`.\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#request-body-object}\n */\nexport function mediaTypes(definition: OASDocument, scope?: OperationScope): string[] {\n const results = Array.from(\n new Set(\n filterByScope(\n queryCached(\n [\n '$..paths..content',\n '$.components.requestBodies..content',\n '$.components.responses..content',\n '$.webhooks..content',\n ],\n definition,\n ),\n scope,\n ).flatMap(res => {\n // This'll transform `results`, which looks like `[['application/json'], ['text/xml']]`\n // into `['application/json', 'text/xml']`.\n return Object.keys(res.value);\n }),\n ),\n );\n\n results.sort();\n return results;\n}\n\n/**\n * Determine if a given API definition uses parameter serialization.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object}\n */\nexport function parameterSerialization(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..parameters[*].style^'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine if a given API definition utilizes schema polymorphism and/of interitance.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object}\n */\nexport function polymorphism(definition: OASDocument, scope?: OperationScope): string[] {\n const results = Array.from(\n new Set(\n filterByScope(queryCached(['$..allOf^', '$..anyOf^', '$..oneOf^'], definition), scope).map(res =>\n refizePointer(res.pointer),\n ),\n ),\n );\n\n results.sort();\n return results;\n}\n\n/**\n * Determine if a given API definition utilizes `$ref` pointers.\n *\n */\nexport function references(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..$ref^'], definition), scope).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine every kind of security type that a given API definition has documented.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object}\n */\nexport function securityTypes(definition: OASDocument, scope?: OperationScope): string[] {\n return Array.from(\n new Set(\n filterByScope(queryCached(['$.components.securitySchemes..type'], definition), scope).map(\n res => res.value as string,\n ),\n ),\n );\n}\n\n/**\n * Determine if a given API definition utilizes server variables.\n *\n * Root-level `servers` apply to every operation unless a path or operation overrides them, so\n * these are always considered in scope regardless of which operation is being analyzed.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#server-variable-object}\n */\nexport function serverVariables(definition: OASDocument): string[] {\n return queryCached(['$.servers..variables^'], definition).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine how many operations are defined in a given API definition.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#operation-object}\n */\nexport function totalOperations(definition: OASDocument): number {\n return queryCached(['$..paths[*]'], definition).flatMap(res => Object.keys(res.value)).length;\n}\n\n/**\n * Determine if a given API definition utilizes `webhooks` support in OpenAPI 3.1.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oasWebhooks}\n */\nexport function webhooks(definition: OASDocument, scope?: OperationScope): string[] {\n const results = queryCached(['$.webhooks[*]'], definition);\n if (!scope) {\n return results.map(res => refizePointer(res.pointer));\n }\n\n // This query reports on an entire webhook (`/webhooks/newBooking`) rather than a specific\n // method on it, so a scope's root pointer (`/webhooks/newBooking/post`) is a *descendant* of a\n // matching result rather than the other way around. We still want to catch the normal case too,\n // where an operation reaches into a webhook by way of a `$ref`.\n return results\n .filter(res => isPointerInScope(res.pointer, scope) || isAncestorOfScope(res.pointer, scope))\n .map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition uses XML in a request body payload.\n *\n * @todo detect `+xml` media types\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n */\nexport function xmlRequests(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(\n [\n \"$..requestBody..['application/xml']\",\n \"$..requestBody..['application/xml-external-parsed-entity']\",\n \"$..requestBody..['application/xml-dtd']\",\n \"$..requestBody..['text/xml']\",\n \"$..requestBody..['text/xml-external-parsed-entity']\",\n '$..requestBody.content[?(@property.match(/\\\\+xml$/i))]',\n ],\n definition,\n ),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition uses XML in a response body.\n *\n * @todo detect `+xml` media types\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n */\nexport function xmlResponses(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(\n [\n \"$..responses..['application/xml']\",\n \"$..responses..['application/xml-external-parsed-entity']\",\n \"$..responses..['application/xml-dtd']\",\n \"$..responses..['text/xml']\",\n \"$..responses..['text/xml-external-parsed-entity']\",\n '$..responses[*].content[?(@property.match(/\\\\+xml$/i))]',\n ],\n definition,\n ),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition utilises the XML object for defining XML schemas.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#xml-object}\n */\nexport function xmlSchemas(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(['$.components.schemas..xml^', '$..parameters..xml^', '$..requestBody..xml^'], definition),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n","import type { OAS31Document, OASDocument } from '../types.js';\nimport type { OperationScope } from './scope.js';\nimport type { AnalyzerQuery, OASAnalysis } from './types.js';\n\nimport { toPointer } from '../lib/refs.js';\nimport { isOpenAPI31 } from '../types.js';\n\nimport { dereferenceOasShared } from './dereference.js';\nimport {\n additionalProperties as analyzeAdditionalProperties,\n callbacks as analyzeCallbacks,\n commonParameters as analyzeCommonParameters,\n discriminators as analyzeDiscriminators,\n links as analyzeLinks,\n mediaTypes as analyzeMediaTypes,\n parameterSerialization as analyzeParameterSerialization,\n polymorphism as analyzePolymorphism,\n references as analyzeReferences,\n securityTypes as analyzeSecurityTypes,\n serverVariables as analyzeServerVariables,\n totalOperations as analyzeTotalOperations,\n webhooks as analyzeWebhooks,\n xmlRequests as analyzeXMLRequests,\n xmlResponses as analyzeXMLResponses,\n xmlSchemas as analyzeXMLSchemas,\n} from './queries/openapi.js';\nimport { computeOperationScope, computeWebhookScope, isPointerInScope } from './scope.js';\n\n/**\n * Run every analyzer query against a definition, optionally narrowed down to a single operation or\n * webhook's `OperationScope`.\n *\n * This is the shared implementation behind `analyzer()`, `analyzeOperation()`, and\n * `analyzeWebhookOperation()`. Dereferencing (the most expensive step here) is cached against the\n * original `definition` reference via `dereferenceOasShared()`, and every JSONPath scan that the\n * individual queries run is cached against it as well, so calling this repeatedly for many\n * operations out of the same definition only pays the full-document cost once.\n *\n */\nasync function buildAnalysis(\n definition: OASDocument,\n query?: AnalyzerQuery[],\n scope?: OperationScope,\n): Promise<OASAnalysis> {\n const analysis: OASAnalysis = {};\n\n if (!query || query.includes('mediaTypes')) {\n analysis.mediaTypes = {\n name: 'Media Type',\n found: analyzeMediaTypes(definition, scope),\n };\n }\n\n if (!query || query.includes('operationTotal')) {\n analysis.operationTotal = {\n name: 'Operation',\n // A scoped analysis is, by definition, looking at exactly one operation.\n found: scope ? 1 : analyzeTotalOperations(definition),\n };\n }\n\n if (!query || query.includes('securityTypes')) {\n analysis.securityTypes = {\n name: 'Security Type',\n found: analyzeSecurityTypes(definition, scope),\n };\n }\n\n if (!query || query.includes('additionalProperties')) {\n const additionalProperties = analyzeAdditionalProperties(definition, scope);\n analysis.additionalProperties = {\n present: !!additionalProperties.length,\n locations: additionalProperties,\n };\n }\n\n if (!query || query.includes('callbacks')) {\n const callbacks = analyzeCallbacks(definition, scope);\n analysis.callbacks = {\n present: !!callbacks.length,\n locations: callbacks,\n };\n }\n\n if (!query || query.includes('circularRefs')) {\n const { circularRefs: dereferencedCircularRefs } = await dereferenceOasShared(definition);\n const circularRefs = (\n scope\n ? dereferencedCircularRefs.filter(ref => isPointerInScope(toPointer(ref), scope))\n : [...dereferencedCircularRefs]\n ).toSorted();\n\n analysis.circularRefs = {\n present: !!circularRefs.length,\n locations: circularRefs,\n };\n }\n\n if (!query || query.includes('commonParameters')) {\n const commonParameters = analyzeCommonParameters(definition, scope);\n analysis.commonParameters = {\n present: !!commonParameters.length,\n locations: commonParameters,\n };\n }\n\n if (!query || query.includes('discriminators')) {\n const discriminators = analyzeDiscriminators(definition, scope);\n analysis.discriminators = {\n present: !!discriminators.length,\n locations: discriminators,\n };\n }\n\n if (!query || query.includes('links')) {\n const links = analyzeLinks(definition, scope);\n analysis.links = {\n present: !!links.length,\n locations: links,\n };\n }\n\n if (!query || query.includes('style')) {\n const parameterSerialization = analyzeParameterSerialization(definition, scope);\n analysis.style = {\n present: !!parameterSerialization.length,\n locations: parameterSerialization,\n };\n }\n\n if (!query || query.includes('polymorphism')) {\n const polymorphism = analyzePolymorphism(definition, scope);\n analysis.polymorphism = {\n present: !!polymorphism.length,\n locations: polymorphism,\n };\n }\n\n if (!query || query.includes('references')) {\n const references = analyzeReferences(definition, scope);\n analysis.references = {\n present: !!references.length,\n locations: references,\n };\n }\n\n if (!query || query.includes('serverVariables')) {\n const serverVariables = analyzeServerVariables(definition);\n analysis.serverVariables = {\n present: !!serverVariables.length,\n locations: serverVariables,\n };\n }\n\n if (!query || query.includes('xmlRequests')) {\n const xmlRequests = analyzeXMLRequests(definition, scope);\n analysis.xmlRequests = {\n present: !!xmlRequests.length,\n locations: xmlRequests,\n };\n }\n\n if (!query || query.includes('xmlResponses')) {\n const xmlResponses = analyzeXMLResponses(definition, scope);\n analysis.xmlResponses = {\n present: !!xmlResponses.length,\n locations: xmlResponses,\n };\n }\n\n if (!query || query.includes('xmlSchemas')) {\n const xmlSchemas = analyzeXMLSchemas(definition, scope);\n analysis.xmlSchemas = {\n present: !!xmlSchemas.length,\n locations: xmlSchemas,\n };\n }\n\n if (!query || query.includes('xmlSchemas')) {\n const webhooks = analyzeWebhooks(definition, scope);\n analysis.webhooks = {\n present: !!webhooks.length,\n locations: webhooks,\n };\n }\n\n return analysis;\n}\n\n/**\n * Analyze a given OpenAPI or Swagger definition for any, or a specific, OpenAPI or JSON Schema\n * feature uses it may contain or utilize.\n *\n */\nexport async function analyzer(definition: OASDocument, query?: AnalyzerQuery[]): Promise<OASAnalysis> {\n return buildAnalysis(definition, query);\n}\n\n/**\n * Analyze a single operation within a given OpenAPI definition for any, or a specific, OpenAPI or\n * JSON Schema feature uses that it, or anything it references, may contain or utilize.\n *\n * When analyzing many operations out of the same definition, pass the *same* `definition`\n * reference to every call — the expensive dereferencing and document-wide JSONPath scans that this\n * relies on are cached against that reference, so only the first call pays for them.\n *\n */\nexport async function analyzeOperation(\n definition: OASDocument,\n {\n path,\n method,\n query,\n }: {\n path: string;\n method: string;\n query?: AnalyzerQuery[];\n },\n): Promise<OASAnalysis> {\n const scope = computeOperationScope(definition, path, method);\n return buildAnalysis(definition, query, scope);\n}\n\n/**\n * Analyze a single webhook operation within a given OpenAPI 3.1 definition for any, or a specific,\n * OpenAPI or JSON Schema feature uses that it, or anything it references, may contain or utilize.\n *\n * @see {@link analyzeOperation}\n */\nexport async function analyzeWebhookOperation(\n definition: OASDocument,\n {\n webhookName,\n method,\n query,\n }: {\n webhookName: string;\n method: string;\n query?: AnalyzerQuery[];\n },\n): Promise<OASAnalysis> {\n if (!isOpenAPI31(definition)) {\n throw new TypeError('The supplied definition is not a OpenAPI 3.1 definition.');\n }\n\n const scope = computeWebhookScope(definition satisfies OAS31Document, webhookName, method);\n return buildAnalysis(definition, query, scope);\n}\n"]}
1
+ {"version":3,"sources":["/Users/erunion/code/readme/oas/packages/oas/dist/analyzer/index.cjs","../../src/analyzer/dereference.ts","../../src/analyzer/query-cache.ts","../../src/analyzer/scope.ts","../../src/analyzer/queries/openapi.ts","../../src/analyzer/index.ts"],"names":["webhooks","query","additionalProperties","callbacks","commonParameters","discriminators","links","parameterSerialization","polymorphism","references","serverVariables","xmlRequests","xmlResponses","xmlSchemas"],"mappings":"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","file":"/Users/erunion/code/readme/oas/packages/oas/dist/analyzer/index.cjs","sourcesContent":[null,"import type { OASDocument } from '../types.js';\nimport type { ParserOptions } from '@readme/openapi-parser';\n\nimport { dereference } from '@readme/openapi-parser';\n\n/**\n * Retrive our dereferencing configuration for `@readme/openapi-parser`.\n *\n */\nfunction getDereferencingOptions(circularRefs: Set<string>): Pick<ParserOptions, 'resolve' | 'dereference'> {\n return {\n resolve: {\n // We shouldn't be resolving external pointers at this point so just ignore them.\n external: false,\n },\n dereference: {\n // If circular `$refs` are ignored they'll remain in the schema as `$ref: String`, otherwise\n // `$ref` just won't exist. This, in tandem with `onCircular`, allows us to do easy and\n // accumulate a list of circular references.\n circular: 'ignore',\n\n onCircular: (path: string) => {\n // The circular references that are coming out of `json-schema-ref-parser` are prefixed\n // with the schema path (file path, URL, whatever) that the schema exists in. Because we\n // don't care about this information for this reporting mechanism, and only the `$ref`\n // pointer, we're removing it.\n circularRefs.add(`#${path.split('#')[1]}`);\n },\n },\n };\n}\n\ninterface DereferenceOasResult {\n api: OASDocument;\n circularRefs: string[];\n}\n\ninterface DereferenceOasState {\n circularRefs: string[];\n complete: boolean;\n processing: boolean;\n promises: {\n reject: (err: unknown) => void;\n resolve: (result: DereferenceOasResult) => void;\n }[];\n result?: DereferenceOasResult;\n}\n\nconst dereferenceStates = new WeakMap<OASDocument, DereferenceOasState>();\n\nfunction getDereferenceState(definition: OASDocument): DereferenceOasState {\n let state = dereferenceStates.get(definition);\n if (!state) {\n state = {\n processing: false,\n complete: false,\n circularRefs: [],\n promises: [],\n };\n dereferenceStates.set(definition, state);\n }\n\n return state;\n}\n\n/**\n * Dereference a given OpenAPI definition so it can be parsed free from the hassle of resolving\n * `$ref` schemas and circular structures.\n *\n */\nexport async function dereferenceOas(\n definition: OASDocument,\n opts?: {\n /**\n * A callback method can be supplied to be called when dereferencing is complete. Used for\n * debugging that the multi-promise handling within this method works.\n *\n * @private\n */\n cb?: () => void;\n },\n): Promise<DereferenceOasResult> {\n const state = getDereferenceState(definition);\n\n if (state.complete && state.result) {\n return state.result;\n }\n\n if (state.processing) {\n return new Promise((resolve, reject) => {\n state.promises.push({ resolve, reject });\n });\n }\n\n state.processing = true;\n\n const circularRefs: Set<string> = new Set();\n const dereferencingOptions = getDereferencingOptions(circularRefs);\n const { promises } = state;\n\n return dereference<OASDocument>(definition, dereferencingOptions)\n .then((dereferenced: OASDocument) => {\n const result: DereferenceOasResult = {\n api: dereferenced,\n circularRefs: [...circularRefs],\n };\n\n state.result = result;\n state.circularRefs = result.circularRefs;\n state.processing = false;\n state.complete = true;\n\n // Used for debugging that dereferencing promise awaiting works.\n if (opts?.cb) {\n opts.cb();\n }\n\n return result;\n })\n .then(result => {\n promises.forEach(deferred => deferred.resolve(result));\n state.promises = [];\n return result;\n })\n .catch(err => {\n state.processing = false;\n promises.forEach(deferred => deferred.reject(err));\n state.promises = [];\n throw err;\n });\n}\n\n/**\n * `dereferenceOas()` caches its result against whatever object reference it's given, but callers\n * that care about not mutating their original definition (like the analyzer) have historically had\n * to pass in a fresh `structuredClone()` on every call — which means that cache is only ever a hit\n * within a single call, never across separate analyses of the same definition.\n *\n * This wraps that up: it clones the given definition exactly once and reuses that same clone (and\n * therefore `dereferenceOas()`'s cached result for it) for every subsequent call with the same\n * original definition reference. This is what lets the analyzer dereference a large API definition\n * a single time and reuse that work across an analysis of every operation within it, instead of\n * re-dereferencing the whole thing per-operation.\n *\n * @param definition An OpenAPI definition to dereference.\n */\nexport function dereferenceOasShared(definition: OASDocument): Promise<DereferenceOasResult> {\n let clone = sharedClones.get(definition);\n if (!clone) {\n clone = structuredClone(definition);\n sharedClones.set(definition, clone);\n }\n\n return dereferenceOas(clone);\n}\n\nconst sharedClones = new WeakMap<OASDocument, OASDocument>();\n","import type { JSONPathResult } from './util.js';\n\nimport { query } from './util.js';\n\nconst cache = new WeakMap<object, Map<string, JSONPathResult[]>>();\n\n/**\n * Every analyzer query in `queries/openapi.ts` runs one or more `$..`-style JSONPath scans across\n * the *entire* API definition, regardless of which operation (if any) we're ultimately interested\n * in. When analyzing a single operation that's cheap to do once, but when analyzing hundreds of\n * operations out of the same definition — which is the whole point of being able to scope analysis\n * to an operation instead of reducing the definition down first — re-running those same full-document\n * scans for every single operation throws away all of that shared work.\n *\n * This cache runs each unique set of JSONPath queries against a given definition exactly once and\n * hands back the same result array on every subsequent call, so callers can cheaply filter it down\n * per-operation instead of re-scanning the whole document each time.\n *\n * Cache entries are keyed by object identity (via `WeakMap`), so this is only effective when\n * callers reuse the same `definition` reference across calls — if you're handed a fresh clone every\n * time there's nothing to reuse. It also means definitions are never manually evicted from the\n * cache; they simply fall out of it once nothing else references them.\n *\n * @param queries JSONPath queries to run.\n * @param definition The object to run them against.\n */\nexport function queryCached(queries: string[], definition: object): JSONPathResult[] {\n let byQuery = cache.get(definition);\n if (!byQuery) {\n byQuery = new Map();\n cache.set(definition, byQuery);\n }\n\n // A space can't appear within a JSONPath query, so it's a safe delimiter to join on for a cache key.\n const cacheKey = queries.join(' ');\n\n let results = byQuery.get(cacheKey);\n if (!results) {\n results = query(queries, definition);\n byQuery.set(cacheKey, results);\n }\n\n return results;\n}\n","import type { OAS31Document, OASDocument, PathItemObject } from '../types.js';\n\nimport jsonPointer from 'jsonpointer';\n\nimport { collectRefsInSchema, dereferenceRef, encodePointer, toPointer } from '../lib/refs.js';\nimport { isRef } from '../types.js';\nimport { supportedMethods } from '../utils.js';\n\n/**\n * Convert a `$ref` into the same JSON pointer shape `jsonpath-plus` emits. Local refs may be\n * percent-encoded (`#/components/pathItems/pet%20ById`); `findRef` decodes that before lookup, so\n * query results live under the decoded key, not the authored encoding.\n */\nfunction canonicalizeRefPointer($ref: string): string {\n const pointer = toPointer($ref);\n try {\n return decodeURIComponent(pointer);\n } catch {\n return pointer;\n }\n}\n\n/**\n * Follow a Path Item `$ref` chain to the last local pointer that actually holds the Path Item.\n * Intermediate aliases and percent-encoded fragments must not become scope anchors — analysis\n * queries report locations under the terminal, decoded target.\n */\nfunction resolvePathItemTargetPointer(raw: unknown, definition: OASDocument): string | undefined {\n if (!isRef(raw)) {\n return undefined;\n }\n\n let current: unknown = raw;\n let lastRef = raw.$ref;\n const seen = new Set<string>();\n\n while (isRef(current)) {\n const ref = current.$ref;\n if (seen.has(ref)) {\n break;\n }\n seen.add(ref);\n lastRef = ref;\n\n let next: unknown;\n try {\n next = jsonPointer.get(definition, canonicalizeRefPointer(ref));\n } catch {\n break;\n }\n\n if (next === undefined || !isRef(next)) {\n break;\n }\n\n current = next;\n }\n\n return canonicalizeRefPointer(lastRef);\n}\n\n/**\n * The set of JSON pointers (in plain `/foo/bar` form, without the leading `#`) that describe\n * everything an operation or webhook touches: itself, any path-level (or webhook-level) common\n * parameters, and every `$ref` pointer that's reachable from either of those.\n *\n * This is what allows the analyzer to be run against a full, undreduced API definition and still\n * only report on what a single operation actually uses.\n */\nexport interface OperationScope {\n /**\n * `anchors`, each with a trailing `/` appended, precomputed once so `isPointerInScope()` isn't\n * concatenating a new string per anchor on every single pointer it checks. Analyzing an\n * operation can mean checking thousands of result pointers against this list, so avoiding a\n * string allocation per comparison meaningfully adds up.\n */\n anchorPrefixes: string[];\n\n /**\n * A flattened, precomputed list of every pointer prefix that's considered \"in scope\". This is\n * derived from `rootPointer`, `extraPointers`, and `reachableRefs` and exists purely so we don't\n * have to rebuild it for every pointer comparison we do.\n */\n anchors: string[];\n\n /**\n * Pointers, beyond `rootPointer`, that should be treated as belonging to this operation (e.g. a\n * path item's common `parameters`).\n */\n extraPointers: string[];\n\n /**\n * Every `$ref` pointer (in `#/...` form) that's transitively reachable from this operation.\n */\n reachableRefs: Set<string>;\n\n /**\n * The pointer to the operation (or webhook operation) itself, e.g. `/paths/~1pet/get` or\n * `/webhooks/newBooking/post`.\n */\n rootPointer: string;\n}\n\n/**\n * Case-insensitively find the real key for a given target within a list of keys. OpenAPI paths and\n * HTTP methods aren't case-sensitive as far as most tooling (including this) is concerned, but the\n * pointers we build need to match the casing that's actually in the document.\n *\n */\nfunction resolveKey(keys: string[], target: string): string | undefined {\n return keys.find(key => key === target) || keys.find(key => key.toLowerCase() === target.toLowerCase());\n}\n\n/**\n * Resolve a Path Item that may be a `$ref` (including OpenAPI 3.1 `components.pathItems`) without\n * mutating the authored definition. Returns `undefined` when the pointer cannot be followed.\n */\nfunction resolvePathItem(raw: unknown, definition: OASDocument): PathItemObject | undefined {\n const resolved = dereferenceRef(raw, definition);\n if (!resolved || typeof resolved !== 'object' || isRef(resolved)) {\n return undefined;\n }\n\n return resolved as PathItemObject;\n}\n\nfunction resolveMethodKey(container: object, method: string): string | undefined {\n const methodKey = resolveKey(Object.keys(container), method);\n if (!methodKey || !supportedMethods.includes(methodKey.toLowerCase() as (typeof supportedMethods)[number])) {\n return undefined;\n }\n\n return methodKey;\n}\n\n/**\n * Starting from a seed set of `$ref` pointers, recursively follow every `$ref` that they, or\n * anything they point to, contain and return the full set of pointers that are reachable.\n *\n * This intentionally mirrors the ref-walking that `OpenAPIReducer` does, but is read-only (no\n * cloning, no mutation) and resolves each `$ref` lazily against the original definition instead of\n * requiring a prebuilt map of every possible reference.\n *\n */\nfunction accumulateReachableRefs(definition: OASDocument, seeds: Iterable<string>): Set<string> {\n const reachable = new Set<string>();\n const queue = [...seeds];\n\n while (queue.length) {\n const ref = queue.shift() as string;\n if (reachable.has(ref)) {\n continue;\n }\n\n reachable.add(ref);\n\n let resolved: unknown;\n try {\n resolved = jsonPointer.get(definition, toPointer(ref));\n } catch {\n // If the `$ref` doesn't resolve to anything (a malformed pointer, an external ref we didn't\n // bundle, etc.) there's nothing further to walk from it.\n continue;\n }\n\n if (resolved === undefined) {\n continue;\n }\n\n collectRefsInSchema(resolved).forEach(nestedRef => {\n if (!reachable.has(nestedRef)) {\n queue.push(nestedRef);\n }\n });\n }\n\n return reachable;\n}\n\nfunction buildAnchors(\n rootPointer: string,\n extraPointers: string[],\n reachableRefs: Set<string>,\n): Pick<OperationScope, 'anchorPrefixes' | 'anchors'> {\n const anchors = [rootPointer, ...extraPointers];\n reachableRefs.forEach(ref => anchors.push(toPointer(ref)));\n\n return { anchors, anchorPrefixes: anchors.map(anchor => `${anchor}/`) };\n}\n\n/**\n * Accumulate the `#/components/securitySchemes/*` refs that a given set of security requirements\n * make use of.\n *\n */\nfunction collectSecuritySchemeRefs(security: unknown): string[] {\n if (!Array.isArray(security)) {\n return [];\n }\n\n const refs: string[] = [];\n security.forEach(requirement => {\n if (requirement && typeof requirement === 'object') {\n Object.keys(requirement).forEach(scheme => {\n refs.push(`#/components/securitySchemes/${scheme}`);\n });\n }\n });\n\n return refs;\n}\n\n/**\n * Compute the `OperationScope` for a single operation within an API definition, so that the\n * analyzer can report only on what that operation (and anything it references) actually uses,\n * without requiring the definition to be reduced down first.\n *\n */\nexport function computeOperationScope(definition: OASDocument, path: string, method: string): OperationScope {\n const pathKey = resolveKey(Object.keys(definition.paths || {}), path);\n if (!pathKey) {\n throw new Error(`Path \\`${path}\\` not found.`);\n }\n\n const rawPathItem = (definition.paths as Record<string, unknown>)[pathKey];\n const pathItem = resolvePathItem(rawPathItem, definition);\n if (!pathItem) {\n throw new Error(`Operation \\`${method} ${path}\\` not found.`);\n }\n\n const methodKey = resolveMethodKey(pathItem, method);\n if (!methodKey) {\n throw new Error(`Operation \\`${method} ${path}\\` not found.`);\n }\n\n const operation = pathItem[methodKey as keyof PathItemObject];\n if (!operation || typeof operation !== 'object') {\n throw new Error(`Operation \\`${method} ${path}\\` not found.`);\n }\n\n const rootPointer = `/paths/${encodePointer(pathKey)}/${methodKey}`;\n const extraPointers: string[] = [];\n const seeds = new Set<string>(collectRefsInSchema(operation));\n\n // Features on a referenced Path Item live at the *terminal* `$ref` target, not under\n // `/paths/{path}/{method}` and not under an intermediate alias or percent-encoded pointer.\n // Scope only this method (and its common parameters) so sibling operations stay out.\n const targetPointer = resolvePathItemTargetPointer(rawPathItem, definition);\n if (targetPointer) {\n extraPointers.push(`${targetPointer}/${methodKey}`);\n }\n\n if (pathItem.parameters) {\n extraPointers.push(targetPointer ? `${targetPointer}/parameters` : `/paths/${encodePointer(pathKey)}/parameters`);\n collectRefsInSchema(pathItem.parameters).forEach(ref => seeds.add(ref));\n }\n\n const security = 'security' in operation ? operation.security : definition.security;\n collectSecuritySchemeRefs(security).forEach(ref => seeds.add(ref));\n\n const reachableRefs = accumulateReachableRefs(definition, seeds);\n\n return { rootPointer, extraPointers, reachableRefs, ...buildAnchors(rootPointer, extraPointers, reachableRefs) };\n}\n\n/**\n * Compute the `OperationScope` for a single webhook operation within an OpenAPI 3.1 definition.\n *\n */\nexport function computeWebhookScope(definition: OAS31Document, webhookName: string, method: string): OperationScope {\n const webhooks = ('webhooks' in definition ? definition.webhooks : {}) as NonNullable<OAS31Document['webhooks']>;\n const webhookKey = resolveKey(Object.keys(webhooks || {}), webhookName);\n if (!webhookKey) {\n throw new Error(`Webhook \\`${webhookName}\\` not found.`);\n }\n\n const rawWebhook = (webhooks as Record<string, unknown>)[webhookKey];\n const webhook = resolvePathItem(rawWebhook, definition);\n if (!webhook) {\n throw new Error(`Webhook operation \\`${method} ${webhookName}\\` not found.`);\n }\n\n const methodKey = resolveMethodKey(webhook, method);\n if (!methodKey) {\n throw new Error(`Webhook operation \\`${method} ${webhookName}\\` not found.`);\n }\n\n const operation = webhook[methodKey as keyof PathItemObject];\n if (!operation || typeof operation !== 'object') {\n throw new Error(`Webhook operation \\`${method} ${webhookName}\\` not found.`);\n }\n\n const rootPointer = `/webhooks/${encodePointer(webhookKey)}/${methodKey}`;\n const extraPointers: string[] = [];\n const seeds = new Set<string>(collectRefsInSchema(operation));\n\n const targetPointer = resolvePathItemTargetPointer(rawWebhook, definition);\n if (targetPointer) {\n extraPointers.push(`${targetPointer}/${methodKey}`);\n }\n\n if (webhook.parameters) {\n extraPointers.push(\n targetPointer ? `${targetPointer}/parameters` : `/webhooks/${encodePointer(webhookKey)}/parameters`,\n );\n collectRefsInSchema(webhook.parameters).forEach(ref => seeds.add(ref));\n }\n\n const security = 'security' in operation ? operation.security : definition.security;\n collectSecuritySchemeRefs(security).forEach(ref => seeds.add(ref));\n\n const reachableRefs = accumulateReachableRefs(definition, seeds);\n\n return { rootPointer, extraPointers, reachableRefs, ...buildAnchors(rootPointer, extraPointers, reachableRefs) };\n}\n\n/**\n * Determine if a given JSON pointer (as returned by `jsonpath-plus`, without a leading `#`) falls\n * within a given `OperationScope`.\n *\n */\nexport function isPointerInScope(pointer: string, scope: OperationScope): boolean {\n const { anchors, anchorPrefixes } = scope;\n for (let i = 0; i < anchors.length; i += 1) {\n if (pointer === anchors[i] || pointer.startsWith(anchorPrefixes[i])) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Determine if a given `OperationScope`'s root is nested *within* a given pointer. This is the\n * inverse of `isPointerInScope()` and exists for the handful of queries (like our `webhooks` one)\n * that report on a coarser pointer than the specific operation we're scoped to, e.g. reporting on\n * `/webhooks/newBooking` as a whole when we're scoped to `/webhooks/newBooking/post`.\n *\n */\nexport function isAncestorOfScope(pointer: string, scope: OperationScope): boolean {\n return scope.rootPointer === pointer || scope.rootPointer.startsWith(`${pointer}/`);\n}\n","import type { OASDocument } from '../../types.js';\nimport type { OperationScope } from '../scope.js';\nimport type { JSONPathResult } from '../util.js';\n\nimport { toPointer } from '../../lib/refs.js';\nimport { dereferenceOas } from '../dereference.js';\nimport { queryCached } from '../query-cache.js';\nimport { isAncestorOfScope, isPointerInScope } from '../scope.js';\nimport { refizePointer } from '../util.js';\n\n/**\n * Narrow a set of JSONPath results down to only the ones that fall within a given operation's\n * scope. When no scope is supplied every result is considered in scope, which preserves the\n * whole-document behavior these queries have always had.\n *\n */\nfunction filterByScope(results: JSONPathResult[], scope?: OperationScope): JSONPathResult[] {\n if (!scope) {\n return results;\n }\n\n return results.filter(res => isPointerInScope(res.pointer, scope));\n}\n\n/**\n * Determine if a given API definition uses the `additionalProperties` schema property.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object}\n */\nexport function additionalProperties(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..additionalProperties'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine if a given API definition utilizes `callbacks`.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#callback-object}\n */\nexport function callbacks(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(['$.components.callbacks', '$.paths.*[?(@.callbacks)].callbacks'], definition),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition has circular refs.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object}\n */\nexport async function circularRefs(definition: OASDocument, scope?: OperationScope): Promise<string[]> {\n // Dereferencing will update the passed in variable, which we don't want to do, so we\n // instantiated `Oas` with a clone.\n const { circularRefs: refs } = await dereferenceOas(structuredClone(definition));\n\n const results = scope ? refs.filter(ref => isPointerInScope(toPointer(ref), scope)) : [...refs];\n results.sort();\n return results;\n}\n\n/**\n * Determine if a given API definition utilizes common parameters.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-item-object}\n */\nexport function commonParameters(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..paths[*].parameters'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine if a given API definition utilizes discriminators.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#discriminator-object}\n */\nexport function discriminators(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..discriminator'], definition), scope).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition utilizes `links`.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#link-object}\n */\nexport function links(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$.components.links', '$.paths..responses.*.links'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine all of the available media types used within an API definition.\n *\n * @todo This query currently picks up false positives if there is an object named `content`.\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#request-body-object}\n */\nexport function mediaTypes(definition: OASDocument, scope?: OperationScope): string[] {\n const results = Array.from(\n new Set(\n filterByScope(\n queryCached(\n [\n '$..paths..content',\n '$.components.requestBodies..content',\n '$.components.responses..content',\n '$.webhooks..content',\n ],\n definition,\n ),\n scope,\n ).flatMap(res => {\n // This'll transform `results`, which looks like `[['application/json'], ['text/xml']]`\n // into `['application/json', 'text/xml']`.\n return Object.keys(res.value);\n }),\n ),\n );\n\n results.sort();\n return results;\n}\n\n/**\n * Determine if a given API definition uses parameter serialization.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object}\n */\nexport function parameterSerialization(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..parameters[*].style^'], definition), scope).map(res =>\n refizePointer(res.pointer),\n );\n}\n\n/**\n * Determine if a given API definition utilizes schema polymorphism and/of interitance.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object}\n */\nexport function polymorphism(definition: OASDocument, scope?: OperationScope): string[] {\n const results = Array.from(\n new Set(\n filterByScope(queryCached(['$..allOf^', '$..anyOf^', '$..oneOf^'], definition), scope).map(res =>\n refizePointer(res.pointer),\n ),\n ),\n );\n\n results.sort();\n return results;\n}\n\n/**\n * Determine if a given API definition utilizes `$ref` pointers.\n *\n */\nexport function references(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(queryCached(['$..$ref^'], definition), scope).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine every kind of security type that a given API definition has documented.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object}\n */\nexport function securityTypes(definition: OASDocument, scope?: OperationScope): string[] {\n return Array.from(\n new Set(\n filterByScope(queryCached(['$.components.securitySchemes..type'], definition), scope).map(\n res => res.value as string,\n ),\n ),\n );\n}\n\n/**\n * Determine if a given API definition utilizes server variables.\n *\n * Root-level `servers` apply to every operation unless a path or operation overrides them, so\n * these are always considered in scope regardless of which operation is being analyzed.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#server-variable-object}\n */\nexport function serverVariables(definition: OASDocument): string[] {\n return queryCached(['$.servers..variables^'], definition).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine how many operations are defined in a given API definition.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#operation-object}\n */\nexport function totalOperations(definition: OASDocument): number {\n return queryCached(['$..paths[*]'], definition).flatMap(res => Object.keys(res.value)).length;\n}\n\n/**\n * Determine if a given API definition utilizes `webhooks` support in OpenAPI 3.1.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oasWebhooks}\n */\nexport function webhooks(definition: OASDocument, scope?: OperationScope): string[] {\n const results = queryCached(['$.webhooks[*]'], definition);\n if (!scope) {\n return results.map(res => refizePointer(res.pointer));\n }\n\n // This query reports on an entire webhook (`/webhooks/newBooking`) rather than a specific\n // method on it, so a scope's root pointer (`/webhooks/newBooking/post`) is a *descendant* of a\n // matching result rather than the other way around. We still want to catch the normal case too,\n // where an operation reaches into a webhook by way of a `$ref`.\n return results\n .filter(res => isPointerInScope(res.pointer, scope) || isAncestorOfScope(res.pointer, scope))\n .map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition uses XML in a request body payload.\n *\n * @todo detect `+xml` media types\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n */\nexport function xmlRequests(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(\n [\n \"$..requestBody..['application/xml']\",\n \"$..requestBody..['application/xml-external-parsed-entity']\",\n \"$..requestBody..['application/xml-dtd']\",\n \"$..requestBody..['text/xml']\",\n \"$..requestBody..['text/xml-external-parsed-entity']\",\n '$..requestBody.content[?(@property.match(/\\\\+xml$/i))]',\n ],\n definition,\n ),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition uses XML in a response body.\n *\n * @todo detect `+xml` media types\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object}\n */\nexport function xmlResponses(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(\n [\n \"$..responses..['application/xml']\",\n \"$..responses..['application/xml-external-parsed-entity']\",\n \"$..responses..['application/xml-dtd']\",\n \"$..responses..['text/xml']\",\n \"$..responses..['text/xml-external-parsed-entity']\",\n '$..responses[*].content[?(@property.match(/\\\\+xml$/i))]',\n ],\n definition,\n ),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n\n/**\n * Determine if a given API definition utilises the XML object for defining XML schemas.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#xml-object}\n */\nexport function xmlSchemas(definition: OASDocument, scope?: OperationScope): string[] {\n return filterByScope(\n queryCached(['$.components.schemas..xml^', '$..parameters..xml^', '$..requestBody..xml^'], definition),\n scope,\n ).map(res => refizePointer(res.pointer));\n}\n","import type { OAS31Document, OASDocument } from '../types.js';\nimport type { OperationScope } from './scope.js';\nimport type { AnalyzerQuery, OASAnalysis } from './types.js';\n\nimport { toPointer } from '../lib/refs.js';\nimport { isOpenAPI31 } from '../types.js';\n\nimport { dereferenceOasShared } from './dereference.js';\nimport {\n additionalProperties as analyzeAdditionalProperties,\n callbacks as analyzeCallbacks,\n commonParameters as analyzeCommonParameters,\n discriminators as analyzeDiscriminators,\n links as analyzeLinks,\n mediaTypes as analyzeMediaTypes,\n parameterSerialization as analyzeParameterSerialization,\n polymorphism as analyzePolymorphism,\n references as analyzeReferences,\n securityTypes as analyzeSecurityTypes,\n serverVariables as analyzeServerVariables,\n totalOperations as analyzeTotalOperations,\n webhooks as analyzeWebhooks,\n xmlRequests as analyzeXMLRequests,\n xmlResponses as analyzeXMLResponses,\n xmlSchemas as analyzeXMLSchemas,\n} from './queries/openapi.js';\nimport { computeOperationScope, computeWebhookScope, isPointerInScope } from './scope.js';\n\n/**\n * Run every analyzer query against a definition, optionally narrowed down to a single operation or\n * webhook's `OperationScope`.\n *\n * This is the shared implementation behind `analyzer()`, `analyzeOperation()`, and\n * `analyzeWebhookOperation()`. Dereferencing (the most expensive step here) is cached against the\n * original `definition` reference via `dereferenceOasShared()`, and every JSONPath scan that the\n * individual queries run is cached against it as well, so calling this repeatedly for many\n * operations out of the same definition only pays the full-document cost once.\n *\n */\nasync function buildAnalysis(\n definition: OASDocument,\n query?: AnalyzerQuery[],\n scope?: OperationScope,\n): Promise<OASAnalysis> {\n const analysis: OASAnalysis = {};\n\n if (!query || query.includes('mediaTypes')) {\n analysis.mediaTypes = {\n name: 'Media Type',\n found: analyzeMediaTypes(definition, scope),\n };\n }\n\n if (!query || query.includes('operationTotal')) {\n analysis.operationTotal = {\n name: 'Operation',\n // A scoped analysis is, by definition, looking at exactly one operation.\n found: scope ? 1 : analyzeTotalOperations(definition),\n };\n }\n\n if (!query || query.includes('securityTypes')) {\n analysis.securityTypes = {\n name: 'Security Type',\n found: analyzeSecurityTypes(definition, scope),\n };\n }\n\n if (!query || query.includes('additionalProperties')) {\n const additionalProperties = analyzeAdditionalProperties(definition, scope);\n analysis.additionalProperties = {\n present: !!additionalProperties.length,\n locations: additionalProperties,\n };\n }\n\n if (!query || query.includes('callbacks')) {\n const callbacks = analyzeCallbacks(definition, scope);\n analysis.callbacks = {\n present: !!callbacks.length,\n locations: callbacks,\n };\n }\n\n if (!query || query.includes('circularRefs')) {\n const { circularRefs: dereferencedCircularRefs } = await dereferenceOasShared(definition);\n const circularRefs = (\n scope\n ? dereferencedCircularRefs.filter(ref => isPointerInScope(toPointer(ref), scope))\n : [...dereferencedCircularRefs]\n ).toSorted();\n\n analysis.circularRefs = {\n present: !!circularRefs.length,\n locations: circularRefs,\n };\n }\n\n if (!query || query.includes('commonParameters')) {\n const commonParameters = analyzeCommonParameters(definition, scope);\n analysis.commonParameters = {\n present: !!commonParameters.length,\n locations: commonParameters,\n };\n }\n\n if (!query || query.includes('discriminators')) {\n const discriminators = analyzeDiscriminators(definition, scope);\n analysis.discriminators = {\n present: !!discriminators.length,\n locations: discriminators,\n };\n }\n\n if (!query || query.includes('links')) {\n const links = analyzeLinks(definition, scope);\n analysis.links = {\n present: !!links.length,\n locations: links,\n };\n }\n\n if (!query || query.includes('style')) {\n const parameterSerialization = analyzeParameterSerialization(definition, scope);\n analysis.style = {\n present: !!parameterSerialization.length,\n locations: parameterSerialization,\n };\n }\n\n if (!query || query.includes('polymorphism')) {\n const polymorphism = analyzePolymorphism(definition, scope);\n analysis.polymorphism = {\n present: !!polymorphism.length,\n locations: polymorphism,\n };\n }\n\n if (!query || query.includes('references')) {\n const references = analyzeReferences(definition, scope);\n analysis.references = {\n present: !!references.length,\n locations: references,\n };\n }\n\n if (!query || query.includes('serverVariables')) {\n const serverVariables = analyzeServerVariables(definition);\n analysis.serverVariables = {\n present: !!serverVariables.length,\n locations: serverVariables,\n };\n }\n\n if (!query || query.includes('xmlRequests')) {\n const xmlRequests = analyzeXMLRequests(definition, scope);\n analysis.xmlRequests = {\n present: !!xmlRequests.length,\n locations: xmlRequests,\n };\n }\n\n if (!query || query.includes('xmlResponses')) {\n const xmlResponses = analyzeXMLResponses(definition, scope);\n analysis.xmlResponses = {\n present: !!xmlResponses.length,\n locations: xmlResponses,\n };\n }\n\n if (!query || query.includes('xmlSchemas')) {\n const xmlSchemas = analyzeXMLSchemas(definition, scope);\n analysis.xmlSchemas = {\n present: !!xmlSchemas.length,\n locations: xmlSchemas,\n };\n }\n\n if (!query || query.includes('webhooks')) {\n const webhooks = analyzeWebhooks(definition, scope);\n analysis.webhooks = {\n present: !!webhooks.length,\n locations: webhooks,\n };\n }\n\n return analysis;\n}\n\n/**\n * Analyze a given OpenAPI or Swagger definition for any, or a specific, OpenAPI or JSON Schema\n * feature uses it may contain or utilize.\n *\n */\nexport async function analyzer(definition: OASDocument, query?: AnalyzerQuery[]): Promise<OASAnalysis> {\n return buildAnalysis(definition, query);\n}\n\n/**\n * Analyze a single operation within a given OpenAPI definition for any, or a specific, OpenAPI or\n * JSON Schema feature uses that it, or anything it references, may contain or utilize.\n *\n * When analyzing many operations out of the same definition, pass the *same* `definition`\n * reference to every call — the expensive dereferencing and document-wide JSONPath scans that this\n * relies on are cached against that reference, so only the first call pays for them.\n *\n */\nexport async function analyzeOperation(\n definition: OASDocument,\n {\n path,\n method,\n query,\n }: {\n path: string;\n method: string;\n query?: AnalyzerQuery[];\n },\n): Promise<OASAnalysis> {\n const scope = computeOperationScope(definition, path, method);\n return buildAnalysis(definition, query, scope);\n}\n\n/**\n * Analyze a single webhook operation within a given OpenAPI 3.1 definition for any, or a specific,\n * OpenAPI or JSON Schema feature uses that it, or anything it references, may contain or utilize.\n *\n * @see {@link analyzeOperation}\n */\nexport async function analyzeWebhookOperation(\n definition: OASDocument,\n {\n webhookName,\n method,\n query,\n }: {\n webhookName: string;\n method: string;\n query?: AnalyzerQuery[];\n },\n): Promise<OASAnalysis> {\n if (!isOpenAPI31(definition)) {\n throw new TypeError('The supplied definition is not a OpenAPI 3.1 definition.');\n }\n\n const scope = computeWebhookScope(definition satisfies OAS31Document, webhookName, method);\n return buildAnalysis(definition, query, scope);\n}\n"]}
@@ -4,13 +4,15 @@ import {
4
4
  } from "../chunk-QTPMJRIE.js";
5
5
  import {
6
6
  collectRefsInSchema,
7
+ dereferenceRef,
7
8
  encodePointer,
8
9
  supportedMethods,
9
10
  toPointer
10
11
  } from "../chunk-6D7GWVEH.js";
11
12
  import "../chunk-DYHV2B4Z.js";
12
13
  import {
13
- isOpenAPI31
14
+ isOpenAPI31,
15
+ isRef
14
16
  } from "../chunk-XG4HGNCN.js";
15
17
 
16
18
  // src/analyzer/dereference.ts
@@ -113,9 +115,58 @@ function queryCached(queries, definition) {
113
115
 
114
116
  // src/analyzer/scope.ts
115
117
  import jsonPointer from "jsonpointer";
118
+ function canonicalizeRefPointer($ref) {
119
+ const pointer = toPointer($ref);
120
+ try {
121
+ return decodeURIComponent(pointer);
122
+ } catch {
123
+ return pointer;
124
+ }
125
+ }
126
+ function resolvePathItemTargetPointer(raw, definition) {
127
+ if (!isRef(raw)) {
128
+ return void 0;
129
+ }
130
+ let current = raw;
131
+ let lastRef = raw.$ref;
132
+ const seen = /* @__PURE__ */ new Set();
133
+ while (isRef(current)) {
134
+ const ref = current.$ref;
135
+ if (seen.has(ref)) {
136
+ break;
137
+ }
138
+ seen.add(ref);
139
+ lastRef = ref;
140
+ let next;
141
+ try {
142
+ next = jsonPointer.get(definition, canonicalizeRefPointer(ref));
143
+ } catch {
144
+ break;
145
+ }
146
+ if (next === void 0 || !isRef(next)) {
147
+ break;
148
+ }
149
+ current = next;
150
+ }
151
+ return canonicalizeRefPointer(lastRef);
152
+ }
116
153
  function resolveKey(keys, target) {
117
154
  return keys.find((key) => key === target) || keys.find((key) => key.toLowerCase() === target.toLowerCase());
118
155
  }
156
+ function resolvePathItem(raw, definition) {
157
+ const resolved = dereferenceRef(raw, definition);
158
+ if (!resolved || typeof resolved !== "object" || isRef(resolved)) {
159
+ return void 0;
160
+ }
161
+ return resolved;
162
+ }
163
+ function resolveMethodKey(container, method) {
164
+ const methodKey = resolveKey(Object.keys(container), method);
165
+ if (!methodKey || !supportedMethods.includes(methodKey.toLowerCase())) {
166
+ return void 0;
167
+ }
168
+ return methodKey;
169
+ }
119
170
  function accumulateReachableRefs(definition, seeds) {
120
171
  const reachable = /* @__PURE__ */ new Set();
121
172
  const queue = [...seeds];
@@ -166,17 +217,28 @@ function computeOperationScope(definition, path, method) {
166
217
  if (!pathKey) {
167
218
  throw new Error(`Path \`${path}\` not found.`);
168
219
  }
169
- const pathItem = definition.paths[pathKey] || {};
170
- const methodKey = resolveKey(Object.keys(pathItem), method);
171
- if (!methodKey || !supportedMethods.includes(methodKey.toLowerCase())) {
220
+ const rawPathItem = definition.paths[pathKey];
221
+ const pathItem = resolvePathItem(rawPathItem, definition);
222
+ if (!pathItem) {
223
+ throw new Error(`Operation \`${method} ${path}\` not found.`);
224
+ }
225
+ const methodKey = resolveMethodKey(pathItem, method);
226
+ if (!methodKey) {
172
227
  throw new Error(`Operation \`${method} ${path}\` not found.`);
173
228
  }
174
229
  const operation = pathItem[methodKey];
230
+ if (!operation || typeof operation !== "object") {
231
+ throw new Error(`Operation \`${method} ${path}\` not found.`);
232
+ }
175
233
  const rootPointer = `/paths/${encodePointer(pathKey)}/${methodKey}`;
176
234
  const extraPointers = [];
177
235
  const seeds = new Set(collectRefsInSchema(operation));
236
+ const targetPointer = resolvePathItemTargetPointer(rawPathItem, definition);
237
+ if (targetPointer) {
238
+ extraPointers.push(`${targetPointer}/${methodKey}`);
239
+ }
178
240
  if (pathItem.parameters) {
179
- extraPointers.push(`/paths/${encodePointer(pathKey)}/parameters`);
241
+ extraPointers.push(targetPointer ? `${targetPointer}/parameters` : `/paths/${encodePointer(pathKey)}/parameters`);
180
242
  collectRefsInSchema(pathItem.parameters).forEach((ref) => seeds.add(ref));
181
243
  }
182
244
  const security = "security" in operation ? operation.security : definition.security;
@@ -190,17 +252,30 @@ function computeWebhookScope(definition, webhookName, method) {
190
252
  if (!webhookKey) {
191
253
  throw new Error(`Webhook \`${webhookName}\` not found.`);
192
254
  }
193
- const webhook = webhooks2[webhookKey] || {};
194
- const methodKey = resolveKey(Object.keys(webhook), method);
195
- if (!methodKey || !supportedMethods.includes(methodKey.toLowerCase())) {
255
+ const rawWebhook = webhooks2[webhookKey];
256
+ const webhook = resolvePathItem(rawWebhook, definition);
257
+ if (!webhook) {
258
+ throw new Error(`Webhook operation \`${method} ${webhookName}\` not found.`);
259
+ }
260
+ const methodKey = resolveMethodKey(webhook, method);
261
+ if (!methodKey) {
196
262
  throw new Error(`Webhook operation \`${method} ${webhookName}\` not found.`);
197
263
  }
198
264
  const operation = webhook[methodKey];
265
+ if (!operation || typeof operation !== "object") {
266
+ throw new Error(`Webhook operation \`${method} ${webhookName}\` not found.`);
267
+ }
199
268
  const rootPointer = `/webhooks/${encodePointer(webhookKey)}/${methodKey}`;
200
269
  const extraPointers = [];
201
270
  const seeds = new Set(collectRefsInSchema(operation));
271
+ const targetPointer = resolvePathItemTargetPointer(rawWebhook, definition);
272
+ if (targetPointer) {
273
+ extraPointers.push(`${targetPointer}/${methodKey}`);
274
+ }
202
275
  if (webhook.parameters) {
203
- extraPointers.push(`/webhooks/${encodePointer(webhookKey)}/parameters`);
276
+ extraPointers.push(
277
+ targetPointer ? `${targetPointer}/parameters` : `/webhooks/${encodePointer(webhookKey)}/parameters`
278
+ );
204
279
  collectRefsInSchema(webhook.parameters).forEach((ref) => seeds.add(ref));
205
280
  }
206
281
  const security = "security" in operation ? operation.security : definition.security;
@@ -468,7 +543,7 @@ async function buildAnalysis(definition, query2, scope) {
468
543
  locations: xmlSchemas2
469
544
  };
470
545
  }
471
- if (!query2 || query2.includes("xmlSchemas")) {
546
+ if (!query2 || query2.includes("webhooks")) {
472
547
  const webhooks2 = webhooks(definition, scope);
473
548
  analysis.webhooks = {
474
549
  present: !!webhooks2.length,