@amritk/lint 0.3.1 → 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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/dist/core/document.js +12 -10
  3. package/dist/core/formats.js +10 -13
  4. package/dist/core/glob.js +96 -118
  5. package/dist/core/index.js +31 -11
  6. package/dist/core/jsonpath.js +487 -561
  7. package/dist/core/lint.js +78 -85
  8. package/dist/core/plugin.js +21 -29
  9. package/dist/core/pointers.js +107 -151
  10. package/dist/core/ruleset.js +0 -0
  11. package/dist/core/runner.js +214 -272
  12. package/dist/core/types.js +4 -1
  13. package/dist/core/validate-ruleset.js +98 -112
  14. package/dist/fix/apply.js +58 -96
  15. package/dist/fix/index.js +7 -2
  16. package/dist/fix/plugin.js +15 -20
  17. package/dist/functions/alphabetical.js +39 -50
  18. package/dist/functions/casing.js +39 -45
  19. package/dist/functions/defined.js +7 -5
  20. package/dist/functions/enumeration.js +15 -25
  21. package/dist/functions/falsy.js +7 -5
  22. package/dist/functions/index.js +60 -44
  23. package/dist/functions/length.js +22 -32
  24. package/dist/functions/or.js +18 -24
  25. package/dist/functions/pattern.js +39 -49
  26. package/dist/functions/schema.js +93 -119
  27. package/dist/functions/truthy.js +7 -5
  28. package/dist/functions/typed-enum.js +33 -36
  29. package/dist/functions/undefined.js +7 -8
  30. package/dist/functions/unreferenced-reusable-object.js +35 -47
  31. package/dist/functions/xor.js +13 -16
  32. package/dist/index.js +114 -164
  33. package/dist/parsers/edit-model.js +316 -444
  34. package/dist/parsers/index.js +22 -19
  35. package/dist/parsers/json.js +39 -37
  36. package/dist/parsers/lines.js +23 -26
  37. package/dist/parsers/types.js +9 -7
  38. package/dist/parsers/yaml.js +137 -204
  39. package/dist/rules/openapi/fixers.js +166 -221
  40. package/dist/rules/openapi/formats.js +26 -27
  41. package/dist/rules/openapi/functions/example-validation.js +98 -138
  42. package/dist/rules/openapi/functions/helpers.js +8 -10
  43. package/dist/rules/openapi/functions/index.js +98 -72
  44. package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
  45. package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
  46. package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
  47. package/dist/rules/openapi/functions/oas-example-value.js +24 -27
  48. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
  49. package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
  50. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
  51. package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
  52. package/dist/rules/openapi/functions/oas-op-params.js +36 -42
  53. package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
  54. package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
  55. package/dist/rules/openapi/functions/oas-path-param.js +75 -96
  56. package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
  57. package/dist/rules/openapi/functions/oas-schema.js +9 -14
  58. package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
  59. package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
  60. package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
  61. package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
  62. package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
  63. package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
  64. package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
  65. package/dist/rules/openapi/functions/ref-siblings.js +13 -11
  66. package/dist/rules/openapi/index.js +99 -117
  67. package/dist/rules/openapi/oas.js +524 -536
  68. package/dist/rules/openapi/schemas/index.js +17 -33
  69. package/dist/rules/openapi/schemas/oas20.json +1 -1592
  70. package/dist/rules/openapi/schemas/oas30.json +1 -1651
  71. package/dist/rules/openapi/schemas/oas31.json +1 -1412
  72. package/dist/rules/openapi/schemas/oas32.json +1 -1684
  73. package/package.json +5 -5
@@ -1,121 +1,107 @@
1
- import { compileQuery } from './jsonpath.js';
2
- const SEVERITIES = new Set(['error', 'warn', 'info', 'hint', 'off']);
3
- const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
1
+ import { compileQuery } from "./jsonpath.js";
2
+ const SEVERITIES = /* @__PURE__ */ new Set(["error", "warn", "info", "hint", "off"]);
3
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
4
  const isValidSeverity = (value) => {
5
- if (typeof value === 'number')
6
- return Number.isInteger(value) && value >= 0 && value <= 3;
7
- return typeof value === 'string' && SEVERITIES.has(value);
5
+ if (typeof value === "number")
6
+ return Number.isInteger(value) && value >= 0 && value <= 3;
7
+ return typeof value === "string" && SEVERITIES.has(value);
8
8
  };
9
9
  const validateThen = (then, path, problems) => {
10
- const entries = Array.isArray(then) ? then : [then];
11
- entries.forEach((entry, index) => {
12
- const at = Array.isArray(then) ? [...path, index] : path;
13
- if (!isObject(entry)) {
14
- problems.push({ message: '`then` must be an object (or array of objects)', path: at });
15
- return;
16
- }
17
- const fn = entry['function'];
18
- if (typeof fn !== 'string' && typeof fn !== 'function') {
19
- problems.push({ message: '`then.function` must be a function name or reference', path: [...at, 'function'] });
20
- }
21
- if (entry['field'] !== undefined && typeof entry['field'] !== 'string') {
22
- problems.push({ message: '`then.field` must be a string', path: [...at, 'field'] });
23
- }
24
- });
25
- };
26
- const validateRule = (name, entry, path, problems) => {
27
- // Shorthand: boolean toggle or a severity string.
28
- if (typeof entry === 'boolean')
29
- return;
30
- if (typeof entry === 'string') {
31
- if (!isValidSeverity(entry)) {
32
- problems.push({ message: `Rule "${name}" has invalid severity "${entry}"`, path });
33
- }
34
- return;
35
- }
10
+ const entries = Array.isArray(then) ? then : [then];
11
+ entries.forEach((entry, index) => {
12
+ const at = Array.isArray(then) ? [...path, index] : path;
36
13
  if (!isObject(entry)) {
37
- problems.push({ message: `Rule "${name}" must be an object, boolean, or severity string`, path });
38
- return;
14
+ problems.push({ message: "`then` must be an object (or array of objects)", path: at });
15
+ return;
39
16
  }
40
- if (entry['given'] === undefined) {
41
- problems.push({ message: `Rule "${name}" is missing \`given\``, path: [...path, 'given'] });
17
+ const fn = entry["function"];
18
+ if (typeof fn !== "string" && typeof fn !== "function") {
19
+ problems.push({ message: "`then.function` must be a function name or reference", path: [...at, "function"] });
42
20
  }
43
- else if (typeof entry['given'] !== 'string' && !Array.isArray(entry['given'])) {
44
- problems.push({ message: `Rule "${name}" \`given\` must be a string or array`, path: [...path, 'given'] });
45
- }
46
- else {
47
- // Flag a malformed JSONPath expression so it does not silently match nothing
48
- // at run time. Alias references (`#Alias`) are only valid once expanded, so
49
- // they are skipped here.
50
- const givens = Array.isArray(entry['given']) ? entry['given'] : [entry['given']];
51
- givens.forEach((given, index) => {
52
- if (typeof given !== 'string' || given.startsWith('#'))
53
- return;
54
- const error = compileQuery(given).error;
55
- if (error !== undefined) {
56
- const at = Array.isArray(entry['given']) ? [...path, 'given', index] : [...path, 'given'];
57
- problems.push({ message: `Rule "${name}" has an invalid \`given\` "${given}": ${error}`, path: at });
58
- }
59
- });
60
- }
61
- if (entry['then'] === undefined) {
62
- problems.push({ message: `Rule "${name}" is missing \`then\``, path: [...path, 'then'] });
63
- }
64
- else {
65
- validateThen(entry['then'], [...path, 'then'], problems);
66
- }
67
- if (entry['severity'] !== undefined && !isValidSeverity(entry['severity'])) {
68
- problems.push({ message: `Rule "${name}" has invalid severity`, path: [...path, 'severity'] });
69
- }
70
- if (entry['formats'] !== undefined && !Array.isArray(entry['formats'])) {
71
- problems.push({ message: `Rule "${name}" \`formats\` must be an array`, path: [...path, 'formats'] });
21
+ if (entry["field"] !== void 0 && typeof entry["field"] !== "string") {
22
+ problems.push({ message: "`then.field` must be a string", path: [...at, "field"] });
72
23
  }
24
+ });
73
25
  };
74
- /**
75
- * Validates the *shape* of a ruleset definition, returning a list of problems
76
- * (empty when valid). This is a lightweight structural check — it does not load
77
- * `extends` targets or verify that referenced functions exist — so a malformed
78
- * ruleset surfaces actionable diagnostics instead of failing obscurely at runtime.
79
- */
80
- export const validateRuleset = (definition) => {
81
- const problems = [];
82
- if (!isObject(definition)) {
83
- return [{ message: 'Ruleset must be an object', path: [] }];
84
- }
85
- if (definition['rules'] !== undefined) {
86
- if (!isObject(definition['rules'])) {
87
- problems.push({ message: '`rules` must be an object', path: ['rules'] });
88
- }
89
- else {
90
- for (const [name, entry] of Object.entries(definition['rules'])) {
91
- validateRule(name, entry, ['rules', name], problems);
92
- }
93
- }
94
- }
95
- const ext = definition['extends'];
96
- if (ext !== undefined && typeof ext !== 'string' && !Array.isArray(ext) && !isObject(ext)) {
97
- problems.push({ message: '`extends` must be a string, array, or object', path: ['extends'] });
98
- }
99
- if (definition['overrides'] !== undefined && !Array.isArray(definition['overrides'])) {
100
- problems.push({ message: '`overrides` must be an array', path: ['overrides'] });
101
- }
102
- else if (Array.isArray(definition['overrides'])) {
103
- definition['overrides'].forEach((override, index) => {
104
- if (!isObject(override) || !Array.isArray(override['files'])) {
105
- problems.push({ message: 'Each override must have a `files` array', path: ['overrides', index] });
106
- }
107
- });
108
- }
109
- if (definition['functions'] !== undefined && !Array.isArray(definition['functions'])) {
110
- problems.push({ message: '`functions` must be an array of names', path: ['functions'] });
111
- }
112
- if (definition['formats'] !== undefined && !Array.isArray(definition['formats'])) {
113
- problems.push({ message: '`formats` must be an array', path: ['formats'] });
114
- }
115
- // A ruleset with neither rules nor extends does nothing — flag it.
116
- if (definition.rules === undefined &&
117
- definition.extends === undefined) {
118
- problems.push({ message: 'Ruleset has no `rules` and no `extends` (it will produce no findings)', path: [] });
119
- }
120
- return problems;
26
+ const validateRule = (name, entry, path, problems) => {
27
+ if (typeof entry === "boolean")
28
+ return;
29
+ if (typeof entry === "string") {
30
+ if (!isValidSeverity(entry)) {
31
+ problems.push({ message: `Rule "${name}" has invalid severity "${entry}"`, path });
32
+ }
33
+ return;
34
+ }
35
+ if (!isObject(entry)) {
36
+ problems.push({ message: `Rule "${name}" must be an object, boolean, or severity string`, path });
37
+ return;
38
+ }
39
+ if (entry["given"] === void 0) {
40
+ problems.push({ message: `Rule "${name}" is missing \`given\``, path: [...path, "given"] });
41
+ } else if (typeof entry["given"] !== "string" && !Array.isArray(entry["given"])) {
42
+ problems.push({ message: `Rule "${name}" \`given\` must be a string or array`, path: [...path, "given"] });
43
+ } else {
44
+ const givens = Array.isArray(entry["given"]) ? entry["given"] : [entry["given"]];
45
+ givens.forEach((given, index) => {
46
+ if (typeof given !== "string" || given.startsWith("#"))
47
+ return;
48
+ const error = compileQuery(given).error;
49
+ if (error !== void 0) {
50
+ const at = Array.isArray(entry["given"]) ? [...path, "given", index] : [...path, "given"];
51
+ problems.push({ message: `Rule "${name}" has an invalid \`given\` "${given}": ${error}`, path: at });
52
+ }
53
+ });
54
+ }
55
+ if (entry["then"] === void 0) {
56
+ problems.push({ message: `Rule "${name}" is missing \`then\``, path: [...path, "then"] });
57
+ } else {
58
+ validateThen(entry["then"], [...path, "then"], problems);
59
+ }
60
+ if (entry["severity"] !== void 0 && !isValidSeverity(entry["severity"])) {
61
+ problems.push({ message: `Rule "${name}" has invalid severity`, path: [...path, "severity"] });
62
+ }
63
+ if (entry["formats"] !== void 0 && !Array.isArray(entry["formats"])) {
64
+ problems.push({ message: `Rule "${name}" \`formats\` must be an array`, path: [...path, "formats"] });
65
+ }
66
+ };
67
+ const validateRuleset = (definition) => {
68
+ const problems = [];
69
+ if (!isObject(definition)) {
70
+ return [{ message: "Ruleset must be an object", path: [] }];
71
+ }
72
+ if (definition["rules"] !== void 0) {
73
+ if (!isObject(definition["rules"])) {
74
+ problems.push({ message: "`rules` must be an object", path: ["rules"] });
75
+ } else {
76
+ for (const [name, entry] of Object.entries(definition["rules"])) {
77
+ validateRule(name, entry, ["rules", name], problems);
78
+ }
79
+ }
80
+ }
81
+ const ext = definition["extends"];
82
+ if (ext !== void 0 && typeof ext !== "string" && !Array.isArray(ext) && !isObject(ext)) {
83
+ problems.push({ message: "`extends` must be a string, array, or object", path: ["extends"] });
84
+ }
85
+ if (definition["overrides"] !== void 0 && !Array.isArray(definition["overrides"])) {
86
+ problems.push({ message: "`overrides` must be an array", path: ["overrides"] });
87
+ } else if (Array.isArray(definition["overrides"])) {
88
+ definition["overrides"].forEach((override, index) => {
89
+ if (!isObject(override) || !Array.isArray(override["files"])) {
90
+ problems.push({ message: "Each override must have a `files` array", path: ["overrides", index] });
91
+ }
92
+ });
93
+ }
94
+ if (definition["functions"] !== void 0 && !Array.isArray(definition["functions"])) {
95
+ problems.push({ message: "`functions` must be an array of names", path: ["functions"] });
96
+ }
97
+ if (definition["formats"] !== void 0 && !Array.isArray(definition["formats"])) {
98
+ problems.push({ message: "`formats` must be an array", path: ["formats"] });
99
+ }
100
+ if (definition.rules === void 0 && definition.extends === void 0) {
101
+ problems.push({ message: "Ruleset has no `rules` and no `extends` (it will produce no findings)", path: [] });
102
+ }
103
+ return problems;
104
+ };
105
+ export {
106
+ validateRuleset
121
107
  };
package/dist/fix/apply.js CHANGED
@@ -1,101 +1,63 @@
1
- import { applyEditOpsWithChanges } from '../parsers/index.js';
2
- /** The ops that structurally reshape an array, invalidating positional indices into it. */
3
- const STRUCTURAL_ARRAY_OPS = new Set(['removeItems', 'reorderArray', 'insertItem']);
1
+ import { applyEditOpsWithChanges } from "../parsers/index.js";
2
+ const STRUCTURAL_ARRAY_OPS = /* @__PURE__ */ new Set(["removeItems", "reorderArray", "insertItem"]);
4
3
  const isStructuralArrayOp = (op) => STRUCTURAL_ARRAY_OPS.has(op.op);
5
- /**
6
- * Whether `path` addresses (or reaches into) an array that an earlier op in this
7
- * batch already reshaped. After a `removeItems`/`reorderArray`/`insertItem`, the
8
- * element indices of that array are stale, so a second op that either targets the
9
- * same array or indexes into it by position would act on the wrong element. Such
10
- * an op is deferred to the next fixpoint pass, which re-derives indices from the
11
- * freshly-parsed document.
12
- */
13
4
  const touchesModifiedArray = (path, modified) => modified.some((array) => {
14
- if (array.length > path.length)
15
- return false;
16
- if (!array.every((segment, i) => segment === path[i]))
17
- return false;
18
- // Another structural op on the same array conflicts; a deeper op conflicts
19
- // only when it indexes the array by position (the stale part).
20
- return array.length === path.length || typeof path[array.length] === 'number';
5
+ if (array.length > path.length)
6
+ return false;
7
+ if (!array.every((segment, i) => segment === path[i]))
8
+ return false;
9
+ return array.length === path.length || typeof path[array.length] === "number";
21
10
  });
22
- /**
23
- * Computes the structural edits for every fixable finding in `diagnostics`, then
24
- * applies them to `input` in one pass. Edits from different findings that come
25
- * out identical (e.g. several "not alphabetical" findings on one array all asking
26
- * for the same reorder) are de-duplicated so the edit is applied once.
27
- *
28
- * `data` is the *unresolved* parsed document: fixers read the real node at a
29
- * finding's path to derive the edit, and edits whose path no longer resolves are
30
- * dropped — so a finding on an inlined `$ref` node simply isn't fixed rather than
31
- * corrupting the source.
32
- *
33
- * When two ops in one batch would both reshape the same array, only the first is
34
- * applied this pass; the second is *deferred* and its finding is left unreported,
35
- * so the surrounding fixpoint loop re-derives it against the already-edited
36
- * document (where the indices are fresh) on the next pass. A finding is reported
37
- * in `applied` only when *every* edit it contributed actually changed the text —
38
- * a partially-applied or deferred fix is retried rather than falsely counted.
39
- */
40
- export const applyFixes = (input, format, data, diagnostics, fixers, options = {}) => {
41
- const safeOnly = options.safeOnly !== false;
42
- // Gather each candidate finding's ops up front so we can reason about conflicts
43
- // across the whole batch before lowering anything to text.
44
- const candidates = [];
45
- for (const diagnostic of diagnostics) {
46
- const fixer = fixers[String(diagnostic.code)];
47
- if (!fixer)
48
- continue;
49
- if (safeOnly && fixer.safe === false)
50
- continue;
51
- const produced = fixer.fix({ diagnostic, data, format });
52
- if (!produced)
53
- continue;
54
- const ops = Array.isArray(produced) ? produced : [produced];
55
- if (ops.length === 0)
56
- continue;
57
- candidates.push({ fix: { code: diagnostic.code, path: diagnostic.path }, ops });
11
+ const applyFixes = (input, format, data, diagnostics, fixers, options = {}) => {
12
+ const safeOnly = options.safeOnly !== false;
13
+ const candidates = [];
14
+ for (const diagnostic of diagnostics) {
15
+ const fixer = fixers[String(diagnostic.code)];
16
+ if (!fixer)
17
+ continue;
18
+ if (safeOnly && fixer.safe === false)
19
+ continue;
20
+ const produced = fixer.fix({ diagnostic, data, format });
21
+ if (!produced)
22
+ continue;
23
+ const ops2 = Array.isArray(produced) ? produced : [produced];
24
+ if (ops2.length === 0)
25
+ continue;
26
+ candidates.push({ fix: { code: diagnostic.code, path: diagnostic.path }, ops: ops2 });
27
+ }
28
+ const ops = [];
29
+ const indexByKey = /* @__PURE__ */ new Map();
30
+ const modifiedArrays = [];
31
+ const planned = [];
32
+ for (const candidate of candidates) {
33
+ const indices = [];
34
+ let deferred = false;
35
+ for (const op of candidate.ops) {
36
+ const key = JSON.stringify(op);
37
+ const existing = indexByKey.get(key);
38
+ if (existing !== void 0) {
39
+ indices.push(existing);
40
+ continue;
41
+ }
42
+ if (touchesModifiedArray(op.path, modifiedArrays)) {
43
+ deferred = true;
44
+ continue;
45
+ }
46
+ const index = ops.length;
47
+ indexByKey.set(key, index);
48
+ ops.push(op);
49
+ if (isStructuralArrayOp(op))
50
+ modifiedArrays.push(op.path);
51
+ indices.push(index);
58
52
  }
59
- const ops = [];
60
- const indexByKey = new Map();
61
- const modifiedArrays = [];
62
- // Per candidate: the batch index of each op that made it into this pass, and
63
- // whether any op had to be deferred (which keeps the finding unreported so it
64
- // is retried once the earlier structural edit has landed).
65
- const planned = [];
66
- for (const candidate of candidates) {
67
- const indices = [];
68
- let deferred = false;
69
- for (const op of candidate.ops) {
70
- const key = JSON.stringify(op);
71
- // De-duplicate identical edits (e.g. several findings on one array all asking
72
- // for the same reorder): the same edit is applied — and counted — once, and
73
- // is never a conflict with itself.
74
- const existing = indexByKey.get(key);
75
- if (existing !== undefined) {
76
- indices.push(existing);
77
- continue;
78
- }
79
- // A distinct op that would reshape or index into an already-reshaped array
80
- // is deferred to the next pass, where indices are re-derived from fresh data.
81
- if (touchesModifiedArray(op.path, modifiedArrays)) {
82
- deferred = true;
83
- continue;
84
- }
85
- const index = ops.length;
86
- indexByKey.set(key, index);
87
- ops.push(op);
88
- if (isStructuralArrayOp(op))
89
- modifiedArrays.push(op.path);
90
- indices.push(index);
91
- }
92
- planned.push({ fix: candidate.fix, indices, deferred });
93
- }
94
- if (ops.length === 0)
95
- return { output: input, applied: [], changed: false };
96
- const { output, changed } = applyEditOpsWithChanges(input, format, ops);
97
- const applied = planned
98
- .filter((plan) => !plan.deferred && plan.indices.length > 0 && plan.indices.every((index) => changed[index]))
99
- .map((plan) => plan.fix);
100
- return { output, applied, changed: output !== input };
53
+ planned.push({ fix: candidate.fix, indices, deferred });
54
+ }
55
+ if (ops.length === 0)
56
+ return { output: input, applied: [], changed: false };
57
+ const { output, changed } = applyEditOpsWithChanges(input, format, ops);
58
+ const applied = planned.filter((plan) => !plan.deferred && plan.indices.length > 0 && plan.indices.every((index) => changed[index])).map((plan) => plan.fix);
59
+ return { output, applied, changed: output !== input };
60
+ };
61
+ export {
62
+ applyFixes
101
63
  };
package/dist/fix/index.js CHANGED
@@ -1,2 +1,7 @@
1
- export { applyFixes } from './apply.js';
2
- export { createFixPlugin, FIX_PLUGIN_NAME } from './plugin.js';
1
+ import { applyFixes } from "./apply.js";
2
+ import { createFixPlugin, FIX_PLUGIN_NAME } from "./plugin.js";
3
+ export {
4
+ FIX_PLUGIN_NAME,
5
+ applyFixes,
6
+ createFixPlugin
7
+ };
@@ -1,21 +1,16 @@
1
- import { applyFixes } from './apply.js';
2
- /** The name the fix plugin registers under (its `data` is surfaced here). */
3
- export const FIX_PLUGIN_NAME = 'fix';
4
- /**
5
- * Builds the auto-fix {@link LintPlugin} from a {@link FixerRegistry}. As a
6
- * post-lint plugin it reads the run's findings and the raw document text, applies
7
- * the fixers' edits, and returns the rewritten text as `output` plus the list of
8
- * repaired findings as `data`. The core engine stays unaware of fixing — remove
9
- * this plugin (and the `../fix` dependency) and Linter lints exactly as
10
- * before.
11
- */
12
- export const createFixPlugin = (fixers, options = {}) => ({
13
- name: FIX_PLUGIN_NAME,
14
- afterLint: (diagnostics, context) => {
15
- const result = applyFixes(context.input, context.format, context.document.data, diagnostics, fixers, options);
16
- if (!result.changed)
17
- return undefined;
18
- const data = { applied: result.applied };
19
- return { output: result.output, data };
20
- },
1
+ import { applyFixes } from "./apply.js";
2
+ const FIX_PLUGIN_NAME = "fix";
3
+ const createFixPlugin = (fixers, options = {}) => ({
4
+ name: FIX_PLUGIN_NAME,
5
+ afterLint: (diagnostics, context) => {
6
+ const result = applyFixes(context.input, context.format, context.document.data, diagnostics, fixers, options);
7
+ if (!result.changed)
8
+ return void 0;
9
+ const data = { applied: result.applied };
10
+ return { output: result.output, data };
11
+ }
21
12
  });
13
+ export {
14
+ FIX_PLUGIN_NAME,
15
+ createFixPlugin
16
+ };
@@ -1,55 +1,44 @@
1
- const isRecord = (value) => typeof value === 'object' && value !== null;
2
- const isStringOrNumber = (value) => typeof value === 'string' || typeof value === 'number';
3
- /** A string made up only of digits, e.g. an integer-like object key such as "10". */
4
- const isIntegerLike = (value) => typeof value === 'string' && /^(?:0|[1-9]\d*)$/.test(value);
1
+ const isRecord = (value) => typeof value === "object" && value !== null;
2
+ const isStringOrNumber = (value) => typeof value === "string" || typeof value === "number";
3
+ const isIntegerLike = (value) => typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value);
5
4
  const compare = (a, b) => {
6
- // Deliberate deviation from Spectral, which relies on source order and falls
7
- // back to `localeCompare`: JavaScript enumerates integer-like object keys in
8
- // ascending numeric order ({ "2": …, "10": }), yet `localeCompare` sorts
9
- // "10" before "2" and would flag that natural key order as a violation.
10
- // Comparing integer-like strings numerically avoids that false positive.
11
- if (isIntegerLike(a) && isIntegerLike(b))
12
- return Math.sign(Number(a) - Number(b));
13
- // Match Spectral: when either side is a real number or a numeric string,
14
- // compare numerically so mixed inputs like [2, "10"] read as ordered.
15
- if ((typeof a === 'number' || !Number.isNaN(Number(a))) && (typeof b === 'number' || !Number.isNaN(Number(b)))) {
16
- return Math.min(1, Math.max(-1, Number(a) - Number(b)));
17
- }
18
- if (typeof a !== 'string' || typeof b !== 'string')
19
- return 0;
20
- return a.localeCompare(b);
5
+ if (isIntegerLike(a) && isIntegerLike(b))
6
+ return Math.sign(Number(a) - Number(b));
7
+ if ((typeof a === "number" || !Number.isNaN(Number(a))) && (typeof b === "number" || !Number.isNaN(Number(b)))) {
8
+ return Math.min(1, Math.max(-1, Number(a) - Number(b)));
9
+ }
10
+ if (typeof a !== "string" || typeof b !== "string")
11
+ return 0;
12
+ return a.localeCompare(b);
21
13
  };
22
- /** Flags array items or object keys that are not in ascending (optionally `keyedBy`) order. */
23
- export const alphabetical = (input, options, context) => {
24
- if (typeof input !== 'object' || input === null)
25
- return [];
26
- const isArray = Array.isArray(input);
27
- const rawItems = isArray ? input : Object.keys(input);
28
- const keyedBy = options?.keyedBy;
29
- // Resolve the actual comparands. With `keyedBy` we read a property off each
30
- // item, which is only meaningful when every item is an object; otherwise the
31
- // comparison would silently run against `undefined`. Surface the same explicit
32
- // findings Spectral does instead of producing a misleading order violation.
33
- const items = [];
34
- for (const item of rawItems) {
35
- if (keyedBy) {
36
- if (!isRecord(item))
37
- return [{ message: 'The value must be an object' }];
38
- items.push(item[keyedBy]);
39
- }
40
- else {
41
- items.push(item);
42
- }
43
- }
44
- if (!items.every(isStringOrNumber)) {
45
- return [{ message: 'The value must be one of the allowed types: number, string' }];
14
+ const alphabetical = (input, options, context) => {
15
+ if (typeof input !== "object" || input === null)
16
+ return [];
17
+ const isArray = Array.isArray(input);
18
+ const rawItems = isArray ? input : Object.keys(input);
19
+ const keyedBy = options?.keyedBy;
20
+ const items = [];
21
+ for (const item of rawItems) {
22
+ if (keyedBy) {
23
+ if (!isRecord(item))
24
+ return [{ message: "The value must be an object" }];
25
+ items.push(item[keyedBy]);
26
+ } else {
27
+ items.push(item);
46
28
  }
47
- const results = [];
48
- for (let i = 0; i < items.length - 1; i++) {
49
- if (compare(items[i], items[i + 1]) > 0) {
50
- const path = isArray ? [...context.path, i + 1] : [...context.path, rawItems[i + 1]];
51
- results.push({ message: 'The items must be in alphabetical order', path });
52
- }
29
+ }
30
+ if (!items.every(isStringOrNumber)) {
31
+ return [{ message: "The value must be one of the allowed types: number, string" }];
32
+ }
33
+ const results = [];
34
+ for (let i = 0; i < items.length - 1; i++) {
35
+ if (compare(items[i], items[i + 1]) > 0) {
36
+ const path = isArray ? [...context.path, i + 1] : [...context.path, rawItems[i + 1]];
37
+ results.push({ message: "The items must be in alphabetical order", path });
53
38
  }
54
- return results;
39
+ }
40
+ return results;
41
+ };
42
+ export {
43
+ alphabetical
55
44
  };
@@ -1,52 +1,46 @@
1
1
  const PATTERNS = {
2
- flat: '[a-z][a-z{d}]*',
3
- camel: '[a-z][a-z{d}]*(?:[A-Z{d}](?:[a-z{d}]+|$))*',
4
- pascal: '[A-Z][a-z{d}]*(?:[A-Z{d}](?:[a-z{d}]+|$))*',
5
- // Segments after a separator may start with a digit, matching Spectral (so
6
- // "foo-2fa" is valid kebab case). The sub-pattern is `[a-z{d}]+`, not the
7
- // stricter `[a-z][a-z{d}]*` which would require a letter right after the sep.
8
- kebab: '[a-z][a-z{d}]*(?:-[a-z{d}]+)*',
9
- cobol: '[A-Z][A-Z{d}]*(?:-[A-Z{d}]+)*',
10
- snake: '[a-z][a-z{d}]*(?:_[a-z{d}]+)*',
11
- macro: '[A-Z][A-Z{d}]*(?:_[A-Z{d}]+)*',
2
+ flat: "[a-z][a-z{d}]*",
3
+ camel: "[a-z][a-z{d}]*(?:[A-Z{d}](?:[a-z{d}]+|$))*",
4
+ pascal: "[A-Z][a-z{d}]*(?:[A-Z{d}](?:[a-z{d}]+|$))*",
5
+ // Segments after a separator may start with a digit, matching Spectral (so
6
+ // "foo-2fa" is valid kebab case). The sub-pattern is `[a-z{d}]+`, not the
7
+ // stricter `[a-z][a-z{d}]*` which would require a letter right after the sep.
8
+ kebab: "[a-z][a-z{d}]*(?:-[a-z{d}]+)*",
9
+ cobol: "[A-Z][A-Z{d}]*(?:-[A-Z{d}]+)*",
10
+ snake: "[a-z][a-z{d}]*(?:_[a-z{d}]+)*",
11
+ macro: "[A-Z][A-Z{d}]*(?:_[A-Z{d}]+)*"
12
12
  };
13
13
  const VALID_TYPES = Object.keys(PATTERNS);
14
- const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
14
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
15
  const buildRegExp = (options) => {
16
- const digits = options.disallowDigits ? '' : '0-9';
17
- const base = PATTERNS[options.type].replace(/\{d\}/g, digits);
18
- if (!options.separator)
19
- return new RegExp(`^${base}$`);
20
- const sep = escapeRegExp(options.separator.char);
21
- const leading = options.separator.allowLeading ? `${sep}?` : '';
22
- return new RegExp(`^${leading}${base}(?:${sep}${base})*$`);
16
+ const digits = options.disallowDigits ? "" : "0-9";
17
+ const base = PATTERNS[options.type].replace(/\{d\}/g, digits);
18
+ if (!options.separator)
19
+ return new RegExp(`^${base}$`);
20
+ const sep = escapeRegExp(options.separator.char);
21
+ const leading = options.separator.allowLeading ? `${sep}?` : "";
22
+ return new RegExp(`^${leading}${base}(?:${sep}${base})*$`);
23
23
  };
24
- /** Flags a string that does not match the configured casing style. */
25
- export const casing = (input, options) => {
26
- if (!options?.type)
27
- return [];
28
- // Guard an unknown `type` before it reaches `PATTERNS[type]`, which would be
29
- // `undefined` and crash on `.replace`. Mirror Spectral's option schema by
30
- // naming every accepted value in a single, clear error finding.
31
- if (!VALID_TYPES.includes(options.type)) {
32
- return [
33
- {
34
- message: `"casing" function and its "type" option accept the following values: ${VALID_TYPES.join(', ')}`,
35
- },
36
- ];
37
- }
38
- if (typeof input !== 'string' || input.length === 0)
39
- return [];
40
- // Spectral special-cases a lone separator char with `allowLeading` as valid.
41
- // This is what keeps the OpenAPI root path "/" from being flagged.
42
- if (input.length === 1 &&
43
- options.separator !== undefined &&
44
- options.separator.allowLeading === true &&
45
- input === options.separator.char) {
46
- return [];
47
- }
48
- if (!buildRegExp(options).test(input)) {
49
- return [{ message: `The value must be in ${options.type} case` }];
50
- }
24
+ const casing = (input, options) => {
25
+ if (!options?.type)
51
26
  return [];
27
+ if (!VALID_TYPES.includes(options.type)) {
28
+ return [
29
+ {
30
+ message: `"casing" function and its "type" option accept the following values: ${VALID_TYPES.join(", ")}`
31
+ }
32
+ ];
33
+ }
34
+ if (typeof input !== "string" || input.length === 0)
35
+ return [];
36
+ if (input.length === 1 && options.separator !== void 0 && options.separator.allowLeading === true && input === options.separator.char) {
37
+ return [];
38
+ }
39
+ if (!buildRegExp(options).test(input)) {
40
+ return [{ message: `The value must be in ${options.type} case` }];
41
+ }
42
+ return [];
43
+ };
44
+ export {
45
+ casing
52
46
  };