@intlayer/babel 9.3.0 → 9.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -2
- package/dist/cjs/babel-plugin-intlayer-extract.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +9 -5
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +0 -1
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
- package/dist/cjs/babelParser.cjs.map +1 -1
- package/dist/cjs/extractContent/babelProcessor.cjs +50 -49
- package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
- package/dist/cjs/extractContent/contentWriter.cjs +3 -2
- package/dist/cjs/extractContent/contentWriter.cjs.map +1 -1
- package/dist/cjs/extractContent/extractContent.cjs +4 -2
- package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
- package/dist/cjs/extractContent/processTsxFile.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/constants.cjs +0 -1
- package/dist/cjs/extractContent/utils/constants.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/detectPackageName.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs +5 -3
- package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/extractDictionaryKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/generateKey.cjs +0 -1
- package/dist/cjs/extractContent/utils/generateKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/getExistingIntlayerInfo.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/resolveDictionaryKey.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/shouldExtract.cjs +0 -1
- package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +1 -1
- package/dist/cjs/extractScriptBlocks.cjs.map +1 -1
- package/dist/cjs/getExtractPluginOptions.cjs +4 -2
- package/dist/cjs/getExtractPluginOptions.cjs.map +1 -1
- package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
- package/dist/cjs/getPurgePluginOptions.cjs.map +1 -1
- package/dist/cjs/transformers.cjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-optimize.mjs +9 -5
- package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
- package/dist/esm/extractContent/babelProcessor.mjs +50 -49
- package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
- package/dist/esm/extractContent/contentWriter.mjs +3 -2
- package/dist/esm/extractContent/contentWriter.mjs.map +1 -1
- package/dist/esm/extractContent/extractContent.mjs +4 -2
- package/dist/esm/extractContent/extractContent.mjs.map +1 -1
- package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs +5 -3
- package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs.map +1 -1
- package/dist/esm/extractContent/utils/getExistingIntlayerInfo.mjs.map +1 -1
- package/dist/esm/getExtractPluginOptions.mjs +4 -2
- package/dist/esm/getExtractPluginOptions.mjs.map +1 -1
- package/package.json +9 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-purge.cjs","names":["makeUsageAnalyzerBabelPlugin","BABEL_PARSER_OPTIONS","getUsageCheckRegex","extractScriptBlocks","buildNestedRenameMapFromContent","getNestedRenameEntryAtPath","ANSIColors","createPruneContext","SOURCE_FILE_REGEX"],"sources":["../../src/babel-plugin-intlayer-purge.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { PluginObject, PluginPass } from '@babel/core';\nimport { transformSync } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport type { NestedFieldReferences } from '@intlayer/core/dictionaryManipulator';\nimport { formatPath } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport {\n buildNestedRenameMapFromContent,\n getNestedRenameEntryAtPath,\n} from './babel-plugin-intlayer-field-rename';\nimport {\n type CompatCallerConfig,\n createPruneContext,\n makeUsageAnalyzerBabelPlugin,\n type NestedRenameMap,\n type PruneContext,\n preserveNestedDictionaryFields,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks } from './extractScriptBlocks';\nimport {\n BABEL_PARSER_OPTIONS,\n getUsageCheckRegex,\n SOURCE_FILE_REGEX,\n} from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerPurgeBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getPurgePluginOptions}) so the plugin itself does not need to read\n * the configuration file on every file transform.\n */\nexport type PurgePluginOptions = {\n /**\n * Absolute path to the project root. Used as the cache key for the shared\n * {@link PruneContext} so two Babel transform pipelines for different\n * workspaces in the same process do not share state.\n */\n baseDir: string;\n\n /**\n * When `true`, remove unused content fields from compiled dictionary JSON\n * files. Mirrors `build.purge` in `intlayer.config.ts`.\n */\n purge: boolean;\n\n /**\n * When `true`, rename content fields to short alphabetic aliases\n * (`title` → `a`, etc.) and strip top-level metadata from compiled\n * dictionaries. Mirrors `build.minify` in `intlayer.config.ts`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. `undefined` means \"auto\" (active for\n * production builds). When explicitly `false`, the plugin is a no-op.\n * Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin skips all processing to preserve full dictionary\n * content for the visual editor. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n\n /**\n * Absolute path to the compiled static dictionaries directory\n * (`.intlayer/dictionaries/` by default).\n */\n dictionariesDir: string;\n\n /**\n * Absolute path to the compiled per-locale dynamic dictionaries directory\n * (`.intlayer/dynamic_dictionaries/` by default).\n */\n dynamicDictionariesDir: string;\n\n /**\n * Pre-built list of component source file paths to analyse for field-usage.\n * Populated by {@link getPurgePluginOptions} from the intlayer config's\n * `content` glob patterns.\n */\n componentFilesList: string[];\n\n /**\n * Dictionaries referenced through `nest()`, mapped to the top-level fields\n * those references read (`'all'` when the whole dictionary is nested).\n *\n * Those fields are resolved at runtime by `getNesting` using their original\n * names, so they must survive purging and must never be renamed.\n */\n nestedDictionaryReferences?: Map<string, NestedFieldReferences>;\n\n /**\n * Per-dictionary import-mode overrides, keyed by dictionary `key`.\n * Dictionaries with mode `'fetch'` are excluded from field renaming because\n * their JSON is served from a remote API using the original field names.\n */\n dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n\n /**\n * Compat-adapter namespace caller configurations.\n *\n * When set, the usage analyser recognises these additional translation\n * function patterns and maps them to dictionary field usage. Each compat\n * adapter package (e.g. `@intlayer/react-i18next`) provides its own caller\n * list; they are NOT hardcoded here so that `@intlayer/babel` stays\n * framework-agnostic.\n *\n * Defaults to `[]` (no compat callers) when omitted.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Logging configuration used to report what the pipeline purged and\n * minified, so a Babel- or SWC-driven build prints the same summary as the\n * Vite one. Mirrors `log` in `intlayer.config.ts`; omit to fall back to the\n * logger defaults.\n */\n logConfig?: Pick<IntlayerConfig, 'log'>;\n};\n\n/** Application logger used to report the pipeline's progress. */\ntype PurgeLogger = ReturnType<typeof getAppLogger>;\n\n// ── Shared module-level state ─────────────────────────────────────────────────\n\n/**\n * Cache of built {@link PruneContext} objects, keyed by the project's\n * `baseDir`. Each context is built exactly once per Node.js process:\n * {@link runIntlayerPurgePipeline} registers the context before running so every\n * subsequent transform is a no-op cache hit.\n */\nconst _pruneContextCache = new Map<string, PruneContext>();\n\n/**\n * Returns the shared {@link PruneContext} for the given base directory, or\n * `null` if {@link intlayerPurgeBabelPlugin} has not yet been initialised for\n * that directory.\n *\n * Used by {@link intlayerMinifyBabelPlugin} to read the rename map without\n * creating a circular dependency.\n */\nexport const getSharedPruneContext = (baseDir: string): PruneContext | null =>\n _pruneContextCache.get(baseDir) ?? null;\n\n// ── Dictionary JSON types ─────────────────────────────────────────────────────\n\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string;\n [extraKey: string]: unknown;\n};\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// ── Prune helpers (mirrors intlayerPrunePlugin) ───────────────────────────────\n\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/** Returns a copy of `record` keeping only the keys listed in `usedFieldNames`. */\nconst filterRecordByUsedFields = (\n record: Record<string, unknown>,\n usedFieldNames: Set<string>\n): Record<string, unknown> => {\n const filteredRecord: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(record)) {\n if (usedFieldNames.has(fieldName)) {\n filteredRecord[fieldName] = fieldValue;\n }\n }\n return filteredRecord;\n};\n\n/**\n * Removes unused fields from a **static** dictionary (all locales in one\n * file). Supports shape A (translation node at the root) and shape B (flat\n * record of translation nodes per field).\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A: { nodeType: \"translation\", translation: { en: { f1, f2 } } }\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n if (isPlainRecord(firstLocaleValue)) {\n const prunedTranslation: Record<string, unknown> = {};\n for (const [locale, localeContent] of Object.entries(\n content.translation\n )) {\n prunedTranslation[locale] = isPlainRecord(localeContent)\n ? filterRecordByUsedFields(localeContent, usedFieldNames)\n : localeContent;\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslation },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B: { field1: { nodeType: \"translation\", … }, field2: { … } }\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Removes unused fields from a **dynamic / per-locale** dictionary file\n * (one JSON per locale, flat `content` record).\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n};\n\n// ── Minify helpers (mirrors intlayerMinifyPlugin) ─────────────────────────────\n\n/**\n * Recursively renames user-defined content fields using `renameMap`.\n * Translation nodes, arrays, and primitives follow the same traversal rules\n * as in the Vite-based minify plugin.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale value with the same map.\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values.\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val;\n }\n }\n return result;\n};\n\n/**\n * Applies a {@link NestedRenameMap} to a parsed dictionary object, renaming\n * only the keys inside `content` while leaving top-level metadata untouched.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// ── Synchronous source-file analysis ─────────────────────────────────────────\n\n/**\n * Runs the usage-analyser Babel plugin synchronously on a single code block,\n * accumulating results into `pruneContext`.\n */\nconst analyzeCodeBlockSync = (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n try {\n transformSync(code, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false,\n });\n } catch {\n pruneContext.hasUnparsableSourceFiles = true;\n }\n};\n\n/**\n * Reads a source file from disk and runs the usage-analyser synchronously.\n * SFC files (Vue / Svelte) are handled by extracting script blocks first.\n */\nconst analyzeSourceFileSync = (\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n let code: string;\n try {\n code = readFileSync(sourceFilePath, 'utf-8');\n } catch {\n return;\n }\n\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n\n if (!usageCheckRegex.test(code)) return;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n analyzeCodeBlockSync(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n// ── Build rename maps ─────────────────────────────────────────────────────────\n\n/**\n * Reads compiled dictionary JSON files to build the nested field-rename maps,\n * mirroring the Phase 4 logic in the Vite `intlayerOptimize` plugin's\n * `buildStart` hook. Results are stored in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n */\nconst buildRenameMapsSynchronously = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n dictionaryKeyToImportModeMap: PurgePluginOptions['dictionaryKeyToImportModeMap'],\n pruneContext: PruneContext,\n logger: PurgeLogger\n): void => {\n let partiallyMinifiedDictionariesCount = 0;\n\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch') continue;\n if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey))\n continue;\n\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(dictionariesDir, `${dictionaryKey}.json`);\n if (existsSync(staticJsonPath)) {\n try {\n const raw = readFileSync(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n // Fall through to dynamic dict.\n }\n }\n\n if (!dictionaryContent) {\n const dynamicDirPath = join(dynamicDictionariesDir, dictionaryKey);\n if (existsSync(dynamicDirPath)) {\n try {\n const localeFiles = readdirSync(dynamicDirPath);\n const firstJsonFile = localeFiles.find((file) =>\n file.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = readFileSync(\n join(dynamicDirPath, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary not readable – skip rename for this key.\n }\n }\n }\n\n if (!dictionaryContent) continue;\n\n const nestedRenameMap = buildNestedRenameMapFromContent(dictionaryContent);\n\n // Preserve children of opaque values to avoid breaking child components.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey);\n if (opaqueFieldMap) {\n const dangerousOccurrences = [...opaqueFieldMap.values()].filter(\n (occurrence) =>\n (getNestedRenameEntryAtPath(nestedRenameMap, occurrence.fieldPath)\n ?.children.size ?? 0) > 0\n );\n\n if (dangerousOccurrences.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousOccurrences.flatMap((occurrence) => [\n `\\n Opaque field:`,\n colorize(`'${occurrence.fieldPath.join('.')}'`, ANSIColors.BLUE),\n `(nested keys preserved for stability).`,\n ...occurrence.locations.map(\n (location) => `\\n at ${formatPath(location)}`\n ),\n ]),\n ],\n { level: 'warn', isVerbose: true }\n );\n\n for (const occurrence of dangerousOccurrences) {\n const renameEntry = getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n );\n if (renameEntry) {\n renameEntry.children = new Map();\n }\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n\n if (partiallyMinifiedDictionariesCount > 0) {\n logger([\n `Partially minified`,\n colorizeNumber(partiallyMinifiedDictionariesCount),\n `dictionar${partiallyMinifiedDictionariesCount === 1 ? 'y' : 'ies'}`,\n `(preserved nested keys for opaque fields).`,\n ]);\n }\n};\n\n// ── Dictionary file writing ───────────────────────────────────────────────────\n\n/**\n * Names of the top-level content fields of a compiled dictionary, whatever\n * shape its `content` uses. Used to report which fields the purge removed.\n */\nconst readContentFieldNames = (\n content: CompiledDictionaryJson['content']\n): string[] => {\n // Shape A – fields live inside each locale object.\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n return isPlainRecord(firstLocaleValue) ? Object.keys(firstLocaleValue) : [];\n }\n\n // Shape B / dynamic – flat content record.\n if (isPlainRecord(content)) return Object.keys(content);\n\n return [];\n};\n\n/**\n * Running totals of the purge, reported once the whole pipeline is done.\n */\ntype PurgeStats = {\n /** Dictionary key → number of fields removed from it. */\n prunedFieldsCountPerDictionary: Map<string, number>;\n /**\n * Dictionary keys whose \"pruned fields\" line has already been printed. A\n * key spans several files (one static, one per locale), and they all remove\n * the same fields.\n */\n loggedPrunedDictionaryKeys: Set<string>;\n /**\n * Dictionary keys whose \"analysis is incomplete\" warning has already been\n * printed, deduplicated the same way.\n */\n loggedIncompleteAnalysisKeys: Set<string>;\n};\n\n/**\n * Reads a compiled dictionary JSON file, applies the purge and/or minify\n * transformations, and writes it back in-place when something changed.\n *\n * `dictionaryKind` selects the prune strategy and the minified output shape:\n * static dictionaries keep `{ key, content }`, dynamic per-locale\n * dictionaries additionally keep `locale`.\n */\nconst processDictionaryFile = (\n filePath: string,\n dictionaryKind: 'static' | 'dynamic',\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger,\n stats: PurgeStats\n): void => {\n let parsedDict: CompiledDictionaryJson;\n try {\n parsedDict = JSON.parse(\n readFileSync(filePath, 'utf-8')\n ) as CompiledDictionaryJson;\n } catch {\n return; // Unreadable or invalid JSON – leave file unchanged.\n }\n\n const { key: dictionaryKey } = parsedDict;\n\n if (!dictionaryKey) {\n logger(\n [\n `Dictionary file`,\n formatPath(filePath),\n `is missing a \"key\" field. Skipping prune for this file.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n let modified = false;\n\n if (shouldPurge) {\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (fieldUsage && fieldUsage !== 'all') {\n const pruneDictionaryContent =\n dictionaryKind === 'static'\n ? pruneStaticDictionaryContent\n : pruneDynamicDictionaryContent;\n const originalFieldNames = readContentFieldNames(parsedDict.content);\n const { prunedDictionary, wasRecognised } = pruneDictionaryContent(\n parsedDict,\n fieldUsage\n );\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Unrecognised content structure in dictionary`,\n colorizeKey(dictionaryKey),\n `(file:`,\n `${formatPath(filePath)}).`,\n `Skipping prune for this dictionary.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n const removedFieldNames = originalFieldNames.filter(\n (fieldName) => !fieldUsage.has(fieldName)\n );\n\n if (removedFieldNames.length > 0) {\n stats.prunedFieldsCountPerDictionary.set(\n dictionaryKey,\n removedFieldNames.length\n );\n\n if (!stats.loggedPrunedDictionaryKeys.has(dictionaryKey)) {\n stats.loggedPrunedDictionaryKeys.add(dictionaryKey);\n logger(\n [\n `Pruned`,\n colorizeNumber(removedFieldNames.length),\n `unused field${removedFieldNames.length === 1 ? '' : 's'} from`,\n `${colorizeKey(dictionaryKey)}:`,\n removedFieldNames\n .map((fieldName) => colorize(fieldName, ANSIColors.GREY_LIGHT))\n .join(', '),\n ],\n { isVerbose: true }\n );\n }\n }\n\n parsedDict = prunedDictionary;\n modified = true;\n } else if (\n !fieldUsage &&\n pruneContext.hasUnparsableSourceFiles &&\n !stats.loggedIncompleteAnalysisKeys.has(dictionaryKey)\n ) {\n // An unparsable source file might be the one referencing this key, so its\n // usage set cannot be trusted to be empty.\n stats.loggedIncompleteAnalysisKeys.add(dictionaryKey);\n logger(\n [\n `Skipping prune for dictionary`,\n colorizeKey(dictionaryKey),\n `: analysis is incomplete due to earlier source-file parse failures.`,\n ],\n { level: 'warn' }\n );\n }\n }\n\n if (shouldMinify) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(\n parsedDict as Record<string, unknown>,\n fieldRenameMap\n ) as CompiledDictionaryJson;\n modified = true;\n }\n }\n\n if (!modified) return;\n\n const outputDict = shouldMinify\n ? dictionaryKind === 'static'\n ? { key: parsedDict.key, content: parsedDict.content }\n : {\n key: parsedDict.key,\n content: parsedDict.content,\n locale: parsedDict.locale,\n }\n : parsedDict;\n\n try {\n writeFileSync(filePath, JSON.stringify(outputDict), 'utf-8');\n } catch {\n // Write failure – leave file unchanged.\n }\n};\n\nconst processAllDictionaryFiles = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger\n): void => {\n const stats: PurgeStats = {\n prunedFieldsCountPerDictionary: new Map(),\n loggedPrunedDictionaryKeys: new Set(),\n loggedIncompleteAnalysisKeys: new Set(),\n };\n\n if (existsSync(dictionariesDir)) {\n for (const entry of readdirSync(dictionariesDir)) {\n if (!entry.endsWith('.json')) continue;\n processDictionaryFile(\n join(dictionariesDir, entry),\n 'static',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n }\n\n if (existsSync(dynamicDictionariesDir)) {\n for (const keyDir of readdirSync(dynamicDictionariesDir)) {\n const keyDirPath = join(dynamicDictionariesDir, keyDir);\n try {\n for (const localeFile of readdirSync(keyDirPath)) {\n if (!localeFile.endsWith('.json')) continue;\n processDictionaryFile(\n join(keyDirPath, localeFile),\n 'dynamic',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n } catch {\n // Unreadable key directory – skip.\n }\n }\n }\n\n const totalPrunedFieldsCount = [\n ...stats.prunedFieldsCountPerDictionary.values(),\n ].reduce((total, count) => total + count, 0);\n const totalPrunedDictionariesCount =\n stats.prunedFieldsCountPerDictionary.size;\n\n if (totalPrunedFieldsCount > 0) {\n logger([\n `Pruned`,\n colorizeNumber(totalPrunedFieldsCount),\n `unused field${totalPrunedFieldsCount === 1 ? '' : 's'} across`,\n colorizeNumber(totalPrunedDictionariesCount),\n `dictionar${totalPrunedDictionariesCount === 1 ? 'y' : 'ies'}.`,\n ]);\n }\n};\n\n// ── Reporting helpers ─────────────────────────────────────────────────────────\n\n/**\n * Explains why the requested steps did nothing: the visual editor needs the\n * full dictionary content, so both purge and minify stand down when it is on.\n */\nconst logStepDisabledByEditor = (\n logger: PurgeLogger,\n purge: boolean,\n minify: boolean\n): void => {\n const explainDisabled = (stepLabel: string) =>\n logger([\n stepLabel,\n 'is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n colorize(\n '— the editor requires full dictionary content',\n ANSIColors.GREY\n ),\n ]);\n\n if (purge) explainDisabled('Dictionary purge');\n if (minify) explainDisabled('Dictionary minification');\n};\n\n/**\n * Reports the dictionaries whose consumption could not be tracked statically,\n * with the source files responsible — those keep their full content.\n */\nconst logUntrackedBindings = (\n logger: PurgeLogger,\n pruneContext: PruneContext\n): void => {\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n};\n\n// ── Main initialisation ───────────────────────────────────────────────────────\n\n/**\n * Runs the full purge/minify pipeline for the given options, using a\n * module-level cache so the work happens at most once per process per\n * unique `baseDir`.\n *\n * Exported so bundler integrations that do not go through Babel can drive the\n * same pipeline. The Next.js integration is the main consumer: its SWC plugin\n * runs as a per-file Wasm transform with no filesystem access, so the analysis\n * and the dictionary rewrite happen here, in Node, and only the resulting\n * field-rename tables (see {@link serializeFieldRenameMap}) are forwarded to\n * the plugin.\n *\n * **This performs file I/O**: it reads every source file listed in\n * `componentFilesList` and overwrites the compiled dictionary JSON files\n * in-place. Call it at most once per build, after the dictionaries have been\n * built.\n */\nexport const runIntlayerPurgePipeline = (\n options: PurgePluginOptions\n): PruneContext => {\n const {\n baseDir,\n purge,\n minify,\n optimize,\n editorEnabled,\n dictionariesDir,\n dynamicDictionariesDir,\n componentFilesList,\n dictionaryKeyToImportModeMap,\n nestedDictionaryReferences,\n compatCallers,\n logConfig,\n } = options;\n\n const cachedContext = _pruneContextCache.get(baseDir);\n if (cachedContext) return cachedContext;\n\n const pruneContext = createPruneContext();\n _pruneContextCache.set(baseDir, pruneContext);\n\n const logger = getAppLogger(logConfig);\n\n const shouldPurge = purge && !editorEnabled;\n const shouldMinify = minify && !editorEnabled;\n\n if (editorEnabled && (purge || minify)) {\n logStepDisabledByEditor(logger, purge, minify);\n }\n\n if ((!shouldPurge && !shouldMinify) || optimize === false)\n return pruneContext;\n\n // Phase 1: Synchronously analyse all component source files.\n for (const sourceFilePath of componentFilesList) {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) continue;\n analyzeSourceFileSync(sourceFilePath, pruneContext, compatCallers);\n }\n\n // Phase 1.5: Keep `nest()` targets resolvable — must run before the rename\n // maps are built so nest targets are excluded from field renaming.\n if (nestedDictionaryReferences) {\n preserveNestedDictionaryFields(pruneContext, nestedDictionaryReferences);\n }\n\n // Phase 1.6: Warn about the dictionaries the analysis could not follow.\n logUntrackedBindings(logger, pruneContext);\n\n // Phase 2: Build field-rename maps (minify only).\n if (shouldMinify) {\n buildRenameMapsSynchronously(\n dictionariesDir,\n dynamicDictionariesDir,\n dictionaryKeyToImportModeMap,\n pruneContext,\n logger\n );\n }\n\n // Phase 3: Write pruned / minified dictionary JSON files to disk.\n processAllDictionaryFiles(\n dictionariesDir,\n dynamicDictionariesDir,\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger\n );\n\n return pruneContext;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that analyses all project source files and rewrites compiled\n * dictionary JSON files in-place to remove unused content fields\n * (`build.purge`) and/or rename them to short alphabetic aliases\n * (`build.minify`).\n *\n * All option values must be pre-resolved via {@link getPurgePluginOptions}\n * before being passed here — the plugin does not load the intlayer\n * configuration itself.\n *\n * This plugin performs **file I/O as a side effect**: on the very first Babel\n * transform in a given Node.js process it synchronously scans the component\n * files listed in `options.componentFilesList`, builds field-usage data, and\n * writes the processed dictionaries to disk. Subsequent transforms are\n * no-ops.\n *\n * Source-code field renames (rewriting `content.title` → `content.a`) are\n * handled by the companion {@link intlayerMinifyBabelPlugin}.\n *\n * @example\n * ```js\n * // babel.config.js\n * const {\n * intlayerPurgeBabelPlugin,\n * intlayerMinifyBabelPlugin,\n * intlayerOptimizeBabelPlugin,\n * getPurgePluginOptions,\n * getMinifyPluginOptions,\n * getOptimizePluginOptions,\n * } = require(\"@intlayer/babel\");\n *\n * module.exports = {\n * presets: [\"next/babel\"],\n * plugins: [\n * [intlayerPurgeBabelPlugin, getPurgePluginOptions()],\n * [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],\n * [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],\n * ],\n * };\n * ```\n *\n * @remarks\n * - Intended for **production builds** only. Dictionary JSON files are\n * overwritten in-place; running `intlayer build` afterwards restores the\n * originals.\n * - The plugin is a no-op when `optimize` is `false` or `editorEnabled` is\n * `true`.\n */\nexport const intlayerPurgeBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObject => ({\n name: 'intlayer-purge',\n\n pre(this: PluginPass) {\n runIntlayerPurgePipeline(this.opts as PurgePluginOptions);\n },\n\n visitor: {\n // No AST transforms: all work is done as a side effect in pre().\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-purge.cjs","names":["makeUsageAnalyzerBabelPlugin","BABEL_PARSER_OPTIONS","readFileSync","getUsageCheckRegex","extractScriptBlocks","join","existsSync","readdirSync","buildNestedRenameMapFromContent","getNestedRenameEntryAtPath","colorizeKey","colorize","ANSIColors","formatPath","colorizeNumber","createPruneContext","getAppLogger","SOURCE_FILE_REGEX"],"sources":["../../src/babel-plugin-intlayer-purge.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { PluginObject, PluginPass } from '@babel/core';\nimport { transformSync } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport type { NestedFieldReferences } from '@intlayer/core/dictionaryManipulator';\nimport { formatPath } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport {\n buildNestedRenameMapFromContent,\n getNestedRenameEntryAtPath,\n} from './babel-plugin-intlayer-field-rename';\nimport {\n type CompatCallerConfig,\n createPruneContext,\n makeUsageAnalyzerBabelPlugin,\n type NestedRenameMap,\n type PruneContext,\n preserveNestedDictionaryFields,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks } from './extractScriptBlocks';\nimport {\n BABEL_PARSER_OPTIONS,\n getUsageCheckRegex,\n SOURCE_FILE_REGEX,\n} from './transformers';\n\n// ── Plugin options ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved options accepted by {@link intlayerPurgeBabelPlugin}.\n *\n * All values are resolved at babel.config.js load time (via\n * {@link getPurgePluginOptions}) so the plugin itself does not need to read\n * the configuration file on every file transform.\n */\nexport type PurgePluginOptions = {\n /**\n * Absolute path to the project root. Used as the cache key for the shared\n * {@link PruneContext} so two Babel transform pipelines for different\n * workspaces in the same process do not share state.\n */\n baseDir: string;\n\n /**\n * When `true`, remove unused content fields from compiled dictionary JSON\n * files. Mirrors `build.purge` in `intlayer.config.ts`.\n */\n purge: boolean;\n\n /**\n * When `true`, rename content fields to short alphabetic aliases\n * (`title` → `a`, etc.) and strip top-level metadata from compiled\n * dictionaries. Mirrors `build.minify` in `intlayer.config.ts`.\n */\n minify: boolean;\n\n /**\n * Build optimisation toggle. `undefined` means \"auto\" (active for\n * production builds). When explicitly `false`, the plugin is a no-op.\n * Mirrors `build.optimize`.\n */\n optimize: boolean | undefined;\n\n /**\n * When `true` the plugin skips all processing to preserve full dictionary\n * content for the visual editor. Mirrors `editor.enabled`.\n */\n editorEnabled: boolean;\n\n /**\n * Absolute path to the compiled static dictionaries directory\n * (`.intlayer/dictionaries/` by default).\n */\n dictionariesDir: string;\n\n /**\n * Absolute path to the compiled per-locale dynamic dictionaries directory\n * (`.intlayer/dynamic_dictionaries/` by default).\n */\n dynamicDictionariesDir: string;\n\n /**\n * Pre-built list of component source file paths to analyse for field-usage.\n * Populated by {@link getPurgePluginOptions} from the intlayer config's\n * `content` glob patterns.\n */\n componentFilesList: string[];\n\n /**\n * Dictionaries referenced through `nest()`, mapped to the top-level fields\n * those references read (`'all'` when the whole dictionary is nested).\n *\n * Those fields are resolved at runtime by `getNesting` using their original\n * names, so they must survive purging and must never be renamed.\n */\n nestedDictionaryReferences?: Map<string, NestedFieldReferences>;\n\n /**\n * Per-dictionary import-mode overrides, keyed by dictionary `key`.\n * Dictionaries with mode `'fetch'` are excluded from field renaming because\n * their JSON is served from a remote API using the original field names.\n */\n dictionaryKeyToImportModeMap: Record<\n string,\n 'static' | 'dynamic' | 'fetch' | undefined\n >;\n\n /**\n * Compat-adapter namespace caller configurations.\n *\n * When set, the usage analyser recognises these additional translation\n * function patterns and maps them to dictionary field usage. Each compat\n * adapter package (e.g. `@intlayer/react-i18next`) provides its own caller\n * list; they are NOT hardcoded here so that `@intlayer/babel` stays\n * framework-agnostic.\n *\n * Defaults to `[]` (no compat callers) when omitted.\n */\n compatCallers?: CompatCallerConfig[];\n\n /**\n * Logging configuration used to report what the pipeline purged and\n * minified, so a Babel- or SWC-driven build prints the same summary as the\n * Vite one. Mirrors `log` in `intlayer.config.ts`; omit to fall back to the\n * logger defaults.\n */\n logConfig?: Pick<IntlayerConfig, 'log'>;\n};\n\n/** Application logger used to report the pipeline's progress. */\ntype PurgeLogger = ReturnType<typeof getAppLogger>;\n\n// ── Shared module-level state ─────────────────────────────────────────────────\n\n/**\n * Cache of built {@link PruneContext} objects, keyed by the project's\n * `baseDir`. Each context is built exactly once per Node.js process:\n * {@link runIntlayerPurgePipeline} registers the context before running so every\n * subsequent transform is a no-op cache hit.\n */\nconst _pruneContextCache = new Map<string, PruneContext>();\n\n/**\n * Returns the shared {@link PruneContext} for the given base directory, or\n * `null` if {@link intlayerPurgeBabelPlugin} has not yet been initialised for\n * that directory.\n *\n * Used by {@link intlayerMinifyBabelPlugin} to read the rename map without\n * creating a circular dependency.\n */\nexport const getSharedPruneContext = (baseDir: string): PruneContext | null =>\n _pruneContextCache.get(baseDir) ?? null;\n\n// ── Dictionary JSON types ─────────────────────────────────────────────────────\n\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string;\n [extraKey: string]: unknown;\n};\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// ── Prune helpers (mirrors intlayerPrunePlugin) ───────────────────────────────\n\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/** Returns a copy of `record` keeping only the keys listed in `usedFieldNames`. */\nconst filterRecordByUsedFields = (\n record: Record<string, unknown>,\n usedFieldNames: Set<string>\n): Record<string, unknown> => {\n const filteredRecord: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(record)) {\n if (usedFieldNames.has(fieldName)) {\n filteredRecord[fieldName] = fieldValue;\n }\n }\n return filteredRecord;\n};\n\n/**\n * Removes unused fields from a **static** dictionary (all locales in one\n * file). Supports shape A (translation node at the root) and shape B (flat\n * record of translation nodes per field).\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A: { nodeType: \"translation\", translation: { en: { f1, f2 } } }\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n if (isPlainRecord(firstLocaleValue)) {\n const prunedTranslation: Record<string, unknown> = {};\n for (const [locale, localeContent] of Object.entries(\n content.translation\n )) {\n prunedTranslation[locale] = isPlainRecord(localeContent)\n ? filterRecordByUsedFields(localeContent, usedFieldNames)\n : localeContent;\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslation },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B: { field1: { nodeType: \"translation\", … }, field2: { … } }\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Removes unused fields from a **dynamic / per-locale** dictionary file\n * (one JSON per locale, flat `content` record).\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n return {\n prunedDictionary: {\n ...dictionary,\n content: filterRecordByUsedFields(content, usedFieldNames),\n },\n wasRecognised: true,\n };\n};\n\n// ── Minify helpers (mirrors intlayerMinifyPlugin) ─────────────────────────────\n\n/**\n * Recursively renames user-defined content fields using `renameMap`.\n * Translation nodes, arrays, and primitives follow the same traversal rules\n * as in the Vite-based minify plugin.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale value with the same map.\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values.\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val;\n }\n }\n return result;\n};\n\n/**\n * Applies a {@link NestedRenameMap} to a parsed dictionary object, renaming\n * only the keys inside `content` while leaving top-level metadata untouched.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n// ── Synchronous source-file analysis ─────────────────────────────────────────\n\n/**\n * Runs the usage-analyser Babel plugin synchronously on a single code block,\n * accumulating results into `pruneContext`.\n */\nconst analyzeCodeBlockSync = (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n try {\n transformSync(code, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false,\n });\n } catch {\n pruneContext.hasUnparsableSourceFiles = true;\n }\n};\n\n/**\n * Reads a source file from disk and runs the usage-analyser synchronously.\n * SFC files (Vue / Svelte) are handled by extracting script blocks first.\n */\nconst analyzeSourceFileSync = (\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): void => {\n let code: string;\n try {\n code = readFileSync(sourceFilePath, 'utf-8');\n } catch {\n return;\n }\n\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n\n if (!usageCheckRegex.test(code)) return;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n analyzeCodeBlockSync(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n// ── Build rename maps ─────────────────────────────────────────────────────────\n\n/**\n * Reads compiled dictionary JSON files to build the nested field-rename maps,\n * mirroring the Phase 4 logic in the Vite `intlayerOptimize` plugin's\n * `buildStart` hook. Results are stored in\n * `pruneContext.dictionaryKeyToFieldRenameMap`.\n */\nconst buildRenameMapsSynchronously = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n dictionaryKeyToImportModeMap: PurgePluginOptions['dictionaryKeyToImportModeMap'],\n pruneContext: PruneContext,\n logger: PurgeLogger\n): void => {\n let partiallyMinifiedDictionariesCount = 0;\n\n for (const [\n dictionaryKey,\n fieldUsage,\n ] of pruneContext.dictionaryKeyToFieldUsageMap) {\n if (fieldUsage === 'all') continue;\n if (dictionaryKeyToImportModeMap[dictionaryKey] === 'fetch') continue;\n if (pruneContext.dictionariesSkippingFieldRename.has(dictionaryKey))\n continue;\n\n let dictionaryContent: unknown = null;\n\n const staticJsonPath = join(dictionariesDir, `${dictionaryKey}.json`);\n if (existsSync(staticJsonPath)) {\n try {\n const raw = readFileSync(staticJsonPath, 'utf-8');\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n } catch {\n // Fall through to dynamic dict.\n }\n }\n\n if (!dictionaryContent) {\n const dynamicDirPath = join(dynamicDictionariesDir, dictionaryKey);\n if (existsSync(dynamicDirPath)) {\n try {\n const localeFiles = readdirSync(dynamicDirPath);\n const firstJsonFile = localeFiles.find((file) =>\n file.endsWith('.json')\n );\n if (firstJsonFile) {\n const raw = readFileSync(\n join(dynamicDirPath, firstJsonFile),\n 'utf-8'\n );\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n dictionaryContent = parsed.content;\n }\n } catch {\n // Dictionary not readable – skip rename for this key.\n }\n }\n }\n\n if (!dictionaryContent) continue;\n\n const nestedRenameMap = buildNestedRenameMapFromContent(dictionaryContent);\n\n // Preserve children of opaque values to avoid breaking child components.\n const opaqueFieldMap =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey);\n if (opaqueFieldMap) {\n const dangerousOccurrences = [...opaqueFieldMap.values()].filter(\n (occurrence) =>\n (getNestedRenameEntryAtPath(nestedRenameMap, occurrence.fieldPath)\n ?.children.size ?? 0) > 0\n );\n\n if (dangerousOccurrences.length > 0) {\n partiallyMinifiedDictionariesCount += 1;\n\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `partially minified.`,\n ...dangerousOccurrences.flatMap((occurrence) => [\n `\\n Opaque field:`,\n colorize(`'${occurrence.fieldPath.join('.')}'`, ANSIColors.BLUE),\n `(nested keys preserved for stability).`,\n ...occurrence.locations.map(\n (location) => `\\n at ${formatPath(location)}`\n ),\n ]),\n ],\n { level: 'warn', isVerbose: true }\n );\n\n for (const occurrence of dangerousOccurrences) {\n const renameEntry = getNestedRenameEntryAtPath(\n nestedRenameMap,\n occurrence.fieldPath\n );\n if (renameEntry) {\n renameEntry.children = new Map();\n }\n }\n }\n }\n\n if (nestedRenameMap.size > 0) {\n pruneContext.dictionaryKeyToFieldRenameMap.set(\n dictionaryKey,\n nestedRenameMap\n );\n }\n }\n\n if (partiallyMinifiedDictionariesCount > 0) {\n logger([\n `Partially minified`,\n colorizeNumber(partiallyMinifiedDictionariesCount),\n `dictionar${partiallyMinifiedDictionariesCount === 1 ? 'y' : 'ies'}`,\n `(preserved nested keys for opaque fields).`,\n ]);\n }\n};\n\n// ── Dictionary file writing ───────────────────────────────────────────────────\n\n/**\n * Names of the top-level content fields of a compiled dictionary, whatever\n * shape its `content` uses. Used to report which fields the purge removed.\n */\nconst readContentFieldNames = (\n content: CompiledDictionaryJson['content']\n): string[] => {\n // Shape A – fields live inside each locale object.\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n return isPlainRecord(firstLocaleValue) ? Object.keys(firstLocaleValue) : [];\n }\n\n // Shape B / dynamic – flat content record.\n if (isPlainRecord(content)) return Object.keys(content);\n\n return [];\n};\n\n/**\n * Running totals of the purge, reported once the whole pipeline is done.\n */\ntype PurgeStats = {\n /** Dictionary key → number of fields removed from it. */\n prunedFieldsCountPerDictionary: Map<string, number>;\n /**\n * Dictionary keys whose \"pruned fields\" line has already been printed. A\n * key spans several files (one static, one per locale), and they all remove\n * the same fields.\n */\n loggedPrunedDictionaryKeys: Set<string>;\n /**\n * Dictionary keys whose \"analysis is incomplete\" warning has already been\n * printed, deduplicated the same way.\n */\n loggedIncompleteAnalysisKeys: Set<string>;\n};\n\n/**\n * Reads a compiled dictionary JSON file, applies the purge and/or minify\n * transformations, and writes it back in-place when something changed.\n *\n * `dictionaryKind` selects the prune strategy and the minified output shape:\n * static dictionaries keep `{ key, content }`, dynamic per-locale\n * dictionaries additionally keep `locale`.\n */\nconst processDictionaryFile = (\n filePath: string,\n dictionaryKind: 'static' | 'dynamic',\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger,\n stats: PurgeStats\n): void => {\n let parsedDict: CompiledDictionaryJson;\n try {\n parsedDict = JSON.parse(\n readFileSync(filePath, 'utf-8')\n ) as CompiledDictionaryJson;\n } catch {\n return; // Unreadable or invalid JSON – leave file unchanged.\n }\n\n const { key: dictionaryKey } = parsedDict;\n\n if (!dictionaryKey) {\n logger(\n [\n `Dictionary file`,\n formatPath(filePath),\n `is missing a \"key\" field. Skipping prune for this file.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n let modified = false;\n\n if (shouldPurge) {\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n if (fieldUsage && fieldUsage !== 'all') {\n const pruneDictionaryContent =\n dictionaryKind === 'static'\n ? pruneStaticDictionaryContent\n : pruneDynamicDictionaryContent;\n const originalFieldNames = readContentFieldNames(parsedDict.content);\n const { prunedDictionary, wasRecognised } = pruneDictionaryContent(\n parsedDict,\n fieldUsage\n );\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Unrecognised content structure in dictionary`,\n colorizeKey(dictionaryKey),\n `(file:`,\n `${formatPath(filePath)}).`,\n `Skipping prune for this dictionary.`,\n ],\n { level: 'warn' }\n );\n return;\n }\n\n const removedFieldNames = originalFieldNames.filter(\n (fieldName) => !fieldUsage.has(fieldName)\n );\n\n if (removedFieldNames.length > 0) {\n stats.prunedFieldsCountPerDictionary.set(\n dictionaryKey,\n removedFieldNames.length\n );\n\n if (!stats.loggedPrunedDictionaryKeys.has(dictionaryKey)) {\n stats.loggedPrunedDictionaryKeys.add(dictionaryKey);\n logger(\n [\n `Pruned`,\n colorizeNumber(removedFieldNames.length),\n `unused field${removedFieldNames.length === 1 ? '' : 's'} from`,\n `${colorizeKey(dictionaryKey)}:`,\n removedFieldNames\n .map((fieldName) => colorize(fieldName, ANSIColors.GREY_LIGHT))\n .join(', '),\n ],\n { isVerbose: true }\n );\n }\n }\n\n parsedDict = prunedDictionary;\n modified = true;\n } else if (\n !fieldUsage &&\n pruneContext.hasUnparsableSourceFiles &&\n !stats.loggedIncompleteAnalysisKeys.has(dictionaryKey)\n ) {\n // An unparsable source file might be the one referencing this key, so its\n // usage set cannot be trusted to be empty.\n stats.loggedIncompleteAnalysisKeys.add(dictionaryKey);\n logger(\n [\n `Skipping prune for dictionary`,\n colorizeKey(dictionaryKey),\n `: analysis is incomplete due to earlier source-file parse failures.`,\n ],\n { level: 'warn' }\n );\n }\n }\n\n if (shouldMinify) {\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(\n parsedDict as Record<string, unknown>,\n fieldRenameMap\n ) as CompiledDictionaryJson;\n modified = true;\n }\n }\n\n if (!modified) return;\n\n const outputDict = shouldMinify\n ? dictionaryKind === 'static'\n ? { key: parsedDict.key, content: parsedDict.content }\n : {\n key: parsedDict.key,\n content: parsedDict.content,\n locale: parsedDict.locale,\n }\n : parsedDict;\n\n try {\n writeFileSync(filePath, JSON.stringify(outputDict), 'utf-8');\n } catch {\n // Write failure – leave file unchanged.\n }\n};\n\nconst processAllDictionaryFiles = (\n dictionariesDir: string,\n dynamicDictionariesDir: string,\n pruneContext: PruneContext,\n shouldPurge: boolean,\n shouldMinify: boolean,\n logger: PurgeLogger\n): void => {\n const stats: PurgeStats = {\n prunedFieldsCountPerDictionary: new Map(),\n loggedPrunedDictionaryKeys: new Set(),\n loggedIncompleteAnalysisKeys: new Set(),\n };\n\n if (existsSync(dictionariesDir)) {\n for (const entry of readdirSync(dictionariesDir)) {\n if (!entry.endsWith('.json')) continue;\n processDictionaryFile(\n join(dictionariesDir, entry),\n 'static',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n }\n\n if (existsSync(dynamicDictionariesDir)) {\n for (const keyDir of readdirSync(dynamicDictionariesDir)) {\n const keyDirPath = join(dynamicDictionariesDir, keyDir);\n try {\n for (const localeFile of readdirSync(keyDirPath)) {\n if (!localeFile.endsWith('.json')) continue;\n processDictionaryFile(\n join(keyDirPath, localeFile),\n 'dynamic',\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger,\n stats\n );\n }\n } catch {\n // Unreadable key directory – skip.\n }\n }\n }\n\n const totalPrunedFieldsCount = [\n ...stats.prunedFieldsCountPerDictionary.values(),\n ].reduce((total, count) => total + count, 0);\n const totalPrunedDictionariesCount =\n stats.prunedFieldsCountPerDictionary.size;\n\n if (totalPrunedFieldsCount > 0) {\n logger([\n `Pruned`,\n colorizeNumber(totalPrunedFieldsCount),\n `unused field${totalPrunedFieldsCount === 1 ? '' : 's'} across`,\n colorizeNumber(totalPrunedDictionariesCount),\n `dictionar${totalPrunedDictionariesCount === 1 ? 'y' : 'ies'}.`,\n ]);\n }\n};\n\n// ── Reporting helpers ─────────────────────────────────────────────────────────\n\n/**\n * Explains why the requested steps did nothing: the visual editor needs the\n * full dictionary content, so both purge and minify stand down when it is on.\n */\nconst logStepDisabledByEditor = (\n logger: PurgeLogger,\n purge: boolean,\n minify: boolean\n): void => {\n const explainDisabled = (stepLabel: string) =>\n logger([\n stepLabel,\n 'is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n colorize(\n '— the editor requires full dictionary content',\n ANSIColors.GREY\n ),\n ]);\n\n if (purge) explainDisabled('Dictionary purge');\n if (minify) explainDisabled('Dictionary minification');\n};\n\n/**\n * Reports the dictionaries whose consumption could not be tracked statically,\n * with the source files responsible — those keep their full content.\n */\nconst logUntrackedBindings = (\n logger: PurgeLogger,\n pruneContext: PruneContext\n): void => {\n for (const [\n dictionaryKey,\n sourceFilePaths,\n ] of pruneContext.dictionaryKeysWithUntrackedBindings) {\n logger(\n [\n `Dictionary`,\n colorizeKey(dictionaryKey),\n `cannot be purged or minified.`,\n `\\n Reason: the result of`,\n `${colorize(`useIntlayer(`, ANSIColors.GREY_LIGHT)}${colorizeKey(\n `'${dictionaryKey}'`\n )}${colorize(`)`, ANSIColors.GREY_LIGHT)}`,\n `is assigned to a plain variable in:`,\n ...sourceFilePaths.map(\n (filePath) => `\\n - ${formatPath(filePath)}`\n ),\n ],\n { level: 'warn' }\n );\n }\n};\n\n// ── Main initialisation ───────────────────────────────────────────────────────\n\n/**\n * Runs the full purge/minify pipeline for the given options, using a\n * module-level cache so the work happens at most once per process per\n * unique `baseDir`.\n *\n * Exported so bundler integrations that do not go through Babel can drive the\n * same pipeline. The Next.js integration is the main consumer: its SWC plugin\n * runs as a per-file Wasm transform with no filesystem access, so the analysis\n * and the dictionary rewrite happen here, in Node, and only the resulting\n * field-rename tables (see {@link serializeFieldRenameMap}) are forwarded to\n * the plugin.\n *\n * **This performs file I/O**: it reads every source file listed in\n * `componentFilesList` and overwrites the compiled dictionary JSON files\n * in-place. Call it at most once per build, after the dictionaries have been\n * built.\n */\nexport const runIntlayerPurgePipeline = (\n options: PurgePluginOptions\n): PruneContext => {\n const {\n baseDir,\n purge,\n minify,\n optimize,\n editorEnabled,\n dictionariesDir,\n dynamicDictionariesDir,\n componentFilesList,\n dictionaryKeyToImportModeMap,\n nestedDictionaryReferences,\n compatCallers,\n logConfig,\n } = options;\n\n const cachedContext = _pruneContextCache.get(baseDir);\n if (cachedContext) return cachedContext;\n\n const pruneContext = createPruneContext();\n _pruneContextCache.set(baseDir, pruneContext);\n\n const logger = getAppLogger(logConfig);\n\n const shouldPurge = purge && !editorEnabled;\n const shouldMinify = minify && !editorEnabled;\n\n if (editorEnabled && (purge || minify)) {\n logStepDisabledByEditor(logger, purge, minify);\n }\n\n if ((!shouldPurge && !shouldMinify) || optimize === false)\n return pruneContext;\n\n // Phase 1: Synchronously analyse all component source files.\n for (const sourceFilePath of componentFilesList) {\n if (!SOURCE_FILE_REGEX.test(sourceFilePath)) continue;\n analyzeSourceFileSync(sourceFilePath, pruneContext, compatCallers);\n }\n\n // Phase 1.5: Keep `nest()` targets resolvable — must run before the rename\n // maps are built so nest targets are excluded from field renaming.\n if (nestedDictionaryReferences) {\n preserveNestedDictionaryFields(pruneContext, nestedDictionaryReferences);\n }\n\n // Phase 1.6: Warn about the dictionaries the analysis could not follow.\n logUntrackedBindings(logger, pruneContext);\n\n // Phase 2: Build field-rename maps (minify only).\n if (shouldMinify) {\n buildRenameMapsSynchronously(\n dictionariesDir,\n dynamicDictionariesDir,\n dictionaryKeyToImportModeMap,\n pruneContext,\n logger\n );\n }\n\n // Phase 3: Write pruned / minified dictionary JSON files to disk.\n processAllDictionaryFiles(\n dictionariesDir,\n dynamicDictionariesDir,\n pruneContext,\n shouldPurge,\n shouldMinify,\n logger\n );\n\n return pruneContext;\n};\n\n// ── Babel plugin ──────────────────────────────────────────────────────────────\n\n/**\n * Babel plugin that analyses all project source files and rewrites compiled\n * dictionary JSON files in-place to remove unused content fields\n * (`build.purge`) and/or rename them to short alphabetic aliases\n * (`build.minify`).\n *\n * All option values must be pre-resolved via {@link getPurgePluginOptions}\n * before being passed here — the plugin does not load the intlayer\n * configuration itself.\n *\n * This plugin performs **file I/O as a side effect**: on the very first Babel\n * transform in a given Node.js process it synchronously scans the component\n * files listed in `options.componentFilesList`, builds field-usage data, and\n * writes the processed dictionaries to disk. Subsequent transforms are\n * no-ops.\n *\n * Source-code field renames (rewriting `content.title` → `content.a`) are\n * handled by the companion {@link intlayerMinifyBabelPlugin}.\n *\n * @example\n * ```js\n * // babel.config.js\n * const {\n * intlayerPurgeBabelPlugin,\n * intlayerMinifyBabelPlugin,\n * intlayerOptimizeBabelPlugin,\n * getPurgePluginOptions,\n * getMinifyPluginOptions,\n * getOptimizePluginOptions,\n * } = require(\"@intlayer/babel\");\n *\n * module.exports = {\n * presets: [\"next/babel\"],\n * plugins: [\n * [intlayerPurgeBabelPlugin, getPurgePluginOptions()],\n * [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],\n * [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],\n * ],\n * };\n * ```\n *\n * @remarks\n * - Intended for **production builds** only. Dictionary JSON files are\n * overwritten in-place; running `intlayer build` afterwards restores the\n * originals.\n * - The plugin is a no-op when `optimize` is `false` or `editorEnabled` is\n * `true`.\n */\nexport const intlayerPurgeBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObject => ({\n name: 'intlayer-purge',\n\n pre(this: PluginPass) {\n runIntlayerPurgePipeline(this.opts as PurgePluginOptions);\n },\n\n visitor: {\n // No AST transforms: all work is done as a side effect in pre().\n },\n});\n"],"mappings":"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"}
|
|
@@ -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/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = ['useIntlayer', 'getIntlayer'] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n// ── Compat-adapter namespace callers ──────────────────────────────────────────\n\n// The caller-descriptor model is shared across the build pipeline (babel, swc\n// wire format), the LSP and the compat packages' bundler plugins. It lives in\n// `@intlayer/config/callers`; see that module for the full documentation.\nexport type {\n CallerDescriptor,\n CallerResultShape,\n CallerValueSource,\n} from '@intlayer/config/callers';\n\n/**\n * Configuration entry for a single compat namespace caller.\n *\n * @deprecated Alias kept for backward compatibility — use the shared\n * {@link CallerDescriptor} from `@intlayer/config/callers` instead.\n */\nexport type CompatCallerConfig = CallerDescriptor;\n\n/**\n * Default registry of compat namespace callers.\n *\n * Intentionally empty — compat-specific caller configurations belong in their\n * respective adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) and are injected into `makeUsageAnalyzerBabelPlugin`\n * via the `compatCallers` option. Centralising them here would couple the\n * core `@intlayer/babel` package to every compat adapter, which violates the\n * design principle that compat logic lives in compat packages.\n */\nexport const DEFAULT_COMPAT_CALLERS: CompatCallerConfig[] = [];\n\n/** Default namespace used by compat callers when no namespace argument is given. */\nconst DEFAULT_COMPAT_NAMESPACE = 'translation';\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n if (existingUsage instanceof Set) {\n // Merge in place — the set is owned by the map.\n for (const fieldName of fieldUsage) existingUsage.add(fieldName);\n return;\n }\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n new Set(fieldUsage)\n );\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n const parentNode = callExpressionPath.parent;\n\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 // ── 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 callExpressionPath,\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 ===\n callExpressionPath.node\n ) {\n let fieldName: string | undefined;\n\n if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) {\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 recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n const memberExprPath = callExpressionPath.parentPath;\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 = callExpressionPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n let hasUntrackedReferenceAccess = false;\n\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 accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n const memberExprPath = variableReferencePath.parentPath;\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\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 hasUntrackedReferenceAccess = true;\n break;\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 accessedTopLevelFieldNames.add(fieldName);\n const memberExprPath = callExprPath?.parentPath;\n if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);\n } else {\n // content()[dynamicKey] – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\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 hasUntrackedReferenceAccess = true;\n break;\n }\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n hasUntrackedReferenceAccess = true;\n break;\n }\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 – conservatively keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);\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// ── Compat namespace-caller analysis ──────────────────────────────────────────\n\n/**\n * Reads a fully-static string from an AST node. Returns `undefined` for\n * dynamic values (identifiers, expressions, template literals with\n * interpolations, …).\n */\nconst readStaticString = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n if (!node) return undefined;\n if (babelTypes.isStringLiteral(node)) return node.value;\n if (\n babelTypes.isTemplateLiteral(node) &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n }\n return undefined;\n};\n\n/** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */\nconst firstPathSegment = (path: string): string => path.split('.')[0] ?? path;\n\n/**\n * Reads the static first dot-path segment from a `t()` first-argument node.\n *\n * t('counter.label') → 'counter'\n * t(`counter.${x}`) → 'counter' (static prefix before the first dot)\n * t(`${x}.label`) → undefined (dynamic first segment)\n * t(someVariable) → undefined\n */\nconst readStaticFirstSegment = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n const staticString = readStaticString(babelTypes, node);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Template literal whose first quasi already contains the dot delimiter, e.g.\n // `counter.${index}` → the leading `counter` segment is statically known.\n if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {\n const firstQuasi =\n node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n if (firstQuasi?.includes('.')) {\n return firstPathSegment(firstQuasi);\n }\n }\n return undefined;\n};\n\n/**\n * Reads a static string property from an object expression. Returns\n * `'__default__'` when the property is absent and `undefined` when present but\n * dynamic.\n */\nconst readObjectProperty = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): string | '__default__' | undefined => {\n for (const property of objectExpression.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n const keyMatches =\n (babelTypes.isIdentifier(property.key) &&\n property.key.name === propertyName) ||\n (babelTypes.isStringLiteral(property.key) &&\n property.key.value === propertyName);\n if (!keyMatches) continue;\n const staticValue = readStaticString(babelTypes, property.value);\n return staticValue ?? undefined; // present but dynamic → undefined\n }\n return '__default__'; // property absent\n};\n\n/**\n * Reads a named JSX attribute as a static string and returns it wrapped in a\n * `StringLiteral` node so the result can be fed to the same namespace resolver\n * as a call argument. Handles both `id=\"home.title\"` and `id={'home.title'}`.\n * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).\n */\nconst readJsxAttributeString = (\n babelTypes: typeof BabelTypes,\n openingElement: BabelTypes.JSXOpeningElement,\n attributeName: string\n): BabelTypes.StringLiteral | undefined => {\n for (const attribute of openingElement.attributes) {\n if (!babelTypes.isJSXAttribute(attribute)) continue;\n if (\n !babelTypes.isJSXIdentifier(attribute.name) ||\n attribute.name.name !== attributeName\n ) {\n continue;\n }\n\n const value = attribute.value;\n // id=\"home.title\"\n if (babelTypes.isStringLiteral(value)) return value;\n // id={'home.title'} / id={`home.title`}\n if (babelTypes.isJSXExpressionContainer(value)) {\n const staticString = readStaticString(babelTypes, value.expression);\n if (staticString !== undefined) {\n return babelTypes.stringLiteral(staticString);\n }\n }\n return undefined; // attribute present but dynamic\n }\n return undefined; // attribute absent\n};\n\n/**\n * Resolves the namespace (dictionary key) for a compat caller call-site from\n * one `CallerValueSource`. Returns the static key, or `'__default__'` when the\n * configured argument is absent (caller falls back to its default namespace),\n * or `undefined` when the value is present but dynamic.\n */\nconst resolveCompatNamespace = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n source: CallerValueSource\n): string | '__default__' | undefined => {\n if (source.from === 'fixed') return source.value;\n\n if (source.from === 'path-first-segment') {\n const firstArg = callArguments[0];\n // No sensible default: the dictionary key is *derived* from the id, so an\n // absent id means the call cannot be attributed to any dictionary → skip.\n if (firstArg === undefined) return undefined;\n\n // String form: formatMessage('home.title', values)\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Descriptor form: formatMessage({ id: 'home.title', ... }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue === '__default__') return '__default__'; // id absent\n if (idValue !== undefined) return firstPathSegment(idValue); // static id\n return undefined; // id present but dynamic\n }\n\n return undefined; // dynamic first argument\n }\n\n if (source.from === 'argument') {\n const argument = callArguments[source.index];\n if (argument === undefined) return '__default__';\n\n // Direct string namespace: useTranslations('about')\n const staticString = readStaticString(babelTypes, argument);\n if (staticString !== undefined) return staticString;\n\n // Object form: getTranslations({ locale, namespace: 'about' })\n if (babelTypes.isObjectExpression(argument)) {\n return readObjectProperty(babelTypes, argument, 'namespace');\n }\n return undefined; // present but dynamic\n }\n\n // from === 'option'\n const optionsArgument = callArguments[source.argumentIndex];\n if (optionsArgument === undefined) return '__default__';\n if (!babelTypes.isObjectExpression(optionsArgument)) return undefined;\n return readObjectProperty(babelTypes, optionsArgument, source.property);\n};\n\n/**\n * Resolves the namespace from a list of `CallerValueSource`s, tried in\n * declaration order. The first source yielding a static string wins.\n * Returns `'__default__'` when every source reports an absent value, and\n * `undefined` when the namespace is present somewhere but dynamic.\n */\nconst resolveNamespaceFromSources = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[]\n): string | '__default__' | undefined => {\n let sawAbsentValue = false;\n\n for (const source of sources) {\n const resolved = resolveCompatNamespace(babelTypes, callArguments, source);\n if (typeof resolved === 'string' && resolved !== '__default__') {\n return resolved;\n }\n if (resolved === '__default__') sawAbsentValue = true;\n }\n\n return sawAbsentValue ? '__default__' : undefined;\n};\n\n/**\n * Resolves an optional `keyPrefix` for a compat caller. Returns the static\n * prefix string, `null` when no prefix is configured/present, or `undefined`\n * when a prefix is present but dynamic.\n */\nconst resolveCompatKeyPrefix = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[] | undefined\n): string | null | undefined => {\n if (!sources || sources.length === 0) return null;\n const resolved = resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n sources\n );\n if (resolved === '__default__') return null; // prefix absent\n return resolved; // string or undefined (dynamic)\n};\n\n/**\n * Climbs past an enclosing `await` expression so that\n * `const t = await getTranslations('ns')` is resolved to its variable\n * declarator the same way the synchronous form is.\n */\nconst unwrapAwait = (\n babelTypes: typeof BabelTypes,\n path: NodePath<BabelTypes.Node>\n): NodePath<BabelTypes.Node> => {\n const parentPath = path.parentPath;\n if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) {\n return parentPath;\n }\n return path;\n};\n\n/**\n * Analyses how the translation function produced by a compat namespace caller\n * (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,\n * `useI18n`) is consumed, then records the accessed top-level dictionary fields\n * into `pruneContext`.\n *\n * Dictionaries consumed this way are always added to\n * `dictionariesSkippingFieldRename`: the field accesses are string-literal\n * dot-paths inside `t()` calls, which the field-rename plugin cannot rewrite,\n * so renaming the compiled JSON keys would break runtime lookups. Pruning\n * (top-level field removal) remains safe because it preserves field names.\n */\nconst analyzeNamespaceCallerUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callArguments: BabelTypes.CallExpression['arguments'],\n callerConfig: CompatCallerConfig,\n isSfcFile: boolean,\n /** Absolute path of the analysed file (for untracked-binding reporting). */\n currentSourceFilePath: string,\n /**\n * The call-expression path, when the caller was matched as a call. Omitted\n * for JSX-element matches (`<FormattedMessage id>`), where only the static\n * `'self'` / `'all'` analysis paths apply — the binding-based\n * `'destructured-t'` / `'return-value'` paths require a call site.\n */\n callExpressionPath?: NodePath<BabelTypes.CallExpression>,\n /**\n * Pre-resolved namespace bypassing `namespaceSources`, e.g. the `ns`\n * attribute of react-i18next's `<Trans ns=\"home\" i18nKey=\"title\" />`.\n */\n namespaceOverride?: string\n): void => {\n // 1. Resolve the dictionary key (namespace).\n const resolvedNamespace =\n namespaceOverride ??\n resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n callerConfig.namespaceSources\n );\n if (resolvedNamespace === undefined) return; // dynamic key – cannot attribute\n const namespaceString =\n resolvedNamespace === '__default__'\n ? DEFAULT_COMPAT_NAMESPACE\n : resolvedNamespace;\n\n // next-intl scopes nested objects through a dotted namespace\n // (`'about.counter'`): the dictionary key is the first segment and the\n // remainder is an implicit key prefix applied to every t() lookup.\n const namespaceSegments = namespaceString.split('.');\n const dictionaryKey = namespaceSegments[0] ?? namespaceString;\n const namespacePrefix =\n namespaceSegments.length > 1 ? namespaceSegments.slice(1).join('.') : null;\n\n // Compat string-path access is never renamable.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n // 2. SFC files (Vue / Svelte / Astro): the translation function is typically\n // invoked from the template, which Babel cannot see. Conservatively keep\n // every field to avoid pruning a template-only access.\n if (isSfcFile) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a. Mark the entire dictionary as used — static field analysis is not\n // applicable (e.g. lingui hashed IDs, Angular templates).\n if (callerConfig.translationFunction === 'all') {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a-bis. The caller returns the dictionary content object itself\n // (`useIntlayer`-like shape): reuse the native member-access /\n // destructuring analysis. Without a call site, keep every field.\n if (callerConfig.translationFunction === 'content') {\n if (callExpressionPath) {\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // 3b. The caller IS the translation call (`translationFunction: 'self'`).\n // The first argument of the current call expression is the message key.\n // Used for lingui's `i18n._('key')` / `i18n.t('key')` and react-intl's\n // `intl.formatMessage({ id: 'key' })` patterns.\n if (callerConfig.translationFunction === 'self') {\n const firstArg = callArguments[0];\n if (!firstArg) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n if (\n callerConfig.namespaceSources.some(\n (source) => source.from === 'path-first-segment'\n )\n ) {\n // The dictionary key is already the first segment of the descriptor id.\n // The field to record is the SECOND segment (first level inside the dict).\n // e.g. formatMessage({ id: 'home.title' }) → dictionaryKey='home', field='title'\n let fullId: string | undefined;\n\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) {\n fullId = staticString;\n } else if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n fullId = idValue;\n }\n }\n\n if (fullId !== undefined) {\n const segments = fullId.split('.');\n const field = segments[1];\n if (field !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([field]));\n } else {\n // Single-segment id — the whole value is the dict key; no sub-field.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // Dynamic id — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // For 'fixed' / 'argument' / 'option' namespaces: the field is the first\n // dot-segment of the first argument (the message key itself) — unless\n // `flatKey` is set, in which case the whole dotted key is the field.\n // String form: i18n._('home.title', values)\n const segment = callerConfig.flatKey\n ? readStaticString(babelTypes, firstArg)\n : readStaticFirstSegment(babelTypes, firstArg);\n if (segment !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([segment]));\n return;\n }\n\n // Descriptor form: i18n._({ id: 'home.title', message: '...' }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([callerConfig.flatKey ? idValue : firstPathSegment(idValue)])\n );\n return;\n }\n }\n\n // Dynamic key — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The remaining strategies (`'destructured-t'` / `'return-value'`) resolve\n // the `t` binding from the call site, so they require a call-expression path.\n // JSX-element matches never reach here (they use `'self'` / `'all'`); guard\n // defensively so a misconfigured JSX caller keeps every field instead of\n // throwing.\n if (!callExpressionPath) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3. Resolve an optional explicit keyPrefix (e.g. react-i18next's\n // `{ keyPrefix }` option). A static prefix fixes the single consumed\n // top-level field regardless of the individual t() paths.\n const explicitKeyPrefix = resolveCompatKeyPrefix(\n babelTypes,\n callArguments,\n callerConfig.keyPrefixSources\n );\n if (explicitKeyPrefix === undefined) {\n // Prefix present but dynamic → unknown field set.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The namespace-derived prefix (next-intl) and the explicit keyPrefix option\n // (react-i18next / i18next) never coexist in practice; prefer whichever is\n // present. Either way, the consumed top-level field is the prefix's first\n // segment.\n const effectivePrefix = namespacePrefix ?? explicitKeyPrefix;\n if (effectivePrefix !== null) {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([firstPathSegment(effectivePrefix)])\n );\n return;\n }\n\n // 4. Locate the `t` function binding.\n let translationBinding: ReturnType<NodePath['scope']['getBinding']> | null =\n null;\n\n if (callerConfig.translationFunction === 'destructured-t') {\n // const { t } = useTranslation('ns')\n const parentNode = callExpressionPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key) &&\n property.key.name === 't' &&\n babelTypes.isIdentifier(property.value)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(property.value.name) ?? null;\n }\n }\n }\n } else {\n // const t = useTranslations('ns') / const t = await getTranslations('ns')\n const resultPath = unwrapAwait(babelTypes, callExpressionPath);\n const parentNode = resultPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(parentNode.id.name) ?? null;\n }\n }\n\n // Could not statically locate `t` (e.g. result stored whole, re-exported) →\n // conservatively keep all fields.\n if (!translationBinding) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 5. Inspect every reference to `t`.\n const accessedFields = new Set<string>();\n let hasUntrackedUsage = false;\n\n for (const referencePath of translationBinding.referencePaths) {\n const parentNode = referencePath.parent;\n\n // Must be a direct call: t('path')\n const isDirectCall =\n (babelTypes.isCallExpression(parentNode) ||\n babelTypes.isOptionalCallExpression(parentNode)) &&\n (parentNode as BabelTypes.CallExpression).callee === referencePath.node;\n\n if (!isDirectCall) {\n // t passed as a prop / argument / reassigned → fields unknown.\n hasUntrackedUsage = true;\n break;\n }\n\n const firstArgument = (parentNode as BabelTypes.CallExpression)\n .arguments[0];\n const segment = readStaticFirstSegment(babelTypes, firstArgument);\n if (segment === undefined) {\n hasUntrackedUsage = true;\n break;\n }\n accessedFields.add(segment);\n }\n\n if (hasUntrackedUsage) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // Only record a finite field set when at least one field was actually\n // accessed. Recording an empty set would prune every field even though the\n // dictionary may be consumed elsewhere.\n if (accessedFields.size > 0) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFields);\n }\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site —\n * and each configured compat namespace caller — accesses. Results are\n * accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n *\n * @param pruneContext - Shared mutable state written by this plugin.\n * @param options - Optional overrides. `compatCallers` defaults to\n * {@link DEFAULT_COMPAT_CALLERS}; pass `[]` to disable\n * compat-adapter analysis entirely.\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (\n pruneContext: PruneContext,\n options?: { compatCallers?: CompatCallerConfig[] }\n ) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => {\n const compatCallers = options?.compatCallers ?? DEFAULT_COMPAT_CALLERS;\n\n return {\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte') ||\n currentSourceFilePath.endsWith('.astro');\n\n // Phase 1: collect local aliases for native intlayer callers and\n // for compat namespace callers (gated by import source).\n const intlayerCallerLocalNameMap = new Map<string, string>();\n const compatCallerLocalNameMap = new Map<\n string,\n CompatCallerConfig\n >();\n\n // Module specifiers imported by the analysed file — used to gate\n // `requiresImport` callers (generic names such as lingui's `t`).\n const fileImportSources = new Set<string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n const importSource = importDeclarationPath.node.source.value;\n fileImportSources.add(importSource);\n\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n continue;\n }\n\n const compatCaller = compatCallers.find(\n (caller) =>\n caller.callerName === importedName &&\n caller.importSources.includes(importSource)\n );\n if (compatCaller) {\n compatCallerLocalNameMap.set(\n importSpecifier.local.name,\n compatCaller\n );\n }\n }\n },\n });\n\n // Method-matched compat callers (e.g. i18next `getFixedT`,\n // `intl.formatMessage`) are recognised by method name on any\n // object. Callers flagged `requiresImport` only participate when\n // the file imports from one of their declared modules.\n const methodCompatCallers = compatCallers.filter(\n (caller) =>\n caller.matchAsMethod &&\n (!caller.requiresImport ||\n caller.importSources.some((source) =>\n fileImportSources.has(source)\n ))\n );\n\n const hasNativeCallers = intlayerCallerLocalNameMap.size > 0;\n const hasCompatCallers =\n compatCallerLocalNameMap.size > 0 ||\n methodCompatCallers.length > 0;\n\n if (!hasNativeCallers && !hasCompatCallers) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n let isMethodCall = false;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n isMethodCall = true;\n }\n\n if (!localCallerName) return;\n\n // Native intlayer caller (useIntlayer / getIntlayer)\n if (intlayerCallerLocalNameMap.has(localCallerName)) {\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const dictionaryKey = readStaticString(\n babelTypes,\n callArguments[0]\n );\n if (!dictionaryKey) return; // dynamic key\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n return;\n }\n\n // Compat namespace caller (imported)\n const importedCompatCaller =\n compatCallerLocalNameMap.get(localCallerName);\n if (importedCompatCaller && !isMethodCall) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n importedCompatCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n return;\n }\n\n // Compat namespace caller (method-matched, e.g. getFixedT)\n if (isMethodCall) {\n const methodCaller = methodCompatCallers.find(\n (caller) => caller.callerName === localCallerName\n );\n if (methodCaller) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n methodCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n }\n }\n },\n JSXOpeningElement: (jsxOpeningElementPath) => {\n const nameNode = jsxOpeningElementPath.node.name;\n if (!babelTypes.isJSXIdentifier(nameNode)) return;\n\n const jsxCaller = compatCallerLocalNameMap.get(nameNode.name);\n if (!jsxCaller?.jsxIdAttribute) return;\n\n // Read the configured id attribute as a static string. A missing\n // or dynamic id yields an empty argument list, which resolves the\n // namespace to undefined and is skipped (consistent with how a\n // dynamic `useIntlayer(key)` call is handled).\n const idNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxIdAttribute\n );\n\n // Optional dedicated namespace attribute, e.g. react-i18next's\n // `<Trans ns=\"home\" i18nKey=\"title\" />`. When configured but\n // absent/dynamic, the dictionary cannot be attributed → skip.\n let namespaceOverride: string | undefined;\n if (jsxCaller.jsxNamespaceAttribute) {\n const namespaceNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxNamespaceAttribute\n );\n if (!namespaceNode) return;\n namespaceOverride = namespaceNode.value;\n }\n\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n idNode ? [idNode] : [],\n jsxCaller,\n isSfcFile,\n currentSourceFilePath,\n undefined,\n namespaceOverride\n );\n },\n });\n },\n },\n },\n };\n };\n"],"mappings":"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"}
|
|
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/** Canonical intlayer caller names that trigger usage analysis. */\nexport const INTLAYER_CALLER_NAMES = ['useIntlayer', 'getIntlayer'] as const;\nexport type IntlayerCallerName = (typeof INTLAYER_CALLER_NAMES)[number];\n\n// ── Compat-adapter namespace callers ──────────────────────────────────────────\n\n// The caller-descriptor model is shared across the build pipeline (babel, swc\n// wire format), the LSP and the compat packages' bundler plugins. It lives in\n// `@intlayer/config/callers`; see that module for the full documentation.\nexport type {\n CallerDescriptor,\n CallerResultShape,\n CallerValueSource,\n} from '@intlayer/config/callers';\n\n/**\n * Configuration entry for a single compat namespace caller.\n *\n * @deprecated Alias kept for backward compatibility — use the shared\n * {@link CallerDescriptor} from `@intlayer/config/callers` instead.\n */\nexport type CompatCallerConfig = CallerDescriptor;\n\n/**\n * Default registry of compat namespace callers.\n *\n * Intentionally empty — compat-specific caller configurations belong in their\n * respective adapter packages (e.g. `@intlayer/react-i18next/plugin`,\n * `@intlayer/vue-i18n/plugin`) and are injected into `makeUsageAnalyzerBabelPlugin`\n * via the `compatCallers` option. Centralising them here would couple the\n * core `@intlayer/babel` package to every compat adapter, which violates the\n * design principle that compat logic lives in compat packages.\n */\nexport const DEFAULT_COMPAT_CALLERS: CompatCallerConfig[] = [];\n\n/** Default namespace used by compat callers when no namespace argument is given. */\nconst DEFAULT_COMPAT_NAMESPACE = 'translation';\n\n/**\n * Records the usage of a specific dictionary key's fields into `pruneContext`.\n * Merges with any previously recorded usage for the same key.\n */\nconst recordFieldUsage = (\n pruneContext: PruneContext,\n dictionaryKey: string,\n fieldUsage: DictionaryFieldUsage\n): void => {\n const existingUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n if (existingUsage === 'all') return; // already saturated\n\n if (fieldUsage === 'all') {\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, 'all');\n return;\n }\n\n if (existingUsage instanceof Set) {\n // Merge in place — the set is owned by the map.\n for (const fieldName of fieldUsage) existingUsage.add(fieldName);\n return;\n }\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(\n dictionaryKey,\n new Set(fieldUsage)\n );\n};\n\n/**\n * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`\n * call expression is consumed, then records the field usage into `pruneContext`.\n *\n * Recognised patterns:\n * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}\n * useIntlayer('key').fieldA → records {fieldA}\n * useIntlayer('key')['fieldA'] → records {fieldA}\n * const { ...rest } = useIntlayer('key') → records 'all' (spread)\n * const result = useIntlayer('key') → records 'all' (untracked binding)\n */\nconst analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n const parentNode = callExpressionPath.parent;\n\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 // ── 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 callExpressionPath,\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 ===\n callExpressionPath.node\n ) {\n let fieldName: string | undefined;\n\n if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) {\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 recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n const memberExprPath = callExpressionPath.parentPath;\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 = callExpressionPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n let hasUntrackedReferenceAccess = false;\n\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 accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n const memberExprPath = variableReferencePath.parentPath;\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\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 hasUntrackedReferenceAccess = true;\n break;\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 accessedTopLevelFieldNames.add(fieldName);\n const memberExprPath = callExprPath?.parentPath;\n if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);\n } else {\n // content()[dynamicKey] – cannot resolve statically\n hasUntrackedReferenceAccess = true;\n break;\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 hasUntrackedReferenceAccess = true;\n break;\n }\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n hasUntrackedReferenceAccess = true;\n break;\n }\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 – conservatively keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);\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// ── Compat namespace-caller analysis ──────────────────────────────────────────\n\n/**\n * Reads a fully-static string from an AST node. Returns `undefined` for\n * dynamic values (identifiers, expressions, template literals with\n * interpolations, …).\n */\nconst readStaticString = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n if (!node) return undefined;\n if (babelTypes.isStringLiteral(node)) return node.value;\n if (\n babelTypes.isTemplateLiteral(node) &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n }\n return undefined;\n};\n\n/** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */\nconst firstPathSegment = (path: string): string => path.split('.')[0] ?? path;\n\n/**\n * Reads the static first dot-path segment from a `t()` first-argument node.\n *\n * t('counter.label') → 'counter'\n * t(`counter.${x}`) → 'counter' (static prefix before the first dot)\n * t(`${x}.label`) → undefined (dynamic first segment)\n * t(someVariable) → undefined\n */\nconst readStaticFirstSegment = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n const staticString = readStaticString(babelTypes, node);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Template literal whose first quasi already contains the dot delimiter, e.g.\n // `counter.${index}` → the leading `counter` segment is statically known.\n if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {\n const firstQuasi =\n node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n if (firstQuasi?.includes('.')) {\n return firstPathSegment(firstQuasi);\n }\n }\n return undefined;\n};\n\n/**\n * Reads a static string property from an object expression. Returns\n * `'__default__'` when the property is absent and `undefined` when present but\n * dynamic.\n */\nconst readObjectProperty = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): string | '__default__' | undefined => {\n for (const property of objectExpression.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n const keyMatches =\n (babelTypes.isIdentifier(property.key) &&\n property.key.name === propertyName) ||\n (babelTypes.isStringLiteral(property.key) &&\n property.key.value === propertyName);\n if (!keyMatches) continue;\n const staticValue = readStaticString(babelTypes, property.value);\n return staticValue ?? undefined; // present but dynamic → undefined\n }\n return '__default__'; // property absent\n};\n\n/**\n * Reads a named JSX attribute as a static string and returns it wrapped in a\n * `StringLiteral` node so the result can be fed to the same namespace resolver\n * as a call argument. Handles both `id=\"home.title\"` and `id={'home.title'}`.\n * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).\n */\nconst readJsxAttributeString = (\n babelTypes: typeof BabelTypes,\n openingElement: BabelTypes.JSXOpeningElement,\n attributeName: string\n): BabelTypes.StringLiteral | undefined => {\n for (const attribute of openingElement.attributes) {\n if (!babelTypes.isJSXAttribute(attribute)) continue;\n if (\n !babelTypes.isJSXIdentifier(attribute.name) ||\n attribute.name.name !== attributeName\n ) {\n continue;\n }\n\n const value = attribute.value;\n // id=\"home.title\"\n if (babelTypes.isStringLiteral(value)) return value;\n // id={'home.title'} / id={`home.title`}\n if (babelTypes.isJSXExpressionContainer(value)) {\n const staticString = readStaticString(babelTypes, value.expression);\n if (staticString !== undefined) {\n return babelTypes.stringLiteral(staticString);\n }\n }\n return undefined; // attribute present but dynamic\n }\n return undefined; // attribute absent\n};\n\n/**\n * Resolves the namespace (dictionary key) for a compat caller call-site from\n * one `CallerValueSource`. Returns the static key, or `'__default__'` when the\n * configured argument is absent (caller falls back to its default namespace),\n * or `undefined` when the value is present but dynamic.\n */\nconst resolveCompatNamespace = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n source: CallerValueSource\n): string | '__default__' | undefined => {\n if (source.from === 'fixed') return source.value;\n\n if (source.from === 'path-first-segment') {\n const firstArg = callArguments[0];\n // No sensible default: the dictionary key is *derived* from the id, so an\n // absent id means the call cannot be attributed to any dictionary → skip.\n if (firstArg === undefined) return undefined;\n\n // String form: formatMessage('home.title', values)\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Descriptor form: formatMessage({ id: 'home.title', ... }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue === '__default__') return '__default__'; // id absent\n if (idValue !== undefined) return firstPathSegment(idValue); // static id\n return undefined; // id present but dynamic\n }\n\n return undefined; // dynamic first argument\n }\n\n if (source.from === 'argument') {\n const argument = callArguments[source.index];\n if (argument === undefined) return '__default__';\n\n // Direct string namespace: useTranslations('about')\n const staticString = readStaticString(babelTypes, argument);\n if (staticString !== undefined) return staticString;\n\n // Object form: getTranslations({ locale, namespace: 'about' })\n if (babelTypes.isObjectExpression(argument)) {\n return readObjectProperty(babelTypes, argument, 'namespace');\n }\n return undefined; // present but dynamic\n }\n\n // from === 'option'\n const optionsArgument = callArguments[source.argumentIndex];\n if (optionsArgument === undefined) return '__default__';\n if (!babelTypes.isObjectExpression(optionsArgument)) return undefined;\n return readObjectProperty(babelTypes, optionsArgument, source.property);\n};\n\n/**\n * Resolves the namespace from a list of `CallerValueSource`s, tried in\n * declaration order. The first source yielding a static string wins.\n * Returns `'__default__'` when every source reports an absent value, and\n * `undefined` when the namespace is present somewhere but dynamic.\n */\nconst resolveNamespaceFromSources = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[]\n): string | '__default__' | undefined => {\n let sawAbsentValue = false;\n\n for (const source of sources) {\n const resolved = resolveCompatNamespace(babelTypes, callArguments, source);\n if (typeof resolved === 'string' && resolved !== '__default__') {\n return resolved;\n }\n if (resolved === '__default__') sawAbsentValue = true;\n }\n\n return sawAbsentValue ? '__default__' : undefined;\n};\n\n/**\n * Resolves an optional `keyPrefix` for a compat caller. Returns the static\n * prefix string, `null` when no prefix is configured/present, or `undefined`\n * when a prefix is present but dynamic.\n */\nconst resolveCompatKeyPrefix = (\n babelTypes: typeof BabelTypes,\n callArguments: BabelTypes.CallExpression['arguments'],\n sources: CallerValueSource[] | undefined\n): string | null | undefined => {\n if (!sources || sources.length === 0) return null;\n const resolved = resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n sources\n );\n if (resolved === '__default__') return null; // prefix absent\n return resolved; // string or undefined (dynamic)\n};\n\n/**\n * Climbs past an enclosing `await` expression so that\n * `const t = await getTranslations('ns')` is resolved to its variable\n * declarator the same way the synchronous form is.\n */\nconst unwrapAwait = (\n babelTypes: typeof BabelTypes,\n path: NodePath<BabelTypes.Node>\n): NodePath<BabelTypes.Node> => {\n const parentPath = path.parentPath;\n if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) {\n return parentPath;\n }\n return path;\n};\n\n/**\n * Analyses how the translation function produced by a compat namespace caller\n * (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,\n * `useI18n`) is consumed, then records the accessed top-level dictionary fields\n * into `pruneContext`.\n *\n * Dictionaries consumed this way are always added to\n * `dictionariesSkippingFieldRename`: the field accesses are string-literal\n * dot-paths inside `t()` calls, which the field-rename plugin cannot rewrite,\n * so renaming the compiled JSON keys would break runtime lookups. Pruning\n * (top-level field removal) remains safe because it preserves field names.\n */\nconst analyzeNamespaceCallerUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callArguments: BabelTypes.CallExpression['arguments'],\n callerConfig: CompatCallerConfig,\n isSfcFile: boolean,\n /** Absolute path of the analysed file (for untracked-binding reporting). */\n currentSourceFilePath: string,\n /**\n * The call-expression path, when the caller was matched as a call. Omitted\n * for JSX-element matches (`<FormattedMessage id>`), where only the static\n * `'self'` / `'all'` analysis paths apply — the binding-based\n * `'destructured-t'` / `'return-value'` paths require a call site.\n */\n callExpressionPath?: NodePath<BabelTypes.CallExpression>,\n /**\n * Pre-resolved namespace bypassing `namespaceSources`, e.g. the `ns`\n * attribute of react-i18next's `<Trans ns=\"home\" i18nKey=\"title\" />`.\n */\n namespaceOverride?: string\n): void => {\n // 1. Resolve the dictionary key (namespace).\n const resolvedNamespace =\n namespaceOverride ??\n resolveNamespaceFromSources(\n babelTypes,\n callArguments,\n callerConfig.namespaceSources\n );\n if (resolvedNamespace === undefined) return; // dynamic key – cannot attribute\n const namespaceString =\n resolvedNamespace === '__default__'\n ? DEFAULT_COMPAT_NAMESPACE\n : resolvedNamespace;\n\n // next-intl scopes nested objects through a dotted namespace\n // (`'about.counter'`): the dictionary key is the first segment and the\n // remainder is an implicit key prefix applied to every t() lookup.\n const namespaceSegments = namespaceString.split('.');\n const dictionaryKey = namespaceSegments[0] ?? namespaceString;\n const namespacePrefix =\n namespaceSegments.length > 1 ? namespaceSegments.slice(1).join('.') : null;\n\n // Compat string-path access is never renamable.\n pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);\n\n // 2. SFC files (Vue / Svelte / Astro): the translation function is typically\n // invoked from the template, which Babel cannot see. Conservatively keep\n // every field to avoid pruning a template-only access.\n if (isSfcFile) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a. Mark the entire dictionary as used — static field analysis is not\n // applicable (e.g. lingui hashed IDs, Angular templates).\n if (callerConfig.translationFunction === 'all') {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3a-bis. The caller returns the dictionary content object itself\n // (`useIntlayer`-like shape): reuse the native member-access /\n // destructuring analysis. Without a call site, keep every field.\n if (callerConfig.translationFunction === 'content') {\n if (callExpressionPath) {\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // 3b. The caller IS the translation call (`translationFunction: 'self'`).\n // The first argument of the current call expression is the message key.\n // Used for lingui's `i18n._('key')` / `i18n.t('key')` and react-intl's\n // `intl.formatMessage({ id: 'key' })` patterns.\n if (callerConfig.translationFunction === 'self') {\n const firstArg = callArguments[0];\n if (!firstArg) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n if (\n callerConfig.namespaceSources.some(\n (source) => source.from === 'path-first-segment'\n )\n ) {\n // The dictionary key is already the first segment of the descriptor id.\n // The field to record is the SECOND segment (first level inside the dict).\n // e.g. formatMessage({ id: 'home.title' }) → dictionaryKey='home', field='title'\n let fullId: string | undefined;\n\n const staticString = readStaticString(babelTypes, firstArg);\n if (staticString !== undefined) {\n fullId = staticString;\n } else if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n fullId = idValue;\n }\n }\n\n if (fullId !== undefined) {\n const segments = fullId.split('.');\n const field = segments[1];\n if (field !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([field]));\n } else {\n // Single-segment id — the whole value is the dict key; no sub-field.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // Dynamic id — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // For 'fixed' / 'argument' / 'option' namespaces: the field is the first\n // dot-segment of the first argument (the message key itself) — unless\n // `flatKey` is set, in which case the whole dotted key is the field.\n // String form: i18n._('home.title', values)\n const segment = callerConfig.flatKey\n ? readStaticString(babelTypes, firstArg)\n : readStaticFirstSegment(babelTypes, firstArg);\n if (segment !== undefined) {\n recordFieldUsage(pruneContext, dictionaryKey, new Set([segment]));\n return;\n }\n\n // Descriptor form: i18n._({ id: 'home.title', message: '...' }, values)\n if (babelTypes.isObjectExpression(firstArg)) {\n const idValue = readObjectProperty(babelTypes, firstArg, 'id');\n if (idValue !== undefined && idValue !== '__default__') {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([callerConfig.flatKey ? idValue : firstPathSegment(idValue)])\n );\n return;\n }\n }\n\n // Dynamic key — cannot prune.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The remaining strategies (`'destructured-t'` / `'return-value'`) resolve\n // the `t` binding from the call site, so they require a call-expression path.\n // JSX-element matches never reach here (they use `'self'` / `'all'`); guard\n // defensively so a misconfigured JSX caller keeps every field instead of\n // throwing.\n if (!callExpressionPath) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 3. Resolve an optional explicit keyPrefix (e.g. react-i18next's\n // `{ keyPrefix }` option). A static prefix fixes the single consumed\n // top-level field regardless of the individual t() paths.\n const explicitKeyPrefix = resolveCompatKeyPrefix(\n babelTypes,\n callArguments,\n callerConfig.keyPrefixSources\n );\n if (explicitKeyPrefix === undefined) {\n // Prefix present but dynamic → unknown field set.\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // The namespace-derived prefix (next-intl) and the explicit keyPrefix option\n // (react-i18next / i18next) never coexist in practice; prefer whichever is\n // present. Either way, the consumed top-level field is the prefix's first\n // segment.\n const effectivePrefix = namespacePrefix ?? explicitKeyPrefix;\n if (effectivePrefix !== null) {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n new Set([firstPathSegment(effectivePrefix)])\n );\n return;\n }\n\n // 4. Locate the `t` function binding.\n let translationBinding: ReturnType<NodePath['scope']['getBinding']> | null =\n null;\n\n if (callerConfig.translationFunction === 'destructured-t') {\n // const { t } = useTranslation('ns')\n const parentNode = callExpressionPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n for (const property of parentNode.id.properties) {\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key) &&\n property.key.name === 't' &&\n babelTypes.isIdentifier(property.value)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(property.value.name) ?? null;\n }\n }\n }\n } else {\n // const t = useTranslations('ns') / const t = await getTranslations('ns')\n const resultPath = unwrapAwait(babelTypes, callExpressionPath);\n const parentNode = resultPath.parent;\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n translationBinding =\n callExpressionPath.scope.getBinding(parentNode.id.name) ?? null;\n }\n }\n\n // Could not statically locate `t` (e.g. result stored whole, re-exported) →\n // conservatively keep all fields.\n if (!translationBinding) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // 5. Inspect every reference to `t`.\n const accessedFields = new Set<string>();\n let hasUntrackedUsage = false;\n\n for (const referencePath of translationBinding.referencePaths) {\n const parentNode = referencePath.parent;\n\n // Must be a direct call: t('path')\n const isDirectCall =\n (babelTypes.isCallExpression(parentNode) ||\n babelTypes.isOptionalCallExpression(parentNode)) &&\n (parentNode as BabelTypes.CallExpression).callee === referencePath.node;\n\n if (!isDirectCall) {\n // t passed as a prop / argument / reassigned → fields unknown.\n hasUntrackedUsage = true;\n break;\n }\n\n const firstArgument = (parentNode as BabelTypes.CallExpression)\n .arguments[0];\n const segment = readStaticFirstSegment(babelTypes, firstArgument);\n if (segment === undefined) {\n hasUntrackedUsage = true;\n break;\n }\n accessedFields.add(segment);\n }\n\n if (hasUntrackedUsage) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n // Only record a finite field set when at least one field was actually\n // accessed. Recording an empty set would prune every field even though the\n // dictionary may be consumed elsewhere.\n if (accessedFields.size > 0) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFields);\n }\n};\n\n/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site —\n * and each configured compat namespace caller — accesses. Results are\n * accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n *\n * @param pruneContext - Shared mutable state written by this plugin.\n * @param options - Optional overrides. `compatCallers` defaults to\n * {@link DEFAULT_COMPAT_CALLERS}; pass `[]` to disable\n * compat-adapter analysis entirely.\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (\n pruneContext: PruneContext,\n options?: { compatCallers?: CompatCallerConfig[] }\n ) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => {\n const compatCallers = options?.compatCallers ?? DEFAULT_COMPAT_CALLERS;\n\n return {\n name: 'intlayer-usage-analyzer',\n visitor: {\n Program: {\n exit: (programPath, state: PluginPass) => {\n const currentSourceFilePath =\n state.file.opts.filename ?? 'unknown file';\n const isSfcFile =\n currentSourceFilePath.endsWith('.vue') ||\n currentSourceFilePath.endsWith('.svelte') ||\n currentSourceFilePath.endsWith('.astro');\n\n // Phase 1: collect local aliases for native intlayer callers and\n // for compat namespace callers (gated by import source).\n const intlayerCallerLocalNameMap = new Map<string, string>();\n const compatCallerLocalNameMap = new Map<\n string,\n CompatCallerConfig\n >();\n\n // Module specifiers imported by the analysed file — used to gate\n // `requiresImport` callers (generic names such as lingui's `t`).\n const fileImportSources = new Set<string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\n const importSource = importDeclarationPath.node.source.value;\n fileImportSources.add(importSource);\n\n for (const importSpecifier of importDeclarationPath.node\n .specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(\n importSpecifier.imported\n )\n ? importSpecifier.imported.name\n : (importSpecifier.imported as BabelTypes.StringLiteral)\n .value;\n\n if (\n INTLAYER_CALLER_NAMES.includes(\n importedName as IntlayerCallerName\n )\n ) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n continue;\n }\n\n const compatCaller = compatCallers.find(\n (caller) =>\n caller.callerName === importedName &&\n caller.importSources.includes(importSource)\n );\n if (compatCaller) {\n compatCallerLocalNameMap.set(\n importSpecifier.local.name,\n compatCaller\n );\n }\n }\n },\n });\n\n // Method-matched compat callers (e.g. i18next `getFixedT`,\n // `intl.formatMessage`) are recognised by method name on any\n // object. Callers flagged `requiresImport` only participate when\n // the file imports from one of their declared modules.\n const methodCompatCallers = compatCallers.filter(\n (caller) =>\n caller.matchAsMethod &&\n (!caller.requiresImport ||\n caller.importSources.some((source) =>\n fileImportSources.has(source)\n ))\n );\n\n const hasNativeCallers = intlayerCallerLocalNameMap.size > 0;\n const hasCompatCallers =\n compatCallerLocalNameMap.size > 0 ||\n methodCompatCallers.length > 0;\n\n if (!hasNativeCallers && !hasCompatCallers) return;\n\n // Phase 2: analyse each call-site\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n let isMethodCall = false;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n isMethodCall = true;\n }\n\n if (!localCallerName) return;\n\n // Native intlayer caller (useIntlayer / getIntlayer)\n if (intlayerCallerLocalNameMap.has(localCallerName)) {\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const dictionaryKey = readStaticString(\n babelTypes,\n callArguments[0]\n );\n if (!dictionaryKey) return; // dynamic key\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n return;\n }\n\n // Compat namespace caller (imported)\n const importedCompatCaller =\n compatCallerLocalNameMap.get(localCallerName);\n if (importedCompatCaller && !isMethodCall) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n importedCompatCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n return;\n }\n\n // Compat namespace caller (method-matched, e.g. getFixedT)\n if (isMethodCall) {\n const methodCaller = methodCompatCallers.find(\n (caller) => caller.callerName === localCallerName\n );\n if (methodCaller) {\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n callExpressionPath.node.arguments,\n methodCaller,\n isSfcFile,\n currentSourceFilePath,\n callExpressionPath\n );\n }\n }\n },\n JSXOpeningElement: (jsxOpeningElementPath) => {\n const nameNode = jsxOpeningElementPath.node.name;\n if (!babelTypes.isJSXIdentifier(nameNode)) return;\n\n const jsxCaller = compatCallerLocalNameMap.get(nameNode.name);\n if (!jsxCaller?.jsxIdAttribute) return;\n\n // Read the configured id attribute as a static string. A missing\n // or dynamic id yields an empty argument list, which resolves the\n // namespace to undefined and is skipped (consistent with how a\n // dynamic `useIntlayer(key)` call is handled).\n const idNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxIdAttribute\n );\n\n // Optional dedicated namespace attribute, e.g. react-i18next's\n // `<Trans ns=\"home\" i18nKey=\"title\" />`. When configured but\n // absent/dynamic, the dictionary cannot be attributed → skip.\n let namespaceOverride: string | undefined;\n if (jsxCaller.jsxNamespaceAttribute) {\n const namespaceNode = readJsxAttributeString(\n babelTypes,\n jsxOpeningElementPath.node,\n jsxCaller.jsxNamespaceAttribute\n );\n if (!namespaceNode) return;\n namespaceOverride = namespaceNode.value;\n }\n\n analyzeNamespaceCallerUsage(\n babelTypes,\n pruneContext,\n idNode ? [idNode] : [],\n jsxCaller,\n isSfcFile,\n currentSourceFilePath,\n undefined,\n namespaceOverride\n );\n },\n });\n },\n },\n },\n };\n };\n"],"mappings":";;AA4IA,MAAa,4BAA0C;CACrD,8CAA8B,IAAI,IAAI;CACtC,2CAA2B,IAAI,IAAI;CACnC,0BAA0B;CAC1B,qDAAqC,IAAI,IAAI;CAC7C,+CAA+B,IAAI,IAAI;CACvC,gDAAgC,IAAI,IAGlC;CACF,iDAAiC,IAAI,IAAI;CACzC,0CAA0B,IAAI,IAAI;AACpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,kCACX,cACA,+BACS;CACT,KAAK,MAAM,CACT,eACA,iBACG,2BAA2B,QAAQ,GAAG;EACzC,aAAa,gCAAgC,IAAI,aAAa;EAE9D,MAAM,oBACJ,aAAa,6BAA6B,IAAI,aAAa;EAI7D,IAAI,iBAAiB,OAAO;GAC1B,aAAa,6BAA6B,IAAI,eAAe,KAAK;GAClE;EACF;EAEA,IAAI,CAAC,mBAAmB;GACtB,IAAI,aAAa,0BAA0B;GAE3C,aAAa,6BAA6B,IACxC,eACA,IAAI,IAAI,YAAY,CACtB;GACA;EACF;EAGA,IAAI,sBAAsB,OAAO;EAEjC,KAAK,MAAM,eAAe,cACxB,kBAAkB,IAAI,WAAW;CAErC;AACF;;AAKA,MAAa,wBAAwB,CAAC,eAAe,aAAa;;;;;;;;;;;AAgClE,MAAa,yBAA+C,CAAC;;AAG7D,MAAM,2BAA2B;;;;;AAMjC,MAAM,oBACJ,cACA,eACA,eACS;CACT,MAAM,gBACJ,aAAa,6BAA6B,IAAI,aAAa;CAE7D,IAAI,kBAAkB,OAAO;CAE7B,IAAI,eAAe,OAAO;EACxB,aAAa,6BAA6B,IAAI,eAAe,KAAK;EAClE;CACF;CAEA,IAAI,yBAAyB,KAAK;EAEhC,KAAK,MAAM,aAAa,YAAY,cAAc,IAAI,SAAS;EAC/D;CACF;CAEA,aAAa,6BAA6B,IACxC,eACA,IAAI,IAAI,UAAU,CACpB;AACF;;;;;;;;;;;;AAaA,MAAM,8BACJ,YACA,cACA,oBACA,eACA,uBACA,cACS;CACT,MAAM,aAAa,mBAAmB;;CAGtC,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;CAGA,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,GACxC;EACA,MAAM,qCAAqB,IAAI,IAAY;EAC3C,IACE,+BACE,WAAW,IACX,oBACA,kBACF,GAEA,iBAAiB,cAAc,eAAe,kBAAkB;OAEhE,iBAAiB,cAAc,eAAe,KAAK;EAErD;CACF;CAGA,KACG,WAAW,mBAAmB,UAAU,KACvC,WAAW,2BAA2B,UAAU,MACjD,WAA2C,WAC1C,mBAAmB,MACrB;EACA,IAAI;EAEJ,IAAI,CAAC,WAAW,YAAY,WAAW,aAAa,WAAW,QAAQ,GACrE,YAAY,WAAW,SAAS;OAC3B,IACL,WAAW,YACX,WAAW,gBAAgB,WAAW,QAAQ,GAE9C,YAAY,WAAW,SAAS;EAGlC,IAAI,WAAW;GACb,iBAAiB,cAAc,+BAAe,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;GAGlE,MAAM,iBAAiB,mBAAmB;GAC1C,IAAI,gBACF,mBAAmB,gBAAgB,CAAC,SAAS,CAAC;EAElD,OACE,qBAAqB;EAEvB;CACF;CAGA,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,aAAa,WAAW,EAAE,GACrC;EACA,MAAM,eAAe,WAAW,GAAG;EACnC,MAAM,kBAAkB,mBAAmB,MAAM,WAAW,YAAY;EAExE,IAAI,CAAC,iBAAiB;GACpB,qBAAqB;GACrB;EACF;EAEA,MAAM,6CAA6B,IAAI,IAAY;EACnD,IAAI,8BAA8B;EAElC,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,2BAA2B,IAAI,SAAS;KAGxC,MAAM,iBAAiB,sBAAsB;KAC7C,IAAI,gBACF,mBAAmB,gBAAgB,CAAC,SAAS,CAAC;IAElD,OAAO;KAEL,8BAA8B;KAC9B;IACF;GACF,OAAO,IAAI,WAAW,kBAAkB,mBAAmB,GAAG;IAO5D,8BAA8B;IAC9B;GACF,OAAO,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,2BAA2B,IAAI,SAAS;MACxC,MAAM,iBAAiB,cAAc;MACrC,IAAI,gBAAgB,mBAAmB,gBAAgB,CAAC,SAAS,CAAC;KACpE,OAAO;MAEL,8BAA8B;MAC9B;KACF;IACF,OAAO,IACL,cACA,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,KACxC,gBACA,+BACE,WAAW,IACX,cACA,0BACF,GACA,CAGF,OAAO;KAEL,8BAA8B;KAC9B;IACF;GACF,OAAO;IAEL,8BAA8B;IAC9B;GACF;EACF;EAEA,IAAI,6BACF,qBAAqB;OAChB,IAAI,WAGT,uBAAuB,YAAY;OAC9B,IAAI,gBAAgB,eAAe,WAAW,GAEnD,qBAAqB;OAErB,iBAAiB,cAAc,eAAe,0BAA0B;EAE1E;CACF;CAGA,IAAI,WAAW,sBAAsB,UAAU,GAC7C;CAIF,qBAAqB;AACvB;;;;;;AASA,MAAM,oBACJ,YACA,SACuB;CACvB,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,WAAW,gBAAgB,IAAI,GAAG,OAAO,KAAK;CAClD,IACE,WAAW,kBAAkB,IAAI,KACjC,KAAK,YAAY,WAAW,KAC5B,KAAK,OAAO,WAAW,GAEvB,OAAO,KAAK,OAAO,EAAE,EAAE,MAAM,UAAU,KAAK,OAAO,EAAE,EAAE,MAAM;AAGjE;;AAGA,MAAM,oBAAoB,SAAyB,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;;;;;;;;;AAUzE,MAAM,0BACJ,YACA,SACuB;CACvB,MAAM,eAAe,iBAAiB,YAAY,IAAI;CACtD,IAAI,iBAAiB,QAAW,OAAO,iBAAiB,YAAY;CAIpE,IAAI,WAAW,kBAAkB,IAAI,KAAK,KAAK,OAAO,SAAS,GAAG;EAChE,MAAM,aACJ,KAAK,OAAO,EAAE,EAAE,MAAM,UAAU,KAAK,OAAO,EAAE,EAAE,MAAM;EACxD,IAAI,YAAY,SAAS,GAAG,GAC1B,OAAO,iBAAiB,UAAU;CAEtC;AAEF;;;;;;AAOA,MAAM,sBACJ,YACA,kBACA,iBACuC;CACvC,KAAK,MAAM,YAAY,iBAAiB,YAAY;EAClD,IAAI,CAAC,WAAW,iBAAiB,QAAQ,GAAG;EAM5C,IAAI,EAJD,WAAW,aAAa,SAAS,GAAG,KACnC,SAAS,IAAI,SAAS,gBACvB,WAAW,gBAAgB,SAAS,GAAG,KACtC,SAAS,IAAI,UAAU,eACV;EAEjB,OADoB,iBAAiB,YAAY,SAAS,KACzC,KAAK;CACxB;CACA,OAAO;AACT;;;;;;;AAQA,MAAM,0BACJ,YACA,gBACA,kBACyC;CACzC,KAAK,MAAM,aAAa,eAAe,YAAY;EACjD,IAAI,CAAC,WAAW,eAAe,SAAS,GAAG;EAC3C,IACE,CAAC,WAAW,gBAAgB,UAAU,IAAI,KAC1C,UAAU,KAAK,SAAS,eAExB;EAGF,MAAM,QAAQ,UAAU;EAExB,IAAI,WAAW,gBAAgB,KAAK,GAAG,OAAO;EAE9C,IAAI,WAAW,yBAAyB,KAAK,GAAG;GAC9C,MAAM,eAAe,iBAAiB,YAAY,MAAM,UAAU;GAClE,IAAI,iBAAiB,QACnB,OAAO,WAAW,cAAc,YAAY;EAEhD;EACA;CACF;AAEF;;;;;;;AAQA,MAAM,0BACJ,YACA,eACA,WACuC;CACvC,IAAI,OAAO,SAAS,SAAS,OAAO,OAAO;CAE3C,IAAI,OAAO,SAAS,sBAAsB;EACxC,MAAM,WAAW,cAAc;EAG/B,IAAI,aAAa,QAAW,OAAO;EAGnC,MAAM,eAAe,iBAAiB,YAAY,QAAQ;EAC1D,IAAI,iBAAiB,QAAW,OAAO,iBAAiB,YAAY;EAGpE,IAAI,WAAW,mBAAmB,QAAQ,GAAG;GAC3C,MAAM,UAAU,mBAAmB,YAAY,UAAU,IAAI;GAC7D,IAAI,YAAY,eAAe,OAAO;GACtC,IAAI,YAAY,QAAW,OAAO,iBAAiB,OAAO;GAC1D;EACF;EAEA;CACF;CAEA,IAAI,OAAO,SAAS,YAAY;EAC9B,MAAM,WAAW,cAAc,OAAO;EACtC,IAAI,aAAa,QAAW,OAAO;EAGnC,MAAM,eAAe,iBAAiB,YAAY,QAAQ;EAC1D,IAAI,iBAAiB,QAAW,OAAO;EAGvC,IAAI,WAAW,mBAAmB,QAAQ,GACxC,OAAO,mBAAmB,YAAY,UAAU,WAAW;EAE7D;CACF;CAGA,MAAM,kBAAkB,cAAc,OAAO;CAC7C,IAAI,oBAAoB,QAAW,OAAO;CAC1C,IAAI,CAAC,WAAW,mBAAmB,eAAe,GAAG,OAAO;CAC5D,OAAO,mBAAmB,YAAY,iBAAiB,OAAO,QAAQ;AACxE;;;;;;;AAQA,MAAM,+BACJ,YACA,eACA,YACuC;CACvC,IAAI,iBAAiB;CAErB,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,uBAAuB,YAAY,eAAe,MAAM;EACzE,IAAI,OAAO,aAAa,YAAY,aAAa,eAC/C,OAAO;EAET,IAAI,aAAa,eAAe,iBAAiB;CACnD;CAEA,OAAO,iBAAiB,gBAAgB;AAC1C;;;;;;AAOA,MAAM,0BACJ,YACA,eACA,YAC8B;CAC9B,IAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,OAAO;CAC7C,MAAM,WAAW,4BACf,YACA,eACA,OACF;CACA,IAAI,aAAa,eAAe,OAAO;CACvC,OAAO;AACT;;;;;;AAOA,MAAM,eACJ,YACA,SAC8B;CAC9B,MAAM,aAAa,KAAK;CACxB,IAAI,cAAc,WAAW,kBAAkB,WAAW,IAAI,GAC5D,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;AAcA,MAAM,+BACJ,YACA,cACA,eACA,cACA,WAEA,uBAOA,oBAKA,sBACS;CAET,MAAM,oBACJ,qBACA,4BACE,YACA,eACA,aAAa,gBACf;CACF,IAAI,sBAAsB,QAAW;CACrC,MAAM,kBACJ,sBAAsB,gBAClB,2BACA;CAKN,MAAM,oBAAoB,gBAAgB,MAAM,GAAG;CACnD,MAAM,gBAAgB,kBAAkB,MAAM;CAC9C,MAAM,kBACJ,kBAAkB,SAAS,IAAI,kBAAkB,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI;CAGxE,aAAa,gCAAgC,IAAI,aAAa;CAK9D,IAAI,WAAW;EACb,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAIA,IAAI,aAAa,wBAAwB,OAAO;EAC9C,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAKA,IAAI,aAAa,wBAAwB,WAAW;EAClD,IAAI,oBACF,2BACE,YACA,cACA,oBACA,eACA,uBACA,SACF;OAEA,iBAAiB,cAAc,eAAe,KAAK;EAErD;CACF;CAMA,IAAI,aAAa,wBAAwB,QAAQ;EAC/C,MAAM,WAAW,cAAc;EAC/B,IAAI,CAAC,UAAU;GACb,iBAAiB,cAAc,eAAe,KAAK;GACnD;EACF;EAEA,IACE,aAAa,iBAAiB,MAC3B,WAAW,OAAO,SAAS,oBAC9B,GACA;GAIA,IAAI;GAEJ,MAAM,eAAe,iBAAiB,YAAY,QAAQ;GAC1D,IAAI,iBAAiB,QACnB,SAAS;QACJ,IAAI,WAAW,mBAAmB,QAAQ,GAAG;IAClD,MAAM,UAAU,mBAAmB,YAAY,UAAU,IAAI;IAC7D,IAAI,YAAY,UAAa,YAAY,eACvC,SAAS;GAEb;GAEA,IAAI,WAAW,QAAW;IAExB,MAAM,QADW,OAAO,MAAM,GACT,CAAC,CAAC;IACvB,IAAI,UAAU,QACZ,iBAAiB,cAAc,+BAAe,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;SAG9D,iBAAiB,cAAc,eAAe,KAAK;IAErD;GACF;GAGA,iBAAiB,cAAc,eAAe,KAAK;GACnD;EACF;EAMA,MAAM,UAAU,aAAa,UACzB,iBAAiB,YAAY,QAAQ,IACrC,uBAAuB,YAAY,QAAQ;EAC/C,IAAI,YAAY,QAAW;GACzB,iBAAiB,cAAc,+BAAe,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;GAChE;EACF;EAGA,IAAI,WAAW,mBAAmB,QAAQ,GAAG;GAC3C,MAAM,UAAU,mBAAmB,YAAY,UAAU,IAAI;GAC7D,IAAI,YAAY,UAAa,YAAY,eAAe;IACtD,iBACE,cACA,+BACA,IAAI,IAAI,CAAC,aAAa,UAAU,UAAU,iBAAiB,OAAO,CAAC,CAAC,CACtE;IACA;GACF;EACF;EAGA,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAOA,IAAI,CAAC,oBAAoB;EACvB,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAKA,MAAM,oBAAoB,uBACxB,YACA,eACA,aAAa,gBACf;CACA,IAAI,sBAAsB,QAAW;EAEnC,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAMA,MAAM,kBAAkB,mBAAmB;CAC3C,IAAI,oBAAoB,MAAM;EAC5B,iBACE,cACA,+BACA,IAAI,IAAI,CAAC,iBAAiB,eAAe,CAAC,CAAC,CAC7C;EACA;CACF;CAGA,IAAI,qBACF;CAEF,IAAI,aAAa,wBAAwB,kBAAkB;EAEzD,MAAM,aAAa,mBAAmB;EACtC,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,gBAAgB,WAAW,EAAE,GAExC;QAAK,MAAM,YAAY,WAAW,GAAG,YACnC,IACE,WAAW,iBAAiB,QAAQ,KACpC,WAAW,aAAa,SAAS,GAAG,KACpC,SAAS,IAAI,SAAS,OACtB,WAAW,aAAa,SAAS,KAAK,GAEtC,qBACE,mBAAmB,MAAM,WAAW,SAAS,MAAM,IAAI,KAAK;EAElE;CAEJ,OAAO;EAGL,MAAM,aADa,YAAY,YAAY,kBACf,CAAC,CAAC;EAC9B,IACE,WAAW,qBAAqB,UAAU,KAC1C,WAAW,aAAa,WAAW,EAAE,GAErC,qBACE,mBAAmB,MAAM,WAAW,WAAW,GAAG,IAAI,KAAK;CAEjE;CAIA,IAAI,CAAC,oBAAoB;EACvB,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAGA,MAAM,iCAAiB,IAAI,IAAY;CACvC,IAAI,oBAAoB;CAExB,KAAK,MAAM,iBAAiB,mBAAmB,gBAAgB;EAC7D,MAAM,aAAa,cAAc;EAQjC,IAAI,GAJD,WAAW,iBAAiB,UAAU,KACrC,WAAW,yBAAyB,UAAU,MAC/C,WAAyC,WAAW,cAAc,OAElD;GAEjB,oBAAoB;GACpB;EACF;EAEA,MAAM,gBAAiB,WACpB,UAAU;EACb,MAAM,UAAU,uBAAuB,YAAY,aAAa;EAChE,IAAI,YAAY,QAAW;GACzB,oBAAoB;GACpB;EACF;EACA,eAAe,IAAI,OAAO;CAC5B;CAEA,IAAI,mBAAmB;EACrB,iBAAiB,cAAc,eAAe,KAAK;EACnD;CACF;CAKA,IAAI,eAAe,OAAO,GACxB,iBAAiB,cAAc,eAAe,cAAc;AAEhE;;;;;;;;;;;;;;;AAgBA,MAAa,gCAET,cACA,aAED,EAAE,OAAO,iBAA6D;CACrE,MAAM,gBAAgB,SAAS,iBAAiB;CAEhD,OAAO;EACL,MAAM;EACN,SAAS,EACP,SAAS,EACP,OAAO,aAAa,UAAsB;GACxC,MAAM,wBACJ,MAAM,KAAK,KAAK,YAAY;GAC9B,MAAM,YACJ,sBAAsB,SAAS,MAAM,KACrC,sBAAsB,SAAS,SAAS,KACxC,sBAAsB,SAAS,QAAQ;GAIzC,MAAM,6CAA6B,IAAI,IAAoB;GAC3D,MAAM,2CAA2B,IAAI,IAGnC;GAIF,MAAM,oCAAoB,IAAI,IAAY;GAE1C,YAAY,SAAS,EACnB,oBAAoB,0BAA0B;IAC5C,MAAM,eAAe,sBAAsB,KAAK,OAAO;IACvD,kBAAkB,IAAI,YAAY;IAElC,KAAK,MAAM,mBAAmB,sBAAsB,KACjD,YAAY;KACb,IAAI,CAAC,WAAW,kBAAkB,eAAe,GAAG;KAEpD,MAAM,eAAe,WAAW,aAC9B,gBAAgB,QAClB,IACI,gBAAgB,SAAS,OACxB,gBAAgB,SACd;KAEP,IACE,sBAAsB,SACpB,YACF,GACA;MACA,2BAA2B,IACzB,gBAAgB,MAAM,MACtB,YACF;MACA;KACF;KAEA,MAAM,eAAe,cAAc,MAChC,WACC,OAAO,eAAe,gBACtB,OAAO,cAAc,SAAS,YAAY,CAC9C;KACA,IAAI,cACF,yBAAyB,IACvB,gBAAgB,MAAM,MACtB,YACF;IAEJ;GACF,EACF,CAAC;GAMD,MAAM,sBAAsB,cAAc,QACvC,WACC,OAAO,kBACN,CAAC,OAAO,kBACP,OAAO,cAAc,MAAM,WACzB,kBAAkB,IAAI,MAAM,CAC9B,EACN;GAEA,MAAM,mBAAmB,2BAA2B,OAAO;GAC3D,MAAM,mBACJ,yBAAyB,OAAO,KAChC,oBAAoB,SAAS;GAE/B,IAAI,CAAC,oBAAoB,CAAC,kBAAkB;GAG5C,YAAY,SAAS;IACnB,iBAAiB,uBAAuB;KACtC,MAAM,aAAa,mBAAmB,KAAK;KAC3C,IAAI;KACJ,IAAI,eAAe;KAEnB,IAAI,WAAW,aAAa,UAAU,GACpC,kBAAkB,WAAW;UACxB,IACL,WAAW,mBAAmB,UAAU,KACxC,WAAW,aAAa,WAAW,QAAQ,GAC3C;MACA,kBAAkB,WAAW,SAAS;MACtC,eAAe;KACjB;KAEA,IAAI,CAAC,iBAAiB;KAGtB,IAAI,2BAA2B,IAAI,eAAe,GAAG;MACnD,MAAM,gBAAgB,mBAAmB,KAAK;MAC9C,IAAI,cAAc,WAAW,GAAG;MAEhC,MAAM,gBAAgB,iBACpB,YACA,cAAc,EAChB;MACA,IAAI,CAAC,eAAe;MAEpB,2BACE,YACA,cACA,oBACA,eACA,uBACA,SACF;MACA;KACF;KAGA,MAAM,uBACJ,yBAAyB,IAAI,eAAe;KAC9C,IAAI,wBAAwB,CAAC,cAAc;MACzC,4BACE,YACA,cACA,mBAAmB,KAAK,WACxB,sBACA,WACA,uBACA,kBACF;MACA;KACF;KAGA,IAAI,cAAc;MAChB,MAAM,eAAe,oBAAoB,MACtC,WAAW,OAAO,eAAe,eACpC;MACA,IAAI,cACF,4BACE,YACA,cACA,mBAAmB,KAAK,WACxB,cACA,WACA,uBACA,kBACF;KAEJ;IACF;IACA,oBAAoB,0BAA0B;KAC5C,MAAM,WAAW,sBAAsB,KAAK;KAC5C,IAAI,CAAC,WAAW,gBAAgB,QAAQ,GAAG;KAE3C,MAAM,YAAY,yBAAyB,IAAI,SAAS,IAAI;KAC5D,IAAI,CAAC,WAAW,gBAAgB;KAMhC,MAAM,SAAS,uBACb,YACA,sBAAsB,MACtB,UAAU,cACZ;KAKA,IAAI;KACJ,IAAI,UAAU,uBAAuB;MACnC,MAAM,gBAAgB,uBACpB,YACA,sBAAsB,MACtB,UAAU,qBACZ;MACA,IAAI,CAAC,eAAe;MACpB,oBAAoB,cAAc;KACpC;KAEA,4BACE,YACA,cACA,SAAS,CAAC,MAAM,IAAI,CAAC,GACrB,WACA,WACA,uBACA,QACA,iBACF;IACF;GACF,CAAC;EACH,EACF,EACF;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babelParser.cjs","names":[],"sources":["../../src/babelParser.ts"],"sourcesContent":["import { parse as babelParse, type ParserOptions } from '@babel/parser';\n\n/**\n * Recast-compatible parsers backed by the `@babel/parser` version owned by\n * `@intlayer/babel`.\n *\n * All Babel operations are centralized in this package so that consumers such\n * as `@intlayer/engine` never need to depend on (or resolve) `@babel/parser`\n * themselves. `@intlayer/engine` loads these parsers lazily, which keeps\n * `@babel/parser` off the hot path until an AST transform is actually run.\n *\n * Recast's own `recast/parsers/typescript` / `recast/parsers/babel-ts` modules\n * resolve `@babel/parser` from the hoisted dependency tree, which can pick up\n * an incompatible major. Exposing the parser here resolves it against this\n * package's own pinned dependency, keeping the `intlayer init` AST transforms\n * working regardless of hoisting.\n *\n * The plugin list mirrors `recast/parsers/_babel_options.js`, adapted to the\n * Babel 8 parser. Only the still-optional plugins are listed here: the many\n * proposals that shipped as language features (e.g. class fields, private\n * methods, static blocks, optional chaining, nullish coalescing, numeric\n * separators, object rest/spread, dynamic import, top-level await, import\n * attributes) are enabled by default in Babel 8 and no longer accepted as\n * explicit plugins. Compared to the Babel 7 list, `decimal` and `recordAndTuple`\n * were removed with their proposals, `importAssertions` became the default\n * import-attributes syntax, and `pipelineOperator` uses the `fsharp` proposal\n * (the `minimal` proposal was removed, and `hack` conflicts with `v8intrinsic`).\n *\n * The goal is still to tolerate as much syntax as possible, since these\n * transforms run on arbitrary user code.\n */\nconst buildBabelOptions = (\n extraPlugins: NonNullable<ParserOptions['plugins']>\n): ParserOptions => ({\n sourceType: 'module',\n strictMode: false,\n allowImportExportEverywhere: true,\n allowReturnOutsideFunction: true,\n startLine: 1,\n tokens: true,\n plugins: [\n 'decorators-legacy',\n 'doExpressions',\n 'exportDefaultFrom',\n 'functionBind',\n 'functionSent',\n 'importMeta',\n ['pipelineOperator', { proposal: 'fsharp' }],\n 'throwExpressions',\n 'v8intrinsic',\n ...extraPlugins,\n ],\n});\n\n/** A parser object accepted by `recast.parse`'s `parser` option. */\nexport type RecastParser = {\n parse: (source: string) => unknown;\n};\n\n/** Drop-in replacement for `recast/parsers/typescript` (TS without JSX). */\nexport const typescriptParser: RecastParser = {\n parse: (source: string) =>\n babelParse(source, buildBabelOptions(['typescript'])),\n};\n\n/** Drop-in replacement for `recast/parsers/babel-ts` (TS *and* JSX). */\nexport const babelTsParser: RecastParser = {\n parse: (source: string) =>\n babelParse(source, buildBabelOptions(['jsx', 'typescript'])),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,qBACJ,kBACmB;CACnB,YAAY;CACZ,YAAY;CACZ,6BAA6B;CAC7B,4BAA4B;CAC5B,WAAW;CACX,QAAQ;CACR,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA,CAAC,oBAAoB,EAAE,UAAU,SAAS,CAAC;EAC3C;EACA;EACA,GAAG;CACL;AACF;;AAQA,MAAa,mBAAiC,EAC5C,QAAQ,
|
|
1
|
+
{"version":3,"file":"babelParser.cjs","names":["babelParse"],"sources":["../../src/babelParser.ts"],"sourcesContent":["import { parse as babelParse, type ParserOptions } from '@babel/parser';\n\n/**\n * Recast-compatible parsers backed by the `@babel/parser` version owned by\n * `@intlayer/babel`.\n *\n * All Babel operations are centralized in this package so that consumers such\n * as `@intlayer/engine` never need to depend on (or resolve) `@babel/parser`\n * themselves. `@intlayer/engine` loads these parsers lazily, which keeps\n * `@babel/parser` off the hot path until an AST transform is actually run.\n *\n * Recast's own `recast/parsers/typescript` / `recast/parsers/babel-ts` modules\n * resolve `@babel/parser` from the hoisted dependency tree, which can pick up\n * an incompatible major. Exposing the parser here resolves it against this\n * package's own pinned dependency, keeping the `intlayer init` AST transforms\n * working regardless of hoisting.\n *\n * The plugin list mirrors `recast/parsers/_babel_options.js`, adapted to the\n * Babel 8 parser. Only the still-optional plugins are listed here: the many\n * proposals that shipped as language features (e.g. class fields, private\n * methods, static blocks, optional chaining, nullish coalescing, numeric\n * separators, object rest/spread, dynamic import, top-level await, import\n * attributes) are enabled by default in Babel 8 and no longer accepted as\n * explicit plugins. Compared to the Babel 7 list, `decimal` and `recordAndTuple`\n * were removed with their proposals, `importAssertions` became the default\n * import-attributes syntax, and `pipelineOperator` uses the `fsharp` proposal\n * (the `minimal` proposal was removed, and `hack` conflicts with `v8intrinsic`).\n *\n * The goal is still to tolerate as much syntax as possible, since these\n * transforms run on arbitrary user code.\n */\nconst buildBabelOptions = (\n extraPlugins: NonNullable<ParserOptions['plugins']>\n): ParserOptions => ({\n sourceType: 'module',\n strictMode: false,\n allowImportExportEverywhere: true,\n allowReturnOutsideFunction: true,\n startLine: 1,\n tokens: true,\n plugins: [\n 'decorators-legacy',\n 'doExpressions',\n 'exportDefaultFrom',\n 'functionBind',\n 'functionSent',\n 'importMeta',\n ['pipelineOperator', { proposal: 'fsharp' }],\n 'throwExpressions',\n 'v8intrinsic',\n ...extraPlugins,\n ],\n});\n\n/** A parser object accepted by `recast.parse`'s `parser` option. */\nexport type RecastParser = {\n parse: (source: string) => unknown;\n};\n\n/** Drop-in replacement for `recast/parsers/typescript` (TS without JSX). */\nexport const typescriptParser: RecastParser = {\n parse: (source: string) =>\n babelParse(source, buildBabelOptions(['typescript'])),\n};\n\n/** Drop-in replacement for `recast/parsers/babel-ts` (TS *and* JSX). */\nexport const babelTsParser: RecastParser = {\n parse: (source: string) =>\n babelParse(source, buildBabelOptions(['jsx', 'typescript'])),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,qBACJ,kBACmB;CACnB,YAAY;CACZ,YAAY;CACZ,6BAA6B;CAC7B,4BAA4B;CAC5B,WAAW;CACX,QAAQ;CACR,SAAS;EACP;EACA;EACA;EACA;EACA;EACA;EACA,CAAC,oBAAoB,EAAE,UAAU,SAAS,CAAC;EAC3C;EACA;EACA,GAAG;CACL;AACF;;AAQA,MAAa,mBAAiC,EAC5C,QAAQ,eACNA,qBAAW,QAAQ,kBAAkB,CAAC,YAAY,CAAC,CAAC,EACxD;;AAGA,MAAa,gBAA8B,EACzC,QAAQ,eACNA,qBAAW,QAAQ,kBAAkB,CAAC,OAAO,YAAY,CAAC,CAAC,EAC/D"}
|