@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.
- package/dist/esm/_virtual/_rolldown/runtime.mjs +36 -0
- package/dist/esm/callers/index.mjs +12 -0
- package/dist/esm/callers/index.mjs.map +1 -0
- package/dist/esm/callers/patterns.mjs +37 -0
- package/dist/esm/callers/patterns.mjs.map +1 -0
- package/dist/esm/dictionaryUtils.mjs +84 -0
- package/dist/esm/dictionaryUtils.mjs.map +1 -0
- package/dist/esm/findFieldInFile.mjs +53 -0
- package/dist/esm/findFieldInFile.mjs.map +1 -0
- package/dist/esm/findKeyAtOffset.mjs +20 -10
- package/dist/esm/findKeyAtOffset.mjs.map +1 -1
- package/dist/esm/findKeyInContentFile.mjs +350 -0
- package/dist/esm/findKeyInContentFile.mjs.map +1 -0
- package/dist/esm/findTCallAtOffset.mjs +33 -0
- package/dist/esm/findTCallAtOffset.mjs.map +1 -0
- package/dist/esm/findUsageFieldAtOffset.mjs +33 -0
- package/dist/esm/findUsageFieldAtOffset.mjs.map +1 -0
- package/dist/esm/index.mjs +533 -38
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/oxcUtils.mjs +130 -0
- package/dist/esm/oxcUtils.mjs.map +1 -0
- package/dist/esm/usageAnalyzer.mjs +692 -0
- package/dist/esm/usageAnalyzer.mjs.map +1 -0
- package/dist/esm/utils.mjs +17 -0
- package/dist/types/callers/index.d.ts +8 -0
- package/dist/types/callers/index.d.ts.map +1 -0
- package/dist/types/callers/patterns.d.ts +30 -0
- package/dist/types/callers/patterns.d.ts.map +1 -0
- package/dist/types/dictionaryUtils.d.ts +12 -0
- package/dist/types/dictionaryUtils.d.ts.map +1 -0
- package/dist/types/findFieldInFile.d.ts +28 -0
- package/dist/types/findFieldInFile.d.ts.map +1 -0
- package/dist/types/findKeyAtOffset.d.ts +14 -3
- package/dist/types/findKeyAtOffset.d.ts.map +1 -1
- package/dist/types/findKeyInContentFile.d.ts +33 -0
- package/dist/types/findKeyInContentFile.d.ts.map +1 -0
- package/dist/types/findTCallAtOffset.d.ts +24 -0
- package/dist/types/findTCallAtOffset.d.ts.map +1 -0
- package/dist/types/findUsageFieldAtOffset.d.ts +23 -0
- package/dist/types/findUsageFieldAtOffset.d.ts.map +1 -0
- package/dist/types/oxcUtils.d.ts +53 -0
- package/dist/types/oxcUtils.d.ts.map +1 -0
- package/dist/types/usageAnalyzer.d.ts +129 -0
- package/dist/types/usageAnalyzer.d.ts.map +1 -0
- package/dist/types/utils.d.ts +19 -0
- 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
|
|
6
|
-
* Returns the
|
|
7
|
-
*
|
|
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
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
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":["
|
|
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"}
|