@intlayer/lsp 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/findKeyInContentFile.mjs +2 -0
- package/dist/esm/findKeyInContentFile.mjs.map +1 -1
- package/dist/esm/index.mjs +26 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/usageAnalyzer.mjs +80 -20
- package/dist/esm/usageAnalyzer.mjs.map +1 -1
- package/dist/types/findKeyInContentFile.d.ts.map +1 -1
- package/dist/types/usageAnalyzer.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -55,6 +55,7 @@ const findContentFieldAtOffset = (text, offset, fileExtension) => {
|
|
|
55
55
|
* `cond` – condition ({ true: "…", false: "…" })
|
|
56
56
|
* `plural` – plural rules ({ one: "…", other: "…" })
|
|
57
57
|
* `gender` – gender rules ({ male: "…", female: "…", other: "…" })
|
|
58
|
+
* `select` – select cases ({ Draft: "…", Published: "…", fallback: "…" })
|
|
58
59
|
* `insert` – insertion ("Hello {{name}}!")
|
|
59
60
|
* `nest` – nested dict ("navbar")
|
|
60
61
|
* `md` – markdown ("# …")
|
|
@@ -67,6 +68,7 @@ const INTLAYER_LEAF_CALL_NAMES = /* @__PURE__ */ new Set([
|
|
|
67
68
|
"cond",
|
|
68
69
|
"plural",
|
|
69
70
|
"gender",
|
|
71
|
+
"select",
|
|
70
72
|
"insert",
|
|
71
73
|
"nest",
|
|
72
74
|
"md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findKeyInContentFile.mjs","names":[],"sources":["../../src/findKeyInContentFile.ts"],"sourcesContent":["import { type OxcNode, parseText, walkAst } from './oxcUtils';\n\n// Group 2: quoted value — key: \"foo\" | key: 'foo' | key: `foo` (TS/JS/JSON)\n// Group 3: unquoted value — key: foo-bar (YAML / Markdown frontmatter)\nconst KEY_DECLARATION_REGULAR_EXPRESSION =\n /(?:^|[{,\\s])\"?key\"?\\s*:\\s*(?:(['\"`])([^'\"`\\r\\n]+)\\1|([^ \\t'\"`{},\\r\\n]+))/g;\n\n/**\n * If `offset` falls within a `key:` declaration in a content file,\n * return the declared key string; otherwise return null.\n * Handles:\n * - Quoted values: key: \"foo\" | key: 'foo' | key: `foo` (TS/JS)\n * - JSON values: \"key\": \"foo\" (JSON/JSON5/JSONC)\n * - Bare values: key: foo-bar (YAML / Markdown frontmatter)\n */\nexport const findKeyInContentFile = (\n text: string,\n offset: number\n): string | null => {\n for (const match of text.matchAll(KEY_DECLARATION_REGULAR_EXPRESSION)) {\n const start = match.index!;\n const end = start + match[0].length;\n\n if (offset >= start && offset <= end) {\n return match[2] ?? match[3] ?? null;\n }\n }\n\n return null;\n};\n\n/**\n * If `offset` falls on a content field property key inside an intlayer content\n * file, return the dictionary key, the leaf field name, and the full field path.\n *\n * The `fileExtension` parameter controls which parser is used:\n *\n * - `.json` / `.json5` / `.jsonc` → dedicated JSON parser\n * - `.yaml` / `.yml` → YAML regex heuristic\n * - `.md` / `.mdx` → always null (body is the content value)\n * - everything else → Oxc AST (TS/JS/TSX/JSX)\n *\n * When `fileExtension` is omitted the function falls back to Oxc, then to\n * JSON/YAML heuristics based on text shape.\n */\nexport const findContentFieldAtOffset = (\n text: string,\n offset: number,\n fileExtension?: string\n): FieldResult | null => {\n const ext = fileExtension?.toLowerCase() ?? '';\n\n // Markdown has no individual navigable content fields\n if (ext === '.md' || ext === '.mdx') return null;\n\n // JSON / JSON5 / JSONC\n if (ext === '.json' || ext === '.json5' || ext === '.jsonc') {\n return findContentFieldAtOffsetJson(text, offset);\n }\n\n // YAML / YML\n if (ext === '.yaml' || ext === '.yml') {\n return findContentFieldAtOffsetYaml(text, offset);\n }\n\n // TS / JS / TSX / JSX — use Oxc AST\n const program = parseText(text);\n if (program) {\n return findContentFieldAtOffsetOxc(text, offset, program);\n }\n\n // Oxc failed to parse — fall back on content-shape heuristics\n // JSON: starts with optional whitespace then `{`\n if (/^\\s*\\{/.test(text)) {\n return findContentFieldAtOffsetJson(text, offset);\n }\n\n // Markdown: YAML frontmatter fence with no `content:` block\n if (/^---\\s*\\r?\\n/.test(text)) {\n if (!/^content\\s*:/m.test(text)) return null;\n }\n\n // YAML (or anything else with a content: block)\n return findContentFieldAtOffsetYaml(text, offset);\n};\n\n// ---------------------------------------------------------------------------\n// Helpers shared by the Oxc and YAML/JSON fallback paths\n// ---------------------------------------------------------------------------\n\n/**\n * All Intlayer helper function names whose arguments are locale-map or\n * control-flow data — NOT navigable content field names. The walker must\n * treat these calls as opaque leaf values and never descend into them.\n *\n * `t` – translation ({ en: \"…\", fr: \"…\" })\n * `enu` – enumeration ({ \"0\": \"…\", \">5\": \"…\" })\n * `cond` – condition ({ true: \"…\", false: \"…\" })\n * `plural` – plural rules ({ one: \"…\", other: \"…\" })\n * `gender` – gender rules ({ male: \"…\", female: \"…\", other: \"…\" })\n * `insert` – insertion (\"Hello {{name}}!\")\n * `nest` – nested dict (\"navbar\")\n * `md` – markdown (\"# …\")\n * `file` – file reference (\"./path/to/file.txt\")\n * `html` – html content (\"<p>…</p>\")\n */\nconst INTLAYER_LEAF_CALL_NAMES: ReadonlySet<string> = new Set([\n 't',\n 'enu',\n 'cond',\n 'plural',\n 'gender',\n 'insert',\n 'nest',\n 'md',\n 'file',\n 'html',\n]);\n\n/** Returns true when `node` is a call to any intlayer leaf helper function. */\nconst isIntlayerLeafCall = (node: OxcNode): boolean => {\n if (node['type'] !== 'CallExpression') return false;\n const callee = node['callee'] as OxcNode | undefined;\n return (\n callee?.['type'] === 'Identifier' &&\n INTLAYER_LEAF_CALL_NAMES.has(callee['name'] as string)\n );\n};\n\n/** Extract a string value from a Literal / StringLiteral / TemplateLiteral node. */\nconst getStringValue = (node: OxcNode): string | null => {\n if (\n (node['type'] === 'Literal' || node['type'] === 'StringLiteral') &&\n typeof node['value'] === 'string'\n ) {\n return node['value'] as string;\n }\n if (node['type'] === 'TemplateLiteral') {\n const quasis = node['quasis'] as OxcNode[] | undefined;\n const valueNode = quasis?.[0]?.['value'] as OxcNode | undefined;\n return (\n (valueNode?.['cooked'] as string | undefined) ??\n (valueNode?.['raw'] as string | undefined) ??\n null\n );\n }\n return null;\n};\n\n// ---------------------------------------------------------------------------\n// JSON content-file support\n// ---------------------------------------------------------------------------\n\n/**\n * Well-known meta-keys that appear inside Intlayer typed nodes in JSON files.\n * e.g. { \"nodeType\": \"translation\", \"translation\": { \"en\": \"…\" } }\n * None of these are navigable content fields.\n */\nconst JSON_INTLAYER_NODE_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 // JSON schema / system properties\n '$schema',\n 'id',\n 'projectIds',\n 'localId',\n 'localIds',\n 'filePath',\n 'versions',\n 'filled',\n // Top-level dictionary meta-properties (not inside content)\n 'key',\n 'title',\n 'description',\n 'tags',\n 'format',\n 'locale',\n 'schema',\n 'location',\n 'fill',\n 'priority',\n 'version',\n 'importMode',\n]);\n\n/**\n * Find the byte offset of `fieldName` (as a JSON property key) inside `text`,\n * searching within the range [searchFrom, searchTo).\n *\n * Matches `\"fieldName\"` surrounded by typical JSON delimiters.\n */\nconst findJsonKeyOffset = (\n text: string,\n fieldName: string,\n searchFrom: number,\n searchTo: number\n): number => {\n const pattern = new RegExp(\n `\"${fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\"`,\n 'g'\n );\n pattern.lastIndex = searchFrom;\n let match: RegExpExecArray | null;\n while (true) {\n match = pattern.exec(text);\n if (match === null) break;\n if (match.index >= searchTo) break;\n return match.index;\n }\n return -1;\n};\n\n/**\n * Strip JSON5/JSONC single-line (//) and multi-line (/* *\\/\\/) comments,\n * and trailing commas, producing valid JSON that JSON.parse can handle.\n */\nconst stripJsonComments = (text: string): string =>\n text\n // Remove single-line comments (// …) EXCEPT in URLs (e.g., https://)\n .replace(/(?<!:)\\/\\/[^\\r\\n]*/g, '')\n // Remove block comments (/* … */)\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n // Remove trailing commas before } or ]\n .replace(/,(\\s*[}\\]])/g, '$1');\n\ntype FieldResult = {\n dictionaryKey: string;\n fieldName: string;\n fieldPath: string[];\n};\n\n/**\n * Parse a JSON/JSON5/JSONC content file and, if `offset` falls on a content\n * field key, return the dictionary key, leaf field name, and full field path.\n *\n * Returns null when:\n * - The text is not valid JSON (even after comment stripping)\n * - The cursor is not on a navigable content field key\n * - The field is a meta/system key (nodeType, translation, locale codes, …)\n */\nconst findContentFieldAtOffsetJson = (\n text: string,\n offset: number\n): FieldResult | null => {\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(stripJsonComments(text)) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const dictionaryKey =\n typeof parsed['key'] === 'string' ? parsed['key'] : null;\n if (!dictionaryKey) return null;\n\n const contentObj = parsed['content'];\n if (\n !contentObj ||\n typeof contentObj !== 'object' ||\n Array.isArray(contentObj)\n )\n return null;\n\n // Find the byte range of the \"content\" block in raw text so we can scope\n // offset comparisons to content fields only.\n const contentKeyIndex = text.indexOf('\"content\"');\n if (contentKeyIndex === -1) return null;\n\n /**\n * Recursively walk the parsed content object.\n * For each property key, check whether the raw text position of that key\n * (within the remaining text slice) overlaps with `offset`.\n *\n * `textFrom` is where we start searching for each key in the raw text —\n * we advance it as we consume properties to avoid false matches on\n * identically-named sibling keys.\n */\n const walk = (\n obj: Record<string, unknown>,\n path: string[],\n searchFrom: number,\n searchTo: number\n ): FieldResult | null => {\n for (const [fieldName, fieldValue] of Object.entries(obj)) {\n // Skip Intlayer-internal node keys — they are not navigable content fields\n if (JSON_INTLAYER_NODE_KEYS.has(fieldName)) continue;\n\n // Skip keys that look like locale codes (e.g. \"en\", \"fr\", \"en-GB\")\n if (/^[a-z]{2}(-[A-Z]{2})?$/.test(fieldName)) continue;\n\n const keyStart = findJsonKeyOffset(text, fieldName, searchFrom, searchTo);\n if (keyStart === -1) continue;\n const keyEnd = keyStart + fieldName.length + 2; // +2 for surrounding quotes\n\n if (offset >= keyStart && offset <= keyEnd) {\n return {\n dictionaryKey,\n fieldName,\n fieldPath: [...path, fieldName],\n };\n }\n\n // Recurse into plain nested objects (skip typed nodes — they have nodeType)\n if (\n fieldValue !== null &&\n typeof fieldValue === 'object' &&\n !Array.isArray(fieldValue)\n ) {\n const nested = fieldValue as Record<string, unknown>;\n // Skip Intlayer typed nodes — their children are not content fields\n if ('nodeType' in nested) continue;\n\n const result = walk(nested, [...path, fieldName], keyEnd, searchTo);\n if (result) return result;\n }\n }\n return null;\n };\n\n return walk(\n contentObj as Record<string, unknown>,\n [],\n contentKeyIndex,\n text.length\n );\n};\n\n// ---------------------------------------------------------------------------\n// YAML / Markdown frontmatter fallback\n// ---------------------------------------------------------------------------\n\n/**\n * Locale-code pattern — keys matching this inside YAML content blocks are\n * translation locale identifiers, not navigable content fields.\n */\nconst LOCALE_CODE_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;\n\n/**\n * YAML / Markdown meta-keys that appear at the top level or inside typed nodes.\n * These should not be treated as navigable content fields.\n */\nconst YAML_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 // Frontmatter / top-level keys\n 'key',\n 'title',\n 'description',\n 'tags',\n 'format',\n 'locale',\n 'schema',\n 'location',\n 'fill',\n 'priority',\n 'version',\n 'importMode',\n '$schema',\n]);\n\n/**\n * Improved regex fallback for YAML and Markdown content files.\n *\n * Strategy:\n * 1. Extract the dictionary key from the frontmatter / top-level `key:` line.\n * 2. Locate the `content:` section (YAML files only; Markdown has no `content:` block).\n * 3. Within the content section, find all `fieldName:` patterns at consistent\n * indentation levels, skipping known meta-keys and locale codes.\n * 4. Check whether `offset` falls within one of those key spans.\n * 5. Return a single-element `fieldPath` (deep path tracking is impractical\n * without a full YAML parser, but single-level is enough for most use cases).\n *\n * For Markdown files, `findContentFieldAtOffset` always returns null because\n * the body text is the content value — individual content \"fields\" are not\n * declared as separate properties.\n */\nconst findContentFieldAtOffsetYaml = (\n text: string,\n offset: number\n): FieldResult | null => {\n // Extract dictionary key\n const keyMatch =\n /^key\\s*:\\s*(?:(['\"`])([^'\"`\\r\\n]+)\\1|([^\\s'\"`{},\\r\\n]+))/m.exec(text);\n if (!keyMatch) return null;\n const dictionaryKey = keyMatch[2] ?? keyMatch[3];\n if (!dictionaryKey) return null;\n\n // Find the content: section start\n const contentSectionMatch = /^content\\s*:/m.exec(text);\n if (!contentSectionMatch) return null;\n const contentStart =\n contentSectionMatch.index + contentSectionMatch[0].length;\n\n // Find the end of the content section: next top-level key (no leading whitespace)\n // or end of file.\n const afterContent = text.slice(contentStart);\n const nextTopLevelMatch = /\\r?\\n[a-zA-Z_$][a-zA-Z0-9_$]*\\s*:/m.exec(\n afterContent\n );\n const contentEnd = nextTopLevelMatch\n ? contentStart + nextTopLevelMatch.index\n : text.length;\n\n // The cursor must be within the content block\n if (offset < contentStart || offset > contentEnd) return null;\n\n // Find all ` fieldName:` patterns within the content block.\n // We look for lines that have at least 2 spaces of indentation and a\n // word-character key followed by a colon.\n const contentText = text.slice(contentStart, contentEnd);\n const fieldPattern = /^([ \\t]+)([a-zA-Z_$][a-zA-Z0-9_$]*)[ \\t]*:/gm;\n\n let bestMatch: FieldResult | null = null;\n let fieldMatch: RegExpExecArray | null;\n\n while (true) {\n fieldMatch = fieldPattern.exec(contentText);\n if (fieldMatch === null) break;\n\n const fieldName = fieldMatch[2]!;\n\n // Skip meta-keys and locale codes\n if (YAML_META_KEYS.has(fieldName)) continue;\n if (LOCALE_CODE_PATTERN.test(fieldName)) continue;\n\n const fieldKeyStart =\n contentStart + fieldMatch.index + fieldMatch[1]!.length; // skip indentation\n const fieldKeyEnd = fieldKeyStart + fieldName.length;\n\n if (offset >= fieldKeyStart && offset <= fieldKeyEnd) {\n bestMatch = { dictionaryKey, fieldName, fieldPath: [fieldName] };\n break;\n }\n }\n\n return bestMatch;\n};\n\n// ---------------------------------------------------------------------------\n// Oxc (TS / JS) AST path\n// ---------------------------------------------------------------------------\n\nconst findContentFieldAtOffsetOxc = (\n _text: string,\n offset: number,\n program: OxcNode\n): FieldResult | null => {\n let foundResult: FieldResult | null = null;\n\n walkAst(program, (node) => {\n if (foundResult) return true;\n if (node['type'] !== 'ObjectExpression') return;\n\n const properties = node['properties'] as OxcNode[] | undefined;\n if (!properties?.length) return;\n\n // Look for sibling `key:` and `content:` properties in the same object\n let dictionaryKey: string | null = null;\n let contentNode: OxcNode | null = null;\n\n for (const prop of properties) {\n if (prop['type'] !== 'Property') continue;\n const propKey = prop['key'] as OxcNode | undefined;\n const propValue = prop['value'] as OxcNode | undefined;\n if (!propKey || !propValue) continue;\n\n const keyName =\n (propKey['name'] as string | undefined) ??\n (propKey['value'] as string | undefined);\n\n if (keyName === 'key') dictionaryKey = getStringValue(propValue);\n if (keyName === 'content') contentNode = propValue;\n }\n\n if (!dictionaryKey || !contentNode) return;\n\n // Walk the `content:` object tree, tracking the path to each property key\n const walkContent = (obj: OxcNode, path: string[]): boolean => {\n if (obj['type'] !== 'ObjectExpression') return false;\n const props = obj['properties'] as OxcNode[] | undefined;\n if (!props) return false;\n\n for (const prop of props) {\n if (prop['type'] !== 'Property') continue;\n const propKey = prop['key'] as OxcNode | undefined;\n const propValue = prop['value'] as OxcNode | undefined;\n if (!propKey) continue;\n\n const fieldName =\n (propKey['name'] as string | undefined) ??\n (propKey['value'] as string | undefined);\n if (!fieldName) continue;\n\n const keyStart = propKey['start'] as number;\n const keyEnd = propKey['end'] as number;\n\n // Cursor is on this property key\n if (offset >= keyStart && offset <= keyEnd) {\n const fieldPath = [...path, fieldName];\n foundResult = { dictionaryKey: dictionaryKey!, fieldName, fieldPath };\n return true;\n }\n\n if (!propValue) continue;\n // Skip all intlayer leaf calls — their arguments are not navigable content fields\n if (isIntlayerLeafCall(propValue)) continue;\n // Recurse into nested plain objects\n if (propValue['type'] === 'ObjectExpression') {\n if (walkContent(propValue, [...path, fieldName])) return true;\n }\n }\n\n return false;\n };\n\n if (walkContent(contentNode, [])) return true;\n });\n\n return foundResult;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"findKeyInContentFile.mjs","names":[],"sources":["../../src/findKeyInContentFile.ts"],"sourcesContent":["import { type OxcNode, parseText, walkAst } from './oxcUtils';\n\n// Group 2: quoted value — key: \"foo\" | key: 'foo' | key: `foo` (TS/JS/JSON)\n// Group 3: unquoted value — key: foo-bar (YAML / Markdown frontmatter)\nconst KEY_DECLARATION_REGULAR_EXPRESSION =\n /(?:^|[{,\\s])\"?key\"?\\s*:\\s*(?:(['\"`])([^'\"`\\r\\n]+)\\1|([^ \\t'\"`{},\\r\\n]+))/g;\n\n/**\n * If `offset` falls within a `key:` declaration in a content file,\n * return the declared key string; otherwise return null.\n * Handles:\n * - Quoted values: key: \"foo\" | key: 'foo' | key: `foo` (TS/JS)\n * - JSON values: \"key\": \"foo\" (JSON/JSON5/JSONC)\n * - Bare values: key: foo-bar (YAML / Markdown frontmatter)\n */\nexport const findKeyInContentFile = (\n text: string,\n offset: number\n): string | null => {\n for (const match of text.matchAll(KEY_DECLARATION_REGULAR_EXPRESSION)) {\n const start = match.index!;\n const end = start + match[0].length;\n\n if (offset >= start && offset <= end) {\n return match[2] ?? match[3] ?? null;\n }\n }\n\n return null;\n};\n\n/**\n * If `offset` falls on a content field property key inside an intlayer content\n * file, return the dictionary key, the leaf field name, and the full field path.\n *\n * The `fileExtension` parameter controls which parser is used:\n *\n * - `.json` / `.json5` / `.jsonc` → dedicated JSON parser\n * - `.yaml` / `.yml` → YAML regex heuristic\n * - `.md` / `.mdx` → always null (body is the content value)\n * - everything else → Oxc AST (TS/JS/TSX/JSX)\n *\n * When `fileExtension` is omitted the function falls back to Oxc, then to\n * JSON/YAML heuristics based on text shape.\n */\nexport const findContentFieldAtOffset = (\n text: string,\n offset: number,\n fileExtension?: string\n): FieldResult | null => {\n const ext = fileExtension?.toLowerCase() ?? '';\n\n // Markdown has no individual navigable content fields\n if (ext === '.md' || ext === '.mdx') return null;\n\n // JSON / JSON5 / JSONC\n if (ext === '.json' || ext === '.json5' || ext === '.jsonc') {\n return findContentFieldAtOffsetJson(text, offset);\n }\n\n // YAML / YML\n if (ext === '.yaml' || ext === '.yml') {\n return findContentFieldAtOffsetYaml(text, offset);\n }\n\n // TS / JS / TSX / JSX — use Oxc AST\n const program = parseText(text);\n if (program) {\n return findContentFieldAtOffsetOxc(text, offset, program);\n }\n\n // Oxc failed to parse — fall back on content-shape heuristics\n // JSON: starts with optional whitespace then `{`\n if (/^\\s*\\{/.test(text)) {\n return findContentFieldAtOffsetJson(text, offset);\n }\n\n // Markdown: YAML frontmatter fence with no `content:` block\n if (/^---\\s*\\r?\\n/.test(text)) {\n if (!/^content\\s*:/m.test(text)) return null;\n }\n\n // YAML (or anything else with a content: block)\n return findContentFieldAtOffsetYaml(text, offset);\n};\n\n// ---------------------------------------------------------------------------\n// Helpers shared by the Oxc and YAML/JSON fallback paths\n// ---------------------------------------------------------------------------\n\n/**\n * All Intlayer helper function names whose arguments are locale-map or\n * control-flow data — NOT navigable content field names. The walker must\n * treat these calls as opaque leaf values and never descend into them.\n *\n * `t` – translation ({ en: \"…\", fr: \"…\" })\n * `enu` – enumeration ({ \"0\": \"…\", \">5\": \"…\" })\n * `cond` – condition ({ true: \"…\", false: \"…\" })\n * `plural` – plural rules ({ one: \"…\", other: \"…\" })\n * `gender` – gender rules ({ male: \"…\", female: \"…\", other: \"…\" })\n * `select` – select cases ({ Draft: \"…\", Published: \"…\", fallback: \"…\" })\n * `insert` – insertion (\"Hello {{name}}!\")\n * `nest` – nested dict (\"navbar\")\n * `md` – markdown (\"# …\")\n * `file` – file reference (\"./path/to/file.txt\")\n * `html` – html content (\"<p>…</p>\")\n */\nconst INTLAYER_LEAF_CALL_NAMES: ReadonlySet<string> = new Set([\n 't',\n 'enu',\n 'cond',\n 'plural',\n 'gender',\n 'select',\n 'insert',\n 'nest',\n 'md',\n 'file',\n 'html',\n]);\n\n/** Returns true when `node` is a call to any intlayer leaf helper function. */\nconst isIntlayerLeafCall = (node: OxcNode): boolean => {\n if (node['type'] !== 'CallExpression') return false;\n const callee = node['callee'] as OxcNode | undefined;\n return (\n callee?.['type'] === 'Identifier' &&\n INTLAYER_LEAF_CALL_NAMES.has(callee['name'] as string)\n );\n};\n\n/** Extract a string value from a Literal / StringLiteral / TemplateLiteral node. */\nconst getStringValue = (node: OxcNode): string | null => {\n if (\n (node['type'] === 'Literal' || node['type'] === 'StringLiteral') &&\n typeof node['value'] === 'string'\n ) {\n return node['value'] as string;\n }\n if (node['type'] === 'TemplateLiteral') {\n const quasis = node['quasis'] as OxcNode[] | undefined;\n const valueNode = quasis?.[0]?.['value'] as OxcNode | undefined;\n return (\n (valueNode?.['cooked'] as string | undefined) ??\n (valueNode?.['raw'] as string | undefined) ??\n null\n );\n }\n return null;\n};\n\n// ---------------------------------------------------------------------------\n// JSON content-file support\n// ---------------------------------------------------------------------------\n\n/**\n * Well-known meta-keys that appear inside Intlayer typed nodes in JSON files.\n * e.g. { \"nodeType\": \"translation\", \"translation\": { \"en\": \"…\" } }\n * None of these are navigable content fields.\n */\nconst JSON_INTLAYER_NODE_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 // JSON schema / system properties\n '$schema',\n 'id',\n 'projectIds',\n 'localId',\n 'localIds',\n 'filePath',\n 'versions',\n 'filled',\n // Top-level dictionary meta-properties (not inside content)\n 'key',\n 'title',\n 'description',\n 'tags',\n 'format',\n 'locale',\n 'schema',\n 'location',\n 'fill',\n 'priority',\n 'version',\n 'importMode',\n]);\n\n/**\n * Find the byte offset of `fieldName` (as a JSON property key) inside `text`,\n * searching within the range [searchFrom, searchTo).\n *\n * Matches `\"fieldName\"` surrounded by typical JSON delimiters.\n */\nconst findJsonKeyOffset = (\n text: string,\n fieldName: string,\n searchFrom: number,\n searchTo: number\n): number => {\n const pattern = new RegExp(\n `\"${fieldName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\"`,\n 'g'\n );\n pattern.lastIndex = searchFrom;\n let match: RegExpExecArray | null;\n while (true) {\n match = pattern.exec(text);\n if (match === null) break;\n if (match.index >= searchTo) break;\n return match.index;\n }\n return -1;\n};\n\n/**\n * Strip JSON5/JSONC single-line (//) and multi-line (/* *\\/\\/) comments,\n * and trailing commas, producing valid JSON that JSON.parse can handle.\n */\nconst stripJsonComments = (text: string): string =>\n text\n // Remove single-line comments (// …) EXCEPT in URLs (e.g., https://)\n .replace(/(?<!:)\\/\\/[^\\r\\n]*/g, '')\n // Remove block comments (/* … */)\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n // Remove trailing commas before } or ]\n .replace(/,(\\s*[}\\]])/g, '$1');\n\ntype FieldResult = {\n dictionaryKey: string;\n fieldName: string;\n fieldPath: string[];\n};\n\n/**\n * Parse a JSON/JSON5/JSONC content file and, if `offset` falls on a content\n * field key, return the dictionary key, leaf field name, and full field path.\n *\n * Returns null when:\n * - The text is not valid JSON (even after comment stripping)\n * - The cursor is not on a navigable content field key\n * - The field is a meta/system key (nodeType, translation, locale codes, …)\n */\nconst findContentFieldAtOffsetJson = (\n text: string,\n offset: number\n): FieldResult | null => {\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(stripJsonComments(text)) as Record<string, unknown>;\n } catch {\n return null;\n }\n\n const dictionaryKey =\n typeof parsed['key'] === 'string' ? parsed['key'] : null;\n if (!dictionaryKey) return null;\n\n const contentObj = parsed['content'];\n if (\n !contentObj ||\n typeof contentObj !== 'object' ||\n Array.isArray(contentObj)\n )\n return null;\n\n // Find the byte range of the \"content\" block in raw text so we can scope\n // offset comparisons to content fields only.\n const contentKeyIndex = text.indexOf('\"content\"');\n if (contentKeyIndex === -1) return null;\n\n /**\n * Recursively walk the parsed content object.\n * For each property key, check whether the raw text position of that key\n * (within the remaining text slice) overlaps with `offset`.\n *\n * `textFrom` is where we start searching for each key in the raw text —\n * we advance it as we consume properties to avoid false matches on\n * identically-named sibling keys.\n */\n const walk = (\n obj: Record<string, unknown>,\n path: string[],\n searchFrom: number,\n searchTo: number\n ): FieldResult | null => {\n for (const [fieldName, fieldValue] of Object.entries(obj)) {\n // Skip Intlayer-internal node keys — they are not navigable content fields\n if (JSON_INTLAYER_NODE_KEYS.has(fieldName)) continue;\n\n // Skip keys that look like locale codes (e.g. \"en\", \"fr\", \"en-GB\")\n if (/^[a-z]{2}(-[A-Z]{2})?$/.test(fieldName)) continue;\n\n const keyStart = findJsonKeyOffset(text, fieldName, searchFrom, searchTo);\n if (keyStart === -1) continue;\n const keyEnd = keyStart + fieldName.length + 2; // +2 for surrounding quotes\n\n if (offset >= keyStart && offset <= keyEnd) {\n return {\n dictionaryKey,\n fieldName,\n fieldPath: [...path, fieldName],\n };\n }\n\n // Recurse into plain nested objects (skip typed nodes — they have nodeType)\n if (\n fieldValue !== null &&\n typeof fieldValue === 'object' &&\n !Array.isArray(fieldValue)\n ) {\n const nested = fieldValue as Record<string, unknown>;\n // Skip Intlayer typed nodes — their children are not content fields\n if ('nodeType' in nested) continue;\n\n const result = walk(nested, [...path, fieldName], keyEnd, searchTo);\n if (result) return result;\n }\n }\n return null;\n };\n\n return walk(\n contentObj as Record<string, unknown>,\n [],\n contentKeyIndex,\n text.length\n );\n};\n\n// ---------------------------------------------------------------------------\n// YAML / Markdown frontmatter fallback\n// ---------------------------------------------------------------------------\n\n/**\n * Locale-code pattern — keys matching this inside YAML content blocks are\n * translation locale identifiers, not navigable content fields.\n */\nconst LOCALE_CODE_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;\n\n/**\n * YAML / Markdown meta-keys that appear at the top level or inside typed nodes.\n * These should not be treated as navigable content fields.\n */\nconst YAML_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 // Frontmatter / top-level keys\n 'key',\n 'title',\n 'description',\n 'tags',\n 'format',\n 'locale',\n 'schema',\n 'location',\n 'fill',\n 'priority',\n 'version',\n 'importMode',\n '$schema',\n]);\n\n/**\n * Improved regex fallback for YAML and Markdown content files.\n *\n * Strategy:\n * 1. Extract the dictionary key from the frontmatter / top-level `key:` line.\n * 2. Locate the `content:` section (YAML files only; Markdown has no `content:` block).\n * 3. Within the content section, find all `fieldName:` patterns at consistent\n * indentation levels, skipping known meta-keys and locale codes.\n * 4. Check whether `offset` falls within one of those key spans.\n * 5. Return a single-element `fieldPath` (deep path tracking is impractical\n * without a full YAML parser, but single-level is enough for most use cases).\n *\n * For Markdown files, `findContentFieldAtOffset` always returns null because\n * the body text is the content value — individual content \"fields\" are not\n * declared as separate properties.\n */\nconst findContentFieldAtOffsetYaml = (\n text: string,\n offset: number\n): FieldResult | null => {\n // Extract dictionary key\n const keyMatch =\n /^key\\s*:\\s*(?:(['\"`])([^'\"`\\r\\n]+)\\1|([^\\s'\"`{},\\r\\n]+))/m.exec(text);\n if (!keyMatch) return null;\n const dictionaryKey = keyMatch[2] ?? keyMatch[3];\n if (!dictionaryKey) return null;\n\n // Find the content: section start\n const contentSectionMatch = /^content\\s*:/m.exec(text);\n if (!contentSectionMatch) return null;\n const contentStart =\n contentSectionMatch.index + contentSectionMatch[0].length;\n\n // Find the end of the content section: next top-level key (no leading whitespace)\n // or end of file.\n const afterContent = text.slice(contentStart);\n const nextTopLevelMatch = /\\r?\\n[a-zA-Z_$][a-zA-Z0-9_$]*\\s*:/m.exec(\n afterContent\n );\n const contentEnd = nextTopLevelMatch\n ? contentStart + nextTopLevelMatch.index\n : text.length;\n\n // The cursor must be within the content block\n if (offset < contentStart || offset > contentEnd) return null;\n\n // Find all ` fieldName:` patterns within the content block.\n // We look for lines that have at least 2 spaces of indentation and a\n // word-character key followed by a colon.\n const contentText = text.slice(contentStart, contentEnd);\n const fieldPattern = /^([ \\t]+)([a-zA-Z_$][a-zA-Z0-9_$]*)[ \\t]*:/gm;\n\n let bestMatch: FieldResult | null = null;\n let fieldMatch: RegExpExecArray | null;\n\n while (true) {\n fieldMatch = fieldPattern.exec(contentText);\n if (fieldMatch === null) break;\n\n const fieldName = fieldMatch[2]!;\n\n // Skip meta-keys and locale codes\n if (YAML_META_KEYS.has(fieldName)) continue;\n if (LOCALE_CODE_PATTERN.test(fieldName)) continue;\n\n const fieldKeyStart =\n contentStart + fieldMatch.index + fieldMatch[1]!.length; // skip indentation\n const fieldKeyEnd = fieldKeyStart + fieldName.length;\n\n if (offset >= fieldKeyStart && offset <= fieldKeyEnd) {\n bestMatch = { dictionaryKey, fieldName, fieldPath: [fieldName] };\n break;\n }\n }\n\n return bestMatch;\n};\n\n// ---------------------------------------------------------------------------\n// Oxc (TS / JS) AST path\n// ---------------------------------------------------------------------------\n\nconst findContentFieldAtOffsetOxc = (\n _text: string,\n offset: number,\n program: OxcNode\n): FieldResult | null => {\n let foundResult: FieldResult | null = null;\n\n walkAst(program, (node) => {\n if (foundResult) return true;\n if (node['type'] !== 'ObjectExpression') return;\n\n const properties = node['properties'] as OxcNode[] | undefined;\n if (!properties?.length) return;\n\n // Look for sibling `key:` and `content:` properties in the same object\n let dictionaryKey: string | null = null;\n let contentNode: OxcNode | null = null;\n\n for (const prop of properties) {\n if (prop['type'] !== 'Property') continue;\n const propKey = prop['key'] as OxcNode | undefined;\n const propValue = prop['value'] as OxcNode | undefined;\n if (!propKey || !propValue) continue;\n\n const keyName =\n (propKey['name'] as string | undefined) ??\n (propKey['value'] as string | undefined);\n\n if (keyName === 'key') dictionaryKey = getStringValue(propValue);\n if (keyName === 'content') contentNode = propValue;\n }\n\n if (!dictionaryKey || !contentNode) return;\n\n // Walk the `content:` object tree, tracking the path to each property key\n const walkContent = (obj: OxcNode, path: string[]): boolean => {\n if (obj['type'] !== 'ObjectExpression') return false;\n const props = obj['properties'] as OxcNode[] | undefined;\n if (!props) return false;\n\n for (const prop of props) {\n if (prop['type'] !== 'Property') continue;\n const propKey = prop['key'] as OxcNode | undefined;\n const propValue = prop['value'] as OxcNode | undefined;\n if (!propKey) continue;\n\n const fieldName =\n (propKey['name'] as string | undefined) ??\n (propKey['value'] as string | undefined);\n if (!fieldName) continue;\n\n const keyStart = propKey['start'] as number;\n const keyEnd = propKey['end'] as number;\n\n // Cursor is on this property key\n if (offset >= keyStart && offset <= keyEnd) {\n const fieldPath = [...path, fieldName];\n foundResult = { dictionaryKey: dictionaryKey!, fieldName, fieldPath };\n return true;\n }\n\n if (!propValue) continue;\n // Skip all intlayer leaf calls — their arguments are not navigable content fields\n if (isIntlayerLeafCall(propValue)) continue;\n // Recurse into nested plain objects\n if (propValue['type'] === 'ObjectExpression') {\n if (walkContent(propValue, [...path, fieldName])) return true;\n }\n }\n\n return false;\n };\n\n if (walkContent(contentNode, [])) return true;\n });\n\n return foundResult;\n};\n"],"mappings":"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"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync } from "node:fs";
|
|
|
11
11
|
import { readFile } from "node:fs/promises";
|
|
12
12
|
import { dirname, isAbsolute, join } from "node:path";
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
14
|
-
import { getConfiguration } from "@intlayer/config/node";
|
|
14
|
+
import { getConfiguration, searchConfigurationFile } from "@intlayer/config/node";
|
|
15
15
|
import { buildComponentFilesList } from "@intlayer/engine/utils";
|
|
16
16
|
import { getUnmergedDictionaries } from "@intlayer/unmerged-dictionaries-entry";
|
|
17
17
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
@@ -36,14 +36,37 @@ const projectDictionaries = /* @__PURE__ */ new Map();
|
|
|
36
36
|
const DICTIONARY_CACHE_TTL_MS = 5e3;
|
|
37
37
|
const projectSourceFiles = /* @__PURE__ */ new Map();
|
|
38
38
|
const SOURCE_FILES_CACHE_TTL_MS = 3e4;
|
|
39
|
+
/**
|
|
40
|
+
* Directory of the file → directory holding the closest intlayer config.
|
|
41
|
+
* Populated lazily by `getProjectRoot`.
|
|
42
|
+
*/
|
|
43
|
+
const projectRoots = /* @__PURE__ */ new Map();
|
|
39
44
|
const invalidateConfigCaches = () => {
|
|
40
45
|
projectConfigs.clear();
|
|
41
46
|
projectDictionaries.clear();
|
|
47
|
+
projectRoots.clear();
|
|
42
48
|
projectSourceFiles.clear();
|
|
43
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the project root for a file — the directory that holds the closest
|
|
52
|
+
* intlayer config file.
|
|
53
|
+
*
|
|
54
|
+
* `getConfiguration({ baseDir })` treats `baseDir` as the project root and
|
|
55
|
+
* derives every `system.*` path from it, so passing the file's own directory
|
|
56
|
+
* would resolve `.intlayer/` next to the file (e.g. `src/.intlayer/…`) instead
|
|
57
|
+
* of at the project root — leaving every dictionary lookup empty.
|
|
58
|
+
*/
|
|
59
|
+
const getProjectRoot = (fileDirectory) => {
|
|
60
|
+
const cached = projectRoots.get(fileDirectory);
|
|
61
|
+
if (cached !== void 0) return cached;
|
|
62
|
+
const { configurationFilePath } = searchConfigurationFile(fileDirectory);
|
|
63
|
+
const root = configurationFilePath ? dirname(configurationFilePath) : workspaceRoot ?? fileDirectory;
|
|
64
|
+
projectRoots.set(fileDirectory, root);
|
|
65
|
+
return root;
|
|
66
|
+
};
|
|
44
67
|
const getProjectConfig = (absolutePath) => {
|
|
45
68
|
try {
|
|
46
|
-
const { system, build, content, compiler } = getConfiguration({ baseDir: dirname(absolutePath) });
|
|
69
|
+
const { system, build, content, compiler } = getConfiguration({ baseDir: getProjectRoot(dirname(absolutePath)) });
|
|
47
70
|
const cacheKey = system.baseDir;
|
|
48
71
|
if (!projectConfigs.has(cacheKey)) {
|
|
49
72
|
projectConfigs.set(cacheKey, {
|
|
@@ -574,6 +597,7 @@ documents.onDidClose((event) => {
|
|
|
574
597
|
connection.onDidChangeWatchedFiles(() => {
|
|
575
598
|
invalidateConfigCaches();
|
|
576
599
|
log("watched files changed — source file list and dictionary cache invalidated");
|
|
600
|
+
for (const document of documents.all()) scheduleDiagnostics(document.uri, document.getText(), document.version);
|
|
577
601
|
});
|
|
578
602
|
documents.listen(connection);
|
|
579
603
|
connection.listen();
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { buildComponentFilesList } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport {\n type CompletionItem,\n CompletionItemKind,\n type CompletionParams,\n createConnection,\n type Diagnostic,\n DiagnosticSeverity,\n type Hover,\n type HoverParams,\n type InitializeParams,\n type InitializeResult,\n Location,\n MarkupKind,\n Position,\n ProposedFeatures,\n Range,\n type ReferenceParams,\n TextDocumentSyncKind,\n TextDocuments,\n} from 'vscode-languageserver/node';\nimport { TextDocument } from 'vscode-languageserver-textdocument';\nimport {\n buildCallerWithKeyPattern,\n buildKeyUsagePatterns,\n getCallerNamesAlternation,\n} from './callers/patterns';\nimport {\n formatDictionaryHover,\n formatFieldHover,\n formatFieldValue,\n getFieldByPath,\n getFieldsAtPath,\n} from './dictionaryUtils';\nimport {\n escapeRegularExpression,\n findFieldRangesInFile,\n offsetToRange,\n} from './findFieldInFile';\nimport { findKeyAtOffset } from './findKeyAtOffset';\nimport {\n findContentFieldAtOffset,\n findKeyInContentFile,\n} from './findKeyInContentFile';\nimport { type OxcNode, parseText, walkAst } from './oxcUtils';\nimport {\n collectNamespaceReferences,\n findMessageUsageAtOffset,\n matchCallNamespace,\n} from './usageAnalyzer';\n\n// Create a connection for the server, using Node's IPC as a transport.\n// Also include all preview / proposed LSP features.\nconst connection = createConnection(ProposedFeatures.all);\n\n// Create a simple text document manager.\nconst documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);\n\n/**\n * Send a log line to the client's Output panel (View → Output → \"Intlayer LSP\").\n * Uses connection.console.log so it travels over the JSON-RPC channel and never\n * touches stdout (which is reserved for the LSP protocol).\n */\nconst log = (message: string): void =>\n connection.console.log(`[Intlayer LSP] ${message}`);\n\nlet workspaceRoot: string | null | undefined;\n\ntype WorkspaceConfig = Pick<\n IntlayerConfig,\n 'system' | 'build' | 'content' | 'compiler'\n>;\n\n/**\n * Resolve (and cache) the Intlayer configuration for the current workspace.\n * The LSP process cwd is not the project root, so we must resolve the config\n * from `workspaceRoot` to get the right `system`/`build`/`content` paths.\n */\nconst projectConfigs = new Map<string, WorkspaceConfig>();\n\ntype DictionariesCache = {\n dictionaries: ReturnType<typeof getUnmergedDictionaries>;\n cachedAt: number;\n};\nconst projectDictionaries = new Map<string, DictionariesCache>();\nconst DICTIONARY_CACHE_TTL_MS = 5_000;\n\ntype SourceFilesCache = {\n files: string[];\n cachedAt: number;\n};\nconst projectSourceFiles = new Map<string, SourceFilesCache>();\nconst SOURCE_FILES_CACHE_TTL_MS = 30_000;\n\nconst invalidateConfigCaches = () => {\n projectConfigs.clear();\n projectDictionaries.clear();\n\n projectSourceFiles.clear();\n};\n\nconst getProjectConfig = (absolutePath: string): WorkspaceConfig | null => {\n try {\n const baseDir = dirname(absolutePath);\n // Find the closest intlayer config relative to the file.\n const { system, build, content, compiler } = getConfiguration({ baseDir });\n const cacheKey = system.baseDir;\n\n if (!projectConfigs.has(cacheKey)) {\n projectConfigs.set(cacheKey, { system, build, content, compiler });\n log(\n `getProjectConfig — OK baseDir=${system.baseDir} unmergedDictionariesDir=${system.unmergedDictionariesDir}`\n );\n }\n return projectConfigs.get(cacheKey)!;\n } catch (error) {\n log(`getProjectConfig — FAILED: ${error}`);\n return null;\n }\n};\n\nconst getUnmergedDictionariesCached = (\n config: WorkspaceConfig\n): ReturnType<typeof getUnmergedDictionaries> => {\n const cacheKey = config.system.baseDir;\n const cached = projectDictionaries.get(cacheKey);\n const now = Date.now();\n\n if (cached && now - cached.cachedAt < DICTIONARY_CACHE_TTL_MS) {\n return cached.dictionaries;\n }\n\n const dictionaries = getUnmergedDictionaries(config);\n projectDictionaries.set(cacheKey, { dictionaries, cachedAt: now });\n return dictionaries;\n};\n\n// ---------------------------------------------------------------------------\n// Pattern helpers — derived lazily from the live config.\n// ---------------------------------------------------------------------------\n\nconst isContentFile = (uri: string, config: WorkspaceConfig): boolean => {\n const fileExtensions = config.content.fileExtensions ?? [];\n return fileExtensions.some((extension) => uri.endsWith(extension));\n};\n\n// ---------------------------------------------------------------------------\n// Async I/O helpers\n// ---------------------------------------------------------------------------\n\n/** Read a file as UTF-8, returning null on any error. */\nconst tryReadFile = async (filePath: string): Promise<string | null> => {\n try {\n return await readFile(filePath, 'utf-8');\n } catch {\n return null;\n }\n};\n\n/**\n * Run `callback` over every item with at most `limit` tasks in-flight at once.\n * JS is single-threaded so `results.push` inside callback is race-free.\n */\nconst runConcurrent = async <T>(\n items: T[],\n callback: (item: T) => Promise<void>,\n limit = 50\n): Promise<void> => {\n if (items.length === 0) return;\n let index = 0;\n const worker = async () => {\n while (index < items.length) {\n await callback(items[index++]!);\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, worker)\n );\n};\n\n/** Find the `key: \"<key>\"` line inside a content file so the cursor lands on it. */\nconst getKeyRange = async (\n absolutePath: string,\n key: string\n): Promise<Range> => {\n try {\n const content = await readFile(absolutePath, 'utf-8');\n const lines = content.split(/\\r?\\n/);\n const keyRegularExpression = new RegExp(\n `key\\\\s*:\\\\s*['\"\\`]${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}['\"\\`]`\n );\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n const column = lines[lineIndex]?.search(keyRegularExpression);\n\n if (column !== undefined && column !== -1) {\n return Range.create(\n Position.create(lineIndex, column),\n Position.create(lineIndex, lines[lineIndex]!.length)\n );\n }\n }\n } catch {\n // fall through to the top of the file\n }\n\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n};\n\n/**\n * Return every `.content` file location that declares the given key.\n * A key can be split across several content files (merged dictionaries), so we\n * return one `Location` per source file, pointing at the `key:` declaration.\n */\nconst getContentDeclarationLocations = async (\n key: string,\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config) return [];\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter((dictionary) => dictionary?.key === key && dictionary.filePath);\n\n const locations: Location[] = [];\n\n await runConcurrent(dictionaries, async (dictionary) => {\n const filePath = dictionary.filePath!;\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : join(config.system.baseDir, filePath);\n\n if (!existsSync(absolutePath)) return;\n\n locations.push(\n Location.create(\n pathToFileURL(absolutePath).href,\n await getKeyRange(absolutePath, key)\n )\n );\n });\n\n return locations;\n};\n\n/**\n * Return the cached source-file list, re-scanning only when the cache has\n * expired or been explicitly invalidated (config change, watched-file event).\n */\nconst getSourceFiles = async (\n directory: string,\n config: WorkspaceConfig\n): Promise<string[]> => {\n const now = Date.now();\n const cached = projectSourceFiles.get(directory);\n\n if (cached && now - cached.cachedAt < SOURCE_FILES_CACHE_TTL_MS) {\n return cached.files;\n }\n const startTime = Date.now();\n const files = buildComponentFilesList(config as unknown as IntlayerConfig, [\n '**/.output/**',\n '**/.svelte-kit/**',\n '**/src-tauri/**',\n '**/.next/**',\n '**/.nuxt/**',\n '**/.expo/**',\n '**/.vercel/**',\n '**/.turbo/**',\n '**/.tanstack/**',\n '**/dist/**',\n '**/build/**',\n '**/node_modules/**',\n ]);\n projectSourceFiles.set(directory, { files, cachedAt: Date.now() });\n log(\n `source files scanned: ${files.length} file(s) in ${Date.now() - startTime}ms`\n );\n return files;\n};\n\n/**\n * Return every source-file location that calls useIntlayer / getIntlayer with\n * the given key.\n */\nconst getKeyUsageLocations = async (\n key: string,\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config) return [];\n\n const usagePatterns = buildKeyUsagePatterns(key);\n\n const filePaths = await getSourceFiles(config.system.baseDir, config);\n const locations: Location[] = [];\n const startTime = Date.now();\n\n await runConcurrent(filePaths, async (filePath) => {\n const text = await tryReadFile(filePath);\n // Quick string check before running the regexes — skips the vast majority of files\n\n if (!text?.includes(key)) return;\n\n const seenStarts = new Set<number>();\n\n for (const usagePattern of usagePatterns) {\n for (const match of text.matchAll(usagePattern)) {\n const matchStart = match.index!;\n\n if (seenStarts.has(matchStart)) continue;\n\n seenStarts.add(matchStart);\n const matchEnd = matchStart + match[0].length;\n locations.push(\n Location.create(\n pathToFileURL(filePath).href,\n offsetToRange(text, matchStart, matchEnd)\n )\n );\n }\n }\n });\n\n log(\n `getKeyUsageLocations(\"${key}\") — ${filePaths.length} file(s) searched in ${Date.now() - startTime}ms → ${locations.length} hit(s)`\n );\n return locations;\n};\n\n/**\n * Return every source-file location that accesses the field at `fieldPath` on\n * the result of `useIntlayer(dictionaryKey)` / `getIntlayer(dictionaryKey)`.\n *\n * `fieldPath` is an array of property names from the content root to the\n * target field (e.g. `['searchInput', 'text']` for a nested field).\n * Single-element paths (e.g. `['greet']`) behave like the previous\n * single-`fieldName` API.\n */\nconst getFieldUsageLocations = async (\n dictionaryKey: string,\n fieldPath: string[],\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config || fieldPath.length === 0) return [];\n\n const leafFieldName = fieldPath[fieldPath.length - 1]!;\n const escapedKey = escapeRegularExpression(dictionaryKey);\n const escapedLeaf = escapeRegularExpression(leafFieldName);\n const useCallRegularExpression = buildCallerWithKeyPattern(dictionaryKey);\n\n const filePaths = await getSourceFiles(config.system.baseDir, config);\n const locations: Location[] = [];\n const startTime = Date.now();\n\n await runConcurrent(filePaths, async (filePath) => {\n const text = await tryReadFile(filePath);\n\n if (!text) return;\n // Pre-filter: dictionaryKey and every segment of the path must appear in the file\n\n if (!text.includes(dictionaryKey)) return;\n\n if (fieldPath.some((segment) => !text.includes(segment))) return;\n\n if (!useCallRegularExpression.test(text)) return;\n\n for (const range of findFieldRangesInFile(\n text,\n escapedKey,\n leafFieldName,\n escapedLeaf,\n fieldPath\n )) {\n locations.push(Location.create(pathToFileURL(filePath).href, range));\n }\n });\n\n log(\n `getFieldUsageLocations(\"${dictionaryKey}.${fieldPath.join('.')}\") — ${filePaths.length} file(s) searched in ${Date.now() - startTime}ms → ${locations.length} hit(s)`\n );\n return locations;\n};\n\n/**\n * Find the line/column of the field's leaf name as a property key inside a\n * content file. Looks for `fieldName:` or `'field.name':` (flat keys with\n * dots, as used by lingui catalogs), not just any occurrence. Falls back to\n * the top of the file if the field cannot be located.\n */\nconst getFieldRangeInContentFile = async (\n absolutePath: string,\n fieldPath: string[]\n): Promise<Range> => {\n const content = await tryReadFile(absolutePath);\n const fieldName = fieldPath[fieldPath.length - 1];\n\n if (!content || !fieldName)\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n\n // Match `fieldName:` at an object property key position (not a value),\n // allowing an optional quoted form for keys containing dots or dashes.\n const fieldRegularExpression = new RegExp(\n `(?<![.\\\\w])(['\"\\`]?)${escapeRegularExpression(fieldName)}\\\\1\\\\s*:`\n );\n const lines = content.split(/\\r?\\n/);\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n const line = lines[lineIndex]!;\n const matchPosition = line.search(fieldRegularExpression);\n\n if (matchPosition === -1) continue;\n const column = line.indexOf(fieldName, matchPosition);\n\n if (column === -1) continue;\n return Range.create(\n Position.create(lineIndex, column),\n Position.create(lineIndex, column + fieldName.length)\n );\n }\n\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n};\n\n/**\n * Return every content-file location where the field at `fieldPath` is\n * defined as a property of the dictionary with the given `dictionaryKey`.\n */\nconst getContentFieldLocations = async (\n dictionaryKey: string,\n fieldPath: string[],\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config) return [];\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter(\n (dictionary) => dictionary?.key === dictionaryKey && dictionary.filePath\n );\n\n const locations: Location[] = [];\n\n await runConcurrent(dictionaries, async (dictionary) => {\n const filePath = dictionary.filePath!;\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : join(config.system.baseDir, filePath);\n\n if (!existsSync(absolutePath)) return;\n\n locations.push(\n Location.create(\n pathToFileURL(absolutePath).href,\n await getFieldRangeInContentFile(absolutePath, fieldPath)\n )\n );\n });\n\n return locations;\n};\n\n// ---------------------------------------------------------------------------\n// Diagnostics — push-based (no capability needed)\n// ---------------------------------------------------------------------------\n\nconst DIAGNOSTIC_DEBOUNCE_MS = 300;\nconst diagnosticTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst computeDiagnostics = (\n text: string,\n config: WorkspaceConfig\n): Diagnostic[] => {\n const namespaceReferences = collectNamespaceReferences(text);\n\n if (namespaceReferences.length === 0) return [];\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const knownKeys = new Set(\n Object.values(unmergedDictionaries)\n .flat()\n .map((dictionary) => dictionary?.key)\n .filter((key): key is string => typeof key === 'string')\n );\n\n const diagnostics: Diagnostic[] = [];\n\n for (const reference of namespaceReferences) {\n if (knownKeys.has(reference.dictionaryKey)) continue;\n\n diagnostics.push({\n range: offsetToRange(\n text,\n reference.namespaceStart,\n reference.namespaceEnd\n ),\n severity: DiagnosticSeverity.Warning,\n source: 'intlayer',\n message: `Dictionary key \"${reference.dictionaryKey}\" is not declared in any content file`,\n });\n }\n\n return diagnostics;\n};\n\nconst sendDiagnosticsForDocument = (\n uri: string,\n text: string,\n version: number | null\n): void => {\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const config = getProjectConfig(absolutePath);\n\n const diagnostics =\n config && !isContentFile(uri, config)\n ? computeDiagnostics(text, config)\n : [];\n\n connection.sendDiagnostics({\n uri,\n version: version ?? undefined,\n diagnostics,\n });\n};\n\nconst scheduleDiagnostics = (\n uri: string,\n text: string,\n version: number | null\n): void => {\n const existing = diagnosticTimers.get(uri);\n\n if (existing !== undefined) clearTimeout(existing);\n\n diagnosticTimers.set(\n uri,\n setTimeout(() => {\n diagnosticTimers.delete(uri);\n sendDiagnosticsForDocument(uri, text, version);\n }, DIAGNOSTIC_DEBOUNCE_MS)\n );\n};\n\n// ---------------------------------------------------------------------------\n// Completion context detection\n// ---------------------------------------------------------------------------\n\ntype CompletionCtx =\n | { kind: 'key' }\n | { kind: 'field'; dictionaryKey: string; fieldPath: string[] }\n | null;\n\n/** Find the dictionary key for a simple variable assigned from any registered caller. */\nconst resolveVariableToDictionaryKey = (\n program: OxcNode,\n varName: string\n): string | null => {\n let result: string | null = null;\n\n walkAst(program, (node) => {\n if (result) return true;\n\n if (node['type'] !== 'VariableDeclarator') return;\n const id = node['id'] as OxcNode | undefined;\n\n if (id?.['type'] !== 'Identifier' || (id['name'] as string) !== varName)\n return;\n const init = node['init'] as OxcNode | undefined;\n\n if (!init) return;\n result = matchCallNamespace(init);\n return true;\n });\n return result;\n};\n\n/**\n * Detect what kind of completion is appropriate at the given offset.\n *\n * Handles:\n * - key completion: cursor inside first string arg of useIntlayer/getIntlayer\n * - field (member): cursor after `variable.` where variable came from useIntlayer\n * - field (destructure): cursor inside `const { | } = useIntlayer(\"key\")`\n */\nconst getCompletionContext = (\n textBefore: string,\n textAfter: string,\n program: OxcNode | null\n): CompletionCtx => {\n // Key completion\n\n if (\n new RegExp(\n `\\\\b(?:${getCallerNamesAlternation()})\\\\b(?:<[^<>]*>)?\\\\s*\\\\(\\\\s*['\"\\`][^'\"\\`\\\\n]*$`\n ).test(textBefore)\n ) {\n return { kind: 'key' };\n }\n\n // Field completion via member access: rootVar.path.to.\n const memberMatch = /\\b(\\w+)((?:\\.\\w+)*)\\.$/.exec(textBefore);\n\n if (memberMatch && program) {\n const rootVar = memberMatch[1]!;\n const chainStr = memberMatch[2]!;\n const dictKey = resolveVariableToDictionaryKey(program, rootVar);\n\n if (dictKey) {\n const fieldPath = chainStr ? chainStr.slice(1).split('.') : [];\n return { kind: 'field', dictionaryKey: dictKey, fieldPath };\n }\n }\n\n // Field completion inside destructuring: const { | } = useIntlayer(\"key\")\n\n if (/(?:const|let|var)\\s*\\{[^}]*$/.test(textBefore)) {\n const destructMatch = new RegExp(\n `^[^}]*\\\\}\\\\s*=\\\\s*(?:${getCallerNamesAlternation()})(?:<[^<>]*>)?\\\\s*\\\\(\\\\s*['\"\\`]([^'\"\\`\\\\n]+)['\"\\`]`\n ).exec(textAfter);\n\n if (destructMatch) {\n return { kind: 'field', dictionaryKey: destructMatch[1]!, fieldPath: [] };\n }\n }\n\n return null;\n};\n\nconnection.onInitialize((parameters: InitializeParams): InitializeResult => {\n workspaceRoot =\n parameters.rootPath ||\n (parameters.workspaceFolders?.[0]?.uri\n ? decodeURIComponent(new URL(parameters.workspaceFolders[0].uri).pathname)\n : undefined);\n invalidateConfigCaches();\n\n log(`initialized — workspaceRoot: ${workspaceRoot ?? '(none)'}`);\n\n if (parameters.workspaceFolders?.length) {\n log(\n `workspaceFolders: ${parameters.workspaceFolders.map((folder) => folder.name).join(', ')}`\n );\n }\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n definitionProvider: true,\n referencesProvider: true,\n hoverProvider: true,\n completionProvider: {\n triggerCharacters: ['\"', \"'\", '`', '.'],\n resolveProvider: false,\n },\n },\n };\n});\n\nconnection.onDefinition(async (parameters) => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const position = parameters.position;\n const document = documents.get(uri);\n\n if (!document) {\n log(`onDefinition — document not found in cache: ${uri}`);\n return null;\n }\n\n const text = document.getText();\n const offset = document.offsetAt(position);\n const shortenedUri = uri\n .replace(/^file:\\/\\//, '')\n .split('/')\n .slice(-2)\n .join('/');\n\n log(`onDefinition — ${shortenedUri} ${position.line}:${position.character}`);\n const config = getProjectConfig(absolutePath);\n\n if (config && isContentFile(uri, config)) {\n // Content-file → component navigation is handled exclusively by the VS Code\n // extension's intlayerContentDefinitionProvider (which returns DefinitionLink[]\n // with a proper originSelectionRange). Returning null here prevents the LSP\n // from emitting a duplicate set of Location[] results for the same request.\n log('onDefinition — content file: delegating to extension provider');\n return null;\n }\n\n // Cursor on the key string / function name → navigate to content declaration.\n const key = findKeyAtOffset(text, offset);\n\n if (key) {\n log(\n `onDefinition — useIntlayer key: \"${key}\" → searching content declarations`\n );\n const locations = await getContentDeclarationLocations(key, absolutePath);\n log(\n `onDefinition — found ${locations.length} content declaration(s) for key \"${key}\"`\n );\n return locations.length ? locations : null;\n }\n\n // Cursor on any message usage: destructured property, member access,\n // t('path.to.field') call, formatMessage({ id }), <Trans>, lingui t`…`, …\n const usage = findMessageUsageAtOffset(text, offset);\n\n if (usage && usage.fieldPath.length > 0) {\n log(\n `onDefinition — usage field: \"${usage.dictionaryKey}.${usage.fieldPath.join('.')}\" (${usage.callerName}) → searching content field`\n );\n const locations = await getContentFieldLocations(\n usage.dictionaryKey,\n usage.fieldPath,\n absolutePath\n );\n log(\n `onDefinition — found ${locations.length} content file(s) with field \"${usage.fieldPath.join('.')}\"`\n );\n return locations.length ? locations : null;\n }\n\n if (usage) {\n // Dictionary-level usage (e.g. bare content variable) → content files.\n const locations = await getContentDeclarationLocations(\n usage.dictionaryKey,\n absolutePath\n );\n return locations.length ? locations : null;\n }\n\n log('onDefinition — no Intlayer key at cursor, no result');\n return null;\n});\n\n/**\n * \"Find All References\" handler (Shift+F12).\n *\n * From a content file, returns every source-file location that uses the key or\n * field at the cursor — the reverse direction of Go-to-Definition.\n *\n * From a source file, returns all content-file declarations for the key or\n * field at the cursor — complementing what Go-to-Definition already handles.\n */\nconnection.onReferences(async (parameters: ReferenceParams) => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const position = parameters.position;\n const document = documents.get(uri);\n\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(position);\n const shortenedUri = uri\n .replace(/^file:\\/\\//, '')\n .split('/')\n .slice(-2)\n .join('/');\n\n log(`onReferences — ${shortenedUri} ${position.line}:${position.character}`);\n const config = getProjectConfig(absolutePath);\n\n if (config && isContentFile(uri, config)) {\n // Cursor on `key: \"…\"` → all useIntlayer(key) call sites\n const key = findKeyInContentFile(text, offset);\n\n if (key) {\n const locations = await getKeyUsageLocations(key, absolutePath);\n log(`onReferences — key \"${key}\" → ${locations.length} call site(s)`);\n return locations.length ? locations : null;\n }\n\n // Cursor on a content field → all field usage sites\n const extMatch = uri.match(/\\.[^.]+$/);\n const ext = extMatch ? extMatch[0] : '';\n const fieldInfo = findContentFieldAtOffset(text, offset, ext);\n\n if (fieldInfo) {\n const locations = await getFieldUsageLocations(\n fieldInfo.dictionaryKey,\n fieldInfo.fieldPath,\n absolutePath\n );\n log(\n `onReferences — field \"${fieldInfo.fieldPath.join('.')}\" → ${locations.length} usage(s)`\n );\n return locations.length ? locations : null;\n }\n\n return null;\n }\n\n // Source file: key string → content-file declarations\n const key = findKeyAtOffset(text, offset);\n\n if (key) {\n const locations = await getContentDeclarationLocations(key, absolutePath);\n log(`onReferences — key \"${key}\" → ${locations.length} declaration(s)`);\n return locations.length ? locations : null;\n }\n\n // Source file: field property, t('field') call, JSX message component, …\n // → content-file field definitions\n const usage = findMessageUsageAtOffset(text, offset);\n\n if (usage && usage.fieldPath.length > 0) {\n const locations = await getContentFieldLocations(\n usage.dictionaryKey,\n usage.fieldPath,\n absolutePath\n );\n log(\n `onReferences — field \"${usage.fieldPath.join('.')}\" → ${locations.length} content definition(s)`\n );\n return locations.length ? locations : null;\n }\n\n return null;\n});\n\n// ---------------------------------------------------------------------------\n// Hover\n// ---------------------------------------------------------------------------\n\nconnection.onHover(async (parameters: HoverParams): Promise<Hover | null> => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const document = documents.get(uri);\n\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(parameters.position);\n const config = getProjectConfig(absolutePath);\n\n if (!config) return null;\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const getDicts = (key: string) =>\n Object.values(unmergedDictionaries)\n .flat()\n .filter((d) => d?.key === key);\n\n if (isContentFile(uri, config)) {\n // Cursor on `key: \"...\"` declaration\n const key = findKeyInContentFile(text, offset);\n\n if (key) {\n const dicts = getDicts(key);\n\n if (dicts.length > 0) {\n return {\n contents: {\n kind: MarkupKind.Markdown,\n value: formatDictionaryHover(dicts, key),\n },\n };\n }\n }\n\n // Cursor on a content field\n const ext = uri.match(/\\.[^.]+$/)?.[0] ?? '';\n const fieldInfo = findContentFieldAtOffset(text, offset, ext);\n\n if (fieldInfo) {\n const dicts = getDicts(fieldInfo.dictionaryKey);\n\n if (dicts.length > 0) {\n const hoverText = formatFieldHover(\n dicts,\n fieldInfo.dictionaryKey,\n fieldInfo.fieldPath\n );\n\n if (hoverText) {\n return {\n contents: { kind: MarkupKind.Markdown, value: hoverText },\n };\n }\n }\n }\n\n return null;\n }\n\n // Source file: cursor on key string inside useIntlayer(\"key\")\n const key = findKeyAtOffset(text, offset);\n\n if (key) {\n const dicts = getDicts(key);\n\n if (dicts.length > 0) {\n return {\n contents: {\n kind: MarkupKind.Markdown,\n value: formatDictionaryHover(dicts, key),\n },\n };\n }\n return null;\n }\n\n // Source file: cursor on any message usage (field property, t('field'),\n // formatMessage({ id }), <Trans>, lingui t`…`, …)\n const usage = findMessageUsageAtOffset(text, offset);\n\n if (usage) {\n const dicts = getDicts(usage.dictionaryKey);\n\n if (dicts.length > 0) {\n if (usage.fieldPath.length === 0) {\n return {\n contents: {\n kind: MarkupKind.Markdown,\n value: formatDictionaryHover(dicts, usage.dictionaryKey),\n },\n };\n }\n\n const hoverText = formatFieldHover(\n dicts,\n usage.dictionaryKey,\n usage.fieldPath\n );\n\n if (hoverText) {\n return {\n contents: { kind: MarkupKind.Markdown, value: hoverText },\n };\n }\n }\n }\n\n return null;\n});\n\n// ---------------------------------------------------------------------------\n// Completion\n// ---------------------------------------------------------------------------\n\nconnection.onCompletion(\n async (parameters: CompletionParams): Promise<CompletionItem[] | null> => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const document = documents.get(uri);\n\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(parameters.position);\n const config = getProjectConfig(absolutePath);\n\n if (!config) return null;\n\n const program = parseText(text);\n const context = getCompletionContext(\n text.slice(0, offset),\n text.slice(offset),\n program\n );\n\n if (!context) return null;\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n\n if (context.kind === 'key') {\n const seen = new Set<string>();\n const items: CompletionItem[] = [];\n for (const dictionary of Object.values(unmergedDictionaries).flat()) {\n if (!dictionary?.key || seen.has(dictionary.key)) continue;\n\n seen.add(dictionary.key);\n items.push({\n label: dictionary.key,\n kind: CompletionItemKind.Value,\n detail: dictionary.title ?? undefined,\n documentation: dictionary.description\n ? { kind: MarkupKind.Markdown, value: dictionary.description }\n : undefined,\n });\n }\n log(`onCompletion — key: ${items.length} dictionary keys`);\n return items.length > 0 ? items : null;\n }\n\n if (context.kind === 'field') {\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter((dictionary) => dictionary?.key === context.dictionaryKey);\n\n if (dictionaries.length === 0) return null;\n\n const primary = dictionaries[0]!;\n const fields = getFieldsAtPath(primary.content, context.fieldPath);\n const items: CompletionItem[] = fields.map((fieldName) => {\n const value = getFieldByPath(primary.content, [\n ...context.fieldPath,\n fieldName,\n ]);\n return {\n label: fieldName,\n kind: CompletionItemKind.Field,\n detail: formatFieldValue(value),\n };\n });\n log(\n `onCompletion — field \"${context.dictionaryKey}.${context.fieldPath.join('.')}\": ${items.length} field(s)`\n );\n return items.length > 0 ? items : null;\n }\n\n return null;\n }\n);\n\n// ---------------------------------------------------------------------------\n// Push diagnostics\n// ---------------------------------------------------------------------------\n\ndocuments.onDidOpen((event) => {\n sendDiagnosticsForDocument(\n event.document.uri,\n event.document.getText(),\n event.document.version\n );\n});\n\ndocuments.onDidChangeContent((event) => {\n scheduleDiagnostics(\n event.document.uri,\n event.document.getText(),\n event.document.version\n );\n});\n\ndocuments.onDidClose((event) => {\n const uri = event.document.uri;\n const timer = diagnosticTimers.get(uri);\n\n if (timer !== undefined) {\n clearTimeout(timer);\n diagnosticTimers.delete(uri);\n }\n connection.sendDiagnostics({ uri, diagnostics: [] });\n});\n\n// Invalidate the source-file list whenever the client reports a file change.\n// The client registers a watcher for *.content.* files; any creation or\n// deletion means the project layout may have changed.\nconnection.onDidChangeWatchedFiles(() => {\n invalidateConfigCaches();\n log(\n 'watched files changed — source file list and dictionary cache invalidated'\n );\n});\n\n// Make the text document manager listen on the connection\n// for open, change and close text document events\ndocuments.listen(connection);\n\n// Listen on the connection\nconnection.listen();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n getConfiguration,\n searchConfigurationFile,\n} from '@intlayer/config/node';\nimport { buildComponentFilesList } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport {\n type CompletionItem,\n CompletionItemKind,\n type CompletionParams,\n createConnection,\n type Diagnostic,\n DiagnosticSeverity,\n type Hover,\n type HoverParams,\n type InitializeParams,\n type InitializeResult,\n Location,\n MarkupKind,\n Position,\n ProposedFeatures,\n Range,\n type ReferenceParams,\n TextDocumentSyncKind,\n TextDocuments,\n} from 'vscode-languageserver/node';\nimport { TextDocument } from 'vscode-languageserver-textdocument';\nimport {\n buildCallerWithKeyPattern,\n buildKeyUsagePatterns,\n getCallerNamesAlternation,\n} from './callers/patterns';\nimport {\n formatDictionaryHover,\n formatFieldHover,\n formatFieldValue,\n getFieldByPath,\n getFieldsAtPath,\n} from './dictionaryUtils';\nimport {\n escapeRegularExpression,\n findFieldRangesInFile,\n offsetToRange,\n} from './findFieldInFile';\nimport { findKeyAtOffset } from './findKeyAtOffset';\nimport {\n findContentFieldAtOffset,\n findKeyInContentFile,\n} from './findKeyInContentFile';\nimport { type OxcNode, parseText, walkAst } from './oxcUtils';\nimport {\n collectNamespaceReferences,\n findMessageUsageAtOffset,\n matchCallNamespace,\n} from './usageAnalyzer';\n\n// Create a connection for the server, using Node's IPC as a transport.\n// Also include all preview / proposed LSP features.\nconst connection = createConnection(ProposedFeatures.all);\n\n// Create a simple text document manager.\nconst documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);\n\n/**\n * Send a log line to the client's Output panel (View → Output → \"Intlayer LSP\").\n * Uses connection.console.log so it travels over the JSON-RPC channel and never\n * touches stdout (which is reserved for the LSP protocol).\n */\nconst log = (message: string): void =>\n connection.console.log(`[Intlayer LSP] ${message}`);\n\nlet workspaceRoot: string | null | undefined;\n\ntype WorkspaceConfig = Pick<\n IntlayerConfig,\n 'system' | 'build' | 'content' | 'compiler'\n>;\n\n/**\n * Resolve (and cache) the Intlayer configuration for the current workspace.\n * The LSP process cwd is not the project root, so we must resolve the config\n * from `workspaceRoot` to get the right `system`/`build`/`content` paths.\n */\nconst projectConfigs = new Map<string, WorkspaceConfig>();\n\ntype DictionariesCache = {\n dictionaries: ReturnType<typeof getUnmergedDictionaries>;\n cachedAt: number;\n};\nconst projectDictionaries = new Map<string, DictionariesCache>();\nconst DICTIONARY_CACHE_TTL_MS = 5_000;\n\ntype SourceFilesCache = {\n files: string[];\n cachedAt: number;\n};\nconst projectSourceFiles = new Map<string, SourceFilesCache>();\nconst SOURCE_FILES_CACHE_TTL_MS = 30_000;\n\n/**\n * Directory of the file → directory holding the closest intlayer config.\n * Populated lazily by `getProjectRoot`.\n */\nconst projectRoots = new Map<string, string>();\n\nconst invalidateConfigCaches = () => {\n projectConfigs.clear();\n projectDictionaries.clear();\n projectRoots.clear();\n\n projectSourceFiles.clear();\n};\n\n/**\n * Resolve the project root for a file — the directory that holds the closest\n * intlayer config file.\n *\n * `getConfiguration({ baseDir })` treats `baseDir` as the project root and\n * derives every `system.*` path from it, so passing the file's own directory\n * would resolve `.intlayer/` next to the file (e.g. `src/.intlayer/…`) instead\n * of at the project root — leaving every dictionary lookup empty.\n */\nconst getProjectRoot = (fileDirectory: string): string => {\n const cached = projectRoots.get(fileDirectory);\n\n if (cached !== undefined) return cached;\n\n const { configurationFilePath } = searchConfigurationFile(fileDirectory);\n const root = configurationFilePath\n ? dirname(configurationFilePath)\n : (workspaceRoot ?? fileDirectory);\n\n projectRoots.set(fileDirectory, root);\n return root;\n};\n\nconst getProjectConfig = (absolutePath: string): WorkspaceConfig | null => {\n try {\n // Find the closest intlayer config relative to the file.\n const baseDir = getProjectRoot(dirname(absolutePath));\n const { system, build, content, compiler } = getConfiguration({ baseDir });\n const cacheKey = system.baseDir;\n\n if (!projectConfigs.has(cacheKey)) {\n projectConfigs.set(cacheKey, { system, build, content, compiler });\n log(\n `getProjectConfig — OK baseDir=${system.baseDir} unmergedDictionariesDir=${system.unmergedDictionariesDir}`\n );\n }\n return projectConfigs.get(cacheKey)!;\n } catch (error) {\n log(`getProjectConfig — FAILED: ${error}`);\n return null;\n }\n};\n\nconst getUnmergedDictionariesCached = (\n config: WorkspaceConfig\n): ReturnType<typeof getUnmergedDictionaries> => {\n const cacheKey = config.system.baseDir;\n const cached = projectDictionaries.get(cacheKey);\n const now = Date.now();\n\n if (cached && now - cached.cachedAt < DICTIONARY_CACHE_TTL_MS) {\n return cached.dictionaries;\n }\n\n const dictionaries = getUnmergedDictionaries(config);\n projectDictionaries.set(cacheKey, { dictionaries, cachedAt: now });\n return dictionaries;\n};\n\n// ---------------------------------------------------------------------------\n// Pattern helpers — derived lazily from the live config.\n// ---------------------------------------------------------------------------\n\nconst isContentFile = (uri: string, config: WorkspaceConfig): boolean => {\n const fileExtensions = config.content.fileExtensions ?? [];\n return fileExtensions.some((extension) => uri.endsWith(extension));\n};\n\n// ---------------------------------------------------------------------------\n// Async I/O helpers\n// ---------------------------------------------------------------------------\n\n/** Read a file as UTF-8, returning null on any error. */\nconst tryReadFile = async (filePath: string): Promise<string | null> => {\n try {\n return await readFile(filePath, 'utf-8');\n } catch {\n return null;\n }\n};\n\n/**\n * Run `callback` over every item with at most `limit` tasks in-flight at once.\n * JS is single-threaded so `results.push` inside callback is race-free.\n */\nconst runConcurrent = async <T>(\n items: T[],\n callback: (item: T) => Promise<void>,\n limit = 50\n): Promise<void> => {\n if (items.length === 0) return;\n let index = 0;\n const worker = async () => {\n while (index < items.length) {\n await callback(items[index++]!);\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, worker)\n );\n};\n\n/** Find the `key: \"<key>\"` line inside a content file so the cursor lands on it. */\nconst getKeyRange = async (\n absolutePath: string,\n key: string\n): Promise<Range> => {\n try {\n const content = await readFile(absolutePath, 'utf-8');\n const lines = content.split(/\\r?\\n/);\n const keyRegularExpression = new RegExp(\n `key\\\\s*:\\\\s*['\"\\`]${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}['\"\\`]`\n );\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n const column = lines[lineIndex]?.search(keyRegularExpression);\n\n if (column !== undefined && column !== -1) {\n return Range.create(\n Position.create(lineIndex, column),\n Position.create(lineIndex, lines[lineIndex]!.length)\n );\n }\n }\n } catch {\n // fall through to the top of the file\n }\n\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n};\n\n/**\n * Return every `.content` file location that declares the given key.\n * A key can be split across several content files (merged dictionaries), so we\n * return one `Location` per source file, pointing at the `key:` declaration.\n */\nconst getContentDeclarationLocations = async (\n key: string,\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config) return [];\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter((dictionary) => dictionary?.key === key && dictionary.filePath);\n\n const locations: Location[] = [];\n\n await runConcurrent(dictionaries, async (dictionary) => {\n const filePath = dictionary.filePath!;\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : join(config.system.baseDir, filePath);\n\n if (!existsSync(absolutePath)) return;\n\n locations.push(\n Location.create(\n pathToFileURL(absolutePath).href,\n await getKeyRange(absolutePath, key)\n )\n );\n });\n\n return locations;\n};\n\n/**\n * Return the cached source-file list, re-scanning only when the cache has\n * expired or been explicitly invalidated (config change, watched-file event).\n */\nconst getSourceFiles = async (\n directory: string,\n config: WorkspaceConfig\n): Promise<string[]> => {\n const now = Date.now();\n const cached = projectSourceFiles.get(directory);\n\n if (cached && now - cached.cachedAt < SOURCE_FILES_CACHE_TTL_MS) {\n return cached.files;\n }\n const startTime = Date.now();\n const files = buildComponentFilesList(config as unknown as IntlayerConfig, [\n '**/.output/**',\n '**/.svelte-kit/**',\n '**/src-tauri/**',\n '**/.next/**',\n '**/.nuxt/**',\n '**/.expo/**',\n '**/.vercel/**',\n '**/.turbo/**',\n '**/.tanstack/**',\n '**/dist/**',\n '**/build/**',\n '**/node_modules/**',\n ]);\n projectSourceFiles.set(directory, { files, cachedAt: Date.now() });\n log(\n `source files scanned: ${files.length} file(s) in ${Date.now() - startTime}ms`\n );\n return files;\n};\n\n/**\n * Return every source-file location that calls useIntlayer / getIntlayer with\n * the given key.\n */\nconst getKeyUsageLocations = async (\n key: string,\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config) return [];\n\n const usagePatterns = buildKeyUsagePatterns(key);\n\n const filePaths = await getSourceFiles(config.system.baseDir, config);\n const locations: Location[] = [];\n const startTime = Date.now();\n\n await runConcurrent(filePaths, async (filePath) => {\n const text = await tryReadFile(filePath);\n // Quick string check before running the regexes — skips the vast majority of files\n\n if (!text?.includes(key)) return;\n\n const seenStarts = new Set<number>();\n\n for (const usagePattern of usagePatterns) {\n for (const match of text.matchAll(usagePattern)) {\n const matchStart = match.index!;\n\n if (seenStarts.has(matchStart)) continue;\n\n seenStarts.add(matchStart);\n const matchEnd = matchStart + match[0].length;\n locations.push(\n Location.create(\n pathToFileURL(filePath).href,\n offsetToRange(text, matchStart, matchEnd)\n )\n );\n }\n }\n });\n\n log(\n `getKeyUsageLocations(\"${key}\") — ${filePaths.length} file(s) searched in ${Date.now() - startTime}ms → ${locations.length} hit(s)`\n );\n return locations;\n};\n\n/**\n * Return every source-file location that accesses the field at `fieldPath` on\n * the result of `useIntlayer(dictionaryKey)` / `getIntlayer(dictionaryKey)`.\n *\n * `fieldPath` is an array of property names from the content root to the\n * target field (e.g. `['searchInput', 'text']` for a nested field).\n * Single-element paths (e.g. `['greet']`) behave like the previous\n * single-`fieldName` API.\n */\nconst getFieldUsageLocations = async (\n dictionaryKey: string,\n fieldPath: string[],\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config || fieldPath.length === 0) return [];\n\n const leafFieldName = fieldPath[fieldPath.length - 1]!;\n const escapedKey = escapeRegularExpression(dictionaryKey);\n const escapedLeaf = escapeRegularExpression(leafFieldName);\n const useCallRegularExpression = buildCallerWithKeyPattern(dictionaryKey);\n\n const filePaths = await getSourceFiles(config.system.baseDir, config);\n const locations: Location[] = [];\n const startTime = Date.now();\n\n await runConcurrent(filePaths, async (filePath) => {\n const text = await tryReadFile(filePath);\n\n if (!text) return;\n // Pre-filter: dictionaryKey and every segment of the path must appear in the file\n\n if (!text.includes(dictionaryKey)) return;\n\n if (fieldPath.some((segment) => !text.includes(segment))) return;\n\n if (!useCallRegularExpression.test(text)) return;\n\n for (const range of findFieldRangesInFile(\n text,\n escapedKey,\n leafFieldName,\n escapedLeaf,\n fieldPath\n )) {\n locations.push(Location.create(pathToFileURL(filePath).href, range));\n }\n });\n\n log(\n `getFieldUsageLocations(\"${dictionaryKey}.${fieldPath.join('.')}\") — ${filePaths.length} file(s) searched in ${Date.now() - startTime}ms → ${locations.length} hit(s)`\n );\n return locations;\n};\n\n/**\n * Find the line/column of the field's leaf name as a property key inside a\n * content file. Looks for `fieldName:` or `'field.name':` (flat keys with\n * dots, as used by lingui catalogs), not just any occurrence. Falls back to\n * the top of the file if the field cannot be located.\n */\nconst getFieldRangeInContentFile = async (\n absolutePath: string,\n fieldPath: string[]\n): Promise<Range> => {\n const content = await tryReadFile(absolutePath);\n const fieldName = fieldPath[fieldPath.length - 1];\n\n if (!content || !fieldName)\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n\n // Match `fieldName:` at an object property key position (not a value),\n // allowing an optional quoted form for keys containing dots or dashes.\n const fieldRegularExpression = new RegExp(\n `(?<![.\\\\w])(['\"\\`]?)${escapeRegularExpression(fieldName)}\\\\1\\\\s*:`\n );\n const lines = content.split(/\\r?\\n/);\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n const line = lines[lineIndex]!;\n const matchPosition = line.search(fieldRegularExpression);\n\n if (matchPosition === -1) continue;\n const column = line.indexOf(fieldName, matchPosition);\n\n if (column === -1) continue;\n return Range.create(\n Position.create(lineIndex, column),\n Position.create(lineIndex, column + fieldName.length)\n );\n }\n\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n};\n\n/**\n * Return every content-file location where the field at `fieldPath` is\n * defined as a property of the dictionary with the given `dictionaryKey`.\n */\nconst getContentFieldLocations = async (\n dictionaryKey: string,\n fieldPath: string[],\n absolutePath: string\n): Promise<Location[]> => {\n const config = getProjectConfig(absolutePath);\n\n if (!config) return [];\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter(\n (dictionary) => dictionary?.key === dictionaryKey && dictionary.filePath\n );\n\n const locations: Location[] = [];\n\n await runConcurrent(dictionaries, async (dictionary) => {\n const filePath = dictionary.filePath!;\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : join(config.system.baseDir, filePath);\n\n if (!existsSync(absolutePath)) return;\n\n locations.push(\n Location.create(\n pathToFileURL(absolutePath).href,\n await getFieldRangeInContentFile(absolutePath, fieldPath)\n )\n );\n });\n\n return locations;\n};\n\n// ---------------------------------------------------------------------------\n// Diagnostics — push-based (no capability needed)\n// ---------------------------------------------------------------------------\n\nconst DIAGNOSTIC_DEBOUNCE_MS = 300;\nconst diagnosticTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst computeDiagnostics = (\n text: string,\n config: WorkspaceConfig\n): Diagnostic[] => {\n const namespaceReferences = collectNamespaceReferences(text);\n\n if (namespaceReferences.length === 0) return [];\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const knownKeys = new Set(\n Object.values(unmergedDictionaries)\n .flat()\n .map((dictionary) => dictionary?.key)\n .filter((key): key is string => typeof key === 'string')\n );\n\n const diagnostics: Diagnostic[] = [];\n\n for (const reference of namespaceReferences) {\n if (knownKeys.has(reference.dictionaryKey)) continue;\n\n diagnostics.push({\n range: offsetToRange(\n text,\n reference.namespaceStart,\n reference.namespaceEnd\n ),\n severity: DiagnosticSeverity.Warning,\n source: 'intlayer',\n message: `Dictionary key \"${reference.dictionaryKey}\" is not declared in any content file`,\n });\n }\n\n return diagnostics;\n};\n\nconst sendDiagnosticsForDocument = (\n uri: string,\n text: string,\n version: number | null\n): void => {\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const config = getProjectConfig(absolutePath);\n\n const diagnostics =\n config && !isContentFile(uri, config)\n ? computeDiagnostics(text, config)\n : [];\n\n connection.sendDiagnostics({\n uri,\n version: version ?? undefined,\n diagnostics,\n });\n};\n\nconst scheduleDiagnostics = (\n uri: string,\n text: string,\n version: number | null\n): void => {\n const existing = diagnosticTimers.get(uri);\n\n if (existing !== undefined) clearTimeout(existing);\n\n diagnosticTimers.set(\n uri,\n setTimeout(() => {\n diagnosticTimers.delete(uri);\n sendDiagnosticsForDocument(uri, text, version);\n }, DIAGNOSTIC_DEBOUNCE_MS)\n );\n};\n\n// ---------------------------------------------------------------------------\n// Completion context detection\n// ---------------------------------------------------------------------------\n\ntype CompletionCtx =\n | { kind: 'key' }\n | { kind: 'field'; dictionaryKey: string; fieldPath: string[] }\n | null;\n\n/** Find the dictionary key for a simple variable assigned from any registered caller. */\nconst resolveVariableToDictionaryKey = (\n program: OxcNode,\n varName: string\n): string | null => {\n let result: string | null = null;\n\n walkAst(program, (node) => {\n if (result) return true;\n\n if (node['type'] !== 'VariableDeclarator') return;\n const id = node['id'] as OxcNode | undefined;\n\n if (id?.['type'] !== 'Identifier' || (id['name'] as string) !== varName)\n return;\n const init = node['init'] as OxcNode | undefined;\n\n if (!init) return;\n result = matchCallNamespace(init);\n return true;\n });\n return result;\n};\n\n/**\n * Detect what kind of completion is appropriate at the given offset.\n *\n * Handles:\n * - key completion: cursor inside first string arg of useIntlayer/getIntlayer\n * - field (member): cursor after `variable.` where variable came from useIntlayer\n * - field (destructure): cursor inside `const { | } = useIntlayer(\"key\")`\n */\nconst getCompletionContext = (\n textBefore: string,\n textAfter: string,\n program: OxcNode | null\n): CompletionCtx => {\n // Key completion\n\n if (\n new RegExp(\n `\\\\b(?:${getCallerNamesAlternation()})\\\\b(?:<[^<>]*>)?\\\\s*\\\\(\\\\s*['\"\\`][^'\"\\`\\\\n]*$`\n ).test(textBefore)\n ) {\n return { kind: 'key' };\n }\n\n // Field completion via member access: rootVar.path.to.\n const memberMatch = /\\b(\\w+)((?:\\.\\w+)*)\\.$/.exec(textBefore);\n\n if (memberMatch && program) {\n const rootVar = memberMatch[1]!;\n const chainStr = memberMatch[2]!;\n const dictKey = resolveVariableToDictionaryKey(program, rootVar);\n\n if (dictKey) {\n const fieldPath = chainStr ? chainStr.slice(1).split('.') : [];\n return { kind: 'field', dictionaryKey: dictKey, fieldPath };\n }\n }\n\n // Field completion inside destructuring: const { | } = useIntlayer(\"key\")\n\n if (/(?:const|let|var)\\s*\\{[^}]*$/.test(textBefore)) {\n const destructMatch = new RegExp(\n `^[^}]*\\\\}\\\\s*=\\\\s*(?:${getCallerNamesAlternation()})(?:<[^<>]*>)?\\\\s*\\\\(\\\\s*['\"\\`]([^'\"\\`\\\\n]+)['\"\\`]`\n ).exec(textAfter);\n\n if (destructMatch) {\n return { kind: 'field', dictionaryKey: destructMatch[1]!, fieldPath: [] };\n }\n }\n\n return null;\n};\n\nconnection.onInitialize((parameters: InitializeParams): InitializeResult => {\n workspaceRoot =\n parameters.rootPath ||\n (parameters.workspaceFolders?.[0]?.uri\n ? decodeURIComponent(new URL(parameters.workspaceFolders[0].uri).pathname)\n : undefined);\n invalidateConfigCaches();\n\n log(`initialized — workspaceRoot: ${workspaceRoot ?? '(none)'}`);\n\n if (parameters.workspaceFolders?.length) {\n log(\n `workspaceFolders: ${parameters.workspaceFolders.map((folder) => folder.name).join(', ')}`\n );\n }\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n definitionProvider: true,\n referencesProvider: true,\n hoverProvider: true,\n completionProvider: {\n triggerCharacters: ['\"', \"'\", '`', '.'],\n resolveProvider: false,\n },\n },\n };\n});\n\nconnection.onDefinition(async (parameters) => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const position = parameters.position;\n const document = documents.get(uri);\n\n if (!document) {\n log(`onDefinition — document not found in cache: ${uri}`);\n return null;\n }\n\n const text = document.getText();\n const offset = document.offsetAt(position);\n const shortenedUri = uri\n .replace(/^file:\\/\\//, '')\n .split('/')\n .slice(-2)\n .join('/');\n\n log(`onDefinition — ${shortenedUri} ${position.line}:${position.character}`);\n const config = getProjectConfig(absolutePath);\n\n if (config && isContentFile(uri, config)) {\n // Content-file → component navigation is handled exclusively by the VS Code\n // extension's intlayerContentDefinitionProvider (which returns DefinitionLink[]\n // with a proper originSelectionRange). Returning null here prevents the LSP\n // from emitting a duplicate set of Location[] results for the same request.\n log('onDefinition — content file: delegating to extension provider');\n return null;\n }\n\n // Cursor on the key string / function name → navigate to content declaration.\n const key = findKeyAtOffset(text, offset);\n\n if (key) {\n log(\n `onDefinition — useIntlayer key: \"${key}\" → searching content declarations`\n );\n const locations = await getContentDeclarationLocations(key, absolutePath);\n log(\n `onDefinition — found ${locations.length} content declaration(s) for key \"${key}\"`\n );\n return locations.length ? locations : null;\n }\n\n // Cursor on any message usage: destructured property, member access,\n // t('path.to.field') call, formatMessage({ id }), <Trans>, lingui t`…`, …\n const usage = findMessageUsageAtOffset(text, offset);\n\n if (usage && usage.fieldPath.length > 0) {\n log(\n `onDefinition — usage field: \"${usage.dictionaryKey}.${usage.fieldPath.join('.')}\" (${usage.callerName}) → searching content field`\n );\n const locations = await getContentFieldLocations(\n usage.dictionaryKey,\n usage.fieldPath,\n absolutePath\n );\n log(\n `onDefinition — found ${locations.length} content file(s) with field \"${usage.fieldPath.join('.')}\"`\n );\n return locations.length ? locations : null;\n }\n\n if (usage) {\n // Dictionary-level usage (e.g. bare content variable) → content files.\n const locations = await getContentDeclarationLocations(\n usage.dictionaryKey,\n absolutePath\n );\n return locations.length ? locations : null;\n }\n\n log('onDefinition — no Intlayer key at cursor, no result');\n return null;\n});\n\n/**\n * \"Find All References\" handler (Shift+F12).\n *\n * From a content file, returns every source-file location that uses the key or\n * field at the cursor — the reverse direction of Go-to-Definition.\n *\n * From a source file, returns all content-file declarations for the key or\n * field at the cursor — complementing what Go-to-Definition already handles.\n */\nconnection.onReferences(async (parameters: ReferenceParams) => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const position = parameters.position;\n const document = documents.get(uri);\n\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(position);\n const shortenedUri = uri\n .replace(/^file:\\/\\//, '')\n .split('/')\n .slice(-2)\n .join('/');\n\n log(`onReferences — ${shortenedUri} ${position.line}:${position.character}`);\n const config = getProjectConfig(absolutePath);\n\n if (config && isContentFile(uri, config)) {\n // Cursor on `key: \"…\"` → all useIntlayer(key) call sites\n const key = findKeyInContentFile(text, offset);\n\n if (key) {\n const locations = await getKeyUsageLocations(key, absolutePath);\n log(`onReferences — key \"${key}\" → ${locations.length} call site(s)`);\n return locations.length ? locations : null;\n }\n\n // Cursor on a content field → all field usage sites\n const extMatch = uri.match(/\\.[^.]+$/);\n const ext = extMatch ? extMatch[0] : '';\n const fieldInfo = findContentFieldAtOffset(text, offset, ext);\n\n if (fieldInfo) {\n const locations = await getFieldUsageLocations(\n fieldInfo.dictionaryKey,\n fieldInfo.fieldPath,\n absolutePath\n );\n log(\n `onReferences — field \"${fieldInfo.fieldPath.join('.')}\" → ${locations.length} usage(s)`\n );\n return locations.length ? locations : null;\n }\n\n return null;\n }\n\n // Source file: key string → content-file declarations\n const key = findKeyAtOffset(text, offset);\n\n if (key) {\n const locations = await getContentDeclarationLocations(key, absolutePath);\n log(`onReferences — key \"${key}\" → ${locations.length} declaration(s)`);\n return locations.length ? locations : null;\n }\n\n // Source file: field property, t('field') call, JSX message component, …\n // → content-file field definitions\n const usage = findMessageUsageAtOffset(text, offset);\n\n if (usage && usage.fieldPath.length > 0) {\n const locations = await getContentFieldLocations(\n usage.dictionaryKey,\n usage.fieldPath,\n absolutePath\n );\n log(\n `onReferences — field \"${usage.fieldPath.join('.')}\" → ${locations.length} content definition(s)`\n );\n return locations.length ? locations : null;\n }\n\n return null;\n});\n\n// ---------------------------------------------------------------------------\n// Hover\n// ---------------------------------------------------------------------------\n\nconnection.onHover(async (parameters: HoverParams): Promise<Hover | null> => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const document = documents.get(uri);\n\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(parameters.position);\n const config = getProjectConfig(absolutePath);\n\n if (!config) return null;\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n const getDicts = (key: string) =>\n Object.values(unmergedDictionaries)\n .flat()\n .filter((d) => d?.key === key);\n\n if (isContentFile(uri, config)) {\n // Cursor on `key: \"...\"` declaration\n const key = findKeyInContentFile(text, offset);\n\n if (key) {\n const dicts = getDicts(key);\n\n if (dicts.length > 0) {\n return {\n contents: {\n kind: MarkupKind.Markdown,\n value: formatDictionaryHover(dicts, key),\n },\n };\n }\n }\n\n // Cursor on a content field\n const ext = uri.match(/\\.[^.]+$/)?.[0] ?? '';\n const fieldInfo = findContentFieldAtOffset(text, offset, ext);\n\n if (fieldInfo) {\n const dicts = getDicts(fieldInfo.dictionaryKey);\n\n if (dicts.length > 0) {\n const hoverText = formatFieldHover(\n dicts,\n fieldInfo.dictionaryKey,\n fieldInfo.fieldPath\n );\n\n if (hoverText) {\n return {\n contents: { kind: MarkupKind.Markdown, value: hoverText },\n };\n }\n }\n }\n\n return null;\n }\n\n // Source file: cursor on key string inside useIntlayer(\"key\")\n const key = findKeyAtOffset(text, offset);\n\n if (key) {\n const dicts = getDicts(key);\n\n if (dicts.length > 0) {\n return {\n contents: {\n kind: MarkupKind.Markdown,\n value: formatDictionaryHover(dicts, key),\n },\n };\n }\n return null;\n }\n\n // Source file: cursor on any message usage (field property, t('field'),\n // formatMessage({ id }), <Trans>, lingui t`…`, …)\n const usage = findMessageUsageAtOffset(text, offset);\n\n if (usage) {\n const dicts = getDicts(usage.dictionaryKey);\n\n if (dicts.length > 0) {\n if (usage.fieldPath.length === 0) {\n return {\n contents: {\n kind: MarkupKind.Markdown,\n value: formatDictionaryHover(dicts, usage.dictionaryKey),\n },\n };\n }\n\n const hoverText = formatFieldHover(\n dicts,\n usage.dictionaryKey,\n usage.fieldPath\n );\n\n if (hoverText) {\n return {\n contents: { kind: MarkupKind.Markdown, value: hoverText },\n };\n }\n }\n }\n\n return null;\n});\n\n// ---------------------------------------------------------------------------\n// Completion\n// ---------------------------------------------------------------------------\n\nconnection.onCompletion(\n async (parameters: CompletionParams): Promise<CompletionItem[] | null> => {\n const uri = parameters.textDocument.uri;\n const absolutePath = decodeURIComponent(new URL(uri).pathname);\n const document = documents.get(uri);\n\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(parameters.position);\n const config = getProjectConfig(absolutePath);\n\n if (!config) return null;\n\n const program = parseText(text);\n const context = getCompletionContext(\n text.slice(0, offset),\n text.slice(offset),\n program\n );\n\n if (!context) return null;\n\n const unmergedDictionaries = getUnmergedDictionariesCached(config);\n\n if (context.kind === 'key') {\n const seen = new Set<string>();\n const items: CompletionItem[] = [];\n for (const dictionary of Object.values(unmergedDictionaries).flat()) {\n if (!dictionary?.key || seen.has(dictionary.key)) continue;\n\n seen.add(dictionary.key);\n items.push({\n label: dictionary.key,\n kind: CompletionItemKind.Value,\n detail: dictionary.title ?? undefined,\n documentation: dictionary.description\n ? { kind: MarkupKind.Markdown, value: dictionary.description }\n : undefined,\n });\n }\n log(`onCompletion — key: ${items.length} dictionary keys`);\n return items.length > 0 ? items : null;\n }\n\n if (context.kind === 'field') {\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter((dictionary) => dictionary?.key === context.dictionaryKey);\n\n if (dictionaries.length === 0) return null;\n\n const primary = dictionaries[0]!;\n const fields = getFieldsAtPath(primary.content, context.fieldPath);\n const items: CompletionItem[] = fields.map((fieldName) => {\n const value = getFieldByPath(primary.content, [\n ...context.fieldPath,\n fieldName,\n ]);\n return {\n label: fieldName,\n kind: CompletionItemKind.Field,\n detail: formatFieldValue(value),\n };\n });\n log(\n `onCompletion — field \"${context.dictionaryKey}.${context.fieldPath.join('.')}\": ${items.length} field(s)`\n );\n return items.length > 0 ? items : null;\n }\n\n return null;\n }\n);\n\n// ---------------------------------------------------------------------------\n// Push diagnostics\n// ---------------------------------------------------------------------------\n\ndocuments.onDidOpen((event) => {\n sendDiagnosticsForDocument(\n event.document.uri,\n event.document.getText(),\n event.document.version\n );\n});\n\ndocuments.onDidChangeContent((event) => {\n scheduleDiagnostics(\n event.document.uri,\n event.document.getText(),\n event.document.version\n );\n});\n\ndocuments.onDidClose((event) => {\n const uri = event.document.uri;\n const timer = diagnosticTimers.get(uri);\n\n if (timer !== undefined) {\n clearTimeout(timer);\n diagnosticTimers.delete(uri);\n }\n connection.sendDiagnostics({ uri, diagnostics: [] });\n});\n\n// Invalidate the source-file list whenever the client reports a file change.\n// The client registers a watcher for *.content.* files; any creation or\n// deletion means the project layout may have changed.\nconnection.onDidChangeWatchedFiles(() => {\n invalidateConfigCaches();\n log(\n 'watched files changed — source file list and dictionary cache invalidated'\n );\n\n // Diagnostics are only recomputed on open/change, so a key reported as\n // undeclared would stay flagged after its content file (or the built\n // dictionaries) appear. Re-publish for every open document.\n for (const document of documents.all()) {\n scheduleDiagnostics(document.uri, document.getText(), document.version);\n }\n});\n\n// Make the text document manager listen on the connection\n// for open, change and close text document events\ndocuments.listen(connection);\n\n// Listen on the connection\nconnection.listen();\n"],"mappings":"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"}
|
|
@@ -139,6 +139,54 @@ const collectImportedLocalNames = (program) => {
|
|
|
139
139
|
}
|
|
140
140
|
return localNameToSource;
|
|
141
141
|
};
|
|
142
|
+
/** Node types that open a new binding scope. */
|
|
143
|
+
const SCOPE_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
144
|
+
"FunctionDeclaration",
|
|
145
|
+
"FunctionExpression",
|
|
146
|
+
"ArrowFunctionExpression",
|
|
147
|
+
"ClassMethod",
|
|
148
|
+
"ObjectMethod",
|
|
149
|
+
"StaticBlock",
|
|
150
|
+
"Program"
|
|
151
|
+
]);
|
|
152
|
+
/**
|
|
153
|
+
* Span of the nearest enclosing function — the region a declaration is
|
|
154
|
+
* visible in. Falls back to the whole program for module-level declarations.
|
|
155
|
+
*/
|
|
156
|
+
const getEnclosingScope = (state, node) => {
|
|
157
|
+
let current = node;
|
|
158
|
+
while (current) {
|
|
159
|
+
if (SCOPE_NODE_TYPES.has(current["type"])) return {
|
|
160
|
+
scopeStart: nodeStart(current),
|
|
161
|
+
scopeEnd: nodeEnd(current)
|
|
162
|
+
};
|
|
163
|
+
current = state.parentMap.get(current);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
scopeStart: nodeStart(state.program),
|
|
167
|
+
scopeEnd: nodeEnd(state.program)
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
const addBinding = (bindings, variableName, binding) => {
|
|
171
|
+
const existing = bindings.get(variableName);
|
|
172
|
+
if (existing) existing.push(binding);
|
|
173
|
+
else bindings.set(variableName, [binding]);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* The binding a variable resolves to at `offset`: the innermost scope that
|
|
177
|
+
* contains the offset, among declarations that precede it.
|
|
178
|
+
*/
|
|
179
|
+
const resolveBinding = (bindings, variableName, offset) => {
|
|
180
|
+
const candidates = bindings.get(variableName);
|
|
181
|
+
if (!candidates) return void 0;
|
|
182
|
+
let best;
|
|
183
|
+
for (const candidate of candidates) {
|
|
184
|
+
if (offset < candidate.scopeStart || offset > candidate.scopeEnd) continue;
|
|
185
|
+
if (offset < candidate.declarationEnd) continue;
|
|
186
|
+
if (!best || candidate.scopeStart > best.scopeStart || candidate.scopeStart === best.scopeStart && candidate.declarationEnd > best.declarationEnd) best = candidate;
|
|
187
|
+
}
|
|
188
|
+
return best;
|
|
189
|
+
};
|
|
142
190
|
/**
|
|
143
191
|
* Find the descriptor matching a call (or tagged template) callee among the
|
|
144
192
|
* active descriptors.
|
|
@@ -165,7 +213,7 @@ const getModuleSource = (state, descriptor) => state.importedLocalNames.get(desc
|
|
|
165
213
|
* → usages: ['searchInput'], ['searchInput', 'text']
|
|
166
214
|
* → binding: alias → ['searchInput', 'text']
|
|
167
215
|
*/
|
|
168
|
-
const collectContentDestructure = (state, pattern, dictionaryKey, basePath, descriptor, moduleSource) => {
|
|
216
|
+
const collectContentDestructure = (state, pattern, dictionaryKey, basePath, descriptor, moduleSource, scope) => {
|
|
169
217
|
for (const property of pattern["properties"] ?? []) {
|
|
170
218
|
const propertyType = property["type"];
|
|
171
219
|
if (propertyType !== "Property" && propertyType !== "ObjectProperty") continue;
|
|
@@ -185,13 +233,14 @@ const collectContentDestructure = (state, pattern, dictionaryKey, basePath, desc
|
|
|
185
233
|
});
|
|
186
234
|
let valueNode = property["value"];
|
|
187
235
|
if (valueNode?.["type"] === "AssignmentPattern") valueNode = valueNode["left"];
|
|
188
|
-
if (valueNode?.["type"] === "Identifier") state.contentBindings
|
|
236
|
+
if (valueNode?.["type"] === "Identifier") addBinding(state.contentBindings, valueNode["name"], {
|
|
189
237
|
dictionaryKey,
|
|
190
238
|
basePath: fieldPath,
|
|
191
239
|
descriptor,
|
|
192
|
-
moduleSource
|
|
240
|
+
moduleSource,
|
|
241
|
+
...scope
|
|
193
242
|
});
|
|
194
|
-
else if (valueNode?.["type"] === "ObjectPattern") collectContentDestructure(state, valueNode, dictionaryKey, fieldPath, descriptor, moduleSource);
|
|
243
|
+
else if (valueNode?.["type"] === "ObjectPattern") collectContentDestructure(state, valueNode, dictionaryKey, fieldPath, descriptor, moduleSource, scope);
|
|
195
244
|
}
|
|
196
245
|
};
|
|
197
246
|
/**
|
|
@@ -237,26 +286,32 @@ const collectBindings = (state, program) => {
|
|
|
237
286
|
const moduleSource = getModuleSource(state, descriptor);
|
|
238
287
|
const idNode = node["id"];
|
|
239
288
|
if (!idNode) return;
|
|
289
|
+
const scope = {
|
|
290
|
+
...getEnclosingScope(state, node),
|
|
291
|
+
declarationEnd: nodeEnd(node)
|
|
292
|
+
};
|
|
240
293
|
if (idNode["type"] === "Identifier") {
|
|
241
294
|
const variableName = idNode["name"];
|
|
242
|
-
if (descriptor.translationFunction === "return-value") state.translatorBindings
|
|
295
|
+
if (descriptor.translationFunction === "return-value") addBinding(state.translatorBindings, variableName, {
|
|
243
296
|
dictionaryKey,
|
|
244
297
|
keyPrefix,
|
|
245
298
|
descriptor,
|
|
246
|
-
moduleSource
|
|
299
|
+
moduleSource,
|
|
300
|
+
...scope
|
|
247
301
|
});
|
|
248
|
-
else if (descriptor.translationFunction === "content" && dictionaryKey) state.contentBindings
|
|
302
|
+
else if (descriptor.translationFunction === "content" && dictionaryKey) addBinding(state.contentBindings, variableName, {
|
|
249
303
|
dictionaryKey,
|
|
250
304
|
basePath: keyPrefix,
|
|
251
305
|
descriptor,
|
|
252
|
-
moduleSource
|
|
306
|
+
moduleSource,
|
|
307
|
+
...scope
|
|
253
308
|
});
|
|
254
309
|
return;
|
|
255
310
|
}
|
|
256
311
|
if (idNode["type"] !== "ObjectPattern") return;
|
|
257
312
|
if (descriptor.translationFunction === "content") {
|
|
258
313
|
if (!dictionaryKey) return;
|
|
259
|
-
collectContentDestructure(state, idNode, dictionaryKey, keyPrefix, descriptor, moduleSource);
|
|
314
|
+
collectContentDestructure(state, idNode, dictionaryKey, keyPrefix, descriptor, moduleSource, scope);
|
|
260
315
|
return;
|
|
261
316
|
}
|
|
262
317
|
if (descriptor.translationFunction !== "destructured-t") return;
|
|
@@ -268,11 +323,12 @@ const collectBindings = (state, program) => {
|
|
|
268
323
|
const fieldName = getPropertyKeyName(keyNode);
|
|
269
324
|
const localName = valueNode?.["type"] === "Identifier" ? valueNode["name"] : null;
|
|
270
325
|
if (fieldName !== "t" || !localName) continue;
|
|
271
|
-
state.translatorBindings
|
|
326
|
+
addBinding(state.translatorBindings, localName, {
|
|
272
327
|
dictionaryKey,
|
|
273
328
|
keyPrefix,
|
|
274
329
|
descriptor,
|
|
275
|
-
moduleSource
|
|
330
|
+
moduleSource,
|
|
331
|
+
...scope
|
|
276
332
|
});
|
|
277
333
|
}
|
|
278
334
|
});
|
|
@@ -290,7 +346,7 @@ const collectCallUsages = (state, program) => {
|
|
|
290
346
|
if (node["type"] !== "CallExpression") return;
|
|
291
347
|
const callee = node["callee"];
|
|
292
348
|
if (callee?.["type"] === "Identifier") {
|
|
293
|
-
const binding = state.translatorBindings
|
|
349
|
+
const binding = resolveBinding(state.translatorBindings, callee["name"], nodeStart(node));
|
|
294
350
|
if (binding) {
|
|
295
351
|
const argumentsList = node["arguments"];
|
|
296
352
|
const messageId = getStaticStringValue(argumentsList?.[0]);
|
|
@@ -388,7 +444,7 @@ const getJsxAttributeString = (openingElement, attributeName) => {
|
|
|
388
444
|
* `<Trans i18nKey="richText" />`, `<Trans id="…" />`.
|
|
389
445
|
*/
|
|
390
446
|
const collectJsxUsages = (state, program) => {
|
|
391
|
-
const translatorNamespaces = new Set([...state.translatorBindings.values()].map((binding) => binding.dictionaryKey).filter((dictionaryKey) => dictionaryKey !== null));
|
|
447
|
+
const translatorNamespaces = new Set([...state.translatorBindings.values()].flat().map((binding) => binding.dictionaryKey).filter((dictionaryKey) => dictionaryKey !== null));
|
|
392
448
|
const fallbackNamespace = translatorNamespaces.size === 1 ? [...translatorNamespaces][0] : null;
|
|
393
449
|
walkAst(program, (node) => {
|
|
394
450
|
if (node["type"] !== "JSXOpeningElement") return;
|
|
@@ -454,7 +510,7 @@ const collectMemberUsages = (state, program, parentMap) => {
|
|
|
454
510
|
if (nodeType !== "Identifier" && nodeType !== "JSXIdentifier") return;
|
|
455
511
|
const rawName = node["name"];
|
|
456
512
|
const variableName = rawName.startsWith("$") ? rawName.slice(1) : rawName;
|
|
457
|
-
const binding = state.contentBindings
|
|
513
|
+
const binding = resolveBinding(state.contentBindings, variableName, nodeStart(node));
|
|
458
514
|
if (!binding) return;
|
|
459
515
|
const parentType = parentMap.get(node)?.["type"];
|
|
460
516
|
if (parentType?.startsWith("TS") || parentType === "JSXAttribute") return;
|
|
@@ -530,17 +586,19 @@ const collectMessageUsages = (text) => {
|
|
|
530
586
|
const program = parseText(text);
|
|
531
587
|
if (!program) return [];
|
|
532
588
|
const fileImportSources = collectImportSources(program);
|
|
589
|
+
const parentMap = buildParentMap(program);
|
|
533
590
|
const state = {
|
|
534
591
|
usages: [],
|
|
535
592
|
translatorBindings: /* @__PURE__ */ new Map(),
|
|
536
593
|
contentBindings: /* @__PURE__ */ new Map(),
|
|
537
594
|
activeDescriptors: callers_exports.ALL_CALLERS.filter((descriptor) => (0, callers_exports.isCallerActive)(descriptor, fileImportSources)),
|
|
538
|
-
importedLocalNames: collectImportedLocalNames(program)
|
|
595
|
+
importedLocalNames: collectImportedLocalNames(program),
|
|
596
|
+
parentMap,
|
|
597
|
+
program
|
|
539
598
|
};
|
|
540
599
|
collectBindings(state, program);
|
|
541
600
|
collectCallUsages(state, program);
|
|
542
601
|
collectJsxUsages(state, program);
|
|
543
|
-
const parentMap = buildParentMap(program);
|
|
544
602
|
collectMemberUsages(state, program, parentMap);
|
|
545
603
|
return state.usages.sort((first, second) => first.start - second.start);
|
|
546
604
|
};
|
|
@@ -638,24 +696,26 @@ const collectCallerBindings = (text) => {
|
|
|
638
696
|
translatorBindings: /* @__PURE__ */ new Map(),
|
|
639
697
|
contentBindings: /* @__PURE__ */ new Map(),
|
|
640
698
|
activeDescriptors: callers_exports.ALL_CALLERS.filter((descriptor) => (0, callers_exports.isCallerActive)(descriptor, fileImportSources)),
|
|
641
|
-
importedLocalNames: collectImportedLocalNames(program)
|
|
699
|
+
importedLocalNames: collectImportedLocalNames(program),
|
|
700
|
+
parentMap: buildParentMap(program),
|
|
701
|
+
program
|
|
642
702
|
};
|
|
643
703
|
collectBindings(state, program);
|
|
644
|
-
return [...[...state.contentBindings.entries()].
|
|
704
|
+
return [...[...state.contentBindings.entries()].flatMap(([variableName, bindings]) => bindings.map((binding) => ({
|
|
645
705
|
variableName,
|
|
646
706
|
dictionaryKey: binding.dictionaryKey,
|
|
647
707
|
basePath: binding.basePath,
|
|
648
708
|
bindingKind: "content",
|
|
649
709
|
callerName: binding.descriptor.callerName,
|
|
650
710
|
library: binding.descriptor.library
|
|
651
|
-
})), ...[...state.translatorBindings.entries()].
|
|
711
|
+
}))), ...[...state.translatorBindings.entries()].flatMap(([variableName, bindings]) => bindings.filter((binding) => binding.dictionaryKey !== null).map((binding) => ({
|
|
652
712
|
variableName,
|
|
653
713
|
dictionaryKey: binding.dictionaryKey,
|
|
654
714
|
basePath: binding.keyPrefix,
|
|
655
715
|
bindingKind: "translator",
|
|
656
716
|
callerName: binding.descriptor.callerName,
|
|
657
717
|
library: binding.descriptor.library
|
|
658
|
-
}))];
|
|
718
|
+
})))];
|
|
659
719
|
};
|
|
660
720
|
/**
|
|
661
721
|
* Find the most specific translation usage at `offset`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usageAnalyzer.mjs","names":["ALL_CALLERS"],"sources":["../../src/usageAnalyzer.ts"],"sourcesContent":["import {\n ALL_CALLERS,\n type CallerDescriptor,\n type CallerValueSource,\n isCallerActive,\n} from './callers';\nimport {\n buildParentMap,\n getObjectPropertyNode,\n getObjectPropertyString,\n getPropertyKeyName,\n getStaticStringValue,\n nodeEnd,\n nodeStart,\n type OxcNode,\n parseText,\n walkAst,\n} from './oxcUtils';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** How a message usage appears in source code. */\nexport type MessageUsageKind =\n /** Translation-function or self call: `t('title')`, `formatMessage({ id })`. */\n | 'call'\n /** Member access / bare reference on a content object: `content.title`. */\n | 'member'\n /** Destructured content field: `const { title } = useIntlayer('home')`. */\n | 'destructure'\n /** JSX message component: `<FormattedMessage id=\"home.title\" />`. */\n | 'jsx'\n /** Tagged template: lingui's ``t`Hello` ``. */\n | 'tagged-template'\n /** The caller call itself (dictionary-level usage): `useIntlayer('home')`. */\n | 'namespace';\n\n/**\n * One resolved translation usage inside a source file: which dictionary and\n * field it reads, and the text span it occupies.\n */\nexport type MessageUsage = {\n /** The intlayer dictionary key the usage reads from. */\n dictionaryKey: string;\n /**\n * Path of content fields from the dictionary root. Empty for\n * dictionary-level usages (`namespace` kind, or a bare content variable).\n */\n fieldPath: string[];\n /**\n * Spans of each `fieldPath` segment, aligned by index, when the segments\n * are addressable in source (member chains). Used to trim the path to the\n * segment under the cursor.\n */\n fieldSpans?: { start: number; end: number }[];\n /** Start offset of the usage expression. */\n start: number;\n /** End offset of the usage expression. */\n end: number;\n kind: MessageUsageKind;\n /** The caller that produced the usage, e.g. `'useTranslations'`. */\n callerName: string;\n /** The library the caller belongs to, e.g. `'next-intl'` or `'intlayer'`. */\n library: string;\n /** Module specifier the caller was imported from, when resolvable. */\n moduleSource?: string;\n /**\n * `true` for `member` usages that are a bare reference to a bound variable\n * (`{greet}` in JSX after `const { greet } = useIntlayer(…)`) — the field\n * name itself is not spelled at the usage site. Excluded from\n * find-references results but kept for decorations and hover.\n */\n isBareReference?: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Internal binding models\n// ---------------------------------------------------------------------------\n\n/**\n * Variable holding a translation function (`t`) with a bound namespace.\n * `dictionaryKey` is `null` for root-scope translators (`useTranslations()`\n * without a namespace) — the dictionary is then the first dot-segment of\n * each message id.\n */\ntype TranslatorBinding = {\n dictionaryKey: string | null;\n keyPrefix: string[];\n descriptor: CallerDescriptor;\n moduleSource?: string;\n};\n\n/** Variable holding dictionary content (or a field subtree of it). */\ntype ContentBinding = {\n dictionaryKey: string;\n basePath: string[];\n descriptor: CallerDescriptor;\n moduleSource?: string;\n};\n\n/**\n * Property names that are framework accessors rather than content fields\n * (react `.value`, vue refs `.value`, editor `.raw`/`.use`): they are skipped\n * when building field paths.\n */\nconst ACCESSOR_PROPERTY_NAMES = new Set(['value', 'raw', 'use']);\n\n// ---------------------------------------------------------------------------\n// Match helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a value source against a call expression's arguments.\n */\nconst readValueSource = (\n callNode: OxcNode,\n source: CallerValueSource\n): string | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n\n switch (source.from) {\n case 'argument':\n return getStaticStringValue(argumentsList?.[source.index]);\n case 'option':\n return getObjectPropertyString(\n argumentsList?.[source.argumentIndex],\n source.property\n );\n case 'fixed':\n return source.value;\n case 'path-first-segment':\n // Resolved from the message id at usage time, not from the call shape.\n return null;\n }\n};\n\n/** Read the first matching value from a list of sources. */\nconst readValueSources = (\n callNode: OxcNode,\n sources: CallerValueSource[] | undefined\n): string | null => {\n for (const source of sources ?? []) {\n const value = readValueSource(callNode, source);\n\n if (value !== null) return value;\n }\n\n return null;\n};\n\n/**\n * Apply nested-namespace semantics: for descriptors flagged\n * `nestedNamespace`, a dotted namespace is split at the first `.` into the\n * dictionary key and a key-prefix path (next-intl:\n * `useTranslations('about.counter')` → dictionary `about`, prefix\n * `['counter']`).\n */\nconst splitNestedNamespace = (\n descriptor: CallerDescriptor,\n rawNamespace: string\n): { dictionaryKey: string; prefixPath: string[] } => {\n if (!descriptor.nestedNamespace || !rawNamespace.includes('.')) {\n return { dictionaryKey: rawNamespace, prefixPath: [] };\n }\n\n const segments = rawNamespace.split('.');\n\n return { dictionaryKey: segments[0]!, prefixPath: segments.slice(1) };\n};\n\n/**\n * Return the caller name matched by a call/tagged-template callee: the bare\n * identifier name, or the property name of a member expression when the\n * descriptor allows method matching.\n */\nconst getCalleeName = (\n calleeNode: OxcNode | undefined\n): { name: string; isMethod: boolean } | null => {\n if (!calleeNode) return null;\n\n if (calleeNode['type'] === 'Identifier') {\n return { name: calleeNode['name'] as string, isMethod: false };\n }\n\n if (\n calleeNode['type'] === 'MemberExpression' &&\n calleeNode['computed'] !== true\n ) {\n const propertyNode = calleeNode['property'] as OxcNode | undefined;\n\n if (propertyNode?.['type'] === 'Identifier') {\n return { name: propertyNode['name'] as string, isMethod: true };\n }\n }\n\n return null;\n};\n\n/**\n * The message id of a self caller: either the first string argument or the\n * `id` property of a descriptor-object first argument\n * (`formatMessage({ id: 'home.title' })`, `i18n._({ id: '…' })`).\n */\nconst getSelfCallMessageId = (callNode: OxcNode): string | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n const firstArgument = argumentsList?.[0];\n\n if (!firstArgument) return null;\n\n return (\n getStaticStringValue(firstArgument) ??\n getObjectPropertyString(firstArgument, 'id')\n );\n};\n\n/**\n * Split a message id into dictionary key + field path according to the\n * descriptor semantics.\n */\nconst resolveMessageId = (\n descriptor: CallerDescriptor,\n fixedDictionaryKey: string | null,\n messageId: string\n): { dictionaryKey: string; fieldPath: string[] } | null => {\n if (fixedDictionaryKey !== null) {\n return {\n dictionaryKey: fixedDictionaryKey,\n fieldPath: descriptor.flatKey ? [messageId] : messageId.split('.'),\n };\n }\n\n const usesPathFirstSegment = descriptor.namespaceSources.some(\n (source) => source.from === 'path-first-segment'\n );\n\n if (!usesPathFirstSegment) return null;\n\n const segments = messageId.split('.');\n\n if (!segments[0]) return null;\n\n return { dictionaryKey: segments[0], fieldPath: segments.slice(1) };\n};\n\n/**\n * i18next-style `ns:key` ids override the bound namespace:\n * `t('about:title')` reads the `about` dictionary even when the translator\n * was created with another namespace.\n */\nconst splitNamespacePrefixedId = (\n messageId: string\n): { namespaceOverride: string | null; messagePath: string } => {\n const separatorIndex = messageId.indexOf(':');\n\n if (separatorIndex <= 0) {\n return { namespaceOverride: null, messagePath: messageId };\n }\n\n return {\n namespaceOverride: messageId.slice(0, separatorIndex),\n messagePath: messageId.slice(separatorIndex + 1),\n };\n};\n\n// ---------------------------------------------------------------------------\n// Analyzer\n// ---------------------------------------------------------------------------\n\n/** Unwrap `await expression` down to the awaited expression. */\nconst unwrapAwait = (node: OxcNode | undefined): OxcNode | undefined =>\n node?.['type'] === 'AwaitExpression'\n ? (node['argument'] as OxcNode | undefined)\n : node;\n\n/** Collect every module specifier imported by the program. */\nconst collectImportSources = (program: OxcNode): Set<string> => {\n const importSources = new Set<string>();\n\n for (const statement of (program['body'] as OxcNode[]) ?? []) {\n if (statement['type'] !== 'ImportDeclaration') continue;\n const sourceValue = (statement['source'] as OxcNode | undefined)?.['value'];\n\n if (typeof sourceValue === 'string') importSources.add(sourceValue);\n }\n\n return importSources;\n};\n\n/** Map each imported local name to its module specifier. */\nconst collectImportedLocalNames = (program: OxcNode): Map<string, string> => {\n const localNameToSource = new Map<string, string>();\n\n for (const statement of (program['body'] as OxcNode[]) ?? []) {\n if (statement['type'] !== 'ImportDeclaration') continue;\n const sourceValue = (statement['source'] as OxcNode | undefined)?.['value'];\n\n if (typeof sourceValue !== 'string') continue;\n\n for (const specifier of (statement['specifiers'] as OxcNode[]) ?? []) {\n const localName = (specifier['local'] as OxcNode | undefined)?.['name'];\n\n if (typeof localName === 'string') {\n localNameToSource.set(localName, sourceValue);\n }\n }\n }\n\n return localNameToSource;\n};\n\ntype AnalyzerState = {\n usages: MessageUsage[];\n translatorBindings: Map<string, TranslatorBinding>;\n contentBindings: Map<string, ContentBinding>;\n activeDescriptors: CallerDescriptor[];\n importedLocalNames: Map<string, string>;\n};\n\n/**\n * Find the descriptor matching a call (or tagged template) callee among the\n * active descriptors.\n */\nconst matchDescriptorForCallee = (\n state: AnalyzerState,\n calleeNode: OxcNode | undefined,\n filter?: (descriptor: CallerDescriptor) => boolean\n): CallerDescriptor | null => {\n const callee = getCalleeName(calleeNode);\n\n if (!callee) return null;\n\n for (const descriptor of state.activeDescriptors) {\n if (descriptor.callerName !== callee.name) continue;\n\n if (callee.isMethod && !descriptor.matchAsMethod) continue;\n\n if (filter && !filter(descriptor)) continue;\n\n return descriptor;\n }\n\n return null;\n};\n\n/** The module specifier a caller local name was imported from, if any. */\nconst getModuleSource = (\n state: AnalyzerState,\n descriptor: CallerDescriptor\n): string | undefined =>\n state.importedLocalNames.get(descriptor.callerName) ?? undefined;\n\n/**\n * Recursively register a content destructure pattern: each property key is a\n * field usage, each bound identifier becomes a content binding, and nested\n * patterns extend the field path.\n *\n * const { searchInput: { text: alias } } = useIntlayer('key')\n * → usages: ['searchInput'], ['searchInput', 'text']\n * → binding: alias → ['searchInput', 'text']\n */\nconst collectContentDestructure = (\n state: AnalyzerState,\n pattern: OxcNode,\n dictionaryKey: string,\n basePath: string[],\n descriptor: CallerDescriptor,\n moduleSource: string | undefined\n): void => {\n for (const property of (pattern['properties'] as OxcNode[]) ?? []) {\n const propertyType = property['type'] as string;\n\n if (propertyType !== 'Property' && propertyType !== 'ObjectProperty') {\n continue;\n }\n\n const keyNode = property['key'] as OxcNode | undefined;\n const fieldName = getPropertyKeyName(keyNode);\n\n if (!keyNode || !fieldName) continue;\n\n const fieldPath = [...basePath, fieldName];\n\n state.usages.push({\n dictionaryKey,\n fieldPath,\n start: nodeStart(keyNode),\n end: nodeEnd(keyNode),\n kind: 'destructure',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource,\n });\n\n // Unwrap default values: { title = fallback }\n let valueNode = property['value'] as OxcNode | undefined;\n\n if (valueNode?.['type'] === 'AssignmentPattern') {\n valueNode = valueNode['left'] as OxcNode | undefined;\n }\n\n if (valueNode?.['type'] === 'Identifier') {\n state.contentBindings.set(valueNode['name'] as string, {\n dictionaryKey,\n basePath: fieldPath,\n descriptor,\n moduleSource,\n });\n } else if (valueNode?.['type'] === 'ObjectPattern') {\n collectContentDestructure(\n state,\n valueNode,\n dictionaryKey,\n fieldPath,\n descriptor,\n moduleSource\n );\n }\n }\n};\n\n/**\n * Register bindings created by `const … = caller(…)` declarations, and record\n * dictionary-level (`namespace`) usages for every matched caller call.\n */\nconst collectBindings = (state: AnalyzerState, program: OxcNode): void => {\n walkAst(program, (node) => {\n if (node['type'] === 'CallExpression') {\n const descriptor = matchDescriptorForCallee(\n state,\n node['callee'] as OxcNode | undefined,\n (candidate) => candidate.translationFunction !== 'self'\n );\n\n if (descriptor) {\n const rawNamespace = readValueSources(\n node,\n descriptor.namespaceSources\n );\n\n if (rawNamespace) {\n const { dictionaryKey } = splitNestedNamespace(\n descriptor,\n rawNamespace\n );\n\n state.usages.push({\n dictionaryKey,\n fieldPath: [],\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'namespace',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n }\n }\n return;\n }\n\n if (node['type'] !== 'VariableDeclarator') return;\n\n const initializer = unwrapAwait(node['init'] as OxcNode | undefined);\n\n if (initializer?.['type'] !== 'CallExpression') return;\n\n const descriptor = matchDescriptorForCallee(\n state,\n initializer['callee'] as OxcNode | undefined,\n (candidate) => candidate.translationFunction !== 'self'\n );\n\n if (!descriptor) return;\n\n const rawNamespace = readValueSources(\n initializer,\n descriptor.namespaceSources\n );\n\n // Root scope: `useTranslations()` — the dictionary comes from each id.\n if (!rawNamespace && !descriptor.allowRootScope) return;\n\n const namespaceSplit = rawNamespace\n ? splitNestedNamespace(descriptor, rawNamespace)\n : { dictionaryKey: null, prefixPath: [] };\n\n const keyPrefixValue = readValueSources(\n initializer,\n descriptor.keyPrefixSources\n );\n const keyPrefix = [\n ...namespaceSplit.prefixPath,\n ...(keyPrefixValue ? keyPrefixValue.split('.') : []),\n ];\n const dictionaryKey = namespaceSplit.dictionaryKey;\n const moduleSource = getModuleSource(state, descriptor);\n const idNode = node['id'] as OxcNode | undefined;\n\n if (!idNode) return;\n\n // const t = useTranslations('ns') / const content = useIntlayer('key')\n if (idNode['type'] === 'Identifier') {\n const variableName = idNode['name'] as string;\n\n if (descriptor.translationFunction === 'return-value') {\n state.translatorBindings.set(variableName, {\n dictionaryKey,\n keyPrefix,\n descriptor,\n moduleSource,\n });\n } else if (\n descriptor.translationFunction === 'content' &&\n dictionaryKey\n ) {\n state.contentBindings.set(variableName, {\n dictionaryKey,\n basePath: keyPrefix,\n descriptor,\n moduleSource,\n });\n }\n return;\n }\n\n // const { t } = useTranslation('ns') / const { title } = useIntlayer('key')\n if (idNode['type'] !== 'ObjectPattern') return;\n\n if (descriptor.translationFunction === 'content') {\n if (!dictionaryKey) return;\n\n collectContentDestructure(\n state,\n idNode,\n dictionaryKey,\n keyPrefix,\n descriptor,\n moduleSource\n );\n return;\n }\n\n if (descriptor.translationFunction !== 'destructured-t') return;\n\n for (const property of (idNode['properties'] as OxcNode[]) ?? []) {\n const propertyType = property['type'] as string;\n\n if (propertyType !== 'Property' && propertyType !== 'ObjectProperty') {\n continue;\n }\n\n const keyNode = property['key'] as OxcNode | undefined;\n const valueNode = property['value'] as OxcNode | undefined;\n const fieldName = getPropertyKeyName(keyNode);\n const localName =\n valueNode?.['type'] === 'Identifier'\n ? (valueNode['name'] as string)\n : null;\n\n // Only the `t` property carries the translation function.\n if (fieldName !== 't' || !localName) continue;\n\n state.translatorBindings.set(localName, {\n dictionaryKey,\n keyPrefix,\n descriptor,\n moduleSource,\n });\n }\n });\n};\n\n/**\n * Record `t('path.to.field')` calls on translator bindings, and self-caller\n * calls (`formatMessage({ id })`, `i18n._('id')`).\n */\nconst collectCallUsages = (state: AnalyzerState, program: OxcNode): void => {\n walkAst(program, (node) => {\n if (node['type'] === 'TaggedTemplateExpression') {\n collectTaggedTemplateUsage(state, node);\n return;\n }\n\n if (node['type'] !== 'CallExpression') return;\n\n const callee = node['callee'] as OxcNode | undefined;\n\n // Translator binding call: t('path.to.field')\n if (callee?.['type'] === 'Identifier') {\n const binding = state.translatorBindings.get(callee['name'] as string);\n\n if (binding) {\n const argumentsList = node['arguments'] as OxcNode[] | undefined;\n const messageId = getStaticStringValue(argumentsList?.[0]);\n\n if (messageId) {\n const { namespaceOverride, messagePath } =\n splitNamespacePrefixedId(messageId);\n let dictionaryKey = namespaceOverride ?? binding.dictionaryKey;\n let fieldPath = namespaceOverride\n ? messagePath.split('.')\n : [...binding.keyPrefix, ...messagePath.split('.')];\n\n // Root-scope translator (`useTranslations()`): the first id\n // segment is the dictionary key.\n if (dictionaryKey === null) {\n const segments = messagePath.split('.');\n dictionaryKey = segments[0] ?? null;\n fieldPath = segments.slice(1);\n }\n\n if (dictionaryKey) {\n state.usages.push({\n dictionaryKey,\n fieldPath,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'call',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n moduleSource: binding.moduleSource,\n });\n }\n }\n return;\n }\n }\n\n // Self caller: the call itself carries the message id.\n const descriptor = matchDescriptorForCallee(\n state,\n callee,\n (candidate) => candidate.translationFunction === 'self'\n );\n\n if (!descriptor) return;\n\n const messageId = getSelfCallMessageId(node);\n\n if (!messageId) return;\n\n const fixedDictionaryKey = readValueSources(\n node,\n descriptor.namespaceSources\n );\n const resolved = resolveMessageId(\n descriptor,\n fixedDictionaryKey,\n messageId\n );\n\n if (!resolved) return;\n\n state.usages.push({\n ...resolved,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'call',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n });\n};\n\n/**\n * Lingui macro tagged template: ``t`Hello ${name}` `` — the catalog id is the\n * template text with `{placeholder}` slots for each expression.\n */\nconst collectTaggedTemplateUsage = (\n state: AnalyzerState,\n node: OxcNode\n): void => {\n const descriptor = matchDescriptorForCallee(\n state,\n node['tag'] as OxcNode | undefined,\n (candidate) => candidate.matchAsTaggedTemplate === true\n );\n\n if (!descriptor) return;\n\n const quasiNode = node['quasi'] as OxcNode | undefined;\n const quasis = (quasiNode?.['quasis'] as OxcNode[]) ?? [];\n const expressions = (quasiNode?.['expressions'] as OxcNode[]) ?? [];\n\n let messageId = '';\n\n for (let index = 0; index < quasis.length; index++) {\n const valueNode = quasis[index]?.['value'] as OxcNode | undefined;\n messageId +=\n (valueNode?.['cooked'] as string) ?? (valueNode?.['raw'] as string) ?? '';\n\n if (index < expressions.length) {\n const expression = expressions[index]!;\n const placeholder =\n expression['type'] === 'Identifier'\n ? (expression['name'] as string)\n : String(index);\n messageId += `{${placeholder}}`;\n }\n }\n\n if (!messageId) return;\n\n const fixedDictionaryKey = readValueSources(\n node,\n descriptor.namespaceSources\n );\n const resolved = resolveMessageId(descriptor, fixedDictionaryKey, messageId);\n\n if (!resolved) return;\n\n state.usages.push({\n ...resolved,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'tagged-template',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n};\n\n/** Read a static string JSX attribute from an opening element. */\nconst getJsxAttributeString = (\n openingElement: OxcNode,\n attributeName: string\n): string | null => {\n for (const attribute of (openingElement['attributes'] as OxcNode[]) ?? []) {\n if (attribute['type'] !== 'JSXAttribute') continue;\n\n const nameNode = attribute['name'] as OxcNode | undefined;\n\n if ((nameNode?.['name'] as string) !== attributeName) continue;\n\n const valueNode = attribute['value'] as OxcNode | undefined;\n\n if (!valueNode) return null;\n\n // <Trans i18nKey=\"key\" /> or <Trans i18nKey={'key'} />\n const directValue = getStaticStringValue(valueNode);\n\n if (directValue !== null) return directValue;\n\n if (valueNode['type'] === 'JSXExpressionContainer') {\n return getStaticStringValue(\n valueNode['expression'] as OxcNode | undefined\n );\n }\n\n return null;\n }\n\n return null;\n};\n\n/**\n * Record JSX message components: `<FormattedMessage id=\"home.title\" />`,\n * `<Trans i18nKey=\"richText\" />`, `<Trans id=\"…\" />`.\n */\nconst collectJsxUsages = (state: AnalyzerState, program: OxcNode): void => {\n // Fallback namespace for JSX components without an explicit one: the single\n // distinct translator namespace declared in the file (react-i18next Trans).\n const translatorNamespaces = new Set(\n [...state.translatorBindings.values()]\n .map((binding) => binding.dictionaryKey)\n .filter(\n (dictionaryKey): dictionaryKey is string => dictionaryKey !== null\n )\n );\n const fallbackNamespace =\n translatorNamespaces.size === 1 ? [...translatorNamespaces][0]! : null;\n\n walkAst(program, (node) => {\n if (node['type'] !== 'JSXOpeningElement') return;\n\n const nameNode = node['name'] as OxcNode | undefined;\n\n if (nameNode?.['type'] !== 'JSXIdentifier') return;\n\n const elementName = nameNode['name'] as string;\n\n for (const descriptor of state.activeDescriptors) {\n if (!descriptor.jsxIdAttribute) continue;\n\n if (descriptor.callerName !== elementName) continue;\n\n const messageId = getJsxAttributeString(node, descriptor.jsxIdAttribute);\n\n if (messageId === null) continue;\n\n const namespaceFromAttribute = descriptor.jsxNamespaceAttribute\n ? getJsxAttributeString(node, descriptor.jsxNamespaceAttribute)\n : null;\n const fixedNamespace = descriptor.namespaceSources.find(\n (source) => source.from === 'fixed'\n );\n const dictionaryKey =\n namespaceFromAttribute ??\n (fixedNamespace?.from === 'fixed' ? fixedNamespace.value : null) ??\n fallbackNamespace;\n\n let resolved: { dictionaryKey: string; fieldPath: string[] } | null;\n\n if (dictionaryKey) {\n resolved = {\n dictionaryKey,\n fieldPath: descriptor.flatKey ? [messageId] : messageId.split('.'),\n };\n } else {\n resolved = resolveMessageId(descriptor, null, messageId);\n }\n\n if (!resolved) continue;\n\n state.usages.push({\n ...resolved,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'jsx',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n return;\n }\n });\n};\n\n/** Identifier positions that are declarations, not references. */\nconst isDeclarationPosition = (\n identifierNode: OxcNode,\n parentMap: Map<OxcNode, OxcNode>\n): boolean => {\n const parent = parentMap.get(identifierNode);\n\n if (!parent) return false;\n\n const parentType = parent['type'] as string;\n\n if (parentType === 'VariableDeclarator' && parent['id'] === identifierNode) {\n return true;\n }\n\n if (\n (parentType === 'Property' || parentType === 'ObjectProperty') &&\n parentMap.get(parent)?.['type'] === 'ObjectPattern'\n ) {\n return true;\n }\n\n if (parentType === 'ImportSpecifier' || parentType === 'PropertyDefinition') {\n return true;\n }\n\n if (\n parentType === 'FunctionDeclaration' ||\n parentType === 'FunctionExpression' ||\n parentType === 'ArrowFunctionExpression'\n ) {\n return true;\n }\n\n // Object-literal keys: { title: … }\n if (\n (parentType === 'Property' || parentType === 'ObjectProperty') &&\n parent['key'] === identifierNode &&\n parentMap.get(parent)?.['type'] === 'ObjectExpression'\n ) {\n return true;\n }\n\n return false;\n};\n\n/** Identifier used as the (non-computed) property name of a member access. */\nconst isPropertyNamePosition = (\n identifierNode: OxcNode,\n parentMap: Map<OxcNode, OxcNode>\n): boolean => {\n const parent = parentMap.get(identifierNode);\n\n if (!parent) return false;\n\n const parentType = parent['type'] as string;\n\n return (\n (parentType === 'MemberExpression' &&\n parent['property'] === identifierNode &&\n parent['computed'] !== true) ||\n (parentType === 'JSXMemberExpression' &&\n parent['property'] === identifierNode)\n );\n};\n\n/**\n * Record member-access / bare-reference usages of content bindings:\n * `content.section.title`, `{title}` (destructured field reference),\n * Svelte's `$content.title`, Vue's `content.value.title`.\n */\nconst collectMemberUsages = (\n state: AnalyzerState,\n program: OxcNode,\n parentMap: Map<OxcNode, OxcNode>\n): void => {\n if (state.contentBindings.size === 0) return;\n\n walkAst(program, (node) => {\n const nodeType = node['type'] as string;\n\n if (nodeType !== 'Identifier' && nodeType !== 'JSXIdentifier') return;\n\n const rawName = node['name'] as string;\n // Svelte store sugar: `$content` reads the `content` store.\n const variableName = rawName.startsWith('$') ? rawName.slice(1) : rawName;\n const binding = state.contentBindings.get(variableName);\n\n if (!binding) return;\n\n // Skip identifiers in non-value positions: TypeScript type annotations\n // (`useIntlayer<{ greet: string }>`) and JSX attribute names.\n const parentType = parentMap.get(node)?.['type'] as string | undefined;\n\n if (parentType?.startsWith('TS') || parentType === 'JSXAttribute') return;\n\n if (isDeclarationPosition(node, parentMap)) return;\n\n if (isPropertyNamePosition(node, parentMap)) return;\n\n // Climb the member chain upward, collecting property segments.\n const fieldPath = [...binding.basePath];\n const fieldSpans: { start: number; end: number }[] = fieldPath.map(() => ({\n start: nodeStart(node),\n end: nodeEnd(node),\n }));\n\n let currentNode: OxcNode = node;\n\n while (true) {\n const parent = parentMap.get(currentNode);\n\n if (!parent) break;\n\n const parentType = parent['type'] as string;\n const isMemberAccess =\n (parentType === 'MemberExpression' &&\n parent['object'] === currentNode &&\n parent['computed'] !== true) ||\n (parentType === 'JSXMemberExpression' &&\n parent['object'] === currentNode);\n\n if (isMemberAccess) {\n const propertyNode = parent['property'] as OxcNode;\n const propertyName = propertyNode['name'] as string;\n\n if (!ACCESSOR_PROPERTY_NAMES.has(propertyName)) {\n fieldPath.push(propertyName);\n fieldSpans.push({\n start: nodeStart(propertyNode),\n end: nodeEnd(propertyNode),\n });\n }\n currentNode = parent;\n continue;\n }\n\n // Computed string access: content['title']\n if (\n parentType === 'MemberExpression' &&\n parent['object'] === currentNode &&\n parent['computed'] === true\n ) {\n const propertyValue = getStaticStringValue(\n parent['property'] as OxcNode | undefined\n );\n\n if (propertyValue === null) break;\n\n fieldPath.push(propertyValue);\n fieldSpans.push({\n start: nodeStart(parent['property'] as OxcNode),\n end: nodeEnd(parent['property'] as OxcNode),\n });\n currentNode = parent;\n continue;\n }\n\n // Call of an accessor / chain terminator: content.title.use()\n if (parentType === 'CallExpression' && parent['callee'] === currentNode) {\n currentNode = parent;\n continue;\n }\n\n break;\n }\n\n state.usages.push({\n dictionaryKey: binding.dictionaryKey,\n fieldPath,\n fieldSpans,\n start: nodeStart(node),\n end: nodeEnd(currentNode),\n kind: 'member',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n moduleSource: binding.moduleSource,\n // No explicit property segment: the usage only names the variable.\n isBareReference: fieldPath.length === binding.basePath.length,\n });\n return true;\n });\n};\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Analyse a source file and return every resolvable translation usage:\n * base-intlayer content access and all compat-library forms (translation\n * functions, self calls, JSX message components, tagged templates).\n *\n * Pure and self-contained (oxc parse only) — shared by the LSP handlers and\n * the VS Code extension providers/decorations.\n *\n * @param text - Full source text (a script: TS/TSX or an extracted SFC block).\n * @returns Usages ordered by start offset.\n */\nexport const collectMessageUsages = (text: string): MessageUsage[] => {\n const program = parseText(text);\n\n if (!program) return [];\n\n const fileImportSources = collectImportSources(program);\n const state: AnalyzerState = {\n usages: [],\n translatorBindings: new Map(),\n contentBindings: new Map(),\n activeDescriptors: ALL_CALLERS.filter((descriptor) =>\n isCallerActive(descriptor, fileImportSources)\n ),\n importedLocalNames: collectImportedLocalNames(program),\n };\n\n collectBindings(state, program);\n collectCallUsages(state, program);\n collectJsxUsages(state, program);\n\n const parentMap = buildParentMap(program);\n collectMemberUsages(state, program, parentMap);\n\n return state.usages.sort((first, second) => first.start - second.start);\n};\n\n/**\n * Resolve the dictionary key declared by a single call-expression node when\n * it matches any registered non-self caller (`useIntlayer(\"key\")`,\n * `useI18n({ namespace })`, …). Used for on-the-fly checks where the full\n * file analysis is not needed (e.g. completion context).\n *\n * @param callNode - A candidate CallExpression node.\n */\nexport const matchCallNamespace = (callNode: OxcNode): string | null => {\n if (callNode['type'] !== 'CallExpression') return null;\n\n const callee = getCalleeName(callNode['callee'] as OxcNode | undefined);\n\n if (!callee) return null;\n\n for (const descriptor of ALL_CALLERS) {\n if (descriptor.translationFunction === 'self') continue;\n\n if (descriptor.callerName !== callee.name) continue;\n\n if (callee.isMethod && !descriptor.matchAsMethod) continue;\n\n const namespaceValue = readValueSources(\n callNode,\n descriptor.namespaceSources\n );\n\n if (namespaceValue !== null) {\n return splitNestedNamespace(descriptor, namespaceValue).dictionaryKey;\n }\n }\n\n return null;\n};\n\n/**\n * A call site declaring which dictionary a caller reads\n * (`useIntlayer(\"home\")`, `useI18n({ namespace: 'home' })`), with the spans\n * needed to anchor diagnostics and cursor checks.\n */\nexport type NamespaceReference = {\n dictionaryKey: string;\n callerName: string;\n library: string;\n /** Span of the whole call expression. */\n callStart: number;\n callEnd: number;\n /** Span of the matched callee name (identifier or method property). */\n calleeStart: number;\n calleeEnd: number;\n /** Span of the namespace argument (string literal or option value). */\n namespaceStart: number;\n namespaceEnd: number;\n};\n\n/**\n * The AST node holding the namespace string for a given value source, when\n * that source is addressable in the call (positional argument or options\n * property).\n */\nconst getNamespaceArgumentNode = (\n callNode: OxcNode,\n source: CallerValueSource\n): OxcNode | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n\n if (source.from === 'argument') {\n return argumentsList?.[source.index] ?? null;\n }\n\n if (source.from === 'option') {\n const property = getObjectPropertyNode(\n argumentsList?.[source.argumentIndex],\n source.property\n );\n\n return (property?.['value'] as OxcNode | undefined) ?? null;\n }\n\n return null;\n};\n\n/**\n * Collect every call site with an addressable namespace argument (positional\n * or options property) resolving to a static string. Powers the\n * unknown-dictionary diagnostics and `findKeyAtOffset`.\n *\n * Self callers (`formatMessage`, lingui `t`/`_`) don't declare a namespace at\n * the call site and are excluded.\n *\n * @param text - Full source text.\n */\nexport const collectNamespaceReferences = (\n text: string\n): NamespaceReference[] => {\n const program = parseText(text);\n\n if (!program) return [];\n\n const fileImportSources = collectImportSources(program);\n const activeDescriptors = ALL_CALLERS.filter(\n (descriptor) =>\n descriptor.translationFunction !== 'self' &&\n isCallerActive(descriptor, fileImportSources)\n );\n const references: NamespaceReference[] = [];\n\n walkAst(program, (node) => {\n if (node['type'] !== 'CallExpression') return;\n\n const calleeNode = node['callee'] as OxcNode | undefined;\n const callee = getCalleeName(calleeNode);\n\n if (!callee) return;\n\n for (const descriptor of activeDescriptors) {\n if (descriptor.callerName !== callee.name) continue;\n\n if (callee.isMethod && !descriptor.matchAsMethod) continue;\n\n const calleeNameNode =\n callee.isMethod && calleeNode\n ? (calleeNode['property'] as OxcNode)\n : calleeNode!;\n\n for (const source of descriptor.namespaceSources) {\n const namespaceNode = getNamespaceArgumentNode(node, source);\n const namespaceValue = getStaticStringValue(namespaceNode ?? undefined);\n\n if (!namespaceNode || namespaceValue === null) continue;\n\n references.push({\n dictionaryKey: splitNestedNamespace(descriptor, namespaceValue)\n .dictionaryKey,\n callerName: descriptor.callerName,\n library: descriptor.library,\n callStart: nodeStart(node),\n callEnd: nodeEnd(node),\n calleeStart: nodeStart(calleeNameNode),\n calleeEnd: nodeEnd(calleeNameNode),\n namespaceStart: nodeStart(namespaceNode),\n namespaceEnd: nodeEnd(namespaceNode),\n });\n return;\n }\n }\n });\n\n return references;\n};\n\n/**\n * A source variable bound to a dictionary by a caller declaration:\n * dictionary content (or a field subtree of it) for `content` callers, or a\n * translation function for `return-value` / `destructured-t` callers.\n */\nexport type CallerVariableBinding = {\n /** Local variable name. */\n variableName: string;\n /** The dictionary the variable reads from. */\n dictionaryKey: string;\n /**\n * Field path from the dictionary root the variable points at (content\n * bindings), or the key prefix applied to every call (translator bindings).\n */\n basePath: string[];\n /** Whether the variable holds content or a translation function. */\n bindingKind: 'content' | 'translator';\n /** The caller that created the binding. */\n callerName: string;\n library: string;\n};\n\n/**\n * Collect every variable bound to a dictionary in the file. Used by consumers\n * that scan non-script regions (Vue/Angular templates) for references to\n * those variables, or that need to know which dictionaries a file consumes.\n *\n * @param text - Full source text (a script: TS/TSX or an extracted SFC block).\n */\nexport const collectCallerBindings = (\n text: string\n): CallerVariableBinding[] => {\n const program = parseText(text);\n\n if (!program) return [];\n\n const fileImportSources = collectImportSources(program);\n const state: AnalyzerState = {\n usages: [],\n translatorBindings: new Map(),\n contentBindings: new Map(),\n activeDescriptors: ALL_CALLERS.filter((descriptor) =>\n isCallerActive(descriptor, fileImportSources)\n ),\n importedLocalNames: collectImportedLocalNames(program),\n };\n\n collectBindings(state, program);\n\n return [\n ...[...state.contentBindings.entries()].map(\n ([variableName, binding]): CallerVariableBinding => ({\n variableName,\n dictionaryKey: binding.dictionaryKey,\n basePath: binding.basePath,\n bindingKind: 'content',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n })\n ),\n ...[...state.translatorBindings.entries()]\n // Root-scope translators have no bound dictionary to expose.\n .filter(([, binding]) => binding.dictionaryKey !== null)\n .map(\n ([variableName, binding]): CallerVariableBinding => ({\n variableName,\n dictionaryKey: binding.dictionaryKey!,\n basePath: binding.keyPrefix,\n bindingKind: 'translator',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n })\n ),\n ];\n};\n\n/**\n * Find the most specific translation usage at `offset`.\n *\n * For member chains the field path is trimmed to the segment under the\n * cursor: hovering `section` in `content.section.title` yields\n * `['section']`, not the full chain.\n *\n * Dictionary-level `namespace` usages are only returned when no field-level\n * usage matches the offset.\n *\n * @param text - Full source text.\n * @param offset - Cursor offset in `text`.\n */\nexport const findMessageUsageAtOffset = (\n text: string,\n offset: number\n): MessageUsage | null => {\n const usages = collectMessageUsages(text);\n\n let bestMatch: MessageUsage | null = null;\n\n for (const usage of usages) {\n if (offset < usage.start || offset > usage.end) continue;\n\n const isBetter =\n !bestMatch ||\n // Prefer field-level usages over dictionary-level ones…\n (bestMatch.kind === 'namespace' && usage.kind !== 'namespace') ||\n // …then the smallest span containing the offset.\n (usage.end - usage.start < bestMatch.end - bestMatch.start &&\n !(bestMatch.kind !== 'namespace' && usage.kind === 'namespace'));\n\n if (isBetter) bestMatch = usage;\n }\n\n if (!bestMatch) return null;\n\n // Trim member chains to the segment under the cursor.\n if (bestMatch.kind === 'member' && bestMatch.fieldSpans) {\n let lastIncludedIndex = -1;\n\n for (let index = 0; index < bestMatch.fieldSpans.length; index++) {\n if (offset >= bestMatch.fieldSpans[index]!.start) {\n lastIncludedIndex = index;\n }\n }\n\n return {\n ...bestMatch,\n fieldPath: bestMatch.fieldPath.slice(0, lastIncludedIndex + 1),\n fieldSpans: bestMatch.fieldSpans.slice(0, lastIncludedIndex + 1),\n };\n }\n\n return bestMatch;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"usageAnalyzer.mjs","names":["ALL_CALLERS"],"sources":["../../src/usageAnalyzer.ts"],"sourcesContent":["import {\n ALL_CALLERS,\n type CallerDescriptor,\n type CallerValueSource,\n isCallerActive,\n} from './callers';\nimport {\n buildParentMap,\n getObjectPropertyNode,\n getObjectPropertyString,\n getPropertyKeyName,\n getStaticStringValue,\n nodeEnd,\n nodeStart,\n type OxcNode,\n parseText,\n walkAst,\n} from './oxcUtils';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** How a message usage appears in source code. */\nexport type MessageUsageKind =\n /** Translation-function or self call: `t('title')`, `formatMessage({ id })`. */\n | 'call'\n /** Member access / bare reference on a content object: `content.title`. */\n | 'member'\n /** Destructured content field: `const { title } = useIntlayer('home')`. */\n | 'destructure'\n /** JSX message component: `<FormattedMessage id=\"home.title\" />`. */\n | 'jsx'\n /** Tagged template: lingui's ``t`Hello` ``. */\n | 'tagged-template'\n /** The caller call itself (dictionary-level usage): `useIntlayer('home')`. */\n | 'namespace';\n\n/**\n * One resolved translation usage inside a source file: which dictionary and\n * field it reads, and the text span it occupies.\n */\nexport type MessageUsage = {\n /** The intlayer dictionary key the usage reads from. */\n dictionaryKey: string;\n /**\n * Path of content fields from the dictionary root. Empty for\n * dictionary-level usages (`namespace` kind, or a bare content variable).\n */\n fieldPath: string[];\n /**\n * Spans of each `fieldPath` segment, aligned by index, when the segments\n * are addressable in source (member chains). Used to trim the path to the\n * segment under the cursor.\n */\n fieldSpans?: { start: number; end: number }[];\n /** Start offset of the usage expression. */\n start: number;\n /** End offset of the usage expression. */\n end: number;\n kind: MessageUsageKind;\n /** The caller that produced the usage, e.g. `'useTranslations'`. */\n callerName: string;\n /** The library the caller belongs to, e.g. `'next-intl'` or `'intlayer'`. */\n library: string;\n /** Module specifier the caller was imported from, when resolvable. */\n moduleSource?: string;\n /**\n * `true` for `member` usages that are a bare reference to a bound variable\n * (`{greet}` in JSX after `const { greet } = useIntlayer(…)`) — the field\n * name itself is not spelled at the usage site. Excluded from\n * find-references results but kept for decorations and hover.\n */\n isBareReference?: boolean;\n};\n\n// ---------------------------------------------------------------------------\n// Internal binding models\n// ---------------------------------------------------------------------------\n\n/**\n * Variable holding a translation function (`t`) with a bound namespace.\n * `dictionaryKey` is `null` for root-scope translators (`useTranslations()`\n * without a namespace) — the dictionary is then the first dot-segment of\n * each message id.\n */\ntype TranslatorBinding = BindingScope & {\n dictionaryKey: string | null;\n keyPrefix: string[];\n descriptor: CallerDescriptor;\n moduleSource?: string;\n};\n\n/** Variable holding dictionary content (or a field subtree of it). */\ntype ContentBinding = BindingScope & {\n dictionaryKey: string;\n basePath: string[];\n descriptor: CallerDescriptor;\n moduleSource?: string;\n};\n\n/**\n * Where a binding is visible. Two components in the same file routinely bind\n * the same variable name to different dictionaries:\n *\n * const Page = () => { const content = useIntlayer('benchmark'); … }\n * const App = () => { const content = useIntlayer('app'); … }\n *\n * Without a scope, the second declaration would shadow the first and every\n * usage in the file would resolve to the last dictionary declared.\n */\ntype BindingScope = {\n /** Span of the function (or the program) the declaration lives in. */\n scopeStart: number;\n scopeEnd: number;\n /** End offset of the declaration itself — usages before it are not bound. */\n declarationEnd: number;\n};\n\n/**\n * Property names that are framework accessors rather than content fields\n * (react `.value`, vue refs `.value`, editor `.raw`/`.use`): they are skipped\n * when building field paths.\n */\nconst ACCESSOR_PROPERTY_NAMES = new Set(['value', 'raw', 'use']);\n\n// ---------------------------------------------------------------------------\n// Match helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a value source against a call expression's arguments.\n */\nconst readValueSource = (\n callNode: OxcNode,\n source: CallerValueSource\n): string | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n\n switch (source.from) {\n case 'argument':\n return getStaticStringValue(argumentsList?.[source.index]);\n case 'option':\n return getObjectPropertyString(\n argumentsList?.[source.argumentIndex],\n source.property\n );\n case 'fixed':\n return source.value;\n case 'path-first-segment':\n // Resolved from the message id at usage time, not from the call shape.\n return null;\n }\n};\n\n/** Read the first matching value from a list of sources. */\nconst readValueSources = (\n callNode: OxcNode,\n sources: CallerValueSource[] | undefined\n): string | null => {\n for (const source of sources ?? []) {\n const value = readValueSource(callNode, source);\n\n if (value !== null) return value;\n }\n\n return null;\n};\n\n/**\n * Apply nested-namespace semantics: for descriptors flagged\n * `nestedNamespace`, a dotted namespace is split at the first `.` into the\n * dictionary key and a key-prefix path (next-intl:\n * `useTranslations('about.counter')` → dictionary `about`, prefix\n * `['counter']`).\n */\nconst splitNestedNamespace = (\n descriptor: CallerDescriptor,\n rawNamespace: string\n): { dictionaryKey: string; prefixPath: string[] } => {\n if (!descriptor.nestedNamespace || !rawNamespace.includes('.')) {\n return { dictionaryKey: rawNamespace, prefixPath: [] };\n }\n\n const segments = rawNamespace.split('.');\n\n return { dictionaryKey: segments[0]!, prefixPath: segments.slice(1) };\n};\n\n/**\n * Return the caller name matched by a call/tagged-template callee: the bare\n * identifier name, or the property name of a member expression when the\n * descriptor allows method matching.\n */\nconst getCalleeName = (\n calleeNode: OxcNode | undefined\n): { name: string; isMethod: boolean } | null => {\n if (!calleeNode) return null;\n\n if (calleeNode['type'] === 'Identifier') {\n return { name: calleeNode['name'] as string, isMethod: false };\n }\n\n if (\n calleeNode['type'] === 'MemberExpression' &&\n calleeNode['computed'] !== true\n ) {\n const propertyNode = calleeNode['property'] as OxcNode | undefined;\n\n if (propertyNode?.['type'] === 'Identifier') {\n return { name: propertyNode['name'] as string, isMethod: true };\n }\n }\n\n return null;\n};\n\n/**\n * The message id of a self caller: either the first string argument or the\n * `id` property of a descriptor-object first argument\n * (`formatMessage({ id: 'home.title' })`, `i18n._({ id: '…' })`).\n */\nconst getSelfCallMessageId = (callNode: OxcNode): string | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n const firstArgument = argumentsList?.[0];\n\n if (!firstArgument) return null;\n\n return (\n getStaticStringValue(firstArgument) ??\n getObjectPropertyString(firstArgument, 'id')\n );\n};\n\n/**\n * Split a message id into dictionary key + field path according to the\n * descriptor semantics.\n */\nconst resolveMessageId = (\n descriptor: CallerDescriptor,\n fixedDictionaryKey: string | null,\n messageId: string\n): { dictionaryKey: string; fieldPath: string[] } | null => {\n if (fixedDictionaryKey !== null) {\n return {\n dictionaryKey: fixedDictionaryKey,\n fieldPath: descriptor.flatKey ? [messageId] : messageId.split('.'),\n };\n }\n\n const usesPathFirstSegment = descriptor.namespaceSources.some(\n (source) => source.from === 'path-first-segment'\n );\n\n if (!usesPathFirstSegment) return null;\n\n const segments = messageId.split('.');\n\n if (!segments[0]) return null;\n\n return { dictionaryKey: segments[0], fieldPath: segments.slice(1) };\n};\n\n/**\n * i18next-style `ns:key` ids override the bound namespace:\n * `t('about:title')` reads the `about` dictionary even when the translator\n * was created with another namespace.\n */\nconst splitNamespacePrefixedId = (\n messageId: string\n): { namespaceOverride: string | null; messagePath: string } => {\n const separatorIndex = messageId.indexOf(':');\n\n if (separatorIndex <= 0) {\n return { namespaceOverride: null, messagePath: messageId };\n }\n\n return {\n namespaceOverride: messageId.slice(0, separatorIndex),\n messagePath: messageId.slice(separatorIndex + 1),\n };\n};\n\n// ---------------------------------------------------------------------------\n// Analyzer\n// ---------------------------------------------------------------------------\n\n/** Unwrap `await expression` down to the awaited expression. */\nconst unwrapAwait = (node: OxcNode | undefined): OxcNode | undefined =>\n node?.['type'] === 'AwaitExpression'\n ? (node['argument'] as OxcNode | undefined)\n : node;\n\n/** Collect every module specifier imported by the program. */\nconst collectImportSources = (program: OxcNode): Set<string> => {\n const importSources = new Set<string>();\n\n for (const statement of (program['body'] as OxcNode[]) ?? []) {\n if (statement['type'] !== 'ImportDeclaration') continue;\n const sourceValue = (statement['source'] as OxcNode | undefined)?.['value'];\n\n if (typeof sourceValue === 'string') importSources.add(sourceValue);\n }\n\n return importSources;\n};\n\n/** Map each imported local name to its module specifier. */\nconst collectImportedLocalNames = (program: OxcNode): Map<string, string> => {\n const localNameToSource = new Map<string, string>();\n\n for (const statement of (program['body'] as OxcNode[]) ?? []) {\n if (statement['type'] !== 'ImportDeclaration') continue;\n const sourceValue = (statement['source'] as OxcNode | undefined)?.['value'];\n\n if (typeof sourceValue !== 'string') continue;\n\n for (const specifier of (statement['specifiers'] as OxcNode[]) ?? []) {\n const localName = (specifier['local'] as OxcNode | undefined)?.['name'];\n\n if (typeof localName === 'string') {\n localNameToSource.set(localName, sourceValue);\n }\n }\n }\n\n return localNameToSource;\n};\n\ntype AnalyzerState = {\n usages: MessageUsage[];\n /** Bindings are keyed by variable name, then by declaration (see BindingScope). */\n translatorBindings: Map<string, TranslatorBinding[]>;\n contentBindings: Map<string, ContentBinding[]>;\n activeDescriptors: CallerDescriptor[];\n importedLocalNames: Map<string, string>;\n parentMap: Map<OxcNode, OxcNode>;\n program: OxcNode;\n};\n\n/** Node types that open a new binding scope. */\nconst SCOPE_NODE_TYPES = new Set([\n 'FunctionDeclaration',\n 'FunctionExpression',\n 'ArrowFunctionExpression',\n 'ClassMethod',\n 'ObjectMethod',\n 'StaticBlock',\n 'Program',\n]);\n\n/**\n * Span of the nearest enclosing function — the region a declaration is\n * visible in. Falls back to the whole program for module-level declarations.\n */\nconst getEnclosingScope = (\n state: AnalyzerState,\n node: OxcNode\n): { scopeStart: number; scopeEnd: number } => {\n let current: OxcNode | undefined = node;\n\n while (current) {\n if (SCOPE_NODE_TYPES.has(current['type'] as string)) {\n return { scopeStart: nodeStart(current), scopeEnd: nodeEnd(current) };\n }\n current = state.parentMap.get(current);\n }\n\n return {\n scopeStart: nodeStart(state.program),\n scopeEnd: nodeEnd(state.program),\n };\n};\n\nconst addBinding = <T>(\n bindings: Map<string, T[]>,\n variableName: string,\n binding: T\n): void => {\n const existing = bindings.get(variableName);\n\n if (existing) {\n existing.push(binding);\n } else {\n bindings.set(variableName, [binding]);\n }\n};\n\n/**\n * The binding a variable resolves to at `offset`: the innermost scope that\n * contains the offset, among declarations that precede it.\n */\nconst resolveBinding = <T extends BindingScope>(\n bindings: Map<string, T[]>,\n variableName: string,\n offset: number\n): T | undefined => {\n const candidates = bindings.get(variableName);\n\n if (!candidates) return undefined;\n\n let best: T | undefined;\n\n for (const candidate of candidates) {\n if (offset < candidate.scopeStart || offset > candidate.scopeEnd) continue;\n\n // A usage textually before its declaration belongs to an outer binding.\n if (offset < candidate.declarationEnd) continue;\n\n // Innermost scope wins; on equal scopes the latest declaration wins.\n if (\n !best ||\n candidate.scopeStart > best.scopeStart ||\n (candidate.scopeStart === best.scopeStart &&\n candidate.declarationEnd > best.declarationEnd)\n ) {\n best = candidate;\n }\n }\n\n return best;\n};\n\n/**\n * Find the descriptor matching a call (or tagged template) callee among the\n * active descriptors.\n */\nconst matchDescriptorForCallee = (\n state: AnalyzerState,\n calleeNode: OxcNode | undefined,\n filter?: (descriptor: CallerDescriptor) => boolean\n): CallerDescriptor | null => {\n const callee = getCalleeName(calleeNode);\n\n if (!callee) return null;\n\n for (const descriptor of state.activeDescriptors) {\n if (descriptor.callerName !== callee.name) continue;\n\n if (callee.isMethod && !descriptor.matchAsMethod) continue;\n\n if (filter && !filter(descriptor)) continue;\n\n return descriptor;\n }\n\n return null;\n};\n\n/** The module specifier a caller local name was imported from, if any. */\nconst getModuleSource = (\n state: AnalyzerState,\n descriptor: CallerDescriptor\n): string | undefined =>\n state.importedLocalNames.get(descriptor.callerName) ?? undefined;\n\n/**\n * Recursively register a content destructure pattern: each property key is a\n * field usage, each bound identifier becomes a content binding, and nested\n * patterns extend the field path.\n *\n * const { searchInput: { text: alias } } = useIntlayer('key')\n * → usages: ['searchInput'], ['searchInput', 'text']\n * → binding: alias → ['searchInput', 'text']\n */\nconst collectContentDestructure = (\n state: AnalyzerState,\n pattern: OxcNode,\n dictionaryKey: string,\n basePath: string[],\n descriptor: CallerDescriptor,\n moduleSource: string | undefined,\n scope: BindingScope\n): void => {\n for (const property of (pattern['properties'] as OxcNode[]) ?? []) {\n const propertyType = property['type'] as string;\n\n if (propertyType !== 'Property' && propertyType !== 'ObjectProperty') {\n continue;\n }\n\n const keyNode = property['key'] as OxcNode | undefined;\n const fieldName = getPropertyKeyName(keyNode);\n\n if (!keyNode || !fieldName) continue;\n\n const fieldPath = [...basePath, fieldName];\n\n state.usages.push({\n dictionaryKey,\n fieldPath,\n start: nodeStart(keyNode),\n end: nodeEnd(keyNode),\n kind: 'destructure',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource,\n });\n\n // Unwrap default values: { title = fallback }\n let valueNode = property['value'] as OxcNode | undefined;\n\n if (valueNode?.['type'] === 'AssignmentPattern') {\n valueNode = valueNode['left'] as OxcNode | undefined;\n }\n\n if (valueNode?.['type'] === 'Identifier') {\n addBinding(state.contentBindings, valueNode['name'] as string, {\n dictionaryKey,\n basePath: fieldPath,\n descriptor,\n moduleSource,\n ...scope,\n });\n } else if (valueNode?.['type'] === 'ObjectPattern') {\n collectContentDestructure(\n state,\n valueNode,\n dictionaryKey,\n fieldPath,\n descriptor,\n moduleSource,\n scope\n );\n }\n }\n};\n\n/**\n * Register bindings created by `const … = caller(…)` declarations, and record\n * dictionary-level (`namespace`) usages for every matched caller call.\n */\nconst collectBindings = (state: AnalyzerState, program: OxcNode): void => {\n walkAst(program, (node) => {\n if (node['type'] === 'CallExpression') {\n const descriptor = matchDescriptorForCallee(\n state,\n node['callee'] as OxcNode | undefined,\n (candidate) => candidate.translationFunction !== 'self'\n );\n\n if (descriptor) {\n const rawNamespace = readValueSources(\n node,\n descriptor.namespaceSources\n );\n\n if (rawNamespace) {\n const { dictionaryKey } = splitNestedNamespace(\n descriptor,\n rawNamespace\n );\n\n state.usages.push({\n dictionaryKey,\n fieldPath: [],\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'namespace',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n }\n }\n return;\n }\n\n if (node['type'] !== 'VariableDeclarator') return;\n\n const initializer = unwrapAwait(node['init'] as OxcNode | undefined);\n\n if (initializer?.['type'] !== 'CallExpression') return;\n\n const descriptor = matchDescriptorForCallee(\n state,\n initializer['callee'] as OxcNode | undefined,\n (candidate) => candidate.translationFunction !== 'self'\n );\n\n if (!descriptor) return;\n\n const rawNamespace = readValueSources(\n initializer,\n descriptor.namespaceSources\n );\n\n // Root scope: `useTranslations()` — the dictionary comes from each id.\n if (!rawNamespace && !descriptor.allowRootScope) return;\n\n const namespaceSplit = rawNamespace\n ? splitNestedNamespace(descriptor, rawNamespace)\n : { dictionaryKey: null, prefixPath: [] };\n\n const keyPrefixValue = readValueSources(\n initializer,\n descriptor.keyPrefixSources\n );\n const keyPrefix = [\n ...namespaceSplit.prefixPath,\n ...(keyPrefixValue ? keyPrefixValue.split('.') : []),\n ];\n const dictionaryKey = namespaceSplit.dictionaryKey;\n const moduleSource = getModuleSource(state, descriptor);\n const idNode = node['id'] as OxcNode | undefined;\n\n if (!idNode) return;\n\n const scope: BindingScope = {\n ...getEnclosingScope(state, node),\n declarationEnd: nodeEnd(node),\n };\n\n // const t = useTranslations('ns') / const content = useIntlayer('key')\n if (idNode['type'] === 'Identifier') {\n const variableName = idNode['name'] as string;\n\n if (descriptor.translationFunction === 'return-value') {\n addBinding(state.translatorBindings, variableName, {\n dictionaryKey,\n keyPrefix,\n descriptor,\n moduleSource,\n ...scope,\n });\n } else if (\n descriptor.translationFunction === 'content' &&\n dictionaryKey\n ) {\n addBinding(state.contentBindings, variableName, {\n dictionaryKey,\n basePath: keyPrefix,\n descriptor,\n moduleSource,\n ...scope,\n });\n }\n return;\n }\n\n // const { t } = useTranslation('ns') / const { title } = useIntlayer('key')\n if (idNode['type'] !== 'ObjectPattern') return;\n\n if (descriptor.translationFunction === 'content') {\n if (!dictionaryKey) return;\n\n collectContentDestructure(\n state,\n idNode,\n dictionaryKey,\n keyPrefix,\n descriptor,\n moduleSource,\n scope\n );\n return;\n }\n\n if (descriptor.translationFunction !== 'destructured-t') return;\n\n for (const property of (idNode['properties'] as OxcNode[]) ?? []) {\n const propertyType = property['type'] as string;\n\n if (propertyType !== 'Property' && propertyType !== 'ObjectProperty') {\n continue;\n }\n\n const keyNode = property['key'] as OxcNode | undefined;\n const valueNode = property['value'] as OxcNode | undefined;\n const fieldName = getPropertyKeyName(keyNode);\n const localName =\n valueNode?.['type'] === 'Identifier'\n ? (valueNode['name'] as string)\n : null;\n\n // Only the `t` property carries the translation function.\n if (fieldName !== 't' || !localName) continue;\n\n addBinding(state.translatorBindings, localName, {\n dictionaryKey,\n keyPrefix,\n descriptor,\n moduleSource,\n ...scope,\n });\n }\n });\n};\n\n/**\n * Record `t('path.to.field')` calls on translator bindings, and self-caller\n * calls (`formatMessage({ id })`, `i18n._('id')`).\n */\nconst collectCallUsages = (state: AnalyzerState, program: OxcNode): void => {\n walkAst(program, (node) => {\n if (node['type'] === 'TaggedTemplateExpression') {\n collectTaggedTemplateUsage(state, node);\n return;\n }\n\n if (node['type'] !== 'CallExpression') return;\n\n const callee = node['callee'] as OxcNode | undefined;\n\n // Translator binding call: t('path.to.field')\n if (callee?.['type'] === 'Identifier') {\n const binding = resolveBinding(\n state.translatorBindings,\n callee['name'] as string,\n nodeStart(node)\n );\n\n if (binding) {\n const argumentsList = node['arguments'] as OxcNode[] | undefined;\n const messageId = getStaticStringValue(argumentsList?.[0]);\n\n if (messageId) {\n const { namespaceOverride, messagePath } =\n splitNamespacePrefixedId(messageId);\n let dictionaryKey = namespaceOverride ?? binding.dictionaryKey;\n let fieldPath = namespaceOverride\n ? messagePath.split('.')\n : [...binding.keyPrefix, ...messagePath.split('.')];\n\n // Root-scope translator (`useTranslations()`): the first id\n // segment is the dictionary key.\n if (dictionaryKey === null) {\n const segments = messagePath.split('.');\n dictionaryKey = segments[0] ?? null;\n fieldPath = segments.slice(1);\n }\n\n if (dictionaryKey) {\n state.usages.push({\n dictionaryKey,\n fieldPath,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'call',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n moduleSource: binding.moduleSource,\n });\n }\n }\n return;\n }\n }\n\n // Self caller: the call itself carries the message id.\n const descriptor = matchDescriptorForCallee(\n state,\n callee,\n (candidate) => candidate.translationFunction === 'self'\n );\n\n if (!descriptor) return;\n\n const messageId = getSelfCallMessageId(node);\n\n if (!messageId) return;\n\n const fixedDictionaryKey = readValueSources(\n node,\n descriptor.namespaceSources\n );\n const resolved = resolveMessageId(\n descriptor,\n fixedDictionaryKey,\n messageId\n );\n\n if (!resolved) return;\n\n state.usages.push({\n ...resolved,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'call',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n });\n};\n\n/**\n * Lingui macro tagged template: ``t`Hello ${name}` `` — the catalog id is the\n * template text with `{placeholder}` slots for each expression.\n */\nconst collectTaggedTemplateUsage = (\n state: AnalyzerState,\n node: OxcNode\n): void => {\n const descriptor = matchDescriptorForCallee(\n state,\n node['tag'] as OxcNode | undefined,\n (candidate) => candidate.matchAsTaggedTemplate === true\n );\n\n if (!descriptor) return;\n\n const quasiNode = node['quasi'] as OxcNode | undefined;\n const quasis = (quasiNode?.['quasis'] as OxcNode[]) ?? [];\n const expressions = (quasiNode?.['expressions'] as OxcNode[]) ?? [];\n\n let messageId = '';\n\n for (let index = 0; index < quasis.length; index++) {\n const valueNode = quasis[index]?.['value'] as OxcNode | undefined;\n messageId +=\n (valueNode?.['cooked'] as string) ?? (valueNode?.['raw'] as string) ?? '';\n\n if (index < expressions.length) {\n const expression = expressions[index]!;\n const placeholder =\n expression['type'] === 'Identifier'\n ? (expression['name'] as string)\n : String(index);\n messageId += `{${placeholder}}`;\n }\n }\n\n if (!messageId) return;\n\n const fixedDictionaryKey = readValueSources(\n node,\n descriptor.namespaceSources\n );\n const resolved = resolveMessageId(descriptor, fixedDictionaryKey, messageId);\n\n if (!resolved) return;\n\n state.usages.push({\n ...resolved,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'tagged-template',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n};\n\n/** Read a static string JSX attribute from an opening element. */\nconst getJsxAttributeString = (\n openingElement: OxcNode,\n attributeName: string\n): string | null => {\n for (const attribute of (openingElement['attributes'] as OxcNode[]) ?? []) {\n if (attribute['type'] !== 'JSXAttribute') continue;\n\n const nameNode = attribute['name'] as OxcNode | undefined;\n\n if ((nameNode?.['name'] as string) !== attributeName) continue;\n\n const valueNode = attribute['value'] as OxcNode | undefined;\n\n if (!valueNode) return null;\n\n // <Trans i18nKey=\"key\" /> or <Trans i18nKey={'key'} />\n const directValue = getStaticStringValue(valueNode);\n\n if (directValue !== null) return directValue;\n\n if (valueNode['type'] === 'JSXExpressionContainer') {\n return getStaticStringValue(\n valueNode['expression'] as OxcNode | undefined\n );\n }\n\n return null;\n }\n\n return null;\n};\n\n/**\n * Record JSX message components: `<FormattedMessage id=\"home.title\" />`,\n * `<Trans i18nKey=\"richText\" />`, `<Trans id=\"…\" />`.\n */\nconst collectJsxUsages = (state: AnalyzerState, program: OxcNode): void => {\n // Fallback namespace for JSX components without an explicit one: the single\n // distinct translator namespace declared in the file (react-i18next Trans).\n const translatorNamespaces = new Set(\n [...state.translatorBindings.values()]\n .flat()\n .map((binding) => binding.dictionaryKey)\n .filter(\n (dictionaryKey): dictionaryKey is string => dictionaryKey !== null\n )\n );\n const fallbackNamespace =\n translatorNamespaces.size === 1 ? [...translatorNamespaces][0]! : null;\n\n walkAst(program, (node) => {\n if (node['type'] !== 'JSXOpeningElement') return;\n\n const nameNode = node['name'] as OxcNode | undefined;\n\n if (nameNode?.['type'] !== 'JSXIdentifier') return;\n\n const elementName = nameNode['name'] as string;\n\n for (const descriptor of state.activeDescriptors) {\n if (!descriptor.jsxIdAttribute) continue;\n\n if (descriptor.callerName !== elementName) continue;\n\n const messageId = getJsxAttributeString(node, descriptor.jsxIdAttribute);\n\n if (messageId === null) continue;\n\n const namespaceFromAttribute = descriptor.jsxNamespaceAttribute\n ? getJsxAttributeString(node, descriptor.jsxNamespaceAttribute)\n : null;\n const fixedNamespace = descriptor.namespaceSources.find(\n (source) => source.from === 'fixed'\n );\n const dictionaryKey =\n namespaceFromAttribute ??\n (fixedNamespace?.from === 'fixed' ? fixedNamespace.value : null) ??\n fallbackNamespace;\n\n let resolved: { dictionaryKey: string; fieldPath: string[] } | null;\n\n if (dictionaryKey) {\n resolved = {\n dictionaryKey,\n fieldPath: descriptor.flatKey ? [messageId] : messageId.split('.'),\n };\n } else {\n resolved = resolveMessageId(descriptor, null, messageId);\n }\n\n if (!resolved) continue;\n\n state.usages.push({\n ...resolved,\n start: nodeStart(node),\n end: nodeEnd(node),\n kind: 'jsx',\n callerName: descriptor.callerName,\n library: descriptor.library,\n moduleSource: getModuleSource(state, descriptor),\n });\n return;\n }\n });\n};\n\n/** Identifier positions that are declarations, not references. */\nconst isDeclarationPosition = (\n identifierNode: OxcNode,\n parentMap: Map<OxcNode, OxcNode>\n): boolean => {\n const parent = parentMap.get(identifierNode);\n\n if (!parent) return false;\n\n const parentType = parent['type'] as string;\n\n if (parentType === 'VariableDeclarator' && parent['id'] === identifierNode) {\n return true;\n }\n\n if (\n (parentType === 'Property' || parentType === 'ObjectProperty') &&\n parentMap.get(parent)?.['type'] === 'ObjectPattern'\n ) {\n return true;\n }\n\n if (parentType === 'ImportSpecifier' || parentType === 'PropertyDefinition') {\n return true;\n }\n\n if (\n parentType === 'FunctionDeclaration' ||\n parentType === 'FunctionExpression' ||\n parentType === 'ArrowFunctionExpression'\n ) {\n return true;\n }\n\n // Object-literal keys: { title: … }\n if (\n (parentType === 'Property' || parentType === 'ObjectProperty') &&\n parent['key'] === identifierNode &&\n parentMap.get(parent)?.['type'] === 'ObjectExpression'\n ) {\n return true;\n }\n\n return false;\n};\n\n/** Identifier used as the (non-computed) property name of a member access. */\nconst isPropertyNamePosition = (\n identifierNode: OxcNode,\n parentMap: Map<OxcNode, OxcNode>\n): boolean => {\n const parent = parentMap.get(identifierNode);\n\n if (!parent) return false;\n\n const parentType = parent['type'] as string;\n\n return (\n (parentType === 'MemberExpression' &&\n parent['property'] === identifierNode &&\n parent['computed'] !== true) ||\n (parentType === 'JSXMemberExpression' &&\n parent['property'] === identifierNode)\n );\n};\n\n/**\n * Record member-access / bare-reference usages of content bindings:\n * `content.section.title`, `{title}` (destructured field reference),\n * Svelte's `$content.title`, Vue's `content.value.title`.\n */\nconst collectMemberUsages = (\n state: AnalyzerState,\n program: OxcNode,\n parentMap: Map<OxcNode, OxcNode>\n): void => {\n if (state.contentBindings.size === 0) return;\n\n walkAst(program, (node) => {\n const nodeType = node['type'] as string;\n\n if (nodeType !== 'Identifier' && nodeType !== 'JSXIdentifier') return;\n\n const rawName = node['name'] as string;\n // Svelte store sugar: `$content` reads the `content` store.\n const variableName = rawName.startsWith('$') ? rawName.slice(1) : rawName;\n const binding = resolveBinding(\n state.contentBindings,\n variableName,\n nodeStart(node)\n );\n\n if (!binding) return;\n\n // Skip identifiers in non-value positions: TypeScript type annotations\n // (`useIntlayer<{ greet: string }>`) and JSX attribute names.\n const parentType = parentMap.get(node)?.['type'] as string | undefined;\n\n if (parentType?.startsWith('TS') || parentType === 'JSXAttribute') return;\n\n if (isDeclarationPosition(node, parentMap)) return;\n\n if (isPropertyNamePosition(node, parentMap)) return;\n\n // Climb the member chain upward, collecting property segments.\n const fieldPath = [...binding.basePath];\n const fieldSpans: { start: number; end: number }[] = fieldPath.map(() => ({\n start: nodeStart(node),\n end: nodeEnd(node),\n }));\n\n let currentNode: OxcNode = node;\n\n while (true) {\n const parent = parentMap.get(currentNode);\n\n if (!parent) break;\n\n const parentType = parent['type'] as string;\n const isMemberAccess =\n (parentType === 'MemberExpression' &&\n parent['object'] === currentNode &&\n parent['computed'] !== true) ||\n (parentType === 'JSXMemberExpression' &&\n parent['object'] === currentNode);\n\n if (isMemberAccess) {\n const propertyNode = parent['property'] as OxcNode;\n const propertyName = propertyNode['name'] as string;\n\n if (!ACCESSOR_PROPERTY_NAMES.has(propertyName)) {\n fieldPath.push(propertyName);\n fieldSpans.push({\n start: nodeStart(propertyNode),\n end: nodeEnd(propertyNode),\n });\n }\n currentNode = parent;\n continue;\n }\n\n // Computed string access: content['title']\n if (\n parentType === 'MemberExpression' &&\n parent['object'] === currentNode &&\n parent['computed'] === true\n ) {\n const propertyValue = getStaticStringValue(\n parent['property'] as OxcNode | undefined\n );\n\n if (propertyValue === null) break;\n\n fieldPath.push(propertyValue);\n fieldSpans.push({\n start: nodeStart(parent['property'] as OxcNode),\n end: nodeEnd(parent['property'] as OxcNode),\n });\n currentNode = parent;\n continue;\n }\n\n // Call of an accessor / chain terminator: content.title.use()\n if (parentType === 'CallExpression' && parent['callee'] === currentNode) {\n currentNode = parent;\n continue;\n }\n\n break;\n }\n\n state.usages.push({\n dictionaryKey: binding.dictionaryKey,\n fieldPath,\n fieldSpans,\n start: nodeStart(node),\n end: nodeEnd(currentNode),\n kind: 'member',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n moduleSource: binding.moduleSource,\n // No explicit property segment: the usage only names the variable.\n isBareReference: fieldPath.length === binding.basePath.length,\n });\n return true;\n });\n};\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Analyse a source file and return every resolvable translation usage:\n * base-intlayer content access and all compat-library forms (translation\n * functions, self calls, JSX message components, tagged templates).\n *\n * Pure and self-contained (oxc parse only) — shared by the LSP handlers and\n * the VS Code extension providers/decorations.\n *\n * @param text - Full source text (a script: TS/TSX or an extracted SFC block).\n * @returns Usages ordered by start offset.\n */\nexport const collectMessageUsages = (text: string): MessageUsage[] => {\n const program = parseText(text);\n\n if (!program) return [];\n\n const fileImportSources = collectImportSources(program);\n const parentMap = buildParentMap(program);\n const state: AnalyzerState = {\n usages: [],\n translatorBindings: new Map(),\n contentBindings: new Map(),\n activeDescriptors: ALL_CALLERS.filter((descriptor) =>\n isCallerActive(descriptor, fileImportSources)\n ),\n importedLocalNames: collectImportedLocalNames(program),\n parentMap,\n program,\n };\n\n collectBindings(state, program);\n collectCallUsages(state, program);\n collectJsxUsages(state, program);\n collectMemberUsages(state, program, parentMap);\n\n return state.usages.sort((first, second) => first.start - second.start);\n};\n\n/**\n * Resolve the dictionary key declared by a single call-expression node when\n * it matches any registered non-self caller (`useIntlayer(\"key\")`,\n * `useI18n({ namespace })`, …). Used for on-the-fly checks where the full\n * file analysis is not needed (e.g. completion context).\n *\n * @param callNode - A candidate CallExpression node.\n */\nexport const matchCallNamespace = (callNode: OxcNode): string | null => {\n if (callNode['type'] !== 'CallExpression') return null;\n\n const callee = getCalleeName(callNode['callee'] as OxcNode | undefined);\n\n if (!callee) return null;\n\n for (const descriptor of ALL_CALLERS) {\n if (descriptor.translationFunction === 'self') continue;\n\n if (descriptor.callerName !== callee.name) continue;\n\n if (callee.isMethod && !descriptor.matchAsMethod) continue;\n\n const namespaceValue = readValueSources(\n callNode,\n descriptor.namespaceSources\n );\n\n if (namespaceValue !== null) {\n return splitNestedNamespace(descriptor, namespaceValue).dictionaryKey;\n }\n }\n\n return null;\n};\n\n/**\n * A call site declaring which dictionary a caller reads\n * (`useIntlayer(\"home\")`, `useI18n({ namespace: 'home' })`), with the spans\n * needed to anchor diagnostics and cursor checks.\n */\nexport type NamespaceReference = {\n dictionaryKey: string;\n callerName: string;\n library: string;\n /** Span of the whole call expression. */\n callStart: number;\n callEnd: number;\n /** Span of the matched callee name (identifier or method property). */\n calleeStart: number;\n calleeEnd: number;\n /** Span of the namespace argument (string literal or option value). */\n namespaceStart: number;\n namespaceEnd: number;\n};\n\n/**\n * The AST node holding the namespace string for a given value source, when\n * that source is addressable in the call (positional argument or options\n * property).\n */\nconst getNamespaceArgumentNode = (\n callNode: OxcNode,\n source: CallerValueSource\n): OxcNode | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n\n if (source.from === 'argument') {\n return argumentsList?.[source.index] ?? null;\n }\n\n if (source.from === 'option') {\n const property = getObjectPropertyNode(\n argumentsList?.[source.argumentIndex],\n source.property\n );\n\n return (property?.['value'] as OxcNode | undefined) ?? null;\n }\n\n return null;\n};\n\n/**\n * Collect every call site with an addressable namespace argument (positional\n * or options property) resolving to a static string. Powers the\n * unknown-dictionary diagnostics and `findKeyAtOffset`.\n *\n * Self callers (`formatMessage`, lingui `t`/`_`) don't declare a namespace at\n * the call site and are excluded.\n *\n * @param text - Full source text.\n */\nexport const collectNamespaceReferences = (\n text: string\n): NamespaceReference[] => {\n const program = parseText(text);\n\n if (!program) return [];\n\n const fileImportSources = collectImportSources(program);\n const activeDescriptors = ALL_CALLERS.filter(\n (descriptor) =>\n descriptor.translationFunction !== 'self' &&\n isCallerActive(descriptor, fileImportSources)\n );\n const references: NamespaceReference[] = [];\n\n walkAst(program, (node) => {\n if (node['type'] !== 'CallExpression') return;\n\n const calleeNode = node['callee'] as OxcNode | undefined;\n const callee = getCalleeName(calleeNode);\n\n if (!callee) return;\n\n for (const descriptor of activeDescriptors) {\n if (descriptor.callerName !== callee.name) continue;\n\n if (callee.isMethod && !descriptor.matchAsMethod) continue;\n\n const calleeNameNode =\n callee.isMethod && calleeNode\n ? (calleeNode['property'] as OxcNode)\n : calleeNode!;\n\n for (const source of descriptor.namespaceSources) {\n const namespaceNode = getNamespaceArgumentNode(node, source);\n const namespaceValue = getStaticStringValue(namespaceNode ?? undefined);\n\n if (!namespaceNode || namespaceValue === null) continue;\n\n references.push({\n dictionaryKey: splitNestedNamespace(descriptor, namespaceValue)\n .dictionaryKey,\n callerName: descriptor.callerName,\n library: descriptor.library,\n callStart: nodeStart(node),\n callEnd: nodeEnd(node),\n calleeStart: nodeStart(calleeNameNode),\n calleeEnd: nodeEnd(calleeNameNode),\n namespaceStart: nodeStart(namespaceNode),\n namespaceEnd: nodeEnd(namespaceNode),\n });\n return;\n }\n }\n });\n\n return references;\n};\n\n/**\n * A source variable bound to a dictionary by a caller declaration:\n * dictionary content (or a field subtree of it) for `content` callers, or a\n * translation function for `return-value` / `destructured-t` callers.\n */\nexport type CallerVariableBinding = {\n /** Local variable name. */\n variableName: string;\n /** The dictionary the variable reads from. */\n dictionaryKey: string;\n /**\n * Field path from the dictionary root the variable points at (content\n * bindings), or the key prefix applied to every call (translator bindings).\n */\n basePath: string[];\n /** Whether the variable holds content or a translation function. */\n bindingKind: 'content' | 'translator';\n /** The caller that created the binding. */\n callerName: string;\n library: string;\n};\n\n/**\n * Collect every variable bound to a dictionary in the file. Used by consumers\n * that scan non-script regions (Vue/Angular templates) for references to\n * those variables, or that need to know which dictionaries a file consumes.\n *\n * @param text - Full source text (a script: TS/TSX or an extracted SFC block).\n */\nexport const collectCallerBindings = (\n text: string\n): CallerVariableBinding[] => {\n const program = parseText(text);\n\n if (!program) return [];\n\n const fileImportSources = collectImportSources(program);\n const state: AnalyzerState = {\n usages: [],\n translatorBindings: new Map(),\n contentBindings: new Map(),\n activeDescriptors: ALL_CALLERS.filter((descriptor) =>\n isCallerActive(descriptor, fileImportSources)\n ),\n importedLocalNames: collectImportedLocalNames(program),\n parentMap: buildParentMap(program),\n program,\n };\n\n collectBindings(state, program);\n\n return [\n ...[...state.contentBindings.entries()].flatMap(\n ([variableName, bindings]) =>\n bindings.map(\n (binding): CallerVariableBinding => ({\n variableName,\n dictionaryKey: binding.dictionaryKey,\n basePath: binding.basePath,\n bindingKind: 'content',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n })\n )\n ),\n ...[...state.translatorBindings.entries()].flatMap(\n ([variableName, bindings]) =>\n bindings\n // Root-scope translators have no bound dictionary to expose.\n .filter((binding) => binding.dictionaryKey !== null)\n .map(\n (binding): CallerVariableBinding => ({\n variableName,\n dictionaryKey: binding.dictionaryKey!,\n basePath: binding.keyPrefix,\n bindingKind: 'translator',\n callerName: binding.descriptor.callerName,\n library: binding.descriptor.library,\n })\n )\n ),\n ];\n};\n\n/**\n * Find the most specific translation usage at `offset`.\n *\n * For member chains the field path is trimmed to the segment under the\n * cursor: hovering `section` in `content.section.title` yields\n * `['section']`, not the full chain.\n *\n * Dictionary-level `namespace` usages are only returned when no field-level\n * usage matches the offset.\n *\n * @param text - Full source text.\n * @param offset - Cursor offset in `text`.\n */\nexport const findMessageUsageAtOffset = (\n text: string,\n offset: number\n): MessageUsage | null => {\n const usages = collectMessageUsages(text);\n\n let bestMatch: MessageUsage | null = null;\n\n for (const usage of usages) {\n if (offset < usage.start || offset > usage.end) continue;\n\n const isBetter =\n !bestMatch ||\n // Prefer field-level usages over dictionary-level ones…\n (bestMatch.kind === 'namespace' && usage.kind !== 'namespace') ||\n // …then the smallest span containing the offset.\n (usage.end - usage.start < bestMatch.end - bestMatch.start &&\n !(bestMatch.kind !== 'namespace' && usage.kind === 'namespace'));\n\n if (isBetter) bestMatch = usage;\n }\n\n if (!bestMatch) return null;\n\n // Trim member chains to the segment under the cursor.\n if (bestMatch.kind === 'member' && bestMatch.fieldSpans) {\n let lastIncludedIndex = -1;\n\n for (let index = 0; index < bestMatch.fieldSpans.length; index++) {\n if (offset >= bestMatch.fieldSpans[index]!.start) {\n lastIncludedIndex = index;\n }\n }\n\n return {\n ...bestMatch,\n fieldPath: bestMatch.fieldPath.slice(0, lastIncludedIndex + 1),\n fieldSpans: bestMatch.fieldSpans.slice(0, lastIncludedIndex + 1),\n };\n }\n\n return bestMatch;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findKeyInContentFile.d.ts","names":[],"sources":["../../src/findKeyInContentFile.ts"],"mappings":";;;;;;;;;cAea,uBACX,cACA;;;;;;;;;;;;;;;cA4BW,2BACX,cACA,gBACA,2BACC;
|
|
1
|
+
{"version":3,"file":"findKeyInContentFile.d.ts","names":[],"sources":["../../src/findKeyInContentFile.ts"],"mappings":";;;;;;;;;cAea,uBACX,cACA;;;;;;;;;;;;;;;cA4BW,2BACX,cACA,gBACA,2BACC;KA4LE;EACH;EACA;EACA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usageAnalyzer.d.ts","names":[],"sources":["../../src/usageAnalyzer.ts"],"mappings":";;;KAwBY;;;;;KAkBA;;EAEV;;;;;EAKA;;;;;;EAMA;IAAe;IAAe;;;EAE9B;;EAEA;EACA,MAAM;;EAEN;;EAEA;;EAEA;;;;;;;EAOA;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"usageAnalyzer.d.ts","names":[],"sources":["../../src/usageAnalyzer.ts"],"mappings":";;;KAwBY;;;;;KAkBA;;EAEV;;;;;EAKA;;;;;;EAMA;IAAe;IAAe;;;EAE9B;;EAEA;EACA,MAAM;;EAEN;;EAEA;;EAEA;;;;;;;EAOA;;;;;;;;;;;;;cAyjCW,uBAAwB,iBAAe;;;;;;;;;cAmCvC,qBAAsB,UAAU;;;;;;KAgCjC;EACV;EACA;EACA;;EAEA;EACA;;EAEA;EACA;;EAEA;EACA;;;;;;;;;;;;cAwCW,6BACX,iBACC;;;;;;KA8DS;;EAEV;;EAEA;;;;;EAKA;;EAEA;;EAEA;EACA;;;;;;;;;cAUW,wBACX,iBACC;;;;;;;;;;;;;;cAkEU,2BACX,cACA,mBACC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/lsp",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Intlayer Language Server. Handle LSP to help IDE and AI agent to use Intlayer",
|
|
6
6
|
"keywords": [
|
|
@@ -83,21 +83,21 @@
|
|
|
83
83
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@intlayer/config": "9.
|
|
87
|
-
"@intlayer/engine": "9.
|
|
88
|
-
"@intlayer/types": "9.
|
|
89
|
-
"@intlayer/unmerged-dictionaries-entry": "9.
|
|
90
|
-
"oxc-parser": "0.
|
|
86
|
+
"@intlayer/config": "9.1.0",
|
|
87
|
+
"@intlayer/engine": "9.1.0",
|
|
88
|
+
"@intlayer/types": "9.1.0",
|
|
89
|
+
"@intlayer/unmerged-dictionaries-entry": "9.1.0",
|
|
90
|
+
"oxc-parser": "0.142.0",
|
|
91
91
|
"vscode-languageserver": "10.1.0",
|
|
92
92
|
"vscode-languageserver-textdocument": "1.0.12"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@types/node": "26.1.
|
|
95
|
+
"@types/node": "26.1.2",
|
|
96
96
|
"@utils/ts-config": "1.0.4",
|
|
97
97
|
"@utils/ts-config-types": "1.0.4",
|
|
98
98
|
"@utils/tsdown-config": "1.0.4",
|
|
99
99
|
"rimraf": "6.1.3",
|
|
100
|
-
"tsdown": "0.22.
|
|
100
|
+
"tsdown": "0.22.14",
|
|
101
101
|
"typescript": "7.0.2",
|
|
102
102
|
"vitest": "4.1.10"
|
|
103
103
|
},
|