glotfile 0.4.5 → 0.5.0
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/dist/server/cli.js +206 -24
- package/dist/server/server.js +154 -14
- package/dist/ui/assets/index-DC89onXX.js +1904 -0
- package/dist/ui/assets/index-DPfAS4pJ.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-3IIAIpZW.css +0 -1
- package/dist/ui/assets/index-pl7PaD7b.js +0 -1891
package/dist/server/cli.js
CHANGED
|
@@ -176,9 +176,6 @@ function validate(raw) {
|
|
|
176
176
|
if (lint.ignore !== void 0 && (!Array.isArray(lint.ignore) || !lint.ignore.every((g) => typeof g === "string"))) {
|
|
177
177
|
fail("config.lint.ignore must be an array of strings");
|
|
178
178
|
}
|
|
179
|
-
if (lint.dictionary !== void 0 && (!Array.isArray(lint.dictionary) || !lint.dictionary.every((w) => typeof w === "string"))) {
|
|
180
|
-
fail("config.lint.dictionary must be an array of strings");
|
|
181
|
-
}
|
|
182
179
|
if (lint.spelling !== void 0) {
|
|
183
180
|
if (!isObject(lint.spelling)) fail("config.lint.spelling must be an object");
|
|
184
181
|
if (lint.spelling.locales !== void 0 && (!isObject(lint.spelling.locales) || !Object.values(lint.spelling.locales).every((v) => typeof v === "string"))) {
|
|
@@ -243,6 +240,20 @@ function validate(raw) {
|
|
|
243
240
|
lv.value = lv.value.trim();
|
|
244
241
|
}
|
|
245
242
|
}
|
|
243
|
+
if (entry.suppressions !== void 0) {
|
|
244
|
+
if (!Array.isArray(entry.suppressions)) fail(`key "${key}" suppressions must be an array`);
|
|
245
|
+
for (const s of entry.suppressions) {
|
|
246
|
+
if (!isObject(s) || typeof s.locale !== "string" || typeof s.source !== "string") {
|
|
247
|
+
fail(`key "${key}" has an invalid suppression (needs string rule, locale, source)`);
|
|
248
|
+
}
|
|
249
|
+
if (!RULE_IDS.includes(s.rule)) {
|
|
250
|
+
fail(`key "${key}" suppression has unknown rule id "${String(s.rule)}"`);
|
|
251
|
+
}
|
|
252
|
+
if (s.at !== void 0 && typeof s.at !== "string") {
|
|
253
|
+
fail(`key "${key}" suppression "at" must be a string`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
246
257
|
if (entry.notes !== void 0) {
|
|
247
258
|
if (!Array.isArray(entry.notes)) fail(`key "${key}" notes must be an array`);
|
|
248
259
|
for (const n of entry.notes) {
|
|
@@ -520,6 +531,36 @@ var init_normalize = __esm({
|
|
|
520
531
|
}
|
|
521
532
|
});
|
|
522
533
|
|
|
534
|
+
// src/server/lint/suppress.ts
|
|
535
|
+
import { createHash } from "crypto";
|
|
536
|
+
function sourceSignature(entry, sourceLocale) {
|
|
537
|
+
const lv = entry.values[sourceLocale];
|
|
538
|
+
if (entry.plural) {
|
|
539
|
+
return Object.entries(lv?.forms ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([cat, body]) => `${cat}:${normalizeSource(body ?? "")}`).join("|");
|
|
540
|
+
}
|
|
541
|
+
return normalizeSource(lv?.value ?? "");
|
|
542
|
+
}
|
|
543
|
+
function sourceHash(entry, sourceLocale) {
|
|
544
|
+
return createHash("sha256").update(sourceSignature(entry, sourceLocale)).digest("hex").slice(0, 12);
|
|
545
|
+
}
|
|
546
|
+
function findSuppression(entry, sourceLocale, ruleId, locale) {
|
|
547
|
+
if (!entry.suppressions?.length) return void 0;
|
|
548
|
+
const current = sourceHash(entry, sourceLocale);
|
|
549
|
+
return entry.suppressions.find((s) => s.rule === ruleId && s.locale === locale && s.source === current);
|
|
550
|
+
}
|
|
551
|
+
function pruneStaleSuppressions(entry, sourceLocale) {
|
|
552
|
+
if (!entry.suppressions?.length) return;
|
|
553
|
+
const current = sourceHash(entry, sourceLocale);
|
|
554
|
+
entry.suppressions = entry.suppressions.filter((s) => s.source === current);
|
|
555
|
+
if (!entry.suppressions.length) delete entry.suppressions;
|
|
556
|
+
}
|
|
557
|
+
var init_suppress = __esm({
|
|
558
|
+
"src/server/lint/suppress.ts"() {
|
|
559
|
+
"use strict";
|
|
560
|
+
init_normalize();
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
523
564
|
// src/server/state.ts
|
|
524
565
|
import { readFileSync as readFileSync2, existsSync as existsSync2, rmSync as rmSync3 } from "fs";
|
|
525
566
|
import { randomUUID } from "crypto";
|
|
@@ -541,6 +582,7 @@ function normalizeState(state) {
|
|
|
541
582
|
const remapped = {};
|
|
542
583
|
for (const [loc, lv] of Object.entries(entry.values)) remapped[canonLocale(loc)] = lv;
|
|
543
584
|
entry.values = remapped;
|
|
585
|
+
for (const s of entry.suppressions ?? []) s.locale = canonLocale(s.locale);
|
|
544
586
|
}
|
|
545
587
|
for (const output of state.config.outputs) {
|
|
546
588
|
if (!output.localeMap) continue;
|
|
@@ -641,6 +683,7 @@ function setSourceValue(state, key, value) {
|
|
|
641
683
|
lv.state = "needs-review";
|
|
642
684
|
}
|
|
643
685
|
}
|
|
686
|
+
pruneStaleSuppressions(entry, state.config.sourceLocale);
|
|
644
687
|
}
|
|
645
688
|
}
|
|
646
689
|
function setTargetValue(state, key, locale, value, clock = systemClock) {
|
|
@@ -664,6 +707,7 @@ function setSourcePluralForms(state, key, forms) {
|
|
|
664
707
|
lv.state = "needs-review";
|
|
665
708
|
}
|
|
666
709
|
}
|
|
710
|
+
pruneStaleSuppressions(entry, state.config.sourceLocale);
|
|
667
711
|
}
|
|
668
712
|
}
|
|
669
713
|
function setPluralForms(state, key, locale, forms, clock = systemClock) {
|
|
@@ -737,6 +781,19 @@ function deleteNote(state, key, id) {
|
|
|
737
781
|
if (!entry.notes) return;
|
|
738
782
|
entry.notes = entry.notes.filter((n) => n.id !== id);
|
|
739
783
|
}
|
|
784
|
+
function addSuppression(state, key, rule, locale, clock = systemClock) {
|
|
785
|
+
const entry = requireKey(state, key);
|
|
786
|
+
if (!RULE_IDS.includes(rule)) throw new GlotfileError(`Unknown lint rule: ${rule}`);
|
|
787
|
+
const list = (entry.suppressions ?? []).filter((s) => !(s.rule === rule && s.locale === locale));
|
|
788
|
+
list.push({ rule, locale, source: sourceHash(entry, state.config.sourceLocale), at: clock() });
|
|
789
|
+
entry.suppressions = list;
|
|
790
|
+
}
|
|
791
|
+
function removeSuppression(state, key, rule, locale) {
|
|
792
|
+
const entry = requireKey(state, key);
|
|
793
|
+
if (!entry.suppressions) return;
|
|
794
|
+
entry.suppressions = entry.suppressions.filter((s) => !(s.rule === rule && s.locale === locale));
|
|
795
|
+
if (!entry.suppressions.length) delete entry.suppressions;
|
|
796
|
+
}
|
|
740
797
|
function upsertGlossaryEntry(state, entry) {
|
|
741
798
|
const i = state.glossary.findIndex((e) => e.term === entry.term);
|
|
742
799
|
if (i === -1) state.glossary.push(entry);
|
|
@@ -782,6 +839,8 @@ var init_state = __esm({
|
|
|
782
839
|
init_plurals();
|
|
783
840
|
init_storage();
|
|
784
841
|
init_normalize();
|
|
842
|
+
init_suppress();
|
|
843
|
+
init_registry();
|
|
785
844
|
systemClock = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
786
845
|
}
|
|
787
846
|
});
|
|
@@ -3705,7 +3764,10 @@ function sortFindings(findings) {
|
|
|
3705
3764
|
}
|
|
3706
3765
|
function countSeverities(findings) {
|
|
3707
3766
|
let error = 0, warn = 0;
|
|
3708
|
-
for (const f of findings)
|
|
3767
|
+
for (const f of findings) {
|
|
3768
|
+
if (f.suppressed) continue;
|
|
3769
|
+
f.severity === "error" ? error++ : warn++;
|
|
3770
|
+
}
|
|
3709
3771
|
return { error, warn };
|
|
3710
3772
|
}
|
|
3711
3773
|
async function loadSpellers(locales, config, load, warn) {
|
|
@@ -3731,7 +3793,7 @@ async function runLint(state, options = {}) {
|
|
|
3731
3793
|
const active = rules.filter(isActive);
|
|
3732
3794
|
const spellingOn = active.some((r) => r.id === "spelling");
|
|
3733
3795
|
const spellers = spellingOn ? await loadSpellers(targetLocales, config, load, warn) : /* @__PURE__ */ new Map();
|
|
3734
|
-
const allowWords = spellingOn ? buildAllowWords(state.glossary, config.
|
|
3796
|
+
const allowWords = spellingOn ? buildAllowWords(state.glossary, state.config.spelling?.customWords) : /* @__PURE__ */ new Set();
|
|
3735
3797
|
const ctx = {
|
|
3736
3798
|
config,
|
|
3737
3799
|
sourceLocale: state.config.sourceLocale,
|
|
@@ -3743,16 +3805,23 @@ async function runLint(state, options = {}) {
|
|
|
3743
3805
|
const ignoreRes = (config.ignore ?? []).map(globToRegExp);
|
|
3744
3806
|
const localeFilter = options.locales ? new Set(options.locales) : null;
|
|
3745
3807
|
const findings = [];
|
|
3808
|
+
let suppressed = 0;
|
|
3746
3809
|
for (const rule of active) {
|
|
3747
3810
|
const severity = resolveSeverity(rule.id, config);
|
|
3748
3811
|
for (const raw of rule.run(state, ctx)) {
|
|
3749
3812
|
if (ignoreRes.some((re) => re.test(raw.key))) continue;
|
|
3750
3813
|
if (localeFilter && raw.locale !== "" && !localeFilter.has(raw.locale)) continue;
|
|
3814
|
+
const entry = state.keys[raw.key];
|
|
3815
|
+
if (raw.locale !== "" && entry && findSuppression(entry, state.config.sourceLocale, rule.id, raw.locale)) {
|
|
3816
|
+
suppressed++;
|
|
3817
|
+
if (options.includeSuppressed) findings.push({ ...raw, severity, suppressed: true });
|
|
3818
|
+
continue;
|
|
3819
|
+
}
|
|
3751
3820
|
findings.push({ ...raw, severity });
|
|
3752
3821
|
}
|
|
3753
3822
|
}
|
|
3754
3823
|
const sorted = sortFindings(findings);
|
|
3755
|
-
const counts = countSeverities(sorted);
|
|
3824
|
+
const counts = { ...countSeverities(sorted), suppressed };
|
|
3756
3825
|
return { findings: sorted, counts, ok: counts.error === 0 };
|
|
3757
3826
|
}
|
|
3758
3827
|
var init_run2 = __esm({
|
|
@@ -3762,6 +3831,7 @@ var init_run2 = __esm({
|
|
|
3762
3831
|
init_registry();
|
|
3763
3832
|
init_rules();
|
|
3764
3833
|
init_spelling();
|
|
3834
|
+
init_suppress();
|
|
3765
3835
|
}
|
|
3766
3836
|
});
|
|
3767
3837
|
|
|
@@ -3791,6 +3861,33 @@ var init_outputs = __esm({
|
|
|
3791
3861
|
}
|
|
3792
3862
|
});
|
|
3793
3863
|
|
|
3864
|
+
// src/server/lint/accept.ts
|
|
3865
|
+
var accept_exports = {};
|
|
3866
|
+
__export(accept_exports, {
|
|
3867
|
+
acceptFindings: () => acceptFindings
|
|
3868
|
+
});
|
|
3869
|
+
function acceptFindings(state, findings, opts = {}, clock = systemClock) {
|
|
3870
|
+
const byRule = {};
|
|
3871
|
+
let accepted = 0;
|
|
3872
|
+
for (const f of findings) {
|
|
3873
|
+
if (f.locale === "" || f.suppressed) continue;
|
|
3874
|
+
if (f.severity === "error" && !opts.includeErrors) continue;
|
|
3875
|
+
if (opts.rules && !opts.rules.includes(f.ruleId)) continue;
|
|
3876
|
+
if (opts.locales && !opts.locales.includes(f.locale)) continue;
|
|
3877
|
+
if (!state.keys[f.key]) continue;
|
|
3878
|
+
addSuppression(state, f.key, f.ruleId, f.locale, clock);
|
|
3879
|
+
byRule[f.ruleId] = (byRule[f.ruleId] ?? 0) + 1;
|
|
3880
|
+
accepted++;
|
|
3881
|
+
}
|
|
3882
|
+
return { accepted, byRule };
|
|
3883
|
+
}
|
|
3884
|
+
var init_accept = __esm({
|
|
3885
|
+
"src/server/lint/accept.ts"() {
|
|
3886
|
+
"use strict";
|
|
3887
|
+
init_state();
|
|
3888
|
+
}
|
|
3889
|
+
});
|
|
3890
|
+
|
|
3794
3891
|
// src/server/import/detect.ts
|
|
3795
3892
|
import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
|
|
3796
3893
|
import { join as join4 } from "path";
|
|
@@ -4465,7 +4562,8 @@ function contains(haystack, needle, caseSensitive) {
|
|
|
4465
4562
|
return caseSensitive ? haystack.includes(needle) : haystack.toLowerCase().includes(needle.toLowerCase());
|
|
4466
4563
|
}
|
|
4467
4564
|
function runChecks(state, opts = {}) {
|
|
4468
|
-
const
|
|
4565
|
+
const ruleOff = (id) => state.config.lint?.rules?.[CHECK_RULE[id]] === "off";
|
|
4566
|
+
const on = (id) => (!opts.only || opts.only.includes(id)) && !ruleOff(id);
|
|
4469
4567
|
const issues = [];
|
|
4470
4568
|
let spellPending = false;
|
|
4471
4569
|
const { sourceLocale } = state.config;
|
|
@@ -4593,16 +4691,28 @@ function runChecks(state, opts = {}) {
|
|
|
4593
4691
|
}
|
|
4594
4692
|
}
|
|
4595
4693
|
}
|
|
4596
|
-
|
|
4694
|
+
const visible = issues.filter((i) => {
|
|
4695
|
+
const entry = state.keys[i.key];
|
|
4696
|
+
return !entry || !findSuppression(entry, sourceLocale, CHECK_RULE[i.check], i.locale);
|
|
4697
|
+
});
|
|
4698
|
+
return { issues: visible, spellPending };
|
|
4597
4699
|
}
|
|
4598
|
-
var CHECK_IDS;
|
|
4700
|
+
var CHECK_IDS, CHECK_RULE;
|
|
4599
4701
|
var init_checks = __esm({
|
|
4600
4702
|
"src/server/checks.ts"() {
|
|
4601
4703
|
"use strict";
|
|
4602
4704
|
init_placeholders();
|
|
4603
4705
|
init_run();
|
|
4604
4706
|
init_spell();
|
|
4707
|
+
init_suppress();
|
|
4605
4708
|
CHECK_IDS = ["untranslated", "placeholder", "spelling", "length", "glossary"];
|
|
4709
|
+
CHECK_RULE = {
|
|
4710
|
+
untranslated: "empty-translation",
|
|
4711
|
+
placeholder: "placeholder-mismatch",
|
|
4712
|
+
spelling: "spelling",
|
|
4713
|
+
length: "max-length",
|
|
4714
|
+
glossary: "glossary-violation"
|
|
4715
|
+
};
|
|
4606
4716
|
}
|
|
4607
4717
|
});
|
|
4608
4718
|
|
|
@@ -4620,19 +4730,23 @@ function readJson2(path) {
|
|
|
4620
4730
|
}
|
|
4621
4731
|
function loadUiPrefs(path) {
|
|
4622
4732
|
const raw = readJson2(path);
|
|
4623
|
-
|
|
4733
|
+
const prefs = { theme: isThemeMode(raw.theme) ? raw.theme : DEFAULTS.theme };
|
|
4734
|
+
if (isPanelWidth(raw.keyColumnWidth)) prefs.keyColumnWidth = Math.round(raw.keyColumnWidth);
|
|
4735
|
+
if (isPanelWidth(raw.detailPanelWidth)) prefs.detailPanelWidth = Math.round(raw.detailPanelWidth);
|
|
4736
|
+
return prefs;
|
|
4624
4737
|
}
|
|
4625
4738
|
function saveUiPrefs(path, prefs) {
|
|
4626
4739
|
const merged = { ...readJson2(path), ...prefs };
|
|
4627
4740
|
writeFileAtomic(path, JSON.stringify(merged, null, 2) + "\n");
|
|
4628
4741
|
}
|
|
4629
|
-
var THEMES, isThemeMode, defaultUiPrefsPath, DEFAULTS;
|
|
4742
|
+
var THEMES, isThemeMode, isPanelWidth, defaultUiPrefsPath, DEFAULTS;
|
|
4630
4743
|
var init_ui_prefs = __esm({
|
|
4631
4744
|
"src/server/ui-prefs.ts"() {
|
|
4632
4745
|
"use strict";
|
|
4633
4746
|
init_atomic_write();
|
|
4634
4747
|
THEMES = ["system", "light", "dark"];
|
|
4635
4748
|
isThemeMode = (v) => THEMES.includes(v);
|
|
4749
|
+
isPanelWidth = (v) => typeof v === "number" && Number.isFinite(v) && v >= 120 && v <= 1200;
|
|
4636
4750
|
defaultUiPrefsPath = () => join8(homedir(), ".glotfile", "ui.json");
|
|
4637
4751
|
DEFAULTS = { theme: "system" };
|
|
4638
4752
|
}
|
|
@@ -4687,9 +4801,20 @@ function createApi(deps) {
|
|
|
4687
4801
|
app.get("/state", (c) => c.json(load()));
|
|
4688
4802
|
app.get("/ui-prefs", (c) => c.json(loadUiPrefs(uiPrefsPath)));
|
|
4689
4803
|
app.put("/ui-prefs", async (c) => {
|
|
4690
|
-
const
|
|
4691
|
-
|
|
4692
|
-
|
|
4804
|
+
const body = await c.req.json();
|
|
4805
|
+
const patch = {};
|
|
4806
|
+
if ("theme" in body) {
|
|
4807
|
+
if (!isThemeMode(body.theme)) return c.json({ error: "theme must be system, light, or dark" }, 400);
|
|
4808
|
+
patch.theme = body.theme;
|
|
4809
|
+
}
|
|
4810
|
+
for (const field of ["keyColumnWidth", "detailPanelWidth"]) {
|
|
4811
|
+
if (field in body) {
|
|
4812
|
+
if (!isPanelWidth(body[field])) return c.json({ error: `${field} must be a number between 120 and 1200` }, 400);
|
|
4813
|
+
patch[field] = Math.round(body[field]);
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
if (Object.keys(patch).length === 0) return c.json({ error: "no recognized preferences in body" }, 400);
|
|
4817
|
+
saveUiPrefs(uiPrefsPath, patch);
|
|
4693
4818
|
return c.json({ ok: true });
|
|
4694
4819
|
});
|
|
4695
4820
|
app.get("/local-settings", (c) => c.json(loadLocalSettings(projectRoot)));
|
|
@@ -5163,12 +5288,47 @@ function createApi(deps) {
|
|
|
5163
5288
|
};
|
|
5164
5289
|
app.get("/lint", async (c) => {
|
|
5165
5290
|
const state = load();
|
|
5291
|
+
const includeSuppressed = c.req.query("includeSuppressed") === "1";
|
|
5166
5292
|
const lint = await runLint(state, { loadSpeller: cachedLoader, warn: () => {
|
|
5167
|
-
} });
|
|
5293
|
+
}, includeSuppressed });
|
|
5168
5294
|
const findings = sortFindings([...lint.findings, ...checkOutputs(state, projectRoot)]);
|
|
5169
|
-
const counts = countSeverities(findings);
|
|
5295
|
+
const counts = { ...countSeverities(findings), suppressed: lint.counts.suppressed };
|
|
5170
5296
|
return c.json({ findings, counts, ok: counts.error === 0 });
|
|
5171
5297
|
});
|
|
5298
|
+
app.post("/keys/:key/suppressions", async (c) => {
|
|
5299
|
+
const key = c.req.param("key");
|
|
5300
|
+
const { rule, locale } = await c.req.json();
|
|
5301
|
+
if (typeof rule !== "string" || !rule) return c.json({ error: "rule is required" }, 400);
|
|
5302
|
+
if (typeof locale !== "string" || !locale) return c.json({ error: "locale is required" }, 400);
|
|
5303
|
+
const s = load();
|
|
5304
|
+
addSuppression(s, key, rule, locale);
|
|
5305
|
+
persist(s);
|
|
5306
|
+
logChange({ kind: "suppression", summary: `Suppressed ${rule} for ${key} [${locale}]`, key, locale, after: rule });
|
|
5307
|
+
return c.json({ ok: true });
|
|
5308
|
+
});
|
|
5309
|
+
app.delete("/keys/:key/suppressions", (c) => {
|
|
5310
|
+
const key = c.req.param("key");
|
|
5311
|
+
const rule = c.req.query("rule") ?? "";
|
|
5312
|
+
const locale = c.req.query("locale") ?? "";
|
|
5313
|
+
if (!rule || !locale) return c.json({ error: "rule and locale are required" }, 400);
|
|
5314
|
+
const s = load();
|
|
5315
|
+
removeSuppression(s, key, rule, locale);
|
|
5316
|
+
persist(s);
|
|
5317
|
+
logChange({ kind: "suppression", summary: `Unsuppressed ${rule} for ${key} [${locale}]`, key, locale, before: rule });
|
|
5318
|
+
return c.json({ ok: true });
|
|
5319
|
+
});
|
|
5320
|
+
app.post("/lint/accept", async (c) => {
|
|
5321
|
+
const body = await c.req.json().catch(() => ({}));
|
|
5322
|
+
const s = load();
|
|
5323
|
+
const lint = await runLint(s, { loadSpeller: cachedLoader, warn: () => {
|
|
5324
|
+
} });
|
|
5325
|
+
const result = acceptFindings(s, lint.findings, { rules: body.rules, locales: body.locales });
|
|
5326
|
+
if (result.accepted > 0) {
|
|
5327
|
+
persist(s);
|
|
5328
|
+
logChange({ kind: "suppression", summary: `Suppressed ${result.accepted} finding(s)`, after: result.byRule });
|
|
5329
|
+
}
|
|
5330
|
+
return c.json({ ok: true, ...result });
|
|
5331
|
+
});
|
|
5172
5332
|
app.get("/checks", (c) => {
|
|
5173
5333
|
const param = c.req.query("checks");
|
|
5174
5334
|
const only = param ? param.split(",").map((s) => s.trim()).filter((s) => CHECK_IDS.includes(s)) : void 0;
|
|
@@ -5523,6 +5683,7 @@ var init_api = __esm({
|
|
|
5523
5683
|
"src/server/api.ts"() {
|
|
5524
5684
|
"use strict";
|
|
5525
5685
|
init_state();
|
|
5686
|
+
init_accept();
|
|
5526
5687
|
init_scan();
|
|
5527
5688
|
init_scanner();
|
|
5528
5689
|
init_context();
|
|
@@ -5732,10 +5893,11 @@ function formatText(report) {
|
|
|
5732
5893
|
lastKey = f.key;
|
|
5733
5894
|
}
|
|
5734
5895
|
const loc = f.locale ? ` ${f.locale}` : "";
|
|
5735
|
-
lines.push(` ${f.severity} ${f.ruleId}${loc} ${f.message}`);
|
|
5896
|
+
lines.push(` ${f.severity} ${f.ruleId}${loc} ${f.message}${f.suppressed ? " (suppressed)" : ""}`);
|
|
5736
5897
|
}
|
|
5737
5898
|
lines.push("");
|
|
5738
|
-
|
|
5899
|
+
const suppressed = report.counts.suppressed ? `, ${report.counts.suppressed} suppressed` : "";
|
|
5900
|
+
lines.push(`\u2716 ${report.counts.error} error(s), ${report.counts.warn} warning(s)${suppressed}`);
|
|
5739
5901
|
return lines.join("\n") + "\n";
|
|
5740
5902
|
}
|
|
5741
5903
|
function formatJson(report) {
|
|
@@ -5823,7 +5985,9 @@ function parseArgs(argv) {
|
|
|
5823
5985
|
} else if (flag === "--max-warnings" && next) {
|
|
5824
5986
|
args.maxWarnings = Number(next);
|
|
5825
5987
|
i++;
|
|
5826
|
-
} else if (flag === "--
|
|
5988
|
+
} else if (flag === "--include-suppressed") args.includeSuppressed = true;
|
|
5989
|
+
else if (flag === "--accept") args.accept = true;
|
|
5990
|
+
else if (flag === "--all") args.all = true;
|
|
5827
5991
|
else if (flag === "--limit" && next) {
|
|
5828
5992
|
args.limit = Number(next);
|
|
5829
5993
|
i++;
|
|
@@ -5995,8 +6159,24 @@ function printReport(report, format, rawText) {
|
|
|
5995
6159
|
}
|
|
5996
6160
|
async function runLintCmd(args) {
|
|
5997
6161
|
const state = loadState(args.statePath);
|
|
6162
|
+
if (args.accept) {
|
|
6163
|
+
const { acceptFindings: acceptFindings2 } = await Promise.resolve().then(() => (init_accept(), accept_exports));
|
|
6164
|
+
const report2 = await runLint(state, { locales: args.locales });
|
|
6165
|
+
const result = acceptFindings2(state, report2.findings, { rules: args.ruleIds, locales: args.locales });
|
|
6166
|
+
if (result.accepted > 0) saveState(args.statePath, state);
|
|
6167
|
+
console.log(`Suppressed ${result.accepted} warning(s).`);
|
|
6168
|
+
for (const [rule, n] of Object.entries(result.byRule)) console.log(` ${rule}: ${n}`);
|
|
6169
|
+
if (result.accepted > 0) {
|
|
6170
|
+
console.log("Each suppression expires automatically when its key's source text changes.");
|
|
6171
|
+
}
|
|
6172
|
+
return;
|
|
6173
|
+
}
|
|
5998
6174
|
const rawText = existsSync12(args.statePath) ? readFileSync15(args.statePath, "utf8") : "";
|
|
5999
|
-
const report = await runLint(state, {
|
|
6175
|
+
const report = await runLint(state, {
|
|
6176
|
+
locales: args.locales,
|
|
6177
|
+
ruleIds: args.ruleIds,
|
|
6178
|
+
includeSuppressed: args.includeSuppressed
|
|
6179
|
+
});
|
|
6000
6180
|
printReport(report, args.format, rawText);
|
|
6001
6181
|
const tooManyWarnings = args.maxWarnings != null && report.counts.warn > args.maxWarnings;
|
|
6002
6182
|
if (!report.ok || tooManyWarnings) process.exitCode = 1;
|
|
@@ -6008,7 +6188,7 @@ async function runCheck(args) {
|
|
|
6008
6188
|
} catch (e) {
|
|
6009
6189
|
const report2 = {
|
|
6010
6190
|
findings: [{ ruleId: "load-error", key: "", locale: "", severity: "error", message: e.message }],
|
|
6011
|
-
counts: { error: 1, warn: 0 },
|
|
6191
|
+
counts: { error: 1, warn: 0, suppressed: 0 },
|
|
6012
6192
|
ok: false
|
|
6013
6193
|
};
|
|
6014
6194
|
printReport(report2, args.format, "");
|
|
@@ -6019,7 +6199,7 @@ async function runCheck(args) {
|
|
|
6019
6199
|
const root = dirname5(resolve11(args.statePath));
|
|
6020
6200
|
const lint = await runLint(state, {});
|
|
6021
6201
|
const findings = sortFindings([...lint.findings, ...checkOutputs(state, root)]);
|
|
6022
|
-
const counts = countSeverities(findings);
|
|
6202
|
+
const counts = { ...countSeverities(findings), suppressed: lint.counts.suppressed };
|
|
6023
6203
|
const report = { findings, counts, ok: counts.error === 0 };
|
|
6024
6204
|
printReport(report, args.format, rawText);
|
|
6025
6205
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -6206,12 +6386,14 @@ var COMMAND_HELP = {
|
|
|
6206
6386
|
},
|
|
6207
6387
|
lint: {
|
|
6208
6388
|
summary: "Check the catalog for problems (placeholders, length, glossary, \u2026).",
|
|
6209
|
-
usage: "glotfile lint [--format <text|json|sarif>] [--locale <list>] [--rule <list>] [--max-warnings <n>]",
|
|
6389
|
+
usage: "glotfile lint [--format <text|json|sarif>] [--locale <list>] [--rule <list>] [--max-warnings <n>] [--include-suppressed] [--accept]",
|
|
6210
6390
|
options: [
|
|
6211
6391
|
["--format <fmt>", "Output format: text (default), json, or sarif"],
|
|
6212
6392
|
["--locale <list>", "Restrict to these comma-separated locales"],
|
|
6213
6393
|
["--rule <list>", "Only run these comma-separated rule ids"],
|
|
6214
|
-
["--max-warnings <n>", "Exit non-zero if warnings exceed n"]
|
|
6394
|
+
["--max-warnings <n>", "Exit non-zero if warnings exceed n"],
|
|
6395
|
+
["--include-suppressed", "Also show findings hidden by suppressions"],
|
|
6396
|
+
["--accept", "Suppress all current warnings (narrow with --rule/--locale); each expires when its source changes"]
|
|
6215
6397
|
]
|
|
6216
6398
|
},
|
|
6217
6399
|
check: {
|