@grexx/grexxlinter 0.2.348 → 0.2.350

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 CHANGED
@@ -14456,7 +14456,7 @@ var require__ = __commonJS({
14456
14456
  });
14457
14457
 
14458
14458
  // src/cli.ts
14459
- var import_yaml2 = __toESM(require_dist(), 1);
14459
+ var import_yaml3 = __toESM(require_dist(), 1);
14460
14460
  import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
14461
14461
  import { availableParallelism } from "node:os";
14462
14462
  import { extname, join as join2, relative } from "node:path";
@@ -14464,6 +14464,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
14464
14464
  import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
14465
14465
 
14466
14466
  // src/node.ts
14467
+ var import_yaml = __toESM(require_dist(), 1);
14467
14468
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
14468
14469
  import { createRequire } from "node:module";
14469
14470
  import { dirname, join, resolve } from "node:path";
@@ -14569,47 +14570,6 @@ function createSchemaRegistry(schemas) {
14569
14570
  };
14570
14571
  }
14571
14572
 
14572
- // src/node.ts
14573
- var require2 = createRequire(import.meta.url);
14574
- function resolveSchemasDir(override) {
14575
- if (override) return resolve(override);
14576
- if (process.env.SCHEMAS_DIR) return resolve(process.env.SCHEMAS_DIR);
14577
- const bundled = resolve(dirname(fileURLToPath(import.meta.url)), "schemas");
14578
- if (existsSync(bundled)) return bundled;
14579
- const ref = require2.resolve("@studio/schemas/extended/casetype.schema.json");
14580
- return dirname(ref);
14581
- }
14582
- function loadSchemasFromDir(dir) {
14583
- const out = [];
14584
- const walk = (d) => {
14585
- for (const entry of readdirSync(d, { withFileTypes: true })) {
14586
- const p = join(d, entry.name);
14587
- if (entry.isDirectory()) walk(p);
14588
- else if (entry.name.endsWith(".schema.json")) out.push(JSON.parse(readFileSync(p, "utf8")));
14589
- }
14590
- };
14591
- walk(dir);
14592
- return out;
14593
- }
14594
- function loadCoverage(dir) {
14595
- const file = join(dir, ".coverage.json");
14596
- if (!existsSync(file) || !statSync(file).isFile()) return null;
14597
- try {
14598
- return buildCoverageIndex(JSON.parse(readFileSync(file, "utf8")));
14599
- } catch {
14600
- return null;
14601
- }
14602
- }
14603
- function createNodeContext(opts = {}) {
14604
- const schemasDir = resolveSchemasDir(opts.schemasDir);
14605
- const registry = createSchemaRegistry(loadSchemasFromDir(schemasDir));
14606
- const coverage = loadCoverage(schemasDir);
14607
- return { registry, coverage, schemasDir };
14608
- }
14609
-
14610
- // src/yaml.ts
14611
- var import_yaml = __toESM(require_dist(), 1);
14612
-
14613
14573
  // src/types.ts
14614
14574
  var DEFAULT_RULE_SEVERITY = {
14615
14575
  "required-field": "error",
@@ -14619,6 +14579,7 @@ var DEFAULT_RULE_SEVERITY = {
14619
14579
  "value-case": "warning",
14620
14580
  "invalid-type": "error",
14621
14581
  "invalid-format": "error",
14582
+ "invalid-reference": "error",
14622
14583
  "unknown-casetype": "warning",
14623
14584
  "parse-error": "error",
14624
14585
  schema: "error"
@@ -14640,6 +14601,7 @@ var LINT_RULE_DESCRIPTORS = [
14640
14601
  { id: "value-case", label: "Value case mismatch", description: "Value matches an allowed option except for case / camelCase / separators (e.g. FORM vs form).", defaultSeverity: "warning" },
14641
14602
  { id: "invalid-type", label: "Invalid type", description: "Value has the wrong JSON type.", defaultSeverity: "error" },
14642
14603
  { id: "invalid-format", label: "Invalid format", description: "Value fails a pattern / format constraint.", defaultSeverity: "error" },
14604
+ { id: "invalid-reference", label: "Invalid reference", description: "A case-reference field points at a file whose `_type` is not in the field's allowedCasetypes.", defaultSeverity: "error" },
14643
14605
  { id: "unknown-casetype", label: "Unknown casetype", description: "No schema found for the document `_type`.", defaultSeverity: "warning" },
14644
14606
  { id: "parse-error", label: "Parse error", description: "The YAML document could not be parsed.", defaultSeverity: "error" },
14645
14607
  { id: "schema", label: "Other schema error", description: "Any other constraint reported by the validator.", defaultSeverity: "error" }
@@ -14655,6 +14617,136 @@ function severityFor(rule, settings) {
14655
14617
  return settings.rules[rule] ?? DEFAULT_RULE_SEVERITY[rule];
14656
14618
  }
14657
14619
 
14620
+ // src/node.ts
14621
+ var require2 = createRequire(import.meta.url);
14622
+ function resolveSchemasDir(override) {
14623
+ if (override) return resolve(override);
14624
+ if (process.env.SCHEMAS_DIR) return resolve(process.env.SCHEMAS_DIR);
14625
+ const bundled = resolve(dirname(fileURLToPath(import.meta.url)), "schemas");
14626
+ if (existsSync(bundled)) return bundled;
14627
+ const ref = require2.resolve("@studio/schemas/extended/casetype.schema.json");
14628
+ return dirname(ref);
14629
+ }
14630
+ function loadSchemasFromDir(dir) {
14631
+ const out = [];
14632
+ const walk = (d) => {
14633
+ for (const entry of readdirSync(d, { withFileTypes: true })) {
14634
+ const p = join(d, entry.name);
14635
+ if (entry.isDirectory()) walk(p);
14636
+ else if (entry.name.endsWith(".schema.json")) out.push(JSON.parse(readFileSync(p, "utf8")));
14637
+ }
14638
+ };
14639
+ walk(dir);
14640
+ return out;
14641
+ }
14642
+ function loadCoverage(dir) {
14643
+ const file = join(dir, ".coverage.json");
14644
+ if (!existsSync(file) || !statSync(file).isFile()) return null;
14645
+ try {
14646
+ return buildCoverageIndex(JSON.parse(readFileSync(file, "utf8")));
14647
+ } catch {
14648
+ return null;
14649
+ }
14650
+ }
14651
+ function loadCaseRefs(dir) {
14652
+ const file = join(dir, ".caserefs.json");
14653
+ if (!existsSync(file) || !statSync(file).isFile()) return {};
14654
+ try {
14655
+ return JSON.parse(readFileSync(file, "utf8"));
14656
+ } catch {
14657
+ return {};
14658
+ }
14659
+ }
14660
+ var docCache = /* @__PURE__ */ new Map();
14661
+ function targetDoc(absPath) {
14662
+ if (docCache.has(absPath)) return docCache.get(absPath);
14663
+ let doc = null;
14664
+ if (existsSync(absPath) && statSync(absPath).isFile()) {
14665
+ try {
14666
+ doc = (0, import_yaml.parse)(readFileSync(absPath, "utf8"));
14667
+ } catch {
14668
+ doc = null;
14669
+ }
14670
+ }
14671
+ docCache.set(absPath, doc);
14672
+ return doc;
14673
+ }
14674
+ function targetType(absPath, fragment) {
14675
+ const doc = targetDoc(absPath);
14676
+ if (doc == null || typeof doc !== "object") return "not-found";
14677
+ if (!fragment) {
14678
+ const t2 = doc._type;
14679
+ return typeof t2 === "string" ? t2 : null;
14680
+ }
14681
+ let found = null;
14682
+ const walk = (n) => {
14683
+ if (found || !n || typeof n !== "object") return;
14684
+ if (Array.isArray(n)) {
14685
+ n.forEach(walk);
14686
+ return;
14687
+ }
14688
+ const o = n;
14689
+ if (o._uid === fragment) {
14690
+ found = o;
14691
+ return;
14692
+ }
14693
+ for (const v of Object.values(o)) walk(v);
14694
+ };
14695
+ walk(doc);
14696
+ if (!found) return "not-found";
14697
+ const t = found._type;
14698
+ return typeof t === "string" ? t : null;
14699
+ }
14700
+ var looksLikePath = (s) => s.includes("/") || s.endsWith(".yaml") || s.endsWith(".yml");
14701
+ function referenceFindings(value, file, caseRefs, settings) {
14702
+ const sev = severityFor("invalid-reference", settings);
14703
+ if (sev === "off" || Object.keys(caseRefs).length === 0) return [];
14704
+ const baseDir = dirname(file);
14705
+ const out = [];
14706
+ const checkRef = (ref, allowed, casetype, field, path) => {
14707
+ if (!looksLikePath(ref)) return;
14708
+ const frag = /^(.*\.ya?ml)#(.+)$/.exec(ref);
14709
+ const pathPart = frag ? frag[1] : ref;
14710
+ const fragment = frag ? frag[2] : "";
14711
+ const t = targetType(resolve(baseDir, pathPart), fragment);
14712
+ if (t === "not-found") {
14713
+ out.push({ severity: sev, rule: "invalid-reference", casetype, path, field, message: `reference target not found: "${ref}"`, file });
14714
+ } else if (t !== null && !allowed.includes(t)) {
14715
+ out.push({ severity: sev, rule: "invalid-reference", casetype, path, field, message: `field "${field}" references a "${t}" but must reference one of: ${allowed.join(", ")}`, file });
14716
+ }
14717
+ };
14718
+ const walk = (node, path) => {
14719
+ if (Array.isArray(node)) {
14720
+ node.forEach((n, i) => walk(n, `${path}/${i}`));
14721
+ return;
14722
+ }
14723
+ if (!node || typeof node !== "object") return;
14724
+ const o = node;
14725
+ const refs = typeof o._type === "string" ? caseRefs[o._type] : void 0;
14726
+ if (refs) {
14727
+ for (const [field, allowed] of Object.entries(refs)) {
14728
+ const v = o[field];
14729
+ if (typeof v === "string") checkRef(v, allowed, o._type, field, `${path}/${field}`);
14730
+ else if (Array.isArray(v)) v.forEach((item, i) => {
14731
+ if (typeof item === "string") checkRef(item, allowed, o._type, field, `${path}/${field}/${i}`);
14732
+ });
14733
+ }
14734
+ }
14735
+ for (const [k, v] of Object.entries(o)) walk(v, `${path}/${k}`);
14736
+ };
14737
+ walk(value, "");
14738
+ return out;
14739
+ }
14740
+ function createNodeContext(opts = {}) {
14741
+ const schemasDir = resolveSchemasDir(opts.schemasDir);
14742
+ const registry = createSchemaRegistry(loadSchemasFromDir(schemasDir));
14743
+ const coverage = loadCoverage(schemasDir);
14744
+ return { registry, coverage, schemasDir };
14745
+ }
14746
+
14747
+ // src/yaml.ts
14748
+ var import_yaml2 = __toESM(require_dist(), 1);
14749
+
14658
14750
  // src/lint.ts
14659
14751
  function ruleForKeyword(keyword) {
14660
14752
  switch (keyword) {
@@ -14922,7 +15014,7 @@ function isLintableYaml(text) {
14922
15014
  // src/cli.ts
14923
15015
  var PARALLEL_THRESHOLD = 200;
14924
15016
  var MAX_WORKERS = 8;
14925
- var VERSION = true ? "0.2.348" : "0.0.0-dev";
15017
+ var VERSION = true ? "0.2.350" : "0.0.0-dev";
14926
15018
  function isNewer(latest, current) {
14927
15019
  const a = latest.split(".").map(Number);
14928
15020
  const b = current.split(".").map(Number);
@@ -15054,6 +15146,7 @@ function printReport(results, kind) {
15054
15146
  var WORKER_FILE = fileURLToPath2(import.meta.url);
15055
15147
  function lintFiles(files, ctx, settings) {
15056
15148
  const out = [];
15149
+ const caseRefs = loadCaseRefs(ctx.schemasDir);
15057
15150
  const parseErr = (file, message) => ({
15058
15151
  file,
15059
15152
  casetype: null,
@@ -15066,7 +15159,7 @@ function lintFiles(files, ctx, settings) {
15066
15159
  if (!isLintableYaml(text)) continue;
15067
15160
  let docs;
15068
15161
  try {
15069
- docs = (0, import_yaml2.parseAllDocuments)(text);
15162
+ docs = (0, import_yaml3.parseAllDocuments)(text);
15070
15163
  } catch (e) {
15071
15164
  out.push(parseErr(file, e.message));
15072
15165
  continue;
@@ -15077,7 +15170,13 @@ function lintFiles(files, ctx, settings) {
15077
15170
  continue;
15078
15171
  }
15079
15172
  const input = { value: doc.toJS(), file };
15080
- out.push(lintDocument({ registry: ctx.registry, coverage: ctx.coverage, settings }, input));
15173
+ const result = lintDocument({ registry: ctx.registry, coverage: ctx.coverage, settings }, input);
15174
+ const refs = referenceFindings(input.value, file, caseRefs, settings);
15175
+ if (refs.length) {
15176
+ result.messages.push(...refs);
15177
+ if (result.ok && refs.some((m) => meetsThreshold(m.severity, settings.failOn))) result.ok = false;
15178
+ }
15179
+ out.push(result);
15081
15180
  }
15082
15181
  }
15083
15182
  return out;