@grexx/grexxlinter 0.2.1318 → 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.
- package/cli.js +216 -150
- package/lsp.js +107 -79
- package/package.json +1 -1
- package/schemas/widget.schema.json +274 -0
package/cli.js
CHANGED
|
@@ -14547,8 +14547,7 @@ function buildCoverageIndex(doc) {
|
|
|
14547
14547
|
return {
|
|
14548
14548
|
clauseCovered(casetype, field, modality, when) {
|
|
14549
14549
|
if (!casetype || !field) return null;
|
|
14550
|
-
|
|
14551
|
-
return v === void 0 ? null : v;
|
|
14550
|
+
return map.get(key(casetype, field, modality, when)) ?? null;
|
|
14552
14551
|
},
|
|
14553
14552
|
uncoveredCount: (casetype) => uncovered.get(casetype) ?? 0
|
|
14554
14553
|
};
|
|
@@ -14999,12 +14998,108 @@ function meetsThreshold(severity, failOn) {
|
|
|
14999
14998
|
function messageKey(m) {
|
|
15000
14999
|
return `${m.rule}|${m.path}|${m.severity}|${m.message}`;
|
|
15001
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
|
+
}
|
|
15002
15098
|
function lintDocument(ctx, input) {
|
|
15003
15099
|
const settings = normalizeSettings(ctx.settings);
|
|
15004
15100
|
const casetype = detectCasetype(input);
|
|
15101
|
+
if (isSkipped(casetype, settings)) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
|
|
15005
15102
|
const messages = [];
|
|
15006
|
-
const skipped = casetype != null && settings.ignoreCasetypes.includes(casetype) || settings.includeCasetypes.length > 0 && (casetype == null || !settings.includeCasetypes.includes(casetype));
|
|
15007
|
-
if (skipped) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
|
|
15008
15103
|
const push = (rule, path, field, message) => {
|
|
15009
15104
|
const sev = severityFor(rule, settings);
|
|
15010
15105
|
if (sev === "off") return;
|
|
@@ -15014,82 +15109,15 @@ function lintDocument(ctx, input) {
|
|
|
15014
15109
|
if (!validate) {
|
|
15015
15110
|
push("unknown-casetype", "", casetype, casetype ? `no schema for casetype "${casetype}"` : "could not determine casetype (no `_type`)");
|
|
15016
15111
|
} else if (!validate(input.value)) {
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
for (const e of errors) {
|
|
15024
|
-
const m = e.keyword === "required" ? anyOfRe.exec(e.schemaPath) : null;
|
|
15025
|
-
if (!m) continue;
|
|
15026
|
-
const key2 = `${m[1]}@${e.instancePath}`;
|
|
15027
|
-
const g = reqAnyGroups.get(key2) ?? { instancePath: e.instancePath, fields: [], err: e };
|
|
15028
|
-
g.fields.push(String(e.params.missingProperty));
|
|
15029
|
-
reqAnyGroups.set(key2, g);
|
|
15030
|
-
}
|
|
15031
|
-
const emittedReqAny = /* @__PURE__ */ new Set();
|
|
15032
|
-
for (const e of errors) {
|
|
15033
|
-
if (META_KEYWORDS.has(e.keyword)) continue;
|
|
15034
|
-
if (e.keyword === "required") {
|
|
15035
|
-
const m = anyOfRe.exec(e.schemaPath);
|
|
15036
|
-
const key2 = m ? `${m[1]}@${e.instancePath}` : null;
|
|
15037
|
-
const g = key2 ? reqAnyGroups.get(key2) : null;
|
|
15038
|
-
if (g && g.fields.length >= 2) {
|
|
15039
|
-
if (!emittedReqAny.has(key2)) {
|
|
15040
|
-
emittedReqAny.add(key2);
|
|
15041
|
-
const state = describeState(schema, g.err, input.value, ctx.registry.resolveRef);
|
|
15042
|
-
push("required-field", g.instancePath, null, `missing at least one of: ${g.fields.join(", ")}${state ? ` ${state}` : ""}`);
|
|
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
|
-
const recv = actual === null ? "null" : Array.isArray(actual) ? "array" : typeof actual;
|
|
15067
|
-
let val = JSON.stringify(actual) ?? "";
|
|
15068
|
-
if (val.length > 60) val = `${val.slice(0, 57)}\u2026"`;
|
|
15069
|
-
const expected = String(e.params.type);
|
|
15070
|
-
push("invalid-type", path, field, `expected ${expected} but received ${recv}${actual === void 0 ? "" : ` with value ${val}`}`);
|
|
15071
|
-
continue;
|
|
15072
|
-
}
|
|
15073
|
-
const rule = ruleForKeyword(e.keyword);
|
|
15074
|
-
let message = humanize(e);
|
|
15075
|
-
if (rule === "forbidden-field" || rule === "required-field") {
|
|
15076
|
-
const state = describeState(schema, e, input.value, ctx.registry.resolveRef);
|
|
15077
|
-
if (state) message += ` ${state}`;
|
|
15078
|
-
}
|
|
15079
|
-
push(rule, path, field, message);
|
|
15080
|
-
}
|
|
15081
|
-
}
|
|
15082
|
-
if (settings.reportUncovered && ctx.coverage && casetype) {
|
|
15083
|
-
const n = ctx.coverage.uncoveredCount(casetype);
|
|
15084
|
-
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
|
+
});
|
|
15085
15118
|
}
|
|
15086
|
-
|
|
15087
|
-
const deduped = messages
|
|
15088
|
-
const k = messageKey(m);
|
|
15089
|
-
if (seen.has(k)) return false;
|
|
15090
|
-
seen.add(k);
|
|
15091
|
-
return true;
|
|
15092
|
-
});
|
|
15119
|
+
if (settings.reportUncovered) pushUncovered(push, ctx.coverage, casetype);
|
|
15120
|
+
const deduped = dedupeMessages(messages);
|
|
15093
15121
|
const ok = !deduped.some((m) => meetsThreshold(m.severity, settings.failOn));
|
|
15094
15122
|
return { file: input.file, casetype, messages: deduped, skipped: false, ok };
|
|
15095
15123
|
}
|
|
@@ -15124,7 +15152,7 @@ function toJSOrError(doc) {
|
|
|
15124
15152
|
// src/cli.ts
|
|
15125
15153
|
var PARALLEL_THRESHOLD = 200;
|
|
15126
15154
|
var MAX_WORKERS = 8;
|
|
15127
|
-
var VERSION =
|
|
15155
|
+
var VERSION = "0.2.1408";
|
|
15128
15156
|
function isNewer(latest, current) {
|
|
15129
15157
|
const a = latest.split(".").map(Number);
|
|
15130
15158
|
const b = current.split(".").map(Number);
|
|
@@ -15144,26 +15172,60 @@ function checkForUpdate(disabled) {
|
|
|
15144
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));
|
|
15145
15173
|
}
|
|
15146
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
|
+
]);
|
|
15147
15213
|
function parseArgs(argv) {
|
|
15148
15214
|
const args = { paths: [], off: [], format: "text", quiet: false, errorsOnly: false, noUpdateCheck: false };
|
|
15149
15215
|
for (let i = 0; i < argv.length; i++) {
|
|
15150
15216
|
const a = argv[i];
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
else if (a === "--errors-only") args.errorsOnly = true;
|
|
15164
|
-
else if (a === "--no-update-check") args.noUpdateCheck = true;
|
|
15165
|
-
else if (a.startsWith("--")) throw new Error(`unknown option: ${a}`);
|
|
15166
|
-
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);
|
|
15167
15229
|
}
|
|
15168
15230
|
if (args.report && args.report !== "rules" && args.report !== "fields") {
|
|
15169
15231
|
throw new Error(`--report must be "rules" or "fields" (got "${args.report}")`);
|
|
@@ -15195,34 +15257,43 @@ function discover(paths, exclude) {
|
|
|
15195
15257
|
for (const p of paths) walk(p);
|
|
15196
15258
|
return files;
|
|
15197
15259
|
}
|
|
15260
|
+
var KEY_SEP = String.fromCodePoint(0);
|
|
15198
15261
|
var COLORS = { error: "\x1B[31m", warning: "\x1B[33m", info: "\x1B[36m" };
|
|
15199
15262
|
var RESET = "\x1B[0m";
|
|
15200
15263
|
var useColor = process.stdout.isTTY;
|
|
15201
15264
|
var paint = (s, c) => useColor ? `${c}${s}${RESET}` : s;
|
|
15202
|
-
function
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
}
|
|
15213
|
-
console.log(head);
|
|
15214
|
-
for (const m of msgs) {
|
|
15215
|
-
const loc = m.path || "/";
|
|
15216
|
-
console.log(` ${paint(m.severity.padEnd(7), COLORS[m.severity])} ${m.rule.padEnd(16)} ${loc} ${m.message}`);
|
|
15217
|
-
}
|
|
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;
|
|
15218
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) {
|
|
15219
15283
|
const s = summarize(results.filter((r) => !r.skipped));
|
|
15220
15284
|
const skipped = results.filter((r) => r.skipped).length;
|
|
15285
|
+
const skippedNote = skipped ? `, ${skipped} skipped` : "";
|
|
15286
|
+
const errorsText = paint(`${s.errors} error(s)`, COLORS.error);
|
|
15287
|
+
const warningsText = paint(`${s.warnings} warning(s)`, COLORS.warning);
|
|
15221
15288
|
console.log(
|
|
15222
15289
|
`
|
|
15223
|
-
${s.files} file(s)${
|
|
15290
|
+
${s.files} file(s)${skippedNote} \u2014 ${errorsText}, ${warningsText}, ${s.infos} info(s) [fail-on: ${settings.failOn}]`
|
|
15224
15291
|
);
|
|
15225
15292
|
}
|
|
15293
|
+
function printText(results, settings, quiet, errorsOnly) {
|
|
15294
|
+
for (const r of results) printResult(r, quiet, errorsOnly);
|
|
15295
|
+
printSummary(results, settings);
|
|
15296
|
+
}
|
|
15226
15297
|
function printReport(results, kind, errorsOnly) {
|
|
15227
15298
|
let msgs = results.filter((r) => !r.skipped).flatMap((r) => r.messages);
|
|
15228
15299
|
if (errorsOnly) msgs = msgs.filter((m) => m.severity === "error");
|
|
@@ -15243,7 +15314,7 @@ function printReport(results, kind, errorsOnly) {
|
|
|
15243
15314
|
}
|
|
15244
15315
|
const by = /* @__PURE__ */ new Map();
|
|
15245
15316
|
for (const m of msgs) {
|
|
15246
|
-
const key2 =
|
|
15317
|
+
const key2 = [m.casetype ?? "?", m.rule, m.severity, m.message].join(KEY_SEP);
|
|
15247
15318
|
const e = by.get(key2) ?? { count: 0, casetype: m.casetype ?? "?", severity: m.severity, message: m.message };
|
|
15248
15319
|
e.count++;
|
|
15249
15320
|
by.set(key2, e);
|
|
@@ -15257,47 +15328,42 @@ function printReport(results, kind, errorsOnly) {
|
|
|
15257
15328
|
}
|
|
15258
15329
|
}
|
|
15259
15330
|
var WORKER_FILE = fileURLToPath2(import.meta.url);
|
|
15260
|
-
function
|
|
15261
|
-
|
|
15262
|
-
const caseRefs = loadCaseRefs(ctx.schemasDir);
|
|
15263
|
-
const parseErr = (file, message) => ({
|
|
15331
|
+
function parseErrorResult(file, message, settings) {
|
|
15332
|
+
return {
|
|
15264
15333
|
file,
|
|
15265
15334
|
casetype: null,
|
|
15266
15335
|
skipped: false,
|
|
15267
15336
|
ok: settings.failOn === "never",
|
|
15268
15337
|
messages: [{ severity: "error", rule: "parse-error", casetype: null, path: "", field: null, message, file }]
|
|
15269
|
-
}
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
const refs = referenceFindings(input.value, file, caseRefs, settings);
|
|
15293
|
-
if (refs.length) {
|
|
15294
|
-
result.messages.push(...refs);
|
|
15295
|
-
if (result.ok && refs.some((m) => meetsThreshold(m.severity, settings.failOn))) result.ok = false;
|
|
15296
|
-
}
|
|
15297
|
-
out.push(result);
|
|
15298
|
-
}
|
|
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)];
|
|
15299
15361
|
}
|
|
15300
|
-
return
|
|
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));
|
|
15301
15367
|
}
|
|
15302
15368
|
async function runParallel(files, settings, schemasDir) {
|
|
15303
15369
|
const n = Math.min(MAX_WORKERS, availableParallelism(), files.length);
|
|
@@ -15363,5 +15429,5 @@ async function main() {
|
|
|
15363
15429
|
}
|
|
15364
15430
|
process.exit(summarize(results.filter((r) => !r.skipped)).exitCode);
|
|
15365
15431
|
}
|
|
15366
|
-
if (isMainThread)
|
|
15432
|
+
if (isMainThread) await main();
|
|
15367
15433
|
else runWorker();
|
package/lsp.js
CHANGED
|
@@ -23616,8 +23616,7 @@ function buildCoverageIndex(doc) {
|
|
|
23616
23616
|
return {
|
|
23617
23617
|
clauseCovered(casetype, field, modality, when) {
|
|
23618
23618
|
if (!casetype || !field) return null;
|
|
23619
|
-
|
|
23620
|
-
return v === void 0 ? null : v;
|
|
23619
|
+
return map.get(key(casetype, field, modality, when)) ?? null;
|
|
23621
23620
|
},
|
|
23622
23621
|
uncoveredCount: (casetype) => uncovered.get(casetype) ?? 0
|
|
23623
23622
|
};
|
|
@@ -23957,12 +23956,108 @@ function meetsThreshold(severity, failOn) {
|
|
|
23957
23956
|
function messageKey(m) {
|
|
23958
23957
|
return `${m.rule}|${m.path}|${m.severity}|${m.message}`;
|
|
23959
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
|
+
}
|
|
23960
24056
|
function lintDocument(ctx2, input) {
|
|
23961
24057
|
const settings2 = normalizeSettings(ctx2.settings);
|
|
23962
24058
|
const casetype = detectCasetype(input);
|
|
24059
|
+
if (isSkipped(casetype, settings2)) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
|
|
23963
24060
|
const messages = [];
|
|
23964
|
-
const skipped = casetype != null && settings2.ignoreCasetypes.includes(casetype) || settings2.includeCasetypes.length > 0 && (casetype == null || !settings2.includeCasetypes.includes(casetype));
|
|
23965
|
-
if (skipped) return { file: input.file, casetype, messages: [], skipped: true, ok: true };
|
|
23966
24061
|
const push = (rule, path, field, message) => {
|
|
23967
24062
|
const sev = severityFor(rule, settings2);
|
|
23968
24063
|
if (sev === "off") return;
|
|
@@ -23972,82 +24067,15 @@ function lintDocument(ctx2, input) {
|
|
|
23972
24067
|
if (!validate) {
|
|
23973
24068
|
push("unknown-casetype", "", casetype, casetype ? `no schema for casetype "${casetype}"` : "could not determine casetype (no `_type`)");
|
|
23974
24069
|
} else if (!validate(input.value)) {
|
|
23975
|
-
|
|
23976
|
-
|
|
23977
|
-
|
|
23978
|
-
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
for (const e of errors) {
|
|
23982
|
-
const m = e.keyword === "required" ? anyOfRe.exec(e.schemaPath) : null;
|
|
23983
|
-
if (!m) continue;
|
|
23984
|
-
const key2 = `${m[1]}@${e.instancePath}`;
|
|
23985
|
-
const g = reqAnyGroups.get(key2) ?? { instancePath: e.instancePath, fields: [], err: e };
|
|
23986
|
-
g.fields.push(String(e.params.missingProperty));
|
|
23987
|
-
reqAnyGroups.set(key2, g);
|
|
23988
|
-
}
|
|
23989
|
-
const emittedReqAny = /* @__PURE__ */ new Set();
|
|
23990
|
-
for (const e of errors) {
|
|
23991
|
-
if (META_KEYWORDS.has(e.keyword)) continue;
|
|
23992
|
-
if (e.keyword === "required") {
|
|
23993
|
-
const m = anyOfRe.exec(e.schemaPath);
|
|
23994
|
-
const key2 = m ? `${m[1]}@${e.instancePath}` : null;
|
|
23995
|
-
const g = key2 ? reqAnyGroups.get(key2) : null;
|
|
23996
|
-
if (g && g.fields.length >= 2) {
|
|
23997
|
-
if (!emittedReqAny.has(key2)) {
|
|
23998
|
-
emittedReqAny.add(key2);
|
|
23999
|
-
const state = describeState(schema, g.err, input.value, ctx2.registry.resolveRef);
|
|
24000
|
-
push("required-field", g.instancePath, null, `missing at least one of: ${g.fields.join(", ")}${state ? ` ${state}` : ""}`);
|
|
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
|
-
const recv = actual === null ? "null" : Array.isArray(actual) ? "array" : typeof actual;
|
|
24025
|
-
let val = JSON.stringify(actual) ?? "";
|
|
24026
|
-
if (val.length > 60) val = `${val.slice(0, 57)}\u2026"`;
|
|
24027
|
-
const expected = String(e.params.type);
|
|
24028
|
-
push("invalid-type", path, field, `expected ${expected} but received ${recv}${actual === void 0 ? "" : ` with value ${val}`}`);
|
|
24029
|
-
continue;
|
|
24030
|
-
}
|
|
24031
|
-
const rule = ruleForKeyword(e.keyword);
|
|
24032
|
-
let message = humanize(e);
|
|
24033
|
-
if (rule === "forbidden-field" || rule === "required-field") {
|
|
24034
|
-
const state = describeState(schema, e, input.value, ctx2.registry.resolveRef);
|
|
24035
|
-
if (state) message += ` ${state}`;
|
|
24036
|
-
}
|
|
24037
|
-
push(rule, path, field, message);
|
|
24038
|
-
}
|
|
24039
|
-
}
|
|
24040
|
-
if (settings2.reportUncovered && ctx2.coverage && casetype) {
|
|
24041
|
-
const n = ctx2.coverage.uncoveredCount(casetype);
|
|
24042
|
-
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
|
+
});
|
|
24043
24076
|
}
|
|
24044
|
-
|
|
24045
|
-
const deduped = messages
|
|
24046
|
-
const k = messageKey(m);
|
|
24047
|
-
if (seen.has(k)) return false;
|
|
24048
|
-
seen.add(k);
|
|
24049
|
-
return true;
|
|
24050
|
-
});
|
|
24077
|
+
if (settings2.reportUncovered) pushUncovered(push, ctx2.coverage, casetype);
|
|
24078
|
+
const deduped = dedupeMessages(messages);
|
|
24051
24079
|
const ok = !deduped.some((m) => meetsThreshold(m.severity, settings2.failOn));
|
|
24052
24080
|
return { file: input.file, casetype, messages: deduped, skipped: false, ok };
|
|
24053
24081
|
}
|
package/package.json
CHANGED
|
@@ -250,6 +250,280 @@
|
|
|
250
250
|
"title": "widget",
|
|
251
251
|
"type": "object",
|
|
252
252
|
"allOf": [
|
|
253
|
+
{
|
|
254
|
+
"$comment": "Shape of the legacy options blob for grids — the only structure it has, since widget.options is declared `additionalProperties: true`. Closed at every level: an unknown key is an additional-field finding. The key set and the types are the union observed over 1275 grid widgets (def-yamls-142 plus three real projects); where the stored spelling and the studio's mapper disagree, both are declared, because both are live.",
|
|
255
|
+
"if": {
|
|
256
|
+
"properties": {
|
|
257
|
+
"widgetType": {
|
|
258
|
+
"enum": [
|
|
259
|
+
"grid",
|
|
260
|
+
"grid-v2"
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"required": [
|
|
265
|
+
"widgetType"
|
|
266
|
+
]
|
|
267
|
+
},
|
|
268
|
+
"then": {
|
|
269
|
+
"properties": {
|
|
270
|
+
"options": {
|
|
271
|
+
"type": "object",
|
|
272
|
+
"additionalProperties": false,
|
|
273
|
+
"properties": {
|
|
274
|
+
"type": {
|
|
275
|
+
"type": "string"
|
|
276
|
+
},
|
|
277
|
+
"usesCases": {
|
|
278
|
+
"type": "boolean"
|
|
279
|
+
},
|
|
280
|
+
"dataset": {
|
|
281
|
+
"type": "string"
|
|
282
|
+
},
|
|
283
|
+
"extraclasses": {
|
|
284
|
+
"$comment": "Extra CSS classes on the widget wrapper. No editor writes it; it rides through from the legacy designer.",
|
|
285
|
+
"type": "string"
|
|
286
|
+
},
|
|
287
|
+
"grid": {
|
|
288
|
+
"type": "object",
|
|
289
|
+
"additionalProperties": false,
|
|
290
|
+
"properties": {
|
|
291
|
+
"showHeader": {
|
|
292
|
+
"type": "boolean"
|
|
293
|
+
},
|
|
294
|
+
"title": {
|
|
295
|
+
"type": "string"
|
|
296
|
+
},
|
|
297
|
+
"showToolbar": {
|
|
298
|
+
"type": "boolean"
|
|
299
|
+
},
|
|
300
|
+
"showSimpleSearch": {
|
|
301
|
+
"type": "boolean"
|
|
302
|
+
},
|
|
303
|
+
"showAdvancedSearch": {
|
|
304
|
+
"type": "boolean"
|
|
305
|
+
},
|
|
306
|
+
"showLinenumbers": {
|
|
307
|
+
"type": "boolean"
|
|
308
|
+
},
|
|
309
|
+
"showColumnHeaders": {
|
|
310
|
+
"type": "boolean"
|
|
311
|
+
},
|
|
312
|
+
"showColumnSelector": {
|
|
313
|
+
"type": "boolean"
|
|
314
|
+
},
|
|
315
|
+
"showFooter": {
|
|
316
|
+
"type": "boolean"
|
|
317
|
+
},
|
|
318
|
+
"noFullCount": {
|
|
319
|
+
"type": "boolean"
|
|
320
|
+
},
|
|
321
|
+
"selectColumn": {
|
|
322
|
+
"type": "boolean"
|
|
323
|
+
},
|
|
324
|
+
"autoHeight": {
|
|
325
|
+
"type": "boolean"
|
|
326
|
+
},
|
|
327
|
+
"saveSearch": {
|
|
328
|
+
"type": "string"
|
|
329
|
+
},
|
|
330
|
+
"orderColumn": {
|
|
331
|
+
"type": "string"
|
|
332
|
+
},
|
|
333
|
+
"height": {
|
|
334
|
+
"$comment": "Every stored value is a string (\"640\"); the studio's mapper writes a number on edit.",
|
|
335
|
+
"type": [
|
|
336
|
+
"string",
|
|
337
|
+
"number"
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
"pagination": {
|
|
341
|
+
"type": "object",
|
|
342
|
+
"additionalProperties": false,
|
|
343
|
+
"properties": {
|
|
344
|
+
"rowsPerPage": {
|
|
345
|
+
"type": [
|
|
346
|
+
"number",
|
|
347
|
+
"string"
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
"inifiteScroll": {
|
|
351
|
+
"$comment": "Misspelled in the atalanta runtime that reads it — this is the wire format.",
|
|
352
|
+
"type": "boolean"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"gridAggregationRowType": {
|
|
357
|
+
"$comment": "Mirror of the first-class gridAggregationRowType; stored as an empty array on all but one of 475 occurrences, so both spellings are allowed.",
|
|
358
|
+
"type": [
|
|
359
|
+
"string",
|
|
360
|
+
"array"
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
"gridAggregationRowPosition": {
|
|
364
|
+
"$comment": "Mirror of the first-class gridAggregationRowPosition; stored as an empty array, as above.",
|
|
365
|
+
"type": [
|
|
366
|
+
"string",
|
|
367
|
+
"array"
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
"row": {
|
|
373
|
+
"type": "object",
|
|
374
|
+
"additionalProperties": false,
|
|
375
|
+
"properties": {
|
|
376
|
+
"clickHandler": {
|
|
377
|
+
"type": "string"
|
|
378
|
+
},
|
|
379
|
+
"clickHandlerColumnCase": {
|
|
380
|
+
"type": "string"
|
|
381
|
+
},
|
|
382
|
+
"rowHeight": {
|
|
383
|
+
"type": [
|
|
384
|
+
"string",
|
|
385
|
+
"number"
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
"viewToOpen": {
|
|
389
|
+
"$comment": "Only read when clickHandler is viewHandler; the studio drops both keys when it is not.",
|
|
390
|
+
"type": "string"
|
|
391
|
+
},
|
|
392
|
+
"customViewModalClasses": {
|
|
393
|
+
"type": "string"
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
"subgrid": {
|
|
398
|
+
"type": "object",
|
|
399
|
+
"additionalProperties": false,
|
|
400
|
+
"properties": {
|
|
401
|
+
"enabled": {
|
|
402
|
+
"type": "boolean"
|
|
403
|
+
},
|
|
404
|
+
"height": {
|
|
405
|
+
"type": [
|
|
406
|
+
"string",
|
|
407
|
+
"number"
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
"columns": {
|
|
413
|
+
"$comment": "Keyed by the dataset column's atalanta case id. On a case-converted grid the entry degrades to a caseId pointer at the gridColumn; on a grid that was never converted it keeps the full inline column settings instead, and those 2798 legacy entries are what the types below are read off. `size` stays untyped: it is a nested map whose own key set has no second source to check against.",
|
|
414
|
+
"type": "object",
|
|
415
|
+
"additionalProperties": {
|
|
416
|
+
"type": "object",
|
|
417
|
+
"additionalProperties": false,
|
|
418
|
+
"properties": {
|
|
419
|
+
"caseId": {
|
|
420
|
+
"type": "string"
|
|
421
|
+
},
|
|
422
|
+
"name": {
|
|
423
|
+
"type": "string"
|
|
424
|
+
},
|
|
425
|
+
"column": {
|
|
426
|
+
"type": "string"
|
|
427
|
+
},
|
|
428
|
+
"title": {
|
|
429
|
+
"type": "string"
|
|
430
|
+
},
|
|
431
|
+
"hidden": {
|
|
432
|
+
"type": "boolean"
|
|
433
|
+
},
|
|
434
|
+
"hideable": {
|
|
435
|
+
"type": "boolean"
|
|
436
|
+
},
|
|
437
|
+
"searchable": {
|
|
438
|
+
"type": "boolean"
|
|
439
|
+
},
|
|
440
|
+
"sortable": {
|
|
441
|
+
"type": "boolean"
|
|
442
|
+
},
|
|
443
|
+
"resizable": {
|
|
444
|
+
"type": "boolean"
|
|
445
|
+
},
|
|
446
|
+
"wordwrap": {
|
|
447
|
+
"type": "boolean"
|
|
448
|
+
},
|
|
449
|
+
"valueAsRowClass": {
|
|
450
|
+
"type": "boolean"
|
|
451
|
+
},
|
|
452
|
+
"render": {
|
|
453
|
+
"type": "string"
|
|
454
|
+
},
|
|
455
|
+
"order": {
|
|
456
|
+
"type": [
|
|
457
|
+
"integer",
|
|
458
|
+
"string"
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
"clickHandler": {
|
|
462
|
+
"type": "string"
|
|
463
|
+
},
|
|
464
|
+
"clickHandlerColumnCase": {
|
|
465
|
+
"type": "string"
|
|
466
|
+
},
|
|
467
|
+
"aggregationOperation": {
|
|
468
|
+
"$comment": "Written here by the studio's mapper, but absent from every stored legacy entry — no observed value to type it from, so the key is accepted untyped."
|
|
469
|
+
},
|
|
470
|
+
"size": {
|
|
471
|
+
"type": "object"
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
"activities": {
|
|
477
|
+
"$comment": "Keyed by slot — addActivityId / editActivity / finishActivity / multiNN. Unlike columns these keep their full legacy field set alongside the caseId pointer, so every key here is observed and typed.",
|
|
478
|
+
"type": "object",
|
|
479
|
+
"additionalProperties": {
|
|
480
|
+
"type": "object",
|
|
481
|
+
"additionalProperties": false,
|
|
482
|
+
"properties": {
|
|
483
|
+
"caseId": {
|
|
484
|
+
"type": "string"
|
|
485
|
+
},
|
|
486
|
+
"activity": {
|
|
487
|
+
"type": "string"
|
|
488
|
+
},
|
|
489
|
+
"enabled": {
|
|
490
|
+
"type": "boolean"
|
|
491
|
+
},
|
|
492
|
+
"title": {
|
|
493
|
+
"type": "string"
|
|
494
|
+
},
|
|
495
|
+
"useActivityTitle": {
|
|
496
|
+
"type": "boolean"
|
|
497
|
+
},
|
|
498
|
+
"icon": {
|
|
499
|
+
"type": "string"
|
|
500
|
+
},
|
|
501
|
+
"type": {
|
|
502
|
+
"type": "string"
|
|
503
|
+
},
|
|
504
|
+
"checkRights": {
|
|
505
|
+
"type": "string"
|
|
506
|
+
},
|
|
507
|
+
"whenDoneRefreshType": {
|
|
508
|
+
"type": "string"
|
|
509
|
+
},
|
|
510
|
+
"breaker": {
|
|
511
|
+
"type": "boolean"
|
|
512
|
+
},
|
|
513
|
+
"order": {
|
|
514
|
+
"type": [
|
|
515
|
+
"integer",
|
|
516
|
+
"string"
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
},
|
|
253
527
|
{
|
|
254
528
|
"if": {
|
|
255
529
|
"properties": {
|