@intlayer/babel 8.7.8-canary.0 → 8.7.9

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.
@@ -79,25 +79,30 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
79
79
  if (babelTypes.isArrayExpression(parentNode)) return;
80
80
  markOpaqueField(fieldName, refPath.node.loc?.start.line);
81
81
  };
82
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
83
- if (parentNode.id.properties.some((prop) => babelTypes.isRestElement(prop))) {
84
- recordFieldUsage(pruneContext, dictionaryKey, "all");
85
- return;
86
- }
87
- const accessedFieldNames = /* @__PURE__ */ new Set();
88
- for (const property of parentNode.id.properties) {
82
+ /**
83
+ * Helper to collect field names from an ObjectPattern (destructuring).
84
+ * Returns true if successful, false if a rest element was found (meaning 'all').
85
+ */
86
+ const collectFieldsFromObjectPattern = (pattern, initPath, targetSet) => {
87
+ if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) return false;
88
+ for (const property of pattern.properties) {
89
89
  let fieldName;
90
90
  if (babelTypes.isObjectProperty(property) && babelTypes.isIdentifier(property.key)) fieldName = property.key.name;
91
91
  else if (babelTypes.isObjectProperty(property) && babelTypes.isStringLiteral(property.key)) fieldName = property.key.value;
92
92
  if (fieldName) {
93
- accessedFieldNames.add(fieldName);
93
+ targetSet.add(fieldName);
94
94
  if (babelTypes.isObjectProperty(property) && babelTypes.isIdentifier(property.value)) {
95
- const variableBinding = callExpressionPath.scope.getBinding(property.value.name);
95
+ const variableBinding = initPath.scope.getBinding(property.value.name);
96
96
  if (variableBinding) for (const refPath of variableBinding.referencePaths) analyzeOpaqueUsage(refPath, fieldName);
97
97
  }
98
98
  }
99
99
  }
100
- recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
100
+ return true;
101
+ };
102
+ if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
103
+ const accessedFieldNames = /* @__PURE__ */ new Set();
104
+ if (collectFieldsFromObjectPattern(parentNode.id, callExpressionPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
105
+ else recordFieldUsage(pruneContext, dictionaryKey, "all");
101
106
  return;
102
107
  }
103
108
  if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === callExpressionPath.node) {
@@ -135,7 +140,27 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
135
140
  hasUntrackedReferenceAccess = true;
136
141
  break;
137
142
  }
138
- } else if (babelTypes.isArrayExpression(referenceParentNode)) {} else {
143
+ } else if (babelTypes.isArrayExpression(referenceParentNode)) {} else if ((babelTypes.isCallExpression(referenceParentNode) || babelTypes.isOptionalCallExpression(referenceParentNode)) && referenceParentNode.callee === variableReferencePath.node) {
144
+ const callExprPath = variableReferencePath.parentPath;
145
+ const callParent = callExprPath?.parent;
146
+ if (callParent && (babelTypes.isMemberExpression(callParent) || babelTypes.isOptionalMemberExpression(callParent)) && callParent.object === callExprPath?.node) {
147
+ const memberExpr = callParent;
148
+ let fieldName;
149
+ if (!memberExpr.computed && babelTypes.isIdentifier(memberExpr.property)) fieldName = memberExpr.property.name;
150
+ else if (memberExpr.computed && babelTypes.isStringLiteral(memberExpr.property)) fieldName = memberExpr.property.value;
151
+ if (fieldName) {
152
+ accessedTopLevelFieldNames.add(fieldName);
153
+ const memberExprPath = callExprPath?.parentPath;
154
+ if (memberExprPath) analyzeOpaqueUsage(memberExprPath, fieldName);
155
+ } else {
156
+ hasUntrackedReferenceAccess = true;
157
+ break;
158
+ }
159
+ } else if (callParent && babelTypes.isVariableDeclarator(callParent) && babelTypes.isObjectPattern(callParent.id) && callExprPath && collectFieldsFromObjectPattern(callParent.id, callExprPath, accessedTopLevelFieldNames)) {} else {
160
+ hasUntrackedReferenceAccess = true;
161
+ break;
162
+ }
163
+ } else {
139
164
  hasUntrackedReferenceAccess = true;
140
165
  break;
141
166
  }
@@ -161,7 +186,7 @@ const makeUsageAnalyzerBabelPlugin = (pruneContext) => ({ types: babelTypes }) =
161
186
  name: "intlayer-usage-analyzer",
162
187
  visitor: { Program: { exit: (programPath, state) => {
163
188
  const currentSourceFilePath = state.file.opts.filename ?? "unknown file";
164
- const isSfcFile = currentSourceFilePath.endsWith(".vue") || currentSourceFilePath.endsWith(".svelte");
189
+ const isSfcFile = currentSourceFilePath.endsWith(".vue") || currentSourceFilePath.endsWith(".svelte") || currentSourceFilePath.endsWith(".astro");
165
190
  const intlayerCallerLocalNameMap = /* @__PURE__ */ new Map();
166
191
  programPath.traverse({ ImportDeclaration: (importDeclarationPath) => {
167
192
  for (const importSpecifier of importDeclarationPath.node.specifiers) {
@@ -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, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\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 * 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 a per-field list of source locations where\n * the field value is consumed \"opaquely\" (passed as-is to a child component\n * or function argument). When a field is opaque AND has nested user-defined\n * structure, its children must not be renamed.\n *\n * Structure: dictionaryKey → fieldName → [\"filePath:line\", …]\n */\n dictionaryKeysWithOpaqueTopLevelFields: Map<string, Map<string, string[]>>;\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 dictionaryKeysWithOpaqueTopLevelFields: new Map<\n string,\n Map<string, string[]>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\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/**\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 const mergedFieldSet =\n existingUsage instanceof Set\n ? new Set([...existingUsage, ...fieldUsage])\n : new Set(fieldUsage);\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, mergedFieldSet);\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 a field value is consumed opaquely (not further destructured). */\n const markOpaqueField = (\n fieldName: string,\n line: number | undefined\n ): void => {\n const fieldToLocations =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey) ??\n new Map<string, string[]>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const locations = fieldToLocations.get(fieldName) ?? [];\n if (!locations.includes(location)) locations.push(location);\n fieldToLocations.set(fieldName, locations);\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.set(\n dictionaryKey,\n fieldToLocations\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 usage of a variable or member access to detect opaque\n * consumption (passing a dictionary field as-is to a prop or function).\n *\n * If a direct, non-chained consumption is found, it calls `markOpaqueField`.\n * Chained accesses (e.g. `field.sub`) are NOT considered opaque for `field`\n * because the renamer can safely track and update them.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldName: string\n ): void => {\n const parentNode = refPath.parent;\n\n // 1. Chained member access (e.g. field.sub or field?.sub)\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n // Chained access is safe: the renamer correctly updates it.\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field)\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n // Destructuring is analogous to member access: safe.\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(fieldName, refPath.node.loc?.start.line);\n };\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ──────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const hasRestElement = parentNode.id.properties.some((prop) =>\n babelTypes.isRestElement(prop)\n );\n\n if (hasRestElement) {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n return;\n }\n\n const accessedFieldNames = new Set<string>();\n for (const property of parentNode.id.properties) {\n let fieldName: string | undefined;\n\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key)\n ) {\n fieldName = property.key.name;\n } else if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isStringLiteral(property.key)\n ) {\n fieldName = property.key.value;\n }\n\n if (fieldName) {\n accessedFieldNames.add(fieldName);\n\n // Check usage of the bound variable to look for opaque consumption\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.value)\n ) {\n const variableBinding = callExpressionPath.scope.getBinding(\n property.value.name\n );\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, fieldName);\n }\n }\n }\n }\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\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 // Ignore array literals (e.g. [content]) – uncommon but benign\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/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site\n * accesses. Results are accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\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\n // Phase 1: collect local aliases for useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\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 }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) 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\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 }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return; // dynamic key – cannot resolve which dictionary\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\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, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\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 * 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 a per-field list of source locations where\n * the field value is consumed \"opaquely\" (passed as-is to a child component\n * or function argument). When a field is opaque AND has nested user-defined\n * structure, its children must not be renamed.\n *\n * Structure: dictionaryKey → fieldName → [\"filePath:line\", …]\n */\n dictionaryKeysWithOpaqueTopLevelFields: Map<string, Map<string, string[]>>;\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 dictionaryKeysWithOpaqueTopLevelFields: new Map<\n string,\n Map<string, string[]>\n >(),\n dictionariesSkippingFieldRename: new Set(),\n pendingFrameworkAnalysis: new Map(),\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/**\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 const mergedFieldSet =\n existingUsage instanceof Set\n ? new Set([...existingUsage, ...fieldUsage])\n : new Set(fieldUsage);\n\n pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, mergedFieldSet);\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 a field value is consumed opaquely (not further destructured). */\n const markOpaqueField = (\n fieldName: string,\n line: number | undefined\n ): void => {\n const fieldToLocations =\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.get(dictionaryKey) ??\n new Map<string, string[]>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const locations = fieldToLocations.get(fieldName) ?? [];\n if (!locations.includes(location)) locations.push(location);\n fieldToLocations.set(fieldName, locations);\n pruneContext.dictionaryKeysWithOpaqueTopLevelFields.set(\n dictionaryKey,\n fieldToLocations\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 usage of a variable or member access to detect opaque\n * consumption (passing a dictionary field as-is to a prop or function).\n *\n * If a direct, non-chained consumption is found, it calls `markOpaqueField`.\n * Chained accesses (e.g. `field.sub`) are NOT considered opaque for `field`\n * because the renamer can safely track and update them.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldName: string\n ): void => {\n const parentNode = refPath.parent;\n\n // 1. Chained member access (e.g. field.sub or field?.sub)\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n // Chained access is safe: the renamer correctly updates it.\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field)\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n // Destructuring is analogous to member access: safe.\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(fieldName, refPath.node.loc?.start.line);\n };\n\n /**\n * Helper to collect field names from an ObjectPattern (destructuring).\n * Returns true if successful, false if a rest element was found (meaning '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 let fieldName: string | undefined;\n\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.key)\n ) {\n fieldName = property.key.name;\n } else if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isStringLiteral(property.key)\n ) {\n fieldName = property.key.value;\n }\n\n if (fieldName) {\n targetSet.add(fieldName);\n\n if (\n babelTypes.isObjectProperty(property) &&\n babelTypes.isIdentifier(property.value)\n ) {\n const variableBinding = initPath.scope.getBinding(\n property.value.name\n );\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, fieldName);\n }\n }\n }\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 // Ignore array literals (e.g. [content]) – uncommon but benign\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/**\n * Creates a Babel plugin that traverses source files and records which\n * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site\n * accesses. Results are accumulated into `pruneContext`.\n *\n * This plugin is analysis-only: it does not transform the code (`code: false`\n * should be passed to `transformAsync` when using it).\n */\nexport const makeUsageAnalyzerBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObj => ({\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 useIntlayer / getIntlayer\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n programPath.traverse({\n ImportDeclaration: (importDeclarationPath) => {\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 }\n }\n },\n });\n\n if (intlayerCallerLocalNameMap.size === 0) 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\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 }\n\n if (\n !localCallerName ||\n !intlayerCallerLocalNameMap.has(localCallerName)\n )\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return; // dynamic key – cannot resolve which dictionary\n\n analyzeCallExpressionUsage(\n babelTypes,\n pruneContext,\n callExpressionPath,\n dictionaryKey,\n currentSourceFilePath,\n isSfcFile\n );\n },\n });\n },\n },\n },\n });\n"],"mappings":"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"}
@@ -154,11 +154,23 @@ const extractBabelContentForComponents = (ast, fileCode, existingKeys, defaultKe
154
154
  componentKeyMap.set(path.node, globalFileKey);
155
155
  hookMap.set(path.node, "getIntlayer");
156
156
  } else {
157
- if (!globalFileKey) {
158
- globalFileKey = require_extractContent_utils_resolveDictionaryKey.resolveDictionaryKey(defaultKey, filePath, configuration, unmergedDictionaries, usedKeysInFile);
159
- usedKeysInFile.add(globalFileKey);
157
+ let inheritedKey;
158
+ let parent = path.parentPath;
159
+ while (parent) {
160
+ if (componentKeyMap.has(parent.node)) {
161
+ inheritedKey = componentKeyMap.get(parent.node);
162
+ break;
163
+ }
164
+ parent = parent.parentPath;
160
165
  }
161
- componentKeyMap.set(path.node, globalFileKey);
166
+ if (!inheritedKey) {
167
+ if (!globalFileKey) {
168
+ globalFileKey = require_extractContent_utils_resolveDictionaryKey.resolveDictionaryKey(defaultKey, filePath, configuration, unmergedDictionaries, usedKeysInFile);
169
+ usedKeysInFile.add(globalFileKey);
170
+ }
171
+ inheritedKey = globalFileKey;
172
+ }
173
+ componentKeyMap.set(path.node, inheritedKey);
162
174
  const compName = require_extractContent_utils_getComponentName.getComponentName(path);
163
175
  const isComponent = compName ? /^[A-Z]/.test(compName) : false;
164
176
  const isHook = compName ? /^use[A-Z]/.test(compName) : false;
@@ -1 +1 @@
1
- {"version":3,"file":"babelProcessor.cjs","names":["_traverse","t","shouldExtract","getOrGenerateKey","getExistingIntlayerInfo","resolveDictionaryKey","getComponentName","ATTRIBUTES_TO_EXTRACT"],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined'\n | 'template-literal';\n componentKey: string;\n childrenToReplace?: t.Node[];\n variables?: string[];\n};\n\n// CJS/ESM interop: @babel/traverse exports its function as `.default` in CJS bundles\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Handles JSX insertions (elements with multiple children, including expressions).\n * Replaces complex JSX structures with variable-based translations.\n */\nexport const handleJsxInsertionBabel = (\n path: NodePath<t.JSXElement | t.JSXFragment>,\n fileCode: string,\n existingKeys: Set<string>,\n getComponentKeyForPath: (path: NodePath) => string,\n extractedContent: Record<string, Record<string, string>>,\n replacements: BabelReplacement[],\n handledNodes: Set<t.Node>\n): boolean => {\n const children = path.node.children;\n\n if (children.length <= 1) return false;\n\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr?: string;\n }[] = [];\n let hasSignificantText = false;\n let hasVariables = false;\n\n for (const child of children) {\n if (t.isJSXText(child)) {\n const text = child.value;\n\n if (text.trim().length > 0) hasSignificantText = true;\n\n parts.push({ type: 'text', value: text });\n } else if (t.isJSXExpressionContainer(child)) {\n if (t.isJSXEmptyExpression(child.expression)) {\n parts.push({ type: 'text', value: '' });\n } else {\n const expr = child.expression;\n\n if (t.isIdentifier(expr)) {\n parts.push({\n type: 'var',\n value: expr.name,\n originalExpr: expr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n\n parts.push({ type: 'var', value: varName, originalExpr: code });\n\n hasVariables = true;\n } else if (t.isTemplateLiteral(expr)) {\n for (let i = 0; i < expr.quasis.length; i++) {\n parts.push({ type: 'text', value: expr.quasis[i].value.raw });\n if (i < expr.expressions.length) {\n const subExpr = expr.expressions[i];\n if (t.isIdentifier(subExpr)) {\n parts.push({\n type: 'var',\n value: subExpr.name,\n originalExpr: subExpr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(subExpr)) {\n const code = fileCode.substring(subExpr.start!, subExpr.end!);\n const varName = t.isIdentifier(subExpr.property)\n ? subExpr.property.name\n : 'var';\n parts.push({ type: 'var', value: varName, originalExpr: code });\n hasVariables = true;\n } else {\n return false;\n }\n }\n }\n } else {\n return false;\n }\n }\n } else {\n return false;\n }\n }\n\n if (!hasSignificantText) return false;\n\n let combinedString = '';\n for (const part of parts) {\n if (part.type === 'var') combinedString += `{{${part.value}}}`;\n else combinedString += part.value;\n }\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract(cleanString)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const varMap = parts\n .filter((part) => part.type === 'var')\n .map((part) => `${part.value}: ${part.originalExpr}`);\n const uniqueVars = Array.from(new Set(varMap));\n\n if (hasVariables) {\n replacements.push({\n path,\n key,\n type: 'jsx-insertion',\n componentKey,\n childrenToReplace: children,\n variables: uniqueVars,\n });\n } else {\n replacements.push({\n path,\n key,\n type: 'jsx-text-combined',\n componentKey,\n childrenToReplace: children,\n });\n }\n\n children.forEach((child) => {\n handledNodes.add(child);\n });\n return true;\n }\n\n return false;\n};\n\n/**\n * Traverses the AST to identify components and extract content.\n * Returns extraction results and metadata about which components need Intlayer hooks.\n */\nexport const extractBabelContentForComponents = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n defaultKey: string = 'default',\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, Record<string, string>>;\n replacements: BabelReplacement[];\n componentsNeedingHooks: Set<NodePath>;\n componentKeyMap: Map<t.Node, string>;\n componentPaths: NodePath[];\n hookMap: Map<t.Node, 'useIntlayer' | 'getIntlayer'>;\n isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n Program(path) {\n componentPaths.push(path);\n },\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (path.isProgram()) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n const isHook = compName ? /^use[A-Z]/.test(compName) : false;\n hookMap.set(\n path.node,\n isComponent || isHook ? 'useIntlayer' : 'getIntlayer'\n );\n }\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n TemplateLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const { quasis, expressions } = path.node;\n\n // Build the combined string with placeholders\n let combinedString = '';\n const variables: string[] = [];\n let hasSignificantText = false;\n\n for (let i = 0; i < quasis.length; i++) {\n const text = quasis[i].value.raw;\n combinedString += text;\n if (text.trim().length > 0) hasSignificantText = true;\n\n if (i < expressions.length) {\n const expr = expressions[i];\n if (t.isIdentifier(expr)) {\n combinedString += `{{${expr.name}}}`;\n variables.push(`${expr.name}: ${expr.name}`);\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n combinedString += `{{${varName}}}`;\n variables.push(`${varName}: ${code}`);\n } else {\n // Complex expression in template literal, skip\n return;\n }\n }\n }\n\n if (!hasSignificantText) return;\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (!shouldExtract(cleanString)) return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const uniqueVars = Array.from(new Set(variables));\n\n replacements.push({\n path,\n key,\n type: 'template-literal',\n componentKey,\n variables: uniqueVars,\n });\n },\n });\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n const hasDirectReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) {\n return true;\n }\n const isOtherComponent = componentPaths.some(\n (p) => p !== componentPath && p.node === current?.node\n );\n if (isOtherComponent) {\n return false;\n }\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasDirectReplacements) {\n componentsNeedingHooks.add(componentPath);\n }\n continue;\n }\n\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath && !ancestorPath.isProgram()) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"babelProcessor.cjs","names":["_traverse","t","shouldExtract","getOrGenerateKey","getExistingIntlayerInfo","resolveDictionaryKey","getComponentName","ATTRIBUTES_TO_EXTRACT"],"sources":["../../../src/extractContent/babelProcessor.ts"],"sourcesContent":["import _traverse, { type NodePath } from '@babel/traverse';\nimport * as t from '@babel/types';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { resolveDictionaryKey } from '../extractContent/utils';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n getComponentName,\n getExistingIntlayerInfo,\n getOrGenerateKey,\n shouldExtract,\n} from './utils';\n\nexport type BabelReplacement = {\n path: NodePath;\n key: string;\n type:\n | 'jsx-text'\n | 'jsx-attribute'\n | 'string-literal'\n | 'jsx-insertion'\n | 'jsx-text-combined'\n | 'template-literal';\n componentKey: string;\n childrenToReplace?: t.Node[];\n variables?: string[];\n};\n\n// CJS/ESM interop: @babel/traverse exports its function as `.default` in CJS bundles\nconst traverse = (\n typeof _traverse === 'function' ? _traverse : (_traverse as any).default\n) as typeof _traverse;\n\n/**\n * Handles JSX insertions (elements with multiple children, including expressions).\n * Replaces complex JSX structures with variable-based translations.\n */\nexport const handleJsxInsertionBabel = (\n path: NodePath<t.JSXElement | t.JSXFragment>,\n fileCode: string,\n existingKeys: Set<string>,\n getComponentKeyForPath: (path: NodePath) => string,\n extractedContent: Record<string, Record<string, string>>,\n replacements: BabelReplacement[],\n handledNodes: Set<t.Node>\n): boolean => {\n const children = path.node.children;\n\n if (children.length <= 1) return false;\n\n const parts: {\n type: 'text' | 'var';\n value: string;\n originalExpr?: string;\n }[] = [];\n let hasSignificantText = false;\n let hasVariables = false;\n\n for (const child of children) {\n if (t.isJSXText(child)) {\n const text = child.value;\n\n if (text.trim().length > 0) hasSignificantText = true;\n\n parts.push({ type: 'text', value: text });\n } else if (t.isJSXExpressionContainer(child)) {\n if (t.isJSXEmptyExpression(child.expression)) {\n parts.push({ type: 'text', value: '' });\n } else {\n const expr = child.expression;\n\n if (t.isIdentifier(expr)) {\n parts.push({\n type: 'var',\n value: expr.name,\n originalExpr: expr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n\n parts.push({ type: 'var', value: varName, originalExpr: code });\n\n hasVariables = true;\n } else if (t.isTemplateLiteral(expr)) {\n for (let i = 0; i < expr.quasis.length; i++) {\n parts.push({ type: 'text', value: expr.quasis[i].value.raw });\n if (i < expr.expressions.length) {\n const subExpr = expr.expressions[i];\n if (t.isIdentifier(subExpr)) {\n parts.push({\n type: 'var',\n value: subExpr.name,\n originalExpr: subExpr.name,\n });\n hasVariables = true;\n } else if (t.isMemberExpression(subExpr)) {\n const code = fileCode.substring(subExpr.start!, subExpr.end!);\n const varName = t.isIdentifier(subExpr.property)\n ? subExpr.property.name\n : 'var';\n parts.push({ type: 'var', value: varName, originalExpr: code });\n hasVariables = true;\n } else {\n return false;\n }\n }\n }\n } else {\n return false;\n }\n }\n } else {\n return false;\n }\n }\n\n if (!hasSignificantText) return false;\n\n let combinedString = '';\n for (const part of parts) {\n if (part.type === 'var') combinedString += `{{${part.value}}}`;\n else combinedString += part.value;\n }\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (shouldExtract(cleanString)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const varMap = parts\n .filter((part) => part.type === 'var')\n .map((part) => `${part.value}: ${part.originalExpr}`);\n const uniqueVars = Array.from(new Set(varMap));\n\n if (hasVariables) {\n replacements.push({\n path,\n key,\n type: 'jsx-insertion',\n componentKey,\n childrenToReplace: children,\n variables: uniqueVars,\n });\n } else {\n replacements.push({\n path,\n key,\n type: 'jsx-text-combined',\n componentKey,\n childrenToReplace: children,\n });\n }\n\n children.forEach((child) => {\n handledNodes.add(child);\n });\n return true;\n }\n\n return false;\n};\n\n/**\n * Traverses the AST to identify components and extract content.\n * Returns extraction results and metadata about which components need Intlayer hooks.\n */\nexport const extractBabelContentForComponents = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n defaultKey: string = 'default',\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, Record<string, string>>;\n replacements: BabelReplacement[];\n componentsNeedingHooks: Set<NodePath>;\n componentKeyMap: Map<t.Node, string>;\n componentPaths: NodePath[];\n hookMap: Map<t.Node, 'useIntlayer' | 'getIntlayer'>;\n isSolid: boolean;\n} => {\n const extractedContent: Record<string, Record<string, string>> = {};\n const replacements: BabelReplacement[] = [];\n const handledNodes = new Set<t.Node>();\n const componentKeyMap = new Map<t.Node, string>();\n const hookMap = new Map<t.Node, 'useIntlayer' | 'getIntlayer'>();\n const usedKeysInFile = new Set<string>();\n let globalFileKey: string | undefined;\n\n const componentPaths: NodePath[] = [];\n\n traverse(ast, {\n Program(path) {\n componentPaths.push(path);\n },\n FunctionDeclaration(path) {\n componentPaths.push(path);\n },\n ArrowFunctionExpression(path) {\n componentPaths.push(path);\n },\n FunctionExpression(path) {\n componentPaths.push(path);\n },\n });\n\n for (const path of componentPaths) {\n const existingInfo = getExistingIntlayerInfo(path);\n\n if (existingInfo) {\n componentKeyMap.set(path.node, existingInfo.key);\n usedKeysInFile.add(existingInfo.key);\n hookMap.set(path.node, existingInfo.hook);\n } else {\n if (path.isProgram()) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n componentKeyMap.set(path.node, globalFileKey);\n hookMap.set(path.node, 'getIntlayer');\n } else {\n let inheritedKey: string | undefined;\n let parent: NodePath | null = path.parentPath;\n while (parent) {\n if (componentKeyMap.has(parent.node)) {\n inheritedKey = componentKeyMap.get(parent.node);\n break;\n }\n parent = parent.parentPath;\n }\n\n if (!inheritedKey) {\n if (!globalFileKey) {\n globalFileKey = resolveDictionaryKey(\n defaultKey,\n filePath,\n configuration,\n unmergedDictionaries,\n usedKeysInFile\n );\n usedKeysInFile.add(globalFileKey);\n }\n inheritedKey = globalFileKey;\n }\n\n componentKeyMap.set(path.node, inheritedKey);\n\n const compName = getComponentName(path);\n const isComponent = compName ? /^[A-Z]/.test(compName) : false;\n const isHook = compName ? /^use[A-Z]/.test(compName) : false;\n hookMap.set(\n path.node,\n isComponent || isHook ? 'useIntlayer' : 'getIntlayer'\n );\n }\n }\n }\n\n const getComponentKeyForPath = (path: NodePath): string => {\n let current: NodePath | null = path;\n while (current) {\n if (componentKeyMap.has(current.node)) {\n return componentKeyMap.get(current.node)!;\n }\n current = current.parentPath;\n }\n return globalFileKey || defaultKey;\n };\n\n traverse(ast, {\n JSXElement(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXFragment(path) {\n if (handledNodes.has(path.node)) return;\n\n handleJsxInsertionBabel(\n path,\n fileCode,\n existingKeys,\n getComponentKeyForPath,\n extractedContent,\n replacements,\n handledNodes\n );\n },\n JSXText(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (shouldExtract(text)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.replace(/\\s+/g, ' ').trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-text', componentKey });\n }\n },\n JSXAttribute(path) {\n if (handledNodes.has(path.node)) return;\n\n const name = path.node.name.name;\n\n if (\n typeof name !== 'string' ||\n !ATTRIBUTES_TO_EXTRACT.includes(name as any)\n )\n return;\n const value = path.node.value;\n\n if (t.isStringLiteral(value) && shouldExtract(value.value)) {\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n value.value.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'jsx-attribute', componentKey });\n }\n },\n StringLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const text = path.node.value;\n\n if (!shouldExtract(text)) return;\n\n const parent = path.parentPath;\n\n if (\n parent.isImportDeclaration() ||\n parent.isImportSpecifier() ||\n parent.isExportDeclaration()\n )\n return;\n\n if (parent.isJSXAttribute()) return;\n\n if (\n parent.isCallExpression() &&\n t.isMemberExpression(parent.node.callee)\n ) {\n if (\n t.isIdentifier(parent.node.callee.object) &&\n parent.node.callee.object.name === 'console' &&\n t.isIdentifier(parent.node.callee.property) &&\n parent.node.callee.property.name === 'log'\n ) {\n return;\n }\n }\n\n if (parent.isObjectProperty() && parent.node.key === path.node) return;\n\n if (parent.isMemberExpression() && parent.node.property === path.node)\n return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n text.trim(),\n componentKey,\n existingKeys,\n extractedContent\n );\n replacements.push({ path, key, type: 'string-literal', componentKey });\n },\n TemplateLiteral(path) {\n if (handledNodes.has(path.node)) return;\n\n const { quasis, expressions } = path.node;\n\n // Build the combined string with placeholders\n let combinedString = '';\n const variables: string[] = [];\n let hasSignificantText = false;\n\n for (let i = 0; i < quasis.length; i++) {\n const text = quasis[i].value.raw;\n combinedString += text;\n if (text.trim().length > 0) hasSignificantText = true;\n\n if (i < expressions.length) {\n const expr = expressions[i];\n if (t.isIdentifier(expr)) {\n combinedString += `{{${expr.name}}}`;\n variables.push(`${expr.name}: ${expr.name}`);\n } else if (t.isMemberExpression(expr)) {\n const code = fileCode.substring(expr.start!, expr.end!);\n const varName = t.isIdentifier(expr.property)\n ? expr.property.name\n : 'var';\n combinedString += `{{${varName}}}`;\n variables.push(`${varName}: ${code}`);\n } else {\n // Complex expression in template literal, skip\n return;\n }\n }\n }\n\n if (!hasSignificantText) return;\n\n const cleanString = combinedString.replace(/\\s+/g, ' ').trim();\n\n if (!shouldExtract(cleanString)) return;\n\n const componentKey = getComponentKeyForPath(path);\n const key = getOrGenerateKey(\n cleanString,\n componentKey,\n existingKeys,\n extractedContent\n );\n\n const uniqueVars = Array.from(new Set(variables));\n\n replacements.push({\n path,\n key,\n type: 'template-literal',\n componentKey,\n variables: uniqueVars,\n });\n },\n });\n\n const componentsNeedingHooks = new Set<NodePath>();\n for (const componentPath of componentPaths) {\n if (componentPath.isProgram()) {\n const hasDirectReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) {\n return true;\n }\n const isOtherComponent = componentPaths.some(\n (p) => p !== componentPath && p.node === current?.node\n );\n if (isOtherComponent) {\n return false;\n }\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasDirectReplacements) {\n componentsNeedingHooks.add(componentPath);\n }\n continue;\n }\n\n const hasReplacements = replacements.some((replacement) => {\n let current: NodePath | null = replacement.path;\n while (current) {\n if (current.node === componentPath.node) return true;\n\n current = current.parentPath;\n }\n return false;\n });\n\n if (hasReplacements) {\n const key = componentKeyMap.get(componentPath.node)!;\n let ancestorProvidesKey = false;\n let currentPath: NodePath | null = componentPath.parentPath;\n while (currentPath) {\n const ancestorPath = componentPaths.find(\n (path) => path.node === currentPath?.node\n );\n\n if (ancestorPath && !ancestorPath.isProgram()) {\n const ancestorKey = componentKeyMap.get(ancestorPath.node);\n\n if (ancestorKey === key) {\n const ancestorHasReplacements = replacements.some((replacement) => {\n let rPath: NodePath | null = replacement.path;\n while (rPath) {\n if (rPath.node === ancestorPath.node) return true;\n\n rPath = rPath.parentPath;\n }\n return false;\n });\n const existingInfo = getExistingIntlayerInfo(ancestorPath);\n\n if (ancestorHasReplacements || existingInfo) {\n ancestorProvidesKey = true;\n break;\n }\n }\n }\n currentPath = currentPath.parentPath;\n }\n\n if (!ancestorProvidesKey) {\n componentsNeedingHooks.add(componentPath);\n }\n }\n }\n\n return {\n extractedContent,\n replacements,\n componentsNeedingHooks,\n componentKeyMap,\n componentPaths,\n hookMap,\n isSolid: false,\n };\n};\n\n/**\n * High-level function to extract content from TS/JS/JSX/TSX AST.\n */\nexport const extractTsContent = (\n ast: t.File,\n fileCode: string,\n existingKeys: Set<string>,\n configuration: IntlayerConfig,\n filePath: string,\n unmergedDictionaries: Record<string, unknown> = {}\n): {\n extractedContent: Record<string, string>;\n replacements: BabelReplacement[];\n} => {\n const { extractedContent, replacements } = extractBabelContentForComponents(\n ast,\n fileCode,\n existingKeys,\n 'default',\n configuration,\n filePath,\n unmergedDictionaries\n );\n\n const flatContent: Record<string, string> = {};\n for (const group of Object.values(extractedContent)) {\n Object.assign(flatContent, group);\n }\n\n return { extractedContent: flatContent, replacements };\n};\n"],"mappings":"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"}
@@ -42,14 +42,48 @@ const processFileInternal = (filePath, packageName, options, dependencies, saveC
42
42
  };
43
43
  if (ext === ".vue") {
44
44
  if (!vueCompiler) throw new Error(`Please install ${(0, _intlayer_config_logger.colorizePath)("@intlayer/vue-compiler", _intlayer_config_colors.YELLOW)} to process Vue files.`);
45
- const res = vueCompiler.processVueFile(filePath, componentKey, packageName, compilerCommonOptions, saveComponent);
45
+ const res = vueCompiler.processVueFile(filePath, componentKey, packageName, compilerCommonOptions, saveComponent, fileText);
46
46
  if (res) return formatCompilerResult(componentKey, res);
47
47
  }
48
48
  if (ext === ".svelte") {
49
49
  if (!svelteCompiler) throw new Error(`Please install ${(0, _intlayer_config_logger.colorizePath)("@intlayer/svelte-compiler", _intlayer_config_colors.YELLOW)} to process Svelte files.`);
50
- const res = svelteCompiler.processSvelteFile(filePath, componentKey, packageName, compilerCommonOptions, saveComponent);
50
+ const res = svelteCompiler.processSvelteFile(filePath, componentKey, packageName, compilerCommonOptions, saveComponent, fileText);
51
51
  if (res) return formatCompilerResult(componentKey, res);
52
52
  }
53
+ if (ext === ".astro") {
54
+ const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(fileText);
55
+ let fakeTsxCode = fileText;
56
+ let hasFrontmatter = false;
57
+ if (frontmatterMatch) {
58
+ hasFrontmatter = true;
59
+ const matchStart = frontmatterMatch.index;
60
+ const matchEnd = matchStart + frontmatterMatch[0].length;
61
+ const before = fileText.substring(0, matchStart);
62
+ const matchText = frontmatterMatch[0];
63
+ const after = fileText.substring(matchEnd);
64
+ fakeTsxCode = before + matchText.replace(/^---/, "///").replace(/---$/, "///") + after;
65
+ } else fakeTsxCode = `///\n;\n///\n${fileText}`;
66
+ const result = require_extractContent_processTsxFile.processTsxFile(filePath, componentKey, packageName, configuration, false, unmergedDictionaries, fakeTsxCode);
67
+ if (!result) return void 0;
68
+ let modifiedFullCode = result.modifiedCode.replace(/^\s*\/\/\//, "---").replace(/\n\/\/\//, "\n---");
69
+ if (!hasFrontmatter) modifiedFullCode = modifiedFullCode.replace(/\n;\n---\n/, "\n---\n");
70
+ if (saveComponent) {
71
+ (0, node_fs.writeFileSync)(filePath, modifiedFullCode);
72
+ const formatCommand = (0, _intlayer_chokidar_cli.detectFormatCommand)(configuration);
73
+ if (formatCommand) try {
74
+ (0, node_child_process.execSync)(formatCommand.replace("{{file}}", filePath), {
75
+ stdio: "inherit",
76
+ cwd: dependencies.configuration.system.baseDir
77
+ });
78
+ } catch (error) {
79
+ console.error(error);
80
+ }
81
+ }
82
+ return {
83
+ extractedContentMap: result.extractedContent,
84
+ transformedCode: modifiedFullCode
85
+ };
86
+ }
53
87
  if ([
54
88
  ".tsx",
55
89
  ".jsx",
@@ -1 +1 @@
1
- {"version":3,"file":"extractContent.cjs","names":["extractDictionaryKey","ATTRIBUTES_TO_EXTRACT","extractTsContent","ANSIColors","processTsxFile","writeContentHelper"],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"extractContent.cjs","names":["extractDictionaryKey","ATTRIBUTES_TO_EXTRACT","extractTsContent","ANSIColors","processTsxFile","writeContentHelper"],"sources":["../../../src/extractContent/extractContent.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { extname, relative } from 'node:path';\nimport type * as t from '@babel/types';\nimport { detectFormatCommand } from '@intlayer/chokidar/cli';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport { extractTsContent } from './babelProcessor';\nimport { writeContentHelper } from './contentWriter';\nimport { processTsxFile } from './processTsxFile';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n type PackageName,\n shouldExtract,\n} from './utils';\nimport { extractDictionaryKey } from './utils/extractDictionaryKey';\nimport { generateKey } from './utils/generateKey';\n\nexport type ExtractIntlayerOptions = {\n configOptions?: GetConfigurationOptions;\n codeOnly?: boolean;\n declarationOnly?: boolean;\n unmergedDictionaries?: Record<string, unknown>;\n configuration?: IntlayerConfig;\n code?: string;\n onExtract?: (result: {\n key: string;\n content: Record<string, string>;\n filePath: string;\n }) => void | Promise<void>;\n};\n\ntype ExternalCompilerResult = {\n extractedContent: Record<string, string>;\n code: string;\n};\n\ntype ExternalCompilerOptions = {\n generateKey: typeof generateKey;\n shouldExtract: typeof shouldExtract;\n attributesToExtract: typeof ATTRIBUTES_TO_EXTRACT;\n extractDictionaryKeyFromPath: typeof extractDictionaryKeyFromPath;\n extractTsContent: (\n ast: unknown,\n code: string,\n keys: Set<string>,\n config: IntlayerConfig,\n path: string\n ) => ReturnType<typeof extractTsContent>;\n};\n\ntype VueCompiler = typeof import('@intlayer/vue-compiler');\ntype SvelteCompiler = typeof import('@intlayer/svelte-compiler');\n\n// Module caches\nlet vueCompiler: VueCompiler | null = null;\nlet svelteCompiler: SvelteCompiler | null = null;\n\ntype InternalExtractResult = {\n extractedContentMap: Record<string, Record<string, string>> | null;\n transformedCode: string | null;\n};\n\nconst formatCompilerResult = (\n componentKey: string,\n res?: ExternalCompilerResult\n): InternalExtractResult | undefined => {\n if (!res) return undefined;\n\n return {\n extractedContentMap: { [componentKey]: res.extractedContent },\n transformedCode: res.code,\n };\n};\n\ntype Dependencies = {\n vueCompiler: VueCompiler | null;\n svelteCompiler: SvelteCompiler | null;\n unmergedDictionaries: Record<string, unknown>;\n configuration: IntlayerConfig;\n};\n\nconst processFileInternal = (\n filePath: string,\n packageName: PackageName,\n options: ExtractIntlayerOptions | undefined,\n dependencies: Dependencies,\n saveComponent: boolean,\n providedComponentKey?: string\n): InternalExtractResult | undefined => {\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const componentKey =\n providedComponentKey ??\n extractDictionaryKey(\n filePath,\n fileText,\n dependencies.configuration.compiler.dictionaryKeyPrefix\n );\n const ext = extname(filePath);\n\n const { vueCompiler, svelteCompiler, unmergedDictionaries, configuration } =\n dependencies;\n\n const compilerCommonOptions: ExternalCompilerOptions = {\n generateKey,\n shouldExtract,\n attributesToExtract: ATTRIBUTES_TO_EXTRACT,\n extractDictionaryKeyFromPath,\n extractTsContent: (ast, code, keys, config, path) =>\n extractTsContent(\n ast as t.File,\n code,\n keys,\n config,\n path,\n unmergedDictionaries\n ),\n };\n\n if (ext === '.vue') {\n if (!vueCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n\n const res = vueCompiler.processVueFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent,\n fileText\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.svelte') {\n if (!svelteCompiler) {\n throw new Error(\n `Please install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n\n const res = svelteCompiler.processSvelteFile(\n filePath,\n componentKey,\n packageName,\n compilerCommonOptions,\n saveComponent,\n fileText\n );\n\n if (res) {\n return formatCompilerResult(componentKey, res);\n }\n }\n\n if (ext === '.astro') {\n const frontmatterMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(fileText);\n let fakeTsxCode = fileText;\n let hasFrontmatter = false;\n\n if (frontmatterMatch) {\n hasFrontmatter = true;\n const matchStart = frontmatterMatch.index;\n const matchEnd = matchStart + frontmatterMatch[0].length;\n\n const before = fileText.substring(0, matchStart);\n const matchText = frontmatterMatch[0];\n const after = fileText.substring(matchEnd);\n\n const newMatchText = matchText\n .replace(/^---/, '///')\n .replace(/---$/, '///');\n fakeTsxCode = before + newMatchText + after;\n } else {\n // Add empty frontmatter with a dummy statement so imports get injected properly\n fakeTsxCode = `///\\n;\\n///\\n${fileText}`;\n }\n\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n false, // don't let processTsxFile write — we reconstruct the full file below\n unmergedDictionaries,\n fakeTsxCode\n );\n\n if (!result) return undefined;\n\n let modifiedFullCode = result.modifiedCode\n .replace(/^\\s*\\/\\/\\//, '---')\n .replace(/\\n\\/\\/\\//, '\\n---');\n\n if (!hasFrontmatter) {\n modifiedFullCode = modifiedFullCode.replace(/\\n;\\n---\\n/, '\\n---\\n');\n }\n\n if (saveComponent) {\n writeFileSync(filePath, modifiedFullCode);\n\n const formatCommand = detectFormatCommand(configuration);\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: dependencies.configuration.system.baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: modifiedFullCode,\n };\n }\n\n if (['.tsx', '.jsx', '.ts', '.js', '.cjs', '.mjs'].includes(ext)) {\n const result = processTsxFile(\n filePath,\n componentKey,\n packageName,\n configuration,\n saveComponent,\n unmergedDictionaries,\n fileText\n );\n\n if (result) {\n return {\n extractedContentMap: result.extractedContent,\n transformedCode: result.modifiedCode,\n };\n }\n }\n\n return undefined;\n};\n\nconst handleExtractionSideEffects = async (\n extractedContentMap: Record<string, Record<string, string>>,\n filePath: string,\n options: ExtractIntlayerOptions | undefined,\n {\n configuration,\n baseDir,\n appLogger,\n }: {\n configuration: IntlayerConfig;\n baseDir: string;\n appLogger: ReturnType<typeof getAppLogger>;\n },\n saveComponent: boolean\n) => {\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n await options.onExtract({ key, content, filePath });\n }\n }\n\n const writeContent = !options?.codeOnly && !options?.onExtract;\n\n if (writeContent) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n const contentFilePath = await writeContentHelper(\n content,\n key,\n filePath,\n configuration\n );\n\n const relativeContentFilePath = relative(baseDir, contentFilePath);\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Created content file: ${colorizePath(relativeContentFilePath)}`\n );\n }\n }\n\n if (saveComponent) {\n const formatCommand = detectFormatCommand(configuration);\n\n if (formatCommand) {\n try {\n execSync(formatCommand.replace('{{file}}', filePath), {\n stdio: 'inherit',\n cwd: baseDir,\n });\n } catch (error) {\n console.error(error);\n }\n }\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated component: ${colorizePath(relative(baseDir, filePath))}`\n );\n }\n};\n\ntype ExtractResult = {\n transformedCode: string | null;\n extractedContentMap: Record<string, Record<string, string>>;\n};\n\ntype ExtractContext = {\n configuration: IntlayerConfig;\n appLogger: ReturnType<typeof getAppLogger>;\n baseDir: string;\n unmergedDictionaries: Record<string, unknown>;\n saveComponent: boolean;\n componentExtension: string;\n};\n\nconst buildContext = (\n filePath: string,\n options: ExtractIntlayerOptions | undefined\n): ExtractContext => {\n const configuration =\n options?.configuration ?? getConfiguration(options?.configOptions);\n const appLogger = getAppLogger(configuration);\n const { baseDir } = configuration.system;\n const unmergedDictionaries =\n options?.unmergedDictionaries ?? getUnmergedDictionaries(configuration);\n const saveComponent = !options?.declarationOnly;\n const componentExtension = extname(filePath);\n\n return {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n };\n};\n\nexport const extractContent = async (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): Promise<ExtractResult | undefined> => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = await import('@intlayer/vue-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = await import('@intlayer/svelte-compiler');\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const fileText = options?.code ?? readFileSync(filePath, 'utf-8');\n const dictionaryKey = extractDictionaryKey(\n filePath,\n fileText,\n configuration.compiler.dictionaryKeyPrefix\n );\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent,\n dictionaryKey\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n await handleExtractionSideEffects(\n result.extractedContentMap,\n filePath,\n options,\n { configuration, baseDir, appLogger },\n saveComponent\n );\n\n return {\n transformedCode: result.transformedCode,\n extractedContentMap: result.extractedContentMap,\n };\n};\n\n/**\n * Synchronous variant of `extractContent` — used by the Babel plugin which\n * runs in a sync transform context (e.g. Next.js / Webpack).\n *\n * Differences from `extractContent`:\n * - Loads external compilers (Vue, Svelte) via `require()` instead of `import()`\n * - Fires `onExtract` callbacks as fire-and-forget (cannot await in sync context)\n * - Does NOT write dictionary files or run the code formatter; callers that\n * need persistence should supply an `onExtract` callback\n */\nexport const extractContentSync = (\n filePath: string,\n packageName: PackageName,\n options?: ExtractIntlayerOptions\n): ExtractResult | undefined => {\n const {\n configuration,\n appLogger,\n baseDir,\n unmergedDictionaries,\n saveComponent,\n componentExtension,\n } = buildContext(filePath, options);\n\n const requireFn = getProjectRequire();\n\n if (componentExtension === '.vue' && !vueCompiler) {\n try {\n vueCompiler = requireFn('@intlayer/vue-compiler') as VueCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/vue-compiler', ANSIColors.YELLOW)} to process Vue files.`\n );\n }\n }\n\n if (componentExtension === '.svelte' && !svelteCompiler) {\n try {\n svelteCompiler = requireFn('@intlayer/svelte-compiler') as SvelteCompiler;\n } catch {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Install ${colorizePath('@intlayer/svelte-compiler', ANSIColors.YELLOW)} to process Svelte files.`\n );\n }\n }\n\n const result = processFileInternal(\n filePath,\n packageName,\n options,\n { vueCompiler, svelteCompiler, unmergedDictionaries, configuration },\n saveComponent\n );\n\n if (!result?.extractedContentMap) {\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} No extractable text found in ${colorizePath(relative(baseDir, filePath))}`,\n { isVerbose: true }\n );\n return undefined;\n }\n\n const { extractedContentMap, transformedCode } = result;\n\n if (options?.onExtract) {\n for (const [key, content] of Object.entries(extractedContentMap)) {\n void options.onExtract({ key, content, filePath });\n }\n }\n\n return { transformedCode, extractedContentMap };\n};\n"],"mappings":"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"}
@@ -40,7 +40,12 @@ const processTsxFile = (filePath, componentKey, packageName, configuration, save
40
40
  const getExistingInfoForPath = (path) => {
41
41
  let current = path;
42
42
  while (current) {
43
- if (componentNodeToPath.has(current.node)) return existingInfoCache.get(current.node);
43
+ if (componentNodeToPath.has(current.node)) {
44
+ const componentPath = componentNodeToPath.get(current.node);
45
+ const existingInfo = existingInfoCache.get(current.node);
46
+ if (existingInfo) return existingInfo;
47
+ if (componentsNeedingHooks.has(componentPath)) return;
48
+ }
44
49
  current = current.parentPath;
45
50
  }
46
51
  };
@@ -57,9 +62,32 @@ const processTsxFile = (filePath, componentKey, packageName, configuration, save
57
62
  }
58
63
  return "useIntlayer";
59
64
  };
65
+ const generatedVarNames = /* @__PURE__ */ new Map();
66
+ for (const componentPath of componentsNeedingHooks) {
67
+ let varName = "content";
68
+ let counter = 1;
69
+ while (componentPath.scope.hasBinding(varName)) {
70
+ varName = `content${counter}`;
71
+ counter++;
72
+ }
73
+ generatedVarNames.set(componentPath, varName);
74
+ }
75
+ const getProvidingVarName = (path) => {
76
+ let current = path;
77
+ while (current) {
78
+ const componentPath = componentNodeToPath.get(current.node);
79
+ if (componentPath) {
80
+ const existingInfo = existingInfoCache.get(componentPath.node);
81
+ if (existingInfo) return existingInfo.variableName ?? "content";
82
+ if (componentsNeedingHooks.has(componentPath)) return generatedVarNames.get(componentPath) || "content";
83
+ }
84
+ current = current.parentPath;
85
+ }
86
+ return "content";
87
+ };
60
88
  for (const { path, key, type, variables, childrenToReplace } of replacements) {
61
89
  const existingInfo = getExistingInfoForPath(path);
62
- const varName = existingInfo?.variableName ?? "content";
90
+ const varName = existingInfo?.variableName ?? getProvidingVarName(path);
63
91
  const contentAccessCode = existingInfo?.isDestructured ? key : isSolid ? `${varName}().${key}` : `${varName}.${key}`;
64
92
  const valueSuffix = getProvidingHookType(path) === "getIntlayer" ? "" : ".value";
65
93
  if (type === "jsx-text" && path.isJSXText()) textEdits.push({
@@ -151,11 +179,12 @@ const processTsxFile = (filePath, componentKey, packageName, configuration, save
151
179
  const hook = hookMap.get(componentPath.node) || "useIntlayer";
152
180
  if (hook === "useIntlayer") needsUseIntlayer = true;
153
181
  if (hook === "getIntlayer") needsGetIntlayer = true;
154
- const hookStatementStr = `\n const content = ${hook}('${finalKey}');\n`;
182
+ const hookStatementStr = `\n const ${generatedVarNames.get(componentPath) || "content"} = ${hook}('${finalKey}');\n`;
155
183
  if (componentPath.isProgram()) {
156
184
  let insertPos = 0;
157
185
  if (componentPath.node.directives.length > 0) insertPos = componentPath.node.directives[componentPath.node.directives.length - 1].end;
158
186
  for (const stmt of componentPath.node.body) if (_babel_types.isImportDeclaration(stmt)) insertPos = Math.max(insertPos, stmt.end);
187
+ if (insertPos === 0 && componentPath.node.body.length > 0) insertPos = componentPath.node.body[0].start;
159
188
  textEdits.push({
160
189
  start: insertPos,
161
190
  end: insertPos,