@intlayer/babel 9.4.0-canary.0 → 9.4.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.
Files changed (28) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +5 -4
  3. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
  4. package/dist/cjs/babel-plugin-intlayer-optimize.cjs +28 -7
  5. package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
  6. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +18 -12
  7. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
  8. package/dist/cjs/index.cjs +1 -0
  9. package/dist/cjs/transformers.cjs +13 -12
  10. package/dist/cjs/transformers.cjs.map +1 -1
  11. package/dist/esm/babel-plugin-intlayer-field-rename.mjs +6 -5
  12. package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
  13. package/dist/esm/babel-plugin-intlayer-optimize.mjs +28 -7
  14. package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
  15. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +18 -13
  16. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
  17. package/dist/esm/index.mjs +2 -2
  18. package/dist/esm/transformers.mjs +14 -13
  19. package/dist/esm/transformers.mjs.map +1 -1
  20. package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
  21. package/dist/types/babel-plugin-intlayer-optimize.d.ts +20 -6
  22. package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
  23. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +10 -3
  24. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
  25. package/dist/types/index.d.ts +2 -2
  26. package/dist/types/transformers.d.ts +11 -6
  27. package/dist/types/transformers.d.ts.map +1 -1
  28. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-usage-analyzer.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-usage-analyzer.ts"],"sourcesContent":["import type { NodePath, PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport type {\n CallerDescriptor,\n CallerValueSource,\n} from '@intlayer/config/callers';\nimport type { NestedFieldReferences } from '@intlayer/core/dictionaryManipulator';\n\n// ── PruneContext types ────────────────────────────────────────────────────────\n\n/**\n * Dictionary field usage result for a single dictionary key.\n *\n * 'all' → could not determine statically which fields are used;\n * keep every field (no pruning possible).\n * Set<string> → the exact top-level content field names that were accessed.\n */\nexport type DictionaryFieldUsage = Set<string> | 'all';\n\n/**\n * One node in the nested field-rename tree.\n *\n * shortName – the compact alias assigned to this field name.\n * children – rename table for the next level of user-defined keys inside\n * this field's value (empty when the value is a leaf / primitive).\n */\nexport type NestedRenameEntry = {\n shortName: string;\n children: NestedRenameMap;\n};\n\n/** A level of the field-rename tree, mapping original field names to entries. */\nexport type NestedRenameMap = Map<string, NestedRenameEntry>;\n\n/**\n * One opaque consumption of a dictionary content value: the value at\n * `fieldPath` escapes static analysis (passed as-is to a child component or a\n * function argument), so its children must keep their original key names in\n * the compiled JSON.\n */\nexport type OpaqueFieldOccurrence = {\n /**\n * Original (pre-rename) user-defined field names from the content root to\n * the escaping value, e.g. `['sections', 'hero']` for\n * `<Card data={content.sections.hero} />`.\n */\n fieldPath: string[];\n\n /** Source locations (`filePath:line`) where the value escapes. */\n locations: string[];\n};\n\n/**\n * Shared mutable state created once by the vite plugin and passed by reference\n * to the usage-analyzer (writer) and the prune/minify plugins (readers).\n *\n * All mutations happen during the usage-analysis `buildStart` phase; readers\n * only access this state during the subsequent `transform` phase.\n */\nexport type PruneContext = {\n /**\n * Maps every dictionary key seen in source files to the set of top-level\n * content fields statically accessed, or `'all'` when the access pattern\n * could not be determined.\n */\n dictionaryKeyToFieldUsageMap: Map<string, DictionaryFieldUsage>;\n\n /**\n * Dictionary keys for which the prune/minify step must be skipped entirely\n * because an edge case was detected during analysis or structure recognition.\n */\n dictionariesWithEdgeCases: Set<string>;\n\n /**\n * True if at least one source file failed to parse during the analysis phase.\n * The prune plugin uses this flag conservatively: any dictionary key without\n * a usage entry might have been referenced by the unparsable file.\n */\n hasUnparsableSourceFiles: boolean;\n\n /**\n * Maps dictionary keys to the source file paths where the result of\n * `useIntlayer` / `getIntlayer` was assigned to a plain variable, making\n * static field analysis impossible.\n */\n dictionaryKeysWithUntrackedBindings: Map<string, string[]>;\n\n /**\n * Maps each dictionary key to a nested field-rename tree built after the\n * usage analysis phase (only populated when `build.minify` is active and\n * the field usage for that dictionary is a finite `Set<string>`).\n */\n dictionaryKeyToFieldRenameMap: Map<string, NestedRenameMap>;\n\n /**\n * Maps each dictionary key to the content field paths whose values are\n * consumed \"opaquely\" (passed as-is to a child component or function\n * argument). When an opaque value has nested user-defined structure, its\n * children must not be renamed.\n *\n * The inner map is keyed by the dot-joined field path for de-duplication\n * only; the authoritative path is {@link OpaqueFieldOccurrence.fieldPath}.\n *\n * Structure: dictionaryKey → joinedFieldPath → { fieldPath, locations }\n */\n dictionaryKeysWithOpaqueFields: Map<\n string,\n Map<string, OpaqueFieldOccurrence>\n >;\n\n /**\n * Dictionary keys for which field-key renaming must be skipped even if a\n * finite field-usage set was determined.\n *\n * Populated for dictionaries whose plain-variable bindings were resolved by\n * the framework-specific extractor (Vue / Svelte SFCs), because the Babel\n * rename plugin cannot update the source-code property accesses for those\n * indirect patterns (Vue `.value.field` / Svelte `$store.field`).\n *\n * Pruning and basic minification still apply; only field-key renaming is\n * suppressed.\n */\n dictionariesSkippingFieldRename: Set<string>;\n\n /**\n * Plain variable bindings that require a framework-specific secondary pass.\n *\n * Populated during the Babel analysis phase for `.vue` and `.svelte` source\n * files where direct field access is not visible to Babel scope analysis:\n * - Vue: `content.value.fieldName` – the `.value` ref-accessor is hidden\n * - Svelte: `$varName.fieldName` – the `$` prefix creates a new identifier\n *\n * Structure: filePath → [{variableName, dictionaryKey}, …]\n */\n pendingFrameworkAnalysis: Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >;\n};\n\nexport const createPruneContext = (): PruneContext => ({\n dictionaryKeyToFieldUsageMap: new Map(),\n dictionariesWithEdgeCases: new Set(),\n hasUnparsableSourceFiles: false,\n dictionaryKeysWithUntrackedBindings: new Map(),\n dictionaryKeyToFieldRenameMap: new Map(),\n dictionaryKeysWithOpaqueFields: new Map<\n string,\n Map<string, OpaqueFieldOccurrence>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\n});\n\n/**\n * Records `nest()` references in a prune context so nest targets stay\n * resolvable — and no larger than they need to be — after purge and minify.\n *\n * A nested node stores the ORIGINAL dot path of the field it reads\n * (`nest('common', 'period')` → `'period'`) and `getNesting` resolves it at\n * runtime against the compiled JSON. That drives three adjustments:\n *\n * - Nest targets never take part in field renaming, since the stored path\n * would no longer match the renamed keys.\n * - A target already consumed by a call site keeps its analysed fields plus\n * the nested ones, so neither consumer loses content.\n * - A target reached *only* through `nest()` is given a usage entry holding\n * exactly the nested fields. Without one, the purge pass leaves the whole\n * dictionary untouched; with one, it trims the dictionary down to what the\n * nest references actually read. This matters most for `dynamic` / `fetch`\n * dictionaries, whose full static snapshot would otherwise be pulled into\n * the eager bundle just to satisfy a single nested field.\n *\n * Seeding is skipped when a source file failed to parse: that file could\n * reference the dictionary in a way the analysis never saw, which is the same\n * guard the purge pass applies to keys with no usage entry.\n *\n * @param pruneContext - Shared analysis state to update in place.\n * @param nestedDictionaryReferences - Nest-target key → referenced top-level\n * fields, as returned by `getNestedDictionaryReferences`.\n */\nexport const preserveNestedDictionaryFields = (\n pruneContext: PruneContext,\n nestedDictionaryReferences: Map<string, NestedFieldReferences>\n): void => {\n for (const [\n dictionaryKey,\n nestedFields,\n ] of nestedDictionaryReferences.entries()) {\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n const currentFieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n // A whole-dictionary reference (`nest('common')`) reads every field, so no\n // field set can describe it — leave the dictionary unpruned.\n if (nestedFields === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n continue;\n }\n\n if (!currentFieldUsage) {\n if (pruneContext.hasUnparsableSourceFiles) continue;\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n new Set(nestedFields)\n );\n continue;\n }\n\n // Already consumed by a call site that reads every field.\n if (currentFieldUsage === 'all') continue;\n\n for (const nestedField of nestedFields) {\n currentFieldUsage.add(nestedField);\n }\n }\n};\n\n// ── Usage-analyzer Babel plugin ───────────────────────────────────────────────\n\n/**\n * Methods the runtime attaches to a resolved content object rather than fields\n * declared in a `.content` file.\n *\n * `vanilla-intlayer`'s `useIntlayer` / `useDictionary` assign `onChange` onto\n * the content they return, so `useIntlayer('app').onChange(cb)` is a\n * subscription — not a read of a field named `onChange`. Without this list the\n * analyser records `onChange` as the only consumed field and the purge pass\n * strips every real field from the compiled dictionary.\n *\n * The analyser looks *through* these calls: the callback parameter is analysed\n * as a fresh content root, and the call result is analysed again because the\n * helpers return the content object for chaining.\n */\nexport const CHAINABLE_RUNTIME_METHOD_NAMES = new Set(['onChange']);\n\n/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = ['useIntlayer', 'getIntlayer'] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n// ── Compat-adapter namespace callers ──────────────────────────────────────────\n\n// The caller-descriptor model is shared across the build pipeline (babel, swc\n// wire format), the LSP and the compat packages' bundler plugins. It lives in\n// `@intlayer/config/callers`; see that module for the full documentation.\nexport type {\n CallerDescriptor,\n CallerResultShape,\n CallerValueSource,\n} from '@intlayer/config/callers';\n\n/**\n * Configuration entry for a single compat namespace caller.\n *\n * @deprecated Alias kept for backward compatibility — use the shared\n * {@link CallerDescriptor} from `@intlayer/config/callers` instead.\n */\nexport type CompatCallerConfig = CallerDescriptor;\n\n/**\n * Default registry of compat namespace callers.\n *\n * Intentionally empty — compat-specific caller configurations belong in their\n * respective adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) and are injected into `makeUsageAnalyzerBabelPlugin`\n * via the `compatCallers` option. Centralising them here would couple the\n * core `@intlayer/babel` package to every compat adapter, which violates the\n * design principle that compat logic lives in compat packages.\n */\nexport const DEFAULT_COMPAT_CALLERS: CompatCallerConfig[] = [];\n\n/** Default namespace used by compat callers when no namespace argument is given. */\nconst DEFAULT_COMPAT_NAMESPACE = 'translation';\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n if (existingUsage instanceof Set) {\n // Merge in place — the set is owned by the map.\n for (const fieldName of fieldUsage) existingUsage.add(fieldName);\n return;\n }\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n new Set(fieldUsage)\n );\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that the content value at `fieldPath` is consumed opaquely. */\n const markOpaqueField = (\n fieldPath: string[],\n line: number | undefined\n ): void => {\n const pathToOccurrence =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey) ??\n new Map<string, OpaqueFieldOccurrence>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const joinedFieldPath = fieldPath.join('.');\n const occurrence = pathToOccurrence.get(joinedFieldPath) ?? {\n fieldPath: [...fieldPath],\n locations: [],\n };\n if (!occurrence.locations.includes(location)) {\n occurrence.locations.push(location);\n }\n pathToOccurrence.set(joinedFieldPath, occurrence);\n pruneContext.dictionaryKeysWithOpaqueFields.set(\n dictionaryKey,\n pathToOccurrence\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n /**\n * Analyses how the content value at `fieldPath` (currently referenced by\n * `refPath`) is consumed, to detect opaque consumption (passing a dictionary\n * value as-is to a prop or function argument).\n *\n * Member-access chains and destructuring are followed recursively with an\n * extended field path, so the *terminal* consumption of a chain like\n * `content.sections.hero` decides opacity — mirroring how far the\n * source-code renamer can rewrite accesses. When the terminal consumption\n * escapes static tracking, the deepest reached field path is marked opaque\n * so the children of that value keep their original key names.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n const parentNode = refPath.parent;\n const parentPath = refPath.parentPath;\n\n // 1. Chained member access (e.g. field.sub or field?.sub): follow the\n // chain with an extended path and analyse the terminal consumption.\n if (\n parentPath &&\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …) is transparent: the JSON renamers\n // apply the same rename map to every array element.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, fieldPath);\n return;\n }\n\n if (\n !memberNode.computed &&\n babelTypes.isIdentifier(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.name,\n ]);\n return;\n }\n\n if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.value,\n ]);\n return;\n }\n\n // Dynamic computed access (field[expr]): the renamer cannot rewrite the\n // key, so the children of the current path must keep their names.\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field): follow each binding.\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);\n return;\n }\n\n // 3. Ignored patterns (e.g. array literals [content])\n if (babelTypes.isArrayExpression(parentNode)) {\n return;\n }\n\n // 4. Opaque consumption (passed to prop, function, etc.)\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n };\n\n /**\n * Analyses a destructuring pattern applied to the content value at\n * `fieldPath`, recursively following each bound local variable's references\n * via {@link analyzeOpaqueUsage}.\n *\n * Patterns the source-code renamer cannot rewrite (rest elements, dynamic\n * computed keys, array patterns) mark the corresponding path opaque.\n */\n const analyzeOpaqueDestructuring = (\n objectPattern: BabelTypes.ObjectPattern,\n scopePath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n for (const property of objectPattern.properties) {\n // A rest element re-exposes the remaining fields under their original\n // names — nothing at this level may be renamed.\n if (babelTypes.isRestElement(property)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be renamed.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const childFieldPath = [...fieldPath, keyName];\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isObjectPattern(bindingTarget)) {\n analyzeOpaqueDestructuring(bindingTarget, scopePath, childFieldPath);\n continue;\n }\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);\n if (!localVarBinding) continue;\n for (const referencePath of localVarBinding.referencePaths) {\n analyzeOpaqueUsage(referencePath, childFieldPath);\n }\n continue;\n }\n\n // Array patterns and other targets escape the rename walk.\n markOpaqueField(childFieldPath, property.loc?.start.line);\n }\n };\n\n /**\n * Helper to collect field names from an ObjectPattern (destructuring).\n * Returns true if successful, false when the accessed fields cannot be\n * determined statically (rest element or dynamic computed key → 'all').\n */\n const collectFieldsFromObjectPattern = (\n pattern: BabelTypes.ObjectPattern,\n initPath: NodePath<BabelTypes.Node>,\n targetSet: Set<string>\n ): boolean => {\n if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) {\n return false;\n }\n\n for (const property of pattern.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be attributed to a\n // specific field — the whole dictionary must be kept.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n return false;\n }\n\n let fieldName: string | undefined;\n\n if (!property.computed && babelTypes.isIdentifier(property.key)) {\n fieldName = property.key.name;\n } else if (babelTypes.isStringLiteral(property.key)) {\n fieldName = property.key.value;\n }\n\n if (!fieldName) continue;\n\n targetSet.add(fieldName);\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const variableBinding = initPath.scope.getBinding(bindingTarget.name);\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, [fieldName]);\n }\n }\n } else if (babelTypes.isObjectPattern(bindingTarget)) {\n // Nested pattern: const { fieldA: { nested } } = … — follow each\n // nested binding so deeper opaque consumption is detected.\n analyzeOpaqueDestructuring(bindingTarget, initPath, [fieldName]);\n } else {\n // Array patterns and other targets escape the rename walk.\n markOpaqueField([fieldName], property.loc?.start.line);\n }\n }\n return true;\n };\n\n /**\n * Returns the call path when `memberExprPath` is a chainable runtime helper\n * being invoked (`content.onChange(cb)`), otherwise `undefined`.\n *\n * See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`\n * reference that is never called is treated as an ordinary field access, so\n * only the invoked form is looked through.\n */\n const getChainedRuntimeCallPath = (\n memberExprPath: NodePath<BabelTypes.Node> | null | undefined,\n fieldName: string\n ): NodePath<BabelTypes.Node> | undefined => {\n if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return undefined;\n if (!memberExprPath) return undefined;\n\n const callPath = memberExprPath.parentPath;\n if (!callPath) return undefined;\n if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) {\n return undefined;\n }\n if (\n (callPath.node as BabelTypes.CallExpression).callee !==\n memberExprPath.node\n ) {\n return undefined;\n }\n\n return callPath;\n };\n\n /**\n * Analyses the callbacks handed to a chainable runtime helper.\n *\n * The callback receives the freshly-resolved content root, so its parameter\n * is walked exactly like `const content = useIntlayer('key')` — either as a\n * plain binding or as a destructuring pattern.\n */\n const analyzeChainedCallbackArguments = (\n callPath: NodePath<BabelTypes.Node>\n ): void => {\n const argumentPaths = callPath.get(\n 'arguments'\n ) as NodePath<BabelTypes.Node>[];\n\n for (const argumentPath of argumentPaths) {\n if (\n !argumentPath.isArrowFunctionExpression() &&\n !argumentPath.isFunctionExpression()\n ) {\n continue; // not a callback (e.g. an options object) – nothing to read\n }\n\n const [firstParameterPath] = argumentPath.get(\n 'params'\n ) as NodePath<BabelTypes.Node>[];\n if (!firstParameterPath) continue; // callback ignores the content\n\n // (content) => … — walk the parameter's references.\n if (firstParameterPath.isIdentifier()) {\n const parameterBinding = argumentPath.scope.getBinding(\n firstParameterPath.node.name\n );\n if (!parameterBinding) {\n markUntrackedBinding();\n continue;\n }\n\n const accessedFieldNames = new Set<string>();\n if (\n analyzeContentBindingReferences(parameterBinding, accessedFieldNames)\n ) {\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n }\n continue;\n }\n\n // ({ title }) => … — destructured directly in the parameter list.\n if (firstParameterPath.isObjectPattern()) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n firstParameterPath.node,\n argumentPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n continue;\n }\n\n markUntrackedBinding();\n }\n };\n\n /**\n * Walks every reference of a local binding holding the content root,\n * collecting the top-level fields it reads into `accessedTopLevelFieldNames`.\n *\n * @returns `true` when a reference escaped static tracking, meaning the whole\n * dictionary must be kept.\n */\n const analyzeContentBindingReferences = (\n variableBinding: { referencePaths: NodePath<BabelTypes.Node>[] },\n accessedTopLevelFieldNames: Set<string>\n ): boolean => {\n for (const variableReferencePath of variableBinding.referencePaths) {\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.name;\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = variableReferencePath.parentPath;\n\n // content.onChange(cb) — a runtime subscription, not a field read.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n return true;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // The binding's value escapes into an array literal\n // (e.g. `[appleWatch, airpods].map((entry) => entry.name)`). Its fields\n // are then accessed through iteration that Babel cannot follow back to\n // this dictionary, so we cannot know which fields are used. This is the\n // canonical meta-record / collection access pattern — conservatively\n // keep every field rather than pruning the content to nothing.\n return true;\n } else if (\n // Solid / Angular: content() signal accessor → content().field\n (babelTypes.isCallExpression(referenceParentNode) ||\n babelTypes.isOptionalCallExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callExprPath = variableReferencePath.parentPath;\n const callParent = callExprPath?.parent;\n\n if (\n callParent &&\n (babelTypes.isMemberExpression(callParent) ||\n babelTypes.isOptionalMemberExpression(callParent)) &&\n (callParent as BabelTypes.MemberExpression).object ===\n callExprPath?.node\n ) {\n // content().field\n const memberExpr = callParent as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpr.computed &&\n babelTypes.isIdentifier(memberExpr.property)\n ) {\n fieldName = memberExpr.property.name;\n } else if (\n memberExpr.computed &&\n babelTypes.isStringLiteral(memberExpr.property)\n ) {\n fieldName = memberExpr.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = callExprPath?.parentPath;\n\n // content().onChange(cb) — a runtime subscription, not a field.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);\n } else {\n // content()[dynamicKey] – cannot resolve statically\n return true;\n }\n } else if (\n callParent &&\n babelTypes.isVariableDeclarator(callParent) &&\n babelTypes.isObjectPattern(callParent.id) &&\n callExprPath &&\n collectFieldsFromObjectPattern(\n callParent.id,\n callExprPath,\n accessedTopLevelFieldNames\n )\n ) {\n // const { title } = content()\n // fields already added to accessedTopLevelFieldNames by collectFieldsFromObjectPattern\n } else {\n // content() with no field access or passed opaquely → cannot prune\n return true;\n }\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n return true;\n }\n }\n\n return false;\n };\n\n /**\n * Analyses how the content root referenced by `rootPath` is consumed.\n *\n * `rootPath` is the `useIntlayer('key')` call itself, or any expression that\n * evaluates back to the same content object — currently the result of a\n * chainable runtime helper such as `.onChange(…)`.\n */\n const analyzeContentRoot = (rootPath: NodePath<BabelTypes.Node>): void => {\n const parentNode = rootPath.parent;\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n parentNode.id,\n rootPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === rootPath.node\n ) {\n let fieldName: string | undefined;\n\n if (\n !parentNode.computed &&\n babelTypes.isIdentifier(parentNode.property)\n ) {\n fieldName = parentNode.property.name;\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n fieldName = parentNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = rootPath.parentPath;\n\n // useIntlayer('key').onChange(cb) — a runtime subscription: read the\n // fields out of the callback instead of recording `onChange` itself.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n return;\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ───────────────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding = rootPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n const hasUntrackedReferenceAccess = analyzeContentBindingReferences(\n variableBinding,\n accessedTopLevelFieldNames\n );\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n accessedTopLevelFieldNames\n );\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ──────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ──────────\n markUntrackedBinding();\n };\n\n analyzeContentRoot(callExpressionPath);\n};\n\n// ── Compat namespace-caller analysis ──────────────────────────────────────────\n\n/**\n * Reads a fully-static string from an AST node. Returns `undefined` for\n * dynamic values (identifiers, expressions, template literals with\n * interpolations, …).\n */\nconst readStaticString = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n if (!node) return undefined;\n if (babelTypes.isStringLiteral(node)) return node.value;\n if (\n babelTypes.isTemplateLiteral(node) &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n }\n return undefined;\n};\n\n/** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */\nconst firstPathSegment = (path: string): string => path.split('.')[0] ?? path;\n\n/**\n * Reads the static first dot-path segment from a `t()` first-argument node.\n *\n * t('counter.label') → 'counter'\n * t(`counter.${x}`) → 'counter' (static prefix before the first dot)\n * t(`${x}.label`) → undefined (dynamic first segment)\n * t(someVariable) → undefined\n */\nconst readStaticFirstSegment = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n const staticString = readStaticString(babelTypes, node);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Template literal whose first quasi already contains the dot delimiter, e.g.\n // `counter.${index}` → the leading `counter` segment is statically known.\n if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {\n const firstQuasi =\n node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n if (firstQuasi?.includes('.')) {\n return firstPathSegment(firstQuasi);\n }\n }\n return undefined;\n};\n\n/**\n * Reads a static string property from an object expression. Returns\n * `'__default__'` when the property is absent and `undefined` when present but\n * dynamic.\n */\nconst readObjectProperty = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): string | '__default__' | undefined => {\n for (const property of objectExpression.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n const keyMatches =\n (babelTypes.isIdentifier(property.key) &&\n property.key.name === propertyName) ||\n (babelTypes.isStringLiteral(property.key) &&\n property.key.value === propertyName);\n if (!keyMatches) continue;\n const staticValue = readStaticString(babelTypes, property.value);\n return staticValue ?? undefined; // present but dynamic → undefined\n }\n return '__default__'; // property absent\n};\n\n/**\n * Reads a named JSX attribute as a static string and returns it wrapped in a\n * `StringLiteral` node so the result can be fed to the same namespace resolver\n * as a call argument. Handles both `id=\"home.title\"` and `id={'home.title'}`.\n * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).\n */\nconst readJsxAttributeString = (\n babelTypes: typeof BabelTypes,\n openingElement: BabelTypes.JSXOpeningElement,\n attributeName: string\n): BabelTypes.StringLiteral | undefined => {\n for (const attribute of openingElement.attributes) {\n if (!babelTypes.isJSXAttribute(attribute)) continue;\n if (\n !babelTypes.isJSXIdentifier(attribute.name) ||\n attribute.name.name !== attributeName\n ) {\n continue;\n }\n\n const value = attribute.value;\n // id=\"home.title\"\n if (babelTypes.isStringLiteral(value)) return value;\n // id={'home.title'} / id={`home.title`}\n if (babelTypes.isJSXExpressionContainer(value)) {\n const staticString = readStaticString(babelTypes, value.expression);\n if (staticString !== undefined) {\n return babelTypes.stringLiteral(staticString);\n }\n }\n return undefined; // attribute present but dynamic\n }\n return undefined; // attribute absent\n};\n\n/**\n * Resolves the namespace (dictionary key) for a compat caller call-site from\n * one `CallerValueSource`. Returns the static key, or `'__default__'` when the\n * configured argument is absent (caller falls back to its default namespace),\n * or `undefined` when the value is present but dynamic.\n */\nconst resolveCompatNamespace = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n source: CallerValueSource\n): string | '__default__' | undefined => {\n if (source.from === 'fixed') return source.value;\n\n if (source.from === 'path-first-segment') {\n const firstArg = callArguments[0];\n // No sensible default: the dictionary key is *derived* from the id, so an\n // absent id means the call cannot be attributed to any dictionary → skip.\n if (firstArg === undefined) return undefined;\n\n // String form: formatMessage('home.title', values)\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Descriptor form: formatMessage({ id: 'home.title', ... }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue === '__default__') return '__default__'; // id absent\n if (idValue !== undefined) return firstPathSegment(idValue); // static id\n return undefined; // id present but dynamic\n }\n\n return undefined; // dynamic first argument\n }\n\n if (source.from === 'argument') {\n const argument = callArguments[source.index];\n if (argument === undefined) return '__default__';\n\n // Direct string namespace: useTranslations('about')\n const staticString = readStaticString(babelTypes, argument);\n if (staticString !== undefined) return staticString;\n\n // Object form: getTranslations({ locale, namespace: 'about' })\n if (babelTypes.isObjectExpression(argument)) {\n return readObjectProperty(babelTypes, argument, 'namespace');\n }\n return undefined; // present but dynamic\n }\n\n // from === 'option'\n const optionsArgument = callArguments[source.argumentIndex];\n if (optionsArgument === undefined) return '__default__';\n if (!babelTypes.isObjectExpression(optionsArgument)) return undefined;\n return readObjectProperty(babelTypes, optionsArgument, source.property);\n};\n\n/**\n * Resolves the namespace from a list of `CallerValueSource`s, tried in\n * declaration order. The first source yielding a static string wins.\n * Returns `'__default__'` when every source reports an absent value, and\n * `undefined` when the namespace is present somewhere but dynamic.\n */\nconst resolveNamespaceFromSources = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[]\n): string | '__default__' | undefined => {\n let sawAbsentValue = false;\n\n for (const source of sources) {\n const resolved = resolveCompatNamespace(babelTypes, callArguments, source);\n if (typeof resolved === 'string' && resolved !== '__default__') {\n return resolved;\n }\n if (resolved === '__default__') sawAbsentValue = true;\n }\n\n return sawAbsentValue ? '__default__' : undefined;\n};\n\n/**\n * Resolves an optional `keyPrefix` for a compat caller. Returns the static\n * prefix string, `null` when no prefix is configured/present, or `undefined`\n * when a prefix is present but dynamic.\n */\nconst resolveCompatKeyPrefix = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[] | undefined\n): string | null | undefined => {\n if (!sources || sources.length === 0) return null;\n const resolved = resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n sources\n );\n if (resolved === '__default__') return null; // prefix absent\n return resolved; // string or undefined (dynamic)\n};\n\n/**\n * Climbs past an enclosing `await` expression so that\n * `const t = await getTranslations('ns')` is resolved to its variable\n * declarator the same way the synchronous form is.\n */\nconst unwrapAwait = (\n babelTypes: typeof BabelTypes,\n path: NodePath<BabelTypes.Node>\n): NodePath<BabelTypes.Node> => {\n const parentPath = path.parentPath;\n if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) {\n return parentPath;\n }\n return path;\n};\n\n/**\n * Analyses how the translation function produced by a compat namespace caller\n * (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,\n * `useI18n`) is consumed, then records the accessed top-level dictionary fields\n * into `pruneContext`.\n *\n * Dictionaries consumed this way are always added to\n * `dictionariesSkippingFieldRename`: the field accesses are string-literal\n * dot-paths inside `t()` calls, which the field-rename plugin cannot rewrite,\n * so renaming the compiled JSON keys would break runtime lookups. Pruning\n * (top-level field removal) remains safe because it preserves field names.\n */\nconst analyzeNamespaceCallerUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callArguments: BabelTypes.CallExpression['arguments'],\n callerConfig: CompatCallerConfig,\n isSfcFile: boolean,\n /** Absolute path of the analysed file (for untracked-binding reporting). */\n currentSourceFilePath: string,\n /**\n * The call-expression path, when the caller was matched as a call. Omitted\n * for JSX-element matches (`<FormattedMessage id>`), where only the static\n * `'self'` / `'all'` analysis paths apply — the binding-based\n * `'destructured-t'` / `'return-value'` paths require a call site.\n */\n callExpressionPath?: NodePath<BabelTypes.CallExpression>,\n /**\n * Pre-resolved namespace bypassing `namespaceSources`, e.g. the `ns`\n * attribute of react-i18next's `<Trans ns=\"home\" i18nKey=\"title\" />`.\n */\n namespaceOverride?: string\n): void => {\n // 1. Resolve the dictionary key (namespace).\n const resolvedNamespace =\n namespaceOverride ??\n resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n callerConfig.namespaceSources\n );\n if (resolvedNamespace === undefined) return; // dynamic key – cannot attribute\n const namespaceString =\n resolvedNamespace === '__default__'\n ? DEFAULT_COMPAT_NAMESPACE\n : resolvedNamespace;\n\n // next-intl scopes nested objects through a dotted namespace\n // (`'about.counter'`): the dictionary key is the first segment and the\n // remainder is an implicit key prefix applied to every t() lookup.\n const namespaceSegments = namespaceString.split('.');\n const dictionaryKey = namespaceSegments[0] ?? namespaceString;\n const namespacePrefix =\n namespaceSegments.length > 1 ? namespaceSegments.slice(1).join('.') : null;\n\n // Compat string-path access is never renamable.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n // 2. SFC files (Vue / Svelte / Astro): the translation function is typically\n // invoked from the template, which Babel cannot see. Conservatively keep\n // every field to avoid pruning a template-only access.\n if (isSfcFile) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a. Mark the entire dictionary as used — static field analysis is not\n // applicable (e.g. lingui hashed IDs, Angular templates).\n if (callerConfig.translationFunction === 'all') {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a-bis. The caller returns the dictionary content object itself\n // (`useIntlayer`-like shape): reuse the native member-access /\n // destructuring analysis. Without a call site, keep every field.\n if (callerConfig.translationFunction === 'content') {\n if (callExpressionPath) {\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // 3b. The caller IS the translation call (`translationFunction: 'self'`).\n // The first argument of the current call expression is the message key.\n // Used for lingui's `i18n._('key')` / `i18n.t('key')` and react-intl's\n // `intl.formatMessage({ id: 'key' })` patterns.\n if (callerConfig.translationFunction === 'self') {\n const firstArg = callArguments[0];\n if (!firstArg) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n if (\n callerConfig.namespaceSources.some(\n (source) => source.from === 'path-first-segment'\n )\n ) {\n // The dictionary key is already the first segment of the descriptor id.\n // The field to record is the SECOND segment (first level inside the dict).\n // e.g. formatMessage({ id: 'home.title' }) → dictionaryKey='home', field='title'\n let fullId: string | undefined;\n\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) {\n fullId = staticString;\n } else if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n fullId = idValue;\n }\n }\n\n if (fullId !== undefined) {\n const segments = fullId.split('.');\n const field = segments[1];\n if (field !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([field]));\n } else {\n // Single-segment id — the whole value is the dict key; no sub-field.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // Dynamic id — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // For 'fixed' / 'argument' / 'option' namespaces: the field is the first\n // dot-segment of the first argument (the message key itself) — unless\n // `flatKey` is set, in which case the whole dotted key is the field.\n // String form: i18n._('home.title', values)\n const segment = callerConfig.flatKey\n ? readStaticString(babelTypes, firstArg)\n : readStaticFirstSegment(babelTypes, firstArg);\n if (segment !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([segment]));\n return;\n }\n\n // Descriptor form: i18n._({ id: 'home.title', message: '...' }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([callerConfig.flatKey ? idValue : firstPathSegment(idValue)])\n );\n return;\n }\n }\n\n // Dynamic key — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The remaining strategies (`'destructured-t'` / `'return-value'`) resolve\n // the `t` binding from the call site, so they require a call-expression path.\n // JSX-element matches never reach here (they use `'self'` / `'all'`); guard\n // defensively so a misconfigured JSX caller keeps every field instead of\n // throwing.\n if (!callExpressionPath) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3. Resolve an optional explicit keyPrefix (e.g. react-i18next's\n // `{ keyPrefix }` option). A static prefix fixes the single consumed\n // top-level field regardless of the individual t() paths.\n const explicitKeyPrefix = resolveCompatKeyPrefix(\n babelTypes,\n callArguments,\n callerConfig.keyPrefixSources\n );\n if (explicitKeyPrefix === undefined) {\n // Prefix present but dynamic → unknown field set.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The namespace-derived prefix (next-intl) and the explicit keyPrefix option\n // (react-i18next / i18next) never coexist in practice; prefer whichever is\n // present. Either way, the consumed top-level field is the prefix's first\n // segment.\n const effectivePrefix = namespacePrefix ?? explicitKeyPrefix;\n if (effectivePrefix !== null) {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([firstPathSegment(effectivePrefix)])\n );\n return;\n }\n\n // 4. Locate the `t` function binding.\n let translationBinding: ReturnType<NodePath['scope']['getBinding']> | null =\n null;\n\n if (callerConfig.translationFunction === 'destructured-t') {\n // const { t } = useTranslation('ns')\n const parentNode = callExpressionPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key) &&\n property.key.name === 't' &&\n babelTypes.isIdentifier(property.value)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(property.value.name) ?? null;\n }\n }\n }\n } else {\n // const t = useTranslations('ns') / const t = await getTranslations('ns')\n const resultPath = unwrapAwait(babelTypes, callExpressionPath);\n const parentNode = resultPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(parentNode.id.name) ?? null;\n }\n }\n\n // Could not statically locate `t` (e.g. result stored whole, re-exported) →\n // conservatively keep all fields.\n if (!translationBinding) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 5. Inspect every reference to `t`.\n const accessedFields = new Set<string>();\n let hasUntrackedUsage = false;\n\n for (const referencePath of translationBinding.referencePaths) {\n const parentNode = referencePath.parent;\n\n // Must be a direct call: t('path')\n const isDirectCall =\n (babelTypes.isCallExpression(parentNode) ||\n babelTypes.isOptionalCallExpression(parentNode)) &&\n (parentNode as BabelTypes.CallExpression).callee === referencePath.node;\n\n if (!isDirectCall) {\n // t passed as a prop / argument / reassigned → fields unknown.\n hasUntrackedUsage = true;\n break;\n }\n\n const firstArgument = (parentNode as BabelTypes.CallExpression)\n .arguments[0];\n const segment = readStaticFirstSegment(babelTypes, firstArgument);\n if (segment === undefined) {\n hasUntrackedUsage = true;\n break;\n }\n accessedFields.add(segment);\n }\n\n if (hasUntrackedUsage) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // Only record a finite field set when at least one field was actually\n // accessed. Recording an empty set would prune every field even though the\n // dictionary may be consumed elsewhere.\n if (accessedFields.size > 0) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFields);\n }\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site —\n * and each configured compat namespace caller — accesses. Results are\n * accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n *\n * @param pruneContext - Shared mutable state written by this plugin.\n * @param options - Optional overrides. `compatCallers` defaults to\n * {@link DEFAULT_COMPAT_CALLERS}; pass `[]` to disable\n * compat-adapter analysis entirely.\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (\n pruneContext: PruneContext,\n options?: { compatCallers?: CompatCallerConfig[] }\n ) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => {\n const compatCallers = options?.compatCallers ?? DEFAULT_COMPAT_CALLERS;\n\n return {\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte') ||\n currentSourceFilePath.endsWith('.astro');\n\n // Phase 1: collect local aliases for native intlayer callers and\n // for compat namespace callers (gated by import source).\n const intlayerCallerLocalNameMap = new Map<string, string>();\n const compatCallerLocalNameMap = new Map<\n string,\n CompatCallerConfig\n >();\n\n // Module specifiers imported by the analysed file — used to gate\n // `requiresImport` callers (generic names such as lingui's `t`).\n const fileImportSources = new Set<string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n const importSource = importDeclarationPath.node.source.value;\n fileImportSources.add(importSource);\n\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n continue;\n }\n\n const compatCaller = compatCallers.find(\n (caller) =>\n caller.callerName === importedName &&\n caller.importSources.includes(importSource)\n );\n if (compatCaller) {\n compatCallerLocalNameMap.set(\n importSpecifier.local.name,\n compatCaller\n );\n }\n }\n },\n });\n\n // Method-matched compat callers (e.g. i18next `getFixedT`,\n // `intl.formatMessage`) are recognised by method name on any\n // object. Callers flagged `requiresImport` only participate when\n // the file imports from one of their declared modules.\n const methodCompatCallers = compatCallers.filter(\n (caller) =>\n caller.matchAsMethod &&\n (!caller.requiresImport ||\n caller.importSources.some((source) =>\n fileImportSources.has(source)\n ))\n );\n\n const hasNativeCallers = intlayerCallerLocalNameMap.size > 0;\n const hasCompatCallers =\n compatCallerLocalNameMap.size > 0 ||\n methodCompatCallers.length > 0;\n\n if (!hasNativeCallers && !hasCompatCallers) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n let isMethodCall = false;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n isMethodCall = true;\n }\n\n if (!localCallerName) return;\n\n // Native intlayer caller (useIntlayer / getIntlayer)\n if (intlayerCallerLocalNameMap.has(localCallerName)) {\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const dictionaryKey = readStaticString(\n babelTypes,\n callArguments[0]\n );\n if (!dictionaryKey) return; // dynamic key\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n return;\n }\n\n // Compat namespace caller (imported)\n const importedCompatCaller =\n compatCallerLocalNameMap.get(localCallerName);\n if (importedCompatCaller && !isMethodCall) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n importedCompatCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n return;\n }\n\n // Compat namespace caller (method-matched, e.g. getFixedT)\n if (isMethodCall) {\n const methodCaller = methodCompatCallers.find(\n (caller) => caller.callerName === localCallerName\n );\n if (methodCaller) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n methodCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n }\n }\n },\n JSXOpeningElement: (jsxOpeningElementPath) => {\n const nameNode = jsxOpeningElementPath.node.name;\n if (!babelTypes.isJSXIdentifier(nameNode)) return;\n\n const jsxCaller = compatCallerLocalNameMap.get(nameNode.name);\n if (!jsxCaller?.jsxIdAttribute) return;\n\n // Read the configured id attribute as a static string. A missing\n // or dynamic id yields an empty argument list, which resolves the\n // namespace to undefined and is skipped (consistent with how a\n // dynamic `useIntlayer(key)` call is handled).\n const idNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxIdAttribute\n );\n\n // Optional dedicated namespace attribute, e.g. react-i18next's\n // `<Trans ns=\"home\" i18nKey=\"title\" />`. When configured but\n // absent/dynamic, the dictionary cannot be attributed → skip.\n let namespaceOverride: string | undefined;\n if (jsxCaller.jsxNamespaceAttribute) {\n const namespaceNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxNamespaceAttribute\n );\n if (!namespaceNode) return;\n namespaceOverride = namespaceNode.value;\n }\n\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n idNode ? [idNode] : [],\n jsxCaller,\n isSfcFile,\n currentSourceFilePath,\n undefined,\n namespaceOverride\n );\n },\n });\n },\n },\n },\n };\n };\n"],"mappings":";AA4IA,MAAa,4BAA0C;CACrD,8CAA8B,IAAI,IAAI;CACtC,2CAA2B,IAAI,IAAI;CACnC,0BAA0B;CAC1B,qDAAqC,IAAI,IAAI;CAC7C,+CAA+B,IAAI,IAAI;CACvC,gDAAgC,IAAI,IAGlC;CACF,iDAAiC,IAAI,IAAI;CACzC,0CAA0B,IAAI,IAAI;AACpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,kCACX,cACA,+BACS;CACT,KAAK,MAAM,CACT,eACA,iBACG,2BAA2B,QAAQ,GAAG;EACzC,aAAa,gCAAgC,IAAI,aAAa;EAE9D,MAAM,oBACJ,aAAa,6BAA6B,IAAI,aAAa;EAI7D,IAAI,iBAAiB,OAAO;GAC1B,aAAa,6BAA6B,IAAI,eAAe,KAAK;GAClE;EACF;EAEA,IAAI,CAAC,mBAAmB;GACtB,IAAI,aAAa,0BAA0B;GAE3C,aAAa,6BAA6B,IACxC,eACA,IAAI,IAAI,YAAY,CACtB;GACA;EACF;EAGA,IAAI,sBAAsB,OAAO;EAEjC,KAAK,MAAM,eAAe,cACxB,kBAAkB,IAAI,WAAW;CAErC;AACF;;;;;;;;;;;;;;;AAkBA,MAAa,iDAAiC,IAAI,IAAI,CAAC,UAAU,CAAC;;AAGlE,MAAa,wBAAwB,CAAC,eAAe,aAAa;;;;;;;;;;;AAgClE,MAAa,yBAA+C,CAAC;;AAG7D,MAAM,2BAA2B;;;;;AAMjC,MAAM,oBACJ,cACA,eACA,eACS;CACT,MAAM,gBACJ,aAAa,6BAA6B,IAAI,aAAa;CAE7D,IAAI,kBAAkB,OAAO;CAE7B,IAAI,eAAe,OAAO;EACxB,aAAa,6BAA6B,IAAI,eAAe,KAAK;EAClE;CACF;CAEA,IAAI,yBAAyB,KAAK;EAEhC,KAAK,MAAM,aAAa,YAAY,cAAc,IAAI,SAAS;EAC/D;CACF;CAEA,aAAa,6BAA6B,IACxC,eACA,IAAI,IAAI,UAAU,CACpB;AACF;;;;;;;;;;;;AAaA,MAAM,8BACJ,YACA,cACA,oBACA,eACA,uBACA,cACS;;CAET,MAAM,6BAAmC;EACvC,MAAM,gBACJ,aAAa,oCAAoC,IAAI,aAAa,KAAK,CAAC;EAC1E,IAAI,CAAC,cAAc,SAAS,qBAAqB,GAC/C,aAAa,oCAAoC,IAAI,eAAe,CAClE,GAAG,eACH,qBACF,CAAC;EAEH,iBAAiB,cAAc,eAAe,KAAK;CACrD;;CAGA,MAAM,mBACJ,WACA,SACS;EACT,MAAM,mBACJ,aAAa,+BAA+B,IAAI,aAAa,qBAC7D,IAAI,IAAmC;EACzC,MAAM,WACJ,SAAS,SACL,GAAG,sBAAsB,GAAG,SAC5B;EACN,MAAM,kBAAkB,UAAU,KAAK,GAAG;EAC1C,MAAM,aAAa,iBAAiB,IAAI,eAAe,KAAK;GAC1D,WAAW,CAAC,GAAG,SAAS;GACxB,WAAW,CAAC;EACd;EACA,IAAI,CAAC,WAAW,UAAU,SAAS,QAAQ,GACzC,WAAW,UAAU,KAAK,QAAQ;EAEpC,iBAAiB,IAAI,iBAAiB,UAAU;EAChD,aAAa,+BAA+B,IAC1C,eACA,gBACF;CACF;;CAGA,MAAM,0BAA0B,iBAA+B;EAC7D,MAAM,WACJ,aAAa,yBAAyB,IAAI,qBAAqB,KAAK,CAAC;EACvE,IACE,CAAC,SAAS,MACP,MACC,EAAE,iBAAiB,gBAAgB,EAAE,kBAAkB,aAC3D,GAEA,SAAS,KAAK;GAAE;GAAc;EAAc,CAAC;EAE/C,aAAa,yBAAyB,IAAI,uBAAuB,QAAQ;CAC3E;;;;;;;;;;;;;CAcA,MAAM,sBACJ,SACA,cACS;EACT,MAAM,aAAa,QAAQ;EAC3B,MAAM,aAAa,QAAQ;EAI3B,IACE,eACC,WAAW,mBAAmB,UAAU,KACvC,WAAW,2BAA2B,UAAU,MACjD,WAA2C,WAAW,QAAQ,MAC/D;GACA,MAAM,aAAa;GAInB,IACE,WAAW,YACX,WAAW,iBAAiB,WAAW,QAAQ,GAC/C;IACA,mBAAmB,YAAY,SAAS;IACxC;GACF;GAEA,IACE,CAAC,WAAW,YACZ,WAAW,aAAa,WAAW,QAAQ,GAC3C;IACA,mBAAmB,YAAY,CAC7B,GAAG,WACH,WAAW,SAAS,IACtB,CAAC;IACD;GACF;GAEA,IACE,WAAW,YACX,WAAW,gBAAgB,WAAW,QAAQ,GAC9C;IACA,mBAAmB,YAAY,CAC7B,GAAG,WACH,WAAW,SAAS,KACtB,CAAC;IACD;GACF;GAIA,gBAAgB,WAAW,QAAQ,KAAK,KAAK,MAAM,IAAI;GACvD;EACF;EAGA,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,KACxC,WAAW,SAAS,QAAQ,MAC5B;GACA,2BAA2B,WAAW,IAAI,SAAS,SAAS;GAC5D;EACF;EAGA,IAAI,WAAW,kBAAkB,UAAU,GACzC;EAIF,gBAAgB,WAAW,QAAQ,KAAK,KAAK,MAAM,IAAI;CACzD;;;;;;;;;CAUA,MAAM,8BACJ,eACA,WACA,cACS;EACT,KAAK,MAAM,YAAY,cAAc,YAAY;GAG/C,IAAI,WAAW,cAAc,QAAQ,GAAG;IACtC,gBAAgB,WAAW,SAAS,KAAK,MAAM,IAAI;IACnD;GACF;GACA,IAAI,CAAC,WAAW,iBAAiB,QAAQ,GAAG;GAG5C,IAAI,SAAS,YAAY,CAAC,WAAW,gBAAgB,SAAS,GAAG,GAAG;IAClE,gBAAgB,WAAW,SAAS,KAAK,MAAM,IAAI;IACnD;GACF;GAEA,MAAM,UAAU,WAAW,aAAa,SAAS,GAAG,IAChD,SAAS,IAAI,OACb,WAAW,gBAAgB,SAAS,GAAG,IACrC,SAAS,IAAI,QACb;GACN,IAAI,CAAC,SAAS;GAEd,MAAM,iBAAiB,CAAC,GAAG,WAAW,OAAO;GAG7C,MAAM,gBAAgB,WAAW,oBAAoB,SAAS,KAAK,IAC/D,SAAS,MAAM,OACf,SAAS;GAEb,IAAI,WAAW,gBAAgB,aAAa,GAAG;IAC7C,2BAA2B,eAAe,WAAW,cAAc;IACnE;GACF;GAEA,IAAI,WAAW,aAAa,aAAa,GAAG;IAC1C,MAAM,kBAAkB,UAAU,MAAM,WAAW,cAAc,IAAI;IACrE,IAAI,CAAC,iBAAiB;IACtB,KAAK,MAAM,iBAAiB,gBAAgB,gBAC1C,mBAAmB,eAAe,cAAc;IAElD;GACF;GAGA,gBAAgB,gBAAgB,SAAS,KAAK,MAAM,IAAI;EAC1D;CACF;;;;;;CAOA,MAAM,kCACJ,SACA,UACA,cACY;EACZ,IAAI,QAAQ,WAAW,MAAM,SAAS,WAAW,cAAc,IAAI,CAAC,GAClE,OAAO;EAGT,KAAK,MAAM,YAAY,QAAQ,YAAY;GACzC,IAAI,CAAC,WAAW,iBAAiB,QAAQ,GAAG;GAI5C,IAAI,SAAS,YAAY,CAAC,WAAW,gBAAgB,SAAS,GAAG,GAC/D,OAAO;GAGT,IAAI;GAEJ,IAAI,CAAC,SAAS,YAAY,WAAW,aAAa,SAAS,GAAG,GAC5D,YAAY,SAAS,IAAI;QACpB,IAAI,WAAW,gBAAgB,SAAS,GAAG,GAChD,YAAY,SAAS,IAAI;GAG3B,IAAI,CAAC,WAAW;GAEhB,UAAU,IAAI,SAAS;GAGvB,MAAM,gBAAgB,WAAW,oBAAoB,SAAS,KAAK,IAC/D,SAAS,MAAM,OACf,SAAS;GAEb,IAAI,WAAW,aAAa,aAAa,GAAG;IAC1C,MAAM,kBAAkB,SAAS,MAAM,WAAW,cAAc,IAAI;IACpE,IAAI,iBACF,KAAK,MAAM,WAAW,gBAAgB,gBACpC,mBAAmB,SAAS,CAAC,SAAS,CAAC;GAG7C,OAAO,IAAI,WAAW,gBAAgB,aAAa,GAGjD,2BAA2B,eAAe,UAAU,CAAC,SAAS,CAAC;QAG/D,gBAAgB,CAAC,SAAS,GAAG,SAAS,KAAK,MAAM,IAAI;EAEzD;EACA,OAAO;CACT;;;;;;;;;CAUA,MAAM,6BACJ,gBACA,cAC0C;EAC1C,IAAI,CAAC,+BAA+B,IAAI,SAAS,GAAG,OAAO;EAC3D,IAAI,CAAC,gBAAgB,OAAO;EAE5B,MAAM,WAAW,eAAe;EAChC,IAAI,CAAC,UAAU,OAAO;EACtB,IAAI,CAAC,SAAS,iBAAiB,KAAK,CAAC,SAAS,yBAAyB,GACrE;EAEF,IACG,SAAS,KAAmC,WAC7C,eAAe,MAEf;EAGF,OAAO;CACT;;;;;;;;CASA,MAAM,mCACJ,aACS;EACT,MAAM,gBAAgB,SAAS,IAC7B,WACF;EAEA,KAAK,MAAM,gBAAgB,eAAe;GACxC,IACE,CAAC,aAAa,0BAA0B,KACxC,CAAC,aAAa,qBAAqB,GAEnC;GAGF,MAAM,CAAC,sBAAsB,aAAa,IACxC,QACF;GACA,IAAI,CAAC,oBAAoB;GAGzB,IAAI,mBAAmB,aAAa,GAAG;IACrC,MAAM,mBAAmB,aAAa,MAAM,WAC1C,mBAAmB,KAAK,IAC1B;IACA,IAAI,CAAC,kBAAkB;KACrB,qBAAqB;KACrB;IACF;IAEA,MAAM,qCAAqB,IAAI,IAAY;IAC3C,IACE,gCAAgC,kBAAkB,kBAAkB,GAEpE,qBAAqB;SAErB,iBAAiB,cAAc,eAAe,kBAAkB;IAElE;GACF;GAGA,IAAI,mBAAmB,gBAAgB,GAAG;IACxC,MAAM,qCAAqB,IAAI,IAAY;IAC3C,IACE,+BACE,mBAAmB,MACnB,cACA,kBACF,GAEA,iBAAiB,cAAc,eAAe,kBAAkB;SAEhE,iBAAiB,cAAc,eAAe,KAAK;IAErD;GACF;GAEA,qBAAqB;EACvB;CACF;;;;;;;;CASA,MAAM,mCACJ,iBACA,+BACY;EACZ,KAAK,MAAM,yBAAyB,gBAAgB,gBAAgB;GAClE,MAAM,sBAAsB,sBAAsB;GAElD,KACG,WAAW,mBAAmB,mBAAmB,KAChD,WAAW,2BAA2B,mBAAmB,MAC1D,oBAAoD,WACnD,sBAAsB,MACxB;IACA,MAAM,uBACJ;IACF,IAAI;IAEJ,IACE,CAAC,qBAAqB,YACtB,WAAW,aAAa,qBAAqB,QAAQ,GAErD,YAAY,qBAAqB,SAAS;SACrC,IACL,qBAAqB,YACrB,WAAW,gBAAgB,qBAAqB,QAAQ,GAExD,YAAY,qBAAqB,SAAS;IAG5C,IAAI,WAAW;KACb,MAAM,iBAAiB,sBAAsB;KAG7C,MAAM,kBAAkB,0BACtB,gBACA,SACF;KACA,IAAI,iBAAiB;MACnB,gCAAgC,eAAe;MAC/C,mBAAmB,eAAe;MAClC;KACF;KAEA,2BAA2B,IAAI,SAAS;KAGxC,IAAI,gBACF,mBAAmB,gBAAgB,CAAC,SAAS,CAAC;IAElD,OAEE,OAAO;GAEX,OAAO,IAAI,WAAW,kBAAkB,mBAAmB,GAOzD,OAAO;QACF,KAEJ,WAAW,iBAAiB,mBAAmB,KAC9C,WAAW,yBAAyB,mBAAmB,MACxD,oBAAkD,WACjD,sBAAsB,MACxB;IACA,MAAM,eAAe,sBAAsB;IAC3C,MAAM,aAAa,cAAc;IAEjC,IACE,eACC,WAAW,mBAAmB,UAAU,KACvC,WAAW,2BAA2B,UAAU,MACjD,WAA2C,WAC1C,cAAc,MAChB;KAEA,MAAM,aAAa;KACnB,IAAI;KAEJ,IACE,CAAC,WAAW,YACZ,WAAW,aAAa,WAAW,QAAQ,GAE3C,YAAY,WAAW,SAAS;UAC3B,IACL,WAAW,YACX,WAAW,gBAAgB,WAAW,QAAQ,GAE9C,YAAY,WAAW,SAAS;KAGlC,IAAI,WAAW;MACb,MAAM,iBAAiB,cAAc;MAGrC,MAAM,kBAAkB,0BACtB,gBACA,SACF;MACA,IAAI,iBAAiB;OACnB,gCAAgC,eAAe;OAC/C,mBAAmB,eAAe;OAClC;MACF;MAEA,2BAA2B,IAAI,SAAS;MACxC,IAAI,gBAAgB,mBAAmB,gBAAgB,CAAC,SAAS,CAAC;KACpE,OAEE,OAAO;IAEX,OAAO,IACL,cACA,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,KACxC,gBACA,+BACE,WAAW,IACX,cACA,0BACF,GACA,CAGF,OAEE,OAAO;GAEX,OAEE,OAAO;EAEX;EAEA,OAAO;CACT;;;;;;;;CASA,MAAM,sBAAsB,aAA8C;EACxE,MAAM,aAAa,SAAS;EAG5B,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,GACxC;GACA,MAAM,qCAAqB,IAAI,IAAY;GAC3C,IACE,+BACE,WAAW,IACX,UACA,kBACF,GAEA,iBAAiB,cAAc,eAAe,kBAAkB;QAEhE,iBAAiB,cAAc,eAAe,KAAK;GAErD;EACF;EAGA,KACG,WAAW,mBAAmB,UAAU,KACvC,WAAW,2BAA2B,UAAU,MACjD,WAA2C,WAAW,SAAS,MAChE;GACA,IAAI;GAEJ,IACE,CAAC,WAAW,YACZ,WAAW,aAAa,WAAW,QAAQ,GAE3C,YAAY,WAAW,SAAS;QAC3B,IACL,WAAW,YACX,WAAW,gBAAgB,WAAW,QAAQ,GAE9C,YAAY,WAAW,SAAS;GAGlC,IAAI,WAAW;IACb,MAAM,iBAAiB,SAAS;IAIhC,MAAM,kBAAkB,0BACtB,gBACA,SACF;IACA,IAAI,iBAAiB;KACnB,gCAAgC,eAAe;KAC/C,mBAAmB,eAAe;KAClC;IACF;IAEA,iBAAiB,cAAc,+BAAe,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IAGlE,IAAI,gBACF,mBAAmB,gBAAgB,CAAC,SAAS,CAAC;GAElD,OACE,qBAAqB;GAEvB;EACF;EAGA,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,aAAa,WAAW,EAAE,GACrC;GACA,MAAM,eAAe,WAAW,GAAG;GACnC,MAAM,kBAAkB,SAAS,MAAM,WAAW,YAAY;GAE9D,IAAI,CAAC,iBAAiB;IACpB,qBAAqB;IACrB;GACF;GAEA,MAAM,6CAA6B,IAAI,IAAY;GAMnD,IALoC,gCAClC,iBACA,0BAG4B,GAC5B,qBAAqB;QAChB,IAAI,WAGT,uBAAuB,YAAY;QAC9B,IAAI,gBAAgB,eAAe,WAAW,GAEnD,qBAAqB;QAErB,iBACE,cACA,eACA,0BACF;GAEF;EACF;EAGA,IAAI,WAAW,sBAAsB,UAAU,GAC7C;EAIF,qBAAqB;CACvB;CAEA,mBAAmB,kBAAkB;AACvC;;;;;;AASA,MAAM,oBACJ,YACA,SACuB;CACvB,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,WAAW,gBAAgB,IAAI,GAAG,OAAO,KAAK;CAClD,IACE,WAAW,kBAAkB,IAAI,KACjC,KAAK,YAAY,WAAW,KAC5B,KAAK,OAAO,WAAW,GAEvB,OAAO,KAAK,OAAO,EAAE,EAAE,MAAM,UAAU,KAAK,OAAO,EAAE,EAAE,MAAM;AAGjE;;AAGA,MAAM,oBAAoB,SAAyB,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;;;;;;;;;AAUzE,MAAM,0BACJ,YACA,SACuB;CACvB,MAAM,eAAe,iBAAiB,YAAY,IAAI;CACtD,IAAI,iBAAiB,QAAW,OAAO,iBAAiB,YAAY;CAIpE,IAAI,WAAW,kBAAkB,IAAI,KAAK,KAAK,OAAO,SAAS,GAAG;EAChE,MAAM,aACJ,KAAK,OAAO,EAAE,EAAE,MAAM,UAAU,KAAK,OAAO,EAAE,EAAE,MAAM;EACxD,IAAI,YAAY,SAAS,GAAG,GAC1B,OAAO,iBAAiB,UAAU;CAEtC;AAEF;;;;;;AAOA,MAAM,sBACJ,YACA,kBACA,iBACuC;CACvC,KAAK,MAAM,YAAY,iBAAiB,YAAY;EAClD,IAAI,CAAC,WAAW,iBAAiB,QAAQ,GAAG;EAM5C,IAAI,EAJD,WAAW,aAAa,SAAS,GAAG,KACnC,SAAS,IAAI,SAAS,gBACvB,WAAW,gBAAgB,SAAS,GAAG,KACtC,SAAS,IAAI,UAAU,eACV;EAEjB,OADoB,iBAAiB,YAAY,SAAS,KACzC,KAAK;CACxB;CACA,OAAO;AACT;;;;;;;AAQA,MAAM,0BACJ,YACA,gBACA,kBACyC;CACzC,KAAK,MAAM,aAAa,eAAe,YAAY;EACjD,IAAI,CAAC,WAAW,eAAe,SAAS,GAAG;EAC3C,IACE,CAAC,WAAW,gBAAgB,UAAU,IAAI,KAC1C,UAAU,KAAK,SAAS,eAExB;EAGF,MAAM,QAAQ,UAAU;EAExB,IAAI,WAAW,gBAAgB,KAAK,GAAG,OAAO;EAE9C,IAAI,WAAW,yBAAyB,KAAK,GAAG;GAC9C,MAAM,eAAe,iBAAiB,YAAY,MAAM,UAAU;GAClE,IAAI,iBAAiB,QACnB,OAAO,WAAW,cAAc,YAAY;EAEhD;EACA;CACF;AAEF;;;;;;;AAQA,MAAM,0BACJ,YACA,eACA,WACuC;CACvC,IAAI,OAAO,SAAS,SAAS,OAAO,OAAO;CAE3C,IAAI,OAAO,SAAS,sBAAsB;EACxC,MAAM,WAAW,cAAc;EAG/B,IAAI,aAAa,QAAW,OAAO;EAGnC,MAAM,eAAe,iBAAiB,YAAY,QAAQ;EAC1D,IAAI,iBAAiB,QAAW,OAAO,iBAAiB,YAAY;EAGpE,IAAI,WAAW,mBAAmB,QAAQ,GAAG;GAC3C,MAAM,UAAU,mBAAmB,YAAY,UAAU,IAAI;GAC7D,IAAI,YAAY,eAAe,OAAO;GACtC,IAAI,YAAY,QAAW,OAAO,iBAAiB,OAAO;GAC1D;EACF;EAEA;CACF;CAEA,IAAI,OAAO,SAAS,YAAY;EAC9B,MAAM,WAAW,cAAc,OAAO;EACtC,IAAI,aAAa,QAAW,OAAO;EAGnC,MAAM,eAAe,iBAAiB,YAAY,QAAQ;EAC1D,IAAI,iBAAiB,QAAW,OAAO;EAGvC,IAAI,WAAW,mBAAmB,QAAQ,GACxC,OAAO,mBAAmB,YAAY,UAAU,WAAW;EAE7D;CACF;CAGA,MAAM,kBAAkB,cAAc,OAAO;CAC7C,IAAI,oBAAoB,QAAW,OAAO;CAC1C,IAAI,CAAC,WAAW,mBAAmB,eAAe,GAAG,OAAO;CAC5D,OAAO,mBAAmB,YAAY,iBAAiB,OAAO,QAAQ;AACxE;;;;;;;AAQA,MAAM,+BACJ,YACA,eACA,YACuC;CACvC,IAAI,iBAAiB;CAErB,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,uBAAuB,YAAY,eAAe,MAAM;EACzE,IAAI,OAAO,aAAa,YAAY,aAAa,eAC/C,OAAO;EAET,IAAI,aAAa,eAAe,iBAAiB;CACnD;CAEA,OAAO,iBAAiB,gBAAgB;AAC1C;;;;;;AAOA,MAAM,0BACJ,YACA,eACA,YAC8B;CAC9B,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO;CAC7C,MAAM,WAAW,4BACf,YACA,eACA,OACF;CACA,IAAI,aAAa,eAAe,OAAO;CACvC,OAAO;AACT;;;;;;AAOA,MAAM,eACJ,YACA,SAC8B;CAC9B,MAAM,aAAa,KAAK;CACxB,IAAI,cAAc,WAAW,kBAAkB,WAAW,IAAI,GAC5D,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;AAcA,MAAM,+BACJ,YACA,cACA,eACA,cACA,WAEA,uBAOA,oBAKA,sBACS;CAET,MAAM,oBACJ,qBACA,4BACE,YACA,eACA,aAAa,gBACf;CACF,IAAI,sBAAsB,QAAW;CACrC,MAAM,kBACJ,sBAAsB,gBAClB,2BACA;CAKN,MAAM,oBAAoB,gBAAgB,MAAM,GAAG;CACnD,MAAM,gBAAgB,kBAAkB,MAAM;CAC9C,MAAM,kBACJ,kBAAkB,SAAS,IAAI,kBAAkB,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;CAGxE,aAAa,gCAAgC,IAAI,aAAa;CAK9D,IAAI,WAAW;EACb,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAIA,IAAI,aAAa,wBAAwB,OAAO;EAC9C,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAKA,IAAI,aAAa,wBAAwB,WAAW;EAClD,IAAI,oBACF,2BACE,YACA,cACA,oBACA,eACA,uBACA,SACF;OAEA,iBAAiB,cAAc,eAAe,KAAK;EAErD;CACF;CAMA,IAAI,aAAa,wBAAwB,QAAQ;EAC/C,MAAM,WAAW,cAAc;EAC/B,IAAI,CAAC,UAAU;GACb,iBAAiB,cAAc,eAAe,KAAK;GACnD;EACF;EAEA,IACE,aAAa,iBAAiB,MAC3B,WAAW,OAAO,SAAS,oBAC9B,GACA;GAIA,IAAI;GAEJ,MAAM,eAAe,iBAAiB,YAAY,QAAQ;GAC1D,IAAI,iBAAiB,QACnB,SAAS;QACJ,IAAI,WAAW,mBAAmB,QAAQ,GAAG;IAClD,MAAM,UAAU,mBAAmB,YAAY,UAAU,IAAI;IAC7D,IAAI,YAAY,UAAa,YAAY,eACvC,SAAS;GAEb;GAEA,IAAI,WAAW,QAAW;IAExB,MAAM,QADW,OAAO,MAAM,GACT,CAAC,CAAC;IACvB,IAAI,UAAU,QACZ,iBAAiB,cAAc,+BAAe,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;SAG9D,iBAAiB,cAAc,eAAe,KAAK;IAErD;GACF;GAGA,iBAAiB,cAAc,eAAe,KAAK;GACnD;EACF;EAMA,MAAM,UAAU,aAAa,UACzB,iBAAiB,YAAY,QAAQ,IACrC,uBAAuB,YAAY,QAAQ;EAC/C,IAAI,YAAY,QAAW;GACzB,iBAAiB,cAAc,+BAAe,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;GAChE;EACF;EAGA,IAAI,WAAW,mBAAmB,QAAQ,GAAG;GAC3C,MAAM,UAAU,mBAAmB,YAAY,UAAU,IAAI;GAC7D,IAAI,YAAY,UAAa,YAAY,eAAe;IACtD,iBACE,cACA,+BACA,IAAI,IAAI,CAAC,aAAa,UAAU,UAAU,iBAAiB,OAAO,CAAC,CAAC,CACtE;IACA;GACF;EACF;EAGA,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAOA,IAAI,CAAC,oBAAoB;EACvB,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAKA,MAAM,oBAAoB,uBACxB,YACA,eACA,aAAa,gBACf;CACA,IAAI,sBAAsB,QAAW;EAEnC,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAMA,MAAM,kBAAkB,mBAAmB;CAC3C,IAAI,oBAAoB,MAAM;EAC5B,iBACE,cACA,+BACA,IAAI,IAAI,CAAC,iBAAiB,eAAe,CAAC,CAAC,CAC7C;EACA;CACF;CAGA,IAAI,qBACF;CAEF,IAAI,aAAa,wBAAwB,kBAAkB;EAEzD,MAAM,aAAa,mBAAmB;EACtC,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,GAExC;QAAK,MAAM,YAAY,WAAW,GAAG,YACnC,IACE,WAAW,iBAAiB,QAAQ,KACpC,WAAW,aAAa,SAAS,GAAG,KACpC,SAAS,IAAI,SAAS,OACtB,WAAW,aAAa,SAAS,KAAK,GAEtC,qBACE,mBAAmB,MAAM,WAAW,SAAS,MAAM,IAAI,KAAK;EAElE;CAEJ,OAAO;EAGL,MAAM,aADa,YAAY,YAAY,kBACf,CAAC,CAAC;EAC9B,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,aAAa,WAAW,EAAE,GAErC,qBACE,mBAAmB,MAAM,WAAW,WAAW,GAAG,IAAI,KAAK;CAEjE;CAIA,IAAI,CAAC,oBAAoB;EACvB,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAGA,MAAM,iCAAiB,IAAI,IAAY;CACvC,IAAI,oBAAoB;CAExB,KAAK,MAAM,iBAAiB,mBAAmB,gBAAgB;EAC7D,MAAM,aAAa,cAAc;EAQjC,IAAI,GAJD,WAAW,iBAAiB,UAAU,KACrC,WAAW,yBAAyB,UAAU,MAC/C,WAAyC,WAAW,cAAc,OAElD;GAEjB,oBAAoB;GACpB;EACF;EAEA,MAAM,gBAAiB,WACpB,UAAU;EACb,MAAM,UAAU,uBAAuB,YAAY,aAAa;EAChE,IAAI,YAAY,QAAW;GACzB,oBAAoB;GACpB;EACF;EACA,eAAe,IAAI,OAAO;CAC5B;CAEA,IAAI,mBAAmB;EACrB,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAKA,IAAI,eAAe,OAAO,GACxB,iBAAiB,cAAc,eAAe,cAAc;AAEhE;;;;;;;;;;;;;;;AAgBA,MAAa,gCAET,cACA,aAED,EAAE,OAAO,iBAA6D;CACrE,MAAM,gBAAgB,SAAS,iBAAiB;CAEhD,OAAO;EACL,MAAM;EACN,SAAS,EACP,SAAS,EACP,OAAO,aAAa,UAAsB;GACxC,MAAM,wBACJ,MAAM,KAAK,KAAK,YAAY;GAC9B,MAAM,YACJ,sBAAsB,SAAS,MAAM,KACrC,sBAAsB,SAAS,SAAS,KACxC,sBAAsB,SAAS,QAAQ;GAIzC,MAAM,6CAA6B,IAAI,IAAoB;GAC3D,MAAM,2CAA2B,IAAI,IAGnC;GAIF,MAAM,oCAAoB,IAAI,IAAY;GAE1C,YAAY,SAAS,EACnB,oBAAoB,0BAA0B;IAC5C,MAAM,eAAe,sBAAsB,KAAK,OAAO;IACvD,kBAAkB,IAAI,YAAY;IAElC,KAAK,MAAM,mBAAmB,sBAAsB,KACjD,YAAY;KACb,IAAI,CAAC,WAAW,kBAAkB,eAAe,GAAG;KAEpD,MAAM,eAAe,WAAW,aAC9B,gBAAgB,QAClB,IACI,gBAAgB,SAAS,OACxB,gBAAgB,SACd;KAEP,IACE,sBAAsB,SACpB,YACF,GACA;MACA,2BAA2B,IACzB,gBAAgB,MAAM,MACtB,YACF;MACA;KACF;KAEA,MAAM,eAAe,cAAc,MAChC,WACC,OAAO,eAAe,gBACtB,OAAO,cAAc,SAAS,YAAY,CAC9C;KACA,IAAI,cACF,yBAAyB,IACvB,gBAAgB,MAAM,MACtB,YACF;IAEJ;GACF,EACF,CAAC;GAMD,MAAM,sBAAsB,cAAc,QACvC,WACC,OAAO,kBACN,CAAC,OAAO,kBACP,OAAO,cAAc,MAAM,WACzB,kBAAkB,IAAI,MAAM,CAC9B,EACN;GAEA,MAAM,mBAAmB,2BAA2B,OAAO;GAC3D,MAAM,mBACJ,yBAAyB,OAAO,KAChC,oBAAoB,SAAS;GAE/B,IAAI,CAAC,oBAAoB,CAAC,kBAAkB;GAG5C,YAAY,SAAS;IACnB,iBAAiB,uBAAuB;KACtC,MAAM,aAAa,mBAAmB,KAAK;KAC3C,IAAI;KACJ,IAAI,eAAe;KAEnB,IAAI,WAAW,aAAa,UAAU,GACpC,kBAAkB,WAAW;UACxB,IACL,WAAW,mBAAmB,UAAU,KACxC,WAAW,aAAa,WAAW,QAAQ,GAC3C;MACA,kBAAkB,WAAW,SAAS;MACtC,eAAe;KACjB;KAEA,IAAI,CAAC,iBAAiB;KAGtB,IAAI,2BAA2B,IAAI,eAAe,GAAG;MACnD,MAAM,gBAAgB,mBAAmB,KAAK;MAC9C,IAAI,cAAc,WAAW,GAAG;MAEhC,MAAM,gBAAgB,iBACpB,YACA,cAAc,EAChB;MACA,IAAI,CAAC,eAAe;MAEpB,2BACE,YACA,cACA,oBACA,eACA,uBACA,SACF;MACA;KACF;KAGA,MAAM,uBACJ,yBAAyB,IAAI,eAAe;KAC9C,IAAI,wBAAwB,CAAC,cAAc;MACzC,4BACE,YACA,cACA,mBAAmB,KAAK,WACxB,sBACA,WACA,uBACA,kBACF;MACA;KACF;KAGA,IAAI,cAAc;MAChB,MAAM,eAAe,oBAAoB,MACtC,WAAW,OAAO,eAAe,eACpC;MACA,IAAI,cACF,4BACE,YACA,cACA,mBAAmB,KAAK,WACxB,cACA,WACA,uBACA,kBACF;KAEJ;IACF;IACA,oBAAoB,0BAA0B;KAC5C,MAAM,WAAW,sBAAsB,KAAK;KAC5C,IAAI,CAAC,WAAW,gBAAgB,QAAQ,GAAG;KAE3C,MAAM,YAAY,yBAAyB,IAAI,SAAS,IAAI;KAC5D,IAAI,CAAC,WAAW,gBAAgB;KAMhC,MAAM,SAAS,uBACb,YACA,sBAAsB,MACtB,UAAU,cACZ;KAKA,IAAI;KACJ,IAAI,UAAU,uBAAuB;MACnC,MAAM,gBAAgB,uBACpB,YACA,sBAAsB,MACtB,UAAU,qBACZ;MACA,IAAI,CAAC,eAAe;MACpB,oBAAoB,cAAc;KACpC;KAEA,4BACE,YACA,cACA,SAAS,CAAC,MAAM,IAAI,CAAC,GACrB,WACA,WACA,uBACA,QACA,iBACF;IACF;GACF,CAAC;EACH,EACF,EACF;CACF;AACF"}
1
+ {"version":3,"file":"babel-plugin-intlayer-usage-analyzer.mjs","names":[],"sources":["../../src/babel-plugin-intlayer-usage-analyzer.ts"],"sourcesContent":["import type { NodePath, PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport type {\n CallerDescriptor,\n CallerValueSource,\n} from '@intlayer/config/callers';\nimport type { NestedFieldReferences } from '@intlayer/core/dictionaryManipulator';\n\n// ── PruneContext types ────────────────────────────────────────────────────────\n\n/**\n * Dictionary field usage result for a single dictionary key.\n *\n * 'all' → could not determine statically which fields are used;\n * keep every field (no pruning possible).\n * Set<string> → the exact top-level content field names that were accessed.\n */\nexport type DictionaryFieldUsage = Set<string> | 'all';\n\n/**\n * One node in the nested field-rename tree.\n *\n * shortName – the compact alias assigned to this field name.\n * children – rename table for the next level of user-defined keys inside\n * this field's value (empty when the value is a leaf / primitive).\n */\nexport type NestedRenameEntry = {\n shortName: string;\n children: NestedRenameMap;\n};\n\n/** A level of the field-rename tree, mapping original field names to entries. */\nexport type NestedRenameMap = Map<string, NestedRenameEntry>;\n\n/**\n * One opaque consumption of a dictionary content value: the value at\n * `fieldPath` escapes static analysis (passed as-is to a child component or a\n * function argument), so its children must keep their original key names in\n * the compiled JSON.\n */\nexport type OpaqueFieldOccurrence = {\n /**\n * Original (pre-rename) user-defined field names from the content root to\n * the escaping value, e.g. `['sections', 'hero']` for\n * `<Card data={content.sections.hero} />`.\n */\n fieldPath: string[];\n\n /** Source locations (`filePath:line`) where the value escapes. */\n locations: string[];\n};\n\n/**\n * Shared mutable state created once by the vite plugin and passed by reference\n * to the usage-analyzer (writer) and the prune/minify plugins (readers).\n *\n * All mutations happen during the usage-analysis `buildStart` phase; readers\n * only access this state during the subsequent `transform` phase.\n */\nexport type PruneContext = {\n /**\n * Maps every dictionary key seen in source files to the set of top-level\n * content fields statically accessed, or `'all'` when the access pattern\n * could not be determined.\n */\n dictionaryKeyToFieldUsageMap: Map<string, DictionaryFieldUsage>;\n\n /**\n * Dictionary keys for which the prune/minify step must be skipped entirely\n * because an edge case was detected during analysis or structure recognition.\n */\n dictionariesWithEdgeCases: Set<string>;\n\n /**\n * True if at least one source file failed to parse during the analysis phase.\n * The prune plugin uses this flag conservatively: any dictionary key without\n * a usage entry might have been referenced by the unparsable file.\n */\n hasUnparsableSourceFiles: boolean;\n\n /**\n * Maps dictionary keys to the source file paths where the result of\n * `useIntlayer` / `getIntlayer` was assigned to a plain variable, making\n * static field analysis impossible.\n */\n dictionaryKeysWithUntrackedBindings: Map<string, string[]>;\n\n /**\n * Maps each dictionary key to a nested field-rename tree built after the\n * usage analysis phase (only populated when `build.minify` is active and\n * the field usage for that dictionary is a finite `Set<string>`).\n */\n dictionaryKeyToFieldRenameMap: Map<string, NestedRenameMap>;\n\n /**\n * Maps each dictionary key to the content field paths whose values are\n * consumed \"opaquely\" (passed as-is to a child component or function\n * argument). When an opaque value has nested user-defined structure, its\n * children must not be renamed.\n *\n * The inner map is keyed by the dot-joined field path for de-duplication\n * only; the authoritative path is {@link OpaqueFieldOccurrence.fieldPath}.\n *\n * Structure: dictionaryKey → joinedFieldPath → { fieldPath, locations }\n */\n dictionaryKeysWithOpaqueFields: Map<\n string,\n Map<string, OpaqueFieldOccurrence>\n >;\n\n /**\n * Dictionary keys for which field-key renaming must be skipped even if a\n * finite field-usage set was determined.\n *\n * Populated for dictionaries whose plain-variable bindings were resolved by\n * the framework-specific extractor (Vue / Svelte SFCs), because the Babel\n * rename plugin cannot update the source-code property accesses for those\n * indirect patterns (Vue `.value.field` / Svelte `$store.field`).\n *\n * Pruning and basic minification still apply; only field-key renaming is\n * suppressed.\n */\n dictionariesSkippingFieldRename: Set<string>;\n\n /**\n * Plain variable bindings that require a framework-specific secondary pass.\n *\n * Populated during the Babel analysis phase for `.vue` and `.svelte` source\n * files where direct field access is not visible to Babel scope analysis:\n * - Vue: `content.value.fieldName` – the `.value` ref-accessor is hidden\n * - Svelte: `$varName.fieldName` – the `$` prefix creates a new identifier\n *\n * Structure: filePath → [{variableName, dictionaryKey}, …]\n */\n pendingFrameworkAnalysis: Map<\n string,\n { variableName: string; dictionaryKey: string }[]\n >;\n};\n\nexport const createPruneContext = (): PruneContext => ({\n dictionaryKeyToFieldUsageMap: new Map(),\n dictionariesWithEdgeCases: new Set(),\n hasUnparsableSourceFiles: false,\n dictionaryKeysWithUntrackedBindings: new Map(),\n dictionaryKeyToFieldRenameMap: new Map(),\n dictionaryKeysWithOpaqueFields: new Map<\n string,\n Map<string, OpaqueFieldOccurrence>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\n});\n\n/**\n * Records `nest()` references in a prune context so nest targets stay\n * resolvable — and no larger than they need to be — after purge and minify.\n *\n * A nested node stores the ORIGINAL dot path of the field it reads\n * (`nest('common', 'period')` → `'period'`) and `getNesting` resolves it at\n * runtime against the compiled JSON. That drives three adjustments:\n *\n * - Nest targets never take part in field renaming, since the stored path\n * would no longer match the renamed keys.\n * - A target already consumed by a call site keeps its analysed fields plus\n * the nested ones, so neither consumer loses content.\n * - A target reached *only* through `nest()` is given a usage entry holding\n * exactly the nested fields. Without one, the purge pass leaves the whole\n * dictionary untouched; with one, it trims the dictionary down to what the\n * nest references actually read. This matters most for `dynamic` / `fetch`\n * dictionaries, whose full static snapshot would otherwise be pulled into\n * the eager bundle just to satisfy a single nested field.\n *\n * Seeding is skipped when a source file failed to parse: that file could\n * reference the dictionary in a way the analysis never saw, which is the same\n * guard the purge pass applies to keys with no usage entry.\n *\n * @param pruneContext - Shared analysis state to update in place.\n * @param nestedDictionaryReferences - Nest-target key → referenced top-level\n * fields, as returned by `getNestedDictionaryReferences`.\n */\nexport const preserveNestedDictionaryFields = (\n pruneContext: PruneContext,\n nestedDictionaryReferences: Map<string, NestedFieldReferences>\n): void => {\n for (const [\n dictionaryKey,\n nestedFields,\n ] of nestedDictionaryReferences.entries()) {\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n const currentFieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n // A whole-dictionary reference (`nest('common')`) reads every field, so no\n // field set can describe it — leave the dictionary unpruned.\n if (nestedFields === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n continue;\n }\n\n if (!currentFieldUsage) {\n if (pruneContext.hasUnparsableSourceFiles) continue;\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n new Set(nestedFields)\n );\n continue;\n }\n\n // Already consumed by a call site that reads every field.\n if (currentFieldUsage === 'all') continue;\n\n for (const nestedField of nestedFields) {\n currentFieldUsage.add(nestedField);\n }\n }\n};\n\n// ── Usage-analyzer Babel plugin ───────────────────────────────────────────────\n\n/**\n * Methods the runtime attaches to a resolved content object rather than fields\n * declared in a `.content` file.\n *\n * `vanilla-intlayer`'s `useIntlayer` / `useDictionary` assign `onChange` onto\n * the content they return, so `useIntlayer('app').onChange(cb)` is a\n * subscription — not a read of a field named `onChange`. Without this list the\n * analyser records `onChange` as the only consumed field and the purge pass\n * strips every real field from the compiled dictionary.\n *\n * The analyser looks *through* these calls: the callback parameter is analysed\n * as a fresh content root, and the call result is analysed again because the\n * helpers return the content object for chaining.\n */\nexport const CHAINABLE_RUNTIME_METHOD_NAMES = new Set(['onChange']);\n\n/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = [\n 'useIntlayer',\n 'getIntlayer',\n 'getIntlayerAsync',\n] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n// ── Compat-adapter namespace callers ──────────────────────────────────────────\n\n// The caller-descriptor model is shared across the build pipeline (babel, swc\n// wire format), the LSP and the compat packages' bundler plugins. It lives in\n// `@intlayer/config/callers`; see that module for the full documentation.\nexport type {\n CallerDescriptor,\n CallerResultShape,\n CallerValueSource,\n} from '@intlayer/config/callers';\n\n/**\n * Configuration entry for a single compat namespace caller.\n *\n * @deprecated Alias kept for backward compatibility — use the shared\n * {@link CallerDescriptor} from `@intlayer/config/callers` instead.\n */\nexport type CompatCallerConfig = CallerDescriptor;\n\n/**\n * Default registry of compat namespace callers.\n *\n * Intentionally empty — compat-specific caller configurations belong in their\n * respective adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) and are injected into `makeUsageAnalyzerBabelPlugin`\n * via the `compatCallers` option. Centralising them here would couple the\n * core `@intlayer/babel` package to every compat adapter, which violates the\n * design principle that compat logic lives in compat packages.\n */\nexport const DEFAULT_COMPAT_CALLERS: CompatCallerConfig[] = [];\n\n/** Default namespace used by compat callers when no namespace argument is given. */\nconst DEFAULT_COMPAT_NAMESPACE = 'translation';\n\n/**\n * Climbs past an enclosing `await` expression so that\n * `const t = await getTranslations('ns')` — or `await getIntlayerAsync('key')`\n * — is resolved to its variable declarator the same way the synchronous form\n * is.\n */\nexport const unwrapAwait = (\n babelTypes: typeof BabelTypes,\n path: NodePath<BabelTypes.Node>\n): NodePath<BabelTypes.Node> => {\n const parentPath = path.parentPath;\n if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) {\n return parentPath;\n }\n return path;\n};\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n if (existingUsage instanceof Set) {\n // Merge in place — the set is owned by the map.\n for (const fieldName of fieldUsage) existingUsage.add(fieldName);\n return;\n }\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n new Set(fieldUsage)\n );\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that the content value at `fieldPath` is consumed opaquely. */\n const markOpaqueField = (\n fieldPath: string[],\n line: number | undefined\n ): void => {\n const pathToOccurrence =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey) ??\n new Map<string, OpaqueFieldOccurrence>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const joinedFieldPath = fieldPath.join('.');\n const occurrence = pathToOccurrence.get(joinedFieldPath) ?? {\n fieldPath: [...fieldPath],\n locations: [],\n };\n if (!occurrence.locations.includes(location)) {\n occurrence.locations.push(location);\n }\n pathToOccurrence.set(joinedFieldPath, occurrence);\n pruneContext.dictionaryKeysWithOpaqueFields.set(\n dictionaryKey,\n pathToOccurrence\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n /**\n * Analyses how the content value at `fieldPath` (currently referenced by\n * `refPath`) is consumed, to detect opaque consumption (passing a dictionary\n * value as-is to a prop or function argument).\n *\n * Member-access chains and destructuring are followed recursively with an\n * extended field path, so the *terminal* consumption of a chain like\n * `content.sections.hero` decides opacity — mirroring how far the\n * source-code renamer can rewrite accesses. When the terminal consumption\n * escapes static tracking, the deepest reached field path is marked opaque\n * so the children of that value keep their original key names.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n const parentNode = refPath.parent;\n const parentPath = refPath.parentPath;\n\n // 1. Chained member access (e.g. field.sub or field?.sub): follow the\n // chain with an extended path and analyse the terminal consumption.\n if (\n parentPath &&\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …) is transparent: the JSON renamers\n // apply the same rename map to every array element.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, fieldPath);\n return;\n }\n\n if (\n !memberNode.computed &&\n babelTypes.isIdentifier(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.name,\n ]);\n return;\n }\n\n if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.value,\n ]);\n return;\n }\n\n // Dynamic computed access (field[expr]): the renamer cannot rewrite the\n // key, so the children of the current path must keep their names.\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field): follow each binding.\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);\n return;\n }\n\n // 3. Ignored patterns (e.g. array literals [content])\n if (babelTypes.isArrayExpression(parentNode)) {\n return;\n }\n\n // 4. Opaque consumption (passed to prop, function, etc.)\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n };\n\n /**\n * Analyses a destructuring pattern applied to the content value at\n * `fieldPath`, recursively following each bound local variable's references\n * via {@link analyzeOpaqueUsage}.\n *\n * Patterns the source-code renamer cannot rewrite (rest elements, dynamic\n * computed keys, array patterns) mark the corresponding path opaque.\n */\n const analyzeOpaqueDestructuring = (\n objectPattern: BabelTypes.ObjectPattern,\n scopePath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n for (const property of objectPattern.properties) {\n // A rest element re-exposes the remaining fields under their original\n // names — nothing at this level may be renamed.\n if (babelTypes.isRestElement(property)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be renamed.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const childFieldPath = [...fieldPath, keyName];\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isObjectPattern(bindingTarget)) {\n analyzeOpaqueDestructuring(bindingTarget, scopePath, childFieldPath);\n continue;\n }\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);\n if (!localVarBinding) continue;\n for (const referencePath of localVarBinding.referencePaths) {\n analyzeOpaqueUsage(referencePath, childFieldPath);\n }\n continue;\n }\n\n // Array patterns and other targets escape the rename walk.\n markOpaqueField(childFieldPath, property.loc?.start.line);\n }\n };\n\n /**\n * Helper to collect field names from an ObjectPattern (destructuring).\n * Returns true if successful, false when the accessed fields cannot be\n * determined statically (rest element or dynamic computed key → 'all').\n */\n const collectFieldsFromObjectPattern = (\n pattern: BabelTypes.ObjectPattern,\n initPath: NodePath<BabelTypes.Node>,\n targetSet: Set<string>\n ): boolean => {\n if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) {\n return false;\n }\n\n for (const property of pattern.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be attributed to a\n // specific field — the whole dictionary must be kept.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n return false;\n }\n\n let fieldName: string | undefined;\n\n if (!property.computed && babelTypes.isIdentifier(property.key)) {\n fieldName = property.key.name;\n } else if (babelTypes.isStringLiteral(property.key)) {\n fieldName = property.key.value;\n }\n\n if (!fieldName) continue;\n\n targetSet.add(fieldName);\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const variableBinding = initPath.scope.getBinding(bindingTarget.name);\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, [fieldName]);\n }\n }\n } else if (babelTypes.isObjectPattern(bindingTarget)) {\n // Nested pattern: const { fieldA: { nested } } = … — follow each\n // nested binding so deeper opaque consumption is detected.\n analyzeOpaqueDestructuring(bindingTarget, initPath, [fieldName]);\n } else {\n // Array patterns and other targets escape the rename walk.\n markOpaqueField([fieldName], property.loc?.start.line);\n }\n }\n return true;\n };\n\n /**\n * Returns the call path when `memberExprPath` is a chainable runtime helper\n * being invoked (`content.onChange(cb)`), otherwise `undefined`.\n *\n * See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`\n * reference that is never called is treated as an ordinary field access, so\n * only the invoked form is looked through.\n */\n const getChainedRuntimeCallPath = (\n memberExprPath: NodePath<BabelTypes.Node> | null | undefined,\n fieldName: string\n ): NodePath<BabelTypes.Node> | undefined => {\n if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return undefined;\n if (!memberExprPath) return undefined;\n\n const callPath = memberExprPath.parentPath;\n if (!callPath) return undefined;\n if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) {\n return undefined;\n }\n if (\n (callPath.node as BabelTypes.CallExpression).callee !==\n memberExprPath.node\n ) {\n return undefined;\n }\n\n return callPath;\n };\n\n /**\n * Analyses the callbacks handed to a chainable runtime helper.\n *\n * The callback receives the freshly-resolved content root, so its parameter\n * is walked exactly like `const content = useIntlayer('key')` — either as a\n * plain binding or as a destructuring pattern.\n */\n const analyzeChainedCallbackArguments = (\n callPath: NodePath<BabelTypes.Node>\n ): void => {\n const argumentPaths = callPath.get(\n 'arguments'\n ) as NodePath<BabelTypes.Node>[];\n\n for (const argumentPath of argumentPaths) {\n if (\n !argumentPath.isArrowFunctionExpression() &&\n !argumentPath.isFunctionExpression()\n ) {\n continue; // not a callback (e.g. an options object) – nothing to read\n }\n\n const [firstParameterPath] = argumentPath.get(\n 'params'\n ) as NodePath<BabelTypes.Node>[];\n if (!firstParameterPath) continue; // callback ignores the content\n\n // (content) => … — walk the parameter's references.\n if (firstParameterPath.isIdentifier()) {\n const parameterBinding = argumentPath.scope.getBinding(\n firstParameterPath.node.name\n );\n if (!parameterBinding) {\n markUntrackedBinding();\n continue;\n }\n\n const accessedFieldNames = new Set<string>();\n if (\n analyzeContentBindingReferences(parameterBinding, accessedFieldNames)\n ) {\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n }\n continue;\n }\n\n // ({ title }) => … — destructured directly in the parameter list.\n if (firstParameterPath.isObjectPattern()) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n firstParameterPath.node,\n argumentPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n continue;\n }\n\n markUntrackedBinding();\n }\n };\n\n /**\n * Walks every reference of a local binding holding the content root,\n * collecting the top-level fields it reads into `accessedTopLevelFieldNames`.\n *\n * @returns `true` when a reference escaped static tracking, meaning the whole\n * dictionary must be kept.\n */\n const analyzeContentBindingReferences = (\n variableBinding: { referencePaths: NodePath<BabelTypes.Node>[] },\n accessedTopLevelFieldNames: Set<string>\n ): boolean => {\n for (const variableReferencePath of variableBinding.referencePaths) {\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.name;\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = variableReferencePath.parentPath;\n\n // content.onChange(cb) — a runtime subscription, not a field read.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n return true;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // The binding's value escapes into an array literal\n // (e.g. `[appleWatch, airpods].map((entry) => entry.name)`). Its fields\n // are then accessed through iteration that Babel cannot follow back to\n // this dictionary, so we cannot know which fields are used. This is the\n // canonical meta-record / collection access pattern — conservatively\n // keep every field rather than pruning the content to nothing.\n return true;\n } else if (\n // Solid / Angular: content() signal accessor → content().field\n (babelTypes.isCallExpression(referenceParentNode) ||\n babelTypes.isOptionalCallExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callExprPath = variableReferencePath.parentPath;\n const callParent = callExprPath?.parent;\n\n if (\n callParent &&\n (babelTypes.isMemberExpression(callParent) ||\n babelTypes.isOptionalMemberExpression(callParent)) &&\n (callParent as BabelTypes.MemberExpression).object ===\n callExprPath?.node\n ) {\n // content().field\n const memberExpr = callParent as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpr.computed &&\n babelTypes.isIdentifier(memberExpr.property)\n ) {\n fieldName = memberExpr.property.name;\n } else if (\n memberExpr.computed &&\n babelTypes.isStringLiteral(memberExpr.property)\n ) {\n fieldName = memberExpr.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = callExprPath?.parentPath;\n\n // content().onChange(cb) — a runtime subscription, not a field.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);\n } else {\n // content()[dynamicKey] – cannot resolve statically\n return true;\n }\n } else if (\n callParent &&\n babelTypes.isVariableDeclarator(callParent) &&\n babelTypes.isObjectPattern(callParent.id) &&\n callExprPath &&\n collectFieldsFromObjectPattern(\n callParent.id,\n callExprPath,\n accessedTopLevelFieldNames\n )\n ) {\n // const { title } = content()\n // fields already added to accessedTopLevelFieldNames by collectFieldsFromObjectPattern\n } else {\n // content() with no field access or passed opaquely → cannot prune\n return true;\n }\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n return true;\n }\n }\n\n return false;\n };\n\n /**\n * Analyses how the content root referenced by `rootPath` is consumed.\n *\n * `rootPath` is the `useIntlayer('key')` call itself, or any expression that\n * evaluates back to the same content object — currently the result of a\n * chainable runtime helper such as `.onChange(…)`.\n */\n const analyzeContentRoot = (rootPath: NodePath<BabelTypes.Node>): void => {\n const parentNode = rootPath.parent;\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n parentNode.id,\n rootPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === rootPath.node\n ) {\n let fieldName: string | undefined;\n\n if (\n !parentNode.computed &&\n babelTypes.isIdentifier(parentNode.property)\n ) {\n fieldName = parentNode.property.name;\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n fieldName = parentNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = rootPath.parentPath;\n\n // useIntlayer('key').onChange(cb) — a runtime subscription: read the\n // fields out of the callback instead of recording `onChange` itself.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n return;\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ───────────────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding = rootPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n const hasUntrackedReferenceAccess = analyzeContentBindingReferences(\n variableBinding,\n accessedTopLevelFieldNames\n );\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n accessedTopLevelFieldNames\n );\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ──────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ──────────\n markUntrackedBinding();\n };\n\n // `getIntlayerAsync('key')` is consumed through its `await`, so the content\n // root is the await expression — reading the call's own parent would see\n // only the `AwaitExpression` and give up on every field.\n analyzeContentRoot(unwrapAwait(babelTypes, callExpressionPath));\n};\n\n// ── Compat namespace-caller analysis ──────────────────────────────────────────\n\n/**\n * Reads a fully-static string from an AST node. Returns `undefined` for\n * dynamic values (identifiers, expressions, template literals with\n * interpolations, …).\n */\nconst readStaticString = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n if (!node) return undefined;\n if (babelTypes.isStringLiteral(node)) return node.value;\n if (\n babelTypes.isTemplateLiteral(node) &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n }\n return undefined;\n};\n\n/** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */\nconst firstPathSegment = (path: string): string => path.split('.')[0] ?? path;\n\n/**\n * Reads the static first dot-path segment from a `t()` first-argument node.\n *\n * t('counter.label') → 'counter'\n * t(`counter.${x}`) → 'counter' (static prefix before the first dot)\n * t(`${x}.label`) → undefined (dynamic first segment)\n * t(someVariable) → undefined\n */\nconst readStaticFirstSegment = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n const staticString = readStaticString(babelTypes, node);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Template literal whose first quasi already contains the dot delimiter, e.g.\n // `counter.${index}` → the leading `counter` segment is statically known.\n if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {\n const firstQuasi =\n node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n if (firstQuasi?.includes('.')) {\n return firstPathSegment(firstQuasi);\n }\n }\n return undefined;\n};\n\n/**\n * Reads a static string property from an object expression. Returns\n * `'__default__'` when the property is absent and `undefined` when present but\n * dynamic.\n */\nconst readObjectProperty = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): string | '__default__' | undefined => {\n for (const property of objectExpression.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n const keyMatches =\n (babelTypes.isIdentifier(property.key) &&\n property.key.name === propertyName) ||\n (babelTypes.isStringLiteral(property.key) &&\n property.key.value === propertyName);\n if (!keyMatches) continue;\n const staticValue = readStaticString(babelTypes, property.value);\n return staticValue ?? undefined; // present but dynamic → undefined\n }\n return '__default__'; // property absent\n};\n\n/**\n * Reads a named JSX attribute as a static string and returns it wrapped in a\n * `StringLiteral` node so the result can be fed to the same namespace resolver\n * as a call argument. Handles both `id=\"home.title\"` and `id={'home.title'}`.\n * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).\n */\nconst readJsxAttributeString = (\n babelTypes: typeof BabelTypes,\n openingElement: BabelTypes.JSXOpeningElement,\n attributeName: string\n): BabelTypes.StringLiteral | undefined => {\n for (const attribute of openingElement.attributes) {\n if (!babelTypes.isJSXAttribute(attribute)) continue;\n if (\n !babelTypes.isJSXIdentifier(attribute.name) ||\n attribute.name.name !== attributeName\n ) {\n continue;\n }\n\n const value = attribute.value;\n // id=\"home.title\"\n if (babelTypes.isStringLiteral(value)) return value;\n // id={'home.title'} / id={`home.title`}\n if (babelTypes.isJSXExpressionContainer(value)) {\n const staticString = readStaticString(babelTypes, value.expression);\n if (staticString !== undefined) {\n return babelTypes.stringLiteral(staticString);\n }\n }\n return undefined; // attribute present but dynamic\n }\n return undefined; // attribute absent\n};\n\n/**\n * Resolves the namespace (dictionary key) for a compat caller call-site from\n * one `CallerValueSource`. Returns the static key, or `'__default__'` when the\n * configured argument is absent (caller falls back to its default namespace),\n * or `undefined` when the value is present but dynamic.\n */\nconst resolveCompatNamespace = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n source: CallerValueSource\n): string | '__default__' | undefined => {\n if (source.from === 'fixed') return source.value;\n\n if (source.from === 'path-first-segment') {\n const firstArg = callArguments[0];\n // No sensible default: the dictionary key is *derived* from the id, so an\n // absent id means the call cannot be attributed to any dictionary → skip.\n if (firstArg === undefined) return undefined;\n\n // String form: formatMessage('home.title', values)\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Descriptor form: formatMessage({ id: 'home.title', ... }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue === '__default__') return '__default__'; // id absent\n if (idValue !== undefined) return firstPathSegment(idValue); // static id\n return undefined; // id present but dynamic\n }\n\n return undefined; // dynamic first argument\n }\n\n if (source.from === 'argument') {\n const argument = callArguments[source.index];\n if (argument === undefined) return '__default__';\n\n // Direct string namespace: useTranslations('about')\n const staticString = readStaticString(babelTypes, argument);\n if (staticString !== undefined) return staticString;\n\n // Object form: getTranslations({ locale, namespace: 'about' })\n if (babelTypes.isObjectExpression(argument)) {\n return readObjectProperty(babelTypes, argument, 'namespace');\n }\n return undefined; // present but dynamic\n }\n\n // from === 'option'\n const optionsArgument = callArguments[source.argumentIndex];\n if (optionsArgument === undefined) return '__default__';\n if (!babelTypes.isObjectExpression(optionsArgument)) return undefined;\n return readObjectProperty(babelTypes, optionsArgument, source.property);\n};\n\n/**\n * Resolves the namespace from a list of `CallerValueSource`s, tried in\n * declaration order. The first source yielding a static string wins.\n * Returns `'__default__'` when every source reports an absent value, and\n * `undefined` when the namespace is present somewhere but dynamic.\n */\nconst resolveNamespaceFromSources = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[]\n): string | '__default__' | undefined => {\n let sawAbsentValue = false;\n\n for (const source of sources) {\n const resolved = resolveCompatNamespace(babelTypes, callArguments, source);\n if (typeof resolved === 'string' && resolved !== '__default__') {\n return resolved;\n }\n if (resolved === '__default__') sawAbsentValue = true;\n }\n\n return sawAbsentValue ? '__default__' : undefined;\n};\n\n/**\n * Resolves an optional `keyPrefix` for a compat caller. Returns the static\n * prefix string, `null` when no prefix is configured/present, or `undefined`\n * when a prefix is present but dynamic.\n */\nconst resolveCompatKeyPrefix = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[] | undefined\n): string | null | undefined => {\n if (!sources || sources.length === 0) return null;\n const resolved = resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n sources\n );\n if (resolved === '__default__') return null; // prefix absent\n return resolved; // string or undefined (dynamic)\n};\n\n/**\n * Analyses how the translation function produced by a compat namespace caller\n * (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,\n * `useI18n`) is consumed, then records the accessed top-level dictionary fields\n * into `pruneContext`.\n *\n * Dictionaries consumed this way are always added to\n * `dictionariesSkippingFieldRename`: the field accesses are string-literal\n * dot-paths inside `t()` calls, which the field-rename plugin cannot rewrite,\n * so renaming the compiled JSON keys would break runtime lookups. Pruning\n * (top-level field removal) remains safe because it preserves field names.\n */\nconst analyzeNamespaceCallerUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callArguments: BabelTypes.CallExpression['arguments'],\n callerConfig: CompatCallerConfig,\n isSfcFile: boolean,\n /** Absolute path of the analysed file (for untracked-binding reporting). */\n currentSourceFilePath: string,\n /**\n * The call-expression path, when the caller was matched as a call. Omitted\n * for JSX-element matches (`<FormattedMessage id>`), where only the static\n * `'self'` / `'all'` analysis paths apply — the binding-based\n * `'destructured-t'` / `'return-value'` paths require a call site.\n */\n callExpressionPath?: NodePath<BabelTypes.CallExpression>,\n /**\n * Pre-resolved namespace bypassing `namespaceSources`, e.g. the `ns`\n * attribute of react-i18next's `<Trans ns=\"home\" i18nKey=\"title\" />`.\n */\n namespaceOverride?: string\n): void => {\n // 1. Resolve the dictionary key (namespace).\n const resolvedNamespace =\n namespaceOverride ??\n resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n callerConfig.namespaceSources\n );\n if (resolvedNamespace === undefined) return; // dynamic key – cannot attribute\n const namespaceString =\n resolvedNamespace === '__default__'\n ? DEFAULT_COMPAT_NAMESPACE\n : resolvedNamespace;\n\n // next-intl scopes nested objects through a dotted namespace\n // (`'about.counter'`): the dictionary key is the first segment and the\n // remainder is an implicit key prefix applied to every t() lookup.\n const namespaceSegments = namespaceString.split('.');\n const dictionaryKey = namespaceSegments[0] ?? namespaceString;\n const namespacePrefix =\n namespaceSegments.length > 1 ? namespaceSegments.slice(1).join('.') : null;\n\n // Compat string-path access is never renamable.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n // 2. SFC files (Vue / Svelte / Astro): the translation function is typically\n // invoked from the template, which Babel cannot see. Conservatively keep\n // every field to avoid pruning a template-only access.\n if (isSfcFile) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a. Mark the entire dictionary as used — static field analysis is not\n // applicable (e.g. lingui hashed IDs, Angular templates).\n if (callerConfig.translationFunction === 'all') {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a-bis. The caller returns the dictionary content object itself\n // (`useIntlayer`-like shape): reuse the native member-access /\n // destructuring analysis. Without a call site, keep every field.\n if (callerConfig.translationFunction === 'content') {\n if (callExpressionPath) {\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // 3b. The caller IS the translation call (`translationFunction: 'self'`).\n // The first argument of the current call expression is the message key.\n // Used for lingui's `i18n._('key')` / `i18n.t('key')` and react-intl's\n // `intl.formatMessage({ id: 'key' })` patterns.\n if (callerConfig.translationFunction === 'self') {\n const firstArg = callArguments[0];\n if (!firstArg) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n if (\n callerConfig.namespaceSources.some(\n (source) => source.from === 'path-first-segment'\n )\n ) {\n // The dictionary key is already the first segment of the descriptor id.\n // The field to record is the SECOND segment (first level inside the dict).\n // e.g. formatMessage({ id: 'home.title' }) → dictionaryKey='home', field='title'\n let fullId: string | undefined;\n\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) {\n fullId = staticString;\n } else if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n fullId = idValue;\n }\n }\n\n if (fullId !== undefined) {\n const segments = fullId.split('.');\n const field = segments[1];\n if (field !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([field]));\n } else {\n // Single-segment id — the whole value is the dict key; no sub-field.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // Dynamic id — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // For 'fixed' / 'argument' / 'option' namespaces: the field is the first\n // dot-segment of the first argument (the message key itself) — unless\n // `flatKey` is set, in which case the whole dotted key is the field.\n // String form: i18n._('home.title', values)\n const segment = callerConfig.flatKey\n ? readStaticString(babelTypes, firstArg)\n : readStaticFirstSegment(babelTypes, firstArg);\n if (segment !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([segment]));\n return;\n }\n\n // Descriptor form: i18n._({ id: 'home.title', message: '...' }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([callerConfig.flatKey ? idValue : firstPathSegment(idValue)])\n );\n return;\n }\n }\n\n // Dynamic key — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The remaining strategies (`'destructured-t'` / `'return-value'`) resolve\n // the `t` binding from the call site, so they require a call-expression path.\n // JSX-element matches never reach here (they use `'self'` / `'all'`); guard\n // defensively so a misconfigured JSX caller keeps every field instead of\n // throwing.\n if (!callExpressionPath) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3. Resolve an optional explicit keyPrefix (e.g. react-i18next's\n // `{ keyPrefix }` option). A static prefix fixes the single consumed\n // top-level field regardless of the individual t() paths.\n const explicitKeyPrefix = resolveCompatKeyPrefix(\n babelTypes,\n callArguments,\n callerConfig.keyPrefixSources\n );\n if (explicitKeyPrefix === undefined) {\n // Prefix present but dynamic → unknown field set.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The namespace-derived prefix (next-intl) and the explicit keyPrefix option\n // (react-i18next / i18next) never coexist in practice; prefer whichever is\n // present. Either way, the consumed top-level field is the prefix's first\n // segment.\n const effectivePrefix = namespacePrefix ?? explicitKeyPrefix;\n if (effectivePrefix !== null) {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([firstPathSegment(effectivePrefix)])\n );\n return;\n }\n\n // 4. Locate the `t` function binding.\n let translationBinding: ReturnType<NodePath['scope']['getBinding']> | null =\n null;\n\n if (callerConfig.translationFunction === 'destructured-t') {\n // const { t } = useTranslation('ns')\n const parentNode = callExpressionPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key) &&\n property.key.name === 't' &&\n babelTypes.isIdentifier(property.value)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(property.value.name) ?? null;\n }\n }\n }\n } else {\n // const t = useTranslations('ns') / const t = await getTranslations('ns')\n const resultPath = unwrapAwait(babelTypes, callExpressionPath);\n const parentNode = resultPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(parentNode.id.name) ?? null;\n }\n }\n\n // Could not statically locate `t` (e.g. result stored whole, re-exported) →\n // conservatively keep all fields.\n if (!translationBinding) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 5. Inspect every reference to `t`.\n const accessedFields = new Set<string>();\n let hasUntrackedUsage = false;\n\n for (const referencePath of translationBinding.referencePaths) {\n const parentNode = referencePath.parent;\n\n // Must be a direct call: t('path')\n const isDirectCall =\n (babelTypes.isCallExpression(parentNode) ||\n babelTypes.isOptionalCallExpression(parentNode)) &&\n (parentNode as BabelTypes.CallExpression).callee === referencePath.node;\n\n if (!isDirectCall) {\n // t passed as a prop / argument / reassigned → fields unknown.\n hasUntrackedUsage = true;\n break;\n }\n\n const firstArgument = (parentNode as BabelTypes.CallExpression)\n .arguments[0];\n const segment = readStaticFirstSegment(babelTypes, firstArgument);\n if (segment === undefined) {\n hasUntrackedUsage = true;\n break;\n }\n accessedFields.add(segment);\n }\n\n if (hasUntrackedUsage) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // Only record a finite field set when at least one field was actually\n // accessed. Recording an empty set would prune every field even though the\n // dictionary may be consumed elsewhere.\n if (accessedFields.size > 0) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFields);\n }\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site —\n * and each configured compat namespace caller — accesses. Results are\n * accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n *\n * @param pruneContext - Shared mutable state written by this plugin.\n * @param options - Optional overrides. `compatCallers` defaults to\n * {@link DEFAULT_COMPAT_CALLERS}; pass `[]` to disable\n * compat-adapter analysis entirely.\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (\n pruneContext: PruneContext,\n options?: { compatCallers?: CompatCallerConfig[] }\n ) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => {\n const compatCallers = options?.compatCallers ?? DEFAULT_COMPAT_CALLERS;\n\n return {\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte') ||\n currentSourceFilePath.endsWith('.astro');\n\n // Phase 1: collect local aliases for native intlayer callers and\n // for compat namespace callers (gated by import source).\n const intlayerCallerLocalNameMap = new Map<string, string>();\n const compatCallerLocalNameMap = new Map<\n string,\n CompatCallerConfig\n >();\n\n // Module specifiers imported by the analysed file — used to gate\n // `requiresImport` callers (generic names such as lingui's `t`).\n const fileImportSources = new Set<string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n const importSource = importDeclarationPath.node.source.value;\n fileImportSources.add(importSource);\n\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n continue;\n }\n\n const compatCaller = compatCallers.find(\n (caller) =>\n caller.callerName === importedName &&\n caller.importSources.includes(importSource)\n );\n if (compatCaller) {\n compatCallerLocalNameMap.set(\n importSpecifier.local.name,\n compatCaller\n );\n }\n }\n },\n });\n\n // Method-matched compat callers (e.g. i18next `getFixedT`,\n // `intl.formatMessage`) are recognised by method name on any\n // object. Callers flagged `requiresImport` only participate when\n // the file imports from one of their declared modules.\n const methodCompatCallers = compatCallers.filter(\n (caller) =>\n caller.matchAsMethod &&\n (!caller.requiresImport ||\n caller.importSources.some((source) =>\n fileImportSources.has(source)\n ))\n );\n\n const hasNativeCallers = intlayerCallerLocalNameMap.size > 0;\n const hasCompatCallers =\n compatCallerLocalNameMap.size > 0 ||\n methodCompatCallers.length > 0;\n\n if (!hasNativeCallers && !hasCompatCallers) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n let isMethodCall = false;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n isMethodCall = true;\n }\n\n if (!localCallerName) return;\n\n // Native intlayer caller (useIntlayer / getIntlayer)\n if (intlayerCallerLocalNameMap.has(localCallerName)) {\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const dictionaryKey = readStaticString(\n babelTypes,\n callArguments[0]\n );\n if (!dictionaryKey) return; // dynamic key\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n return;\n }\n\n // Compat namespace caller (imported)\n const importedCompatCaller =\n compatCallerLocalNameMap.get(localCallerName);\n if (importedCompatCaller && !isMethodCall) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n importedCompatCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n return;\n }\n\n // Compat namespace caller (method-matched, e.g. getFixedT)\n if (isMethodCall) {\n const methodCaller = methodCompatCallers.find(\n (caller) => caller.callerName === localCallerName\n );\n if (methodCaller) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n methodCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n }\n }\n },\n JSXOpeningElement: (jsxOpeningElementPath) => {\n const nameNode = jsxOpeningElementPath.node.name;\n if (!babelTypes.isJSXIdentifier(nameNode)) return;\n\n const jsxCaller = compatCallerLocalNameMap.get(nameNode.name);\n if (!jsxCaller?.jsxIdAttribute) return;\n\n // Read the configured id attribute as a static string. A missing\n // or dynamic id yields an empty argument list, which resolves the\n // namespace to undefined and is skipped (consistent with how a\n // dynamic `useIntlayer(key)` call is handled).\n const idNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxIdAttribute\n );\n\n // Optional dedicated namespace attribute, e.g. react-i18next's\n // `<Trans ns=\"home\" i18nKey=\"title\" />`. When configured but\n // absent/dynamic, the dictionary cannot be attributed → skip.\n let namespaceOverride: string | undefined;\n if (jsxCaller.jsxNamespaceAttribute) {\n const namespaceNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxNamespaceAttribute\n );\n if (!namespaceNode) return;\n namespaceOverride = namespaceNode.value;\n }\n\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n idNode ? [idNode] : [],\n jsxCaller,\n isSfcFile,\n currentSourceFilePath,\n undefined,\n namespaceOverride\n );\n },\n });\n },\n },\n },\n };\n };\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { CHAINABLE_RUNTIME_METHOD_NAMES, DEFAULT_COMPAT_CALLERS, INTLAYER_CALLER_NAMES, createPruneContext, makeUsageAnalyzerBabelPlugin, preserveNestedDictionaryFields } from "./babel-plugin-intlayer-usage-analyzer.mjs";
1
+ import { CHAINABLE_RUNTIME_METHOD_NAMES, DEFAULT_COMPAT_CALLERS, INTLAYER_CALLER_NAMES, createPruneContext, makeUsageAnalyzerBabelPlugin, preserveNestedDictionaryFields, unwrapAwait } from "./babel-plugin-intlayer-usage-analyzer.mjs";
2
2
  import { extractScriptBlocks, injectScriptBlocks } from "./extractScriptBlocks.mjs";
3
3
  import { extractDictionaryKey, extractDictionaryKeyFromPath } from "./extractContent/utils/extractDictionaryKey.mjs";
4
4
  import { extractDictionaryInfo, getOutput, resolveContentFilePaths } from "./extractContent/utils/extractDictionaryInfo.mjs";
@@ -18,4 +18,4 @@ import { intlayerMinifyBabelPlugin } from "./babel-plugin-intlayer-minify.mjs";
18
18
  import { getOptimizePluginOptions } from "./getOptimizePluginOptions.mjs";
19
19
  import { getMinifyPluginOptions, getPurgePluginOptions } from "./getPurgePluginOptions.mjs";
20
20
 
21
- export { BABEL_PARSER_OPTIONS, CHAINABLE_RUNTIME_METHOD_NAMES, DEFAULT_COMPAT_CALLERS, INTLAYER_CALLER_NAMES, INTLAYER_USAGE_REGEX, SERVER_CAPABLE_PACKAGES, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, buildUsageCheckRegex, createPruneContext, detectPackageName, extractContent, extractContentSync, extractDictionaryInfo, extractDictionaryKey, extractDictionaryKeyFromPath, extractScriptBlocks, generateKey, generateShortFieldName, getComponentName, getExtractPluginOptions, getMinifyPluginOptions, getNestedRenameEntryAtPath, getOptimizePluginOptions, getOutput, getPurgePluginOptions, getSharedPruneContext, getUsageCheckRegex, injectScriptBlocks, intlayerExtractBabelPlugin, intlayerMinifyBabelPlugin, intlayerOptimizeBabelPlugin, intlayerPurgeBabelPlugin, makeFieldRenameBabelPlugin, makeUsageAnalyzerBabelPlugin, mergeWithExistingMultilingualDictionary, mergeWithExistingPerLocaleDictionary, optimizeSourceFile, packageList, preserveNestedDictionaryFields, renameFieldsInCode, renameFieldsInSourceFile, renameIntlayerFieldAccesses, resolveContentFilePaths, runIntlayerPurgePipeline, serializeFieldRenameMap, writeContentHelper };
21
+ export { BABEL_PARSER_OPTIONS, CHAINABLE_RUNTIME_METHOD_NAMES, DEFAULT_COMPAT_CALLERS, INTLAYER_CALLER_NAMES, INTLAYER_USAGE_REGEX, SERVER_CAPABLE_PACKAGES, SOURCE_FILE_REGEX, analyzeFieldUsageInFile, buildNestedRenameMapFromContent, buildUsageCheckRegex, createPruneContext, detectPackageName, extractContent, extractContentSync, extractDictionaryInfo, extractDictionaryKey, extractDictionaryKeyFromPath, extractScriptBlocks, generateKey, generateShortFieldName, getComponentName, getExtractPluginOptions, getMinifyPluginOptions, getNestedRenameEntryAtPath, getOptimizePluginOptions, getOutput, getPurgePluginOptions, getSharedPruneContext, getUsageCheckRegex, injectScriptBlocks, intlayerExtractBabelPlugin, intlayerMinifyBabelPlugin, intlayerOptimizeBabelPlugin, intlayerPurgeBabelPlugin, makeFieldRenameBabelPlugin, makeUsageAnalyzerBabelPlugin, mergeWithExistingMultilingualDictionary, mergeWithExistingPerLocaleDictionary, optimizeSourceFile, packageList, preserveNestedDictionaryFields, renameFieldsInCode, renameFieldsInSourceFile, renameIntlayerFieldAccesses, resolveContentFilePaths, runIntlayerPurgePipeline, serializeFieldRenameMap, unwrapAwait, writeContentHelper };
@@ -1,4 +1,4 @@
1
- import { makeUsageAnalyzerBabelPlugin } from "./babel-plugin-intlayer-usage-analyzer.mjs";
1
+ import { INTLAYER_CALLER_NAMES, makeUsageAnalyzerBabelPlugin } from "./babel-plugin-intlayer-usage-analyzer.mjs";
2
2
  import { extractScriptBlocks, injectScriptBlocks } from "./extractScriptBlocks.mjs";
3
3
  import { makeFieldRenameBabelPlugin } from "./babel-plugin-intlayer-field-rename.mjs";
4
4
  import { intlayerOptimizeBabelPlugin } from "./babel-plugin-intlayer-optimize.mjs";
@@ -28,18 +28,17 @@ const BABEL_PARSER_OPTIONS = {
28
28
  ]
29
29
  };
30
30
  /**
31
- * Fast pre-check: matches files that could contain native intlayer calls
32
- * (`useIntlayer` / `getIntlayer`). Used by the optimize/transform pass, which
33
- * only rewrites native calls.
34
- */
35
- const INTLAYER_USAGE_REGEX = /\b(use|get)Intlayer\b/;
36
- /**
37
31
  * Builds a fast pre-check regex that matches files potentially containing
38
32
  * native intlayer calls and/or the given compat namespace caller names.
39
33
  *
40
34
  * The result is used to skip files that definitely contain none of the
41
35
  * relevant function names, avoiding unnecessary Babel parsing.
42
36
  *
37
+ * The native names come from {@link INTLAYER_CALLER_NAMES} rather than being
38
+ * spelled out here: a name missing from this regex silently opts every file
39
+ * using it out of the optimize, purge and minify passes, leaving its calls to
40
+ * resolve through the dictionary registry — empty in browser bundles.
41
+ *
43
42
  * Compat caller names are NOT hardcoded here — they are provided at runtime by
44
43
  * the compat adapter packages that configure the usage-analysis pipeline.
45
44
  *
@@ -48,16 +47,18 @@ const INTLAYER_USAGE_REGEX = /\b(use|get)Intlayer\b/;
48
47
  * @returns A `RegExp` matching any of the native or provided caller names.
49
48
  */
50
49
  const buildUsageCheckRegex = (extraCallerNames) => {
51
- const callerNames = [
52
- "useIntlayer",
53
- "getIntlayer",
54
- ...extraCallerNames ?? []
55
- ];
50
+ const callerNames = [...INTLAYER_CALLER_NAMES, ...extraCallerNames ?? []];
56
51
  const uniqueNames = [...new Set(callerNames)];
57
52
  return new RegExp(`\\b(${uniqueNames.join("|")})\\b`);
58
53
  };
54
+ /**
55
+ * Fast pre-check: matches files that could contain native intlayer calls
56
+ * (`useIntlayer` / `getIntlayer` / `getIntlayerAsync`). Used by the
57
+ * optimize/transform pass, which only rewrites native calls.
58
+ */
59
+ const INTLAYER_USAGE_REGEX = buildUsageCheckRegex();
59
60
  /** Usage-check regex covering only the native callers (no compat callers). */
60
- const NATIVE_USAGE_CHECK_REGEX = buildUsageCheckRegex();
61
+ const NATIVE_USAGE_CHECK_REGEX = INTLAYER_USAGE_REGEX;
61
62
  /** Cache of usage-check regexes, keyed by the compat-caller array reference. */
62
63
  const usageCheckRegexCache = /* @__PURE__ */ new WeakMap();
63
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"transformers.mjs","names":[],"sources":["../../src/transformers.ts"],"sourcesContent":["import { transformAsync } from '@babel/core';\n// Typed from `@babel/parser` rather than `@babel/core`'s\n// `InputOptions['parserOpts']` (Babel 8's rename of `TransformOptions`): the\n// two describe the same shape, but naming `@babel/core` in an exported type\n// pulls its declaration file into every consumer's type-check, and that file\n// needs `@types/convert-source-map` to resolve.\nimport type { ParserOptions } from '@babel/parser';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport {\n intlayerOptimizeBabelPlugin,\n type OptimizePluginOptions,\n} from './babel-plugin-intlayer-optimize';\nimport {\n type CompatCallerConfig,\n makeUsageAnalyzerBabelPlugin,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks, injectScriptBlocks } from './extractScriptBlocks';\n\n// ── Shared Babel parser configuration ─────────────────────────────────────────\n\n/**\n * Babel parser options covering the superset of syntaxes used across all\n * supported frameworks (React / Vue / Svelte / Angular / …).\n */\nexport const BABEL_PARSER_OPTIONS: ParserOptions = {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n};\n\n/**\n * Fast pre-check: matches files that could contain native intlayer calls\n * (`useIntlayer` / `getIntlayer`). Used by the optimize/transform pass, which\n * only rewrites native calls.\n */\nexport const INTLAYER_USAGE_REGEX = /\\b(use|get)Intlayer\\b/;\n\n/**\n * Builds a fast pre-check regex that matches files potentially containing\n * native intlayer calls and/or the given compat namespace caller names.\n *\n * The result is used to skip files that definitely contain none of the\n * relevant function names, avoiding unnecessary Babel parsing.\n *\n * Compat caller names are NOT hardcoded here — they are provided at runtime by\n * the compat adapter packages that configure the usage-analysis pipeline.\n *\n * @param extraCallerNames - Additional caller function names to match\n * (e.g. `['useTranslation', 'getFixedT']` from `@intlayer/react-i18next`).\n * @returns A `RegExp` matching any of the native or provided caller names.\n */\nexport const buildUsageCheckRegex = (extraCallerNames?: string[]): RegExp => {\n const callerNames = [\n 'useIntlayer',\n 'getIntlayer',\n ...(extraCallerNames ?? []),\n ];\n const uniqueNames = [...new Set(callerNames)];\n return new RegExp(`\\\\b(${uniqueNames.join('|')})\\\\b`);\n};\n\n/** Usage-check regex covering only the native callers (no compat callers). */\nconst NATIVE_USAGE_CHECK_REGEX = buildUsageCheckRegex();\n\n/** Cache of usage-check regexes, keyed by the compat-caller array reference. */\nconst usageCheckRegexCache = new WeakMap<\n readonly CompatCallerConfig[],\n RegExp\n>();\n\n/**\n * Returns the usage-check regex for the given compat callers, memoised by the\n * array reference. Plugins pass the same options array for every analysed\n * file, so the regex is built once per build instead of once per file.\n */\nexport const getUsageCheckRegex = (\n compatCallers?: readonly CompatCallerConfig[]\n): RegExp => {\n if (!compatCallers || compatCallers.length === 0) {\n return NATIVE_USAGE_CHECK_REGEX;\n }\n\n let usageCheckRegex = usageCheckRegexCache.get(compatCallers);\n if (!usageCheckRegex) {\n usageCheckRegex = buildUsageCheckRegex(\n compatCallers.map((caller) => caller.callerName)\n );\n usageCheckRegexCache.set(compatCallers, usageCheckRegex);\n }\n return usageCheckRegex;\n};\n\n/**\n * Matches source files that are valid targets for usage analysis and Babel\n * transformation. Excludes sourcemap files, declaration files, and other\n * non-source extensions.\n */\nexport const SOURCE_FILE_REGEX = /\\.(tsx?|[mc]?jsx?|vue|svelte|astro)$/;\n\n// ── High-level transformer functions ──────────────────────────────────────────\n\n/**\n * Runs the usage-analysis Babel plugin on a single JS/TS code string.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the content.\n */\nconst analyzeScriptContent = async (\n scriptContent: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n await transformAsync(scriptContent, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false, // analysis only – no output needed\n });\n};\n\n/**\n * Runs the usage-analysis Babel plugin on a source file, accumulating\n * field-usage data into `pruneContext`.\n *\n * For Vue / Svelte SFC files, script blocks are extracted before analysis so\n * Babel does not attempt to parse the full SFC syntax (templates, styles, …).\n * For plain JS/TS files, the whole file content is analysed directly.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the file (caller should handle and flag\n * `pruneContext.hasUnparsableSourceFiles`).\n */\nexport const analyzeFieldUsageInFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n // For SFC files (Vue / Svelte): scriptBlocks[0].contentStartOffset > 0\n // means we extracted actual <script> tags from the file.\n // For plain JS/TS: extractScriptBlocks returns the whole file as a single\n // block with offset 0, so we fall through to the same path.\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n await analyzeScriptContent(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n/**\n * Applies field-renaming to a single JS/TS code string (not an SFC).\n *\n * Returns the renamed code string, or `null` if nothing changed or if\n * Babel failed to parse the input (caller should fall back to original code).\n */\nexport const renameFieldsInCode = async (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n try {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [makeFieldRenameBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n });\n return result?.code ?? null;\n } catch {\n return null; // parse failure – caller falls back to original code\n }\n};\n\n/**\n * Applies field-renaming to a source file, correctly handling both plain\n * JS/TS files and SFC files (Vue / Svelte) by operating on each script block\n * individually and injecting the results back into the original source.\n *\n * Returns the renamed code string, or `null` if nothing changed.\n */\nexport const renameFieldsInSourceFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return null;\n if (!INTLAYER_USAGE_REGEX.test(code)) return null;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n const isSFC =\n scriptBlocks.length > 0 &&\n ((scriptBlocks[0]?.contentStartOffset ?? 0) > 0 || scriptBlocks.length > 1);\n\n if (isSFC) {\n // Raw SFC: rename each script block individually and inject back.\n const modifications: Array<{\n block: (typeof scriptBlocks)[number];\n modifiedContent: string;\n }> = [];\n\n for (const block of scriptBlocks) {\n if (!INTLAYER_USAGE_REGEX.test(block.content)) continue;\n\n const renamedCode = await renameFieldsInCode(\n block.content,\n sourceFilePath,\n pruneContext\n );\n if (renamedCode && renamedCode !== block.content) {\n modifications.push({ block, modifiedContent: renamedCode });\n }\n }\n\n if (modifications.length === 0) return null;\n return injectScriptBlocks(code, modifications);\n }\n\n // Plain JS/TS or compiled SFC (no block delimiters) – rename the whole file.\n return renameFieldsInCode(code, sourceFilePath, pruneContext);\n};\n\n/**\n * Runs the intlayer optimize Babel plugin on a source file, transforming\n * `useIntlayer('key')` / `getIntlayer('key')` calls into `useDictionary(_hash)`\n * / `getDictionary(_hash)` and injecting the corresponding dictionary imports.\n *\n * Returns `{ code, map }` on success, or `null` if the transformation produced\n * no output.\n */\nexport const optimizeSourceFile = async (\n code: string,\n sourceFilePath: string,\n options: OptimizePluginOptions\n): Promise<{\n code: string;\n map: string | object | null | undefined;\n} | null> => {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [[intlayerOptimizeBabelPlugin, options]],\n parserOpts: BABEL_PARSER_OPTIONS,\n });\n\n if (!result?.code) return null;\n\n return { code: result.code, map: result.map };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"transformers.mjs","names":[],"sources":["../../src/transformers.ts"],"sourcesContent":["import { transformAsync } from '@babel/core';\n// Typed from `@babel/parser` rather than `@babel/core`'s\n// `InputOptions['parserOpts']` (Babel 8's rename of `TransformOptions`): the\n// two describe the same shape, but naming `@babel/core` in an exported type\n// pulls its declaration file into every consumer's type-check, and that file\n// needs `@types/convert-source-map` to resolve.\nimport type { ParserOptions } from '@babel/parser';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport {\n intlayerOptimizeBabelPlugin,\n type OptimizePluginOptions,\n} from './babel-plugin-intlayer-optimize';\nimport {\n type CompatCallerConfig,\n INTLAYER_CALLER_NAMES,\n makeUsageAnalyzerBabelPlugin,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks, injectScriptBlocks } from './extractScriptBlocks';\n\n// ── Shared Babel parser configuration ─────────────────────────────────────────\n\n/**\n * Babel parser options covering the superset of syntaxes used across all\n * supported frameworks (React / Vue / Svelte / Angular / …).\n */\nexport const BABEL_PARSER_OPTIONS: ParserOptions = {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n};\n\n/**\n * Builds a fast pre-check regex that matches files potentially containing\n * native intlayer calls and/or the given compat namespace caller names.\n *\n * The result is used to skip files that definitely contain none of the\n * relevant function names, avoiding unnecessary Babel parsing.\n *\n * The native names come from {@link INTLAYER_CALLER_NAMES} rather than being\n * spelled out here: a name missing from this regex silently opts every file\n * using it out of the optimize, purge and minify passes, leaving its calls to\n * resolve through the dictionary registry — empty in browser bundles.\n *\n * Compat caller names are NOT hardcoded here — they are provided at runtime by\n * the compat adapter packages that configure the usage-analysis pipeline.\n *\n * @param extraCallerNames - Additional caller function names to match\n * (e.g. `['useTranslation', 'getFixedT']` from `@intlayer/react-i18next`).\n * @returns A `RegExp` matching any of the native or provided caller names.\n */\nexport const buildUsageCheckRegex = (extraCallerNames?: string[]): RegExp => {\n const callerNames = [...INTLAYER_CALLER_NAMES, ...(extraCallerNames ?? [])];\n const uniqueNames = [...new Set(callerNames)];\n return new RegExp(`\\\\b(${uniqueNames.join('|')})\\\\b`);\n};\n\n/**\n * Fast pre-check: matches files that could contain native intlayer calls\n * (`useIntlayer` / `getIntlayer` / `getIntlayerAsync`). Used by the\n * optimize/transform pass, which only rewrites native calls.\n */\nexport const INTLAYER_USAGE_REGEX = buildUsageCheckRegex();\n\n/** Usage-check regex covering only the native callers (no compat callers). */\nconst NATIVE_USAGE_CHECK_REGEX = INTLAYER_USAGE_REGEX;\n\n/** Cache of usage-check regexes, keyed by the compat-caller array reference. */\nconst usageCheckRegexCache = new WeakMap<\n readonly CompatCallerConfig[],\n RegExp\n>();\n\n/**\n * Returns the usage-check regex for the given compat callers, memoised by the\n * array reference. Plugins pass the same options array for every analysed\n * file, so the regex is built once per build instead of once per file.\n */\nexport const getUsageCheckRegex = (\n compatCallers?: readonly CompatCallerConfig[]\n): RegExp => {\n if (!compatCallers || compatCallers.length === 0) {\n return NATIVE_USAGE_CHECK_REGEX;\n }\n\n let usageCheckRegex = usageCheckRegexCache.get(compatCallers);\n if (!usageCheckRegex) {\n usageCheckRegex = buildUsageCheckRegex(\n compatCallers.map((caller) => caller.callerName)\n );\n usageCheckRegexCache.set(compatCallers, usageCheckRegex);\n }\n return usageCheckRegex;\n};\n\n/**\n * Matches source files that are valid targets for usage analysis and Babel\n * transformation. Excludes sourcemap files, declaration files, and other\n * non-source extensions.\n */\nexport const SOURCE_FILE_REGEX = /\\.(tsx?|[mc]?jsx?|vue|svelte|astro)$/;\n\n// ── High-level transformer functions ──────────────────────────────────────────\n\n/**\n * Runs the usage-analysis Babel plugin on a single JS/TS code string.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the content.\n */\nconst analyzeScriptContent = async (\n scriptContent: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n await transformAsync(scriptContent, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false, // analysis only – no output needed\n });\n};\n\n/**\n * Runs the usage-analysis Babel plugin on a source file, accumulating\n * field-usage data into `pruneContext`.\n *\n * For Vue / Svelte SFC files, script blocks are extracted before analysis so\n * Babel does not attempt to parse the full SFC syntax (templates, styles, …).\n * For plain JS/TS files, the whole file content is analysed directly.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the file (caller should handle and flag\n * `pruneContext.hasUnparsableSourceFiles`).\n */\nexport const analyzeFieldUsageInFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n // For SFC files (Vue / Svelte): scriptBlocks[0].contentStartOffset > 0\n // means we extracted actual <script> tags from the file.\n // For plain JS/TS: extractScriptBlocks returns the whole file as a single\n // block with offset 0, so we fall through to the same path.\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n await analyzeScriptContent(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n/**\n * Applies field-renaming to a single JS/TS code string (not an SFC).\n *\n * Returns the renamed code string, or `null` if nothing changed or if\n * Babel failed to parse the input (caller should fall back to original code).\n */\nexport const renameFieldsInCode = async (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n try {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [makeFieldRenameBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n });\n return result?.code ?? null;\n } catch {\n return null; // parse failure – caller falls back to original code\n }\n};\n\n/**\n * Applies field-renaming to a source file, correctly handling both plain\n * JS/TS files and SFC files (Vue / Svelte) by operating on each script block\n * individually and injecting the results back into the original source.\n *\n * Returns the renamed code string, or `null` if nothing changed.\n */\nexport const renameFieldsInSourceFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return null;\n if (!INTLAYER_USAGE_REGEX.test(code)) return null;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n const isSFC =\n scriptBlocks.length > 0 &&\n ((scriptBlocks[0]?.contentStartOffset ?? 0) > 0 || scriptBlocks.length > 1);\n\n if (isSFC) {\n // Raw SFC: rename each script block individually and inject back.\n const modifications: Array<{\n block: (typeof scriptBlocks)[number];\n modifiedContent: string;\n }> = [];\n\n for (const block of scriptBlocks) {\n if (!INTLAYER_USAGE_REGEX.test(block.content)) continue;\n\n const renamedCode = await renameFieldsInCode(\n block.content,\n sourceFilePath,\n pruneContext\n );\n if (renamedCode && renamedCode !== block.content) {\n modifications.push({ block, modifiedContent: renamedCode });\n }\n }\n\n if (modifications.length === 0) return null;\n return injectScriptBlocks(code, modifications);\n }\n\n // Plain JS/TS or compiled SFC (no block delimiters) – rename the whole file.\n return renameFieldsInCode(code, sourceFilePath, pruneContext);\n};\n\n/**\n * Runs the intlayer optimize Babel plugin on a source file, transforming\n * `useIntlayer('key')` / `getIntlayer('key')` calls into `useDictionary(_hash)`\n * / `getDictionary(_hash)` and injecting the corresponding dictionary imports.\n *\n * Returns `{ code, map }` on success, or `null` if the transformation produced\n * no output.\n */\nexport const optimizeSourceFile = async (\n code: string,\n sourceFilePath: string,\n options: OptimizePluginOptions\n): Promise<{\n code: string;\n map: string | object | null | undefined;\n} | null> => {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [[intlayerOptimizeBabelPlugin, options]],\n parserOpts: BABEL_PARSER_OPTIONS,\n });\n\n if (!result?.code) return null;\n\n return { code: result.code, map: result.map };\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-field-rename.d.ts","names":[],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"mappings":";;;;;;;;cA2Ba,yBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0CtB,kCAA+B,0BAEzC;;;;;;;;;;;;cA2EU,6BAA0B,WAC1B,iBAAe,iCAEzB;;;;;;;KAqBS;;EAEV;;EAEA,UAAU;;;KAIA,2BAA2B,eAErC;;;;;;;;;;;;;;cAgCW,0BAAuB,cACpB,iBACb,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;cA4aL,8BAA2B,mBACnB,YAAU,aAChB,SAAS,WAAW,UAAQ,cAC3B;;;;;;;;;;cAkIH,6BAA0B,cACtB,oBAAY,OAAA;EACD,cAAc;MAAe"}
1
+ {"version":3,"file":"babel-plugin-intlayer-field-rename.d.ts","names":[],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"mappings":";;;;;;;;cA4Ba,yBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0CtB,kCAA+B,0BAEzC;;;;;;;;;;;;cA2EU,6BAA0B,WAC1B,iBAAe,iCAEzB;;;;;;;KAqBS;;EAEV;;EAEA,UAAU;;;KAIA,2BAA2B,eAErC;;;;;;;;;;;;;;cAgCW,0BAAuB,cACpB,iBACb,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;cA4aL,8BAA2B,mBACnB,YAAU,aAChB,SAAS,WAAW,UAAQ,cAC3B;;;;;;;;;;cAsIH,6BAA0B,cACtB,oBAAY,OAAA;EACD,cAAc;MAAe"}
@@ -2,7 +2,7 @@ import { PluginObject, PluginPass } from "@babel/core";
2
2
  import * as BabelTypes from "@babel/types";
3
3
  import { CallerDescriptor } from "@intlayer/config/callers";
4
4
  //#region src/babel-plugin-intlayer-optimize.d.ts
5
- declare const CALLER_LIST: readonly ['useIntlayer', 'getIntlayer'];
5
+ declare const CALLER_LIST: readonly ['useIntlayer', 'getIntlayer', 'getIntlayerAsync'];
6
6
  /**
7
7
  * Options for the optimization Babel plugin
8
8
  */
@@ -115,9 +115,10 @@ type State = PluginPass & {
115
115
  /**
116
116
  * Babel plugin that transforms Intlayer function calls and auto-imports dictionaries.
117
117
  *
118
- * This plugin transforms calls to `useIntlayer()` and `getIntlayer()` from various Intlayer
119
- * packages into optimized dictionary access patterns, automatically importing the required
120
- * dictionary files based on the configured import mode.
118
+ * This plugin transforms calls to `useIntlayer()`, `getIntlayer()` and
119
+ * `getIntlayerAsync()` from various Intlayer packages into optimized dictionary
120
+ * access patterns, automatically importing the required dictionary files based
121
+ * on the configured import mode.
121
122
  *
122
123
  * ## Supported Input Patterns
123
124
  *
@@ -128,12 +129,25 @@ type State = PluginPass & {
128
129
  * import { useIntlayer } from 'react-intlayer';
129
130
  * import { useIntlayer } from 'next-intlayer';
130
131
  *
131
- * // getIntlayer
132
- * import { getIntlayer } from 'intlayer';
132
+ * // getIntlayer / getIntlayerAsync
133
+ * import { getIntlayer, getIntlayerAsync } from 'intlayer';
133
134
  *
134
135
  * // Usage
135
136
  * const content = useIntlayer('app');
136
137
  * const content = getIntlayer('app');
138
+ * const content = await getIntlayerAsync('app', locale);
139
+ * ```
140
+ *
141
+ * `getIntlayerAsync` is the exception to the mode table below: it always
142
+ * resolves to the per-locale loader (`getDictionaryAsync`), in every import
143
+ * mode, since loading a single locale is what it exists for.
144
+ *
145
+ * **Output (any mode):**
146
+ * ```ts
147
+ * import _dicHash_dyn from '../../.intlayer/dynamic_dictionaries/app.mjs';
148
+ * import { getDictionaryAsync as getIntlayerAsync } from 'intlayer';
149
+ *
150
+ * const content = await getIntlayerAsync(_dicHash_dyn, 'app', locale);
137
151
  * ```
138
152
  *
139
153
  * ## Transformation Modes
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-optimize.d.ts","names":[],"sources":["../../src/babel-plugin-intlayer-optimize.ts"],"mappings":";;;;cA8BM;;;;KAqDM;;;;EAIV;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;;;;;;;;;;;;EAeA;;;;EAIA;;;;EAIA,oBAAoB;;;;EAOpB;;;;EAIA;;;;;;;;;;;;;;EAcA,gBAAgB;;KAGb,QAAQ;EACX,MAAM;;EAEN,oBAAoB,YAAY,WAAW;;EAE3C,qBAAqB,YAAY,WAAW;;EAE5C;;EAEA,aAAa,oBAAoB;;EAEjC,oBAAoB;;EAEpB,mBAAmB,YAAY;;EAE/B;;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiTW,8BAA2B;EACtC,cAAc;MACZ,aAAa"}
1
+ {"version":3,"file":"babel-plugin-intlayer-optimize.d.ts","names":[],"sources":["../../src/babel-plugin-intlayer-optimize.ts"],"mappings":";;;;cA8BM;;;;KAwDM;;;;EAIV;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;EAIA;;;;;;;;;;;;;;;EAeA;;;;EAIA;;;;EAIA,oBAAoB;;;;EAOpB;;;;EAIA;;;;;;;;;;;;;;EAcA,gBAAgB;;KAGb,QAAQ;EACX,MAAM;;EAEN,oBAAoB,YAAY,WAAW;;EAE3C,qBAAqB,YAAY,WAAW;;EAE5C;;EAEA,aAAa,oBAAoB;;EAEjC,oBAAoB;;EAEpB,mBAAmB,YAAY;;EAE/B;;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+TW,8BAA2B;EACtC,cAAc;MACZ,aAAa"}