@amritk/lint 0.3.2 → 0.3.3

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.
Files changed (72) hide show
  1. package/dist/core/document.js +12 -10
  2. package/dist/core/formats.js +10 -13
  3. package/dist/core/glob.js +96 -118
  4. package/dist/core/index.js +31 -11
  5. package/dist/core/jsonpath.js +487 -561
  6. package/dist/core/lint.js +78 -85
  7. package/dist/core/plugin.js +21 -29
  8. package/dist/core/pointers.js +107 -151
  9. package/dist/core/ruleset.js +0 -0
  10. package/dist/core/runner.js +214 -272
  11. package/dist/core/types.js +4 -1
  12. package/dist/core/validate-ruleset.js +98 -112
  13. package/dist/fix/apply.js +58 -96
  14. package/dist/fix/index.js +7 -2
  15. package/dist/fix/plugin.js +15 -20
  16. package/dist/functions/alphabetical.js +39 -50
  17. package/dist/functions/casing.js +39 -45
  18. package/dist/functions/defined.js +7 -5
  19. package/dist/functions/enumeration.js +15 -25
  20. package/dist/functions/falsy.js +7 -5
  21. package/dist/functions/index.js +60 -44
  22. package/dist/functions/length.js +22 -32
  23. package/dist/functions/or.js +18 -24
  24. package/dist/functions/pattern.js +39 -49
  25. package/dist/functions/schema.js +93 -119
  26. package/dist/functions/truthy.js +7 -5
  27. package/dist/functions/typed-enum.js +33 -36
  28. package/dist/functions/undefined.js +7 -8
  29. package/dist/functions/unreferenced-reusable-object.js +35 -47
  30. package/dist/functions/xor.js +13 -16
  31. package/dist/index.js +114 -164
  32. package/dist/parsers/edit-model.js +316 -444
  33. package/dist/parsers/index.js +22 -19
  34. package/dist/parsers/json.js +39 -37
  35. package/dist/parsers/lines.js +23 -26
  36. package/dist/parsers/types.js +9 -7
  37. package/dist/parsers/yaml.js +137 -204
  38. package/dist/rules/openapi/fixers.js +166 -221
  39. package/dist/rules/openapi/formats.js +26 -27
  40. package/dist/rules/openapi/functions/example-validation.js +98 -138
  41. package/dist/rules/openapi/functions/helpers.js +8 -10
  42. package/dist/rules/openapi/functions/index.js +98 -72
  43. package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
  44. package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
  45. package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
  46. package/dist/rules/openapi/functions/oas-example-value.js +24 -27
  47. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
  48. package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
  49. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
  50. package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
  51. package/dist/rules/openapi/functions/oas-op-params.js +36 -42
  52. package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
  53. package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
  54. package/dist/rules/openapi/functions/oas-path-param.js +75 -96
  55. package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
  56. package/dist/rules/openapi/functions/oas-schema.js +9 -14
  57. package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
  58. package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
  59. package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
  60. package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
  61. package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
  62. package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
  63. package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
  64. package/dist/rules/openapi/functions/ref-siblings.js +13 -11
  65. package/dist/rules/openapi/index.js +99 -117
  66. package/dist/rules/openapi/oas.js +524 -536
  67. package/dist/rules/openapi/schemas/index.js +17 -33
  68. package/dist/rules/openapi/schemas/oas20.json +1 -1592
  69. package/dist/rules/openapi/schemas/oas30.json +1 -1651
  70. package/dist/rules/openapi/schemas/oas31.json +1 -1412
  71. package/dist/rules/openapi/schemas/oas32.json +1 -1684
  72. package/package.json +5 -5
@@ -1,31 +1,28 @@
1
- import { isObject } from './helpers.js';
2
- // OpenAPI 3.2 Example Object exclusivity (MUST-level, per the spec's field
3
- // table). The `value`/`externalValue` pair is intentionally omitted here — the
4
- // 3.x-wide `oas3-examples-value-or-externalValue` rule already covers it, so we
5
- // only police the constraints introduced with the new `dataValue` /
6
- // `serializedValue` fields to avoid double-reporting.
1
+ import { isObject } from "./helpers.js";
7
2
  const EXAMPLE_EXCLUSIONS = [
8
- // dataValue MUST NOT accompany value.
9
- { field: 'dataValue', conflicts: ['value'] },
10
- // serializedValue MUST NOT accompany value or externalValue.
11
- { field: 'serializedValue', conflicts: ['value', 'externalValue'] },
3
+ // dataValue MUST NOT accompany value.
4
+ { field: "dataValue", conflicts: ["value"] },
5
+ // serializedValue MUST NOT accompany value or externalValue.
6
+ { field: "serializedValue", conflicts: ["value", "externalValue"] }
12
7
  ];
13
- /** Flags forbidden field combinations on an OpenAPI 3.2 Example Object. */
14
- export const oasExampleValue = (example, _options, context) => {
15
- if (!isObject(example))
16
- return [];
17
- const results = [];
18
- for (const { field, conflicts } of EXAMPLE_EXCLUSIONS) {
19
- if (example[field] === undefined)
20
- continue;
21
- for (const other of conflicts) {
22
- if (example[other] !== undefined) {
23
- results.push({
24
- message: `"${field}" must not be used together with "${other}"`,
25
- path: [...context.path, other],
26
- });
27
- }
28
- }
8
+ const oasExampleValue = (example, _options, context) => {
9
+ if (!isObject(example))
10
+ return [];
11
+ const results = [];
12
+ for (const { field, conflicts } of EXAMPLE_EXCLUSIONS) {
13
+ if (example[field] === void 0)
14
+ continue;
15
+ for (const other of conflicts) {
16
+ if (example[other] !== void 0) {
17
+ results.push({
18
+ message: `"${field}" must not be used together with "${other}"`,
19
+ path: [...context.path, other]
20
+ });
21
+ }
29
22
  }
30
- return results;
23
+ }
24
+ return results;
25
+ };
26
+ export {
27
+ oasExampleValue
31
28
  };
@@ -1,20 +1,16 @@
1
- import { isObject } from './helpers.js';
2
- /**
3
- * Flags objects that carry more than one of a set of mutually exclusive
4
- * properties. OpenAPI 3.1's License Object, for example, defines `identifier`
5
- * (SPDX) as "mutually exclusive of the url field" — neither, or exactly one, is
6
- * allowed, but not both.
7
- */
8
- export const oasMutuallyExclusive = (input, options, context) => {
9
- if (!isObject(input))
10
- return [];
11
- const present = (options?.properties ?? []).filter((property) => input[property] !== undefined);
12
- if (present.length <= 1)
13
- return [];
14
- // The first present property is the "anchor"; flag every later one as the conflict.
15
- const [anchor, ...conflicts] = present;
16
- return conflicts.map((property) => ({
17
- message: `"${property}" must not be used together with "${anchor}" (mutually exclusive)`,
18
- path: [...context.path, property],
19
- }));
1
+ import { isObject } from "./helpers.js";
2
+ const oasMutuallyExclusive = (input, options, context) => {
3
+ if (!isObject(input))
4
+ return [];
5
+ const present = (options?.properties ?? []).filter((property) => input[property] !== void 0);
6
+ if (present.length <= 1)
7
+ return [];
8
+ const [anchor, ...conflicts] = present;
9
+ return conflicts.map((property) => ({
10
+ message: `"${property}" must not be used together with "${anchor}" (mutually exclusive)`,
11
+ path: [...context.path, property]
12
+ }));
13
+ };
14
+ export {
15
+ oasMutuallyExclusive
20
16
  };
@@ -1,22 +1,14 @@
1
- import { isObject } from './helpers.js';
2
- /**
3
- * Flags a Schema Object's `nullable` keyword, removed in OpenAPI 3.1+ (JSON
4
- * Schema 2020-12 uses a `"null"` type instead). Targets the *parent* of a
5
- * `nullable` key (`$..nullable^`) rather than `$..nullable` directly, which fixes
6
- * two problems with the naive `then: falsy` approach:
7
- * - a property literally named `nullable` (`properties: { nullable: {...} }`)
8
- * is no longer flagged, because there the `nullable` value is a Schema Object,
9
- * not the boolean keyword, and
10
- * - `nullable: false` is flagged too (a boolean of either value counts as the
11
- * keyword being present), so the migration fixer can drop it.
12
- */
13
- export const oasNoNullable = (parent, _options, context) => {
14
- if (!isObject(parent) || typeof parent['nullable'] !== 'boolean')
15
- return [];
16
- return [
17
- {
18
- message: 'nullable is not available in OpenAPI 3.1 or later; use a "null" type instead.',
19
- path: [...context.path, 'nullable'],
20
- },
21
- ];
1
+ import { isObject } from "./helpers.js";
2
+ const oasNoNullable = (parent, _options, context) => {
3
+ if (!isObject(parent) || typeof parent["nullable"] !== "boolean")
4
+ return [];
5
+ return [
6
+ {
7
+ message: 'nullable is not available in OpenAPI 3.1 or later; use a "null" type instead.',
8
+ path: [...context.path, "nullable"]
9
+ }
10
+ ];
11
+ };
12
+ export {
13
+ oasNoNullable
22
14
  };
@@ -1,21 +1,23 @@
1
- import { isObject } from './helpers.js';
2
- /** Validates OpenAPI v2 formData operations declare a compatible `consumes`. */
3
- export const oasOpFormDataConsumeCheck = (operation, _options, context) => {
4
- if (!isObject(operation))
5
- return [];
6
- const params = Array.isArray(operation['parameters']) ? operation['parameters'] : [];
7
- const hasFormData = params.some((param) => isObject(param) && param['in'] === 'formData');
8
- if (!hasFormData)
9
- return [];
10
- const consumes = Array.isArray(operation['consumes']) ? operation['consumes'] : [];
11
- const ok = consumes.some((type) => type === 'application/x-www-form-urlencoded' || type === 'multipart/form-data');
12
- if (!ok) {
13
- return [
14
- {
15
- message: 'Operations with formData parameters must consume application/x-www-form-urlencoded or multipart/form-data',
16
- path: [...context.path, 'consumes'],
17
- },
18
- ];
19
- }
1
+ import { isObject } from "./helpers.js";
2
+ const oasOpFormDataConsumeCheck = (operation, _options, context) => {
3
+ if (!isObject(operation))
20
4
  return [];
5
+ const params = Array.isArray(operation["parameters"]) ? operation["parameters"] : [];
6
+ const hasFormData = params.some((param) => isObject(param) && param["in"] === "formData");
7
+ if (!hasFormData)
8
+ return [];
9
+ const consumes = Array.isArray(operation["consumes"]) ? operation["consumes"] : [];
10
+ const ok = consumes.some((type) => type === "application/x-www-form-urlencoded" || type === "multipart/form-data");
11
+ if (!ok) {
12
+ return [
13
+ {
14
+ message: "Operations with formData parameters must consume application/x-www-form-urlencoded or multipart/form-data",
15
+ path: [...context.path, "consumes"]
16
+ }
17
+ ];
18
+ }
19
+ return [];
20
+ };
21
+ export {
22
+ oasOpFormDataConsumeCheck
21
23
  };
@@ -1,29 +1,29 @@
1
- import { isObject, OPERATION_METHODS } from './helpers.js';
2
- /** Ensures `operationId` values are unique across the document. */
3
- export const oasOpIdUnique = (paths, _options, context) => {
4
- if (!isObject(paths))
5
- return [];
6
- const seen = new Set();
7
- const results = [];
8
- for (const [path, item] of Object.entries(paths)) {
9
- if (!isObject(item))
10
- continue;
11
- for (const [method, operation] of Object.entries(item)) {
12
- // Only real operations carry an `operationId`; an `x-`extension object with
13
- // an `operationId`-looking key would otherwise create a phantom duplicate.
14
- if (!OPERATION_METHODS.has(method) || !isObject(operation))
15
- continue;
16
- const id = operation['operationId'];
17
- if (typeof id !== 'string')
18
- continue;
19
- if (seen.has(id)) {
20
- results.push({
21
- message: `operationId "${id}" must be unique`,
22
- path: [...context.path, path, method, 'operationId'],
23
- });
24
- }
25
- seen.add(id);
26
- }
1
+ import { isObject, OPERATION_METHODS } from "./helpers.js";
2
+ const oasOpIdUnique = (paths, _options, context) => {
3
+ if (!isObject(paths))
4
+ return [];
5
+ const seen = /* @__PURE__ */ new Set();
6
+ const results = [];
7
+ for (const [path, item] of Object.entries(paths)) {
8
+ if (!isObject(item))
9
+ continue;
10
+ for (const [method, operation] of Object.entries(item)) {
11
+ if (!OPERATION_METHODS.has(method) || !isObject(operation))
12
+ continue;
13
+ const id = operation["operationId"];
14
+ if (typeof id !== "string")
15
+ continue;
16
+ if (seen.has(id)) {
17
+ results.push({
18
+ message: `operationId "${id}" must be unique`,
19
+ path: [...context.path, path, method, "operationId"]
20
+ });
21
+ }
22
+ seen.add(id);
27
23
  }
28
- return results;
24
+ }
25
+ return results;
26
+ };
27
+ export {
28
+ oasOpIdUnique
29
29
  };
@@ -1,46 +1,40 @@
1
- import { isObject } from './helpers.js';
2
- /**
3
- * Validates an operation's `parameters` array, mirroring Spectral's `oasOpParams`:
4
- * flags duplicate `name`+`in` combinations, and — for OpenAPI 2.0, where the body
5
- * is a parameter — flags more than one `in: body` parameter (even with different
6
- * names) as well as mixing `in: body` with `in: formData`. `$ref` entries are
7
- * skipped because their real `name`/`in` is only known after resolution.
8
- */
9
- export const oasOpParams = (params, _options, context) => {
10
- if (!Array.isArray(params))
11
- return [];
12
- const results = [];
13
- const seen = new Set();
14
- const bodyIndices = [];
15
- const formDataIndices = [];
16
- params.forEach((param, index) => {
17
- if (!isObject(param) || '$ref' in param)
18
- return;
19
- if (param['name'] !== undefined && param['in'] !== undefined) {
20
- const key = `${String(param['in'])}:${String(param['name'])}`;
21
- if (seen.has(key)) {
22
- results.push({
23
- message: `Duplicate parameter "${String(param['name'])}" in "${String(param['in'])}"`,
24
- path: [...context.path, index],
25
- });
26
- }
27
- seen.add(key);
28
- }
29
- if (param['in'] === 'body')
30
- bodyIndices.push(index);
31
- else if (param['in'] === 'formData')
32
- formDataIndices.push(index);
33
- });
34
- // OAS2 allows at most one body parameter, and body and formData are mutually
35
- // exclusive within a single operation.
36
- if (bodyIndices.length > 0 && formDataIndices.length > 0) {
37
- results.push({ message: 'Operation must not have both "in:body" and "in:formData" parameters' });
38
- }
39
- for (let i = 1; i < bodyIndices.length; i++) {
1
+ import { isObject } from "./helpers.js";
2
+ const oasOpParams = (params, _options, context) => {
3
+ if (!Array.isArray(params))
4
+ return [];
5
+ const results = [];
6
+ const seen = /* @__PURE__ */ new Set();
7
+ const bodyIndices = [];
8
+ const formDataIndices = [];
9
+ params.forEach((param, index) => {
10
+ if (!isObject(param) || "$ref" in param)
11
+ return;
12
+ if (param["name"] !== void 0 && param["in"] !== void 0) {
13
+ const key = `${String(param["in"])}:${String(param["name"])}`;
14
+ if (seen.has(key)) {
40
15
  results.push({
41
- message: 'Operation must not have more than a single instance of the "in:body" parameter',
42
- path: [...context.path, bodyIndices[i]],
16
+ message: `Duplicate parameter "${String(param["name"])}" in "${String(param["in"])}"`,
17
+ path: [...context.path, index]
43
18
  });
19
+ }
20
+ seen.add(key);
44
21
  }
45
- return results;
22
+ if (param["in"] === "body")
23
+ bodyIndices.push(index);
24
+ else if (param["in"] === "formData")
25
+ formDataIndices.push(index);
26
+ });
27
+ if (bodyIndices.length > 0 && formDataIndices.length > 0) {
28
+ results.push({ message: 'Operation must not have both "in:body" and "in:formData" parameters' });
29
+ }
30
+ for (let i = 1; i < bodyIndices.length; i++) {
31
+ results.push({
32
+ message: 'Operation must not have more than a single instance of the "in:body" parameter',
33
+ path: [...context.path, bodyIndices[i]]
34
+ });
35
+ }
36
+ return results;
37
+ };
38
+ export {
39
+ oasOpParams
46
40
  };
@@ -1,44 +1,45 @@
1
- import { isObject, OPERATION_METHODS } from './helpers.js';
2
- /** Walks `path` into `root`, returning the nested value or undefined. */
1
+ import { isObject, OPERATION_METHODS } from "./helpers.js";
3
2
  const getIn = (root, path) => {
4
- let current = root;
5
- for (const key of path) {
6
- if (!isObject(current))
7
- return undefined;
8
- current = current[key];
9
- }
10
- return current;
3
+ let current = root;
4
+ for (const key of path) {
5
+ if (!isObject(current))
6
+ return void 0;
7
+ current = current[key];
8
+ }
9
+ return current;
11
10
  };
12
- /** Validates that every referenced security scheme is defined. */
13
- export const oasOpSecurityDefined = (root, options, context) => {
14
- if (!isObject(root))
15
- return [];
16
- const schemes = getIn(root, options?.schemesPath ?? []);
17
- const defined = new Set(isObject(schemes) ? Object.keys(schemes) : []);
18
- const results = [];
19
- const check = (security, path) => {
20
- if (!Array.isArray(security))
21
- return;
22
- security.forEach((requirement, index) => {
23
- if (!isObject(requirement))
24
- return;
25
- for (const name of Object.keys(requirement)) {
26
- if (!defined.has(name)) {
27
- results.push({ message: `Security scheme "${name}" is not defined`, path: [...path, index, name] });
28
- }
29
- }
30
- });
31
- };
32
- check(root['security'], [...context.path, 'security']);
33
- const paths = isObject(root['paths']) ? root['paths'] : {};
34
- for (const [path, item] of Object.entries(paths)) {
35
- if (!isObject(item))
36
- continue;
37
- for (const [method, operation] of Object.entries(item)) {
38
- if (OPERATION_METHODS.has(method) && isObject(operation)) {
39
- check(operation['security'], [...context.path, 'paths', path, method, 'security']);
40
- }
11
+ const oasOpSecurityDefined = (root, options, context) => {
12
+ if (!isObject(root))
13
+ return [];
14
+ const schemes = getIn(root, options?.schemesPath ?? []);
15
+ const defined = new Set(isObject(schemes) ? Object.keys(schemes) : []);
16
+ const results = [];
17
+ const check = (security, path) => {
18
+ if (!Array.isArray(security))
19
+ return;
20
+ security.forEach((requirement, index) => {
21
+ if (!isObject(requirement))
22
+ return;
23
+ for (const name of Object.keys(requirement)) {
24
+ if (!defined.has(name)) {
25
+ results.push({ message: `Security scheme "${name}" is not defined`, path: [...path, index, name] });
41
26
  }
27
+ }
28
+ });
29
+ };
30
+ check(root["security"], [...context.path, "security"]);
31
+ const paths = isObject(root["paths"]) ? root["paths"] : {};
32
+ for (const [path, item] of Object.entries(paths)) {
33
+ if (!isObject(item))
34
+ continue;
35
+ for (const [method, operation] of Object.entries(item)) {
36
+ if (OPERATION_METHODS.has(method) && isObject(operation)) {
37
+ check(operation["security"], [...context.path, "paths", path, method, "security"]);
38
+ }
42
39
  }
43
- return results;
40
+ }
41
+ return results;
42
+ };
43
+ export {
44
+ oasOpSecurityDefined
44
45
  };
@@ -1,16 +1,14 @@
1
- import { isObject } from './helpers.js';
2
- // A success response is any 2xx/3xx status code, or a `2XX`/`3XX` wildcard (3.x).
3
- // `default` is intentionally NOT counted — it can carry any status, including an
4
- // error, so it does not prove the operation has a success response (this matches
5
- // Spectral, which counts only codes in the [200, 400) range plus the wildcards).
1
+ import { isObject } from "./helpers.js";
6
2
  const SUCCESS_CODE = /^[23](\d\d|XX)$/;
7
- /** Ensures every operation declares at least one 2xx or 3xx response. */
8
- export const oasOpSuccessResponse = (responses) => {
9
- if (!isObject(responses))
10
- return [];
11
- const hasSuccess = Object.keys(responses).some((code) => SUCCESS_CODE.test(code));
12
- if (!hasSuccess) {
13
- return [{ message: 'Operation must define at least one 2xx or 3xx response' }];
14
- }
3
+ const oasOpSuccessResponse = (responses) => {
4
+ if (!isObject(responses))
15
5
  return [];
6
+ const hasSuccess = Object.keys(responses).some((code) => SUCCESS_CODE.test(code));
7
+ if (!hasSuccess) {
8
+ return [{ message: "Operation must define at least one 2xx or 3xx response" }];
9
+ }
10
+ return [];
11
+ };
12
+ export {
13
+ oasOpSuccessResponse
16
14
  };
@@ -1,106 +1,85 @@
1
- import { isObject, OPERATION_METHODS } from './helpers.js';
2
- // Path template parameters, e.g. `{id}`. Matches Spectral's grammar so matrix
3
- // (`{;id}`), optional (`{id?}`), and explode (`{id*}`) markers are stripped off
4
- // the captured name before comparison.
1
+ import { isObject, OPERATION_METHODS } from "./helpers.js";
5
2
  const PATH_TEMPLATE = /(\{;?\??[a-zA-Z0-9_-]+\*?\})/g;
6
- const namedPathParam = (param) => param['in'] === 'path' && typeof param['name'] === 'string' ? param['name'] : undefined;
7
- /**
8
- * Records a single `in: path` definition, emitting Spectral's `required: true`
9
- * and duplicate-definition findings. Returns the parameter name when it is the
10
- * first definition seen (so the caller registers it as usable), else undefined.
11
- */
3
+ const namedPathParam = (param) => param["in"] === "path" && typeof param["name"] === "string" ? param["name"] : void 0;
12
4
  const recordPathParam = (param, definitionPath, seen, results) => {
13
- const name = namedPathParam(param);
14
- if (name === undefined)
15
- return undefined;
16
- if (param['required'] !== true) {
5
+ const name = namedPathParam(param);
6
+ if (name === void 0)
7
+ return void 0;
8
+ if (param["required"] !== true) {
9
+ results.push({
10
+ message: `Path parameter "${name}" must have a "required" property that is set to "true"`,
11
+ path: definitionPath
12
+ });
13
+ }
14
+ if (name in seen) {
15
+ results.push({ message: `Path parameter "${name}" must not be defined multiple times`, path: definitionPath });
16
+ return void 0;
17
+ }
18
+ return name;
19
+ };
20
+ const oasPathParam = (paths, _options, context) => {
21
+ if (!isObject(paths))
22
+ return [];
23
+ const results = [];
24
+ for (const [path, item] of Object.entries(paths)) {
25
+ if (!isObject(item))
26
+ continue;
27
+ const templates = [];
28
+ for (const match of path.matchAll(PATH_TEMPLATE)) {
29
+ const name = match[0].replace(/[{}?*;]/g, "");
30
+ if (templates.includes(name)) {
17
31
  results.push({
18
- message: `Path parameter "${name}" must have a "required" property that is set to "true"`,
19
- path: definitionPath,
32
+ message: `Path "${path}" must not use parameter "{${name}}" more than once`,
33
+ path: [...context.path, path]
20
34
  });
35
+ } else {
36
+ templates.push(name);
37
+ }
21
38
  }
22
- if (name in seen) {
23
- results.push({ message: `Path parameter "${name}" must not be defined multiple times`, path: definitionPath });
24
- return undefined;
39
+ const topParams = {};
40
+ if (Array.isArray(item["parameters"])) {
41
+ item["parameters"].forEach((param, index) => {
42
+ if (!isObject(param))
43
+ return;
44
+ const definitionPath = [...context.path, path, "parameters", index];
45
+ const name = recordPathParam(param, definitionPath, topParams, results);
46
+ if (name !== void 0)
47
+ topParams[name] = definitionPath;
48
+ });
25
49
  }
26
- return name;
27
- };
28
- /**
29
- * Validates path templating per operation, mirroring Spectral's `oasPathParam`.
30
- * For each operation on a path it checks that (a) every `{template}` in the path
31
- * has a matching `in: path` definition, (b) every `in: path` definition is used
32
- * in the template, (c) path parameters carry `required: true`, (d) a template is
33
- * not repeated in the path key, and (e) a parameter is not defined twice. Path
34
- * Item level parameters are merged with the operation's own parameters. Runs on
35
- * the resolved document (the rule is `resolved: true`), so `$ref`d parameters are
36
- * already inlined.
37
- */
38
- export const oasPathParam = (paths, _options, context) => {
39
- if (!isObject(paths))
40
- return [];
41
- const results = [];
42
- for (const [path, item] of Object.entries(paths)) {
43
- if (!isObject(item))
44
- continue;
45
- // (d) Templates declared in the path key, deduplicated (a repeat is an error).
46
- const templates = [];
47
- for (const match of path.matchAll(PATH_TEMPLATE)) {
48
- const name = match[0].replace(/[{}?*;]/g, '');
49
- if (templates.includes(name)) {
50
- results.push({
51
- message: `Path "${path}" must not use parameter "{${name}}" more than once`,
52
- path: [...context.path, path],
53
- });
54
- }
55
- else {
56
- templates.push(name);
57
- }
58
- }
59
- // Path Item level parameters apply to every operation on the path.
60
- const topParams = {};
61
- if (Array.isArray(item['parameters'])) {
62
- item['parameters'].forEach((param, index) => {
63
- if (!isObject(param))
64
- return;
65
- const definitionPath = [...context.path, path, 'parameters', index];
66
- const name = recordPathParam(param, definitionPath, topParams, results);
67
- if (name !== undefined)
68
- topParams[name] = definitionPath;
69
- });
50
+ for (const [method, operation] of Object.entries(item)) {
51
+ if (method === "parameters" || !OPERATION_METHODS.has(method) || !isObject(operation))
52
+ continue;
53
+ const operationPath = [...context.path, path, method];
54
+ const operationParams = {};
55
+ if (Array.isArray(operation["parameters"])) {
56
+ operation["parameters"].forEach((param, index) => {
57
+ if (!isObject(param))
58
+ return;
59
+ const definitionPath = [...operationPath, "parameters", index];
60
+ const name = recordPathParam(param, definitionPath, operationParams, results);
61
+ if (name !== void 0)
62
+ operationParams[name] = definitionPath;
63
+ });
64
+ }
65
+ const defined = { ...topParams, ...operationParams };
66
+ for (const [name, definitionPath] of Object.entries(defined)) {
67
+ if (definitionPath && !templates.includes(name)) {
68
+ results.push({ message: `Parameter "${name}" must be used in path "${path}"`, path: definitionPath });
70
69
  }
71
- for (const [method, operation] of Object.entries(item)) {
72
- if (method === 'parameters' || !OPERATION_METHODS.has(method) || !isObject(operation))
73
- continue;
74
- const operationPath = [...context.path, path, method];
75
- // Each operation is evaluated against its own params plus the shared path-item ones.
76
- const operationParams = {};
77
- if (Array.isArray(operation['parameters'])) {
78
- operation['parameters'].forEach((param, index) => {
79
- if (!isObject(param))
80
- return;
81
- const definitionPath = [...operationPath, 'parameters', index];
82
- const name = recordPathParam(param, definitionPath, operationParams, results);
83
- if (name !== undefined)
84
- operationParams[name] = definitionPath;
85
- });
86
- }
87
- const defined = { ...topParams, ...operationParams };
88
- // (b) Every defined `in: path` parameter must appear in the path template.
89
- for (const [name, definitionPath] of Object.entries(defined)) {
90
- if (definitionPath && !templates.includes(name)) {
91
- results.push({ message: `Parameter "${name}" must be used in path "${path}"`, path: definitionPath });
92
- }
93
- }
94
- // (a) Every `{template}` must have a matching definition on the operation.
95
- for (const name of templates) {
96
- if (!(name in defined)) {
97
- results.push({
98
- message: `Operation must define path parameter "{${name}}" as expected by path "${path}"`,
99
- path: operationPath,
100
- });
101
- }
102
- }
70
+ }
71
+ for (const name of templates) {
72
+ if (!(name in defined)) {
73
+ results.push({
74
+ message: `Operation must define path parameter "{${name}}" as expected by path "${path}"`,
75
+ path: operationPath
76
+ });
103
77
  }
78
+ }
104
79
  }
105
- return results;
80
+ }
81
+ return results;
82
+ };
83
+ export {
84
+ oasPathParam
106
85
  };