@grexx/grexxlinter 0.2.1372 → 0.2.1429
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 +208 -150
- package/lsp.js +106 -81
- package/package.json +1 -1
- package/schemas/folderInfo.schema.json +7 -1
- package/schemas/labelRegistry.schema.json +6 -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
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
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
|
-
|
|
15090
|
-
const deduped = messages
|
|
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 =
|
|
15155
|
+
var VERSION = "0.2.1429";
|
|
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
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
|
|
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
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
if (
|
|
15215
|
-
|
|
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
|
|
15269
|
-
|
|
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
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
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
|
|
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
|
-
|
|
23975
|
-
|
|
23976
|
-
|
|
23977
|
-
|
|
23978
|
-
|
|
23979
|
-
|
|
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
|
-
|
|
24048
|
-
const deduped = messages
|
|
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
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
"type": "string"
|
|
16
16
|
},
|
|
17
17
|
"folderName": {
|
|
18
|
-
"type": "string"
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Legacy echo of the folder's own path leaf, written by the studio's create-component action. Never read for display. Kept so existing repos stay valid; new presentation names belong in `displayName`."
|
|
20
|
+
},
|
|
21
|
+
"displayName": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"minLength": 1,
|
|
24
|
+
"description": "How the component is PRESENTED — its page heading, cards, filters, breadcrumbs. A component's REFERENCE is its folder PATH, which is how every file beneath it is addressed; renaming that is a git move of every blob and is deliberately not offered. This field is free to change instead: it lives only in this file, so editing it is one write that moves nothing. Omitted = fall back to `folderName`, then to the path leaf — which is what every component showed before this field existed."
|
|
19
25
|
},
|
|
20
26
|
"folderType": {
|
|
21
27
|
"type": "string"
|
|
@@ -32,7 +32,12 @@
|
|
|
32
32
|
"name": {
|
|
33
33
|
"type": "string",
|
|
34
34
|
"minLength": 1,
|
|
35
|
-
"description": "
|
|
35
|
+
"description": "The label's REFERENCE: the exact text written into each object's `_labels`, and what every carrying file is matched on. Treat it as an identifier, not a caption — changing it means rewriting every file that carries the label, so the studio does not offer that. Rename `displayName` instead."
|
|
36
|
+
},
|
|
37
|
+
"displayName": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 1,
|
|
40
|
+
"description": "How the label is PRESENTED — pills, headings, pickers, filters. Free to change at any time: it lives only here, so editing it is one write to this file and touches no carrying file, breaks no `_labels` match and changes no URL. Omitted = fall back to `name`, which is what every label had before this field existed."
|
|
36
41
|
},
|
|
37
42
|
"color": {
|
|
38
43
|
"enum": [
|