@dialexjs/language-server 0.7.2 → 0.9.2
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/README.md +6 -5
- package/dist/features-Bpcwl0Es.mjs +361 -0
- package/dist/features-Bpcwl0Es.mjs.map +1 -0
- package/dist/features.mjs +1 -360
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +10 -2
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +281 -224
- package/dist/server.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/features.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -37,11 +37,12 @@ It reads configs and dictionaries from the syntax tree and **never runs your pro
|
|
|
37
37
|
|
|
38
38
|
Sent under the `dialex` section (`workspace/configuration`):
|
|
39
39
|
|
|
40
|
-
| Setting
|
|
41
|
-
|
|
|
42
|
-
| `enable`
|
|
43
|
-
| `unusedKeys`
|
|
44
|
-
| `
|
|
40
|
+
| Setting | Default | Description |
|
|
41
|
+
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------ |
|
|
42
|
+
| `enable` | `true` | Turn all features off |
|
|
43
|
+
| `unusedKeys` | `false` | Fade out keys and dictionaries that no source file seems to use |
|
|
44
|
+
| `autoGenerate` | `true` | Regenerate `dialex.generated.ts` when a dictionary or config file changes, in projects that already have one |
|
|
45
|
+
| `configPath` | — | Config file relative to each project root; empty uses `dialex.config.*` |
|
|
45
46
|
|
|
46
47
|
## Library use
|
|
47
48
|
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { TODO_PREFIX, copyLeaf, dictionaryLocation, getLeafSource, hasPath, listLocales, locationOf, parseDictionaryText, renderDictionaryFile, scanFiles, scanReferences, setString } from "@dialexjs/cli/api";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
//#region src/features/model.ts
|
|
5
|
+
function buildModel(root, defaultLocale, result) {
|
|
6
|
+
return {
|
|
7
|
+
root,
|
|
8
|
+
defaultLocale,
|
|
9
|
+
dictionaries: new Map(result.dictionaries.map((d) => [d.name, d]))
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** The model whose root contains `file`, preferring the deepest (nested projects). */
|
|
13
|
+
function modelForFile(models, file) {
|
|
14
|
+
let best;
|
|
15
|
+
for (const model of models) {
|
|
16
|
+
const rel = path.relative(model.root, file);
|
|
17
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
18
|
+
if (!best || model.root.length > best.root.length) best = model;
|
|
19
|
+
}
|
|
20
|
+
return best;
|
|
21
|
+
}
|
|
22
|
+
/** Locales with the default locale first. */
|
|
23
|
+
function orderedLocales(model, dictionary) {
|
|
24
|
+
return [...dictionary.locales.filter((l) => l === model.defaultLocale), ...dictionary.locales.filter((l) => l !== model.defaultLocale)];
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/features/bindings.ts
|
|
28
|
+
/**
|
|
29
|
+
* `const s = useDictionary("showcase")`, `const home = await getDictionary("home", locale)`,
|
|
30
|
+
* `let t: Home = injectDictionary<Home>("home")`: a variable that holds a dictionary.
|
|
31
|
+
*/
|
|
32
|
+
const DECLARATION = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=\n]+)?=\s*(?:await\s+)?(?:[A-Za-z_$][\w$]*\.)*(?:getDictionary|useDictionary|injectDictionary)\s*(?:<[^()]*?>)?\(\s*(["'`])([^"'`$\n]+)\2/g;
|
|
33
|
+
/**
|
|
34
|
+
* Finds the variables in a file that hold a dictionary, so `s.nav.features` can be resolved to
|
|
35
|
+
* `showcase.nav.features`. It is a text match: a variable that is reassigned or shadowed in another
|
|
36
|
+
* scope may resolve to the wrong dictionary, which only affects editor hints.
|
|
37
|
+
*/
|
|
38
|
+
function findBindings(text) {
|
|
39
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const match of text.matchAll(DECLARATION)) bindings.set(match[1], match[3]);
|
|
41
|
+
return bindings;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/features/references.ts
|
|
45
|
+
/** `getDictionary("home").nav.about` -> the `.nav.about` member chain after the call. */
|
|
46
|
+
const MEMBER_CHAIN = /(?:getDictionary|useDictionary)\(\s*(["'`])([^"'`$\n]+)\1[^()\n]*\)((?:\??\.[A-Za-z_$][\w$]*)+)/dg;
|
|
47
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48
|
+
/**
|
|
49
|
+
* The Dialex reference under the cursor on one line of code: a dictionary name, a `t("a.b.c")`
|
|
50
|
+
* path, a member of `getDictionary("a")` such as `.title`, or, with `bindings`, a member of a
|
|
51
|
+
* variable that holds a dictionary (`s.nav.features` after `const s = useDictionary("showcase")`).
|
|
52
|
+
* Offsets are into `lineText`.
|
|
53
|
+
*/
|
|
54
|
+
function findReferenceAt(lineText, character, bindings) {
|
|
55
|
+
const direct = scanReferences(lineText).find((r) => character >= r.start && character <= r.end);
|
|
56
|
+
if (direct) return direct;
|
|
57
|
+
for (const match of lineText.matchAll(MEMBER_CHAIN)) {
|
|
58
|
+
const dictionary = match[2];
|
|
59
|
+
const [chainStart] = match.indices[3];
|
|
60
|
+
const members = /\??\.([A-Za-z_$][\w$]*)/g;
|
|
61
|
+
const keys = [];
|
|
62
|
+
for (const member of match[3].matchAll(members)) {
|
|
63
|
+
const start = chainStart + member.index + member[0].length - member[1].length;
|
|
64
|
+
const end = start + member[1].length;
|
|
65
|
+
keys.push(member[1]);
|
|
66
|
+
if (character >= start && character <= end) return {
|
|
67
|
+
kind: "path",
|
|
68
|
+
value: [dictionary, ...keys].join("."),
|
|
69
|
+
start,
|
|
70
|
+
end
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const [name, dictionary] of bindings ?? []) {
|
|
75
|
+
const chain = new RegExp(`(?<![\\w$.])${escapeRegExp(name)}(?![\\w$])((?:\\??\\.[A-Za-z_$][\\w$]*)*)`, "dg");
|
|
76
|
+
for (const match of lineText.matchAll(chain)) {
|
|
77
|
+
const nameStart = match.index;
|
|
78
|
+
const nameEnd = nameStart + name.length;
|
|
79
|
+
if (character >= nameStart && character <= nameEnd) return {
|
|
80
|
+
kind: "dictionary",
|
|
81
|
+
value: dictionary,
|
|
82
|
+
start: nameStart,
|
|
83
|
+
end: nameEnd
|
|
84
|
+
};
|
|
85
|
+
const [chainStart] = match.indices[1];
|
|
86
|
+
const keys = [];
|
|
87
|
+
for (const member of match[1].matchAll(/\??\.([A-Za-z_$][\w$]*)/g)) {
|
|
88
|
+
const start = chainStart + member.index + member[0].length - member[1].length;
|
|
89
|
+
const end = start + member[1].length;
|
|
90
|
+
keys.push(member[1]);
|
|
91
|
+
if (character >= start && character <= end) return {
|
|
92
|
+
kind: "path",
|
|
93
|
+
value: [dictionary, ...keys].join("."),
|
|
94
|
+
start,
|
|
95
|
+
end
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function resolveReference(model, ref) {
|
|
102
|
+
if (ref.kind === "dictionary") {
|
|
103
|
+
const dictionary = model.dictionaries.get(ref.value);
|
|
104
|
+
return dictionary ? {
|
|
105
|
+
dictionary,
|
|
106
|
+
keyPath: []
|
|
107
|
+
} : void 0;
|
|
108
|
+
}
|
|
109
|
+
const [name, ...keyPath] = ref.value.split(".");
|
|
110
|
+
const dictionary = model.dictionaries.get(name);
|
|
111
|
+
return dictionary ? {
|
|
112
|
+
dictionary,
|
|
113
|
+
keyPath
|
|
114
|
+
} : void 0;
|
|
115
|
+
}
|
|
116
|
+
/** Whether `keyPath` is a leaf, a group of leaves, or nothing in this dictionary. */
|
|
117
|
+
function classifyPath(dictionary, keyPath) {
|
|
118
|
+
const joined = keyPath.join(".");
|
|
119
|
+
if (dictionary.paths.includes(joined)) return "leaf";
|
|
120
|
+
return dictionary.paths.some((p) => p.startsWith(`${joined}.`)) ? "group" : "missing";
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/features/hover.ts
|
|
124
|
+
/** Escapes Markdown so translation text cannot change the hover's formatting. */
|
|
125
|
+
function escapeMarkdown(text) {
|
|
126
|
+
return text.replace(/[\\`*_{}[\]()#+!|<>~]/g, "\\$&").replace(/\r?\n/g, "<br>").slice(0, 400);
|
|
127
|
+
}
|
|
128
|
+
function cell(dictionary, locale, keyPath) {
|
|
129
|
+
const leaf = dictionary.leaves.get(locale)?.get(keyPath.join("."));
|
|
130
|
+
if (!leaf) return "_missing_";
|
|
131
|
+
if (leaf.kind === "string") return escapeMarkdown(leaf.value ?? "");
|
|
132
|
+
if (leaf.kind === "function") {
|
|
133
|
+
const source = dictionary.df ? getLeafSource(dictionary.df, locale, keyPath) : void 0;
|
|
134
|
+
return source ? `\`${source.replace(/`/g, "'").replace(/\s+/g, " ").slice(0, 160)}\`` : "_function_";
|
|
135
|
+
}
|
|
136
|
+
return "_value_";
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Markdown for a hover over a Dialex reference: every locale's text for a key, or a summary of a
|
|
140
|
+
* dictionary. Returns `undefined` for unknown references (diagnostics already flag those).
|
|
141
|
+
*/
|
|
142
|
+
function buildHover(model, ref) {
|
|
143
|
+
const resolved = resolveReference(model, ref);
|
|
144
|
+
if (!resolved) return void 0;
|
|
145
|
+
const { dictionary, keyPath } = resolved;
|
|
146
|
+
const file = path.basename(dictionary.file);
|
|
147
|
+
if (keyPath.length === 0) return [
|
|
148
|
+
`**${dictionary.name}** · ${file}`,
|
|
149
|
+
"",
|
|
150
|
+
`${dictionary.paths.length} key${dictionary.paths.length === 1 ? "" : "s"} · locales: ${dictionary.locales.join(", ")}`
|
|
151
|
+
].join("\n");
|
|
152
|
+
const kind = classifyPath(dictionary, keyPath);
|
|
153
|
+
const title = `**${dictionary.name}.${keyPath.join(".")}** · ${file}`;
|
|
154
|
+
if (kind === "missing") return void 0;
|
|
155
|
+
if (kind === "group") {
|
|
156
|
+
const prefix = `${keyPath.join(".")}.`;
|
|
157
|
+
const children = new Set(dictionary.paths.filter((p) => p.startsWith(prefix)).map((p) => p.slice(prefix.length).split(".")[0]));
|
|
158
|
+
return `${title}\n\n${children.size} key${children.size === 1 ? "" : "s"}: ${[...children].map((c) => `\`${c}\``).join(", ")}`;
|
|
159
|
+
}
|
|
160
|
+
return [
|
|
161
|
+
title,
|
|
162
|
+
"",
|
|
163
|
+
"| Locale | Text |",
|
|
164
|
+
"| --- | --- |",
|
|
165
|
+
...orderedLocales(model, dictionary).map((locale) => `| ${locale} | ${cell(dictionary, locale, keyPath)} |`)
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/features/definition.ts
|
|
170
|
+
const START = {
|
|
171
|
+
start: {
|
|
172
|
+
line: 0,
|
|
173
|
+
column: 0
|
|
174
|
+
},
|
|
175
|
+
end: {
|
|
176
|
+
line: 0,
|
|
177
|
+
column: 0
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Where a reference is defined: the key in the default locale if it has one, otherwise in the
|
|
182
|
+
* first locale that does. A dictionary reference points at the dictionary object.
|
|
183
|
+
*/
|
|
184
|
+
function definitionFor(model, ref) {
|
|
185
|
+
const resolved = resolveReference(model, ref);
|
|
186
|
+
if (!resolved) return void 0;
|
|
187
|
+
const { dictionary, keyPath } = resolved;
|
|
188
|
+
const { df } = dictionary;
|
|
189
|
+
if (!df) return {
|
|
190
|
+
file: dictionary.file,
|
|
191
|
+
range: START
|
|
192
|
+
};
|
|
193
|
+
if (keyPath.length === 0) return {
|
|
194
|
+
file: dictionary.file,
|
|
195
|
+
range: dictionaryLocation(df) ?? START
|
|
196
|
+
};
|
|
197
|
+
for (const locale of orderedLocales(model, dictionary)) {
|
|
198
|
+
const range = locationOf(df, locale, keyPath);
|
|
199
|
+
if (range) return {
|
|
200
|
+
file: dictionary.file,
|
|
201
|
+
range
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/features/completion.ts
|
|
207
|
+
const CALL = /(?<![\w$.])(t|getDictionary|useDictionary|DialexDictionary)\(\s*(["'`])([^"'`\n]*)$/;
|
|
208
|
+
/**
|
|
209
|
+
* Whether the cursor is inside the string argument of a Dialex call, and what is being typed.
|
|
210
|
+
* `textBeforeCursor` is the line up to the cursor. `length` is how many characters before the
|
|
211
|
+
* cursor a completion replaces.
|
|
212
|
+
*/
|
|
213
|
+
function completionContextAt(textBeforeCursor, bindings) {
|
|
214
|
+
const match = CALL.exec(textBeforeCursor);
|
|
215
|
+
if (!match) return bindings ? memberContextAt(textBeforeCursor, bindings) : void 0;
|
|
216
|
+
const [, call, , value] = match;
|
|
217
|
+
if (call === "t" && value.includes(".")) {
|
|
218
|
+
const segments = value.split(".");
|
|
219
|
+
const typed = segments.pop();
|
|
220
|
+
const [dictionary, ...parent] = segments;
|
|
221
|
+
return {
|
|
222
|
+
kind: "path",
|
|
223
|
+
dictionary,
|
|
224
|
+
parent,
|
|
225
|
+
typed,
|
|
226
|
+
length: typed.length
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
kind: "dictionary",
|
|
231
|
+
call,
|
|
232
|
+
typed: value,
|
|
233
|
+
length: value.length
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** `s.nav.fe|` where `s` holds a dictionary: the keys under `nav` that start with `fe`. */
|
|
237
|
+
function memberContextAt(textBeforeCursor, bindings) {
|
|
238
|
+
for (const [name, dictionary] of bindings) {
|
|
239
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
240
|
+
const match = new RegExp(`(?<![\\w$.])${escaped}((?:\\??\\.[A-Za-z_$][\\w$]*)*)\\??\\.([A-Za-z_$][\\w$]*)?$`).exec(textBeforeCursor);
|
|
241
|
+
if (!match) continue;
|
|
242
|
+
const parent = [...match[1].matchAll(/\??\.([A-Za-z_$][\w$]*)/g)].map((m) => m[1]);
|
|
243
|
+
const typed = match[2] ?? "";
|
|
244
|
+
return {
|
|
245
|
+
kind: "path",
|
|
246
|
+
dictionary,
|
|
247
|
+
parent,
|
|
248
|
+
typed,
|
|
249
|
+
length: typed.length
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const preview = (text) => text === void 0 ? void 0 : text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
254
|
+
function dictionaryEntries(model, context) {
|
|
255
|
+
return [...model.dictionaries.values()].filter((d) => d.name.startsWith(context.typed)).map((d) => {
|
|
256
|
+
const continues = context.call === "t";
|
|
257
|
+
return {
|
|
258
|
+
label: d.name,
|
|
259
|
+
kind: "dictionary",
|
|
260
|
+
detail: `${d.paths.length} keys · ${d.locales.join(", ")}`,
|
|
261
|
+
insertText: continues ? `${d.name}.` : d.name,
|
|
262
|
+
retrigger: continues
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function pathEntries(model, dictionary, context) {
|
|
267
|
+
const prefix = context.parent.length > 0 ? `${context.parent.join(".")}.` : "";
|
|
268
|
+
const children = /* @__PURE__ */ new Map();
|
|
269
|
+
for (const keyPath of dictionary.paths) {
|
|
270
|
+
if (!keyPath.startsWith(prefix)) continue;
|
|
271
|
+
const rest = keyPath.slice(prefix.length).split(".");
|
|
272
|
+
const name = rest[0];
|
|
273
|
+
if (!name.startsWith(context.typed)) continue;
|
|
274
|
+
const entry = children.get(name) ?? { leaf: true };
|
|
275
|
+
if (rest.length > 1) entry.leaf = false;
|
|
276
|
+
children.set(name, entry);
|
|
277
|
+
}
|
|
278
|
+
return [...children].map(([name, { leaf }]) => {
|
|
279
|
+
const full = `${prefix}${name}`;
|
|
280
|
+
const holder = [model.defaultLocale, ...dictionary.locales].find((l) => dictionary.leaves.get(l)?.has(full));
|
|
281
|
+
const text = holder ? dictionary.leaves.get(holder).get(full) : void 0;
|
|
282
|
+
return {
|
|
283
|
+
label: name,
|
|
284
|
+
kind: leaf ? "leaf" : "group",
|
|
285
|
+
detail: leaf ? preview(text?.kind === "string" ? text.value : text ? `(${text.kind})` : void 0) ?? "" : "keys…",
|
|
286
|
+
insertText: leaf ? name : `${name}.`,
|
|
287
|
+
retrigger: !leaf
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/** Suggestions for the string being typed, filtered by what is already there. */
|
|
292
|
+
function completionEntries(model, context) {
|
|
293
|
+
if (context.kind === "dictionary") return dictionaryEntries(model, context);
|
|
294
|
+
const dictionary = model.dictionaries.get(context.dictionary);
|
|
295
|
+
return dictionary ? pathEntries(model, dictionary, context) : [];
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/features/quickfix.ts
|
|
299
|
+
/**
|
|
300
|
+
* New text for a dictionary file with `missing` keys added to their locales, copied from the
|
|
301
|
+
* source locale (strings get the `[TODO] ` marker, like `dialex check --fix`). Returns
|
|
302
|
+
* `undefined` if nothing could be added.
|
|
303
|
+
*/
|
|
304
|
+
function addMissingKeys(file, text, missing) {
|
|
305
|
+
const df = parseDictionaryText(file, text);
|
|
306
|
+
if (!df) return void 0;
|
|
307
|
+
let changed = false;
|
|
308
|
+
for (const { locale, path, sourceLocale } of missing) {
|
|
309
|
+
if (!sourceLocale) continue;
|
|
310
|
+
if (copyLeaf(df, sourceLocale, locale, path, (value) => `${TODO_PREFIX}${value}`)) changed = true;
|
|
311
|
+
}
|
|
312
|
+
return changed ? renderDictionaryFile(df) : void 0;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* New text for a dictionary file with a key that code refers to but the dictionary lacks, added
|
|
316
|
+
* to every locale as a `[TODO]` placeholder named after the key.
|
|
317
|
+
*/
|
|
318
|
+
function addUnknownKey(file, text, keyPath) {
|
|
319
|
+
const df = parseDictionaryText(file, text);
|
|
320
|
+
if (!df) return void 0;
|
|
321
|
+
const placeholder = `${TODO_PREFIX}${keyPath[keyPath.length - 1]}`;
|
|
322
|
+
let changed = false;
|
|
323
|
+
for (const locale of listLocales(df)) if (!hasPath(df, locale, keyPath) && setString(df, locale, keyPath, placeholder) !== "skipped") changed = true;
|
|
324
|
+
return changed ? renderDictionaryFile(df) : void 0;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/features/projects.ts
|
|
328
|
+
const IGNORE = ["**/.git/**", "**/.nuxt/**"];
|
|
329
|
+
/** The nearest folder at or above `dir` (but inside `folder`) that has a package.json. */
|
|
330
|
+
function packageRoot(folder, dir) {
|
|
331
|
+
for (let current = dir; current.startsWith(folder); current = path.dirname(current)) {
|
|
332
|
+
if (fs.existsSync(path.join(current, "package.json"))) return current;
|
|
333
|
+
if (current === folder) break;
|
|
334
|
+
}
|
|
335
|
+
return folder;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Finds the Dialex projects inside a workspace folder.
|
|
339
|
+
*
|
|
340
|
+
* - Every directory with a `dialex.config.*` or `i18n.config.*` is a project.
|
|
341
|
+
* - A `*.content.ts` file that no such project contains belongs to the nearest folder with a
|
|
342
|
+
* `package.json` (or the workspace folder), which then uses the default settings. This covers
|
|
343
|
+
* monorepos where only some packages have a config file.
|
|
344
|
+
* - A project excludes the projects nested inside it, so one dictionary is never reported twice.
|
|
345
|
+
*/
|
|
346
|
+
function discoverProjects(folder) {
|
|
347
|
+
const configs = scanFiles(folder, ["**/dialex.config.*", "**/i18n.config.*"], { ignore: IGNORE });
|
|
348
|
+
const configured = [...new Set(configs.map((f) => path.dirname(f)))];
|
|
349
|
+
const roots = new Set(configured);
|
|
350
|
+
const dictionaries = scanFiles(folder, "**/*.content.ts", { ignore: IGNORE });
|
|
351
|
+
for (const file of dictionaries) if (!configured.some((root) => file.startsWith(root + path.sep))) roots.add(packageRoot(folder, path.dirname(file)));
|
|
352
|
+
const all = [...roots].sort((a, b) => a.length - b.length);
|
|
353
|
+
return all.map((root) => ({
|
|
354
|
+
root,
|
|
355
|
+
ignore: all.filter((other) => other !== root && other.startsWith(root + path.sep)).map((other) => `${path.relative(root, other).split(path.sep).join("/")}/**`)
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
export { completionEntries as a, escapeMarkdown as c, resolveReference as d, findBindings as f, orderedLocales as h, completionContextAt as i, classifyPath as l, modelForFile as m, addMissingKeys as n, definitionFor as o, buildModel as p, addUnknownKey as r, buildHover as s, discoverProjects as t, findReferenceAt as u };
|
|
360
|
+
|
|
361
|
+
//# sourceMappingURL=features-Bpcwl0Es.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features-Bpcwl0Es.mjs","names":[],"sources":["../src/features/model.ts","../src/features/bindings.ts","../src/features/references.ts","../src/features/hover.ts","../src/features/definition.ts","../src/features/completion.ts","../src/features/quickfix.ts","../src/features/projects.ts"],"sourcesContent":["import path from \"node:path\";\n\nimport type { AnalysisResult, AnalyzedDictionary } from \"@dialexjs/cli/api\";\n\n/** What the editor features know about one Dialex project (a folder with its own config). */\nexport interface ProjectModel {\n root: string;\n defaultLocale: string;\n dictionaries: Map<string, AnalyzedDictionary>;\n}\n\nexport function buildModel(\n root: string,\n defaultLocale: string,\n result: Pick<AnalysisResult, \"dictionaries\">,\n): ProjectModel {\n return {\n root,\n defaultLocale,\n dictionaries: new Map(result.dictionaries.map((d) => [d.name, d])),\n };\n}\n\n/** The model whose root contains `file`, preferring the deepest (nested projects). */\nexport function modelForFile(\n models: Iterable<ProjectModel>,\n file: string,\n): ProjectModel | undefined {\n let best: ProjectModel | undefined;\n for (const model of models) {\n const rel = path.relative(model.root, file);\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) continue;\n if (!best || model.root.length > best.root.length) best = model;\n }\n return best;\n}\n\n/** Locales with the default locale first. */\nexport function orderedLocales(model: ProjectModel, dictionary: AnalyzedDictionary): string[] {\n return [\n ...dictionary.locales.filter((l) => l === model.defaultLocale),\n ...dictionary.locales.filter((l) => l !== model.defaultLocale),\n ];\n}\n","/** Variable name -> dictionary name, for `const s = useDictionary(\"showcase\")`. */\nexport type Bindings = ReadonlyMap<string, string>;\n\n/**\n * `const s = useDictionary(\"showcase\")`, `const home = await getDictionary(\"home\", locale)`,\n * `let t: Home = injectDictionary<Home>(\"home\")`: a variable that holds a dictionary.\n */\nconst DECLARATION =\n /\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::[^=\\n]+)?=\\s*(?:await\\s+)?(?:[A-Za-z_$][\\w$]*\\.)*(?:getDictionary|useDictionary|injectDictionary)\\s*(?:<[^()]*?>)?\\(\\s*([\"'`])([^\"'`$\\n]+)\\2/g;\n\n/**\n * Finds the variables in a file that hold a dictionary, so `s.nav.features` can be resolved to\n * `showcase.nav.features`. It is a text match: a variable that is reassigned or shadowed in another\n * scope may resolve to the wrong dictionary, which only affects editor hints.\n */\nexport function findBindings(text: string): Bindings {\n const bindings = new Map<string, string>();\n for (const match of text.matchAll(DECLARATION)) {\n bindings.set(match[1], match[3]);\n }\n return bindings;\n}\n","import { scanReferences, type AnalyzedDictionary, type Reference } from \"@dialexjs/cli/api\";\n\nimport type { Bindings } from \"./bindings.js\";\nimport type { ProjectModel } from \"./model.js\";\n\n/** `getDictionary(\"home\").nav.about` -> the `.nav.about` member chain after the call. */\nconst MEMBER_CHAIN =\n /(?:getDictionary|useDictionary)\\(\\s*([\"'`])([^\"'`$\\n]+)\\1[^()\\n]*\\)((?:\\??\\.[A-Za-z_$][\\w$]*)+)/dg;\n\nconst escapeRegExp = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\n/**\n * The Dialex reference under the cursor on one line of code: a dictionary name, a `t(\"a.b.c\")`\n * path, a member of `getDictionary(\"a\")` such as `.title`, or, with `bindings`, a member of a\n * variable that holds a dictionary (`s.nav.features` after `const s = useDictionary(\"showcase\")`).\n * Offsets are into `lineText`.\n */\nexport function findReferenceAt(\n lineText: string,\n character: number,\n bindings?: Bindings,\n): Reference | undefined {\n const direct = scanReferences(lineText).find((r) => character >= r.start && character <= r.end);\n if (direct) return direct;\n\n for (const match of lineText.matchAll(MEMBER_CHAIN)) {\n const dictionary = match[2];\n const [chainStart] = match.indices![3] as [number, number];\n const members = /\\??\\.([A-Za-z_$][\\w$]*)/g;\n const keys: string[] = [];\n for (const member of match[3].matchAll(members)) {\n const start = chainStart + member.index! + member[0].length - member[1].length;\n const end = start + member[1].length;\n keys.push(member[1]);\n if (character >= start && character <= end) {\n return { kind: \"path\", value: [dictionary, ...keys].join(\".\"), start, end };\n }\n }\n }\n\n for (const [name, dictionary] of bindings ?? []) {\n const chain = new RegExp(\n `(?<![\\\\w$.])${escapeRegExp(name)}(?![\\\\w$])((?:\\\\??\\\\.[A-Za-z_$][\\\\w$]*)*)`,\n \"dg\",\n );\n for (const match of lineText.matchAll(chain)) {\n const nameStart = match.index!;\n const nameEnd = nameStart + name.length;\n if (character >= nameStart && character <= nameEnd) {\n return { kind: \"dictionary\", value: dictionary, start: nameStart, end: nameEnd };\n }\n\n const [chainStart] = match.indices![1] as [number, number];\n const keys: string[] = [];\n for (const member of match[1].matchAll(/\\??\\.([A-Za-z_$][\\w$]*)/g)) {\n const start = chainStart + member.index! + member[0].length - member[1].length;\n const end = start + member[1].length;\n keys.push(member[1]);\n if (character >= start && character <= end) {\n return { kind: \"path\", value: [dictionary, ...keys].join(\".\"), start, end };\n }\n }\n }\n }\n return undefined;\n}\n\nexport interface ResolvedReference {\n dictionary: AnalyzedDictionary;\n /** Empty for a reference to the dictionary itself. */\n keyPath: string[];\n}\n\nexport function resolveReference(\n model: ProjectModel,\n ref: Reference,\n): ResolvedReference | undefined {\n if (ref.kind === \"dictionary\") {\n const dictionary = model.dictionaries.get(ref.value);\n return dictionary ? { dictionary, keyPath: [] } : undefined;\n }\n const [name, ...keyPath] = ref.value.split(\".\");\n const dictionary = model.dictionaries.get(name);\n return dictionary ? { dictionary, keyPath } : undefined;\n}\n\n/** Whether `keyPath` is a leaf, a group of leaves, or nothing in this dictionary. */\nexport function classifyPath(\n dictionary: AnalyzedDictionary,\n keyPath: string[],\n): \"leaf\" | \"group\" | \"missing\" {\n const joined = keyPath.join(\".\");\n if (dictionary.paths.includes(joined)) return \"leaf\";\n return dictionary.paths.some((p) => p.startsWith(`${joined}.`)) ? \"group\" : \"missing\";\n}\n","import path from \"node:path\";\n\nimport { getLeafSource, type AnalyzedDictionary, type Reference } from \"@dialexjs/cli/api\";\n\nimport { orderedLocales, type ProjectModel } from \"./model.js\";\nimport { classifyPath, resolveReference } from \"./references.js\";\n\n/** Escapes Markdown so translation text cannot change the hover's formatting. */\nexport function escapeMarkdown(text: string): string {\n return text\n .replace(/[\\\\`*_{}[\\]()#+!|<>~]/g, \"\\\\$&\")\n .replace(/\\r?\\n/g, \"<br>\")\n .slice(0, 400);\n}\n\nfunction cell(dictionary: AnalyzedDictionary, locale: string, keyPath: string[]): string {\n const leaf = dictionary.leaves.get(locale)?.get(keyPath.join(\".\"));\n if (!leaf) return \"_missing_\";\n if (leaf.kind === \"string\") return escapeMarkdown(leaf.value ?? \"\");\n if (leaf.kind === \"function\") {\n const source = dictionary.df ? getLeafSource(dictionary.df, locale, keyPath) : undefined;\n return source\n ? `\\`${source.replace(/`/g, \"'\").replace(/\\s+/g, \" \").slice(0, 160)}\\``\n : \"_function_\";\n }\n return \"_value_\";\n}\n\n/**\n * Markdown for a hover over a Dialex reference: every locale's text for a key, or a summary of a\n * dictionary. Returns `undefined` for unknown references (diagnostics already flag those).\n */\nexport function buildHover(model: ProjectModel, ref: Reference): string | undefined {\n const resolved = resolveReference(model, ref);\n if (!resolved) return undefined;\n const { dictionary, keyPath } = resolved;\n const file = path.basename(dictionary.file);\n\n if (keyPath.length === 0) {\n return [\n `**${dictionary.name}** · ${file}`,\n \"\",\n `${dictionary.paths.length} key${dictionary.paths.length === 1 ? \"\" : \"s\"} · locales: ${dictionary.locales.join(\", \")}`,\n ].join(\"\\n\");\n }\n\n const kind = classifyPath(dictionary, keyPath);\n const title = `**${dictionary.name}.${keyPath.join(\".\")}** · ${file}`;\n\n if (kind === \"missing\") return undefined;\n\n if (kind === \"group\") {\n const prefix = `${keyPath.join(\".\")}.`;\n const children = new Set(\n dictionary.paths\n .filter((p) => p.startsWith(prefix))\n .map((p) => p.slice(prefix.length).split(\".\")[0]),\n );\n return `${title}\\n\\n${children.size} key${children.size === 1 ? \"\" : \"s\"}: ${[...children].map((c) => `\\`${c}\\``).join(\", \")}`;\n }\n\n const rows = orderedLocales(model, dictionary).map(\n (locale) => `| ${locale} | ${cell(dictionary, locale, keyPath)} |`,\n );\n return [title, \"\", \"| Locale | Text |\", \"| --- | --- |\", ...rows].join(\"\\n\");\n}\n","import { dictionaryLocation, locationOf, type SourceRange } from \"@dialexjs/cli/api\";\nimport type { Reference } from \"@dialexjs/cli/api\";\n\nimport { orderedLocales, type ProjectModel } from \"./model.js\";\nimport { resolveReference } from \"./references.js\";\n\nexport interface DefinitionTarget {\n file: string;\n range: SourceRange;\n}\n\nconst START: SourceRange = { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };\n\n/**\n * Where a reference is defined: the key in the default locale if it has one, otherwise in the\n * first locale that does. A dictionary reference points at the dictionary object.\n */\nexport function definitionFor(model: ProjectModel, ref: Reference): DefinitionTarget | undefined {\n const resolved = resolveReference(model, ref);\n if (!resolved) return undefined;\n const { dictionary, keyPath } = resolved;\n const { df } = dictionary;\n if (!df) return { file: dictionary.file, range: START };\n\n if (keyPath.length === 0) {\n return { file: dictionary.file, range: dictionaryLocation(df) ?? START };\n }\n\n for (const locale of orderedLocales(model, dictionary)) {\n const range = locationOf(df, locale, keyPath);\n if (range) return { file: dictionary.file, range };\n }\n return undefined;\n}\n","import type { AnalyzedDictionary } from \"@dialexjs/cli/api\";\n\nimport type { Bindings } from \"./bindings.js\";\nimport type { ProjectModel } from \"./model.js\";\n\nexport type CompletionContext =\n | { kind: \"dictionary\"; call: string; typed: string; length: number }\n | { kind: \"path\"; dictionary: string; parent: string[]; typed: string; length: number };\n\nconst CALL = /(?<![\\w$.])(t|getDictionary|useDictionary|DialexDictionary)\\(\\s*([\"'`])([^\"'`\\n]*)$/;\n\n/**\n * Whether the cursor is inside the string argument of a Dialex call, and what is being typed.\n * `textBeforeCursor` is the line up to the cursor. `length` is how many characters before the\n * cursor a completion replaces.\n */\nexport function completionContextAt(\n textBeforeCursor: string,\n bindings?: Bindings,\n): CompletionContext | undefined {\n const match = CALL.exec(textBeforeCursor);\n if (!match) return bindings ? memberContextAt(textBeforeCursor, bindings) : undefined;\n const [, call, , value] = match;\n\n if (call === \"t\" && value.includes(\".\")) {\n const segments = value.split(\".\");\n const typed = segments.pop()!;\n const [dictionary, ...parent] = segments;\n return { kind: \"path\", dictionary, parent, typed, length: typed.length };\n }\n return { kind: \"dictionary\", call, typed: value, length: value.length };\n}\n\n/** `s.nav.fe|` where `s` holds a dictionary: the keys under `nav` that start with `fe`. */\nfunction memberContextAt(\n textBeforeCursor: string,\n bindings: Bindings,\n): CompletionContext | undefined {\n for (const [name, dictionary] of bindings) {\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const match = new RegExp(\n `(?<![\\\\w$.])${escaped}((?:\\\\??\\\\.[A-Za-z_$][\\\\w$]*)*)\\\\??\\\\.([A-Za-z_$][\\\\w$]*)?$`,\n ).exec(textBeforeCursor);\n if (!match) continue;\n\n const parent = [...match[1].matchAll(/\\??\\.([A-Za-z_$][\\w$]*)/g)].map((m) => m[1]);\n const typed = match[2] ?? \"\";\n return { kind: \"path\", dictionary, parent, typed, length: typed.length };\n }\n return undefined;\n}\n\nexport interface CompletionEntry {\n label: string;\n kind: \"dictionary\" | \"group\" | \"leaf\";\n detail: string;\n /** Text to insert in place of what was typed. */\n insertText: string;\n /** Re-open the suggestion list after inserting (after `home.` or `nav.`). */\n retrigger: boolean;\n documentation?: string;\n}\n\nconst preview = (text: string | undefined) =>\n text === undefined ? undefined : text.length > 60 ? `${text.slice(0, 57)}...` : text;\n\nfunction dictionaryEntries(\n model: ProjectModel,\n context: Extract<CompletionContext, { kind: \"dictionary\" }>,\n) {\n return [...model.dictionaries.values()]\n .filter((d) => d.name.startsWith(context.typed))\n .map<CompletionEntry>((d) => {\n // t(\"home.title\") continues with a dot; getDictionary(\"home\") is complete as is\n const continues = context.call === \"t\";\n return {\n label: d.name,\n kind: \"dictionary\",\n detail: `${d.paths.length} keys · ${d.locales.join(\", \")}`,\n insertText: continues ? `${d.name}.` : d.name,\n retrigger: continues,\n };\n });\n}\n\nfunction pathEntries(\n model: ProjectModel,\n dictionary: AnalyzedDictionary,\n context: Extract<CompletionContext, { kind: \"path\" }>,\n) {\n const prefix = context.parent.length > 0 ? `${context.parent.join(\".\")}.` : \"\";\n const children = new Map<string, { leaf: boolean }>();\n\n for (const keyPath of dictionary.paths) {\n if (!keyPath.startsWith(prefix)) continue;\n const rest = keyPath.slice(prefix.length).split(\".\");\n const name = rest[0];\n if (!name.startsWith(context.typed)) continue;\n const entry = children.get(name) ?? { leaf: true };\n if (rest.length > 1) entry.leaf = false;\n children.set(name, entry);\n }\n\n return [...children].map<CompletionEntry>(([name, { leaf }]) => {\n const full = `${prefix}${name}`;\n const holder = [model.defaultLocale, ...dictionary.locales].find((l) =>\n dictionary.leaves.get(l)?.has(full),\n );\n const text = holder ? dictionary.leaves.get(holder)!.get(full)! : undefined;\n return {\n label: name,\n kind: leaf ? \"leaf\" : \"group\",\n detail: leaf\n ? (preview(text?.kind === \"string\" ? text.value : text ? `(${text.kind})` : undefined) ??\n \"\")\n : \"keys…\",\n insertText: leaf ? name : `${name}.`,\n retrigger: !leaf,\n };\n });\n}\n\n/** Suggestions for the string being typed, filtered by what is already there. */\nexport function completionEntries(\n model: ProjectModel,\n context: CompletionContext,\n): CompletionEntry[] {\n if (context.kind === \"dictionary\") return dictionaryEntries(model, context);\n const dictionary = model.dictionaries.get(context.dictionary);\n return dictionary ? pathEntries(model, dictionary, context) : [];\n}\n","import {\n TODO_PREFIX,\n copyLeaf,\n hasPath,\n listLocales,\n parseDictionaryText,\n renderDictionaryFile,\n setString,\n} from \"@dialexjs/cli/api\";\n\nexport interface MissingKey {\n locale: string;\n path: string[];\n /** A locale that has the key, to copy its value from. */\n sourceLocale?: string;\n}\n\n/**\n * New text for a dictionary file with `missing` keys added to their locales, copied from the\n * source locale (strings get the `[TODO] ` marker, like `dialex check --fix`). Returns\n * `undefined` if nothing could be added.\n */\nexport function addMissingKeys(\n file: string,\n text: string,\n missing: MissingKey[],\n): string | undefined {\n const df = parseDictionaryText(file, text);\n if (!df) return undefined;\n\n let changed = false;\n for (const { locale, path, sourceLocale } of missing) {\n if (!sourceLocale) continue;\n if (copyLeaf(df, sourceLocale, locale, path, (value) => `${TODO_PREFIX}${value}`))\n changed = true;\n }\n return changed ? renderDictionaryFile(df) : undefined;\n}\n\n/**\n * New text for a dictionary file with a key that code refers to but the dictionary lacks, added\n * to every locale as a `[TODO]` placeholder named after the key.\n */\nexport function addUnknownKey(file: string, text: string, keyPath: string[]): string | undefined {\n const df = parseDictionaryText(file, text);\n if (!df) return undefined;\n\n const placeholder = `${TODO_PREFIX}${keyPath[keyPath.length - 1]}`;\n let changed = false;\n for (const locale of listLocales(df)) {\n if (\n !hasPath(df, locale, keyPath) &&\n setString(df, locale, keyPath, placeholder) !== \"skipped\"\n ) {\n changed = true;\n }\n }\n return changed ? renderDictionaryFile(df) : undefined;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { scanFiles } from \"@dialexjs/cli/api\";\n\nexport interface ProjectRoot {\n root: string;\n /** Globs (relative to `root`) for nested projects that belong to someone else. */\n ignore: string[];\n}\n\n// On top of the CLI's built-in list and the project's `.gitignore` files.\nconst IGNORE = [\"**/.git/**\", \"**/.nuxt/**\"];\n\n/** The nearest folder at or above `dir` (but inside `folder`) that has a package.json. */\nfunction packageRoot(folder: string, dir: string): string {\n for (let current = dir; current.startsWith(folder); current = path.dirname(current)) {\n if (fs.existsSync(path.join(current, \"package.json\"))) return current;\n if (current === folder) break;\n }\n return folder;\n}\n\n/**\n * Finds the Dialex projects inside a workspace folder.\n *\n * - Every directory with a `dialex.config.*` or `i18n.config.*` is a project.\n * - A `*.content.ts` file that no such project contains belongs to the nearest folder with a\n * `package.json` (or the workspace folder), which then uses the default settings. This covers\n * monorepos where only some packages have a config file.\n * - A project excludes the projects nested inside it, so one dictionary is never reported twice.\n */\nexport function discoverProjects(folder: string): ProjectRoot[] {\n const configs = scanFiles(folder, [\"**/dialex.config.*\", \"**/i18n.config.*\"], {\n ignore: IGNORE,\n });\n const configured = [...new Set(configs.map((f) => path.dirname(f)))];\n const roots = new Set(configured);\n\n // Judge coverage against the configured projects only; a fallback root must not hide others.\n const dictionaries = scanFiles(folder, \"**/*.content.ts\", { ignore: IGNORE });\n for (const file of dictionaries) {\n if (!configured.some((root) => file.startsWith(root + path.sep))) {\n roots.add(packageRoot(folder, path.dirname(file)));\n }\n }\n\n const all = [...roots].sort((a, b) => a.length - b.length);\n return all.map((root) => ({\n root,\n ignore: all\n .filter((other) => other !== root && other.startsWith(root + path.sep))\n .map((other) => `${path.relative(root, other).split(path.sep).join(\"/\")}/**`),\n }));\n}\n"],"mappings":"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"}
|