@grexx/grexxlinter 0.2.1372 → 0.2.1408

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 (3) hide show
  1. package/cli.js +208 -150
  2. package/lsp.js +106 -81
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -14998,12 +14998,108 @@ function meetsThreshold(severity, failOn) {
14998
14998
  function messageKey(m) {
14999
14999
  return `${m.rule}|${m.path}|${m.severity}|${m.message}`;
15000
15000
  }
15001
+ var ANY_OF_REQUIRED = /^(.*\/anyOf)\/\d+\/required$/;
15002
+ function requireAnyKey(e) {
15003
+ if (e.keyword !== "required") return null;
15004
+ const m = ANY_OF_REQUIRED.exec(e.schemaPath);
15005
+ return m ? `${m[1]}@${e.instancePath}` : null;
15006
+ }
15007
+ function collectMapState(errors) {
15008
+ const forbidden = /* @__PURE__ */ new Set();
15009
+ const requireAny = /* @__PURE__ */ new Map();
15010
+ for (const e of errors) {
15011
+ if (e.keyword === "false schema") forbidden.add(e.instancePath);
15012
+ const key2 = requireAnyKey(e);
15013
+ if (key2 === null) continue;
15014
+ const g = requireAny.get(key2) ?? { instancePath: e.instancePath, fields: [], err: e };
15015
+ g.fields.push(String(e.params.missingProperty));
15016
+ requireAny.set(key2, g);
15017
+ }
15018
+ return { forbidden, requireAny, emittedRequireAny: /* @__PURE__ */ new Set() };
15019
+ }
15020
+ function pushRequireAny(e, state, ctx) {
15021
+ const key2 = requireAnyKey(e);
15022
+ const g = key2 === null ? null : state.requireAny.get(key2);
15023
+ if (!g || g.fields.length < 2) return false;
15024
+ if (!state.emittedRequireAny.has(key2)) {
15025
+ state.emittedRequireAny.add(key2);
15026
+ const where = describeState(ctx.schema, g.err, ctx.root, ctx.resolve);
15027
+ const suffix = where ? ` ${where}` : "";
15028
+ ctx.push("required-field", g.instancePath, null, `missing at least one of: ${g.fields.join(", ")}${suffix}`);
15029
+ }
15030
+ return true;
15031
+ }
15032
+ function pushValueError(e, loc, ctx) {
15033
+ const p = e.params;
15034
+ const allowed = e.keyword === "enum" ? p.allowedValues ?? [] : [p.allowedValue];
15035
+ const actual = valueAtPointer(ctx.root, e.instancePath);
15036
+ const want = caseOnlyMatch(actual, allowed);
15037
+ if (want != null) {
15038
+ ctx.push("value-case", loc.path, loc.field, `"${String(actual)}" should be "${want}" (case/format mismatch)`);
15039
+ return;
15040
+ }
15041
+ const got = actual === void 0 ? "absent" : JSON.stringify(actual);
15042
+ ctx.push("invalid-value", loc.path, loc.field, `got ${got} \u2014 ${humanize(e)}`);
15043
+ }
15044
+ function receivedType(v) {
15045
+ if (v === null) return "null";
15046
+ if (Array.isArray(v)) return "array";
15047
+ return typeof v;
15048
+ }
15049
+ function pushTypeError(e, loc, ctx) {
15050
+ const actual = valueAtPointer(ctx.root, e.instancePath);
15051
+ let val = JSON.stringify(actual) ?? "";
15052
+ if (val.length > 60) val = `${val.slice(0, 57)}\u2026"`;
15053
+ const expected = String(e.params.type);
15054
+ const withValue = actual === void 0 ? "" : ` with value ${val}`;
15055
+ ctx.push("invalid-type", loc.path, loc.field, `expected ${expected} but received ${receivedType(actual)}${withValue}`);
15056
+ }
15057
+ function pushSchemaError(e, loc, ctx) {
15058
+ const rule = ruleForKeyword(e.keyword);
15059
+ let message = humanize(e);
15060
+ if (rule === "forbidden-field" || rule === "required-field") {
15061
+ const where = describeState(ctx.schema, e, ctx.root, ctx.resolve);
15062
+ if (where) message += ` ${where}`;
15063
+ }
15064
+ ctx.push(rule, loc.path, loc.field, message);
15065
+ }
15066
+ function mapError(e, state, ctx) {
15067
+ if (META_KEYWORDS.has(e.keyword)) return;
15068
+ if (pushRequireAny(e, state, ctx)) return;
15069
+ const loc = locate(e);
15070
+ const isAdditional = e.keyword === "additionalProperties" || e.keyword === "unevaluatedProperties";
15071
+ if (isAdditional && state.forbidden.has(loc.path)) return;
15072
+ if (e.keyword === "enum" || e.keyword === "const") pushValueError(e, loc, ctx);
15073
+ else if (e.keyword === "type") pushTypeError(e, loc, ctx);
15074
+ else pushSchemaError(e, loc, ctx);
15075
+ }
15076
+ function mapAjvErrors(errors, ctx) {
15077
+ const state = collectMapState(errors);
15078
+ for (const e of errors) mapError(e, state, ctx);
15079
+ }
15080
+ function isSkipped(casetype, settings) {
15081
+ if (casetype != null && settings.ignoreCasetypes.includes(casetype)) return true;
15082
+ return settings.includeCasetypes.length > 0 && (casetype == null || !settings.includeCasetypes.includes(casetype));
15083
+ }
15084
+ function pushUncovered(push, coverage, casetype) {
15085
+ if (!coverage || !casetype) return;
15086
+ const n = coverage.uncoveredCount(casetype);
15087
+ if (n > 0) push("unknown-casetype", "", casetype, `${n} rule clause(s) for "${casetype}" are behaviour-only and not checked by the schema`);
15088
+ }
15089
+ function dedupeMessages(messages) {
15090
+ const seen = /* @__PURE__ */ new Set();
15091
+ return messages.filter((m) => {
15092
+ const k = messageKey(m);
15093
+ if (seen.has(k)) return false;
15094
+ seen.add(k);
15095
+ return true;
15096
+ });
15097
+ }
15001
15098
  function lintDocument(ctx, input) {
15002
15099
  const settings = normalizeSettings(ctx.settings);
15003
15100
  const casetype = detectCasetype(input);
15101
+ if (isSkipped(casetype, settings)) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
15004
15102
  const messages = [];
15005
- const skipped = casetype != null && settings.ignoreCasetypes.includes(casetype) || settings.includeCasetypes.length > 0 && (casetype == null || !settings.includeCasetypes.includes(casetype));
15006
- if (skipped) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
15007
15103
  const push = (rule, path, field, message) => {
15008
15104
  const sev = severityFor(rule, settings);
15009
15105
  if (sev === "off") return;
@@ -15013,86 +15109,15 @@ function lintDocument(ctx, input) {
15013
15109
  if (!validate) {
15014
15110
  push("unknown-casetype", "", casetype, casetype ? `no schema for casetype "${casetype}"` : "could not determine casetype (no `_type`)");
15015
15111
  } else if (!validate(input.value)) {
15016
- const errors = validate.errors ?? [];
15017
- const schema = validate.schema;
15018
- const forbidden = /* @__PURE__ */ new Set();
15019
- for (const e of errors) if (e.keyword === "false schema") forbidden.add(e.instancePath);
15020
- const anyOfRe = /^(.*\/anyOf)\/\d+\/required$/;
15021
- const reqAnyGroups = /* @__PURE__ */ new Map();
15022
- for (const e of errors) {
15023
- const m = e.keyword === "required" ? anyOfRe.exec(e.schemaPath) : null;
15024
- if (!m) continue;
15025
- const key2 = `${m[1]}@${e.instancePath}`;
15026
- const g = reqAnyGroups.get(key2) ?? { instancePath: e.instancePath, fields: [], err: e };
15027
- g.fields.push(String(e.params.missingProperty));
15028
- reqAnyGroups.set(key2, g);
15029
- }
15030
- const emittedReqAny = /* @__PURE__ */ new Set();
15031
- for (const e of errors) {
15032
- if (META_KEYWORDS.has(e.keyword)) continue;
15033
- if (e.keyword === "required") {
15034
- const m = anyOfRe.exec(e.schemaPath);
15035
- const key2 = m ? `${m[1]}@${e.instancePath}` : null;
15036
- const g = key2 ? reqAnyGroups.get(key2) : null;
15037
- if (g && g.fields.length >= 2) {
15038
- if (!emittedReqAny.has(key2)) {
15039
- emittedReqAny.add(key2);
15040
- const state = describeState(schema, g.err, input.value, ctx.registry.resolveRef);
15041
- const stateSuffix = state ? ` ${state}` : "";
15042
- push("required-field", g.instancePath, null, `missing at least one of: ${g.fields.join(", ")}${stateSuffix}`);
15043
- }
15044
- continue;
15045
- }
15046
- }
15047
- const { field, path } = locate(e);
15048
- if ((e.keyword === "additionalProperties" || e.keyword === "unevaluatedProperties") && forbidden.has(path)) {
15049
- continue;
15050
- }
15051
- if (e.keyword === "enum" || e.keyword === "const") {
15052
- const p = e.params;
15053
- const allowed = e.keyword === "enum" ? p.allowedValues ?? [] : [p.allowedValue];
15054
- const actual = valueAtPointer(input.value, e.instancePath);
15055
- const want = caseOnlyMatch(actual, allowed);
15056
- if (want != null) {
15057
- push("value-case", path, field, `"${String(actual)}" should be "${want}" (case/format mismatch)`);
15058
- continue;
15059
- }
15060
- const got = actual === void 0 ? "absent" : JSON.stringify(actual);
15061
- push("invalid-value", path, field, `got ${got} \u2014 ${humanize(e)}`);
15062
- continue;
15063
- }
15064
- if (e.keyword === "type") {
15065
- const actual = valueAtPointer(input.value, e.instancePath);
15066
- let recv = typeof actual;
15067
- if (actual === null) recv = "null";
15068
- else if (Array.isArray(actual)) recv = "array";
15069
- let val = JSON.stringify(actual) ?? "";
15070
- if (val.length > 60) val = `${val.slice(0, 57)}\u2026"`;
15071
- const expected = String(e.params.type);
15072
- const withValue = actual === void 0 ? "" : ` with value ${val}`;
15073
- push("invalid-type", path, field, `expected ${expected} but received ${recv}${withValue}`);
15074
- continue;
15075
- }
15076
- const rule = ruleForKeyword(e.keyword);
15077
- let message = humanize(e);
15078
- if (rule === "forbidden-field" || rule === "required-field") {
15079
- const state = describeState(schema, e, input.value, ctx.registry.resolveRef);
15080
- if (state) message += ` ${state}`;
15081
- }
15082
- push(rule, path, field, message);
15083
- }
15084
- }
15085
- if (settings.reportUncovered && ctx.coverage && casetype) {
15086
- const n = ctx.coverage.uncoveredCount(casetype);
15087
- if (n > 0) push("unknown-casetype", "", casetype, `${n} rule clause(s) for "${casetype}" are behaviour-only and not checked by the schema`);
15112
+ mapAjvErrors(validate.errors ?? [], {
15113
+ push,
15114
+ schema: validate.schema,
15115
+ root: input.value,
15116
+ resolve: ctx.registry.resolveRef
15117
+ });
15088
15118
  }
15089
- const seen = /* @__PURE__ */ new Set();
15090
- const deduped = messages.filter((m) => {
15091
- const k = messageKey(m);
15092
- if (seen.has(k)) return false;
15093
- seen.add(k);
15094
- return true;
15095
- });
15119
+ if (settings.reportUncovered) pushUncovered(push, ctx.coverage, casetype);
15120
+ const deduped = dedupeMessages(messages);
15096
15121
  const ok = !deduped.some((m) => meetsThreshold(m.severity, settings.failOn));
15097
15122
  return { file: input.file, casetype, messages: deduped, skipped: false, ok };
15098
15123
  }
@@ -15127,7 +15152,7 @@ function toJSOrError(doc) {
15127
15152
  // src/cli.ts
15128
15153
  var PARALLEL_THRESHOLD = 200;
15129
15154
  var MAX_WORKERS = 8;
15130
- var VERSION = true ? "0.2.1372" : "0.0.0-dev";
15155
+ var VERSION = "0.2.1408";
15131
15156
  function isNewer(latest, current) {
15132
15157
  const a = latest.split(".").map(Number);
15133
15158
  const b = current.split(".").map(Number);
@@ -15147,26 +15172,60 @@ function checkForUpdate(disabled) {
15147
15172
  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));
15148
15173
  }
15149
15174
  var VALID_RULES = new Set(LINT_RULE_DESCRIPTORS.map((d) => d.id));
15175
+ function parseOffList(raw) {
15176
+ const list = raw.split(",").map((s) => s.trim()).filter(Boolean);
15177
+ for (const r of list) {
15178
+ if (!VALID_RULES.has(r)) throw new Error(`--off: unknown rule "${r}" (valid: ${[...VALID_RULES].join(", ")})`);
15179
+ }
15180
+ return list;
15181
+ }
15182
+ var VALUE_FLAGS = /* @__PURE__ */ new Map([
15183
+ ["--settings", (args, v) => {
15184
+ args.settingsFile = v;
15185
+ }],
15186
+ ["--off", (args, v) => {
15187
+ args.off.push(...parseOffList(v ?? ""));
15188
+ }],
15189
+ ["--schemas-dir", (args, v) => {
15190
+ args.schemasDir = v;
15191
+ }],
15192
+ ["--fail-on", (args, v) => {
15193
+ args.failOn = v;
15194
+ }],
15195
+ ["--format", (args, v) => {
15196
+ args.format = v;
15197
+ }],
15198
+ ["--report", (args, v) => {
15199
+ args.report = v;
15200
+ }]
15201
+ ]);
15202
+ var BOOL_FLAGS = /* @__PURE__ */ new Map([
15203
+ ["--quiet", (args) => {
15204
+ args.quiet = true;
15205
+ }],
15206
+ ["--errors-only", (args) => {
15207
+ args.errorsOnly = true;
15208
+ }],
15209
+ ["--no-update-check", (args) => {
15210
+ args.noUpdateCheck = true;
15211
+ }]
15212
+ ]);
15150
15213
  function parseArgs(argv) {
15151
15214
  const args = { paths: [], off: [], format: "text", quiet: false, errorsOnly: false, noUpdateCheck: false };
15152
15215
  for (let i = 0; i < argv.length; i++) {
15153
15216
  const a = argv[i];
15154
- if (a === "--settings") args.settingsFile = argv[++i];
15155
- else if (a === "--off") {
15156
- const list = (argv[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
15157
- for (const r of list) {
15158
- if (!VALID_RULES.has(r)) throw new Error(`--off: unknown rule "${r}" (valid: ${[...VALID_RULES].join(", ")})`);
15159
- args.off.push(r);
15160
- }
15161
- } else if (a === "--schemas-dir") args.schemasDir = argv[++i];
15162
- else if (a === "--fail-on") args.failOn = argv[++i];
15163
- else if (a === "--format") args.format = argv[++i];
15164
- else if (a === "--report") args.report = argv[++i];
15165
- else if (a === "--quiet") args.quiet = true;
15166
- else if (a === "--errors-only") args.errorsOnly = true;
15167
- else if (a === "--no-update-check") args.noUpdateCheck = true;
15168
- else if (a.startsWith("--")) throw new Error(`unknown option: ${a}`);
15169
- else args.paths.push(a);
15217
+ const withValue = VALUE_FLAGS.get(a);
15218
+ if (withValue) {
15219
+ withValue(args, argv[++i]);
15220
+ continue;
15221
+ }
15222
+ const toggle = BOOL_FLAGS.get(a);
15223
+ if (toggle) {
15224
+ toggle(args);
15225
+ continue;
15226
+ }
15227
+ if (a.startsWith("--")) throw new Error(`unknown option: ${a}`);
15228
+ args.paths.push(a);
15170
15229
  }
15171
15230
  if (args.report && args.report !== "rules" && args.report !== "fields") {
15172
15231
  throw new Error(`--report must be "rules" or "fields" (got "${args.report}")`);
@@ -15203,24 +15262,24 @@ var COLORS = { error: "\x1B[31m", warning: "\x1B[33m", info: "\x1B[36m" };
15203
15262
  var RESET = "\x1B[0m";
15204
15263
  var useColor = process.stdout.isTTY;
15205
15264
  var paint = (s, c) => useColor ? `${c}${s}${RESET}` : s;
15206
- function printText(results, settings, quiet, errorsOnly) {
15207
- for (const r of results) {
15208
- if (r.skipped) continue;
15209
- const msgs = errorsOnly ? r.messages.filter((m) => m.severity === "error") : r.messages;
15210
- if (quiet && msgs.length === 0) continue;
15211
- const rel = r.file ? relative(process.cwd(), r.file) : "<input>";
15212
- const casetypeTag = r.casetype ? ` (${r.casetype})` : "";
15213
- const head = `${rel}${casetypeTag}`;
15214
- if (msgs.length === 0) {
15215
- if (!quiet) console.log(`${paint("ok", COLORS.info)} ${head}`);
15216
- continue;
15217
- }
15218
- console.log(head);
15219
- for (const m of msgs) {
15220
- const loc = m.path || "/";
15221
- console.log(` ${paint(m.severity.padEnd(7), COLORS[m.severity])} ${m.rule.padEnd(16)} ${loc} ${m.message}`);
15222
- }
15265
+ function printResult(r, quiet, errorsOnly) {
15266
+ if (r.skipped) return;
15267
+ const msgs = errorsOnly ? r.messages.filter((m) => m.severity === "error") : r.messages;
15268
+ if (quiet && msgs.length === 0) return;
15269
+ const rel = r.file ? relative(process.cwd(), r.file) : "<input>";
15270
+ const casetypeTag = r.casetype ? ` (${r.casetype})` : "";
15271
+ const head = `${rel}${casetypeTag}`;
15272
+ if (msgs.length === 0) {
15273
+ if (!quiet) console.log(`${paint("ok", COLORS.info)} ${head}`);
15274
+ return;
15223
15275
  }
15276
+ console.log(head);
15277
+ for (const m of msgs) {
15278
+ const loc = m.path || "/";
15279
+ console.log(` ${paint(m.severity.padEnd(7), COLORS[m.severity])} ${m.rule.padEnd(16)} ${loc} ${m.message}`);
15280
+ }
15281
+ }
15282
+ function printSummary(results, settings) {
15224
15283
  const s = summarize(results.filter((r) => !r.skipped));
15225
15284
  const skipped = results.filter((r) => r.skipped).length;
15226
15285
  const skippedNote = skipped ? `, ${skipped} skipped` : "";
@@ -15231,6 +15290,10 @@ function printText(results, settings, quiet, errorsOnly) {
15231
15290
  ${s.files} file(s)${skippedNote} \u2014 ${errorsText}, ${warningsText}, ${s.infos} info(s) [fail-on: ${settings.failOn}]`
15232
15291
  );
15233
15292
  }
15293
+ function printText(results, settings, quiet, errorsOnly) {
15294
+ for (const r of results) printResult(r, quiet, errorsOnly);
15295
+ printSummary(results, settings);
15296
+ }
15234
15297
  function printReport(results, kind, errorsOnly) {
15235
15298
  let msgs = results.filter((r) => !r.skipped).flatMap((r) => r.messages);
15236
15299
  if (errorsOnly) msgs = msgs.filter((m) => m.severity === "error");
@@ -15265,47 +15328,42 @@ function printReport(results, kind, errorsOnly) {
15265
15328
  }
15266
15329
  }
15267
15330
  var WORKER_FILE = fileURLToPath2(import.meta.url);
15268
- function lintFiles(files, ctx, settings) {
15269
- const out = [];
15270
- const caseRefs = loadCaseRefs(ctx.schemasDir);
15271
- const parseErr = (file, message) => ({
15331
+ function parseErrorResult(file, message, settings) {
15332
+ return {
15272
15333
  file,
15273
15334
  casetype: null,
15274
15335
  skipped: false,
15275
15336
  ok: settings.failOn === "never",
15276
15337
  messages: [{ severity: "error", rule: "parse-error", casetype: null, path: "", field: null, message, file }]
15277
- });
15278
- for (const file of files) {
15279
- const text = readFileSync2(file, "utf8");
15280
- if (!isLintableYaml(text)) continue;
15281
- let docs;
15282
- try {
15283
- docs = (0, import_yaml3.parseAllDocuments)(text);
15284
- } catch (e) {
15285
- out.push(parseErr(file, e.message));
15286
- continue;
15287
- }
15288
- for (const doc of docs) {
15289
- if (doc.errors.length > 0) {
15290
- out.push(parseErr(file, doc.errors[0].message));
15291
- continue;
15292
- }
15293
- const resolved = toJSOrError(doc);
15294
- if (resolved.error !== void 0) {
15295
- out.push(parseErr(file, resolved.error));
15296
- continue;
15297
- }
15298
- const input = { value: resolved.value, file };
15299
- const result = lintDocument({ registry: ctx.registry, coverage: ctx.coverage, settings }, input);
15300
- const refs = referenceFindings(input.value, file, caseRefs, settings);
15301
- if (refs.length) {
15302
- result.messages.push(...refs);
15303
- if (result.ok && refs.some((m) => meetsThreshold(m.severity, settings.failOn))) result.ok = false;
15304
- }
15305
- out.push(result);
15306
- }
15338
+ };
15339
+ }
15340
+ function lintOneDoc(doc, file, ctx, settings, caseRefs) {
15341
+ if (doc.errors.length > 0) return parseErrorResult(file, doc.errors[0].message, settings);
15342
+ const resolved = toJSOrError(doc);
15343
+ if (resolved.error !== void 0) return parseErrorResult(file, resolved.error, settings);
15344
+ const input = { value: resolved.value, file };
15345
+ const result = lintDocument({ registry: ctx.registry, coverage: ctx.coverage, settings }, input);
15346
+ const refs = referenceFindings(input.value, file, caseRefs, settings);
15347
+ if (refs.length) {
15348
+ result.messages.push(...refs);
15349
+ if (result.ok && refs.some((m) => meetsThreshold(m.severity, settings.failOn))) result.ok = false;
15350
+ }
15351
+ return result;
15352
+ }
15353
+ function lintOneFile(file, ctx, settings, caseRefs) {
15354
+ const text = readFileSync2(file, "utf8");
15355
+ if (!isLintableYaml(text)) return [];
15356
+ let docs;
15357
+ try {
15358
+ docs = (0, import_yaml3.parseAllDocuments)(text);
15359
+ } catch (e) {
15360
+ return [parseErrorResult(file, e.message, settings)];
15307
15361
  }
15308
- return out;
15362
+ return docs.map((doc) => lintOneDoc(doc, file, ctx, settings, caseRefs));
15363
+ }
15364
+ function lintFiles(files, ctx, settings) {
15365
+ const caseRefs = loadCaseRefs(ctx.schemasDir);
15366
+ return files.flatMap((file) => lintOneFile(file, ctx, settings, caseRefs));
15309
15367
  }
15310
15368
  async function runParallel(files, settings, schemasDir) {
15311
15369
  const n = Math.min(MAX_WORKERS, availableParallelism(), files.length);
package/lsp.js CHANGED
@@ -23956,12 +23956,108 @@ function meetsThreshold(severity, failOn) {
23956
23956
  function messageKey(m) {
23957
23957
  return `${m.rule}|${m.path}|${m.severity}|${m.message}`;
23958
23958
  }
23959
+ var ANY_OF_REQUIRED = /^(.*\/anyOf)\/\d+\/required$/;
23960
+ function requireAnyKey(e) {
23961
+ if (e.keyword !== "required") return null;
23962
+ const m = ANY_OF_REQUIRED.exec(e.schemaPath);
23963
+ return m ? `${m[1]}@${e.instancePath}` : null;
23964
+ }
23965
+ function collectMapState(errors) {
23966
+ const forbidden = /* @__PURE__ */ new Set();
23967
+ const requireAny = /* @__PURE__ */ new Map();
23968
+ for (const e of errors) {
23969
+ if (e.keyword === "false schema") forbidden.add(e.instancePath);
23970
+ const key2 = requireAnyKey(e);
23971
+ if (key2 === null) continue;
23972
+ const g = requireAny.get(key2) ?? { instancePath: e.instancePath, fields: [], err: e };
23973
+ g.fields.push(String(e.params.missingProperty));
23974
+ requireAny.set(key2, g);
23975
+ }
23976
+ return { forbidden, requireAny, emittedRequireAny: /* @__PURE__ */ new Set() };
23977
+ }
23978
+ function pushRequireAny(e, state, ctx2) {
23979
+ const key2 = requireAnyKey(e);
23980
+ const g = key2 === null ? null : state.requireAny.get(key2);
23981
+ if (!g || g.fields.length < 2) return false;
23982
+ if (!state.emittedRequireAny.has(key2)) {
23983
+ state.emittedRequireAny.add(key2);
23984
+ const where = describeState(ctx2.schema, g.err, ctx2.root, ctx2.resolve);
23985
+ const suffix = where ? ` ${where}` : "";
23986
+ ctx2.push("required-field", g.instancePath, null, `missing at least one of: ${g.fields.join(", ")}${suffix}`);
23987
+ }
23988
+ return true;
23989
+ }
23990
+ function pushValueError(e, loc, ctx2) {
23991
+ const p = e.params;
23992
+ const allowed = e.keyword === "enum" ? p.allowedValues ?? [] : [p.allowedValue];
23993
+ const actual = valueAtPointer(ctx2.root, e.instancePath);
23994
+ const want = caseOnlyMatch(actual, allowed);
23995
+ if (want != null) {
23996
+ ctx2.push("value-case", loc.path, loc.field, `"${String(actual)}" should be "${want}" (case/format mismatch)`);
23997
+ return;
23998
+ }
23999
+ const got = actual === void 0 ? "absent" : JSON.stringify(actual);
24000
+ ctx2.push("invalid-value", loc.path, loc.field, `got ${got} \u2014 ${humanize(e)}`);
24001
+ }
24002
+ function receivedType(v) {
24003
+ if (v === null) return "null";
24004
+ if (Array.isArray(v)) return "array";
24005
+ return typeof v;
24006
+ }
24007
+ function pushTypeError(e, loc, ctx2) {
24008
+ const actual = valueAtPointer(ctx2.root, e.instancePath);
24009
+ let val = JSON.stringify(actual) ?? "";
24010
+ if (val.length > 60) val = `${val.slice(0, 57)}\u2026"`;
24011
+ const expected = String(e.params.type);
24012
+ const withValue = actual === void 0 ? "" : ` with value ${val}`;
24013
+ ctx2.push("invalid-type", loc.path, loc.field, `expected ${expected} but received ${receivedType(actual)}${withValue}`);
24014
+ }
24015
+ function pushSchemaError(e, loc, ctx2) {
24016
+ const rule = ruleForKeyword(e.keyword);
24017
+ let message = humanize(e);
24018
+ if (rule === "forbidden-field" || rule === "required-field") {
24019
+ const where = describeState(ctx2.schema, e, ctx2.root, ctx2.resolve);
24020
+ if (where) message += ` ${where}`;
24021
+ }
24022
+ ctx2.push(rule, loc.path, loc.field, message);
24023
+ }
24024
+ function mapError(e, state, ctx2) {
24025
+ if (META_KEYWORDS.has(e.keyword)) return;
24026
+ if (pushRequireAny(e, state, ctx2)) return;
24027
+ const loc = locate(e);
24028
+ const isAdditional = e.keyword === "additionalProperties" || e.keyword === "unevaluatedProperties";
24029
+ if (isAdditional && state.forbidden.has(loc.path)) return;
24030
+ if (e.keyword === "enum" || e.keyword === "const") pushValueError(e, loc, ctx2);
24031
+ else if (e.keyword === "type") pushTypeError(e, loc, ctx2);
24032
+ else pushSchemaError(e, loc, ctx2);
24033
+ }
24034
+ function mapAjvErrors(errors, ctx2) {
24035
+ const state = collectMapState(errors);
24036
+ for (const e of errors) mapError(e, state, ctx2);
24037
+ }
24038
+ function isSkipped(casetype, settings2) {
24039
+ if (casetype != null && settings2.ignoreCasetypes.includes(casetype)) return true;
24040
+ return settings2.includeCasetypes.length > 0 && (casetype == null || !settings2.includeCasetypes.includes(casetype));
24041
+ }
24042
+ function pushUncovered(push, coverage, casetype) {
24043
+ if (!coverage || !casetype) return;
24044
+ const n = coverage.uncoveredCount(casetype);
24045
+ if (n > 0) push("unknown-casetype", "", casetype, `${n} rule clause(s) for "${casetype}" are behaviour-only and not checked by the schema`);
24046
+ }
24047
+ function dedupeMessages(messages) {
24048
+ const seen = /* @__PURE__ */ new Set();
24049
+ return messages.filter((m) => {
24050
+ const k = messageKey(m);
24051
+ if (seen.has(k)) return false;
24052
+ seen.add(k);
24053
+ return true;
24054
+ });
24055
+ }
23959
24056
  function lintDocument(ctx2, input) {
23960
24057
  const settings2 = normalizeSettings(ctx2.settings);
23961
24058
  const casetype = detectCasetype(input);
24059
+ if (isSkipped(casetype, settings2)) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
23962
24060
  const messages = [];
23963
- const skipped = casetype != null && settings2.ignoreCasetypes.includes(casetype) || settings2.includeCasetypes.length > 0 && (casetype == null || !settings2.includeCasetypes.includes(casetype));
23964
- if (skipped) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
23965
24061
  const push = (rule, path, field, message) => {
23966
24062
  const sev = severityFor(rule, settings2);
23967
24063
  if (sev === "off") return;
@@ -23971,86 +24067,15 @@ function lintDocument(ctx2, input) {
23971
24067
  if (!validate) {
23972
24068
  push("unknown-casetype", "", casetype, casetype ? `no schema for casetype "${casetype}"` : "could not determine casetype (no `_type`)");
23973
24069
  } else if (!validate(input.value)) {
23974
- const errors = validate.errors ?? [];
23975
- const schema = validate.schema;
23976
- const forbidden = /* @__PURE__ */ new Set();
23977
- for (const e of errors) if (e.keyword === "false schema") forbidden.add(e.instancePath);
23978
- const anyOfRe = /^(.*\/anyOf)\/\d+\/required$/;
23979
- const reqAnyGroups = /* @__PURE__ */ new Map();
23980
- for (const e of errors) {
23981
- const m = e.keyword === "required" ? anyOfRe.exec(e.schemaPath) : null;
23982
- if (!m) continue;
23983
- const key2 = `${m[1]}@${e.instancePath}`;
23984
- const g = reqAnyGroups.get(key2) ?? { instancePath: e.instancePath, fields: [], err: e };
23985
- g.fields.push(String(e.params.missingProperty));
23986
- reqAnyGroups.set(key2, g);
23987
- }
23988
- const emittedReqAny = /* @__PURE__ */ new Set();
23989
- for (const e of errors) {
23990
- if (META_KEYWORDS.has(e.keyword)) continue;
23991
- if (e.keyword === "required") {
23992
- const m = anyOfRe.exec(e.schemaPath);
23993
- const key2 = m ? `${m[1]}@${e.instancePath}` : null;
23994
- const g = key2 ? reqAnyGroups.get(key2) : null;
23995
- if (g && g.fields.length >= 2) {
23996
- if (!emittedReqAny.has(key2)) {
23997
- emittedReqAny.add(key2);
23998
- const state = describeState(schema, g.err, input.value, ctx2.registry.resolveRef);
23999
- const stateSuffix = state ? ` ${state}` : "";
24000
- push("required-field", g.instancePath, null, `missing at least one of: ${g.fields.join(", ")}${stateSuffix}`);
24001
- }
24002
- continue;
24003
- }
24004
- }
24005
- const { field, path } = locate(e);
24006
- if ((e.keyword === "additionalProperties" || e.keyword === "unevaluatedProperties") && forbidden.has(path)) {
24007
- continue;
24008
- }
24009
- if (e.keyword === "enum" || e.keyword === "const") {
24010
- const p = e.params;
24011
- const allowed = e.keyword === "enum" ? p.allowedValues ?? [] : [p.allowedValue];
24012
- const actual = valueAtPointer(input.value, e.instancePath);
24013
- const want = caseOnlyMatch(actual, allowed);
24014
- if (want != null) {
24015
- push("value-case", path, field, `"${String(actual)}" should be "${want}" (case/format mismatch)`);
24016
- continue;
24017
- }
24018
- const got = actual === void 0 ? "absent" : JSON.stringify(actual);
24019
- push("invalid-value", path, field, `got ${got} \u2014 ${humanize(e)}`);
24020
- continue;
24021
- }
24022
- if (e.keyword === "type") {
24023
- const actual = valueAtPointer(input.value, e.instancePath);
24024
- let recv = typeof actual;
24025
- if (actual === null) recv = "null";
24026
- else if (Array.isArray(actual)) recv = "array";
24027
- let val = JSON.stringify(actual) ?? "";
24028
- if (val.length > 60) val = `${val.slice(0, 57)}\u2026"`;
24029
- const expected = String(e.params.type);
24030
- const withValue = actual === void 0 ? "" : ` with value ${val}`;
24031
- push("invalid-type", path, field, `expected ${expected} but received ${recv}${withValue}`);
24032
- continue;
24033
- }
24034
- const rule = ruleForKeyword(e.keyword);
24035
- let message = humanize(e);
24036
- if (rule === "forbidden-field" || rule === "required-field") {
24037
- const state = describeState(schema, e, input.value, ctx2.registry.resolveRef);
24038
- if (state) message += ` ${state}`;
24039
- }
24040
- push(rule, path, field, message);
24041
- }
24042
- }
24043
- if (settings2.reportUncovered && ctx2.coverage && casetype) {
24044
- const n = ctx2.coverage.uncoveredCount(casetype);
24045
- if (n > 0) push("unknown-casetype", "", casetype, `${n} rule clause(s) for "${casetype}" are behaviour-only and not checked by the schema`);
24070
+ mapAjvErrors(validate.errors ?? [], {
24071
+ push,
24072
+ schema: validate.schema,
24073
+ root: input.value,
24074
+ resolve: ctx2.registry.resolveRef
24075
+ });
24046
24076
  }
24047
- const seen = /* @__PURE__ */ new Set();
24048
- const deduped = messages.filter((m) => {
24049
- const k = messageKey(m);
24050
- if (seen.has(k)) return false;
24051
- seen.add(k);
24052
- return true;
24053
- });
24077
+ if (settings2.reportUncovered) pushUncovered(push, ctx2.coverage, casetype);
24078
+ const deduped = dedupeMessages(messages);
24054
24079
  const ok = !deduped.some((m) => meetsThreshold(m.severity, settings2.failOn));
24055
24080
  return { file: input.file, casetype, messages: deduped, skipped: false, ok };
24056
24081
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grexx/grexxlinter",
3
- "version": "0.2.1372",
3
+ "version": "0.2.1408",
4
4
  "description": "Lint Grexx Studio casetype YAML against the studio JSON Schemas.",
5
5
  "type": "module",
6
6
  "bin": {