@intlayer/lsp 8.11.3 → 9.0.0-canary.12

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.
Files changed (46) hide show
  1. package/dist/esm/_virtual/_rolldown/runtime.mjs +36 -0
  2. package/dist/esm/callers/index.mjs +12 -0
  3. package/dist/esm/callers/index.mjs.map +1 -0
  4. package/dist/esm/callers/patterns.mjs +37 -0
  5. package/dist/esm/callers/patterns.mjs.map +1 -0
  6. package/dist/esm/dictionaryUtils.mjs +84 -0
  7. package/dist/esm/dictionaryUtils.mjs.map +1 -0
  8. package/dist/esm/findFieldInFile.mjs +53 -0
  9. package/dist/esm/findFieldInFile.mjs.map +1 -0
  10. package/dist/esm/findKeyAtOffset.mjs +20 -10
  11. package/dist/esm/findKeyAtOffset.mjs.map +1 -1
  12. package/dist/esm/findKeyInContentFile.mjs +350 -0
  13. package/dist/esm/findKeyInContentFile.mjs.map +1 -0
  14. package/dist/esm/findTCallAtOffset.mjs +33 -0
  15. package/dist/esm/findTCallAtOffset.mjs.map +1 -0
  16. package/dist/esm/findUsageFieldAtOffset.mjs +33 -0
  17. package/dist/esm/findUsageFieldAtOffset.mjs.map +1 -0
  18. package/dist/esm/index.mjs +533 -38
  19. package/dist/esm/index.mjs.map +1 -1
  20. package/dist/esm/oxcUtils.mjs +130 -0
  21. package/dist/esm/oxcUtils.mjs.map +1 -0
  22. package/dist/esm/usageAnalyzer.mjs +692 -0
  23. package/dist/esm/usageAnalyzer.mjs.map +1 -0
  24. package/dist/esm/utils.mjs +17 -0
  25. package/dist/types/callers/index.d.ts +8 -0
  26. package/dist/types/callers/index.d.ts.map +1 -0
  27. package/dist/types/callers/patterns.d.ts +30 -0
  28. package/dist/types/callers/patterns.d.ts.map +1 -0
  29. package/dist/types/dictionaryUtils.d.ts +12 -0
  30. package/dist/types/dictionaryUtils.d.ts.map +1 -0
  31. package/dist/types/findFieldInFile.d.ts +28 -0
  32. package/dist/types/findFieldInFile.d.ts.map +1 -0
  33. package/dist/types/findKeyAtOffset.d.ts +14 -3
  34. package/dist/types/findKeyAtOffset.d.ts.map +1 -1
  35. package/dist/types/findKeyInContentFile.d.ts +33 -0
  36. package/dist/types/findKeyInContentFile.d.ts.map +1 -0
  37. package/dist/types/findTCallAtOffset.d.ts +24 -0
  38. package/dist/types/findTCallAtOffset.d.ts.map +1 -0
  39. package/dist/types/findUsageFieldAtOffset.d.ts +23 -0
  40. package/dist/types/findUsageFieldAtOffset.d.ts.map +1 -0
  41. package/dist/types/oxcUtils.d.ts +53 -0
  42. package/dist/types/oxcUtils.d.ts.map +1 -0
  43. package/dist/types/usageAnalyzer.d.ts +129 -0
  44. package/dist/types/usageAnalyzer.d.ts.map +1 -0
  45. package/dist/types/utils.d.ts +19 -0
  46. package/package.json +18 -24
@@ -0,0 +1,36 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) {
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ }
14
+ if (!no_symbols) {
15
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ }
17
+ return target;
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
22
+ key = keys[i];
23
+ if (!__hasOwnProp.call(to, key) && key !== except) {
24
+ __defProp(to, key, {
25
+ get: ((k) => from[k]).bind(null, key),
26
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
27
+ });
28
+ }
29
+ }
30
+ }
31
+ return to;
32
+ };
33
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
34
+
35
+ //#endregion
36
+ export { __exportAll, __reExport };
@@ -0,0 +1,12 @@
1
+ import { __exportAll, __reExport } from "../_virtual/_rolldown/runtime.mjs";
2
+
3
+ export * from "@intlayer/config/callers"
4
+
5
+ //#region src/callers/index.ts
6
+ var callers_exports = /* @__PURE__ */ __exportAll({});
7
+ import * as import__intlayer_config_callers from "@intlayer/config/callers";
8
+ __reExport(callers_exports, import__intlayer_config_callers);
9
+
10
+ //#endregion
11
+ export { callers_exports };
12
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/callers/index.ts"],"sourcesContent":["/**\n * Caller registry — re-exported from the shared source of truth in\n * `@intlayer/config/callers`.\n *\n * The descriptors are shared with `@intlayer/babel` (usage analysis + optimize\n * rewrite), `@intlayer/swc` (via `toSwcExtraCallers`) and every compat\n * package's bundler plugin, so a compat library declared once in\n * `@intlayer/config/src/callers/compat/<lib>.ts` is supported everywhere.\n *\n * Editor-specific matching helpers built on top of the registry live in\n * `./patterns.ts`.\n */\nexport * from '@intlayer/config/callers';\n"],"mappings":""}
@@ -0,0 +1,37 @@
1
+ import { callers_exports } from "./index.mjs";
2
+ import { escapeRegularExpression } from "../findFieldInFile.mjs";
3
+
4
+ //#region src/callers/patterns.ts
5
+ /**
6
+ * Alternation of every caller name whose dictionary key appears as a
7
+ * positional string argument — for building text-search patterns.
8
+ */
9
+ const getCallerNamesAlternation = () => (0, callers_exports.getPositionalKeyCallerNames)().join("|");
10
+ /**
11
+ * Regexes matching call sites that reference the dictionary `key` in project
12
+ * source files. Used by "find all references" (content file → call sites).
13
+ *
14
+ * Two shapes are covered:
15
+ * 1. Positional key argument, with optional TypeScript generic:
16
+ * `useIntlayer("key")`, `useTranslations<T>("key")`, `getFixedT("en", "key")`
17
+ * 2. Options/descriptor object property:
18
+ * `getTranslations({ namespace: 'key' })`, `useI18n({ namespace: 'key' })`,
19
+ * `formatMessage({ id: 'key.field' })`
20
+ *
21
+ * @param key - The dictionary key to search for.
22
+ */
23
+ const buildKeyUsagePatterns = (key) => {
24
+ const escapedKey = escapeRegularExpression(key);
25
+ return [new RegExp(`\\b(?:${getCallerNamesAlternation()})\\b\\s*(?:<[^<>()]*>)?\\s*\\(\\s*(['"\`])${escapedKey}\\1`, "g"), new RegExp(`\\{\\s*(?:id|namespace)\\s*:\\s*(['"\`])${escapedKey}(?:\\.[^'"\`\\n]*)?\\1`, "g")];
26
+ };
27
+ /**
28
+ * Regex testing whether a file contains a caller invoked with the given
29
+ * dictionary key — a cheap pre-filter before running field-level analysis.
30
+ *
31
+ * @param key - The dictionary key.
32
+ */
33
+ const buildCallerWithKeyPattern = (key) => new RegExp(`\\b(?:${getCallerNamesAlternation()})\\b(?:<[^<>()]*>)?\\s*\\(\\s*['"\`]${escapeRegularExpression(key)}['"\`]`);
34
+
35
+ //#endregion
36
+ export { buildCallerWithKeyPattern, buildKeyUsagePatterns, getCallerNamesAlternation };
37
+ //# sourceMappingURL=patterns.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.mjs","names":[],"sources":["../../../src/callers/patterns.ts"],"sourcesContent":["import { escapeRegularExpression } from '../findFieldInFile';\nimport { getPositionalKeyCallerNames } from './index';\n\n/**\n * Alternation of every caller name whose dictionary key appears as a\n * positional string argument — for building text-search patterns.\n */\nexport const getCallerNamesAlternation = (): string =>\n getPositionalKeyCallerNames().join('|');\n\n/**\n * Regexes matching call sites that reference the dictionary `key` in project\n * source files. Used by \"find all references\" (content file → call sites).\n *\n * Two shapes are covered:\n * 1. Positional key argument, with optional TypeScript generic:\n * `useIntlayer(\"key\")`, `useTranslations<T>(\"key\")`, `getFixedT(\"en\", \"key\")`\n * 2. Options/descriptor object property:\n * `getTranslations({ namespace: 'key' })`, `useI18n({ namespace: 'key' })`,\n * `formatMessage({ id: 'key.field' })`\n *\n * @param key - The dictionary key to search for.\n */\nexport const buildKeyUsagePatterns = (key: string): RegExp[] => {\n const escapedKey = escapeRegularExpression(key);\n\n return [\n new RegExp(\n `\\\\b(?:${getCallerNamesAlternation()})\\\\b\\\\s*(?:<[^<>()]*>)?\\\\s*\\\\(\\\\s*(['\"\\`])${escapedKey}\\\\1`,\n 'g'\n ),\n new RegExp(\n `\\\\{\\\\s*(?:id|namespace)\\\\s*:\\\\s*(['\"\\`])${escapedKey}(?:\\\\.[^'\"\\`\\\\n]*)?\\\\1`,\n 'g'\n ),\n ];\n};\n\n/**\n * Regex testing whether a file contains a caller invoked with the given\n * dictionary key — a cheap pre-filter before running field-level analysis.\n *\n * @param key - The dictionary key.\n */\nexport const buildCallerWithKeyPattern = (key: string): RegExp =>\n new RegExp(\n `\\\\b(?:${getCallerNamesAlternation()})\\\\b(?:<[^<>()]*>)?\\\\s*\\\\(\\\\s*['\"\\`]${escapeRegularExpression(key)}['\"\\`]`\n );\n"],"mappings":";;;;;;;;AAOA,MAAa,oFACkB,CAAC,KAAK,IAAI;;;;;;;;;;;;;;AAezC,MAAa,yBAAyB,QAA0B;CAC9D,MAAM,aAAa,wBAAwB,IAAI;AAE/C,QAAO,CACL,IAAI,OACF,SAAS,2BAA2B,CAAC,4CAA4C,WAAW,MAC5F,IACD,EACD,IAAI,OACF,2CAA2C,WAAW,yBACtD,IACD,CACF;;;;;;;;AASH,MAAa,6BAA6B,QACxC,IAAI,OACF,SAAS,2BAA2B,CAAC,sCAAsC,wBAAwB,IAAI,CAAC,QACzG"}
@@ -0,0 +1,84 @@
1
+ //#region src/dictionaryUtils.ts
2
+ const LOCALE_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;
3
+ const META_KEYS = new Set([
4
+ "nodeType",
5
+ "translation",
6
+ "enumeration",
7
+ "condition",
8
+ "plural",
9
+ "insertion",
10
+ "nested",
11
+ "markdown",
12
+ "html",
13
+ "file",
14
+ "composite",
15
+ "dictionaryKey"
16
+ ]);
17
+ const isTypedNode = (val) => typeof val === "object" && val !== null && typeof val["nodeType"] === "string";
18
+ const getTypedNodeSummary = (node) => {
19
+ const nodeType = node["nodeType"];
20
+ const inner = node[nodeType];
21
+ if (inner && typeof inner === "object") {
22
+ const map = inner;
23
+ const localeEntries = Object.entries(map).filter(([key]) => LOCALE_PATTERN.test(key));
24
+ const entries = localeEntries.length > 0 ? localeEntries : Object.entries(map);
25
+ if (entries.length > 0) return entries.slice(0, 5).map(([k, v]) => `**${k}**: ${typeof v === "string" ? v : JSON.stringify(v)}`).join("\n\n");
26
+ }
27
+ return `*(${nodeType})*`;
28
+ };
29
+ const formatFieldValue = (value) => {
30
+ if (value === null || value === void 0) return `\`${String(value)}\``;
31
+ if (typeof value === "string") return `"${value}"`;
32
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
33
+ if (typeof value === "object") {
34
+ const obj = value;
35
+ if (isTypedNode(obj)) return getTypedNodeSummary(obj);
36
+ const fields = Object.keys(obj).filter((key) => !META_KEYS.has(key) && !LOCALE_PATTERN.test(key));
37
+ return fields.length > 0 ? `{ ${fields.slice(0, 8).join(", ")} }` : "{}";
38
+ }
39
+ return JSON.stringify(value);
40
+ };
41
+ const getFieldByPath = (content, path) => {
42
+ let current = content;
43
+ for (const key of path) {
44
+ if (current === null || typeof current !== "object") return void 0;
45
+ current = current[key];
46
+ }
47
+ return current;
48
+ };
49
+ const isNavigableField = (key) => !META_KEYS.has(key) && !LOCALE_PATTERN.test(key);
50
+ const getTopLevelFields = (content) => {
51
+ if (!content || typeof content !== "object") return [];
52
+ return Object.keys(content).filter(isNavigableField);
53
+ };
54
+ const getFieldsAtPath = (content, path) => {
55
+ const value = path.length === 0 ? content : getFieldByPath(content, path);
56
+ if (!value || typeof value !== "object") return [];
57
+ if (isTypedNode(value)) return [];
58
+ return Object.keys(value).filter(isNavigableField);
59
+ };
60
+ const formatDictionaryHover = (dictionaries, key) => {
61
+ const primary = dictionaries[0];
62
+ const lines = [`**Intlayer dictionary** \`${key}\``];
63
+ if (primary.title) lines.push(`*${primary.title}*`);
64
+ if (primary.description) lines.push(primary.description);
65
+ const fields = getTopLevelFields(primary.content);
66
+ if (fields.length > 0) {
67
+ lines.push("");
68
+ lines.push(`**Fields:** ${fields.map((field) => `\`${field}\``).join(", ")}`);
69
+ }
70
+ if (dictionaries.length > 1) lines.push(`*(${dictionaries.length} source files)*`);
71
+ return lines.join("\n\n");
72
+ };
73
+ const formatFieldHover = (dictionaries, dictionaryKey, fieldPath) => {
74
+ for (const dict of dictionaries) {
75
+ const value = getFieldByPath(dict.content, fieldPath);
76
+ if (value === void 0) continue;
77
+ return [`**\`${dictionaryKey}.${fieldPath.join(".")}\`**`, formatFieldValue(value)].join("\n\n");
78
+ }
79
+ return null;
80
+ };
81
+
82
+ //#endregion
83
+ export { formatDictionaryHover, formatFieldHover, formatFieldValue, getFieldByPath, getFieldsAtPath, getTopLevelFields };
84
+ //# sourceMappingURL=dictionaryUtils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionaryUtils.mjs","names":[],"sources":["../../src/dictionaryUtils.ts"],"sourcesContent":["import type { Dictionary } from '@intlayer/types/dictionary';\n\ntype ContentObj = Record<string, unknown>;\n\nconst LOCALE_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;\nconst META_KEYS: ReadonlySet<string> = new Set([\n 'nodeType',\n 'translation',\n 'enumeration',\n 'condition',\n 'plural',\n 'insertion',\n 'nested',\n 'markdown',\n 'html',\n 'file',\n 'composite',\n 'dictionaryKey',\n]);\n\nconst isTypedNode = (val: unknown): val is ContentObj =>\n typeof val === 'object' &&\n val !== null &&\n typeof (val as ContentObj)['nodeType'] === 'string';\n\nconst getTypedNodeSummary = (node: ContentObj): string => {\n const nodeType = node['nodeType'] as string;\n const inner = node[nodeType];\n\n if (inner && typeof inner === 'object') {\n const map = inner as Record<string, unknown>;\n const localeEntries = Object.entries(map).filter(([key]) =>\n LOCALE_PATTERN.test(key)\n );\n\n const entries =\n localeEntries.length > 0 ? localeEntries : Object.entries(map);\n\n if (entries.length > 0) {\n return entries\n .slice(0, 5)\n .map(\n ([k, v]) =>\n `**${k}**: ${typeof v === 'string' ? v : JSON.stringify(v)}`\n )\n .join('\\n\\n');\n }\n }\n\n return `*(${nodeType})*`;\n};\n\nexport const formatFieldValue = (value: unknown): string => {\n if (value === null || value === undefined) return `\\`${String(value)}\\``;\n\n if (typeof value === 'string') return `\"${value}\"`;\n\n if (typeof value === 'number' || typeof value === 'boolean')\n return String(value);\n\n if (typeof value === 'object') {\n const obj = value as ContentObj;\n\n if (isTypedNode(obj)) return getTypedNodeSummary(obj);\n const fields = Object.keys(obj).filter(\n (key) => !META_KEYS.has(key) && !LOCALE_PATTERN.test(key)\n );\n return fields.length > 0 ? `{ ${fields.slice(0, 8).join(', ')} }` : '{}';\n }\n return JSON.stringify(value);\n};\n\nexport const getFieldByPath = (content: unknown, path: string[]): unknown => {\n let current = content;\n for (const key of path) {\n if (current === null || typeof current !== 'object') return undefined;\n current = (current as ContentObj)[key];\n }\n return current;\n};\n\nconst isNavigableField = (key: string): boolean =>\n !META_KEYS.has(key) && !LOCALE_PATTERN.test(key);\n\nexport const getTopLevelFields = (content: unknown): string[] => {\n if (!content || typeof content !== 'object') return [];\n return Object.keys(content as ContentObj).filter(isNavigableField);\n};\n\nexport const getFieldsAtPath = (content: unknown, path: string[]): string[] => {\n const value = path.length === 0 ? content : getFieldByPath(content, path);\n\n if (!value || typeof value !== 'object') return [];\n\n if (isTypedNode(value as ContentObj)) return [];\n return Object.keys(value as ContentObj).filter(isNavigableField);\n};\n\nexport const formatDictionaryHover = (\n dictionaries: Dictionary[],\n key: string\n): string => {\n const primary = dictionaries[0]!;\n const lines: string[] = [`**Intlayer dictionary** \\`${key}\\``];\n\n if (primary.title) lines.push(`*${primary.title}*`);\n\n if (primary.description) lines.push(primary.description);\n\n const fields = getTopLevelFields(primary.content);\n\n if (fields.length > 0) {\n lines.push('');\n lines.push(\n `**Fields:** ${fields.map((field) => `\\`${field}\\``).join(', ')}`\n );\n }\n\n if (dictionaries.length > 1) {\n lines.push(`*(${dictionaries.length} source files)*`);\n }\n\n return lines.join('\\n\\n');\n};\n\nexport const formatFieldHover = (\n dictionaries: Dictionary[],\n dictionaryKey: string,\n fieldPath: string[]\n): string | null => {\n for (const dict of dictionaries) {\n const value = getFieldByPath(dict.content, fieldPath);\n\n if (value === undefined) continue;\n return [\n `**\\`${dictionaryKey}.${fieldPath.join('.')}\\`**`,\n formatFieldValue(value),\n ].join('\\n\\n');\n }\n return null;\n};\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { collectMessageUsages } from "./usageAnalyzer.mjs";
2
+ import { Position, Range } from "vscode-languageserver/node";
3
+
4
+ //#region src/findFieldInFile.ts
5
+ const escapeRegularExpression = (content) => content.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6
+ const offsetToRange = (text, start, end) => {
7
+ const beforeStart = text.slice(0, start);
8
+ const startLine = (beforeStart.match(/\n/g) ?? []).length;
9
+ const startCharacter = start - (beforeStart.lastIndexOf("\n") + 1);
10
+ const beforeEnd = text.slice(0, end);
11
+ const endLine = (beforeEnd.match(/\n/g) ?? []).length;
12
+ const endCharacter = end - (beforeEnd.lastIndexOf("\n") + 1);
13
+ return Range.create(Position.create(startLine, startCharacter), Position.create(endLine, endCharacter));
14
+ };
15
+ /**
16
+ * Within a single file's text, find all positions where `fieldPath` (or legacy
17
+ * `fieldName`) is consumed from the dictionary `escapedKey` — through any
18
+ * registered caller.
19
+ *
20
+ * Covers base-intlayer patterns:
21
+ * 1. Direct destructure: const { field } = useIntlayer("key")
22
+ * 2. Nested destructure: const { parent: { child } } = useIntlayer("key")
23
+ * 3. Indirect member access: const { parent } = useIntlayer("key"); parent.child
24
+ * 4. Full chain access: const content = useIntlayer("key"); content.parent.child
25
+ *
26
+ * …and every compat form resolved by `collectMessageUsages`: translation
27
+ * calls (`t('parent.child')`), self calls (`formatMessage({ id })`), JSX
28
+ * message components and lingui tagged templates.
29
+ *
30
+ * `fieldPath` (optional 5th parameter) is the preferred input; when omitted
31
+ * the function derives the single-element path from `escapedField`, preserving
32
+ * backward compatibility.
33
+ */
34
+ const findFieldRangesInFile = (text, escapedKey, _fieldName, escapedField, fieldPath) => {
35
+ const dictionaryKey = escapedKey.replace(/\\([.*+?^${}()|[\]\\])/g, "$1");
36
+ const unescapedLeaf = escapedField.replace(/\\([.*+?^${}()|[\]\\])/g, "$1");
37
+ const path = fieldPath && fieldPath.length > 0 ? fieldPath : [unescapedLeaf];
38
+ const ranges = [];
39
+ for (const usage of collectMessageUsages(text)) {
40
+ if (usage.dictionaryKey !== dictionaryKey) continue;
41
+ if (usage.isBareReference) continue;
42
+ if (usage.fieldPath.length !== path.length) continue;
43
+ if (!usage.fieldPath.every((segment, index) => segment === path[index])) continue;
44
+ const lastFieldSpan = usage.fieldSpans?.[usage.fieldSpans.length - 1];
45
+ if (usage.kind === "member" && lastFieldSpan) ranges.push(offsetToRange(text, lastFieldSpan.start, lastFieldSpan.end));
46
+ else ranges.push(offsetToRange(text, usage.start, usage.end));
47
+ }
48
+ return ranges;
49
+ };
50
+
51
+ //#endregion
52
+ export { escapeRegularExpression, findFieldRangesInFile, offsetToRange };
53
+ //# sourceMappingURL=findFieldInFile.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findFieldInFile.mjs","names":[],"sources":["../../src/findFieldInFile.ts"],"sourcesContent":["import { Position, Range } from 'vscode-languageserver/node';\nimport { collectMessageUsages } from './usageAnalyzer';\n\nexport const escapeRegularExpression = (content: string): string =>\n content.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const offsetToRange = (\n text: string,\n start: number,\n end: number\n): Range => {\n const beforeStart = text.slice(0, start);\n const startLine = (beforeStart.match(/\\n/g) ?? []).length;\n const startCharacter = start - (beforeStart.lastIndexOf('\\n') + 1);\n const beforeEnd = text.slice(0, end);\n const endLine = (beforeEnd.match(/\\n/g) ?? []).length;\n const endCharacter = end - (beforeEnd.lastIndexOf('\\n') + 1);\n\n return Range.create(\n Position.create(startLine, startCharacter),\n Position.create(endLine, endCharacter)\n );\n};\n\n/**\n * Within a single file's text, find all positions where `fieldPath` (or legacy\n * `fieldName`) is consumed from the dictionary `escapedKey` — through any\n * registered caller.\n *\n * Covers base-intlayer patterns:\n * 1. Direct destructure: const { field } = useIntlayer(\"key\")\n * 2. Nested destructure: const { parent: { child } } = useIntlayer(\"key\")\n * 3. Indirect member access: const { parent } = useIntlayer(\"key\"); parent.child\n * 4. Full chain access: const content = useIntlayer(\"key\"); content.parent.child\n *\n * …and every compat form resolved by `collectMessageUsages`: translation\n * calls (`t('parent.child')`), self calls (`formatMessage({ id })`), JSX\n * message components and lingui tagged templates.\n *\n * `fieldPath` (optional 5th parameter) is the preferred input; when omitted\n * the function derives the single-element path from `escapedField`, preserving\n * backward compatibility.\n */\nexport const findFieldRangesInFile = (\n text: string,\n escapedKey: string,\n _fieldName: string,\n escapedField: string,\n fieldPath?: string[]\n): Range[] => {\n const dictionaryKey = escapedKey.replace(/\\\\([.*+?^${}()|[\\]\\\\])/g, '$1');\n const unescapedLeaf = escapedField.replace(/\\\\([.*+?^${}()|[\\]\\\\])/g, '$1');\n\n // Effective path: use the provided fieldPath when available, else single-element\n const path = fieldPath && fieldPath.length > 0 ? fieldPath : [unescapedLeaf];\n\n const ranges: Range[] = [];\n\n for (const usage of collectMessageUsages(text)) {\n if (usage.dictionaryKey !== dictionaryKey) continue;\n\n // Bare variable references don't spell the field name — one hit per\n // declaration site, not per flow of the variable.\n if (usage.isBareReference) continue;\n\n if (usage.fieldPath.length !== path.length) continue;\n\n if (!usage.fieldPath.every((segment, index) => segment === path[index])) {\n continue;\n }\n\n // Member chains highlight the leaf property; other usages highlight the\n // whole expression (call, JSX element, tagged template, destructure key).\n const lastFieldSpan = usage.fieldSpans?.[usage.fieldSpans.length - 1];\n\n if (usage.kind === 'member' && lastFieldSpan) {\n ranges.push(offsetToRange(text, lastFieldSpan.start, lastFieldSpan.end));\n } else {\n ranges.push(offsetToRange(text, usage.start, usage.end));\n }\n }\n\n return ranges;\n};\n"],"mappings":";;;;AAGA,MAAa,2BAA2B,YACtC,QAAQ,QAAQ,uBAAuB,OAAO;AAEhD,MAAa,iBACX,MACA,OACA,QACU;CACV,MAAM,cAAc,KAAK,MAAM,GAAG,MAAM;CACxC,MAAM,aAAa,YAAY,MAAM,MAAM,IAAI,EAAE,EAAE;CACnD,MAAM,iBAAiB,SAAS,YAAY,YAAY,KAAK,GAAG;CAChE,MAAM,YAAY,KAAK,MAAM,GAAG,IAAI;CACpC,MAAM,WAAW,UAAU,MAAM,MAAM,IAAI,EAAE,EAAE;CAC/C,MAAM,eAAe,OAAO,UAAU,YAAY,KAAK,GAAG;AAE1D,QAAO,MAAM,OACX,SAAS,OAAO,WAAW,eAAe,EAC1C,SAAS,OAAO,SAAS,aAAa,CACvC;;;;;;;;;;;;;;;;;;;;;AAsBH,MAAa,yBACX,MACA,YACA,YACA,cACA,cACY;CACZ,MAAM,gBAAgB,WAAW,QAAQ,2BAA2B,KAAK;CACzE,MAAM,gBAAgB,aAAa,QAAQ,2BAA2B,KAAK;CAG3E,MAAM,OAAO,aAAa,UAAU,SAAS,IAAI,YAAY,CAAC,cAAc;CAE5E,MAAM,SAAkB,EAAE;AAE1B,MAAK,MAAM,SAAS,qBAAqB,KAAK,EAAE;AAC9C,MAAI,MAAM,kBAAkB,cAAe;AAI3C,MAAI,MAAM,gBAAiB;AAE3B,MAAI,MAAM,UAAU,WAAW,KAAK,OAAQ;AAE5C,MAAI,CAAC,MAAM,UAAU,OAAO,SAAS,UAAU,YAAY,KAAK,OAAO,CACrE;EAKF,MAAM,gBAAgB,MAAM,aAAa,MAAM,WAAW,SAAS;AAEnE,MAAI,MAAM,SAAS,YAAY,cAC7B,QAAO,KAAK,cAAc,MAAM,cAAc,OAAO,cAAc,IAAI,CAAC;MAExE,QAAO,KAAK,cAAc,MAAM,MAAM,OAAO,MAAM,IAAI,CAAC;;AAI5D,QAAO"}
@@ -1,17 +1,27 @@
1
+ import { collectNamespaceReferences } from "./usageAnalyzer.mjs";
2
+
1
3
  //#region src/findKeyAtOffset.ts
2
- const INTLAYER_GETTERS = ["useIntlayer", "getIntlayer"];
3
- const buildGetterRegex = () => new RegExp(`\\b(${INTLAYER_GETTERS.join("|")})\\b\\s*(?:<[^<>()]*>)?\\s*\\(\\s*(['"\`])([^'"\`]+)\\2`, "g");
4
4
  /**
5
- * Scan `text` for an Intlayer getter call whose match span contains `offset`.
6
- * Returns the dictionary key string, or `null` if the cursor is not inside any
7
- * recognised call.
5
+ * Scan `text` for a caller whose namespace (dictionary key) is addressed at
6
+ * `offset`. Returns the namespace string when the cursor is on the caller
7
+ * name or on the namespace argument itself, `null` otherwise.
8
+ *
9
+ * Handles every registered caller with a positional or options-object
10
+ * namespace:
11
+ * useIntlayer("my-key") ← cursor on "my-key" or the callee
12
+ * useTranslations("namespace") ← idem
13
+ * getFixedT("en", "namespace") ← cursor on "namespace", not "en"
14
+ * useI18n({ namespace: "ns" }) ← cursor on "ns" or the callee
15
+ * getTranslations({ namespace: "ns" }) ← idem
16
+ *
17
+ * Self callers (`formatMessage`, lingui `t`/`_`) are message *usages*, not
18
+ * namespace declarations — they are resolved by `findMessageUsageAtOffset`.
8
19
  */
9
20
  const findKeyAtOffset = (text, offset) => {
10
- const regex = buildGetterRegex();
11
- for (const match of text.matchAll(regex)) {
12
- const start = match.index;
13
- const end = start + match[0].length;
14
- if (offset >= start && offset <= end) return match[3];
21
+ for (const reference of collectNamespaceReferences(text)) {
22
+ const isOnCalleeName = offset >= reference.calleeStart && offset <= reference.calleeEnd;
23
+ const isOnNamespaceArgument = offset >= reference.namespaceStart && offset <= reference.namespaceEnd;
24
+ if (isOnCalleeName || isOnNamespaceArgument) return reference.dictionaryKey;
15
25
  }
16
26
  return null;
17
27
  };
@@ -1 +1 @@
1
- {"version":3,"file":"findKeyAtOffset.mjs","names":[],"sources":["../../src/findKeyAtOffset.ts"],"sourcesContent":["const INTLAYER_GETTERS = ['useIntlayer', 'getIntlayer'] as const;\n\n// \\s* deliberately uses \\s (which includes \\n) so multi-line calls match:\n// useIntlayer(\n// \"my-key\"\n// )\n// Capture group 3 is the dictionary key.\nconst buildGetterRegex = () =>\n new RegExp(\n `\\\\b(${INTLAYER_GETTERS.join('|')})\\\\b\\\\s*(?:<[^<>()]*>)?\\\\s*\\\\(\\\\s*(['\"\\`])([^'\"\\`]+)\\\\2`,\n 'g'\n );\n\n/**\n * Scan `text` for an Intlayer getter call whose match span contains `offset`.\n * Returns the dictionary key string, or `null` if the cursor is not inside any\n * recognised call.\n */\nexport const findKeyAtOffset = (\n text: string,\n offset: number\n): string | null => {\n const regex = buildGetterRegex();\n for (const match of text.matchAll(regex)) {\n const start = match.index;\n const end = start + match[0].length;\n if (offset >= start && offset <= end) {\n return match[3];\n }\n }\n return null;\n};\n"],"mappings":";AAAA,MAAM,mBAAmB,CAAC,eAAe,aAAa;AAOtD,MAAM,yBACJ,IAAI,OACF,OAAO,iBAAiB,KAAK,GAAG,EAAE,0DAClC,GACF;;;;;;AAOF,MAAa,mBACX,MACA,WACkB;CAClB,MAAM,QAAQ,iBAAiB;CAC/B,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GAAG;EACxC,MAAM,QAAQ,MAAM;EACpB,MAAM,MAAM,QAAQ,MAAM,GAAG;EAC7B,IAAI,UAAU,SAAS,UAAU,KAC/B,OAAO,MAAM;CAEjB;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"findKeyAtOffset.mjs","names":[],"sources":["../../src/findKeyAtOffset.ts"],"sourcesContent":["import { collectNamespaceReferences } from './usageAnalyzer';\n\n/**\n * Scan `text` for a caller whose namespace (dictionary key) is addressed at\n * `offset`. Returns the namespace string when the cursor is on the caller\n * name or on the namespace argument itself, `null` otherwise.\n *\n * Handles every registered caller with a positional or options-object\n * namespace:\n * useIntlayer(\"my-key\") ← cursor on \"my-key\" or the callee\n * useTranslations(\"namespace\") ← idem\n * getFixedT(\"en\", \"namespace\") ← cursor on \"namespace\", not \"en\"\n * useI18n({ namespace: \"ns\" }) ← cursor on \"ns\" or the callee\n * getTranslations({ namespace: \"ns\" }) ← idem\n *\n * Self callers (`formatMessage`, lingui `t`/`_`) are message *usages*, not\n * namespace declarations — they are resolved by `findMessageUsageAtOffset`.\n */\nexport const findKeyAtOffset = (\n text: string,\n offset: number\n): string | null => {\n for (const reference of collectNamespaceReferences(text)) {\n const isOnCalleeName =\n offset >= reference.calleeStart && offset <= reference.calleeEnd;\n const isOnNamespaceArgument =\n offset >= reference.namespaceStart && offset <= reference.namespaceEnd;\n\n if (isOnCalleeName || isOnNamespaceArgument) {\n return reference.dictionaryKey;\n }\n }\n\n return null;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,MAAa,mBACX,MACA,WACkB;AAClB,MAAK,MAAM,aAAa,2BAA2B,KAAK,EAAE;EACxD,MAAM,iBACJ,UAAU,UAAU,eAAe,UAAU,UAAU;EACzD,MAAM,wBACJ,UAAU,UAAU,kBAAkB,UAAU,UAAU;AAE5D,MAAI,kBAAkB,sBACpB,QAAO,UAAU;;AAIrB,QAAO"}