@intlayer/babel 9.5.10 → 9.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cjs/analyzeContentUsage.cjs +54 -26
  2. package/dist/cjs/analyzeContentUsage.cjs.map +1 -1
  3. package/dist/cjs/babel-plugin-intlayer-extract.cjs +11 -1
  4. package/dist/cjs/babel-plugin-intlayer-extract.cjs.map +1 -1
  5. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +39 -18
  6. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
  7. package/dist/cjs/babel-plugin-intlayer-optimize.cjs +11 -1
  8. package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
  9. package/dist/cjs/babel-plugin-intlayer-purge.cjs +2 -0
  10. package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -1
  11. package/dist/cjs/getExtractPluginOptions.cjs.map +1 -1
  12. package/dist/cjs/staticAstReaders.cjs +96 -0
  13. package/dist/cjs/staticAstReaders.cjs.map +1 -1
  14. package/dist/cjs/transformers.cjs +36 -7
  15. package/dist/cjs/transformers.cjs.map +1 -1
  16. package/dist/esm/analyzeContentUsage.mjs +55 -27
  17. package/dist/esm/analyzeContentUsage.mjs.map +1 -1
  18. package/dist/esm/babel-plugin-intlayer-extract.mjs +11 -1
  19. package/dist/esm/babel-plugin-intlayer-extract.mjs.map +1 -1
  20. package/dist/esm/babel-plugin-intlayer-field-rename.mjs +40 -19
  21. package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
  22. package/dist/esm/babel-plugin-intlayer-optimize.mjs +11 -1
  23. package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
  24. package/dist/esm/babel-plugin-intlayer-purge.mjs +2 -0
  25. package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -1
  26. package/dist/esm/getExtractPluginOptions.mjs +1 -1
  27. package/dist/esm/getExtractPluginOptions.mjs.map +1 -1
  28. package/dist/esm/staticAstReaders.mjs +94 -1
  29. package/dist/esm/staticAstReaders.mjs.map +1 -1
  30. package/dist/esm/transformers.mjs +36 -7
  31. package/dist/esm/transformers.mjs.map +1 -1
  32. package/dist/types/analyzeContentUsage.d.ts +6 -1
  33. package/dist/types/analyzeContentUsage.d.ts.map +1 -1
  34. package/dist/types/babel-plugin-intlayer-extract.d.ts +8 -22
  35. package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
  36. package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
  37. package/dist/types/babel-plugin-intlayer-optimize.d.ts +8 -112
  38. package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
  39. package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -1
  40. package/dist/types/staticAstReaders.d.ts +41 -0
  41. package/dist/types/staticAstReaders.d.ts.map +1 -1
  42. package/dist/types/transformers.d.ts +8 -1
  43. package/dist/types/transformers.d.ts.map +1 -1
  44. package/package.json +8 -8
@@ -13,7 +13,12 @@ const require_pruneContext = require('./pruneContext.cjs');
13
13
  * useIntlayer('key').fieldA → records {fieldA}
14
14
  * useIntlayer('key')['fieldA'] → records {fieldA}
15
15
  * const { ...rest } = useIntlayer('key') → records 'all' (spread)
16
- * const result = useIntlayer('key') → records 'all' (untracked binding)
16
+ * const result = useIntlayer('key') → records the fields read on `result`
17
+ * const [a, { b }] = await Promise.all([getIntlayerAsync('x'), …])
18
+ * → each element tracked like a declarator
19
+ *
20
+ * TypeScript casts (`as`, `satisfies`, `!`) are looked through, and test-only
21
+ * uses (`!field`, `field && …`) expose no key names.
17
22
  */
18
23
  const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile) => {
19
24
  /** Mark the dictionary key as having an untracked binding in this file. */
@@ -56,7 +61,9 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
56
61
  * escapes static tracking, the deepest reached field path is marked opaque
57
62
  * so the children of that value keep their original key names.
58
63
  */
59
- const analyzeOpaqueUsage = (refPath, fieldPath) => {
64
+ const analyzeOpaqueUsage = (referencePath, fieldPath) => {
65
+ const refPath = require_staticAstReaders.skipTransparentWrappers(babelTypes, referencePath);
66
+ if (require_staticAstReaders.isTestOnlyUse(babelTypes, refPath)) return;
60
67
  const parentNode = refPath.parent;
61
68
  const parentPath = refPath.parentPath;
62
69
  if (parentPath && (babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === refPath.node) {
@@ -80,6 +87,15 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
80
87
  analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);
81
88
  return;
82
89
  }
90
+ if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id) && parentNode.init === refPath.node) {
91
+ const aliasBinding = refPath.scope.getBinding(parentNode.id.name);
92
+ if (isSfcFile || !aliasBinding?.constant) {
93
+ markOpaqueField(fieldPath, refPath.node.loc?.start.line);
94
+ return;
95
+ }
96
+ for (const aliasReferencePath of aliasBinding.referencePaths) analyzeOpaqueUsage(aliasReferencePath, fieldPath);
97
+ return;
98
+ }
83
99
  if (babelTypes.isArrayExpression(parentNode)) return;
84
100
  markOpaqueField(fieldPath, refPath.node.loc?.start.line);
85
101
  };
@@ -201,7 +217,9 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
201
217
  * dictionary must be kept.
202
218
  */
203
219
  const analyzeContentBindingReferences = (variableBinding, accessedTopLevelFieldNames) => {
204
- for (const variableReferencePath of variableBinding.referencePaths) {
220
+ for (const bindingReferencePath of variableBinding.referencePaths) {
221
+ const variableReferencePath = require_staticAstReaders.skipTransparentWrappers(babelTypes, bindingReferencePath);
222
+ if (require_staticAstReaders.isTestOnlyUse(babelTypes, variableReferencePath)) continue;
205
223
  const referenceParentNode = variableReferencePath.parent;
206
224
  if ((babelTypes.isMemberExpression(referenceParentNode) || babelTypes.isOptionalMemberExpression(referenceParentNode)) && referenceParentNode.object === variableReferencePath.node) {
207
225
  const memberExpressionNode = referenceParentNode;
@@ -245,20 +263,44 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
245
263
  return false;
246
264
  };
247
265
  /**
248
- * Analyses how the content root referenced by `rootPath` is consumed.
266
+ * Records the fields read through `variableName`, the local binding that
267
+ * receives the content root at `rootPath`.
268
+ */
269
+ const analyzeContentVariable = (rootPath, variableName) => {
270
+ const variableBinding = rootPath.scope.getBinding(variableName);
271
+ if (!variableBinding) {
272
+ markUntrackedBinding();
273
+ return;
274
+ }
275
+ const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
276
+ if (analyzeContentBindingReferences(variableBinding, accessedTopLevelFieldNames)) markUntrackedBinding();
277
+ else if (isSfcFile) deferFrameworkAnalysis(variableName);
278
+ else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
279
+ else require_pruneContext.recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
280
+ };
281
+ /**
282
+ * Analyses how the content root produced by `expressionPath` is consumed.
249
283
  *
250
- * `rootPath` is the `useIntlayer('key')` call itself, or any expression that
284
+ * `expressionPath` is the `useIntlayer('key')` call itself, or any expression that
251
285
  * evaluates back to the same content object — currently the result of a
252
286
  * chainable runtime helper such as `.onChange(…)`.
253
287
  */
254
- const analyzeContentRoot = (rootPath) => {
255
- const parentNode = rootPath.parent;
256
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
257
- const accessedFieldNames = /* @__PURE__ */ new Set();
258
- if (collectFieldsFromObjectPattern(parentNode.id, rootPath, accessedFieldNames)) require_pruneContext.recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
259
- else require_pruneContext.recordFieldUsage(pruneContext, dictionaryKey, "all");
288
+ const analyzeContentRoot = (expressionPath) => {
289
+ const resolution = require_staticAstReaders.resolveContentRoot(babelTypes, expressionPath);
290
+ if (resolution.kind === "discarded") return;
291
+ const { rootPath } = resolution;
292
+ if (resolution.kind === "binding") {
293
+ const { bindingTarget } = resolution;
294
+ if (babelTypes.isObjectPattern(bindingTarget)) {
295
+ const accessedFieldNames = /* @__PURE__ */ new Set();
296
+ if (collectFieldsFromObjectPattern(bindingTarget, rootPath, accessedFieldNames)) require_pruneContext.recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
297
+ else require_pruneContext.recordFieldUsage(pruneContext, dictionaryKey, "all");
298
+ return;
299
+ }
300
+ analyzeContentVariable(rootPath, bindingTarget.name);
260
301
  return;
261
302
  }
303
+ const parentNode = rootPath.parent;
262
304
  if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === rootPath.node) {
263
305
  let fieldName;
264
306
  if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) fieldName = parentNode.property.name;
@@ -276,24 +318,10 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
276
318
  } else markUntrackedBinding();
277
319
  return;
278
320
  }
279
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
280
- const variableName = parentNode.id.name;
281
- const variableBinding = rootPath.scope.getBinding(variableName);
282
- if (!variableBinding) {
283
- markUntrackedBinding();
284
- return;
285
- }
286
- const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
287
- if (analyzeContentBindingReferences(variableBinding, accessedTopLevelFieldNames)) markUntrackedBinding();
288
- else if (isSfcFile) deferFrameworkAnalysis(variableName);
289
- else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
290
- else require_pruneContext.recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
291
- return;
292
- }
293
321
  if (babelTypes.isExpressionStatement(parentNode)) return;
294
322
  markUntrackedBinding();
295
323
  };
296
- analyzeContentRoot(require_staticAstReaders.unwrapAwait(babelTypes, callExpressionPath));
324
+ analyzeContentRoot(callExpressionPath);
297
325
  };
298
326
 
299
327
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"analyzeContentUsage.cjs","names":["CHAINABLE_RUNTIME_METHOD_NAMES","unwrapAwait"],"sources":["../../src/analyzeContentUsage.ts"],"sourcesContent":["import type { NodePath } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { CHAINABLE_RUNTIME_METHOD_NAMES } from './nativeCallers';\nimport {\n type OpaqueFieldOccurrence,\n type PruneContext,\n recordFieldUsage,\n} from './pruneContext';\nimport { unwrapAwait } from './staticAstReaders';\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 */\nexport const analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that the content value at `fieldPath` is consumed opaquely. */\n const markOpaqueField = (\n fieldPath: string[],\n line: number | undefined\n ): void => {\n const pathToOccurrence =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey) ??\n new Map<string, OpaqueFieldOccurrence>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const joinedFieldPath = fieldPath.join('.');\n const occurrence = pathToOccurrence.get(joinedFieldPath) ?? {\n fieldPath: [...fieldPath],\n locations: [],\n };\n if (!occurrence.locations.includes(location)) {\n occurrence.locations.push(location);\n }\n pathToOccurrence.set(joinedFieldPath, occurrence);\n pruneContext.dictionaryKeysWithOpaqueFields.set(\n dictionaryKey,\n pathToOccurrence\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n /**\n * Analyses how the content value at `fieldPath` (currently referenced by\n * `refPath`) is consumed, to detect opaque consumption (passing a dictionary\n * value as-is to a prop or function argument).\n *\n * Member-access chains and destructuring are followed recursively with an\n * extended field path, so the *terminal* consumption of a chain like\n * `content.sections.hero` decides opacity — mirroring how far the\n * source-code renamer can rewrite accesses. When the terminal consumption\n * escapes static tracking, the deepest reached field path is marked opaque\n * so the children of that value keep their original key names.\n */\n const analyzeOpaqueUsage = (\n refPath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n const parentNode = refPath.parent;\n const parentPath = refPath.parentPath;\n\n // 1. Chained member access (e.g. field.sub or field?.sub): follow the\n // chain with an extended path and analyse the terminal consumption.\n if (\n parentPath &&\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …) is transparent: the JSON renamers\n // apply the same rename map to every array element.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, fieldPath);\n return;\n }\n\n if (\n !memberNode.computed &&\n babelTypes.isIdentifier(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.name,\n ]);\n return;\n }\n\n if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.value,\n ]);\n return;\n }\n\n // Dynamic computed access (field[expr]): the renamer cannot rewrite the\n // key, so the children of the current path must keep their names.\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field): follow each binding.\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);\n return;\n }\n\n // 3. Ignored patterns (e.g. array literals [content])\n if (babelTypes.isArrayExpression(parentNode)) {\n return;\n }\n\n // 4. Opaque consumption (passed to prop, function, etc.)\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n };\n\n /**\n * Analyses a destructuring pattern applied to the content value at\n * `fieldPath`, recursively following each bound local variable's references\n * via {@link analyzeOpaqueUsage}.\n *\n * Patterns the source-code renamer cannot rewrite (rest elements, dynamic\n * computed keys, array patterns) mark the corresponding path opaque.\n */\n const analyzeOpaqueDestructuring = (\n objectPattern: BabelTypes.ObjectPattern,\n scopePath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n for (const property of objectPattern.properties) {\n // A rest element re-exposes the remaining fields under their original\n // names — nothing at this level may be renamed.\n if (babelTypes.isRestElement(property)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be renamed.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const childFieldPath = [...fieldPath, keyName];\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isObjectPattern(bindingTarget)) {\n analyzeOpaqueDestructuring(bindingTarget, scopePath, childFieldPath);\n continue;\n }\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);\n if (!localVarBinding) continue;\n for (const referencePath of localVarBinding.referencePaths) {\n analyzeOpaqueUsage(referencePath, childFieldPath);\n }\n continue;\n }\n\n // Array patterns and other targets escape the rename walk.\n markOpaqueField(childFieldPath, property.loc?.start.line);\n }\n };\n\n /**\n * Helper to collect field names from an ObjectPattern (destructuring).\n * Returns true if successful, false when the accessed fields cannot be\n * determined statically (rest element or dynamic computed key → 'all').\n */\n const collectFieldsFromObjectPattern = (\n pattern: BabelTypes.ObjectPattern,\n initPath: NodePath<BabelTypes.Node>,\n targetSet: Set<string>\n ): boolean => {\n if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) {\n return false;\n }\n\n for (const property of pattern.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be attributed to a\n // specific field — the whole dictionary must be kept.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n return false;\n }\n\n let fieldName: string | undefined;\n\n if (!property.computed && babelTypes.isIdentifier(property.key)) {\n fieldName = property.key.name;\n } else if (babelTypes.isStringLiteral(property.key)) {\n fieldName = property.key.value;\n }\n\n if (!fieldName) continue;\n\n targetSet.add(fieldName);\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const variableBinding = initPath.scope.getBinding(bindingTarget.name);\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, [fieldName]);\n }\n }\n } else if (babelTypes.isObjectPattern(bindingTarget)) {\n // Nested pattern: const { fieldA: { nested } } = … — follow each\n // nested binding so deeper opaque consumption is detected.\n analyzeOpaqueDestructuring(bindingTarget, initPath, [fieldName]);\n } else {\n // Array patterns and other targets escape the rename walk.\n markOpaqueField([fieldName], property.loc?.start.line);\n }\n }\n return true;\n };\n\n /**\n * Returns the call path when `memberExprPath` is a chainable runtime helper\n * being invoked (`content.onChange(cb)`), otherwise `undefined`.\n *\n * See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`\n * reference that is never called is treated as an ordinary field access, so\n * only the invoked form is looked through.\n */\n const getChainedRuntimeCallPath = (\n memberExprPath: NodePath<BabelTypes.Node> | null | undefined,\n fieldName: string\n ): NodePath<BabelTypes.Node> | undefined => {\n if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return undefined;\n if (!memberExprPath) return undefined;\n\n const callPath = memberExprPath.parentPath;\n if (!callPath) return undefined;\n if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) {\n return undefined;\n }\n if (\n (callPath.node as BabelTypes.CallExpression).callee !==\n memberExprPath.node\n ) {\n return undefined;\n }\n\n return callPath;\n };\n\n /**\n * Analyses the callbacks handed to a chainable runtime helper.\n *\n * The callback receives the freshly-resolved content root, so its parameter\n * is walked exactly like `const content = useIntlayer('key')` — either as a\n * plain binding or as a destructuring pattern.\n */\n const analyzeChainedCallbackArguments = (\n callPath: NodePath<BabelTypes.Node>\n ): void => {\n const argumentPaths = callPath.get(\n 'arguments'\n ) as NodePath<BabelTypes.Node>[];\n\n for (const argumentPath of argumentPaths) {\n if (\n !argumentPath.isArrowFunctionExpression() &&\n !argumentPath.isFunctionExpression()\n ) {\n continue; // not a callback (e.g. an options object) – nothing to read\n }\n\n const [firstParameterPath] = argumentPath.get(\n 'params'\n ) as NodePath<BabelTypes.Node>[];\n if (!firstParameterPath) continue; // callback ignores the content\n\n // (content) => … — walk the parameter's references.\n if (firstParameterPath.isIdentifier()) {\n const parameterBinding = argumentPath.scope.getBinding(\n firstParameterPath.node.name\n );\n if (!parameterBinding) {\n markUntrackedBinding();\n continue;\n }\n\n const accessedFieldNames = new Set<string>();\n if (\n analyzeContentBindingReferences(parameterBinding, accessedFieldNames)\n ) {\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n }\n continue;\n }\n\n // ({ title }) => … — destructured directly in the parameter list.\n if (firstParameterPath.isObjectPattern()) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n firstParameterPath.node,\n argumentPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n continue;\n }\n\n markUntrackedBinding();\n }\n };\n\n /**\n * Walks every reference of a local binding holding the content root,\n * collecting the top-level fields it reads into `accessedTopLevelFieldNames`.\n *\n * @returns `true` when a reference escaped static tracking, meaning the whole\n * dictionary must be kept.\n */\n const analyzeContentBindingReferences = (\n variableBinding: { referencePaths: NodePath<BabelTypes.Node>[] },\n accessedTopLevelFieldNames: Set<string>\n ): boolean => {\n for (const variableReferencePath of variableBinding.referencePaths) {\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.name;\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = variableReferencePath.parentPath;\n\n // content.onChange(cb) — a runtime subscription, not a field read.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n return true;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // The binding's value escapes into an array literal\n // (e.g. `[appleWatch, airpods].map((entry) => entry.name)`). Its fields\n // are then accessed through iteration that Babel cannot follow back to\n // this dictionary, so we cannot know which fields are used. This is the\n // canonical meta-record / collection access pattern — conservatively\n // keep every field rather than pruning the content to nothing.\n return true;\n } else if (\n // Solid / Angular: content() signal accessor → content().field\n (babelTypes.isCallExpression(referenceParentNode) ||\n babelTypes.isOptionalCallExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callExprPath = variableReferencePath.parentPath;\n const callParent = callExprPath?.parent;\n\n if (\n callParent &&\n (babelTypes.isMemberExpression(callParent) ||\n babelTypes.isOptionalMemberExpression(callParent)) &&\n (callParent as BabelTypes.MemberExpression).object ===\n callExprPath?.node\n ) {\n // content().field\n const memberExpr = callParent as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpr.computed &&\n babelTypes.isIdentifier(memberExpr.property)\n ) {\n fieldName = memberExpr.property.name;\n } else if (\n memberExpr.computed &&\n babelTypes.isStringLiteral(memberExpr.property)\n ) {\n fieldName = memberExpr.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = callExprPath?.parentPath;\n\n // content().onChange(cb) — a runtime subscription, not a field.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);\n } else {\n // content()[dynamicKey] – cannot resolve statically\n return true;\n }\n } else if (\n callParent &&\n babelTypes.isVariableDeclarator(callParent) &&\n babelTypes.isObjectPattern(callParent.id) &&\n callExprPath &&\n collectFieldsFromObjectPattern(\n callParent.id,\n callExprPath,\n accessedTopLevelFieldNames\n )\n ) {\n // const { title } = content()\n // fields already added to accessedTopLevelFieldNames by collectFieldsFromObjectPattern\n } else {\n // content() with no field access or passed opaquely → cannot prune\n return true;\n }\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n return true;\n }\n }\n\n return false;\n };\n\n /**\n * Analyses how the content root referenced by `rootPath` is consumed.\n *\n * `rootPath` is the `useIntlayer('key')` call itself, or any expression that\n * evaluates back to the same content object — currently the result of a\n * chainable runtime helper such as `.onChange(…)`.\n */\n const analyzeContentRoot = (rootPath: NodePath<BabelTypes.Node>): void => {\n const parentNode = rootPath.parent;\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n parentNode.id,\n rootPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === rootPath.node\n ) {\n let fieldName: string | undefined;\n\n if (\n !parentNode.computed &&\n babelTypes.isIdentifier(parentNode.property)\n ) {\n fieldName = parentNode.property.name;\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n fieldName = parentNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = rootPath.parentPath;\n\n // useIntlayer('key').onChange(cb) — a runtime subscription: read the\n // fields out of the callback instead of recording `onChange` itself.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n return;\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ───────────────────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableName = parentNode.id.name;\n const variableBinding = rootPath.scope.getBinding(variableName);\n\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n const hasUntrackedReferenceAccess = analyzeContentBindingReferences(\n variableBinding,\n accessedTopLevelFieldNames\n );\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(\n pruneContext,\n dictionaryKey,\n accessedTopLevelFieldNames\n );\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ──────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ──────────\n markUntrackedBinding();\n };\n\n // `getIntlayerAsync('key')` is consumed through its `await`, so the content\n // root is the await expression — reading the call's own parent would see\n // only the `AwaitExpression` and give up on every field.\n analyzeContentRoot(unwrapAwait(babelTypes, callExpressionPath));\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"analyzeContentUsage.cjs","names":["skipTransparentWrappers","isTestOnlyUse","CHAINABLE_RUNTIME_METHOD_NAMES","resolveContentRoot"],"sources":["../../src/analyzeContentUsage.ts"],"sourcesContent":["import type { NodePath } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport { CHAINABLE_RUNTIME_METHOD_NAMES } from './nativeCallers';\nimport {\n type OpaqueFieldOccurrence,\n type PruneContext,\n recordFieldUsage,\n} from './pruneContext';\nimport {\n isTestOnlyUse,\n resolveContentRoot,\n skipTransparentWrappers,\n} from './staticAstReaders';\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 the fields read on `result`\n * const [a, { b }] = await Promise.all([getIntlayerAsync('x'), …])\n * → each element tracked like a declarator\n *\n * TypeScript casts (`as`, `satisfies`, `!`) are looked through, and test-only\n * uses (`!field`, `field && …`) expose no key names.\n */\nexport const analyzeCallExpressionUsage = (\n babelTypes: typeof BabelTypes,\n pruneContext: PruneContext,\n callExpressionPath: NodePath<BabelTypes.CallExpression>,\n dictionaryKey: string,\n currentSourceFilePath: string,\n isSfcFile: boolean\n): void => {\n /** Mark the dictionary key as having an untracked binding in this file. */\n const markUntrackedBinding = (): void => {\n const existingPaths =\n pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];\n if (!existingPaths.includes(currentSourceFilePath)) {\n pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [\n ...existingPaths,\n currentSourceFilePath,\n ]);\n }\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n };\n\n /** Record that the content value at `fieldPath` is consumed opaquely. */\n const markOpaqueField = (\n fieldPath: string[],\n line: number | undefined\n ): void => {\n const pathToOccurrence =\n pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey) ??\n new Map<string, OpaqueFieldOccurrence>();\n const location =\n line !== undefined\n ? `${currentSourceFilePath}:${line}`\n : currentSourceFilePath;\n const joinedFieldPath = fieldPath.join('.');\n const occurrence = pathToOccurrence.get(joinedFieldPath) ?? {\n fieldPath: [...fieldPath],\n locations: [],\n };\n if (!occurrence.locations.includes(location)) {\n occurrence.locations.push(location);\n }\n pathToOccurrence.set(joinedFieldPath, occurrence);\n pruneContext.dictionaryKeysWithOpaqueFields.set(\n dictionaryKey,\n pathToOccurrence\n );\n };\n\n /** Register a plain variable binding in an SFC file for a second-pass analysis. */\n const deferFrameworkAnalysis = (variableName: string): void => {\n const existing =\n pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];\n if (\n !existing.some(\n (e) =>\n e.variableName === variableName && e.dictionaryKey === dictionaryKey\n )\n ) {\n existing.push({ variableName, dictionaryKey });\n }\n pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);\n };\n\n /**\n * Analyses how the content value at `fieldPath` (currently referenced by\n * `refPath`) is consumed, to detect opaque consumption (passing a dictionary\n * value as-is to a prop or function argument).\n *\n * Member-access chains and destructuring are followed recursively with an\n * extended field path, so the *terminal* consumption of a chain like\n * `content.sections.hero` decides opacity — mirroring how far the\n * source-code renamer can rewrite accesses. When the terminal consumption\n * escapes static tracking, the deepest reached field path is marked opaque\n * so the children of that value keep their original key names.\n */\n const analyzeOpaqueUsage = (\n referencePath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n const refPath = skipTransparentWrappers(babelTypes, referencePath);\n\n // `!field`, `field && …`: only tested, no key name escapes.\n if (isTestOnlyUse(babelTypes, refPath)) return;\n\n const parentNode = refPath.parent;\n const parentPath = refPath.parentPath;\n\n // 1. Chained member access (e.g. field.sub or field?.sub): follow the\n // chain with an extended path and analyse the terminal consumption.\n if (\n parentPath &&\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === refPath.node\n ) {\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …) is transparent: the JSON renamers\n // apply the same rename map to every array element.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, fieldPath);\n return;\n }\n\n if (\n !memberNode.computed &&\n babelTypes.isIdentifier(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.name,\n ]);\n return;\n }\n\n if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n analyzeOpaqueUsage(parentPath, [\n ...fieldPath,\n memberNode.property.value,\n ]);\n return;\n }\n\n // Dynamic computed access (field[expr]): the renamer cannot rewrite the\n // key, so the children of the current path must keep their names.\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n return;\n }\n\n // 2. Destructuring (e.g. const { sub } = field): follow each binding.\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);\n return;\n }\n\n // 3. Alias (e.g. const table = content.summary.table): follow the alias.\n // A reassigned alias may hold anything, and an SFC template can read\n // the alias out of Babel's sight, so both stay opaque.\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id) &&\n parentNode.init === refPath.node\n ) {\n const aliasBinding = refPath.scope.getBinding(parentNode.id.name);\n if (isSfcFile || !aliasBinding?.constant) {\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n return;\n }\n for (const aliasReferencePath of aliasBinding.referencePaths) {\n analyzeOpaqueUsage(aliasReferencePath, fieldPath);\n }\n return;\n }\n\n // 4. Ignored patterns (e.g. array literals [content])\n if (babelTypes.isArrayExpression(parentNode)) {\n return;\n }\n\n // 5. Opaque consumption (passed to prop, function, etc.)\n markOpaqueField(fieldPath, refPath.node.loc?.start.line);\n };\n\n /**\n * Analyses a destructuring pattern applied to the content value at\n * `fieldPath`, recursively following each bound local variable's references\n * via {@link analyzeOpaqueUsage}.\n *\n * Patterns the source-code renamer cannot rewrite (rest elements, dynamic\n * computed keys, array patterns) mark the corresponding path opaque.\n */\n const analyzeOpaqueDestructuring = (\n objectPattern: BabelTypes.ObjectPattern,\n scopePath: NodePath<BabelTypes.Node>,\n fieldPath: string[]\n ): void => {\n for (const property of objectPattern.properties) {\n // A rest element re-exposes the remaining fields under their original\n // names — nothing at this level may be renamed.\n if (babelTypes.isRestElement(property)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be renamed.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n markOpaqueField(fieldPath, property.loc?.start.line);\n continue;\n }\n\n const keyName = babelTypes.isIdentifier(property.key)\n ? property.key.name\n : babelTypes.isStringLiteral(property.key)\n ? property.key.value\n : null;\n if (!keyName) continue;\n\n const childFieldPath = [...fieldPath, keyName];\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isObjectPattern(bindingTarget)) {\n analyzeOpaqueDestructuring(bindingTarget, scopePath, childFieldPath);\n continue;\n }\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);\n if (!localVarBinding) continue;\n for (const referencePath of localVarBinding.referencePaths) {\n analyzeOpaqueUsage(referencePath, childFieldPath);\n }\n continue;\n }\n\n // Array patterns and other targets escape the rename walk.\n markOpaqueField(childFieldPath, property.loc?.start.line);\n }\n };\n\n /**\n * Helper to collect field names from an ObjectPattern (destructuring).\n * Returns true if successful, false when the accessed fields cannot be\n * determined statically (rest element or dynamic computed key → 'all').\n */\n const collectFieldsFromObjectPattern = (\n pattern: BabelTypes.ObjectPattern,\n initPath: NodePath<BabelTypes.Node>,\n targetSet: Set<string>\n ): boolean => {\n if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) {\n return false;\n }\n\n for (const property of pattern.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Dynamic computed keys ({ [expr]: x }) cannot be attributed to a\n // specific field — the whole dictionary must be kept.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\n return false;\n }\n\n let fieldName: string | undefined;\n\n if (!property.computed && babelTypes.isIdentifier(property.key)) {\n fieldName = property.key.name;\n } else if (babelTypes.isStringLiteral(property.key)) {\n fieldName = property.key.value;\n }\n\n if (!fieldName) continue;\n\n targetSet.add(fieldName);\n\n // A default value ({ field = fallback }) wraps the binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n if (babelTypes.isIdentifier(bindingTarget)) {\n const variableBinding = initPath.scope.getBinding(bindingTarget.name);\n if (variableBinding) {\n for (const refPath of variableBinding.referencePaths) {\n analyzeOpaqueUsage(refPath, [fieldName]);\n }\n }\n } else if (babelTypes.isObjectPattern(bindingTarget)) {\n // Nested pattern: const { fieldA: { nested } } = … — follow each\n // nested binding so deeper opaque consumption is detected.\n analyzeOpaqueDestructuring(bindingTarget, initPath, [fieldName]);\n } else {\n // Array patterns and other targets escape the rename walk.\n markOpaqueField([fieldName], property.loc?.start.line);\n }\n }\n return true;\n };\n\n /**\n * Returns the call path when `memberExprPath` is a chainable runtime helper\n * being invoked (`content.onChange(cb)`), otherwise `undefined`.\n *\n * See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`\n * reference that is never called is treated as an ordinary field access, so\n * only the invoked form is looked through.\n */\n const getChainedRuntimeCallPath = (\n memberExprPath: NodePath<BabelTypes.Node> | null | undefined,\n fieldName: string\n ): NodePath<BabelTypes.Node> | undefined => {\n if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return undefined;\n if (!memberExprPath) return undefined;\n\n const callPath = memberExprPath.parentPath;\n if (!callPath) return undefined;\n if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) {\n return undefined;\n }\n if (\n (callPath.node as BabelTypes.CallExpression).callee !==\n memberExprPath.node\n ) {\n return undefined;\n }\n\n return callPath;\n };\n\n /**\n * Analyses the callbacks handed to a chainable runtime helper.\n *\n * The callback receives the freshly-resolved content root, so its parameter\n * is walked exactly like `const content = useIntlayer('key')` — either as a\n * plain binding or as a destructuring pattern.\n */\n const analyzeChainedCallbackArguments = (\n callPath: NodePath<BabelTypes.Node>\n ): void => {\n const argumentPaths = callPath.get(\n 'arguments'\n ) as NodePath<BabelTypes.Node>[];\n\n for (const argumentPath of argumentPaths) {\n if (\n !argumentPath.isArrowFunctionExpression() &&\n !argumentPath.isFunctionExpression()\n ) {\n continue; // not a callback (e.g. an options object) – nothing to read\n }\n\n const [firstParameterPath] = argumentPath.get(\n 'params'\n ) as NodePath<BabelTypes.Node>[];\n if (!firstParameterPath) continue; // callback ignores the content\n\n // (content) => … — walk the parameter's references.\n if (firstParameterPath.isIdentifier()) {\n const parameterBinding = argumentPath.scope.getBinding(\n firstParameterPath.node.name\n );\n if (!parameterBinding) {\n markUntrackedBinding();\n continue;\n }\n\n const accessedFieldNames = new Set<string>();\n if (\n analyzeContentBindingReferences(parameterBinding, accessedFieldNames)\n ) {\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n }\n continue;\n }\n\n // ({ title }) => … — destructured directly in the parameter list.\n if (firstParameterPath.isObjectPattern()) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n firstParameterPath.node,\n argumentPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n continue;\n }\n\n markUntrackedBinding();\n }\n };\n\n /**\n * Walks every reference of a local binding holding the content root,\n * collecting the top-level fields it reads into `accessedTopLevelFieldNames`.\n *\n * @returns `true` when a reference escaped static tracking, meaning the whole\n * dictionary must be kept.\n */\n const analyzeContentBindingReferences = (\n variableBinding: { referencePaths: NodePath<BabelTypes.Node>[] },\n accessedTopLevelFieldNames: Set<string>\n ): boolean => {\n for (const bindingReferencePath of variableBinding.referencePaths) {\n const variableReferencePath = skipTransparentWrappers(\n babelTypes,\n bindingReferencePath\n );\n\n // `if (!content)`: only tested, no key name escapes.\n if (isTestOnlyUse(babelTypes, variableReferencePath)) continue;\n\n const referenceParentNode = variableReferencePath.parent;\n\n if (\n (babelTypes.isMemberExpression(referenceParentNode) ||\n babelTypes.isOptionalMemberExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.MemberExpression).object ===\n variableReferencePath.node\n ) {\n const memberExpressionNode =\n referenceParentNode as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpressionNode.computed &&\n babelTypes.isIdentifier(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.name;\n } else if (\n memberExpressionNode.computed &&\n babelTypes.isStringLiteral(memberExpressionNode.property)\n ) {\n fieldName = memberExpressionNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = variableReferencePath.parentPath;\n\n // content.onChange(cb) — a runtime subscription, not a field read.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n\n // Check usage of the field to look for opaque consumption\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n // Dynamic computed access – cannot resolve statically\n return true;\n }\n } else if (babelTypes.isArrayExpression(referenceParentNode)) {\n // The binding's value escapes into an array literal\n // (e.g. `[appleWatch, airpods].map((entry) => entry.name)`). Its fields\n // are then accessed through iteration that Babel cannot follow back to\n // this dictionary, so we cannot know which fields are used. This is the\n // canonical meta-record / collection access pattern — conservatively\n // keep every field rather than pruning the content to nothing.\n return true;\n } else if (\n // Solid / Angular: content() signal accessor → content().field\n (babelTypes.isCallExpression(referenceParentNode) ||\n babelTypes.isOptionalCallExpression(referenceParentNode)) &&\n (referenceParentNode as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callExprPath = variableReferencePath.parentPath;\n const callParent = callExprPath?.parent;\n\n if (\n callParent &&\n (babelTypes.isMemberExpression(callParent) ||\n babelTypes.isOptionalMemberExpression(callParent)) &&\n (callParent as BabelTypes.MemberExpression).object ===\n callExprPath?.node\n ) {\n // content().field\n const memberExpr = callParent as BabelTypes.MemberExpression;\n let fieldName: string | undefined;\n\n if (\n !memberExpr.computed &&\n babelTypes.isIdentifier(memberExpr.property)\n ) {\n fieldName = memberExpr.property.name;\n } else if (\n memberExpr.computed &&\n babelTypes.isStringLiteral(memberExpr.property)\n ) {\n fieldName = memberExpr.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = callExprPath?.parentPath;\n\n // content().onChange(cb) — a runtime subscription, not a field.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n continue;\n }\n\n accessedTopLevelFieldNames.add(fieldName);\n if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);\n } else {\n // content()[dynamicKey] – cannot resolve statically\n return true;\n }\n } else if (\n callParent &&\n babelTypes.isVariableDeclarator(callParent) &&\n babelTypes.isObjectPattern(callParent.id) &&\n callExprPath &&\n collectFieldsFromObjectPattern(\n callParent.id,\n callExprPath,\n accessedTopLevelFieldNames\n )\n ) {\n // const { title } = content()\n // fields already added to accessedTopLevelFieldNames by collectFieldsFromObjectPattern\n } else {\n // content() with no field access or passed opaquely → cannot prune\n return true;\n }\n } else {\n // Variable used in a non-member-access context (spread, function arg, etc.)\n return true;\n }\n }\n\n return false;\n };\n\n /**\n * Records the fields read through `variableName`, the local binding that\n * receives the content root at `rootPath`.\n */\n const analyzeContentVariable = (\n rootPath: NodePath<BabelTypes.Node>,\n variableName: string\n ): void => {\n const variableBinding = rootPath.scope.getBinding(variableName);\n if (!variableBinding) {\n markUntrackedBinding();\n return;\n }\n\n const accessedTopLevelFieldNames = new Set<string>();\n const hasUntrackedReferenceAccess = analyzeContentBindingReferences(\n variableBinding,\n accessedTopLevelFieldNames\n );\n\n if (hasUntrackedReferenceAccess) {\n markUntrackedBinding();\n } else if (isSfcFile) {\n // Vue / Svelte SFC: defer to the framework-specific extractor because\n // Babel scope analysis cannot see through `.value` or `$` indirection.\n deferFrameworkAnalysis(variableName);\n } else if (variableBinding.referencePaths.length === 0) {\n // Non-SFC file with no visible references – keep all fields.\n markUntrackedBinding();\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);\n }\n };\n\n /**\n * Analyses how the content root produced by `expressionPath` is consumed.\n *\n * `expressionPath` is the `useIntlayer('key')` call itself, or any expression that\n * evaluates back to the same content object — currently the result of a\n * chainable runtime helper such as `.onChange(…)`.\n */\n const analyzeContentRoot = (\n expressionPath: NodePath<BabelTypes.Node>\n ): void => {\n const resolution = resolveContentRoot(babelTypes, expressionPath);\n\n // const [, b] = await Promise.all([getIntlayerAsync('skipped'), …])\n if (resolution.kind === 'discarded') return;\n\n const { rootPath } = resolution;\n\n if (resolution.kind === 'binding') {\n const { bindingTarget } = resolution;\n\n // ── Pattern 1: const { fieldA, fieldB } = useIntlayer('key') ──────────\n if (babelTypes.isObjectPattern(bindingTarget)) {\n const accessedFieldNames = new Set<string>();\n if (\n collectFieldsFromObjectPattern(\n bindingTarget,\n rootPath,\n accessedFieldNames\n )\n ) {\n recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);\n } else {\n recordFieldUsage(pruneContext, dictionaryKey, 'all');\n }\n return;\n }\n\n // ── Pattern 3: const content = useIntlayer('key') ─────────────────────\n analyzeContentVariable(rootPath, bindingTarget.name);\n return;\n }\n\n const parentNode = rootPath.parent;\n\n // ── Pattern 2: useIntlayer('key').fieldA / useIntlayer('key')?.fieldA ────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object === rootPath.node\n ) {\n let fieldName: string | undefined;\n\n if (\n !parentNode.computed &&\n babelTypes.isIdentifier(parentNode.property)\n ) {\n fieldName = parentNode.property.name;\n } else if (\n parentNode.computed &&\n babelTypes.isStringLiteral(parentNode.property)\n ) {\n fieldName = parentNode.property.value;\n }\n\n if (fieldName) {\n const memberExprPath = rootPath.parentPath;\n\n // useIntlayer('key').onChange(cb) — a runtime subscription: read the\n // fields out of the callback instead of recording `onChange` itself.\n const chainedCallPath = getChainedRuntimeCallPath(\n memberExprPath,\n fieldName\n );\n if (chainedCallPath) {\n analyzeChainedCallbackArguments(chainedCallPath);\n analyzeContentRoot(chainedCallPath);\n return;\n }\n\n recordFieldUsage(pruneContext, dictionaryKey, new Set([fieldName]));\n\n // Check for opaque usage (e.g. passed directly to a prop)\n if (memberExprPath) {\n analyzeOpaqueUsage(memberExprPath, [fieldName]);\n }\n } else {\n markUntrackedBinding();\n }\n return;\n }\n\n // ── Pattern 4: bare call – result is discarded ──────────────────────────\n if (babelTypes.isExpressionStatement(parentNode)) {\n return; // no usage to record\n }\n\n // ── Fallback: result passed as argument, used in ternary, etc. ──────────\n markUntrackedBinding();\n };\n\n // `getIntlayerAsync('key')` is consumed through its `await`, so the content\n // root is the await expression — reading the call's own parent would see\n // only the `AwaitExpression` and give up on every field.\n analyzeContentRoot(callExpressionPath);\n};\n"],"mappings":"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"}
@@ -28,7 +28,7 @@ let _babel_parser = require("@babel/parser");
28
28
  * };
29
29
  * ```
30
30
  */
31
- const intlayerExtractBabelPlugin = (_babel) => {
31
+ const extractBabelPlugin = (_babel) => {
32
32
  return {
33
33
  name: "babel-plugin-intlayer-extract",
34
34
  visitor: { Program: { enter(programPath, state) {
@@ -72,6 +72,16 @@ const intlayerExtractBabelPlugin = (_babel) => {
72
72
  } } }
73
73
  };
74
74
  };
75
+ /**
76
+ * Intlayer extract Babel plugin (see `extractBabelPlugin`), typed as Babel's
77
+ * `PluginTarget`.
78
+ *
79
+ * Babel types tuple options as `object` (`[PluginTarget<object>, object]`), so
80
+ * a plugin whose handlers read typed `this.opts` is rejected in
81
+ * `plugins: [[plugin, options]]` under `strict`. The options passed in the
82
+ * tuple are the typed options read by the handlers.
83
+ */
84
+ const intlayerExtractBabelPlugin = extractBabelPlugin;
75
85
 
76
86
  //#endregion
77
87
  exports.intlayerExtractBabelPlugin = intlayerExtractBabelPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-extract.cjs","names":["getNormalizedFilesListSet","normalizePath","getAppLogger","detectPackageName","extractContentSync","parse","colorize","ANSIColors","colorizePath","relative"],"sources":["../../src/babel-plugin-intlayer-extract.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport type { PluginObject, PluginPass } from '@babel/core';\nimport { parse } from '@babel/parser';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport { extractContentSync } from './extractContent/extractContent';\nimport type { PackageName } from './extractContent/utils/constants';\nimport { detectPackageName } from './extractContent/utils/detectPackageName';\nimport { getNormalizedFilesListSet } from './normalizedFilesList';\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: Record<string, string>;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n packageName?: PackageName;\n filesList: string[];\n enabled: boolean;\n\n shouldExtract?: (text: string) => boolean;\n configuration: IntlayerConfig;\n /**\n * Callback invoked for each extracted dictionary key/content pair.\n * Used by `getExtractPluginOptions` to write dictionaries to disk.\n * May be async — the plugin will fire-and-forget (Babel transforms are sync).\n */\n onExtract?: (result: ExtractResult) => void | Promise<void>;\n /**\n * Defines the output files path.\n */\n output?: FilePathPattern;\n};\n\ntype State = PluginPass & { opts: ExtractPluginOptions };\n\n/**\n * Babel plugin that extracts translatable content from source files and\n * injects Intlayer hooks (`useIntlayer` / `getIntlayer`) automatically.\n *\n * Designed for use with Babel-based build tools such as Next.js and Webpack.\n *\n * @example babel.config.js\n * ```js\n * const { intlayerExtractBabelPlugin, getExtractPluginOptions } = require('@intlayer/babel');\n * module.exports = {\n * presets: ['next/babel'],\n * plugins: [\n * [intlayerExtractBabelPlugin, getExtractPluginOptions()],\n * ],\n * };\n * ```\n */\nexport const intlayerExtractBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObject<State> => {\n return {\n name: 'babel-plugin-intlayer-extract',\n\n visitor: {\n Program: {\n enter(programPath, state) {\n const opts = state.opts;\n\n // Merge plugin options with the unified compiler config\n const isEnabled = opts.enabled;\n\n if (isEnabled === false) return;\n\n const filename = state.file.opts.filename;\n\n if (!filename) return;\n\n // Compare as POSIX paths (babel filenames use OS separators on Windows).\n if (\n opts.filesList &&\n !getNormalizedFilesListSet(opts.filesList).has(\n normalizePath(filename)\n )\n ) {\n return;\n }\n\n const fileCode: string = state.file.code ?? '';\n if (!fileCode) return;\n\n const appLogger = getAppLogger(opts.configuration);\n const packageName = opts.packageName ?? detectPackageName(filename);\n\n const { saveComponents } = opts.configuration.compiler;\n\n const result = extractContentSync(filename, packageName, {\n configuration: opts.configuration,\n code: fileCode,\n onExtract: (extractResult: {\n key: string;\n content: Record<string, string>;\n }) => {\n if (opts.onExtract) {\n opts.onExtract({\n dictionaryKey: extractResult.key,\n filePath: filename,\n content: extractResult.content,\n locale: opts.configuration.internationalization.defaultLocale,\n });\n }\n },\n declarationOnly: !saveComponents,\n });\n\n if (!result) return;\n\n const { transformedCode: modifiedCode } = result;\n\n if (!modifiedCode) return;\n\n // Replace the Babel AST with the transformed code by re-parsing it.\n // This lets Babel serialise the injected hooks/imports through its\n // own code generator, preserving compatibility with other plugins.\n try {\n const newAst = parse(modifiedCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n programPath.node.body = newAst.program.body;\n programPath.node.directives = newAst.program.directives;\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted content from ${colorizePath(relative(opts.configuration.system.baseDir, filename))}`,\n { level: 'debug' }\n );\n } catch (error) {\n appLogger(\n [\n `Failed to parse transformed code for ${colorizePath(relative(opts.configuration.system.baseDir, filename))}:`,\n error,\n ],\n { level: 'error' }\n );\n }\n },\n },\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAa,8BAA8B,WAEhB;CACzB,OAAO;EACL,MAAM;EAEN,SAAS,EACP,SAAS,EACP,MAAM,aAAa,OAAO;GACxB,MAAM,OAAO,MAAM;GAKnB,IAFkB,KAAK,YAEL,OAAO;GAEzB,MAAM,WAAW,MAAM,KAAK,KAAK;GAEjC,IAAI,CAAC,UAAU;GAGf,IACE,KAAK,aACL,CAACA,sDAA0B,KAAK,SAAS,CAAC,CAAC,QACzCC,sCAAc,QAAQ,CACxB,GAEA;GAGF,MAAM,WAAmB,MAAM,KAAK,QAAQ;GAC5C,IAAI,CAAC,UAAU;GAEf,MAAM,gBAAYC,sCAAa,KAAK,aAAa;GACjD,MAAM,cAAc,KAAK,eAAeC,iEAAkB,QAAQ;GAElE,MAAM,EAAE,mBAAmB,KAAK,cAAc;GAE9C,MAAM,SAASC,yDAAmB,UAAU,aAAa;IACvD,eAAe,KAAK;IACpB,MAAM;IACN,YAAY,kBAGN;KACJ,IAAI,KAAK,WACP,KAAK,UAAU;MACb,eAAe,cAAc;MAC7B,UAAU;MACV,SAAS,cAAc;MACvB,QAAQ,KAAK,cAAc,qBAAqB;KAClD,CAAC;IAEL;IACA,iBAAiB,CAAC;GACpB,CAAC;GAED,IAAI,CAAC,QAAQ;GAEb,MAAM,EAAE,iBAAiB,iBAAiB;GAE1C,IAAI,CAAC,cAAc;GAKnB,IAAI;IACF,MAAM,aAASC,qBAAM,cAAc;KACjC,YAAY;KACZ,SAAS,CAAC,OAAO,YAAY;IAC/B,CAAC;IAED,YAAY,KAAK,OAAO,OAAO,QAAQ;IACvC,YAAY,KAAK,aAAa,OAAO,QAAQ;IAE7C,UACE,OAAGC,kCAAS,aAAaC,wBAAW,SAAS,EAAE,8BAA0BC,0CAAaC,oBAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,KAC3I,EAAE,OAAO,QAAQ,CACnB;GACF,SAAS,OAAO;IACd,UACE,CACE,4CAAwCD,0CAAaC,oBAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,EAAE,IAC5G,KACF,GACA,EAAE,OAAO,QAAQ,CACnB;GACF;EACF,EACF,EACF;CACF;AACF"}
1
+ {"version":3,"file":"babel-plugin-intlayer-extract.cjs","names":["getNormalizedFilesListSet","normalizePath","getAppLogger","detectPackageName","extractContentSync","parse","colorize","ANSIColors","colorizePath","relative"],"sources":["../../src/babel-plugin-intlayer-extract.ts"],"sourcesContent":["import { relative } from 'node:path';\nimport type { PluginObject, PluginPass, PluginTarget } from '@babel/core';\nimport { parse } from '@babel/parser';\nimport type * as BabelTypes from '@babel/types';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, getAppLogger } from '@intlayer/config/logger';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { FilePathPattern } from '@intlayer/types/filePathPattern';\nimport { extractContentSync } from './extractContent/extractContent';\nimport type { PackageName } from './extractContent/utils/constants';\nimport { detectPackageName } from './extractContent/utils/detectPackageName';\nimport { getNormalizedFilesListSet } from './normalizedFilesList';\n\nexport type ExtractResult = {\n dictionaryKey: string;\n filePath: string;\n content: Record<string, string>;\n locale: Locale;\n};\n\nexport type ExtractPluginOptions = {\n packageName?: PackageName;\n filesList: string[];\n enabled: boolean;\n\n shouldExtract?: (text: string) => boolean;\n configuration: IntlayerConfig;\n /**\n * Callback invoked for each extracted dictionary key/content pair.\n * Used by `getExtractPluginOptions` to write dictionaries to disk.\n * May be async — the plugin will fire-and-forget (Babel transforms are sync).\n */\n onExtract?: (result: ExtractResult) => void | Promise<void>;\n /**\n * Defines the output files path.\n */\n output?: FilePathPattern;\n};\n\ntype State = PluginPass & { opts: ExtractPluginOptions };\n\n/**\n * Babel plugin that extracts translatable content from source files and\n * injects Intlayer hooks (`useIntlayer` / `getIntlayer`) automatically.\n *\n * Designed for use with Babel-based build tools such as Next.js and Webpack.\n *\n * @example babel.config.js\n * ```js\n * const { intlayerExtractBabelPlugin, getExtractPluginOptions } = require('@intlayer/babel');\n * module.exports = {\n * presets: ['next/babel'],\n * plugins: [\n * [intlayerExtractBabelPlugin, getExtractPluginOptions()],\n * ],\n * };\n * ```\n */\nconst extractBabelPlugin = (_babel: {\n types: typeof BabelTypes;\n}): PluginObject<State> => {\n return {\n name: 'babel-plugin-intlayer-extract',\n\n visitor: {\n Program: {\n enter(programPath, state) {\n const opts = state.opts;\n\n // Merge plugin options with the unified compiler config\n const isEnabled = opts.enabled;\n\n if (isEnabled === false) return;\n\n const filename = state.file.opts.filename;\n\n if (!filename) return;\n\n // Compare as POSIX paths (babel filenames use OS separators on Windows).\n if (\n opts.filesList &&\n !getNormalizedFilesListSet(opts.filesList).has(\n normalizePath(filename)\n )\n ) {\n return;\n }\n\n const fileCode: string = state.file.code ?? '';\n if (!fileCode) return;\n\n const appLogger = getAppLogger(opts.configuration);\n const packageName = opts.packageName ?? detectPackageName(filename);\n\n const { saveComponents } = opts.configuration.compiler;\n\n const result = extractContentSync(filename, packageName, {\n configuration: opts.configuration,\n code: fileCode,\n onExtract: (extractResult: {\n key: string;\n content: Record<string, string>;\n }) => {\n if (opts.onExtract) {\n opts.onExtract({\n dictionaryKey: extractResult.key,\n filePath: filename,\n content: extractResult.content,\n locale: opts.configuration.internationalization.defaultLocale,\n });\n }\n },\n declarationOnly: !saveComponents,\n });\n\n if (!result) return;\n\n const { transformedCode: modifiedCode } = result;\n\n if (!modifiedCode) return;\n\n // Replace the Babel AST with the transformed code by re-parsing it.\n // This lets Babel serialise the injected hooks/imports through its\n // own code generator, preserving compatibility with other plugins.\n try {\n const newAst = parse(modifiedCode, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n programPath.node.body = newAst.program.body;\n programPath.node.directives = newAst.program.directives;\n\n appLogger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted content from ${colorizePath(relative(opts.configuration.system.baseDir, filename))}`,\n { level: 'debug' }\n );\n } catch (error) {\n appLogger(\n [\n `Failed to parse transformed code for ${colorizePath(relative(opts.configuration.system.baseDir, filename))}:`,\n error,\n ],\n { level: 'error' }\n );\n }\n },\n },\n },\n };\n};\n\n/**\n * Intlayer extract Babel plugin (see `extractBabelPlugin`), typed as Babel's\n * `PluginTarget`.\n *\n * Babel types tuple options as `object` (`[PluginTarget<object>, object]`), so\n * a plugin whose handlers read typed `this.opts` is rejected in\n * `plugins: [[plugin, options]]` under `strict`. The options passed in the\n * tuple are the typed options read by the handlers.\n */\nexport const intlayerExtractBabelPlugin: PluginTarget =\n extractBabelPlugin as PluginTarget;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAM,sBAAsB,WAED;CACzB,OAAO;EACL,MAAM;EAEN,SAAS,EACP,SAAS,EACP,MAAM,aAAa,OAAO;GACxB,MAAM,OAAO,MAAM;GAKnB,IAFkB,KAAK,YAEL,OAAO;GAEzB,MAAM,WAAW,MAAM,KAAK,KAAK;GAEjC,IAAI,CAAC,UAAU;GAGf,IACE,KAAK,aACL,CAACA,sDAA0B,KAAK,SAAS,CAAC,CAAC,QACzCC,sCAAc,QAAQ,CACxB,GAEA;GAGF,MAAM,WAAmB,MAAM,KAAK,QAAQ;GAC5C,IAAI,CAAC,UAAU;GAEf,MAAM,gBAAYC,sCAAa,KAAK,aAAa;GACjD,MAAM,cAAc,KAAK,eAAeC,iEAAkB,QAAQ;GAElE,MAAM,EAAE,mBAAmB,KAAK,cAAc;GAE9C,MAAM,SAASC,yDAAmB,UAAU,aAAa;IACvD,eAAe,KAAK;IACpB,MAAM;IACN,YAAY,kBAGN;KACJ,IAAI,KAAK,WACP,KAAK,UAAU;MACb,eAAe,cAAc;MAC7B,UAAU;MACV,SAAS,cAAc;MACvB,QAAQ,KAAK,cAAc,qBAAqB;KAClD,CAAC;IAEL;IACA,iBAAiB,CAAC;GACpB,CAAC;GAED,IAAI,CAAC,QAAQ;GAEb,MAAM,EAAE,iBAAiB,iBAAiB;GAE1C,IAAI,CAAC,cAAc;GAKnB,IAAI;IACF,MAAM,aAASC,qBAAM,cAAc;KACjC,YAAY;KACZ,SAAS,CAAC,OAAO,YAAY;IAC/B,CAAC;IAED,YAAY,KAAK,OAAO,OAAO,QAAQ;IACvC,YAAY,KAAK,aAAa,OAAO,QAAQ;IAE7C,UACE,OAAGC,kCAAS,aAAaC,wBAAW,SAAS,EAAE,8BAA0BC,0CAAaC,oBAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,KAC3I,EAAE,OAAO,QAAQ,CACnB;GACF,SAAS,OAAO;IACd,UACE,CACE,4CAAwCD,0CAAaC,oBAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,EAAE,IAC5G,KACF,GACA,EAAE,OAAO,QAAQ,CACnB;GACF;EACF,EACF,EACF;CACF;AACF;;;;;;;;;;AAWA,MAAa,6BACX"}
@@ -132,11 +132,13 @@ const serializeFieldRenameMap = (pruneContext) => {
132
132
  * as transparent pass-throughs: the rename map is kept unchanged and the walk
133
133
  * continues past the index. This means `content.field[0].sub` is handled
134
134
  * correctly — `field` and `sub` are both renamed while `[0]` is left intact.
135
+ * TypeScript casts are looked through as well: `(content.field as any).sub`.
135
136
  */
136
137
  const walkRenameChain = (babelTypes, startPath, currentRenameMap) => {
137
138
  let refPath = startPath;
138
139
  let renameMap = currentRenameMap;
139
140
  while (true) {
141
+ refPath = require_staticAstReaders.skipTransparentWrappers(babelTypes, refPath);
140
142
  const parentPath = refPath.parentPath;
141
143
  if (!parentPath) break;
142
144
  const parentNode = parentPath.node;
@@ -204,10 +206,7 @@ const renameObjectPatternProperties = (babelTypes, objectPattern, scopePath, ren
204
206
  if (babelTypes.isIdentifier(bindingTarget)) {
205
207
  const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);
206
208
  if (!localVarBinding) continue;
207
- for (const nestedRefPath of localVarBinding.referencePaths) {
208
- const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, nestedRefPath, renameEntry.children);
209
- walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
210
- }
209
+ for (const nestedRefPath of localVarBinding.referencePaths) renameFieldConsumers(babelTypes, nestedRefPath, renameEntry.children);
211
210
  }
212
211
  }
213
212
  };
@@ -235,6 +234,28 @@ const walkObjectDestructuring = (babelTypes, refPath, renameMap) => {
235
234
  renameObjectPatternProperties(babelTypes, parentNode.id, refPath, renameMap);
236
235
  };
237
236
  /**
237
+ * Renames the field accesses reachable from `refPath`, a reference to a
238
+ * content value: a member-access chain, then a destructuring assignment or an
239
+ * alias (`const table = content.summary.table`) of where the chain ends.
240
+ */
241
+ const renameFieldConsumers = (babelTypes, refPath, renameMap) => {
242
+ const chainEnd = walkRenameChain(babelTypes, refPath, renameMap);
243
+ walkObjectDestructuring(babelTypes, chainEnd.finalPath, chainEnd.finalRenameMap);
244
+ renameAliasReferences(babelTypes, chainEnd.finalPath, chainEnd.finalRenameMap);
245
+ return chainEnd;
246
+ };
247
+ /**
248
+ * Follows `const alias = <valuePath>` and renames the accesses made through
249
+ * `alias`, mirroring the analyser's alias tracking.
250
+ */
251
+ const renameAliasReferences = (babelTypes, valuePath, renameMap) => {
252
+ if (renameMap.size === 0) return;
253
+ const parentNode = valuePath.parent;
254
+ if (!babelTypes.isVariableDeclarator(parentNode) || !babelTypes.isIdentifier(parentNode.id) || parentNode.init !== valuePath.node) return;
255
+ const aliasBinding = valuePath.scope.getBinding(parentNode.id.name);
256
+ for (const aliasReferencePath of aliasBinding?.referencePaths ?? []) renameFieldConsumers(babelTypes, aliasReferencePath, renameMap);
257
+ };
258
+ /**
238
259
  * Renames every field access reachable from `rootPath`, whose value is the
239
260
  * dictionary content root.
240
261
  *
@@ -243,8 +264,7 @@ const walkObjectDestructuring = (babelTypes, refPath, renameMap) => {
243
264
  * `.onChange(cb)`.
244
265
  */
245
266
  const renameContentConsumers = (babelTypes, rootPath, renameMap) => {
246
- const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, rootPath, renameMap);
247
- walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
267
+ const { finalPath, finalRenameMap } = renameFieldConsumers(babelTypes, rootPath, renameMap);
248
268
  renameChainedRuntimeCall(babelTypes, finalPath, finalRenameMap);
249
269
  };
250
270
  /**
@@ -361,18 +381,16 @@ const renameIntlayerFieldAccesses = (babelTypes, programPath, pruneContext) => {
361
381
  if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;
362
382
  const fieldRenameMap = pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);
363
383
  if (!fieldRenameMap || fieldRenameMap.size === 0) return;
364
- const contentRootPath = require_staticAstReaders.unwrapAwait(babelTypes, callExpressionPath);
365
- const parentNode = contentRootPath.parent;
366
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
367
- walkObjectDestructuring(babelTypes, contentRootPath, fieldRenameMap);
368
- return;
369
- }
370
- if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === contentRootPath.node) {
371
- renameContentConsumers(babelTypes, contentRootPath, fieldRenameMap);
372
- return;
373
- }
374
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
375
- const variableBinding = callExpressionPath.scope.getBinding(parentNode.id.name);
384
+ const resolution = require_staticAstReaders.resolveContentRoot(babelTypes, callExpressionPath);
385
+ if (resolution.kind === "discarded") return;
386
+ const { rootPath: contentRootPath } = resolution;
387
+ if (resolution.kind === "binding") {
388
+ const { bindingTarget } = resolution;
389
+ if (babelTypes.isObjectPattern(bindingTarget)) {
390
+ renameObjectPatternProperties(babelTypes, bindingTarget, contentRootPath, fieldRenameMap);
391
+ return;
392
+ }
393
+ const variableBinding = contentRootPath.scope.getBinding(bindingTarget.name);
376
394
  if (!variableBinding) return;
377
395
  for (const variableReferencePath of variableBinding.referencePaths) {
378
396
  renameContentConsumers(babelTypes, variableReferencePath, fieldRenameMap);
@@ -382,7 +400,10 @@ const renameIntlayerFieldAccesses = (babelTypes, programPath, pruneContext) => {
382
400
  if (callPath) renameContentConsumers(babelTypes, callPath, fieldRenameMap);
383
401
  }
384
402
  }
403
+ return;
385
404
  }
405
+ const parentNode = contentRootPath.parent;
406
+ if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === contentRootPath.node) renameContentConsumers(babelTypes, contentRootPath, fieldRenameMap);
386
407
  } });
387
408
  };
388
409
  /**