@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.
- package/AI.md +55 -0
- package/dist/core/document.js +12 -10
- package/dist/core/formats.js +10 -13
- package/dist/core/glob.js +96 -118
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +30 -11
- package/dist/core/jsonpath.js +487 -561
- package/dist/core/lint.js +78 -85
- package/dist/core/plugin.js +21 -29
- package/dist/core/pointers.js +107 -151
- package/dist/core/ruleset.js +0 -0
- package/dist/core/runner.js +214 -272
- package/dist/core/types.js +4 -1
- package/dist/core/validate-ruleset.js +98 -112
- package/dist/fix/apply.d.ts +1 -1
- package/dist/fix/apply.js +58 -96
- package/dist/fix/index.js +7 -2
- package/dist/fix/plugin.js +15 -20
- package/dist/fix/types.d.ts +1 -1
- package/dist/functions/alphabetical.d.ts +1 -1
- package/dist/functions/alphabetical.js +39 -50
- package/dist/functions/casing.d.ts +1 -1
- package/dist/functions/casing.js +39 -45
- package/dist/functions/defined.d.ts +1 -1
- package/dist/functions/defined.js +7 -5
- package/dist/functions/enumeration.d.ts +1 -1
- package/dist/functions/enumeration.js +15 -25
- package/dist/functions/falsy.d.ts +1 -1
- package/dist/functions/falsy.js +7 -5
- package/dist/functions/index.d.ts +1 -1
- package/dist/functions/index.js +60 -44
- package/dist/functions/length.d.ts +1 -1
- package/dist/functions/length.js +22 -32
- package/dist/functions/or.d.ts +1 -1
- package/dist/functions/or.js +18 -24
- package/dist/functions/pattern.d.ts +1 -1
- package/dist/functions/pattern.js +39 -49
- package/dist/functions/schema.d.ts +1 -1
- package/dist/functions/schema.js +93 -119
- package/dist/functions/truthy.d.ts +1 -1
- package/dist/functions/truthy.js +7 -5
- package/dist/functions/typed-enum.d.ts +1 -1
- package/dist/functions/typed-enum.js +33 -36
- package/dist/functions/undefined.d.ts +1 -1
- package/dist/functions/undefined.js +7 -8
- package/dist/functions/unreferenced-reusable-object.d.ts +1 -1
- package/dist/functions/unreferenced-reusable-object.js +35 -47
- package/dist/functions/xor.d.ts +1 -1
- package/dist/functions/xor.js +13 -16
- package/dist/index.d.ts +17 -4
- package/dist/index.js +149 -164
- package/dist/parsers/edit-model.js +316 -444
- package/dist/parsers/index.d.ts +1 -1
- package/dist/parsers/index.js +23 -19
- package/dist/parsers/json.js +39 -37
- package/dist/parsers/lines.js +23 -26
- package/dist/parsers/types.js +9 -7
- package/dist/parsers/yaml.js +137 -204
- package/dist/rules/openapi/fixers.js +166 -221
- package/dist/rules/openapi/formats.js +26 -27
- package/dist/rules/openapi/functions/example-validation.d.ts +1 -1
- package/dist/rules/openapi/functions/example-validation.js +98 -138
- package/dist/rules/openapi/functions/helpers.js +8 -10
- package/dist/rules/openapi/functions/index.d.ts +1 -1
- package/dist/rules/openapi/functions/index.js +98 -72
- package/dist/rules/openapi/functions/oas-additional-operations.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
- package/dist/rules/openapi/functions/oas-discriminator.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
- package/dist/rules/openapi/functions/oas-example-external-value.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
- package/dist/rules/openapi/functions/oas-example-value.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-example-value.js +24 -27
- package/dist/rules/openapi/functions/oas-mutually-exclusive.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
- package/dist/rules/openapi/functions/oas-no-nullable.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
- package/dist/rules/openapi/functions/oas-op-form-data-consume-check.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
- package/dist/rules/openapi/functions/oas-op-id-unique.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
- package/dist/rules/openapi/functions/oas-op-params.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-params.js +36 -42
- package/dist/rules/openapi/functions/oas-op-security-defined.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
- package/dist/rules/openapi/functions/oas-op-success-response.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
- package/dist/rules/openapi/functions/oas-path-param.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-path-param.js +75 -96
- package/dist/rules/openapi/functions/oas-schema-example-deprecated.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
- package/dist/rules/openapi/functions/oas-schema.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-schema.js +9 -14
- package/dist/rules/openapi/functions/oas-server-name-unique.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
- package/dist/rules/openapi/functions/oas-server-variables.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
- package/dist/rules/openapi/functions/oas-tag-defined.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
- package/dist/rules/openapi/functions/oas-tag-kind.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
- package/dist/rules/openapi/functions/oas-tag-parent-defined.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
- package/dist/rules/openapi/functions/oas-tags-unique.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
- package/dist/rules/openapi/functions/oas-unused-component.d.ts +1 -1
- package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
- package/dist/rules/openapi/functions/ref-siblings.d.ts +1 -1
- package/dist/rules/openapi/functions/ref-siblings.js +13 -11
- package/dist/rules/openapi/index.d.ts +2 -1
- package/dist/rules/openapi/index.js +99 -117
- package/dist/rules/openapi/oas.d.ts +1 -1
- package/dist/rules/openapi/oas.js +524 -536
- package/dist/rules/openapi/schemas/index.js +17 -33
- package/dist/rules/openapi/schemas/oas20.json +1 -1592
- package/dist/rules/openapi/schemas/oas30.json +1 -1651
- package/dist/rules/openapi/schemas/oas31.json +1 -1412
- package/dist/rules/openapi/schemas/oas32.json +1 -1684
- package/package.json +11 -6
package/dist/core/runner.js
CHANGED
|
@@ -1,306 +1,248 @@
|
|
|
1
|
-
import { detectFormats } from
|
|
2
|
-
import { matchesGlob } from
|
|
3
|
-
import { compileQuery, query, queryMany } from
|
|
4
|
-
import { pointerToPath, resolveSourcePath } from
|
|
5
|
-
import { DiagnosticSeverity
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 ===
|
|
14
|
-
const isThenable = (value) => typeof value?.then ===
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
};
|
package/dist/core/types.js
CHANGED