@amritk/lint 0.3.2 → 0.4.0

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 (119) hide show
  1. package/AI.md +55 -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.d.ts +0 -1
  6. package/dist/core/index.js +30 -11
  7. package/dist/core/jsonpath.js +487 -561
  8. package/dist/core/lint.js +78 -85
  9. package/dist/core/plugin.js +21 -29
  10. package/dist/core/pointers.js +107 -151
  11. package/dist/core/ruleset.js +0 -0
  12. package/dist/core/runner.js +214 -272
  13. package/dist/core/types.js +4 -1
  14. package/dist/core/validate-ruleset.js +98 -112
  15. package/dist/fix/apply.d.ts +1 -1
  16. package/dist/fix/apply.js +58 -96
  17. package/dist/fix/index.js +7 -2
  18. package/dist/fix/plugin.js +15 -20
  19. package/dist/fix/types.d.ts +1 -1
  20. package/dist/functions/alphabetical.d.ts +1 -1
  21. package/dist/functions/alphabetical.js +39 -50
  22. package/dist/functions/casing.d.ts +1 -1
  23. package/dist/functions/casing.js +39 -45
  24. package/dist/functions/defined.d.ts +1 -1
  25. package/dist/functions/defined.js +7 -5
  26. package/dist/functions/enumeration.d.ts +1 -1
  27. package/dist/functions/enumeration.js +15 -25
  28. package/dist/functions/falsy.d.ts +1 -1
  29. package/dist/functions/falsy.js +7 -5
  30. package/dist/functions/index.d.ts +1 -1
  31. package/dist/functions/index.js +60 -44
  32. package/dist/functions/length.d.ts +1 -1
  33. package/dist/functions/length.js +22 -32
  34. package/dist/functions/or.d.ts +1 -1
  35. package/dist/functions/or.js +18 -24
  36. package/dist/functions/pattern.d.ts +1 -1
  37. package/dist/functions/pattern.js +39 -49
  38. package/dist/functions/schema.d.ts +1 -1
  39. package/dist/functions/schema.js +93 -119
  40. package/dist/functions/truthy.d.ts +1 -1
  41. package/dist/functions/truthy.js +7 -5
  42. package/dist/functions/typed-enum.d.ts +1 -1
  43. package/dist/functions/typed-enum.js +33 -36
  44. package/dist/functions/undefined.d.ts +1 -1
  45. package/dist/functions/undefined.js +7 -8
  46. package/dist/functions/unreferenced-reusable-object.d.ts +1 -1
  47. package/dist/functions/unreferenced-reusable-object.js +35 -47
  48. package/dist/functions/xor.d.ts +1 -1
  49. package/dist/functions/xor.js +13 -16
  50. package/dist/index.d.ts +17 -4
  51. package/dist/index.js +149 -164
  52. package/dist/parsers/edit-model.js +316 -444
  53. package/dist/parsers/index.d.ts +1 -1
  54. package/dist/parsers/index.js +23 -19
  55. package/dist/parsers/json.js +39 -37
  56. package/dist/parsers/lines.js +23 -26
  57. package/dist/parsers/types.js +9 -7
  58. package/dist/parsers/yaml.js +137 -204
  59. package/dist/rules/openapi/fixers.js +166 -221
  60. package/dist/rules/openapi/formats.js +26 -27
  61. package/dist/rules/openapi/functions/example-validation.d.ts +1 -1
  62. package/dist/rules/openapi/functions/example-validation.js +98 -138
  63. package/dist/rules/openapi/functions/helpers.js +8 -10
  64. package/dist/rules/openapi/functions/index.d.ts +1 -1
  65. package/dist/rules/openapi/functions/index.js +98 -72
  66. package/dist/rules/openapi/functions/oas-additional-operations.d.ts +1 -1
  67. package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
  68. package/dist/rules/openapi/functions/oas-discriminator.d.ts +1 -1
  69. package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
  70. package/dist/rules/openapi/functions/oas-example-external-value.d.ts +1 -1
  71. package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
  72. package/dist/rules/openapi/functions/oas-example-value.d.ts +1 -1
  73. package/dist/rules/openapi/functions/oas-example-value.js +24 -27
  74. package/dist/rules/openapi/functions/oas-mutually-exclusive.d.ts +1 -1
  75. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
  76. package/dist/rules/openapi/functions/oas-no-nullable.d.ts +1 -1
  77. package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
  78. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.d.ts +1 -1
  79. package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
  80. package/dist/rules/openapi/functions/oas-op-id-unique.d.ts +1 -1
  81. package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
  82. package/dist/rules/openapi/functions/oas-op-params.d.ts +1 -1
  83. package/dist/rules/openapi/functions/oas-op-params.js +36 -42
  84. package/dist/rules/openapi/functions/oas-op-security-defined.d.ts +1 -1
  85. package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
  86. package/dist/rules/openapi/functions/oas-op-success-response.d.ts +1 -1
  87. package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
  88. package/dist/rules/openapi/functions/oas-path-param.d.ts +1 -1
  89. package/dist/rules/openapi/functions/oas-path-param.js +75 -96
  90. package/dist/rules/openapi/functions/oas-schema-example-deprecated.d.ts +1 -1
  91. package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
  92. package/dist/rules/openapi/functions/oas-schema.d.ts +1 -1
  93. package/dist/rules/openapi/functions/oas-schema.js +9 -14
  94. package/dist/rules/openapi/functions/oas-server-name-unique.d.ts +1 -1
  95. package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
  96. package/dist/rules/openapi/functions/oas-server-variables.d.ts +1 -1
  97. package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
  98. package/dist/rules/openapi/functions/oas-tag-defined.d.ts +1 -1
  99. package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
  100. package/dist/rules/openapi/functions/oas-tag-kind.d.ts +1 -1
  101. package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
  102. package/dist/rules/openapi/functions/oas-tag-parent-defined.d.ts +1 -1
  103. package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
  104. package/dist/rules/openapi/functions/oas-tags-unique.d.ts +1 -1
  105. package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
  106. package/dist/rules/openapi/functions/oas-unused-component.d.ts +1 -1
  107. package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
  108. package/dist/rules/openapi/functions/ref-siblings.d.ts +1 -1
  109. package/dist/rules/openapi/functions/ref-siblings.js +13 -11
  110. package/dist/rules/openapi/index.d.ts +2 -1
  111. package/dist/rules/openapi/index.js +99 -117
  112. package/dist/rules/openapi/oas.d.ts +1 -1
  113. package/dist/rules/openapi/oas.js +524 -536
  114. package/dist/rules/openapi/schemas/index.js +17 -33
  115. package/dist/rules/openapi/schemas/oas20.json +1 -1592
  116. package/dist/rules/openapi/schemas/oas30.json +1 -1651
  117. package/dist/rules/openapi/schemas/oas31.json +1 -1412
  118. package/dist/rules/openapi/schemas/oas32.json +1 -1684
  119. package/package.json +11 -6
@@ -1,306 +1,248 @@
1
- import { detectFormats } from './formats.js';
2
- import { matchesGlob } from './glob.js';
3
- import { compileQuery, query, queryMany } from './jsonpath.js';
4
- import { pointerToPath, resolveSourcePath } from './pointers.js';
5
- import { DiagnosticSeverity, } from './types.js';
1
+ import { detectFormats } from "./formats.js";
2
+ import { matchesGlob } from "./glob.js";
3
+ import { compileQuery, query, queryMany } from "./jsonpath.js";
4
+ import { pointerToPath, resolveSourcePath } from "./pointers.js";
5
+ import { DiagnosticSeverity } from "./types.js";
6
6
  const SEVERITY_NAMES = {
7
- error: DiagnosticSeverity.Error,
8
- warn: DiagnosticSeverity.Warning,
9
- info: DiagnosticSeverity.Information,
10
- hint: DiagnosticSeverity.Hint,
7
+ error: DiagnosticSeverity.Error,
8
+ warn: DiagnosticSeverity.Warning,
9
+ info: DiagnosticSeverity.Information,
10
+ hint: DiagnosticSeverity.Hint
11
11
  };
12
12
  const ZERO_RANGE = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
13
- const isIndexable = (value) => typeof value === 'object' && value !== null;
14
- const isThenable = (value) => typeof value?.then === 'function';
15
- /**
16
- * Narrows a matched value to the nodes a `then` runs against, mirroring
17
- * Spectral's `getLintTargets`. Both objects and arrays are indexable containers,
18
- * so `@key` yields keys/indices and a numeric field indexes into an array; a
19
- * field against a primitive lints the matched value itself; a `$`-prefixed field
20
- * runs as a sub-query.
21
- */
13
+ const isIndexable = (value) => typeof value === "object" && value !== null;
14
+ const isThenable = (value) => typeof value?.then === "function";
22
15
  const resolveTargets = (value, path, field) => {
23
- if (field === undefined)
24
- return [{ value, path }];
25
- // A field only narrows into a container; against a primitive (or null) the
26
- // matched value itself is the target.
27
- if (!isIndexable(value))
28
- return [{ value, path }];
29
- if (field === '@key') {
30
- // Object.keys over an array yields its indices (as strings); normalize those
31
- // back to numbers for the path so lookups stay consistent.
32
- return Object.keys(value).map((key) => ({
33
- value: key,
34
- path: [...path, Array.isArray(value) ? Number(key) : key],
35
- }));
36
- }
37
- if (field.startsWith('$')) {
38
- return query(value, field).map((match) => ({ value: match.value, path: [...path, ...match.path] }));
39
- }
40
- // A plain property name, or a numeric index into an array.
41
- const key = Array.isArray(value) && /^\d+$/.test(field) ? Number(field) : field;
42
- return [{ value: value[key], path: [...path, key] }];
16
+ if (field === void 0)
17
+ return [{ value, path }];
18
+ if (!isIndexable(value))
19
+ return [{ value, path }];
20
+ if (field === "@key") {
21
+ return Object.keys(value).map((key2) => ({
22
+ value: key2,
23
+ path: [...path, Array.isArray(value) ? Number(key2) : key2]
24
+ }));
25
+ }
26
+ if (field.startsWith("$")) {
27
+ return query(value, field).map((match) => ({ value: match.value, path: [...path, ...match.path] }));
28
+ }
29
+ const key = Array.isArray(value) && /^\d+$/.test(field) ? Number(field) : field;
30
+ return [{ value: value[key], path: [...path, key] }];
43
31
  };
44
32
  const stringify = (value) => {
45
- if (value === undefined)
46
- return '';
47
- if (typeof value === 'string')
48
- return value;
49
- return JSON.stringify(value);
33
+ if (value === void 0)
34
+ return "";
35
+ if (typeof value === "string")
36
+ return value;
37
+ return JSON.stringify(value);
50
38
  };
51
39
  const applyTemplate = (template, ctx) => template.replace(/\{\{([^}]+)\}\}/g, (_match, raw) => {
52
- const key = raw.trim();
53
- return key in ctx ? stringify(ctx[key]) : '';
40
+ const key = raw.trim();
41
+ return key in ctx ? stringify(ctx[key]) : "";
54
42
  });
55
- /** Resolves where a finding's `path` maps to in the source: its range and originating `source`. */
56
43
  const locate = (rule, path, document, sources) => {
57
- // A resolved finding may sit on a node inlined from another file. With a source
58
- // set, follow the `$ref` chain across documents so the range and `source` point
59
- // at the originating file; otherwise fall back to the root document, following
60
- // internal `$ref`s only.
61
- let originDocument = document;
62
- let sourcePath;
63
- if (rule.resolved && sources) {
64
- const origin = sources.origin(path);
65
- originDocument = sources.get(origin.location) ?? document;
66
- sourcePath = origin.path;
67
- }
68
- else {
69
- sourcePath = rule.resolved ? resolveSourcePath(document.data, path) : path;
70
- }
71
- const location = originDocument.getLocationForJsonPath(sourcePath, true);
72
- return { range: location?.range ?? ZERO_RANGE, source: originDocument.source };
44
+ let originDocument = document;
45
+ let sourcePath;
46
+ if (rule.resolved && sources) {
47
+ const origin = sources.origin(path);
48
+ originDocument = sources.get(origin.location) ?? document;
49
+ sourcePath = origin.path;
50
+ } else {
51
+ sourcePath = rule.resolved ? resolveSourcePath(document.data, path) : path;
52
+ }
53
+ const location = originDocument.getLocationForJsonPath(sourcePath, true);
54
+ return { range: location?.range ?? ZERO_RANGE, source: originDocument.source };
73
55
  };
74
- /** Builds an error-severity diagnostic (a failed rule function or an unknown-function reference). */
75
56
  const errorDiagnostic = (rule, path, message, range, source) => {
76
- const diagnostic = {
77
- code: rule.name,
78
- message,
79
- path,
80
- severity: DiagnosticSeverity.Error,
81
- range,
82
- };
83
- if (source !== undefined)
84
- diagnostic.source = source;
85
- return diagnostic;
57
+ const diagnostic = {
58
+ code: rule.name,
59
+ message,
60
+ path,
61
+ severity: DiagnosticSeverity.Error,
62
+ range
63
+ };
64
+ if (source !== void 0)
65
+ diagnostic.source = source;
66
+ return diagnostic;
86
67
  };
87
- /** Returns the name of the first `then` function this rule references that is not registered. */
88
68
  const missingFunction = (ruleset, rule) => {
89
- for (const then of rule.then) {
90
- if (typeof then?.function === 'string' && !ruleset.getFunction(then.function))
91
- return then.function;
92
- }
93
- return undefined;
69
+ for (const then of rule.then) {
70
+ if (typeof then?.function === "string" && !ruleset.getFunction(then.function))
71
+ return then.function;
72
+ }
73
+ return void 0;
94
74
  };
95
- /**
96
- * Builds a deduped query plan for `rules` against `data`: each distinct
97
- * (expanded) `given` is compiled and evaluated once, then its matches fan out to
98
- * every rule that shares it. Recursive descents are evaluated with a single
99
- * shared tree walk inside `queryMany`.
100
- *
101
- * A rule that references an unknown named function is reported once (not per
102
- * node) and skipped; a rule function that throws (or rejects) becomes an
103
- * error-severity diagnostic on that node so one bad rule cannot abort the run.
104
- */
105
75
  const runPlan = async (ruleset, rules, data, document, formats, out, sources) => {
106
- if (rules.length === 0)
107
- return;
108
- const order = [];
109
- const groups = new Map();
110
- for (const rule of rules) {
111
- // Surface an unknown-function reference once for the whole rule, then skip it
112
- // rather than throwing mid-run at the first matched node.
113
- const missing = missingFunction(ruleset, rule);
114
- if (missing !== undefined) {
115
- out.push(errorDiagnostic(rule, [], `Rule "${rule.name}" references unknown function "${missing}"`, ZERO_RANGE, document.source));
116
- continue;
117
- }
118
- for (const given of ruleset.expandGiven(rule.given, formats)) {
119
- let group = groups.get(given);
120
- if (!group) {
121
- group = { compiled: compileQuery(given), rules: [] };
122
- groups.set(given, group);
123
- order.push(given);
124
- }
125
- // Guard against a rule listing the same `given` twice.
126
- if (group.rules[group.rules.length - 1] !== rule)
127
- group.rules.push(rule);
128
- }
76
+ if (rules.length === 0)
77
+ return;
78
+ const order = [];
79
+ const groups = /* @__PURE__ */ new Map();
80
+ for (const rule of rules) {
81
+ const missing = missingFunction(ruleset, rule);
82
+ if (missing !== void 0) {
83
+ out.push(errorDiagnostic(rule, [], `Rule "${rule.name}" references unknown function "${missing}"`, ZERO_RANGE, document.source));
84
+ continue;
85
+ }
86
+ for (const given of ruleset.expandGiven(rule.given, formats)) {
87
+ let group = groups.get(given);
88
+ if (!group) {
89
+ group = { compiled: compileQuery(given), rules: [] };
90
+ groups.set(given, group);
91
+ order.push(given);
92
+ }
93
+ if (group.rules[group.rules.length - 1] !== rule)
94
+ group.rules.push(rule);
129
95
  }
130
- const compiled = order.map((given) => groups.get(given).compiled);
131
- const matchesPerGiven = queryMany(data, compiled);
132
- for (let i = 0; i < order.length; i++) {
133
- const group = groups.get(order[i]);
134
- const matches = matchesPerGiven[i];
135
- for (const match of matches) {
136
- for (const rule of group.rules) {
137
- for (const then of rule.then) {
138
- await runThen(ruleset, rule, then, match.value, match.path, document, out, sources);
139
- }
140
- }
96
+ }
97
+ const compiled = order.map((given) => groups.get(given).compiled);
98
+ const matchesPerGiven = queryMany(data, compiled);
99
+ for (let i = 0; i < order.length; i++) {
100
+ const group = groups.get(order[i]);
101
+ const matches = matchesPerGiven[i];
102
+ for (const match of matches) {
103
+ for (const rule of group.rules) {
104
+ for (const then of rule.then) {
105
+ await runThen(ruleset, rule, then, match.value, match.path, document, out, sources);
141
106
  }
107
+ }
142
108
  }
109
+ }
143
110
  };
144
- /** Applies `overrides` whose `files` entry includes a JSON pointer scope (`file#/path`). */
145
111
  const applyScopedOverrides = (ruleset, source, diagnostics) => {
146
- if (!source)
147
- return diagnostics;
148
- const scoped = [];
149
- for (const override of ruleset.overrides) {
150
- if (!override.rules)
151
- continue;
152
- for (const file of override.files) {
153
- const hashIndex = file.indexOf('#');
154
- if (hashIndex === -1)
155
- continue;
156
- const glob = file.slice(0, hashIndex);
157
- const path = pointerToPath(file.slice(hashIndex));
158
- if (path && (glob === '' || matchesGlob(source, [glob]))) {
159
- scoped.push({ path, rules: override.rules });
160
- }
161
- }
112
+ if (!source)
113
+ return diagnostics;
114
+ const scoped = [];
115
+ for (const override of ruleset.overrides) {
116
+ if (!override.rules)
117
+ continue;
118
+ for (const file of override.files) {
119
+ const hashIndex = file.indexOf("#");
120
+ if (hashIndex === -1)
121
+ continue;
122
+ const glob = file.slice(0, hashIndex);
123
+ const path = pointerToPath(file.slice(hashIndex));
124
+ if (path && (glob === "" || matchesGlob(source, [glob]))) {
125
+ scoped.push({ path, rules: override.rules });
126
+ }
162
127
  }
163
- if (scoped.length === 0)
164
- return diagnostics;
165
- const isPrefix = (prefix, path) => prefix.every((segment, index) => String(path[index]) === String(segment));
166
- // Note: a pointer-scoped override can *disable* (`off`/`false`) or *remap the
167
- // severity* of a finding under its path, but it cannot *enable* a rule — it
168
- // filters/adjusts findings that were already produced, so a rule turned off
169
- // elsewhere has no finding here to switch back on.
170
- const result = [];
171
- for (const diagnostic of diagnostics) {
172
- let dropped = false;
173
- for (const { path, rules } of scoped) {
174
- if (!isPrefix(path, diagnostic.path))
175
- continue;
176
- const entry = rules[String(diagnostic.code)];
177
- if (entry === undefined)
178
- continue;
179
- if (entry === false || entry === 'off') {
180
- dropped = true;
181
- break;
182
- }
183
- if (typeof entry === 'number')
184
- diagnostic.severity = entry;
185
- else if (typeof entry === 'string' && entry in SEVERITY_NAMES) {
186
- diagnostic.severity = SEVERITY_NAMES[entry];
187
- }
188
- }
189
- if (!dropped)
190
- result.push(diagnostic);
128
+ }
129
+ if (scoped.length === 0)
130
+ return diagnostics;
131
+ const isPrefix = (prefix, path) => prefix.every((segment, index) => String(path[index]) === String(segment));
132
+ const result = [];
133
+ for (const diagnostic of diagnostics) {
134
+ let dropped = false;
135
+ for (const { path, rules } of scoped) {
136
+ if (!isPrefix(path, diagnostic.path))
137
+ continue;
138
+ const entry = rules[String(diagnostic.code)];
139
+ if (entry === void 0)
140
+ continue;
141
+ if (entry === false || entry === "off") {
142
+ dropped = true;
143
+ break;
144
+ }
145
+ if (typeof entry === "number")
146
+ diagnostic.severity = entry;
147
+ else if (typeof entry === "string" && entry in SEVERITY_NAMES) {
148
+ diagnostic.severity = SEVERITY_NAMES[entry];
149
+ }
191
150
  }
192
- return result;
151
+ if (!dropped)
152
+ result.push(diagnostic);
153
+ }
154
+ return result;
193
155
  };
194
156
  const runThen = async (ruleset, rule, then, value, path, document, out, sources) => {
195
- // A malformed rule (e.g. a `then` with no `function`, which `validateRuleset`
196
- // warns about) is skipped rather than crashing the whole run.
197
- if (!then || (typeof then.function !== 'function' && typeof then.function !== 'string'))
198
- return;
199
- const fn = typeof then.function === 'function' ? then.function : ruleset.getFunction(then.function);
200
- // Unknown named functions are handled once per rule in `runPlan`; nothing to do.
201
- if (!fn)
202
- return;
203
- for (const target of resolveTargets(value, path, then.field)) {
204
- let results;
205
- try {
206
- // The declared signature is synchronous, but a function reference may in
207
- // fact return a Promise (a Spectral-style async function), so treat the raw
208
- // result as unknown and await it when it is thenable.
209
- const raw = fn(target.value, then.functionOptions ?? {}, {
210
- document,
211
- path: target.path,
212
- value: target.value,
213
- rule,
214
- functionOptions: then.functionOptions,
215
- });
216
- results = (isThenable(raw) ? await raw : raw);
217
- }
218
- catch (error) {
219
- // Isolate the failure: convert it into an error diagnostic on this node so
220
- // the rest of the run (and already-collected findings) survive.
221
- const { range, source } = locate(rule, target.path, document, sources);
222
- const reason = error instanceof Error ? error.message : String(error);
223
- out.push(errorDiagnostic(rule, target.path, `Rule "${rule.name}" threw: ${reason}`, range, source));
224
- continue;
225
- }
226
- if (!results)
227
- continue;
228
- for (const result of results) {
229
- out.push(toDiagnostic(rule, result, target, document, sources));
230
- }
157
+ if (!then || typeof then.function !== "function" && typeof then.function !== "string")
158
+ return;
159
+ const fn = typeof then.function === "function" ? then.function : ruleset.getFunction(then.function);
160
+ if (!fn)
161
+ return;
162
+ for (const target of resolveTargets(value, path, then.field)) {
163
+ let results;
164
+ try {
165
+ const raw = fn(target.value, then.functionOptions ?? {}, {
166
+ document,
167
+ path: target.path,
168
+ value: target.value,
169
+ rule,
170
+ functionOptions: then.functionOptions
171
+ });
172
+ results = isThenable(raw) ? await raw : raw;
173
+ } catch (error) {
174
+ const { range, source } = locate(rule, target.path, document, sources);
175
+ const reason = error instanceof Error ? error.message : String(error);
176
+ out.push(errorDiagnostic(rule, target.path, `Rule "${rule.name}" threw: ${reason}`, range, source));
177
+ continue;
231
178
  }
179
+ if (!results)
180
+ continue;
181
+ for (const result of results) {
182
+ out.push(toDiagnostic(rule, result, target, document, sources));
183
+ }
184
+ }
232
185
  };
233
186
  const toDiagnostic = (rule, result, target, document, sources) => {
234
- const path = result.path ?? target.path;
235
- const property = path.length > 0 ? path[path.length - 1] : undefined;
236
- const message = rule.message
237
- ? applyTemplate(rule.message, {
238
- property,
239
- value: target.value,
240
- path: path.join('.'),
241
- error: result.message,
242
- description: rule.description ?? '',
243
- })
244
- : result.message;
245
- const { range, source } = locate(rule, path, document, sources);
246
- const diagnostic = {
247
- code: rule.name,
248
- message,
249
- path,
250
- severity: rule.severity,
251
- range,
252
- };
253
- if (source !== undefined)
254
- diagnostic.source = source;
255
- return diagnostic;
187
+ const path = result.path ?? target.path;
188
+ const property = path.length > 0 ? path[path.length - 1] : void 0;
189
+ const message = rule.message ? applyTemplate(rule.message, {
190
+ property,
191
+ value: target.value,
192
+ path: path.join("."),
193
+ error: result.message,
194
+ description: rule.description ?? ""
195
+ }) : result.message;
196
+ const { range, source } = locate(rule, path, document, sources);
197
+ const diagnostic = {
198
+ code: rule.name,
199
+ message,
200
+ path,
201
+ severity: rule.severity,
202
+ range
203
+ };
204
+ if (source !== void 0)
205
+ diagnostic.source = source;
206
+ return diagnostic;
256
207
  };
257
208
  const hasIntersection = (a, b) => {
258
- for (const value of a) {
259
- if (b.has(value))
260
- return true;
261
- }
262
- return false;
209
+ for (const value of a) {
210
+ if (b.has(value))
211
+ return true;
212
+ }
213
+ return false;
263
214
  };
264
- /**
265
- * Orders findings by source, then line, then character. Sorting by position
266
- * alone interleaves findings from different files once a run spans multiple
267
- * sources; grouping by `source` first keeps each file's findings contiguous.
268
- */
269
215
  const bySourceThenPosition = (a, b) => {
270
- const sa = a.source ?? '';
271
- const sb = b.source ?? '';
272
- if (sa !== sb)
273
- return sa < sb ? -1 : 1;
274
- return a.range.start.line - b.range.start.line || a.range.start.character - b.range.start.character;
216
+ const sa = a.source ?? "";
217
+ const sb = b.source ?? "";
218
+ if (sa !== sb)
219
+ return sa < sb ? -1 : 1;
220
+ return a.range.start.line - b.range.start.line || a.range.start.character - b.range.start.character;
275
221
  };
276
- /** Creates a {@link Linter} runner bound to a normalized `ruleset`. */
277
- export const createLinter = (ruleset) => ({
278
- run: async (document, options = {}) => {
279
- const documentFormats = detectFormats(document.data, ruleset.formats);
280
- const diagnostics = [];
281
- // Rules split by which document they run against (resolved vs raw). Each
282
- // dataset is linted with a single deduped query plan so that identical
283
- // `given`s evaluate once and all `$..` descents share one tree walk.
284
- const resolvedAvailable = options.resolved !== undefined;
285
- const rawRules = [];
286
- const resolvedRules = [];
287
- for (const rule of ruleset.rulesForSource(document.source)) {
288
- if (!rule.enabled)
289
- continue;
290
- if (rule.formats && !hasIntersection(rule.formats, documentFormats))
291
- continue;
292
- if (rule.resolved && resolvedAvailable)
293
- resolvedRules.push(rule);
294
- else
295
- rawRules.push(rule);
296
- }
297
- // Raw rules run against the root's unresolved tree, so positions always come
298
- // straight from the root document; only resolved rules can land on inlined
299
- // external nodes and need the source set.
300
- await runPlan(ruleset, rawRules, document.data, document, documentFormats, diagnostics, undefined);
301
- if (resolvedRules.length > 0) {
302
- await runPlan(ruleset, resolvedRules, options.resolved, document, documentFormats, diagnostics, options.sources);
303
- }
304
- return applyScopedOverrides(ruleset, document.source, diagnostics).sort(bySourceThenPosition);
305
- },
222
+ const createLinter = (ruleset) => ({
223
+ run: async (document, options = {}) => {
224
+ const documentFormats = detectFormats(document.data, ruleset.formats);
225
+ const diagnostics = [];
226
+ const resolvedAvailable = options.resolved !== void 0;
227
+ const rawRules = [];
228
+ const resolvedRules = [];
229
+ for (const rule of ruleset.rulesForSource(document.source)) {
230
+ if (!rule.enabled)
231
+ continue;
232
+ if (rule.formats && !hasIntersection(rule.formats, documentFormats))
233
+ continue;
234
+ if (rule.resolved && resolvedAvailable)
235
+ resolvedRules.push(rule);
236
+ else
237
+ rawRules.push(rule);
238
+ }
239
+ await runPlan(ruleset, rawRules, document.data, document, documentFormats, diagnostics, void 0);
240
+ if (resolvedRules.length > 0) {
241
+ await runPlan(ruleset, resolvedRules, options.resolved, document, documentFormats, diagnostics, options.sources);
242
+ }
243
+ return applyScopedOverrides(ruleset, document.source, diagnostics).sort(bySourceThenPosition);
244
+ }
306
245
  });
246
+ export {
247
+ createLinter
248
+ };
@@ -1 +1,4 @@
1
- export { DiagnosticSeverity } from '../parsers/index.js';
1
+ import { DiagnosticSeverity } from "../parsers/index.js";
2
+ export {
3
+ DiagnosticSeverity
4
+ };