@intlayer/lsp 9.0.0-canary.12 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/esm/callers/patterns.mjs.map +1 -1
  2. package/dist/esm/dictionaryUtils.mjs +1 -1
  3. package/dist/esm/dictionaryUtils.mjs.map +1 -1
  4. package/dist/esm/findFieldInFile.mjs.map +1 -1
  5. package/dist/esm/findKeyAtOffset.mjs.map +1 -1
  6. package/dist/esm/findKeyInContentFile.mjs +3 -3
  7. package/dist/esm/findKeyInContentFile.mjs.map +1 -1
  8. package/dist/esm/findTCallAtOffset.mjs.map +1 -1
  9. package/dist/esm/findUsageFieldAtOffset.mjs.map +1 -1
  10. package/dist/esm/index.mjs +32 -5
  11. package/dist/esm/index.mjs.map +1 -1
  12. package/dist/esm/oxcUtils.mjs.map +1 -1
  13. package/dist/esm/usageAnalyzer.mjs +86 -23
  14. package/dist/esm/usageAnalyzer.mjs.map +1 -1
  15. package/dist/types/callers/index.d.ts +0 -3
  16. package/dist/types/callers/patterns.d.ts +20 -20
  17. package/dist/types/callers/patterns.d.ts.map +1 -1
  18. package/dist/types/dictionaryUtils.d.ts +0 -1
  19. package/dist/types/dictionaryUtils.d.ts.map +1 -1
  20. package/dist/types/findFieldInFile.d.ts +18 -19
  21. package/dist/types/findFieldInFile.d.ts.map +1 -1
  22. package/dist/types/findKeyAtOffset.d.ts +15 -15
  23. package/dist/types/findKeyAtOffset.d.ts.map +1 -1
  24. package/dist/types/findKeyInContentFile.d.ts +20 -20
  25. package/dist/types/findKeyInContentFile.d.ts.map +1 -1
  26. package/dist/types/findTCallAtOffset.d.ts +15 -15
  27. package/dist/types/findTCallAtOffset.d.ts.map +1 -1
  28. package/dist/types/findUsageFieldAtOffset.d.ts +14 -14
  29. package/dist/types/findUsageFieldAtOffset.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +1 -1
  31. package/dist/types/oxcUtils.d.ts +22 -22
  32. package/dist/types/oxcUtils.d.ts.map +1 -1
  33. package/dist/types/usageAnalyzer.d.ts +95 -83
  34. package/dist/types/usageAnalyzer.d.ts.map +1 -1
  35. package/package.json +11 -11
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"oxcUtils.mjs","names":[],"sources":["../../src/oxcUtils.ts"],"sourcesContent":["import { parseSync } from 'oxc-parser';\n\nexport type OxcNode = Record<string, unknown>;\n\n/**\n * Parse `text` into an Oxc program AST.\n * Returns null when the parse completely fails (e.g. unsupported syntax).\n * Oxc is generally resilient to partial/in-progress edits and still returns a\n * usable AST alongside error diagnostics, so this is rare.\n */\nexport const parseText = (text: string): OxcNode | null => {\n try {\n const { program } = parseSync('file.tsx', text);\n\n return program as unknown as OxcNode;\n } catch {\n return null;\n }\n};\n\n/**\n * Depth-first walk of an Oxc AST. Visitor is called for every node that has\n * a `type` property; returning `true` prunes that subtree.\n */\nexport const walkAst = (\n node: unknown,\n // biome-ignore lint/suspicious/noConfusingVoidType: visitors without a return value must stay assignable\n visitor: (node: OxcNode) => boolean | void\n): void => {\n if (!node || typeof node !== 'object') return;\n\n if (Array.isArray(node)) {\n for (const child of node) walkAst(child, visitor);\n\n return;\n }\n const oxcNode = node as OxcNode;\n\n if (typeof oxcNode['type'] !== 'string') return;\n\n if (visitor(oxcNode)) return; // pruned\n\n for (const key of Object.keys(oxcNode)) {\n if (key === 'start' || key === 'end' || key === 'type') continue;\n walkAst(oxcNode[key], visitor);\n }\n};\n\n/**\n * Build a child → parent reference map for upward traversal.\n * Only AST-node-valued properties are followed; primitives are ignored.\n */\nexport const buildParentMap = (root: OxcNode): Map<OxcNode, OxcNode> => {\n const parentMap = new Map<OxcNode, OxcNode>();\n\n const walk = (node: unknown, parent: OxcNode | null): void => {\n if (!node || typeof node !== 'object') return;\n\n if (Array.isArray(node)) {\n for (const child of node) walk(child, parent);\n\n return;\n }\n const oxcNode = node as OxcNode;\n\n if (typeof oxcNode['type'] !== 'string') return;\n\n if (parent) parentMap.set(oxcNode, parent);\n\n for (const key of Object.keys(oxcNode)) {\n if (key === 'start' || key === 'end' || key === 'type') continue;\n walk(oxcNode[key], oxcNode);\n }\n };\n\n walk(root, null);\n return parentMap;\n};\n\n/** Byte offset of the node's start. */\nexport const nodeStart = (node: OxcNode): number =>\n (node['start'] as number | undefined) ??\n (node['range'] as number[] | undefined)?.[0] ??\n 0;\n\n/** Byte offset of the node's end. */\nexport const nodeEnd = (node: OxcNode): number =>\n (node['end'] as number | undefined) ??\n (node['range'] as number[] | undefined)?.[1] ??\n 0;\n\n/** Returns `true` when `offset` falls inside the node's span (inclusive). */\nexport const nodeContainsOffset = (node: OxcNode, offset: number): boolean =>\n offset >= nodeStart(node) && offset <= nodeEnd(node);\n\n/**\n * Returns `true` for nodes that carry a static string value:\n * Literal / StringLiteral, and no-substitution TemplateLiteral.\n */\nexport const isStringLiteralNode = (node: OxcNode): boolean => {\n const nodeType = node['type'] as string;\n\n if (nodeType === 'Literal' || nodeType === 'StringLiteral') {\n return typeof node['value'] === 'string';\n }\n\n if (nodeType === 'TemplateLiteral') {\n return ((node['expressions'] as unknown[])?.length ?? 0) === 0;\n }\n\n return false;\n};\n\n/**\n * Extract the static string value from a literal node, or null when the node\n * is not a static string.\n */\nexport const getStaticStringValue = (\n node: OxcNode | undefined\n): string | null => {\n if (!node) return null;\n const nodeType = node['type'] as string;\n\n if (\n (nodeType === 'Literal' || nodeType === 'StringLiteral') &&\n typeof node['value'] === 'string'\n ) {\n return node['value'];\n }\n\n if (\n nodeType === 'TemplateLiteral' &&\n ((node['expressions'] as unknown[])?.length ?? 0) === 0\n ) {\n const templateElements = node['quasis'] as OxcNode[] | undefined;\n const valueNode = templateElements?.[0]?.['value'] as OxcNode | undefined;\n\n return (\n (valueNode?.['cooked'] as string) ??\n (valueNode?.['raw'] as string) ??\n null\n );\n }\n\n return null;\n};\n\n/** Extract the string value from the Nth argument of a call expression. */\nexport const getStringArgAt = (\n callNode: OxcNode,\n index: number\n): string | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n\n if (!argumentsList || argumentsList.length <= index) return null;\n\n return getStaticStringValue(argumentsList[index]);\n};\n\n/** Extract the string value from the first argument of a call expression. */\nexport const getFirstStringArg = (callExpressionNode: OxcNode): string | null =>\n getStringArgAt(callExpressionNode, 0);\n\n/**\n * Find the property node named `propertyName` inside an ObjectExpression.\n */\nexport const getObjectPropertyNode = (\n objectExpression: OxcNode | undefined,\n propertyName: string\n): OxcNode | null => {\n if (objectExpression?.['type'] !== 'ObjectExpression') return null;\n\n for (const property of (objectExpression['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 keyName =\n keyNode?.['type'] === 'Identifier'\n ? (keyNode['name'] as string)\n : typeof keyNode?.['value'] === 'string'\n ? (keyNode['value'] as string)\n : null;\n\n if (keyName === propertyName) return property;\n }\n\n return null;\n};\n\n/**\n * Read a static string property from an ObjectExpression argument,\n * e.g. the `namespace` of `useI18n({ namespace: 'about' })`.\n */\nexport const getObjectPropertyString = (\n objectExpression: OxcNode | undefined,\n propertyName: string\n): string | null => {\n const property = getObjectPropertyNode(objectExpression, propertyName);\n\n if (!property) return null;\n\n return getStaticStringValue(property['value'] as OxcNode | undefined);\n};\n\n/** Get the identifier or string name of an object-property key node. */\nexport const getPropertyKeyName = (\n keyNode: OxcNode | undefined\n): string | null => {\n if (!keyNode) return null;\n const nodeType = keyNode['type'] as string;\n\n if (nodeType === 'Identifier') return keyNode['name'] as string;\n\n if (\n (nodeType === 'Literal' || nodeType === 'StringLiteral') &&\n typeof keyNode['value'] === 'string'\n ) {\n return keyNode['value'];\n }\n\n return null;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"oxcUtils.mjs","names":[],"sources":["../../src/oxcUtils.ts"],"sourcesContent":["import { parseSync } from 'oxc-parser';\n\nexport type OxcNode = Record<string, unknown>;\n\n/**\n * Parse `text` into an Oxc program AST.\n * Returns null when the parse completely fails (e.g. unsupported syntax).\n * Oxc is generally resilient to partial/in-progress edits and still returns a\n * usable AST alongside error diagnostics, so this is rare.\n */\nexport const parseText = (text: string): OxcNode | null => {\n try {\n const { program } = parseSync('file.tsx', text);\n\n return program as unknown as OxcNode;\n } catch {\n return null;\n }\n};\n\n/**\n * Depth-first walk of an Oxc AST. Visitor is called for every node that has\n * a `type` property; returning `true` prunes that subtree.\n */\nexport const walkAst = (\n node: unknown,\n // biome-ignore lint/suspicious/noConfusingVoidType: visitors without a return value must stay assignable\n visitor: (node: OxcNode) => boolean | void\n): void => {\n if (!node || typeof node !== 'object') return;\n\n if (Array.isArray(node)) {\n for (const child of node) walkAst(child, visitor);\n\n return;\n }\n const oxcNode = node as OxcNode;\n\n if (typeof oxcNode['type'] !== 'string') return;\n\n if (visitor(oxcNode)) return; // pruned\n\n for (const key of Object.keys(oxcNode)) {\n if (key === 'start' || key === 'end' || key === 'type') continue;\n walkAst(oxcNode[key], visitor);\n }\n};\n\n/**\n * Build a child → parent reference map for upward traversal.\n * Only AST-node-valued properties are followed; primitives are ignored.\n */\nexport const buildParentMap = (root: OxcNode): Map<OxcNode, OxcNode> => {\n const parentMap = new Map<OxcNode, OxcNode>();\n\n const walk = (node: unknown, parent: OxcNode | null): void => {\n if (!node || typeof node !== 'object') return;\n\n if (Array.isArray(node)) {\n for (const child of node) walk(child, parent);\n\n return;\n }\n const oxcNode = node as OxcNode;\n\n if (typeof oxcNode['type'] !== 'string') return;\n\n if (parent) parentMap.set(oxcNode, parent);\n\n for (const key of Object.keys(oxcNode)) {\n if (key === 'start' || key === 'end' || key === 'type') continue;\n walk(oxcNode[key], oxcNode);\n }\n };\n\n walk(root, null);\n return parentMap;\n};\n\n/** Byte offset of the node's start. */\nexport const nodeStart = (node: OxcNode): number =>\n (node['start'] as number | undefined) ??\n (node['range'] as number[] | undefined)?.[0] ??\n 0;\n\n/** Byte offset of the node's end. */\nexport const nodeEnd = (node: OxcNode): number =>\n (node['end'] as number | undefined) ??\n (node['range'] as number[] | undefined)?.[1] ??\n 0;\n\n/** Returns `true` when `offset` falls inside the node's span (inclusive). */\nexport const nodeContainsOffset = (node: OxcNode, offset: number): boolean =>\n offset >= nodeStart(node) && offset <= nodeEnd(node);\n\n/**\n * Returns `true` for nodes that carry a static string value:\n * Literal / StringLiteral, and no-substitution TemplateLiteral.\n */\nexport const isStringLiteralNode = (node: OxcNode): boolean => {\n const nodeType = node['type'] as string;\n\n if (nodeType === 'Literal' || nodeType === 'StringLiteral') {\n return typeof node['value'] === 'string';\n }\n\n if (nodeType === 'TemplateLiteral') {\n return ((node['expressions'] as unknown[])?.length ?? 0) === 0;\n }\n\n return false;\n};\n\n/**\n * Extract the static string value from a literal node, or null when the node\n * is not a static string.\n */\nexport const getStaticStringValue = (\n node: OxcNode | undefined\n): string | null => {\n if (!node) return null;\n const nodeType = node['type'] as string;\n\n if (\n (nodeType === 'Literal' || nodeType === 'StringLiteral') &&\n typeof node['value'] === 'string'\n ) {\n return node['value'];\n }\n\n if (\n nodeType === 'TemplateLiteral' &&\n ((node['expressions'] as unknown[])?.length ?? 0) === 0\n ) {\n const templateElements = node['quasis'] as OxcNode[] | undefined;\n const valueNode = templateElements?.[0]?.['value'] as OxcNode | undefined;\n\n return (\n (valueNode?.['cooked'] as string) ??\n (valueNode?.['raw'] as string) ??\n null\n );\n }\n\n return null;\n};\n\n/** Extract the string value from the Nth argument of a call expression. */\nexport const getStringArgAt = (\n callNode: OxcNode,\n index: number\n): string | null => {\n const argumentsList = callNode['arguments'] as OxcNode[] | undefined;\n\n if (!argumentsList || argumentsList.length <= index) return null;\n\n return getStaticStringValue(argumentsList[index]);\n};\n\n/** Extract the string value from the first argument of a call expression. */\nexport const getFirstStringArg = (callExpressionNode: OxcNode): string | null =>\n getStringArgAt(callExpressionNode, 0);\n\n/**\n * Find the property node named `propertyName` inside an ObjectExpression.\n */\nexport const getObjectPropertyNode = (\n objectExpression: OxcNode | undefined,\n propertyName: string\n): OxcNode | null => {\n if (objectExpression?.['type'] !== 'ObjectExpression') return null;\n\n for (const property of (objectExpression['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 keyName =\n keyNode?.['type'] === 'Identifier'\n ? (keyNode['name'] as string)\n : typeof keyNode?.['value'] === 'string'\n ? (keyNode['value'] as string)\n : null;\n\n if (keyName === propertyName) return property;\n }\n\n return null;\n};\n\n/**\n * Read a static string property from an ObjectExpression argument,\n * e.g. the `namespace` of `useI18n({ namespace: 'about' })`.\n */\nexport const getObjectPropertyString = (\n objectExpression: OxcNode | undefined,\n propertyName: string\n): string | null => {\n const property = getObjectPropertyNode(objectExpression, propertyName);\n\n if (!property) return null;\n\n return getStaticStringValue(property['value'] as OxcNode | undefined);\n};\n\n/** Get the identifier or string name of an object-property key node. */\nexport const getPropertyKeyName = (\n keyNode: OxcNode | undefined\n): string | null => {\n if (!keyNode) return null;\n const nodeType = keyNode['type'] as string;\n\n if (nodeType === 'Identifier') return keyNode['name'] as string;\n\n if (\n (nodeType === 'Literal' || nodeType === 'StringLiteral') &&\n typeof keyNode['value'] === 'string'\n ) {\n return keyNode['value'];\n }\n\n return null;\n};\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ import { buildParentMap, getObjectPropertyNode, getObjectPropertyString, getProp
7
7
  * (react `.value`, vue refs `.value`, editor `.raw`/`.use`): they are skipped
8
8
  * when building field paths.
9
9
  */
10
- const ACCESSOR_PROPERTY_NAMES = new Set([
10
+ const ACCESSOR_PROPERTY_NAMES = /* @__PURE__ */ new Set([
11
11
  "value",
12
12
  "raw",
13
13
  "use"
@@ -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.set(valueNode["name"], {
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.set(variableName, {
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.set(variableName, {
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.set(localName, {
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.get(callee["name"]);
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]);
@@ -321,7 +377,8 @@ const collectCallUsages = (state, program) => {
321
377
  if (!descriptor) return;
322
378
  const messageId = getSelfCallMessageId(node);
323
379
  if (!messageId) return;
324
- const resolved = resolveMessageId(descriptor, readValueSources(node, descriptor.namespaceSources), messageId);
380
+ const fixedDictionaryKey = readValueSources(node, descriptor.namespaceSources);
381
+ const resolved = resolveMessageId(descriptor, fixedDictionaryKey, messageId);
325
382
  if (!resolved) return;
326
383
  state.usages.push({
327
384
  ...resolved,
@@ -355,7 +412,8 @@ const collectTaggedTemplateUsage = (state, node) => {
355
412
  }
356
413
  }
357
414
  if (!messageId) return;
358
- const resolved = resolveMessageId(descriptor, readValueSources(node, descriptor.namespaceSources), messageId);
415
+ const fixedDictionaryKey = readValueSources(node, descriptor.namespaceSources);
416
+ const resolved = resolveMessageId(descriptor, fixedDictionaryKey, messageId);
359
417
  if (!resolved) return;
360
418
  state.usages.push({
361
419
  ...resolved,
@@ -386,7 +444,7 @@ const getJsxAttributeString = (openingElement, attributeName) => {
386
444
  * `<Trans i18nKey="richText" />`, `<Trans id="…" />`.
387
445
  */
388
446
  const collectJsxUsages = (state, program) => {
389
- 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));
390
448
  const fallbackNamespace = translatorNamespaces.size === 1 ? [...translatorNamespaces][0] : null;
391
449
  walkAst(program, (node) => {
392
450
  if (node["type"] !== "JSXOpeningElement") return;
@@ -452,7 +510,7 @@ const collectMemberUsages = (state, program, parentMap) => {
452
510
  if (nodeType !== "Identifier" && nodeType !== "JSXIdentifier") return;
453
511
  const rawName = node["name"];
454
512
  const variableName = rawName.startsWith("$") ? rawName.slice(1) : rawName;
455
- const binding = state.contentBindings.get(variableName);
513
+ const binding = resolveBinding(state.contentBindings, variableName, nodeStart(node));
456
514
  if (!binding) return;
457
515
  const parentType = parentMap.get(node)?.["type"];
458
516
  if (parentType?.startsWith("TS") || parentType === "JSXAttribute") return;
@@ -528,17 +586,20 @@ const collectMessageUsages = (text) => {
528
586
  const program = parseText(text);
529
587
  if (!program) return [];
530
588
  const fileImportSources = collectImportSources(program);
589
+ const parentMap = buildParentMap(program);
531
590
  const state = {
532
591
  usages: [],
533
592
  translatorBindings: /* @__PURE__ */ new Map(),
534
593
  contentBindings: /* @__PURE__ */ new Map(),
535
594
  activeDescriptors: callers_exports.ALL_CALLERS.filter((descriptor) => (0, callers_exports.isCallerActive)(descriptor, fileImportSources)),
536
- importedLocalNames: collectImportedLocalNames(program)
595
+ importedLocalNames: collectImportedLocalNames(program),
596
+ parentMap,
597
+ program
537
598
  };
538
599
  collectBindings(state, program);
539
600
  collectCallUsages(state, program);
540
601
  collectJsxUsages(state, program);
541
- collectMemberUsages(state, program, buildParentMap(program));
602
+ collectMemberUsages(state, program, parentMap);
542
603
  return state.usages.sort((first, second) => first.start - second.start);
543
604
  };
544
605
  /**
@@ -635,24 +696,26 @@ const collectCallerBindings = (text) => {
635
696
  translatorBindings: /* @__PURE__ */ new Map(),
636
697
  contentBindings: /* @__PURE__ */ new Map(),
637
698
  activeDescriptors: callers_exports.ALL_CALLERS.filter((descriptor) => (0, callers_exports.isCallerActive)(descriptor, fileImportSources)),
638
- importedLocalNames: collectImportedLocalNames(program)
699
+ importedLocalNames: collectImportedLocalNames(program),
700
+ parentMap: buildParentMap(program),
701
+ program
639
702
  };
640
703
  collectBindings(state, program);
641
- return [...[...state.contentBindings.entries()].map(([variableName, binding]) => ({
704
+ return [...[...state.contentBindings.entries()].flatMap(([variableName, bindings]) => bindings.map((binding) => ({
642
705
  variableName,
643
706
  dictionaryKey: binding.dictionaryKey,
644
707
  basePath: binding.basePath,
645
708
  bindingKind: "content",
646
709
  callerName: binding.descriptor.callerName,
647
710
  library: binding.descriptor.library
648
- })), ...[...state.translatorBindings.entries()].filter(([, binding]) => binding.dictionaryKey !== null).map(([variableName, binding]) => ({
711
+ }))), ...[...state.translatorBindings.entries()].flatMap(([variableName, bindings]) => bindings.filter((binding) => binding.dictionaryKey !== null).map((binding) => ({
649
712
  variableName,
650
713
  dictionaryKey: binding.dictionaryKey,
651
714
  basePath: binding.keyPrefix,
652
715
  bindingKind: "translator",
653
716
  callerName: binding.descriptor.callerName,
654
717
  library: binding.descriptor.library
655
- }))];
718
+ })))];
656
719
  };
657
720
  /**
658
721
  * Find the most specific translation usage at `offset`.