@intlayer/babel 9.4.3 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/cjs/analyzeContentUsage.cjs +301 -0
- package/dist/cjs/analyzeContentUsage.cjs.map +1 -0
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +5 -4
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +173 -282
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-purge.cjs +4 -3
- package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +49 -699
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
- package/dist/cjs/compat/namespaceResolution.cjs +124 -0
- package/dist/cjs/compat/namespaceResolution.cjs.map +1 -0
- package/dist/cjs/compat/optimizePass.cjs +144 -0
- package/dist/cjs/compat/optimizePass.cjs.map +1 -0
- package/dist/cjs/compat/usageAnalysis.cjs +195 -0
- package/dist/cjs/compat/usageAnalysis.cjs.map +1 -0
- package/dist/cjs/dictionaryImports.cjs +40 -0
- package/dist/cjs/dictionaryImports.cjs.map +1 -0
- package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs +1 -1
- package/dist/cjs/getExtractPluginOptions.cjs +1 -1
- package/dist/cjs/getOptimizePluginOptions.cjs +1 -1
- package/dist/cjs/getPurgePluginOptions.cjs +1 -1
- package/dist/cjs/index.cjs +9 -5
- package/dist/cjs/nativeCallers.cjs +41 -0
- package/dist/cjs/nativeCallers.cjs.map +1 -0
- package/dist/cjs/pruneContext.cjs +79 -0
- package/dist/cjs/pruneContext.cjs.map +1 -0
- package/dist/cjs/staticAstReaders.cjs +140 -0
- package/dist/cjs/staticAstReaders.cjs.map +1 -0
- package/dist/cjs/transformers.cjs +2 -1
- package/dist/cjs/transformers.cjs.map +1 -1
- package/dist/esm/analyzeContentUsage.mjs +300 -0
- package/dist/esm/analyzeContentUsage.mjs.map +1 -0
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs +3 -2
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-optimize.mjs +173 -282
- package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-purge.mjs +3 -2
- package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +43 -694
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
- package/dist/esm/compat/namespaceResolution.mjs +120 -0
- package/dist/esm/compat/namespaceResolution.mjs.map +1 -0
- package/dist/esm/compat/optimizePass.mjs +143 -0
- package/dist/esm/compat/optimizePass.mjs.map +1 -0
- package/dist/esm/compat/usageAnalysis.mjs +193 -0
- package/dist/esm/compat/usageAnalysis.mjs.map +1 -0
- package/dist/esm/dictionaryImports.mjs +38 -0
- package/dist/esm/dictionaryImports.mjs.map +1 -0
- package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs +1 -1
- package/dist/esm/getExtractPluginOptions.mjs +1 -1
- package/dist/esm/getOptimizePluginOptions.mjs +1 -1
- package/dist/esm/getPurgePluginOptions.mjs +1 -1
- package/dist/esm/index.mjs +5 -2
- package/dist/esm/nativeCallers.mjs +38 -0
- package/dist/esm/nativeCallers.mjs.map +1 -0
- package/dist/esm/pruneContext.mjs +76 -0
- package/dist/esm/pruneContext.mjs.map +1 -0
- package/dist/esm/staticAstReaders.mjs +129 -0
- package/dist/esm/staticAstReaders.mjs.map +1 -0
- package/dist/esm/transformers.mjs +2 -1
- package/dist/esm/transformers.mjs.map +1 -1
- package/dist/types/analyzeContentUsage.d.ts +18 -0
- package/dist/types/analyzeContentUsage.d.ts.map +1 -0
- package/dist/types/babel-plugin-intlayer-extract.d.ts +3 -4
- package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts +10 -10
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-minify.d.ts +2 -3
- package/dist/types/babel-plugin-intlayer-minify.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-optimize.d.ts +11 -18
- package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-purge.d.ts +7 -7
- package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +22 -189
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
- package/dist/types/babelParser.d.ts +3 -4
- package/dist/types/babelParser.d.ts.map +1 -1
- package/dist/types/compat/namespaceResolution.d.ts +66 -0
- package/dist/types/compat/namespaceResolution.d.ts.map +1 -0
- package/dist/types/compat/optimizePass.d.ts +58 -0
- package/dist/types/compat/optimizePass.d.ts.map +1 -0
- package/dist/types/compat/usageAnalysis.d.ts +75 -0
- package/dist/types/compat/usageAnalysis.d.ts.map +1 -0
- package/dist/types/dictionaryImports.d.ts +46 -0
- package/dist/types/dictionaryImports.d.ts.map +1 -0
- package/dist/types/extractContent/babelProcessor.d.ts +4 -5
- package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
- package/dist/types/extractContent/contentWriter.d.ts +3 -4
- package/dist/types/extractContent/contentWriter.d.ts.map +1 -1
- package/dist/types/extractContent/extractContent.d.ts +3 -4
- package/dist/types/extractContent/extractContent.d.ts.map +1 -1
- package/dist/types/extractContent/processTsxFile.d.ts +2 -3
- package/dist/types/extractContent/processTsxFile.d.ts.map +1 -1
- package/dist/types/extractContent/utils/constants.d.ts +3 -4
- package/dist/types/extractContent/utils/constants.d.ts.map +1 -1
- package/dist/types/extractContent/utils/detectPackageName.d.ts +1 -2
- package/dist/types/extractContent/utils/detectPackageName.d.ts.map +1 -1
- package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts +5 -6
- package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts.map +1 -1
- package/dist/types/extractContent/utils/extractDictionaryKey.d.ts +2 -3
- package/dist/types/extractContent/utils/extractDictionaryKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/generateKey.d.ts +1 -2
- package/dist/types/extractContent/utils/generateKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getComponentName.d.ts +1 -2
- package/dist/types/extractContent/utils/getComponentName.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts +2 -3
- package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts.map +1 -1
- package/dist/types/extractContent/utils/getOrGenerateKey.d.ts +1 -2
- package/dist/types/extractContent/utils/getOrGenerateKey.d.ts.map +1 -1
- package/dist/types/extractContent/utils/resolveDictionaryKey.d.ts +1 -2
- package/dist/types/extractContent/utils/resolveDictionaryKey.d.ts.map +1 -1
- package/dist/types/extractScriptBlocks.d.ts +3 -4
- package/dist/types/extractScriptBlocks.d.ts.map +1 -1
- package/dist/types/getExtractPluginOptions.d.ts +2 -3
- package/dist/types/getExtractPluginOptions.d.ts.map +1 -1
- package/dist/types/getOptimizePluginOptions.d.ts +1 -2
- package/dist/types/getOptimizePluginOptions.d.ts.map +1 -1
- package/dist/types/getPurgePluginOptions.d.ts +2 -3
- package/dist/types/getPurgePluginOptions.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -2
- package/dist/types/nativeCallers.d.ts +33 -0
- package/dist/types/nativeCallers.d.ts.map +1 -0
- package/dist/types/normalizedFilesList.d.ts +1 -2
- package/dist/types/normalizedFilesList.d.ts.map +1 -1
- package/dist/types/pruneContext.d.ts +152 -0
- package/dist/types/pruneContext.d.ts.map +1 -0
- package/dist/types/staticAstReaders.d.ts +84 -0
- package/dist/types/staticAstReaders.d.ts.map +1 -0
- package/dist/types/transformers.d.ts +11 -11
- package/dist/types/transformers.d.ts.map +1 -1
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-usage-analyzer.cjs","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":";;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;CACnC;CACA;CACA;AACF;;;;;;;;;;;AAgCA,MAAa,yBAA+C,CAAC;;AAG7D,MAAM,2BAA2B;;;;;;;AAQjC,MAAa,eACX,YACA,SAC8B;CAC9B,MAAM,aAAa,KAAK;CACxB,IAAI,cAAc,WAAW,kBAAkB,WAAW,IAAI,GAC5D,OAAO;CAET,OAAO;AACT;;;;;AAMA,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;CAKA,mBAAmB,YAAY,YAAY,kBAAkB,CAAC;AAChE;;;;;;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;;;;;;;;;;;;;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.cjs","names":["createCompatUsageAnalyzer","NATIVE_CALLER_NAME_SET","readStaticString"],"sources":["../../src/babel-plugin-intlayer-usage-analyzer.ts"],"sourcesContent":["import type { PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport type { CallerDescriptor } from '@intlayer/config/callers';\nimport { analyzeCallExpressionUsage } from './analyzeContentUsage';\nimport { createCompatUsageAnalyzer } from './compat/usageAnalysis';\nimport { NATIVE_CALLER_NAME_SET } from './nativeCallers';\nimport type { PruneContext } from './pruneContext';\nimport { readStaticString } from './staticAstReaders';\n\nexport type {\n CallerDescriptor,\n CallerResultShape,\n CallerValueSource,\n} from '@intlayer/config/callers';\nexport {\n CHAINABLE_RUNTIME_METHOD_NAMES,\n INTLAYER_CALLER_NAMES,\n type IntlayerCallerName,\n NATIVE_CALLER_NAME_SET,\n} from './nativeCallers';\n// Re-exported so `@intlayer/babel`'s public surface stays unchanged after the\n// split into `pruneContext` / `analyzeContentUsage` / `compat/*` modules.\nexport {\n createPruneContext,\n type DictionaryFieldUsage,\n type NestedRenameEntry,\n type NestedRenameMap,\n type OpaqueFieldOccurrence,\n type PruneContext,\n preserveNestedDictionaryFields,\n} from './pruneContext';\nexport { unwrapAwait } from './staticAstReaders';\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 * @deprecated Always empty. Compat callers are injected per build through\n * `options.compatCallers`; there is no default registry to fall back to.\n */\nexport const DEFAULT_COMPAT_CALLERS: CallerDescriptor[] = [];\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 * accesses. Results are accumulated into `pruneContext`.\n *\n * Compat-adapter callers (`useTranslation`, `useTranslations`, `formatMessage`,\n * …) are *not* known to this plugin. Each compat package's bundler plugin\n * injects its own descriptors through `options.compatCallers`, and the whole\n * compat analysis then runs behind {@link createCompatUsageAnalyzer}. With no\n * descriptors injected — the default — none of that code is reached, so a\n * plain intlayer build behaves exactly as if the adapters did not exist.\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 an\n * empty registry, disabling compat-adapter analysis.\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (\n pruneContext: PruneContext,\n options?: { compatCallers?: readonly CallerDescriptor[] }\n ) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => {\n const compatCallers = options?.compatCallers ?? [];\n\n return {\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const sourceFilePath = state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n sourceFilePath.endsWith('.vue') ||\n sourceFilePath.endsWith('.svelte') ||\n sourceFilePath.endsWith('.astro');\n\n const compatAnalyzer = createCompatUsageAnalyzer(\n babelTypes,\n pruneContext,\n compatCallers,\n { sourceFilePath, isSfcFile }\n );\n\n // Phase 1: collect the local aliases of the native intlayer\n // callers, handing every import to the compat analyser too.\n //\n // An import declaration is only ever a direct child of Program, so\n // the body is scanned directly rather than walking the whole AST.\n const nativeCallerLocalNames = new Set<string>();\n\n for (const statement of programPath.node.body) {\n if (!babelTypes.isImportDeclaration(statement)) continue;\n\n const importSource = statement.source.value;\n\n for (const importSpecifier of statement.specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : importSpecifier.imported.value;\n const localName = importSpecifier.local.name;\n\n if (NATIVE_CALLER_NAME_SET.has(importedName)) {\n nativeCallerLocalNames.add(localName);\n continue;\n }\n\n compatAnalyzer?.noteImport(\n importSource,\n importedName,\n localName\n );\n }\n }\n\n const hasCompatCallers =\n compatAnalyzer?.hasMatchableCallers() ?? false;\n\n if (nativeCallerLocalNames.size === 0 && !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 (nativeCallerLocalNames.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 sourceFilePath,\n isSfcFile\n );\n return;\n }\n\n compatAnalyzer?.analyzeCall(\n callExpressionPath,\n localCallerName,\n isMethodCall\n );\n },\n JSXOpeningElement: (jsxOpeningElementPath) => {\n compatAnalyzer?.analyzeJsxElement(jsxOpeningElementPath);\n },\n });\n },\n },\n },\n };\n };\n"],"mappings":";;;;;;;;;;;;AA6CA,MAAa,yBAA6C,CAAC;;;;;;;;;;;;;;;;;;;;AAqB3D,MAAa,gCAET,cACA,aAED,EAAE,OAAO,iBAA6D;CACrE,MAAM,gBAAgB,SAAS,iBAAiB,CAAC;CAEjD,OAAO;EACL,MAAM;EACN,SAAS,EACP,SAAS,EACP,OAAO,aAAa,UAAsB;GACxC,MAAM,iBAAiB,MAAM,KAAK,KAAK,YAAY;GACnD,MAAM,YACJ,eAAe,SAAS,MAAM,KAC9B,eAAe,SAAS,SAAS,KACjC,eAAe,SAAS,QAAQ;GAElC,MAAM,iBAAiBA,uDACrB,YACA,cACA,eACA;IAAE;IAAgB;GAAU,CAC9B;GAOA,MAAM,yCAAyB,IAAI,IAAY;GAE/C,KAAK,MAAM,aAAa,YAAY,KAAK,MAAM;IAC7C,IAAI,CAAC,WAAW,oBAAoB,SAAS,GAAG;IAEhD,MAAM,eAAe,UAAU,OAAO;IAEtC,KAAK,MAAM,mBAAmB,UAAU,YAAY;KAClD,IAAI,CAAC,WAAW,kBAAkB,eAAe,GAAG;KAEpD,MAAM,eAAe,WAAW,aAC9B,gBAAgB,QAClB,IACI,gBAAgB,SAAS,OACzB,gBAAgB,SAAS;KAC7B,MAAM,YAAY,gBAAgB,MAAM;KAExC,IAAIC,6CAAuB,IAAI,YAAY,GAAG;MAC5C,uBAAuB,IAAI,SAAS;MACpC;KACF;KAEA,gBAAgB,WACd,cACA,cACA,SACF;IACF;GACF;GAEA,MAAM,mBACJ,gBAAgB,oBAAoB,KAAK;GAE3C,IAAI,uBAAuB,SAAS,KAAK,CAAC,kBAAkB;GAG5D,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,uBAAuB,IAAI,eAAe,GAAG;MAC/C,MAAM,gBAAgB,mBAAmB,KAAK;MAC9C,IAAI,cAAc,WAAW,GAAG;MAEhC,MAAM,gBAAgBC,0CACpB,YACA,cAAc,EAChB;MACA,IAAI,CAAC,eAAe;MAEpB,uDACE,YACA,cACA,oBACA,eACA,gBACA,SACF;MACA;KACF;KAEA,gBAAgB,YACd,oBACA,iBACA,YACF;IACF;IACA,oBAAoB,0BAA0B;KAC5C,gBAAgB,kBAAkB,qBAAqB;IACzD;GACF,CAAC;EACH,EACF,EACF;CACF;AACF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_staticAstReaders = require('../staticAstReaders.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/compat/namespaceResolution.ts
|
|
5
|
+
/**
|
|
6
|
+
* Resolution of a compat caller's namespace from its
|
|
7
|
+
* {@link CallerValueSource} list.
|
|
8
|
+
*
|
|
9
|
+
* Two flavours coexist on purpose, because the two passes ask different
|
|
10
|
+
* questions of the same descriptor:
|
|
11
|
+
*
|
|
12
|
+
* - {@link resolveNamespaceForAnalysis} answers *which dictionary does this
|
|
13
|
+
* call read* — used by the usage analyser, which only needs to attribute a
|
|
14
|
+
* call to a dictionary and may fall back to the caller's default namespace.
|
|
15
|
+
* - {@link resolveNamespaceForRewrite} answers *can this call be rewritten,
|
|
16
|
+
* and where do I edit it* — used by the optimize pass, which needs an exact
|
|
17
|
+
* static namespace plus a handle on the node it must replace. It has no
|
|
18
|
+
* default-namespace fallback: rewriting a call whose namespace is only
|
|
19
|
+
* implied would bind the wrong dictionary.
|
|
20
|
+
*/
|
|
21
|
+
/** Namespace used by compat callers when no namespace argument is given. */
|
|
22
|
+
const DEFAULT_COMPAT_NAMESPACE = "translation";
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the namespace (dictionary key) for a compat caller call-site from
|
|
25
|
+
* one `CallerValueSource`. Returns the static key, {@link ABSENT_VALUE} when
|
|
26
|
+
* the configured argument is absent (caller falls back to its default
|
|
27
|
+
* namespace), or `undefined` when the value is present but dynamic.
|
|
28
|
+
*/
|
|
29
|
+
const resolveNamespaceFromSource = (babelTypes, callArguments, source) => {
|
|
30
|
+
if (source.from === "fixed") return source.value;
|
|
31
|
+
if (source.from === "path-first-segment") {
|
|
32
|
+
const firstArgument = callArguments[0];
|
|
33
|
+
if (firstArgument === void 0) return void 0;
|
|
34
|
+
const firstSegment = require_staticAstReaders.readStaticFirstSegment(babelTypes, firstArgument);
|
|
35
|
+
if (firstSegment !== void 0) return firstSegment;
|
|
36
|
+
if (babelTypes.isObjectExpression(firstArgument)) {
|
|
37
|
+
const idNode = require_staticAstReaders.readObjectPropertyNode(babelTypes, firstArgument, "id");
|
|
38
|
+
if (idNode === "__default__") return require_staticAstReaders.ABSENT_VALUE;
|
|
39
|
+
if (idNode === void 0) return void 0;
|
|
40
|
+
return require_staticAstReaders.readStaticFirstSegment(babelTypes, idNode);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (source.from === "argument") {
|
|
45
|
+
const argument = callArguments[source.index];
|
|
46
|
+
if (argument === void 0) return require_staticAstReaders.ABSENT_VALUE;
|
|
47
|
+
const staticString = require_staticAstReaders.readStaticString(babelTypes, argument);
|
|
48
|
+
if (staticString !== void 0) return staticString;
|
|
49
|
+
if (babelTypes.isObjectExpression(argument)) return require_staticAstReaders.readObjectProperty(babelTypes, argument, "namespace");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const optionsArgument = callArguments[source.argumentIndex];
|
|
53
|
+
if (optionsArgument === void 0) return require_staticAstReaders.ABSENT_VALUE;
|
|
54
|
+
if (!babelTypes.isObjectExpression(optionsArgument)) return void 0;
|
|
55
|
+
return require_staticAstReaders.readObjectProperty(babelTypes, optionsArgument, source.property);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Resolves the namespace from a list of `CallerValueSource`s, tried in
|
|
59
|
+
* declaration order. The first source yielding a static string wins.
|
|
60
|
+
* Returns {@link ABSENT_VALUE} when every source reports an absent value, and
|
|
61
|
+
* `undefined` when the namespace is present somewhere but dynamic.
|
|
62
|
+
*/
|
|
63
|
+
const resolveNamespaceForAnalysis = (babelTypes, callArguments, sources) => {
|
|
64
|
+
let sawAbsentValue = false;
|
|
65
|
+
for (const source of sources) {
|
|
66
|
+
const resolved = resolveNamespaceFromSource(babelTypes, callArguments, source);
|
|
67
|
+
if (typeof resolved === "string" && resolved !== "__default__") return resolved;
|
|
68
|
+
if (resolved === "__default__") sawAbsentValue = true;
|
|
69
|
+
}
|
|
70
|
+
return sawAbsentValue ? require_staticAstReaders.ABSENT_VALUE : void 0;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Resolves an optional `keyPrefix` for a compat caller. Returns the static
|
|
74
|
+
* prefix string, `null` when no prefix is configured/present, or `undefined`
|
|
75
|
+
* when a prefix is present but dynamic.
|
|
76
|
+
*/
|
|
77
|
+
const resolveKeyPrefixForAnalysis = (babelTypes, callArguments, sources) => {
|
|
78
|
+
if (!sources || sources.length === 0) return null;
|
|
79
|
+
const resolved = resolveNamespaceForAnalysis(babelTypes, callArguments, sources);
|
|
80
|
+
if (resolved === "__default__") return null;
|
|
81
|
+
return resolved;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Statically resolves the namespace of a compat caller call-site for the
|
|
85
|
+
* optimize pass. Only the `argument` / `option` / `fixed` sources qualify: the
|
|
86
|
+
* per-message-id `path-first-segment` form cannot bind one dictionary to the
|
|
87
|
+
* call site and is skipped.
|
|
88
|
+
*
|
|
89
|
+
* Returns `undefined` when the namespace is absent or dynamic, in which case
|
|
90
|
+
* the call is left untouched and resolves through the runtime dictionary
|
|
91
|
+
* registry.
|
|
92
|
+
*/
|
|
93
|
+
const resolveNamespaceForRewrite = (babelTypes, callArguments, descriptor) => {
|
|
94
|
+
for (const source of descriptor.namespaceSources) {
|
|
95
|
+
if (source.from === "fixed") return { fullNamespace: source.value };
|
|
96
|
+
if (source.from === "argument") {
|
|
97
|
+
const namespace = require_staticAstReaders.readStaticString(babelTypes, callArguments[source.index]);
|
|
98
|
+
if (namespace !== void 0) return {
|
|
99
|
+
fullNamespace: namespace,
|
|
100
|
+
argumentIndex: source.index
|
|
101
|
+
};
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (source.from === "option") {
|
|
105
|
+
const optionsArgument = callArguments[source.argumentIndex];
|
|
106
|
+
if (!optionsArgument || !babelTypes.isObjectExpression(optionsArgument)) continue;
|
|
107
|
+
const property = require_staticAstReaders.findObjectProperty(babelTypes, optionsArgument, source.property);
|
|
108
|
+
if (!property) continue;
|
|
109
|
+
const namespace = require_staticAstReaders.readStaticString(babelTypes, property.value);
|
|
110
|
+
if (namespace !== void 0) return {
|
|
111
|
+
fullNamespace: namespace,
|
|
112
|
+
optionProperty: property,
|
|
113
|
+
optionsObject: optionsArgument
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
exports.DEFAULT_COMPAT_NAMESPACE = DEFAULT_COMPAT_NAMESPACE;
|
|
121
|
+
exports.resolveKeyPrefixForAnalysis = resolveKeyPrefixForAnalysis;
|
|
122
|
+
exports.resolveNamespaceForAnalysis = resolveNamespaceForAnalysis;
|
|
123
|
+
exports.resolveNamespaceForRewrite = resolveNamespaceForRewrite;
|
|
124
|
+
//# sourceMappingURL=namespaceResolution.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namespaceResolution.cjs","names":["readStaticFirstSegment","readObjectPropertyNode","ABSENT_VALUE","readStaticString","readObjectProperty","findObjectProperty"],"sources":["../../../src/compat/namespaceResolution.ts"],"sourcesContent":["import type * as BabelTypes from '@babel/types';\nimport type {\n CallerDescriptor,\n CallerValueSource,\n} from '@intlayer/config/callers';\nimport {\n ABSENT_VALUE,\n findObjectProperty,\n readObjectProperty,\n readObjectPropertyNode,\n readStaticFirstSegment,\n readStaticString,\n type StaticOrAbsent,\n} from '../staticAstReaders';\n\n/**\n * Resolution of a compat caller's namespace from its\n * {@link CallerValueSource} list.\n *\n * Two flavours coexist on purpose, because the two passes ask different\n * questions of the same descriptor:\n *\n * - {@link resolveNamespaceForAnalysis} answers *which dictionary does this\n * call read* — used by the usage analyser, which only needs to attribute a\n * call to a dictionary and may fall back to the caller's default namespace.\n * - {@link resolveNamespaceForRewrite} answers *can this call be rewritten,\n * and where do I edit it* — used by the optimize pass, which needs an exact\n * static namespace plus a handle on the node it must replace. It has no\n * default-namespace fallback: rewriting a call whose namespace is only\n * implied would bind the wrong dictionary.\n */\n\n/** Namespace used by compat callers when no namespace argument is given. */\nexport const DEFAULT_COMPAT_NAMESPACE = 'translation';\n\n/**\n * Resolves the namespace (dictionary key) for a compat caller call-site from\n * one `CallerValueSource`. Returns the static key, {@link ABSENT_VALUE} when\n * the configured argument is absent (caller falls back to its default\n * namespace), or `undefined` when the value is present but dynamic.\n */\nconst resolveNamespaceFromSource = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n source: CallerValueSource\n): StaticOrAbsent<string> => {\n if (source.from === 'fixed') return source.value;\n\n if (source.from === 'path-first-segment') {\n const firstArgument = 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 (firstArgument === undefined) return undefined;\n\n // String form: formatMessage('home.title', values). A template literal\n // whose leading quasi already carries the dot — formatMessage(`home.${x}`)\n // — names its dictionary just as unambiguously as a plain string, even\n // though the full id stays dynamic.\n const firstSegment = readStaticFirstSegment(babelTypes, firstArgument);\n if (firstSegment !== undefined) return firstSegment;\n\n // Descriptor form: formatMessage({ id: 'home.title', ... }, values)\n if (babelTypes.isObjectExpression(firstArgument)) {\n const idNode = readObjectPropertyNode(babelTypes, firstArgument, 'id');\n if (idNode === ABSENT_VALUE) return ABSENT_VALUE;\n if (idNode === undefined) return undefined;\n // Same reasoning as above for `{ id: `home.${x}` }`.\n return readStaticFirstSegment(babelTypes, idNode);\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 ABSENT_VALUE;\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 ABSENT_VALUE;\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 {@link ABSENT_VALUE} when every source reports an absent value, and\n * `undefined` when the namespace is present somewhere but dynamic.\n */\nexport const resolveNamespaceForAnalysis = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[]\n): StaticOrAbsent<string> => {\n let sawAbsentValue = false;\n\n for (const source of sources) {\n const resolved = resolveNamespaceFromSource(\n babelTypes,\n callArguments,\n source\n );\n if (typeof resolved === 'string' && resolved !== ABSENT_VALUE) {\n return resolved;\n }\n if (resolved === ABSENT_VALUE) sawAbsentValue = true;\n }\n\n return sawAbsentValue ? ABSENT_VALUE : 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 */\nexport const resolveKeyPrefixForAnalysis = (\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\n const resolved = resolveNamespaceForAnalysis(\n babelTypes,\n callArguments,\n sources\n );\n if (resolved === ABSENT_VALUE) return null; // prefix absent\n return resolved; // string or undefined (dynamic)\n};\n\n/**\n * Result of statically resolving the namespace of a compat caller call-site\n * for rewriting, carrying the handles the optimize pass mutates.\n */\nexport type RewritableNamespaceMatch = {\n /** The full namespace string, e.g. `'about.counter'`. */\n fullNamespace: string;\n /** Index of the positional namespace argument, when read from an argument. */\n argumentIndex?: number;\n /**\n * The options-object property holding the namespace, when read from an\n * option. The rewrite mutates it in place (key-prefix remainder) or removes\n * it from `optionsObject`.\n */\n optionProperty?: BabelTypes.ObjectProperty;\n /** The options object owning `optionProperty`. */\n optionsObject?: BabelTypes.ObjectExpression;\n};\n\n/**\n * Statically resolves the namespace of a compat caller call-site for the\n * optimize pass. Only the `argument` / `option` / `fixed` sources qualify: the\n * per-message-id `path-first-segment` form cannot bind one dictionary to the\n * call site and is skipped.\n *\n * Returns `undefined` when the namespace is absent or dynamic, in which case\n * the call is left untouched and resolves through the runtime dictionary\n * registry.\n */\nexport const resolveNamespaceForRewrite = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n descriptor: CallerDescriptor\n): RewritableNamespaceMatch | undefined => {\n for (const source of descriptor.namespaceSources) {\n if (source.from === 'fixed') {\n return { fullNamespace: source.value };\n }\n\n if (source.from === 'argument') {\n const namespace = readStaticString(\n babelTypes,\n callArguments[source.index]\n );\n if (namespace !== undefined) {\n return { fullNamespace: namespace, argumentIndex: source.index };\n }\n continue;\n }\n\n if (source.from === 'option') {\n const optionsArgument = callArguments[source.argumentIndex];\n if (!optionsArgument || !babelTypes.isObjectExpression(optionsArgument)) {\n continue;\n }\n\n const property = findObjectProperty(\n babelTypes,\n optionsArgument,\n source.property\n );\n if (!property) continue;\n\n const namespace = readStaticString(babelTypes, property.value);\n if (namespace !== undefined) {\n return {\n fullNamespace: namespace,\n optionProperty: property,\n optionsObject: optionsArgument,\n };\n }\n }\n }\n\n return undefined;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,2BAA2B;;;;;;;AAQxC,MAAM,8BACJ,YACA,eACA,WAC2B;CAC3B,IAAI,OAAO,SAAS,SAAS,OAAO,OAAO;CAE3C,IAAI,OAAO,SAAS,sBAAsB;EACxC,MAAM,gBAAgB,cAAc;EAGpC,IAAI,kBAAkB,QAAW,OAAO;EAMxC,MAAM,eAAeA,gDAAuB,YAAY,aAAa;EACrE,IAAI,iBAAiB,QAAW,OAAO;EAGvC,IAAI,WAAW,mBAAmB,aAAa,GAAG;GAChD,MAAM,SAASC,gDAAuB,YAAY,eAAe,IAAI;GACrE,IAAI,0BAAyB,OAAOC;GACpC,IAAI,WAAW,QAAW,OAAO;GAEjC,OAAOF,gDAAuB,YAAY,MAAM;EAClD;EAEA;CACF;CAEA,IAAI,OAAO,SAAS,YAAY;EAC9B,MAAM,WAAW,cAAc,OAAO;EACtC,IAAI,aAAa,QAAW,OAAOE;EAGnC,MAAM,eAAeC,0CAAiB,YAAY,QAAQ;EAC1D,IAAI,iBAAiB,QAAW,OAAO;EAGvC,IAAI,WAAW,mBAAmB,QAAQ,GACxC,OAAOC,4CAAmB,YAAY,UAAU,WAAW;EAE7D;CACF;CAGA,MAAM,kBAAkB,cAAc,OAAO;CAC7C,IAAI,oBAAoB,QAAW,OAAOF;CAC1C,IAAI,CAAC,WAAW,mBAAmB,eAAe,GAAG,OAAO;CAC5D,OAAOE,4CAAmB,YAAY,iBAAiB,OAAO,QAAQ;AACxE;;;;;;;AAQA,MAAa,+BACX,YACA,eACA,YAC2B;CAC3B,IAAI,iBAAiB;CAErB,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,2BACf,YACA,eACA,MACF;EACA,IAAI,OAAO,aAAa,YAAY,4BAClC,OAAO;EAET,IAAI,4BAA2B,iBAAiB;CAClD;CAEA,OAAO,iBAAiBF,wCAAe;AACzC;;;;;;AAOA,MAAa,+BACX,YACA,eACA,YAC8B;CAC9B,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO;CAE7C,MAAM,WAAW,4BACf,YACA,eACA,OACF;CACA,IAAI,4BAA2B,OAAO;CACtC,OAAO;AACT;;;;;;;;;;;AA+BA,MAAa,8BACX,YACA,eACA,eACyC;CACzC,KAAK,MAAM,UAAU,WAAW,kBAAkB;EAChD,IAAI,OAAO,SAAS,SAClB,OAAO,EAAE,eAAe,OAAO,MAAM;EAGvC,IAAI,OAAO,SAAS,YAAY;GAC9B,MAAM,YAAYC,0CAChB,YACA,cAAc,OAAO,MACvB;GACA,IAAI,cAAc,QAChB,OAAO;IAAE,eAAe;IAAW,eAAe,OAAO;GAAM;GAEjE;EACF;EAEA,IAAI,OAAO,SAAS,UAAU;GAC5B,MAAM,kBAAkB,cAAc,OAAO;GAC7C,IAAI,CAAC,mBAAmB,CAAC,WAAW,mBAAmB,eAAe,GACpE;GAGF,MAAM,WAAWE,4CACf,YACA,iBACA,OAAO,QACT;GACA,IAAI,CAAC,UAAU;GAEf,MAAM,YAAYF,0CAAiB,YAAY,SAAS,KAAK;GAC7D,IAAI,cAAc,QAChB,OAAO;IACL,eAAe;IACf,gBAAgB;IAChB,eAAe;GACjB;EAEJ;CACF;AAGF"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_staticAstReaders = require('../staticAstReaders.cjs');
|
|
3
|
+
const require_compat_namespaceResolution = require('./namespaceResolution.cjs');
|
|
4
|
+
let _intlayer_config_callers = require("@intlayer/config/callers");
|
|
5
|
+
|
|
6
|
+
//#region src/compat/optimizePass.ts
|
|
7
|
+
/**
|
|
8
|
+
* Rewritable slice of a descriptor registry, cached by array reference.
|
|
9
|
+
*
|
|
10
|
+
* Babel reuses one plugin-options object for every file of a build, so the
|
|
11
|
+
* filter runs once per build instead of once per transformed file.
|
|
12
|
+
*/
|
|
13
|
+
const rewritableCallersCache = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
const getCachedRewritableCallers = (compatCallers) => {
|
|
15
|
+
let rewritable = rewritableCallersCache.get(compatCallers);
|
|
16
|
+
if (!rewritable) {
|
|
17
|
+
rewritable = (0, _intlayer_config_callers.getRewritableCallers)([...compatCallers]);
|
|
18
|
+
rewritableCallersCache.set(compatCallers, rewritable);
|
|
19
|
+
}
|
|
20
|
+
return rewritable;
|
|
21
|
+
};
|
|
22
|
+
const callerIndexCache = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
const getCallerIndex = (rewritableCallers) => {
|
|
24
|
+
let index = callerIndexCache.get(rewritableCallers);
|
|
25
|
+
if (!index) {
|
|
26
|
+
index = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const descriptor of rewritableCallers) for (const importSource of descriptor.importSources) {
|
|
28
|
+
let byName = index.get(importSource);
|
|
29
|
+
if (!byName) {
|
|
30
|
+
byName = /* @__PURE__ */ new Map();
|
|
31
|
+
index.set(importSource, byName);
|
|
32
|
+
}
|
|
33
|
+
byName.set(descriptor.callerName, descriptor);
|
|
34
|
+
}
|
|
35
|
+
callerIndexCache.set(rewritableCallers, index);
|
|
36
|
+
}
|
|
37
|
+
return index;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Builds the compat optimize pass for one source file, or `null` when no
|
|
41
|
+
* compat caller was injected.
|
|
42
|
+
*
|
|
43
|
+
* @param babelTypes - Babel's type helpers.
|
|
44
|
+
* @param compatCallers - Descriptors injected by the compat packages' plugins.
|
|
45
|
+
* @param imports - Shared dictionary-import registry for the file.
|
|
46
|
+
* @param buildModes - The file's global import mode and per-dictionary overrides.
|
|
47
|
+
*/
|
|
48
|
+
const createCompatOptimizePass = (babelTypes, compatCallers, imports, buildModes) => {
|
|
49
|
+
const rewritableCallers = getCachedRewritableCallers(compatCallers);
|
|
50
|
+
if (rewritableCallers.length === 0) return null;
|
|
51
|
+
const callerIndex = getCallerIndex(rewritableCallers);
|
|
52
|
+
const { importMode, dictionaryModeMap } = buildModes;
|
|
53
|
+
/** Local alias → the descriptor it was imported as. */
|
|
54
|
+
const callersByLocalName = /* @__PURE__ */ new Map();
|
|
55
|
+
/**
|
|
56
|
+
* File-level decision: one import specifier serves every call in the file, so
|
|
57
|
+
* a global dynamic/fetch mode — or any per-dictionary override reached from
|
|
58
|
+
* this file — flips all rewritten compat calls to the dynamic helper.
|
|
59
|
+
*/
|
|
60
|
+
let useDynamicHelpers = false;
|
|
61
|
+
/** Narrows to the two modes that resolve through a per-locale loader. */
|
|
62
|
+
const isDynamicMode = (mode) => mode === "dynamic" || mode === "fetch";
|
|
63
|
+
/** Import mode a compat call site resolves to for `dictionaryKey`. */
|
|
64
|
+
const importModeFor = (dictionaryKey) => {
|
|
65
|
+
if (!useDynamicHelpers) return "static";
|
|
66
|
+
const override = dictionaryModeMap?.[dictionaryKey];
|
|
67
|
+
if (isDynamicMode(override)) return override;
|
|
68
|
+
return isDynamicMode(importMode) ? importMode : "dynamic";
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
ownsImportSource: (importSource) => callerIndex.has(importSource),
|
|
72
|
+
noteImport: (importSource, importedName, localName) => {
|
|
73
|
+
const descriptor = callerIndex.get(importSource)?.get(importedName);
|
|
74
|
+
if (descriptor) callersByLocalName.set(localName, descriptor);
|
|
75
|
+
},
|
|
76
|
+
ownsLocalName: (localName) => callersByLocalName.has(localName),
|
|
77
|
+
hasCallers: () => callersByLocalName.size > 0,
|
|
78
|
+
analyzeCalls: (callNodes) => {
|
|
79
|
+
if (callersByLocalName.size === 0) return;
|
|
80
|
+
/**
|
|
81
|
+
* Locals with at least one call site whose namespace could not be read:
|
|
82
|
+
* one import specifier serves every call in the file, so re-pointing it
|
|
83
|
+
* while leaving those calls untouched would hand a raw namespace string
|
|
84
|
+
* — or nothing at all — to the dictionary-accepting helper.
|
|
85
|
+
*/
|
|
86
|
+
const unresolvableLocalNames = /* @__PURE__ */ new Set();
|
|
87
|
+
let hasDynamicCall = false;
|
|
88
|
+
for (const callNode of callNodes) {
|
|
89
|
+
const callee = callNode.callee;
|
|
90
|
+
if (!babelTypes.isIdentifier(callee)) continue;
|
|
91
|
+
const descriptor = callersByLocalName.get(callee.name);
|
|
92
|
+
if (!descriptor) continue;
|
|
93
|
+
const namespaceMatch = require_compat_namespaceResolution.resolveNamespaceForRewrite(babelTypes, callNode.arguments, descriptor);
|
|
94
|
+
if (!namespaceMatch) {
|
|
95
|
+
unresolvableLocalNames.add(callee.name);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const { dictionaryKey } = require_staticAstReaders.splitNamespace(namespaceMatch.fullNamespace);
|
|
99
|
+
if (isDynamicMode(dictionaryModeMap?.[dictionaryKey])) hasDynamicCall = true;
|
|
100
|
+
}
|
|
101
|
+
for (const localName of unresolvableLocalNames) callersByLocalName.delete(localName);
|
|
102
|
+
useDynamicHelpers = isDynamicMode(importMode) || hasDynamicCall;
|
|
103
|
+
},
|
|
104
|
+
rewriteCall: (callNode) => {
|
|
105
|
+
const callee = callNode.callee;
|
|
106
|
+
if (!babelTypes.isIdentifier(callee)) return;
|
|
107
|
+
const descriptor = callersByLocalName.get(callee.name);
|
|
108
|
+
if (!descriptor) return;
|
|
109
|
+
const callArguments = callNode.arguments;
|
|
110
|
+
const namespaceMatch = require_compat_namespaceResolution.resolveNamespaceForRewrite(babelTypes, callArguments, descriptor);
|
|
111
|
+
if (!namespaceMatch) return;
|
|
112
|
+
const { dictionaryKey, keyPrefix } = require_staticAstReaders.splitNamespace(namespaceMatch.fullNamespace);
|
|
113
|
+
const mode = importModeFor(dictionaryKey);
|
|
114
|
+
const ident = imports.identFor(dictionaryKey, mode);
|
|
115
|
+
const isDynamicHelper = mode !== "static";
|
|
116
|
+
if (namespaceMatch.argumentIndex !== void 0) {
|
|
117
|
+
callArguments[namespaceMatch.argumentIndex] = babelTypes.identifier(ident.name);
|
|
118
|
+
const insertedArguments = [];
|
|
119
|
+
if (isDynamicHelper) insertedArguments.push(babelTypes.stringLiteral(dictionaryKey));
|
|
120
|
+
if (keyPrefix) insertedArguments.push(babelTypes.stringLiteral(keyPrefix));
|
|
121
|
+
callArguments.splice(namespaceMatch.argumentIndex + 1, 0, ...insertedArguments);
|
|
122
|
+
} else if (isDynamicHelper) callArguments.unshift(babelTypes.identifier(ident.name), babelTypes.stringLiteral(dictionaryKey));
|
|
123
|
+
else callArguments.unshift(babelTypes.identifier(ident.name));
|
|
124
|
+
if (namespaceMatch.optionProperty && namespaceMatch.optionsObject) {
|
|
125
|
+
if (keyPrefix) namespaceMatch.optionProperty.value = babelTypes.stringLiteral(keyPrefix);
|
|
126
|
+
else namespaceMatch.optionsObject.properties = namespaceMatch.optionsObject.properties.filter((property) => property !== namespaceMatch.optionProperty);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
rewriteImportSpecifiers: (importNode) => {
|
|
130
|
+
const byName = callerIndex.get(importNode.source.value);
|
|
131
|
+
if (!byName) return;
|
|
132
|
+
for (const specifier of importNode.specifiers) {
|
|
133
|
+
if (!babelTypes.isImportSpecifier(specifier)) continue;
|
|
134
|
+
const descriptor = callersByLocalName.get(specifier.local.name);
|
|
135
|
+
if (!descriptor || !byName.has(descriptor.callerName)) continue;
|
|
136
|
+
specifier.imported = babelTypes.identifier(useDynamicHelpers ? descriptor.dynamicReplacement : descriptor.staticReplacement);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
exports.createCompatOptimizePass = createCompatOptimizePass;
|
|
144
|
+
//# sourceMappingURL=optimizePass.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizePass.cjs","names":["getRewritableCallers","resolveNamespaceForRewrite","splitNamespace"],"sources":["../../../src/compat/optimizePass.ts"],"sourcesContent":["import type * as BabelTypes from '@babel/types';\nimport {\n type CallerDescriptor,\n getRewritableCallers,\n} from '@intlayer/config/callers';\nimport type {\n DictionaryImportRegistry,\n ImportMode,\n} from '../dictionaryImports';\nimport { splitNamespace } from '../staticAstReaders';\nimport { resolveNamespaceForRewrite } from './namespaceResolution';\n\n/**\n * Compat half of the optimize pass.\n *\n * Every adapter-specific behaviour of the build-time rewrite lives here:\n * matching a compat caller's import, resolving its namespace, and re-pointing\n * the import specifier at the dictionary-accepting helper.\n *\n * The pass works on plain AST nodes rather than `NodePath`s: the optimize\n * plugin collects the call sites in its single traversal and hands them over,\n * so nothing here walks the tree.\n *\n * It is created only when compat descriptors were injected by a compat\n * package's bundler plugin, so a plain intlayer build never allocates it —\n * {@link createCompatOptimizePass} returns `null` for an empty registry.\n */\nexport type CompatOptimizePass = {\n /**\n * Whether `importSource` exports at least one rewritable compat caller, so\n * the optimize plugin knows the import is worth inspecting even though it is\n * not a native intlayer package.\n */\n ownsImportSource: (importSource: string) => boolean;\n /** Records the local alias one import specifier introduces. */\n noteImport: (\n importSource: string,\n importedName: string,\n localName: string\n ) => void;\n /** Whether `localName` was bound to a compat caller by an import. */\n ownsLocalName: (localName: string) => boolean;\n /** Whether the file imported any compat caller at all. */\n hasCallers: () => boolean;\n /**\n * Resolves the namespace of every collected call, dropping the callers that\n * cannot be rewritten and deciding the file-level helper family. Must run\n * after every import was noted and before any rewrite.\n */\n analyzeCalls: (callNodes: readonly BabelTypes.CallExpression[]) => void;\n /** Rewrites one compat call site, if its caller survived {@link analyzeCalls}. */\n rewriteCall: (callNode: BabelTypes.CallExpression) => void;\n /** Re-points the compat specifiers of one import declaration. */\n rewriteImportSpecifiers: (importNode: BabelTypes.ImportDeclaration) => void;\n};\n\n/**\n * Rewritable slice of a descriptor registry, cached by array reference.\n *\n * Babel reuses one plugin-options object for every file of a build, so the\n * filter runs once per build instead of once per transformed file.\n */\nconst rewritableCallersCache = new WeakMap<\n readonly CallerDescriptor[],\n readonly CallerDescriptor[]\n>();\n\nconst getCachedRewritableCallers = (\n compatCallers: readonly CallerDescriptor[]\n): readonly CallerDescriptor[] => {\n let rewritable = rewritableCallersCache.get(compatCallers);\n\n if (!rewritable) {\n rewritable = getRewritableCallers([...compatCallers]);\n rewritableCallersCache.set(compatCallers, rewritable);\n }\n\n return rewritable;\n};\n\n/**\n * `importSource` → the callers it exports, indexed by their imported name.\n *\n * Built once per descriptor registry so matching an import specifier is two\n * map lookups instead of a scan over every descriptor's `importSources`.\n */\ntype CallerIndex = Map<string, Map<string, CallerDescriptor>>;\n\nconst callerIndexCache = new WeakMap<\n readonly CallerDescriptor[],\n CallerIndex\n>();\n\nconst getCallerIndex = (\n rewritableCallers: readonly CallerDescriptor[]\n): CallerIndex => {\n let index = callerIndexCache.get(rewritableCallers);\n\n if (!index) {\n index = new Map();\n\n for (const descriptor of rewritableCallers) {\n for (const importSource of descriptor.importSources) {\n let byName = index.get(importSource);\n\n if (!byName) {\n byName = new Map();\n index.set(importSource, byName);\n }\n\n byName.set(descriptor.callerName, descriptor);\n }\n }\n\n callerIndexCache.set(rewritableCallers, index);\n }\n\n return index;\n};\n\n/**\n * Builds the compat optimize pass for one source file, or `null` when no\n * compat caller was injected.\n *\n * @param babelTypes - Babel's type helpers.\n * @param compatCallers - Descriptors injected by the compat packages' plugins.\n * @param imports - Shared dictionary-import registry for the file.\n * @param buildModes - The file's global import mode and per-dictionary overrides.\n */\nexport const createCompatOptimizePass = (\n babelTypes: typeof BabelTypes,\n compatCallers: readonly CallerDescriptor[],\n imports: DictionaryImportRegistry,\n buildModes: {\n importMode: ImportMode | undefined;\n dictionaryModeMap?: Record<string, ImportMode | undefined>;\n }\n): CompatOptimizePass | null => {\n const rewritableCallers = getCachedRewritableCallers(compatCallers);\n if (rewritableCallers.length === 0) return null;\n\n const callerIndex = getCallerIndex(rewritableCallers);\n const { importMode, dictionaryModeMap } = buildModes;\n\n /** Local alias → the descriptor it was imported as. */\n const callersByLocalName = new Map<string, CallerDescriptor>();\n\n /**\n * File-level decision: one import specifier serves every call in the file, so\n * a global dynamic/fetch mode — or any per-dictionary override reached from\n * this file — flips all rewritten compat calls to the dynamic helper.\n */\n let useDynamicHelpers = false;\n\n /** Narrows to the two modes that resolve through a per-locale loader. */\n const isDynamicMode = (\n mode: ImportMode | undefined\n ): mode is 'dynamic' | 'fetch' => mode === 'dynamic' || mode === 'fetch';\n\n /** Import mode a compat call site resolves to for `dictionaryKey`. */\n const importModeFor = (dictionaryKey: string): ImportMode => {\n if (!useDynamicHelpers) return 'static';\n\n const override = dictionaryModeMap?.[dictionaryKey];\n if (isDynamicMode(override)) return override;\n\n return isDynamicMode(importMode) ? importMode : 'dynamic';\n };\n\n return {\n ownsImportSource: (importSource) => callerIndex.has(importSource),\n\n noteImport: (importSource, importedName, localName) => {\n const descriptor = callerIndex.get(importSource)?.get(importedName);\n if (descriptor) callersByLocalName.set(localName, descriptor);\n },\n\n ownsLocalName: (localName) => callersByLocalName.has(localName),\n\n hasCallers: () => callersByLocalName.size > 0,\n\n analyzeCalls: (callNodes) => {\n if (callersByLocalName.size === 0) return;\n\n /**\n * Locals with at least one call site whose namespace could not be read:\n * one import specifier serves every call in the file, so re-pointing it\n * while leaving those calls untouched would hand a raw namespace string\n * — or nothing at all — to the dictionary-accepting helper.\n */\n const unresolvableLocalNames = new Set<string>();\n let hasDynamicCall = false;\n\n for (const callNode of callNodes) {\n const callee = callNode.callee;\n if (!babelTypes.isIdentifier(callee)) continue;\n\n const descriptor = callersByLocalName.get(callee.name);\n if (!descriptor) continue;\n\n const namespaceMatch = resolveNamespaceForRewrite(\n babelTypes,\n callNode.arguments,\n descriptor\n );\n\n // No readable namespace — a computed key, or a call that passes none\n // at all (`useTranslations()`). Either way the dictionary is unknown at\n // build time, so the call keeps resolving through the runtime registry\n // and holds back the siblings sharing its import.\n if (!namespaceMatch) {\n unresolvableLocalNames.add(callee.name);\n continue;\n }\n\n const { dictionaryKey } = splitNamespace(namespaceMatch.fullNamespace);\n if (isDynamicMode(dictionaryModeMap?.[dictionaryKey])) {\n hasDynamicCall = true;\n }\n }\n\n for (const localName of unresolvableLocalNames) {\n callersByLocalName.delete(localName);\n }\n\n useDynamicHelpers = isDynamicMode(importMode) || hasDynamicCall;\n },\n\n rewriteCall: (callNode) => {\n const callee = callNode.callee;\n if (!babelTypes.isIdentifier(callee)) return;\n\n const descriptor = callersByLocalName.get(callee.name);\n if (!descriptor) return;\n\n const callArguments = callNode.arguments;\n const namespaceMatch = resolveNamespaceForRewrite(\n babelTypes,\n callArguments,\n descriptor\n );\n // Filtered out by `analyzeCalls` — the import keeps its original specifier.\n if (!namespaceMatch) return;\n\n const { dictionaryKey, keyPrefix } = splitNamespace(\n namespaceMatch.fullNamespace\n );\n const mode = importModeFor(dictionaryKey);\n const ident = imports.identFor(dictionaryKey, mode);\n const isDynamicHelper = mode !== 'static';\n\n if (namespaceMatch.argumentIndex !== undefined) {\n // Positional namespace: replace the string with the dictionary, then\n // the (dynamic) key and the (nested) prefix.\n // useTranslation('about.counter', opts)\n // static → useDictionary(_hash, 'counter', opts)\n // dynamic → useDictionaryDynamic(_hash_dyn, 'about', 'counter', opts)\n callArguments[namespaceMatch.argumentIndex] = babelTypes.identifier(\n ident.name\n );\n\n const insertedArguments: BabelTypes.Expression[] = [];\n if (isDynamicHelper) {\n insertedArguments.push(babelTypes.stringLiteral(dictionaryKey));\n }\n if (keyPrefix) {\n insertedArguments.push(babelTypes.stringLiteral(keyPrefix));\n }\n callArguments.splice(\n namespaceMatch.argumentIndex + 1,\n 0,\n ...insertedArguments\n );\n } else if (isDynamicHelper) {\n // Fixed / option namespace: prepend the loader and the dictionary key.\n callArguments.unshift(\n babelTypes.identifier(ident.name),\n babelTypes.stringLiteral(dictionaryKey)\n );\n } else {\n // Fixed / option namespace, static helper: prepend the dictionary.\n // useLingui() → useDictionary(_hash)\n callArguments.unshift(babelTypes.identifier(ident.name));\n }\n\n // Option namespace: leave only the key-prefix remainder in the options\n // object (or drop the property entirely), so the runtime helper does not\n // re-apply the dictionary key as a lookup prefix.\n // useI18n({ namespace: 'about' }) → useDictionary(_hash, {})\n if (namespaceMatch.optionProperty && namespaceMatch.optionsObject) {\n if (keyPrefix) {\n namespaceMatch.optionProperty.value =\n babelTypes.stringLiteral(keyPrefix);\n } else {\n namespaceMatch.optionsObject.properties =\n namespaceMatch.optionsObject.properties.filter(\n (property) => property !== namespaceMatch.optionProperty\n );\n }\n }\n },\n\n rewriteImportSpecifiers: (importNode) => {\n const byName = callerIndex.get(importNode.source.value);\n if (!byName) return;\n\n for (const specifier of importNode.specifiers) {\n if (!babelTypes.isImportSpecifier(specifier)) continue;\n\n // Only a specifier still held by `callersByLocalName` is rewritable —\n // `analyzeCalls` removed the ones with an unresolvable call site.\n const descriptor = callersByLocalName.get(specifier.local.name);\n if (!descriptor || !byName.has(descriptor.callerName)) continue;\n\n // Keep the local alias so call sites read unchanged; only the imported\n // name moves to the dictionary-accepting helper.\n specifier.imported = babelTypes.identifier(\n useDynamicHelpers\n ? descriptor.dynamicReplacement!\n : descriptor.staticReplacement!\n );\n }\n },\n };\n};\n"],"mappings":"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"}
|