@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.
- package/LICENSE +21 -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.js +31 -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.js +58 -96
- package/dist/fix/index.js +7 -2
- package/dist/fix/plugin.js +15 -20
- package/dist/functions/alphabetical.js +39 -50
- package/dist/functions/casing.js +39 -45
- package/dist/functions/defined.js +7 -5
- package/dist/functions/enumeration.js +15 -25
- package/dist/functions/falsy.js +7 -5
- package/dist/functions/index.js +60 -44
- package/dist/functions/length.js +22 -32
- package/dist/functions/or.js +18 -24
- package/dist/functions/pattern.js +39 -49
- package/dist/functions/schema.js +93 -119
- package/dist/functions/truthy.js +7 -5
- package/dist/functions/typed-enum.js +33 -36
- package/dist/functions/undefined.js +7 -8
- package/dist/functions/unreferenced-reusable-object.js +35 -47
- package/dist/functions/xor.js +13 -16
- package/dist/index.js +114 -164
- package/dist/parsers/edit-model.js +316 -444
- package/dist/parsers/index.js +22 -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.js +98 -138
- package/dist/rules/openapi/functions/helpers.js +8 -10
- package/dist/rules/openapi/functions/index.js +98 -72
- package/dist/rules/openapi/functions/oas-additional-operations.js +16 -22
- package/dist/rules/openapi/functions/oas-discriminator.js +24 -22
- package/dist/rules/openapi/functions/oas-example-external-value.js +13 -21
- package/dist/rules/openapi/functions/oas-example-value.js +24 -27
- package/dist/rules/openapi/functions/oas-mutually-exclusive.js +15 -19
- package/dist/rules/openapi/functions/oas-no-nullable.js +13 -21
- package/dist/rules/openapi/functions/oas-op-form-data-consume-check.js +21 -19
- package/dist/rules/openapi/functions/oas-op-id-unique.js +27 -27
- package/dist/rules/openapi/functions/oas-op-params.js +36 -42
- package/dist/rules/openapi/functions/oas-op-security-defined.js +40 -39
- package/dist/rules/openapi/functions/oas-op-success-response.js +11 -13
- package/dist/rules/openapi/functions/oas-path-param.js +75 -96
- package/dist/rules/openapi/functions/oas-schema-example-deprecated.js +33 -40
- package/dist/rules/openapi/functions/oas-schema.js +9 -14
- package/dist/rules/openapi/functions/oas-server-name-unique.js +21 -19
- package/dist/rules/openapi/functions/oas-server-variables.js +45 -49
- package/dist/rules/openapi/functions/oas-tag-defined.js +20 -20
- package/dist/rules/openapi/functions/oas-tag-kind.js +16 -16
- package/dist/rules/openapi/functions/oas-tag-parent-defined.js +40 -43
- package/dist/rules/openapi/functions/oas-tags-unique.js +18 -16
- package/dist/rules/openapi/functions/oas-unused-component.js +48 -58
- package/dist/rules/openapi/functions/ref-siblings.js +13 -11
- package/dist/rules/openapi/index.js +99 -117
- 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 +5 -5
|
@@ -1,121 +1,107 @@
|
|
|
1
|
-
import { compileQuery } from
|
|
2
|
-
const SEVERITIES = new Set([
|
|
3
|
-
const isObject = (value) => typeof 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
38
|
-
|
|
14
|
+
problems.push({ message: "`then` must be an object (or array of objects)", path: at });
|
|
15
|
+
return;
|
|
39
16
|
}
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
2
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
+
};
|
package/dist/fix/plugin.js
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import { applyFixes } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 ===
|
|
2
|
-
const isStringOrNumber = (value) => typeof value ===
|
|
3
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
};
|
|
42
|
+
export {
|
|
43
|
+
alphabetical
|
|
55
44
|
};
|
package/dist/functions/casing.js
CHANGED
|
@@ -1,52 +1,46 @@
|
|
|
1
1
|
const PATTERNS = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
};
|