@grexx/grexxlinter 0.2.326 → 0.2.348
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/cli.js +186 -9
- package/lsp.js +122 -5
- package/package.json +1 -1
- package/schemas/.coverage.json +75 -714
- package/schemas/activity.schema.json +0 -19
- package/schemas/activityAttribute.schema.json +0 -17
- package/schemas/activityRight.schema.json +2 -8
- package/schemas/attribute.schema.json +2 -17
- package/schemas/casetype.schema.json +0 -1
- package/schemas/catchBlock.schema.json +12 -5
- package/schemas/conditionList.schema.json +69 -50
- package/schemas/container.schema.json +57 -74
- package/schemas/customThrow.schema.json +11 -3
- package/schemas/dataset.schema.json +0 -52
- package/schemas/datasetColumn.schema.json +0 -114
- package/schemas/datasetCondition.schema.json +0 -77
- package/schemas/datasetRight.schema.json +0 -2
- package/schemas/directRole.schema.json +0 -7
- package/schemas/folderInfo.schema.json +45 -0
- package/schemas/forEach.schema.json +42 -27
- package/schemas/formAction.schema.json +1 -17
- package/schemas/formActionInlineJavascript.schema.json +49 -54
- package/schemas/formGroup.schema.json +71 -82
- package/schemas/formRight.schema.json +0 -2
- package/schemas/formfield.schema.json +0 -40
- package/schemas/gridActivity.schema.json +59 -78
- package/schemas/gridColumn.schema.json +107 -144
- package/schemas/ifthenelse.schema.json +0 -10
- package/schemas/indirectRole.schema.json +0 -7
- package/schemas/jobStep.schema.json +47 -64
- package/schemas/mapping.schema.json +33 -43
- package/schemas/menuItem.schema.json +43 -56
- package/schemas/menuItemRight.schema.json +45 -71
- package/schemas/metadata.schema.json +3 -18
- package/schemas/notCondition.schema.json +45 -66
- package/schemas/parallelBlock.schema.json +55 -60
- package/schemas/picklist.schema.json +1 -2
- package/schemas/picklistItem.schema.json +49 -55
- package/schemas/platformAttribute.schema.json +0 -22
- package/schemas/platformRole.schema.json +0 -11
- package/schemas/plugin.schema.json +71 -77
- package/schemas/pluginLibrary.schema.json +0 -2
- package/schemas/requestContext.schema.json +10 -0
- package/schemas/securityProfile.schema.json +53 -47
- package/schemas/securityRequirement.schema.json +52 -47
- package/schemas/simpleCondition.schema.json +0 -10
- package/schemas/template/date-function.schema.json +1 -0
- package/schemas/template/html.schema.json +1 -0
- package/schemas/template/if.schema.json +1 -0
- package/schemas/template/iterator.schema.json +1 -0
- package/schemas/template/multivalue.schema.json +1 -0
- package/schemas/template/picklist.schema.json +1 -0
- package/schemas/template/util.schema.json +4 -0
- package/schemas/template.schema.json +1 -2
- package/schemas/trigger.schema.json +131 -68
- package/schemas/tryBlock.schema.json +12 -5
- package/schemas/view.schema.json +0 -17
- package/schemas/while.schema.json +23 -43
- package/schemas/widget.schema.json +0 -463
- package/schemas/widgetRight.schema.json +1 -2
package/cli.js
CHANGED
|
@@ -14518,9 +14518,11 @@ function createSchemaRegistry(schemas) {
|
|
|
14518
14518
|
validateSchema: false
|
|
14519
14519
|
});
|
|
14520
14520
|
const casetypes = [];
|
|
14521
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14521
14522
|
for (const schema of schemas) {
|
|
14522
14523
|
const id = typeof schema.$id === "string" ? schema.$id : void 0;
|
|
14523
14524
|
if (!id) continue;
|
|
14525
|
+
byId.set(id, schema);
|
|
14524
14526
|
if (!ajv.getSchema(id)) ajv.addSchema(schema, id);
|
|
14525
14527
|
const m = /^https:\/\/grexx\.net\/studio-next\/schemas\/([^/]+)\.schema\.json$/.exec(id);
|
|
14526
14528
|
if (m) casetypes.push(m[1]);
|
|
@@ -14538,10 +14540,32 @@ function createSchemaRegistry(schemas) {
|
|
|
14538
14540
|
cache.set(casetype, fn);
|
|
14539
14541
|
return fn;
|
|
14540
14542
|
}
|
|
14543
|
+
function resolveRef(ref, base) {
|
|
14544
|
+
const hash = ref.indexOf("#");
|
|
14545
|
+
const path = hash === -1 ? ref : ref.slice(0, hash);
|
|
14546
|
+
const frag = hash === -1 ? "" : ref.slice(hash + 1);
|
|
14547
|
+
let schema;
|
|
14548
|
+
if (path === "") {
|
|
14549
|
+
schema = base;
|
|
14550
|
+
} else {
|
|
14551
|
+
const file = path.replace(/^\.\//, "");
|
|
14552
|
+
schema = byId.get(file.startsWith("http") ? file : `${SCHEMA_ID_BASE}${file}`);
|
|
14553
|
+
}
|
|
14554
|
+
if (!schema) return null;
|
|
14555
|
+
if (!frag) return schema;
|
|
14556
|
+
let cur = schema;
|
|
14557
|
+
for (const raw of frag.split("/").filter(Boolean)) {
|
|
14558
|
+
const key2 = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
14559
|
+
if (cur == null || typeof cur !== "object") return null;
|
|
14560
|
+
cur = cur[key2];
|
|
14561
|
+
}
|
|
14562
|
+
return cur ?? null;
|
|
14563
|
+
}
|
|
14541
14564
|
return {
|
|
14542
14565
|
casetypes,
|
|
14543
14566
|
has: (casetype) => validatorFor(casetype) != null,
|
|
14544
|
-
validatorFor
|
|
14567
|
+
validatorFor,
|
|
14568
|
+
resolveRef
|
|
14545
14569
|
};
|
|
14546
14570
|
}
|
|
14547
14571
|
|
|
@@ -14590,7 +14614,7 @@ var import_yaml = __toESM(require_dist(), 1);
|
|
|
14590
14614
|
var DEFAULT_RULE_SEVERITY = {
|
|
14591
14615
|
"required-field": "error",
|
|
14592
14616
|
"forbidden-field": "error",
|
|
14593
|
-
"
|
|
14617
|
+
"additional-field": "warning",
|
|
14594
14618
|
"invalid-value": "error",
|
|
14595
14619
|
"value-case": "warning",
|
|
14596
14620
|
"invalid-type": "error",
|
|
@@ -14608,6 +14632,18 @@ var DEFAULT_LINTER_SETTINGS = {
|
|
|
14608
14632
|
include: ["**/*.yaml", "**/*.yml"],
|
|
14609
14633
|
exclude: ["node_modules", "dist", ".git"]
|
|
14610
14634
|
};
|
|
14635
|
+
var LINT_RULE_DESCRIPTORS = [
|
|
14636
|
+
{ id: "required-field", label: "Required field", description: "A field the casetype state requires (MUST) is missing.", defaultSeverity: "error" },
|
|
14637
|
+
{ id: "forbidden-field", label: "Forbidden field", description: "A field present that the casetype state forbids (MUST_NOT).", defaultSeverity: "error" },
|
|
14638
|
+
{ id: "additional-field", label: "Additional field", description: "A field not defined by the schema. Allowed in principle, but flagged for review.", defaultSeverity: "warning" },
|
|
14639
|
+
{ id: "invalid-value", label: "Invalid value", description: "Value not allowed by an enum / const.", defaultSeverity: "error" },
|
|
14640
|
+
{ id: "value-case", label: "Value case mismatch", description: "Value matches an allowed option except for case / camelCase / separators (e.g. FORM vs form).", defaultSeverity: "warning" },
|
|
14641
|
+
{ id: "invalid-type", label: "Invalid type", description: "Value has the wrong JSON type.", defaultSeverity: "error" },
|
|
14642
|
+
{ id: "invalid-format", label: "Invalid format", description: "Value fails a pattern / format constraint.", defaultSeverity: "error" },
|
|
14643
|
+
{ id: "unknown-casetype", label: "Unknown casetype", description: "No schema found for the document `_type`.", defaultSeverity: "warning" },
|
|
14644
|
+
{ id: "parse-error", label: "Parse error", description: "The YAML document could not be parsed.", defaultSeverity: "error" },
|
|
14645
|
+
{ id: "schema", label: "Other schema error", description: "Any other constraint reported by the validator.", defaultSeverity: "error" }
|
|
14646
|
+
];
|
|
14611
14647
|
function normalizeSettings(partial) {
|
|
14612
14648
|
return {
|
|
14613
14649
|
...DEFAULT_LINTER_SETTINGS,
|
|
@@ -14628,7 +14664,7 @@ function ruleForKeyword(keyword) {
|
|
|
14628
14664
|
return "forbidden-field";
|
|
14629
14665
|
case "additionalProperties":
|
|
14630
14666
|
case "unevaluatedProperties":
|
|
14631
|
-
return "
|
|
14667
|
+
return "additional-field";
|
|
14632
14668
|
case "const":
|
|
14633
14669
|
case "enum":
|
|
14634
14670
|
return "invalid-value";
|
|
@@ -14656,7 +14692,7 @@ function humanize(err) {
|
|
|
14656
14692
|
return `field "${lastSegment(err.instancePath) ?? ""}" is not allowed for this casetype state`;
|
|
14657
14693
|
case "additionalProperties":
|
|
14658
14694
|
case "unevaluatedProperties":
|
|
14659
|
-
return `
|
|
14695
|
+
return `additional field "${String(p.additionalProperty)}" not defined by the schema`;
|
|
14660
14696
|
case "const":
|
|
14661
14697
|
return `must equal ${JSON.stringify(p.allowedValue)}`;
|
|
14662
14698
|
case "enum":
|
|
@@ -14683,6 +14719,92 @@ function locate(err) {
|
|
|
14683
14719
|
}
|
|
14684
14720
|
return { field: lastSegment(err.instancePath), path: err.instancePath };
|
|
14685
14721
|
}
|
|
14722
|
+
function collectConsts(cond, out = /* @__PURE__ */ new Map()) {
|
|
14723
|
+
if (!cond || typeof cond !== "object") return out;
|
|
14724
|
+
const c = cond;
|
|
14725
|
+
const props = c.properties;
|
|
14726
|
+
if (props) {
|
|
14727
|
+
for (const [k, sub] of Object.entries(props)) {
|
|
14728
|
+
if (sub && typeof sub === "object" && "const" in sub) {
|
|
14729
|
+
(out.get(k) ?? out.set(k, /* @__PURE__ */ new Set()).get(k)).add(sub.const);
|
|
14730
|
+
}
|
|
14731
|
+
}
|
|
14732
|
+
}
|
|
14733
|
+
for (const key2 of ["allOf", "anyOf", "oneOf"]) {
|
|
14734
|
+
if (Array.isArray(c[key2])) c[key2].forEach((s) => collectConsts(s, out));
|
|
14735
|
+
}
|
|
14736
|
+
return out;
|
|
14737
|
+
}
|
|
14738
|
+
function parentPointer(pointer) {
|
|
14739
|
+
const i = pointer.lastIndexOf("/");
|
|
14740
|
+
return i <= 0 ? "" : pointer.slice(0, i);
|
|
14741
|
+
}
|
|
14742
|
+
function fmtStateValue(v) {
|
|
14743
|
+
if (v === void 0 || v === null || v === "") return "null";
|
|
14744
|
+
if (typeof v === "string") return v;
|
|
14745
|
+
if (typeof v === "object") return Array.isArray(v) ? `[${v.length}]` : "{\u2026}";
|
|
14746
|
+
return String(v);
|
|
14747
|
+
}
|
|
14748
|
+
function deref(schema, resolve2, docRoot) {
|
|
14749
|
+
let cur = schema;
|
|
14750
|
+
for (let i = 0; cur && typeof cur === "object" && typeof cur.$ref === "string" && i < 16; i++) {
|
|
14751
|
+
const ref = cur.$ref;
|
|
14752
|
+
const next = resolve2(ref, docRoot.v ?? void 0);
|
|
14753
|
+
if (!next) break;
|
|
14754
|
+
if (!ref.startsWith("#")) docRoot.v = next;
|
|
14755
|
+
cur = next;
|
|
14756
|
+
}
|
|
14757
|
+
return cur ?? null;
|
|
14758
|
+
}
|
|
14759
|
+
function propSchema(schema, key2) {
|
|
14760
|
+
const direct = schema.properties;
|
|
14761
|
+
if (direct && key2 in direct) return direct[key2];
|
|
14762
|
+
for (const branch of schema.allOf ?? []) {
|
|
14763
|
+
const t = branch.then ?? branch;
|
|
14764
|
+
const props = t.properties;
|
|
14765
|
+
if (props && key2 in props) return props[key2];
|
|
14766
|
+
}
|
|
14767
|
+
return null;
|
|
14768
|
+
}
|
|
14769
|
+
function schemaAtPointer(rootSchema, pointer, resolve2) {
|
|
14770
|
+
const docRoot = { v: rootSchema };
|
|
14771
|
+
let schema = deref(rootSchema, resolve2, docRoot);
|
|
14772
|
+
for (const raw of pointer.split("/").filter(Boolean)) {
|
|
14773
|
+
if (!schema) return null;
|
|
14774
|
+
if (/^\d+$/.test(raw)) {
|
|
14775
|
+
let items = schema.items;
|
|
14776
|
+
if (Array.isArray(items)) items = items[Number(raw)] ?? items[0];
|
|
14777
|
+
schema = deref(items, resolve2, docRoot);
|
|
14778
|
+
} else {
|
|
14779
|
+
const key2 = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
14780
|
+
schema = deref(propSchema(schema, key2), resolve2, docRoot);
|
|
14781
|
+
}
|
|
14782
|
+
}
|
|
14783
|
+
return schema;
|
|
14784
|
+
}
|
|
14785
|
+
function describeState(rootSchema, err, root, resolve2) {
|
|
14786
|
+
const m = /^#\/allOf\/(\d+)\/(then|else)\b/.exec(err.schemaPath);
|
|
14787
|
+
if (!m || !rootSchema || typeof rootSchema !== "object") return null;
|
|
14788
|
+
const branch = m[2];
|
|
14789
|
+
const objPath = err.keyword === "false schema" ? parentPointer(err.instancePath) : err.instancePath;
|
|
14790
|
+
const govSchema = schemaAtPointer(rootSchema, objPath, resolve2);
|
|
14791
|
+
const allOf = govSchema?.allOf;
|
|
14792
|
+
const clause = Array.isArray(allOf) ? allOf[Number(m[1])] : void 0;
|
|
14793
|
+
if (!clause?.if) return null;
|
|
14794
|
+
const consts = collectConsts(clause.if);
|
|
14795
|
+
if (consts.size === 0) return null;
|
|
14796
|
+
const obj = valueAtPointer(root, objPath);
|
|
14797
|
+
if (!obj || typeof obj !== "object") return null;
|
|
14798
|
+
const rec = obj;
|
|
14799
|
+
if (branch === "then") {
|
|
14800
|
+
for (const [field, values] of consts) if (!values.has(rec[field])) return null;
|
|
14801
|
+
const parts2 = [...consts].map(([f, vs]) => `${f}=${fmtStateValue([...vs][0])}`);
|
|
14802
|
+
return `(${parts2.join(", ")})`;
|
|
14803
|
+
}
|
|
14804
|
+
for (const field of consts.keys()) if (!(field in rec)) return null;
|
|
14805
|
+
const parts = [...consts.keys()].map((f) => `${f}=${fmtStateValue(rec[f])}`);
|
|
14806
|
+
return `(${parts.join(", ")})`;
|
|
14807
|
+
}
|
|
14686
14808
|
function valueAtPointer(root, pointer) {
|
|
14687
14809
|
if (!pointer) return root;
|
|
14688
14810
|
let cur = root;
|
|
@@ -14734,6 +14856,7 @@ function lintDocument(ctx, input) {
|
|
|
14734
14856
|
push("unknown-casetype", "", casetype, casetype ? `no schema for casetype "${casetype}"` : "could not determine casetype (no `_type`)");
|
|
14735
14857
|
} else if (!validate(input.value)) {
|
|
14736
14858
|
const errors = validate.errors ?? [];
|
|
14859
|
+
const schema = validate.schema;
|
|
14737
14860
|
const forbidden = /* @__PURE__ */ new Set();
|
|
14738
14861
|
for (const e of errors) if (e.keyword === "false schema") forbidden.add(e.instancePath);
|
|
14739
14862
|
for (const e of errors) {
|
|
@@ -14752,7 +14875,13 @@ function lintDocument(ctx, input) {
|
|
|
14752
14875
|
continue;
|
|
14753
14876
|
}
|
|
14754
14877
|
}
|
|
14755
|
-
|
|
14878
|
+
const rule = ruleForKeyword(e.keyword);
|
|
14879
|
+
let message = humanize(e);
|
|
14880
|
+
if (rule === "forbidden-field" || rule === "required-field") {
|
|
14881
|
+
const state = describeState(schema, e, input.value, ctx.registry.resolveRef);
|
|
14882
|
+
if (state) message += ` ${state}`;
|
|
14883
|
+
}
|
|
14884
|
+
push(rule, path, field, message);
|
|
14756
14885
|
}
|
|
14757
14886
|
}
|
|
14758
14887
|
if (settings.reportUncovered && ctx.coverage && casetype) {
|
|
@@ -14793,7 +14922,7 @@ function isLintableYaml(text) {
|
|
|
14793
14922
|
// src/cli.ts
|
|
14794
14923
|
var PARALLEL_THRESHOLD = 200;
|
|
14795
14924
|
var MAX_WORKERS = 8;
|
|
14796
|
-
var VERSION = true ? "0.2.
|
|
14925
|
+
var VERSION = true ? "0.2.348" : "0.0.0-dev";
|
|
14797
14926
|
function isNewer(latest, current) {
|
|
14798
14927
|
const a = latest.split(".").map(Number);
|
|
14799
14928
|
const b = current.split(".").map(Number);
|
|
@@ -14812,19 +14941,30 @@ function checkForUpdate(disabled) {
|
|
|
14812
14941
|
const timer = setTimeout(() => ctrl.abort(), 1500);
|
|
14813
14942
|
return fetch("https://registry.npmjs.org/@grexx%2Fgrexxlinter/latest", { signal: ctrl.signal }).then((r) => r.ok ? r.json() : null).then((j) => j?.version && isNewer(j.version, VERSION) ? j.version : null).catch(() => null).finally(() => clearTimeout(timer));
|
|
14814
14943
|
}
|
|
14944
|
+
var VALID_RULES = new Set(LINT_RULE_DESCRIPTORS.map((d) => d.id));
|
|
14815
14945
|
function parseArgs(argv) {
|
|
14816
|
-
const args = { paths: [], format: "text", quiet: false, noUpdateCheck: false };
|
|
14946
|
+
const args = { paths: [], off: [], format: "text", quiet: false, noUpdateCheck: false };
|
|
14817
14947
|
for (let i = 0; i < argv.length; i++) {
|
|
14818
14948
|
const a = argv[i];
|
|
14819
14949
|
if (a === "--settings") args.settingsFile = argv[++i];
|
|
14820
|
-
else if (a === "--
|
|
14950
|
+
else if (a === "--off") {
|
|
14951
|
+
const list = (argv[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
14952
|
+
for (const r of list) {
|
|
14953
|
+
if (!VALID_RULES.has(r)) throw new Error(`--off: unknown rule "${r}" (valid: ${[...VALID_RULES].join(", ")})`);
|
|
14954
|
+
args.off.push(r);
|
|
14955
|
+
}
|
|
14956
|
+
} else if (a === "--schemas-dir") args.schemasDir = argv[++i];
|
|
14821
14957
|
else if (a === "--fail-on") args.failOn = argv[++i];
|
|
14822
14958
|
else if (a === "--format") args.format = argv[++i];
|
|
14959
|
+
else if (a === "--report") args.report = argv[++i];
|
|
14823
14960
|
else if (a === "--quiet") args.quiet = true;
|
|
14824
14961
|
else if (a === "--no-update-check") args.noUpdateCheck = true;
|
|
14825
14962
|
else if (a.startsWith("--")) throw new Error(`unknown option: ${a}`);
|
|
14826
14963
|
else args.paths.push(a);
|
|
14827
14964
|
}
|
|
14965
|
+
if (args.report && args.report !== "rules" && args.report !== "fields") {
|
|
14966
|
+
throw new Error(`--report must be "rules" or "fields" (got "${args.report}")`);
|
|
14967
|
+
}
|
|
14828
14968
|
if (args.paths.length === 0) args.paths.push(".");
|
|
14829
14969
|
return args;
|
|
14830
14970
|
}
|
|
@@ -14879,6 +15019,38 @@ function printText(results, settings, quiet) {
|
|
|
14879
15019
|
${s.files} file(s)${skipped ? `, ${skipped} skipped` : ""} \u2014 ${paint(`${s.errors} error(s)`, COLORS.error)}, ${paint(`${s.warnings} warning(s)`, COLORS.warning)}, ${s.infos} info(s) [fail-on: ${settings.failOn}]`
|
|
14880
15020
|
);
|
|
14881
15021
|
}
|
|
15022
|
+
function printReport(results, kind) {
|
|
15023
|
+
const msgs = results.filter((r) => !r.skipped).flatMap((r) => r.messages);
|
|
15024
|
+
if (kind === "rules") {
|
|
15025
|
+
const by2 = /* @__PURE__ */ new Map();
|
|
15026
|
+
for (const m of msgs) {
|
|
15027
|
+
const e = by2.get(m.rule) ?? { count: 0, severity: m.severity };
|
|
15028
|
+
e.count++;
|
|
15029
|
+
by2.set(m.rule, e);
|
|
15030
|
+
}
|
|
15031
|
+
const rows2 = [...by2.entries()].map(([rule, e]) => ({ count: e.count, rule, severity: e.severity })).sort((a, b) => b.count - a.count);
|
|
15032
|
+
const w = Math.max(5, ...rows2.map((r) => String(r.count).length));
|
|
15033
|
+
console.log(`${"count".padStart(w)} rule severity`);
|
|
15034
|
+
for (const r of rows2) {
|
|
15035
|
+
console.log(`${paint(String(r.count).padStart(w), COLORS[r.severity])} ${r.rule.padEnd(16)} ${r.severity}`);
|
|
15036
|
+
}
|
|
15037
|
+
return;
|
|
15038
|
+
}
|
|
15039
|
+
const by = /* @__PURE__ */ new Map();
|
|
15040
|
+
for (const m of msgs) {
|
|
15041
|
+
const key2 = `${m.casetype ?? "?"}\0${m.rule}\0${m.severity}\0${m.message}`;
|
|
15042
|
+
const e = by.get(key2) ?? { count: 0, casetype: m.casetype ?? "?", severity: m.severity, message: m.message };
|
|
15043
|
+
e.count++;
|
|
15044
|
+
by.set(key2, e);
|
|
15045
|
+
}
|
|
15046
|
+
const rows = [...by.values()].sort((a, b) => b.count - a.count || a.casetype.localeCompare(b.casetype));
|
|
15047
|
+
const cw = Math.max(5, ...rows.map((r) => String(r.count).length));
|
|
15048
|
+
const tw = Math.max(8, ...rows.map((r) => r.casetype.length));
|
|
15049
|
+
console.log(`${"count".padStart(cw)} ${"casetype".padEnd(tw)} severity message`);
|
|
15050
|
+
for (const r of rows) {
|
|
15051
|
+
console.log(`${paint(String(r.count).padStart(cw), COLORS[r.severity])} ${r.casetype.padEnd(tw)} ${r.severity.padEnd(8)} ${r.message}`);
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
14882
15054
|
var WORKER_FILE = fileURLToPath2(import.meta.url);
|
|
14883
15055
|
function lintFiles(files, ctx, settings) {
|
|
14884
15056
|
const out = [];
|
|
@@ -14946,6 +15118,9 @@ async function main() {
|
|
|
14946
15118
|
let partial = {};
|
|
14947
15119
|
if (args.settingsFile) partial = JSON.parse(readFileSync2(args.settingsFile, "utf8"));
|
|
14948
15120
|
if (args.failOn) partial = { ...partial, failOn: args.failOn };
|
|
15121
|
+
if (args.off.length > 0) {
|
|
15122
|
+
partial = { ...partial, rules: { ...partial.rules, ...Object.fromEntries(args.off.map((r) => [r, "off"])) } };
|
|
15123
|
+
}
|
|
14949
15124
|
const settings = normalizeSettings(partial);
|
|
14950
15125
|
const files = discover(args.paths, settings.exclude);
|
|
14951
15126
|
const canParallel = WORKER_FILE.endsWith(".js") && files.length > PARALLEL_THRESHOLD;
|
|
@@ -14956,7 +15131,9 @@ async function main() {
|
|
|
14956
15131
|
const ctx = createNodeContext({ schemasDir: args.schemasDir });
|
|
14957
15132
|
results = lintFiles(files, ctx, settings);
|
|
14958
15133
|
}
|
|
14959
|
-
if (args.
|
|
15134
|
+
if (args.report) {
|
|
15135
|
+
printReport(results, args.report);
|
|
15136
|
+
} else if (args.format === "json") {
|
|
14960
15137
|
const active = results.filter((r) => !r.skipped);
|
|
14961
15138
|
console.log(JSON.stringify({ summary: summarize(active), results }, null, 2));
|
|
14962
15139
|
} else {
|
package/lsp.js
CHANGED
|
@@ -23587,9 +23587,11 @@ function createSchemaRegistry(schemas) {
|
|
|
23587
23587
|
validateSchema: false
|
|
23588
23588
|
});
|
|
23589
23589
|
const casetypes = [];
|
|
23590
|
+
const byId = /* @__PURE__ */ new Map();
|
|
23590
23591
|
for (const schema of schemas) {
|
|
23591
23592
|
const id = typeof schema.$id === "string" ? schema.$id : void 0;
|
|
23592
23593
|
if (!id) continue;
|
|
23594
|
+
byId.set(id, schema);
|
|
23593
23595
|
if (!ajv.getSchema(id)) ajv.addSchema(schema, id);
|
|
23594
23596
|
const m = /^https:\/\/grexx\.net\/studio-next\/schemas\/([^/]+)\.schema\.json$/.exec(id);
|
|
23595
23597
|
if (m) casetypes.push(m[1]);
|
|
@@ -23607,10 +23609,32 @@ function createSchemaRegistry(schemas) {
|
|
|
23607
23609
|
cache.set(casetype, fn);
|
|
23608
23610
|
return fn;
|
|
23609
23611
|
}
|
|
23612
|
+
function resolveRef(ref, base) {
|
|
23613
|
+
const hash = ref.indexOf("#");
|
|
23614
|
+
const path = hash === -1 ? ref : ref.slice(0, hash);
|
|
23615
|
+
const frag = hash === -1 ? "" : ref.slice(hash + 1);
|
|
23616
|
+
let schema;
|
|
23617
|
+
if (path === "") {
|
|
23618
|
+
schema = base;
|
|
23619
|
+
} else {
|
|
23620
|
+
const file = path.replace(/^\.\//, "");
|
|
23621
|
+
schema = byId.get(file.startsWith("http") ? file : `${SCHEMA_ID_BASE}${file}`);
|
|
23622
|
+
}
|
|
23623
|
+
if (!schema) return null;
|
|
23624
|
+
if (!frag) return schema;
|
|
23625
|
+
let cur = schema;
|
|
23626
|
+
for (const raw of frag.split("/").filter(Boolean)) {
|
|
23627
|
+
const key2 = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
23628
|
+
if (cur == null || typeof cur !== "object") return null;
|
|
23629
|
+
cur = cur[key2];
|
|
23630
|
+
}
|
|
23631
|
+
return cur ?? null;
|
|
23632
|
+
}
|
|
23610
23633
|
return {
|
|
23611
23634
|
casetypes,
|
|
23612
23635
|
has: (casetype) => validatorFor(casetype) != null,
|
|
23613
|
-
validatorFor
|
|
23636
|
+
validatorFor,
|
|
23637
|
+
resolveRef
|
|
23614
23638
|
};
|
|
23615
23639
|
}
|
|
23616
23640
|
|
|
@@ -23656,7 +23680,7 @@ function createNodeContext(opts = {}) {
|
|
|
23656
23680
|
var DEFAULT_RULE_SEVERITY = {
|
|
23657
23681
|
"required-field": "error",
|
|
23658
23682
|
"forbidden-field": "error",
|
|
23659
|
-
"
|
|
23683
|
+
"additional-field": "warning",
|
|
23660
23684
|
"invalid-value": "error",
|
|
23661
23685
|
"value-case": "warning",
|
|
23662
23686
|
"invalid-type": "error",
|
|
@@ -23694,7 +23718,7 @@ function ruleForKeyword(keyword) {
|
|
|
23694
23718
|
return "forbidden-field";
|
|
23695
23719
|
case "additionalProperties":
|
|
23696
23720
|
case "unevaluatedProperties":
|
|
23697
|
-
return "
|
|
23721
|
+
return "additional-field";
|
|
23698
23722
|
case "const":
|
|
23699
23723
|
case "enum":
|
|
23700
23724
|
return "invalid-value";
|
|
@@ -23722,7 +23746,7 @@ function humanize(err) {
|
|
|
23722
23746
|
return `field "${lastSegment(err.instancePath) ?? ""}" is not allowed for this casetype state`;
|
|
23723
23747
|
case "additionalProperties":
|
|
23724
23748
|
case "unevaluatedProperties":
|
|
23725
|
-
return `
|
|
23749
|
+
return `additional field "${String(p.additionalProperty)}" not defined by the schema`;
|
|
23726
23750
|
case "const":
|
|
23727
23751
|
return `must equal ${JSON.stringify(p.allowedValue)}`;
|
|
23728
23752
|
case "enum":
|
|
@@ -23749,6 +23773,92 @@ function locate(err) {
|
|
|
23749
23773
|
}
|
|
23750
23774
|
return { field: lastSegment(err.instancePath), path: err.instancePath };
|
|
23751
23775
|
}
|
|
23776
|
+
function collectConsts(cond, out = /* @__PURE__ */ new Map()) {
|
|
23777
|
+
if (!cond || typeof cond !== "object") return out;
|
|
23778
|
+
const c = cond;
|
|
23779
|
+
const props = c.properties;
|
|
23780
|
+
if (props) {
|
|
23781
|
+
for (const [k, sub] of Object.entries(props)) {
|
|
23782
|
+
if (sub && typeof sub === "object" && "const" in sub) {
|
|
23783
|
+
(out.get(k) ?? out.set(k, /* @__PURE__ */ new Set()).get(k)).add(sub.const);
|
|
23784
|
+
}
|
|
23785
|
+
}
|
|
23786
|
+
}
|
|
23787
|
+
for (const key2 of ["allOf", "anyOf", "oneOf"]) {
|
|
23788
|
+
if (Array.isArray(c[key2])) c[key2].forEach((s) => collectConsts(s, out));
|
|
23789
|
+
}
|
|
23790
|
+
return out;
|
|
23791
|
+
}
|
|
23792
|
+
function parentPointer(pointer) {
|
|
23793
|
+
const i = pointer.lastIndexOf("/");
|
|
23794
|
+
return i <= 0 ? "" : pointer.slice(0, i);
|
|
23795
|
+
}
|
|
23796
|
+
function fmtStateValue(v) {
|
|
23797
|
+
if (v === void 0 || v === null || v === "") return "null";
|
|
23798
|
+
if (typeof v === "string") return v;
|
|
23799
|
+
if (typeof v === "object") return Array.isArray(v) ? `[${v.length}]` : "{\u2026}";
|
|
23800
|
+
return String(v);
|
|
23801
|
+
}
|
|
23802
|
+
function deref(schema, resolve2, docRoot) {
|
|
23803
|
+
let cur = schema;
|
|
23804
|
+
for (let i = 0; cur && typeof cur === "object" && typeof cur.$ref === "string" && i < 16; i++) {
|
|
23805
|
+
const ref = cur.$ref;
|
|
23806
|
+
const next = resolve2(ref, docRoot.v ?? void 0);
|
|
23807
|
+
if (!next) break;
|
|
23808
|
+
if (!ref.startsWith("#")) docRoot.v = next;
|
|
23809
|
+
cur = next;
|
|
23810
|
+
}
|
|
23811
|
+
return cur ?? null;
|
|
23812
|
+
}
|
|
23813
|
+
function propSchema(schema, key2) {
|
|
23814
|
+
const direct = schema.properties;
|
|
23815
|
+
if (direct && key2 in direct) return direct[key2];
|
|
23816
|
+
for (const branch of schema.allOf ?? []) {
|
|
23817
|
+
const t = branch.then ?? branch;
|
|
23818
|
+
const props = t.properties;
|
|
23819
|
+
if (props && key2 in props) return props[key2];
|
|
23820
|
+
}
|
|
23821
|
+
return null;
|
|
23822
|
+
}
|
|
23823
|
+
function schemaAtPointer(rootSchema, pointer, resolve2) {
|
|
23824
|
+
const docRoot = { v: rootSchema };
|
|
23825
|
+
let schema = deref(rootSchema, resolve2, docRoot);
|
|
23826
|
+
for (const raw of pointer.split("/").filter(Boolean)) {
|
|
23827
|
+
if (!schema) return null;
|
|
23828
|
+
if (/^\d+$/.test(raw)) {
|
|
23829
|
+
let items = schema.items;
|
|
23830
|
+
if (Array.isArray(items)) items = items[Number(raw)] ?? items[0];
|
|
23831
|
+
schema = deref(items, resolve2, docRoot);
|
|
23832
|
+
} else {
|
|
23833
|
+
const key2 = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
23834
|
+
schema = deref(propSchema(schema, key2), resolve2, docRoot);
|
|
23835
|
+
}
|
|
23836
|
+
}
|
|
23837
|
+
return schema;
|
|
23838
|
+
}
|
|
23839
|
+
function describeState(rootSchema, err, root, resolve2) {
|
|
23840
|
+
const m = /^#\/allOf\/(\d+)\/(then|else)\b/.exec(err.schemaPath);
|
|
23841
|
+
if (!m || !rootSchema || typeof rootSchema !== "object") return null;
|
|
23842
|
+
const branch = m[2];
|
|
23843
|
+
const objPath = err.keyword === "false schema" ? parentPointer(err.instancePath) : err.instancePath;
|
|
23844
|
+
const govSchema = schemaAtPointer(rootSchema, objPath, resolve2);
|
|
23845
|
+
const allOf = govSchema?.allOf;
|
|
23846
|
+
const clause = Array.isArray(allOf) ? allOf[Number(m[1])] : void 0;
|
|
23847
|
+
if (!clause?.if) return null;
|
|
23848
|
+
const consts = collectConsts(clause.if);
|
|
23849
|
+
if (consts.size === 0) return null;
|
|
23850
|
+
const obj = valueAtPointer(root, objPath);
|
|
23851
|
+
if (!obj || typeof obj !== "object") return null;
|
|
23852
|
+
const rec = obj;
|
|
23853
|
+
if (branch === "then") {
|
|
23854
|
+
for (const [field, values] of consts) if (!values.has(rec[field])) return null;
|
|
23855
|
+
const parts2 = [...consts].map(([f, vs]) => `${f}=${fmtStateValue([...vs][0])}`);
|
|
23856
|
+
return `(${parts2.join(", ")})`;
|
|
23857
|
+
}
|
|
23858
|
+
for (const field of consts.keys()) if (!(field in rec)) return null;
|
|
23859
|
+
const parts = [...consts.keys()].map((f) => `${f}=${fmtStateValue(rec[f])}`);
|
|
23860
|
+
return `(${parts.join(", ")})`;
|
|
23861
|
+
}
|
|
23752
23862
|
function valueAtPointer(root, pointer) {
|
|
23753
23863
|
if (!pointer) return root;
|
|
23754
23864
|
let cur = root;
|
|
@@ -23800,6 +23910,7 @@ function lintDocument(ctx2, input) {
|
|
|
23800
23910
|
push("unknown-casetype", "", casetype, casetype ? `no schema for casetype "${casetype}"` : "could not determine casetype (no `_type`)");
|
|
23801
23911
|
} else if (!validate(input.value)) {
|
|
23802
23912
|
const errors = validate.errors ?? [];
|
|
23913
|
+
const schema = validate.schema;
|
|
23803
23914
|
const forbidden = /* @__PURE__ */ new Set();
|
|
23804
23915
|
for (const e of errors) if (e.keyword === "false schema") forbidden.add(e.instancePath);
|
|
23805
23916
|
for (const e of errors) {
|
|
@@ -23818,7 +23929,13 @@ function lintDocument(ctx2, input) {
|
|
|
23818
23929
|
continue;
|
|
23819
23930
|
}
|
|
23820
23931
|
}
|
|
23821
|
-
|
|
23932
|
+
const rule = ruleForKeyword(e.keyword);
|
|
23933
|
+
let message = humanize(e);
|
|
23934
|
+
if (rule === "forbidden-field" || rule === "required-field") {
|
|
23935
|
+
const state = describeState(schema, e, input.value, ctx2.registry.resolveRef);
|
|
23936
|
+
if (state) message += ` ${state}`;
|
|
23937
|
+
}
|
|
23938
|
+
push(rule, path, field, message);
|
|
23822
23939
|
}
|
|
23823
23940
|
}
|
|
23824
23941
|
if (settings2.reportUncovered && ctx2.coverage && casetype) {
|