@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 +144 -45
- package/lsp.js +13294 -13292
- package/package.json +1 -1
- package/schemas/.caserefs.json +174 -0
- package/schemas/datasetColumn.schema.json +7 -2
package/cli.js
CHANGED
|
@@ -14456,7 +14456,7 @@ var require__ = __commonJS({
|
|
|
14456
14456
|
});
|
|
14457
14457
|
|
|
14458
14458
|
// src/cli.ts
|
|
14459
|
-
var
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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;
|