glotfile 0.6.1 → 0.6.3
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
CHANGED
|
@@ -187,7 +187,7 @@ function validate(raw) {
|
|
|
187
187
|
if (config.scan !== void 0) {
|
|
188
188
|
const scan = config.scan;
|
|
189
189
|
if (!isObject(scan)) fail("config.scan must be an object");
|
|
190
|
-
for (const f of ["include", "exclude", "accessors", "patterns"]) {
|
|
190
|
+
for (const f of ["include", "exclude", "accessors", "patterns", "keep"]) {
|
|
191
191
|
const v = scan[f];
|
|
192
192
|
if (v !== void 0 && (!Array.isArray(v) || !v.every((x) => typeof x === "string"))) {
|
|
193
193
|
fail(`config.scan.${f} must be an array of strings`);
|
|
@@ -3225,13 +3225,35 @@ function computeUsedKeys(state, cache2) {
|
|
|
3225
3225
|
if (p.prefix) prefixes.push(p.prefix);
|
|
3226
3226
|
}
|
|
3227
3227
|
}
|
|
3228
|
-
|
|
3228
|
+
const matchers = [];
|
|
3229
|
+
const seenLiterals = /* @__PURE__ */ new Set();
|
|
3230
|
+
for (const entry of Object.values(cache2.files)) {
|
|
3231
|
+
for (const l of entry.literals ?? []) {
|
|
3232
|
+
if (!l.literal || seenLiterals.has(l.literal)) continue;
|
|
3233
|
+
seenLiterals.add(l.literal);
|
|
3234
|
+
matchers.push(literalMatcher(l.literal));
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
const keep = (state.config.scan?.keep ?? []).map(globToRegExp);
|
|
3238
|
+
return Object.keys(state.keys).filter((key) => exact.has(key) || prefixes.some((p) => key.startsWith(p)) || matchers.some((matches) => matches(key)) || keep.some((re) => re.test(key))).sort();
|
|
3239
|
+
}
|
|
3240
|
+
function escapeRe(s) {
|
|
3241
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3242
|
+
}
|
|
3243
|
+
function literalMatcher(literal) {
|
|
3244
|
+
if (/%[sd]/.test(literal)) {
|
|
3245
|
+
const re = new RegExp(`^${literal.split(/%[sd]/).map(escapeRe).join("[^.]+")}$`);
|
|
3246
|
+
return (key) => re.test(key);
|
|
3247
|
+
}
|
|
3248
|
+
if (literal.endsWith(".")) return (key) => key.startsWith(literal);
|
|
3249
|
+
return (key) => key === literal || key.startsWith(literal + ".");
|
|
3229
3250
|
}
|
|
3230
3251
|
var init_scan = __esm({
|
|
3231
3252
|
"src/server/scan.ts"() {
|
|
3232
3253
|
"use strict";
|
|
3233
3254
|
init_atomic_write();
|
|
3234
3255
|
init_glotfile_dir();
|
|
3256
|
+
init_glob();
|
|
3235
3257
|
}
|
|
3236
3258
|
});
|
|
3237
3259
|
|
|
@@ -3241,7 +3263,7 @@ import { join as join3, extname as extname2, relative } from "path";
|
|
|
3241
3263
|
function scannerForExt(ext) {
|
|
3242
3264
|
return EXT_SCANNER[ext] ?? null;
|
|
3243
3265
|
}
|
|
3244
|
-
function
|
|
3266
|
+
function escapeRe2(s) {
|
|
3245
3267
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3246
3268
|
}
|
|
3247
3269
|
function detectFlutterAccessors(content) {
|
|
@@ -3258,7 +3280,7 @@ function flutterPatterns(content, opts) {
|
|
|
3258
3280
|
...FLUTTER_ACCESSOR_DEFAULTS,
|
|
3259
3281
|
...detectFlutterAccessors(content),
|
|
3260
3282
|
...opts?.accessors ?? []
|
|
3261
|
-
])].map(
|
|
3283
|
+
])].map(escapeRe2);
|
|
3262
3284
|
return [
|
|
3263
3285
|
// AppLocalizations.of(context)!.key — tolerates the !/? null-assertion.
|
|
3264
3286
|
/AppLocalizations\.of\([^)]*\)[!?]?\.([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
@@ -3343,6 +3365,32 @@ function extractPrefixes(content, scanner) {
|
|
|
3343
3365
|
result.sort((a, b) => a.line - b.line || a.col - b.col);
|
|
3344
3366
|
return result;
|
|
3345
3367
|
}
|
|
3368
|
+
function extractLiterals(content) {
|
|
3369
|
+
const starts = lineStartOffsets(content);
|
|
3370
|
+
const result = [];
|
|
3371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3372
|
+
for (const pattern of STRING_LITERALS) {
|
|
3373
|
+
const re = new RegExp(pattern.source, "g");
|
|
3374
|
+
let m;
|
|
3375
|
+
while ((m = re.exec(content)) !== null) {
|
|
3376
|
+
let text = m[1];
|
|
3377
|
+
const marker = text.search(/\{\$|\$\{/);
|
|
3378
|
+
if (marker !== -1) {
|
|
3379
|
+
text = text.slice(0, marker);
|
|
3380
|
+
if (!text.endsWith(".")) continue;
|
|
3381
|
+
}
|
|
3382
|
+
if (!KEY_SHAPE.test(text)) continue;
|
|
3383
|
+
const { line, col } = offsetToLineCol(starts, m.index);
|
|
3384
|
+
const dedup = `${line}:${col}:${text}`;
|
|
3385
|
+
if (!seen.has(dedup)) {
|
|
3386
|
+
seen.add(dedup);
|
|
3387
|
+
result.push({ literal: text, line, col });
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
result.sort((a, b) => a.line - b.line || a.col - b.col);
|
|
3392
|
+
return result;
|
|
3393
|
+
}
|
|
3346
3394
|
function matchesGlob(relPath, glob) {
|
|
3347
3395
|
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(//g, ".*");
|
|
3348
3396
|
return new RegExp(`^${escaped}$`).test(relPath);
|
|
@@ -3417,13 +3465,14 @@ function runScan(projectRoot, opts, existing) {
|
|
|
3417
3465
|
mtime,
|
|
3418
3466
|
size,
|
|
3419
3467
|
refs: extractRefs(content, scanner, opts),
|
|
3420
|
-
prefixes: extractPrefixes(content, scanner)
|
|
3468
|
+
prefixes: extractPrefixes(content, scanner),
|
|
3469
|
+
literals: extractLiterals(content)
|
|
3421
3470
|
};
|
|
3422
3471
|
}
|
|
3423
3472
|
saveUsageCache(projectRoot, cache2);
|
|
3424
3473
|
return cache2;
|
|
3425
3474
|
}
|
|
3426
|
-
var PATTERNS, PREFIX_PATTERNS, CACHE_VERSION, EXT_SCANNER, ALWAYS_EXCLUDE, FLUTTER_ACCESSOR_DEFAULTS;
|
|
3475
|
+
var PATTERNS, PREFIX_PATTERNS, CACHE_VERSION, EXT_SCANNER, ALWAYS_EXCLUDE, FLUTTER_ACCESSOR_DEFAULTS, KEY_SHAPE, STRING_LITERALS;
|
|
3427
3476
|
var init_scanner = __esm({
|
|
3428
3477
|
"src/server/scanner.ts"() {
|
|
3429
3478
|
"use strict";
|
|
@@ -3480,7 +3529,7 @@ var init_scanner = __esm({
|
|
|
3480
3529
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$]*)\$\{/g
|
|
3481
3530
|
]
|
|
3482
3531
|
};
|
|
3483
|
-
CACHE_VERSION =
|
|
3532
|
+
CACHE_VERSION = 6;
|
|
3484
3533
|
EXT_SCANNER = {
|
|
3485
3534
|
".php": "laravel",
|
|
3486
3535
|
".vue": "js-i18n",
|
|
@@ -3514,6 +3563,12 @@ var init_scanner = __esm({
|
|
|
3514
3563
|
"__pycache__"
|
|
3515
3564
|
]);
|
|
3516
3565
|
FLUTTER_ACCESSOR_DEFAULTS = ["l10n", "loc", "localizations", "translations"];
|
|
3566
|
+
KEY_SHAPE = /^[A-Za-z0-9_][A-Za-z0-9_/-]*(?:\.(?:[A-Za-z0-9_-]+|%[sd]))+\.?$/;
|
|
3567
|
+
STRING_LITERALS = [
|
|
3568
|
+
/'([^'\\\n]+)'/g,
|
|
3569
|
+
/"([^"\\\n]+)"/g,
|
|
3570
|
+
/`([^`\\\n]+)`/g
|
|
3571
|
+
];
|
|
3517
3572
|
}
|
|
3518
3573
|
});
|
|
3519
3574
|
|
|
@@ -6726,23 +6781,34 @@ function createApi(deps) {
|
|
|
6726
6781
|
app.get("/scan/usage", (c) => {
|
|
6727
6782
|
const key = c.req.query("key") ?? "";
|
|
6728
6783
|
const cache2 = loadUsageCache(projectRoot);
|
|
6729
|
-
if (!cache2) return c.json({ indexed: false, count: 0, refs: [], prefixCount: 0, prefixRefs: [] });
|
|
6784
|
+
if (!cache2) return c.json({ indexed: false, count: 0, refs: [], prefixCount: 0, prefixRefs: [], literalCount: 0, literalRefs: [] });
|
|
6730
6785
|
const refs = [];
|
|
6731
6786
|
const prefixRefs = [];
|
|
6787
|
+
const literalRefs = [];
|
|
6732
6788
|
for (const [file, entry] of Object.entries(cache2.files)) {
|
|
6733
6789
|
const abs = resolve9(projectRoot, file);
|
|
6790
|
+
const refLines = /* @__PURE__ */ new Set();
|
|
6734
6791
|
for (const r of entry.refs) {
|
|
6735
|
-
if (r.key === key)
|
|
6792
|
+
if (r.key === key) {
|
|
6793
|
+
refs.push({ file, abs, line: r.line, col: r.col, scanner: r.scanner });
|
|
6794
|
+
refLines.add(r.line);
|
|
6795
|
+
}
|
|
6736
6796
|
}
|
|
6737
6797
|
for (const p of entry.prefixes) {
|
|
6738
6798
|
if (key.startsWith(p.prefix)) {
|
|
6739
6799
|
prefixRefs.push({ file, abs, line: p.line, col: p.col, scanner: p.scanner, prefix: p.prefix });
|
|
6740
6800
|
}
|
|
6741
6801
|
}
|
|
6802
|
+
for (const l of entry.literals ?? []) {
|
|
6803
|
+
if (literalMatcher(l.literal)(key) && !refLines.has(l.line)) {
|
|
6804
|
+
literalRefs.push({ file, abs, line: l.line, col: l.col, literal: l.literal });
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6742
6807
|
}
|
|
6743
6808
|
const byFileLine = (a, b) => a.file.localeCompare(b.file) || a.line - b.line;
|
|
6744
6809
|
refs.sort(byFileLine);
|
|
6745
6810
|
prefixRefs.sort(byFileLine);
|
|
6811
|
+
literalRefs.sort(byFileLine);
|
|
6746
6812
|
return c.json({
|
|
6747
6813
|
indexed: true,
|
|
6748
6814
|
scannedAt: cache2.scannedAt,
|
|
@@ -6750,7 +6816,9 @@ function createApi(deps) {
|
|
|
6750
6816
|
count: refs.length,
|
|
6751
6817
|
refs,
|
|
6752
6818
|
prefixCount: prefixRefs.length,
|
|
6753
|
-
prefixRefs
|
|
6819
|
+
prefixRefs,
|
|
6820
|
+
literalCount: literalRefs.length,
|
|
6821
|
+
literalRefs
|
|
6754
6822
|
});
|
|
6755
6823
|
});
|
|
6756
6824
|
app.get("/scan/used", (c) => {
|
package/dist/server/server.js
CHANGED
|
@@ -186,7 +186,7 @@ function validate(raw) {
|
|
|
186
186
|
if (config.scan !== void 0) {
|
|
187
187
|
const scan = config.scan;
|
|
188
188
|
if (!isObject(scan)) fail("config.scan must be an object");
|
|
189
|
-
for (const f of ["include", "exclude", "accessors", "patterns"]) {
|
|
189
|
+
for (const f of ["include", "exclude", "accessors", "patterns", "keep"]) {
|
|
190
190
|
const v = scan[f];
|
|
191
191
|
if (v !== void 0 && (!Array.isArray(v) || !v.every((x) => typeof x === "string"))) {
|
|
192
192
|
fail(`config.scan.${f} must be an array of strings`);
|
|
@@ -817,6 +817,12 @@ function ensureGlotfileDir(projectRoot) {
|
|
|
817
817
|
return dir;
|
|
818
818
|
}
|
|
819
819
|
|
|
820
|
+
// src/server/glob.ts
|
|
821
|
+
function globToRegExp(glob) {
|
|
822
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
823
|
+
return new RegExp(`^${escaped}$`);
|
|
824
|
+
}
|
|
825
|
+
|
|
820
826
|
// src/server/scan.ts
|
|
821
827
|
function loadUsageCache(projectRoot) {
|
|
822
828
|
const path = resolve2(projectRoot, ".glotfile", "usage.json");
|
|
@@ -854,7 +860,28 @@ function computeUsedKeys(state, cache2) {
|
|
|
854
860
|
if (p.prefix) prefixes.push(p.prefix);
|
|
855
861
|
}
|
|
856
862
|
}
|
|
857
|
-
|
|
863
|
+
const matchers = [];
|
|
864
|
+
const seenLiterals = /* @__PURE__ */ new Set();
|
|
865
|
+
for (const entry of Object.values(cache2.files)) {
|
|
866
|
+
for (const l of entry.literals ?? []) {
|
|
867
|
+
if (!l.literal || seenLiterals.has(l.literal)) continue;
|
|
868
|
+
seenLiterals.add(l.literal);
|
|
869
|
+
matchers.push(literalMatcher(l.literal));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const keep = (state.config.scan?.keep ?? []).map(globToRegExp);
|
|
873
|
+
return Object.keys(state.keys).filter((key) => exact.has(key) || prefixes.some((p) => key.startsWith(p)) || matchers.some((matches) => matches(key)) || keep.some((re) => re.test(key))).sort();
|
|
874
|
+
}
|
|
875
|
+
function escapeRe(s) {
|
|
876
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
877
|
+
}
|
|
878
|
+
function literalMatcher(literal) {
|
|
879
|
+
if (/%[sd]/.test(literal)) {
|
|
880
|
+
const re = new RegExp(`^${literal.split(/%[sd]/).map(escapeRe).join("[^.]+")}$`);
|
|
881
|
+
return (key) => re.test(key);
|
|
882
|
+
}
|
|
883
|
+
if (literal.endsWith(".")) return (key) => key.startsWith(literal);
|
|
884
|
+
return (key) => key === literal || key.startsWith(literal + ".");
|
|
858
885
|
}
|
|
859
886
|
|
|
860
887
|
// src/server/scanner.ts
|
|
@@ -912,7 +939,7 @@ var PREFIX_PATTERNS = {
|
|
|
912
939
|
/(?<!\.)(?<![a-zA-Z0-9_$])\bt\s*\(\s*`([^`$]*)\$\{/g
|
|
913
940
|
]
|
|
914
941
|
};
|
|
915
|
-
var CACHE_VERSION =
|
|
942
|
+
var CACHE_VERSION = 6;
|
|
916
943
|
var EXT_SCANNER = {
|
|
917
944
|
".php": "laravel",
|
|
918
945
|
".vue": "js-i18n",
|
|
@@ -949,7 +976,7 @@ function scannerForExt(ext) {
|
|
|
949
976
|
return EXT_SCANNER[ext] ?? null;
|
|
950
977
|
}
|
|
951
978
|
var FLUTTER_ACCESSOR_DEFAULTS = ["l10n", "loc", "localizations", "translations"];
|
|
952
|
-
function
|
|
979
|
+
function escapeRe2(s) {
|
|
953
980
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
954
981
|
}
|
|
955
982
|
function detectFlutterAccessors(content) {
|
|
@@ -966,7 +993,7 @@ function flutterPatterns(content, opts) {
|
|
|
966
993
|
...FLUTTER_ACCESSOR_DEFAULTS,
|
|
967
994
|
...detectFlutterAccessors(content),
|
|
968
995
|
...opts?.accessors ?? []
|
|
969
|
-
])].map(
|
|
996
|
+
])].map(escapeRe2);
|
|
970
997
|
return [
|
|
971
998
|
// AppLocalizations.of(context)!.key — tolerates the !/? null-assertion.
|
|
972
999
|
/AppLocalizations\.of\([^)]*\)[!?]?\.([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
@@ -1051,6 +1078,38 @@ function extractPrefixes(content, scanner) {
|
|
|
1051
1078
|
result.sort((a, b) => a.line - b.line || a.col - b.col);
|
|
1052
1079
|
return result;
|
|
1053
1080
|
}
|
|
1081
|
+
var KEY_SHAPE = /^[A-Za-z0-9_][A-Za-z0-9_/-]*(?:\.(?:[A-Za-z0-9_-]+|%[sd]))+\.?$/;
|
|
1082
|
+
var STRING_LITERALS = [
|
|
1083
|
+
/'([^'\\\n]+)'/g,
|
|
1084
|
+
/"([^"\\\n]+)"/g,
|
|
1085
|
+
/`([^`\\\n]+)`/g
|
|
1086
|
+
];
|
|
1087
|
+
function extractLiterals(content) {
|
|
1088
|
+
const starts = lineStartOffsets(content);
|
|
1089
|
+
const result = [];
|
|
1090
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1091
|
+
for (const pattern of STRING_LITERALS) {
|
|
1092
|
+
const re = new RegExp(pattern.source, "g");
|
|
1093
|
+
let m;
|
|
1094
|
+
while ((m = re.exec(content)) !== null) {
|
|
1095
|
+
let text = m[1];
|
|
1096
|
+
const marker = text.search(/\{\$|\$\{/);
|
|
1097
|
+
if (marker !== -1) {
|
|
1098
|
+
text = text.slice(0, marker);
|
|
1099
|
+
if (!text.endsWith(".")) continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (!KEY_SHAPE.test(text)) continue;
|
|
1102
|
+
const { line, col } = offsetToLineCol(starts, m.index);
|
|
1103
|
+
const dedup = `${line}:${col}:${text}`;
|
|
1104
|
+
if (!seen.has(dedup)) {
|
|
1105
|
+
seen.add(dedup);
|
|
1106
|
+
result.push({ literal: text, line, col });
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
result.sort((a, b) => a.line - b.line || a.col - b.col);
|
|
1111
|
+
return result;
|
|
1112
|
+
}
|
|
1054
1113
|
function matchesGlob(relPath, glob) {
|
|
1055
1114
|
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(//g, ".*");
|
|
1056
1115
|
return new RegExp(`^${escaped}$`).test(relPath);
|
|
@@ -1125,7 +1184,8 @@ function runScan(projectRoot, opts, existing) {
|
|
|
1125
1184
|
mtime,
|
|
1126
1185
|
size,
|
|
1127
1186
|
refs: extractRefs(content, scanner, opts),
|
|
1128
|
-
prefixes: extractPrefixes(content, scanner)
|
|
1187
|
+
prefixes: extractPrefixes(content, scanner),
|
|
1188
|
+
literals: extractLiterals(content)
|
|
1129
1189
|
};
|
|
1130
1190
|
}
|
|
1131
1191
|
saveUsageCache(projectRoot, cache2);
|
|
@@ -1139,7 +1199,7 @@ var MAX_CONTEXT_LENGTH = 500;
|
|
|
1139
1199
|
var SNIPPET_WINDOW = 15;
|
|
1140
1200
|
var MAX_SNIPPETS = 3;
|
|
1141
1201
|
var EXCLUDED_DIRS = ["node_modules/", "vendor/", "dist/", ".git/", ".glotfile/"];
|
|
1142
|
-
function
|
|
1202
|
+
function globToRegExp2(glob) {
|
|
1143
1203
|
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1144
1204
|
return new RegExp(`^${escaped}$`);
|
|
1145
1205
|
}
|
|
@@ -1182,7 +1242,7 @@ function buildUsageIndex(cache2) {
|
|
|
1182
1242
|
}
|
|
1183
1243
|
function selectContextTargets(state, opts, cache2, lastRunAt) {
|
|
1184
1244
|
const cutoff = opts.all ? void 0 : opts.since ?? lastRunAt;
|
|
1185
|
-
const keyRe = opts.keyGlob ?
|
|
1245
|
+
const keyRe = opts.keyGlob ? globToRegExp2(opts.keyGlob) : null;
|
|
1186
1246
|
const keySet = opts.keys ? new Set(opts.keys) : null;
|
|
1187
1247
|
const usageIndex = buildUsageIndex(cache2);
|
|
1188
1248
|
let candidates = [];
|
|
@@ -1678,12 +1738,6 @@ function runChecks(state, opts = {}) {
|
|
|
1678
1738
|
return { issues: visible, spellPending };
|
|
1679
1739
|
}
|
|
1680
1740
|
|
|
1681
|
-
// src/server/glob.ts
|
|
1682
|
-
function globToRegExp2(glob) {
|
|
1683
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1684
|
-
return new RegExp(`^${escaped}$`);
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
1741
|
// src/server/lint/spelling.ts
|
|
1688
1742
|
var spellingRule = {
|
|
1689
1743
|
id: "spelling",
|
|
@@ -1933,7 +1987,7 @@ async function runLint(state, options = {}) {
|
|
|
1933
1987
|
spellers,
|
|
1934
1988
|
allowWords
|
|
1935
1989
|
};
|
|
1936
|
-
const ignoreRes = (config.ignore ?? []).map(
|
|
1990
|
+
const ignoreRes = (config.ignore ?? []).map(globToRegExp);
|
|
1937
1991
|
const localeFilter = options.locales ? new Set(options.locales) : null;
|
|
1938
1992
|
const findings = [];
|
|
1939
1993
|
let suppressed = 0;
|
|
@@ -3461,7 +3515,7 @@ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
|
3461
3515
|
import { resolve as resolve5, extname as extname2 } from "path";
|
|
3462
3516
|
function selectRequests(state, opts) {
|
|
3463
3517
|
const targets = (opts.locales ?? state.config.locales).filter((l) => l !== state.config.sourceLocale);
|
|
3464
|
-
const keyRe = opts.keyGlob ?
|
|
3518
|
+
const keyRe = opts.keyGlob ? globToRegExp(opts.keyGlob) : null;
|
|
3465
3519
|
const keySet = opts.keys ? new Set(opts.keys) : null;
|
|
3466
3520
|
const reqs = [];
|
|
3467
3521
|
let id = 0;
|
|
@@ -6198,23 +6252,34 @@ function createApi(deps) {
|
|
|
6198
6252
|
app.get("/scan/usage", (c) => {
|
|
6199
6253
|
const key = c.req.query("key") ?? "";
|
|
6200
6254
|
const cache2 = loadUsageCache(projectRoot);
|
|
6201
|
-
if (!cache2) return c.json({ indexed: false, count: 0, refs: [], prefixCount: 0, prefixRefs: [] });
|
|
6255
|
+
if (!cache2) return c.json({ indexed: false, count: 0, refs: [], prefixCount: 0, prefixRefs: [], literalCount: 0, literalRefs: [] });
|
|
6202
6256
|
const refs = [];
|
|
6203
6257
|
const prefixRefs = [];
|
|
6258
|
+
const literalRefs = [];
|
|
6204
6259
|
for (const [file, entry] of Object.entries(cache2.files)) {
|
|
6205
6260
|
const abs = resolve9(projectRoot, file);
|
|
6261
|
+
const refLines = /* @__PURE__ */ new Set();
|
|
6206
6262
|
for (const r of entry.refs) {
|
|
6207
|
-
if (r.key === key)
|
|
6263
|
+
if (r.key === key) {
|
|
6264
|
+
refs.push({ file, abs, line: r.line, col: r.col, scanner: r.scanner });
|
|
6265
|
+
refLines.add(r.line);
|
|
6266
|
+
}
|
|
6208
6267
|
}
|
|
6209
6268
|
for (const p of entry.prefixes) {
|
|
6210
6269
|
if (key.startsWith(p.prefix)) {
|
|
6211
6270
|
prefixRefs.push({ file, abs, line: p.line, col: p.col, scanner: p.scanner, prefix: p.prefix });
|
|
6212
6271
|
}
|
|
6213
6272
|
}
|
|
6273
|
+
for (const l of entry.literals ?? []) {
|
|
6274
|
+
if (literalMatcher(l.literal)(key) && !refLines.has(l.line)) {
|
|
6275
|
+
literalRefs.push({ file, abs, line: l.line, col: l.col, literal: l.literal });
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6214
6278
|
}
|
|
6215
6279
|
const byFileLine = (a, b) => a.file.localeCompare(b.file) || a.line - b.line;
|
|
6216
6280
|
refs.sort(byFileLine);
|
|
6217
6281
|
prefixRefs.sort(byFileLine);
|
|
6282
|
+
literalRefs.sort(byFileLine);
|
|
6218
6283
|
return c.json({
|
|
6219
6284
|
indexed: true,
|
|
6220
6285
|
scannedAt: cache2.scannedAt,
|
|
@@ -6222,7 +6287,9 @@ function createApi(deps) {
|
|
|
6222
6287
|
count: refs.length,
|
|
6223
6288
|
refs,
|
|
6224
6289
|
prefixCount: prefixRefs.length,
|
|
6225
|
-
prefixRefs
|
|
6290
|
+
prefixRefs,
|
|
6291
|
+
literalCount: literalRefs.length,
|
|
6292
|
+
literalRefs
|
|
6226
6293
|
});
|
|
6227
6294
|
});
|
|
6228
6295
|
app.get("/scan/used", (c) => {
|