@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
@@ -1,4 +1,4 @@
1
- import { unwrapAwait } from "./staticAstReaders.mjs";
1
+ import { isTestOnlyUse, resolveContentRoot, skipTransparentWrappers } from "./staticAstReaders.mjs";
2
2
  import { CHAINABLE_RUNTIME_METHOD_NAMES } from "./nativeCallers.mjs";
3
3
  import { recordFieldUsage } from "./pruneContext.mjs";
4
4
 
@@ -12,7 +12,12 @@ import { recordFieldUsage } from "./pruneContext.mjs";
12
12
  * useIntlayer('key').fieldA → records {fieldA}
13
13
  * useIntlayer('key')['fieldA'] → records {fieldA}
14
14
  * const { ...rest } = useIntlayer('key') → records 'all' (spread)
15
- * const result = useIntlayer('key') → records 'all' (untracked binding)
15
+ * const result = useIntlayer('key') → records the fields read on `result`
16
+ * const [a, { b }] = await Promise.all([getIntlayerAsync('x'), …])
17
+ * → each element tracked like a declarator
18
+ *
19
+ * TypeScript casts (`as`, `satisfies`, `!`) are looked through, and test-only
20
+ * uses (`!field`, `field && …`) expose no key names.
16
21
  */
17
22
  const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile) => {
18
23
  /** Mark the dictionary key as having an untracked binding in this file. */
@@ -55,7 +60,9 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
55
60
  * escapes static tracking, the deepest reached field path is marked opaque
56
61
  * so the children of that value keep their original key names.
57
62
  */
58
- const analyzeOpaqueUsage = (refPath, fieldPath) => {
63
+ const analyzeOpaqueUsage = (referencePath, fieldPath) => {
64
+ const refPath = skipTransparentWrappers(babelTypes, referencePath);
65
+ if (isTestOnlyUse(babelTypes, refPath)) return;
59
66
  const parentNode = refPath.parent;
60
67
  const parentPath = refPath.parentPath;
61
68
  if (parentPath && (babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === refPath.node) {
@@ -79,6 +86,15 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
79
86
  analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);
80
87
  return;
81
88
  }
89
+ if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id) && parentNode.init === refPath.node) {
90
+ const aliasBinding = refPath.scope.getBinding(parentNode.id.name);
91
+ if (isSfcFile || !aliasBinding?.constant) {
92
+ markOpaqueField(fieldPath, refPath.node.loc?.start.line);
93
+ return;
94
+ }
95
+ for (const aliasReferencePath of aliasBinding.referencePaths) analyzeOpaqueUsage(aliasReferencePath, fieldPath);
96
+ return;
97
+ }
82
98
  if (babelTypes.isArrayExpression(parentNode)) return;
83
99
  markOpaqueField(fieldPath, refPath.node.loc?.start.line);
84
100
  };
@@ -200,7 +216,9 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
200
216
  * dictionary must be kept.
201
217
  */
202
218
  const analyzeContentBindingReferences = (variableBinding, accessedTopLevelFieldNames) => {
203
- for (const variableReferencePath of variableBinding.referencePaths) {
219
+ for (const bindingReferencePath of variableBinding.referencePaths) {
220
+ const variableReferencePath = skipTransparentWrappers(babelTypes, bindingReferencePath);
221
+ if (isTestOnlyUse(babelTypes, variableReferencePath)) continue;
204
222
  const referenceParentNode = variableReferencePath.parent;
205
223
  if ((babelTypes.isMemberExpression(referenceParentNode) || babelTypes.isOptionalMemberExpression(referenceParentNode)) && referenceParentNode.object === variableReferencePath.node) {
206
224
  const memberExpressionNode = referenceParentNode;
@@ -244,20 +262,44 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
244
262
  return false;
245
263
  };
246
264
  /**
247
- * Analyses how the content root referenced by `rootPath` is consumed.
265
+ * Records the fields read through `variableName`, the local binding that
266
+ * receives the content root at `rootPath`.
267
+ */
268
+ const analyzeContentVariable = (rootPath, variableName) => {
269
+ const variableBinding = rootPath.scope.getBinding(variableName);
270
+ if (!variableBinding) {
271
+ markUntrackedBinding();
272
+ return;
273
+ }
274
+ const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
275
+ if (analyzeContentBindingReferences(variableBinding, accessedTopLevelFieldNames)) markUntrackedBinding();
276
+ else if (isSfcFile) deferFrameworkAnalysis(variableName);
277
+ else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
278
+ else recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
279
+ };
280
+ /**
281
+ * Analyses how the content root produced by `expressionPath` is consumed.
248
282
  *
249
- * `rootPath` is the `useIntlayer('key')` call itself, or any expression that
283
+ * `expressionPath` is the `useIntlayer('key')` call itself, or any expression that
250
284
  * evaluates back to the same content object — currently the result of a
251
285
  * chainable runtime helper such as `.onChange(…)`.
252
286
  */
253
- const analyzeContentRoot = (rootPath) => {
254
- const parentNode = rootPath.parent;
255
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
256
- const accessedFieldNames = /* @__PURE__ */ new Set();
257
- if (collectFieldsFromObjectPattern(parentNode.id, rootPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
258
- else recordFieldUsage(pruneContext, dictionaryKey, "all");
287
+ const analyzeContentRoot = (expressionPath) => {
288
+ const resolution = resolveContentRoot(babelTypes, expressionPath);
289
+ if (resolution.kind === "discarded") return;
290
+ const { rootPath } = resolution;
291
+ if (resolution.kind === "binding") {
292
+ const { bindingTarget } = resolution;
293
+ if (babelTypes.isObjectPattern(bindingTarget)) {
294
+ const accessedFieldNames = /* @__PURE__ */ new Set();
295
+ if (collectFieldsFromObjectPattern(bindingTarget, rootPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
296
+ else recordFieldUsage(pruneContext, dictionaryKey, "all");
297
+ return;
298
+ }
299
+ analyzeContentVariable(rootPath, bindingTarget.name);
259
300
  return;
260
301
  }
302
+ const parentNode = rootPath.parent;
261
303
  if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === rootPath.node) {
262
304
  let fieldName;
263
305
  if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) fieldName = parentNode.property.name;
@@ -275,24 +317,10 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
275
317
  } else markUntrackedBinding();
276
318
  return;
277
319
  }
278
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
279
- const variableName = parentNode.id.name;
280
- const variableBinding = rootPath.scope.getBinding(variableName);
281
- if (!variableBinding) {
282
- markUntrackedBinding();
283
- return;
284
- }
285
- const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
286
- if (analyzeContentBindingReferences(variableBinding, accessedTopLevelFieldNames)) markUntrackedBinding();
287
- else if (isSfcFile) deferFrameworkAnalysis(variableName);
288
- else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
289
- else recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
290
- return;
291
- }
292
320
  if (babelTypes.isExpressionStatement(parentNode)) return;
293
321
  markUntrackedBinding();
294
322
  };
295
- analyzeContentRoot(unwrapAwait(babelTypes, callExpressionPath));
323
+ analyzeContentRoot(callExpressionPath);
296
324
  };
297
325
 
298
326
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"analyzeContentUsage.mjs","names":[],"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.mjs","names":[],"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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"}
@@ -25,7 +25,7 @@ import { parse } from "@babel/parser";
25
25
  * };
26
26
  * ```
27
27
  */
28
- const intlayerExtractBabelPlugin = (_babel) => {
28
+ const extractBabelPlugin = (_babel) => {
29
29
  return {
30
30
  name: "babel-plugin-intlayer-extract",
31
31
  visitor: { Program: { enter(programPath, state) {
@@ -69,6 +69,16 @@ const intlayerExtractBabelPlugin = (_babel) => {
69
69
  } } }
70
70
  };
71
71
  };
72
+ /**
73
+ * Intlayer extract Babel plugin (see `extractBabelPlugin`), typed as Babel's
74
+ * `PluginTarget`.
75
+ *
76
+ * Babel types tuple options as `object` (`[PluginTarget<object>, object]`), so
77
+ * a plugin whose handlers read typed `this.opts` is rejected in
78
+ * `plugins: [[plugin, options]]` under `strict`. The options passed in the
79
+ * tuple are the typed options read by the handlers.
80
+ */
81
+ const intlayerExtractBabelPlugin = extractBabelPlugin;
72
82
 
73
83
  //#endregion
74
84
  export { intlayerExtractBabelPlugin };
@@ -1 +1 @@
1
- {"version":3,"file":"babel-plugin-intlayer-extract.mjs","names":[],"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,CAAC,0BAA0B,KAAK,SAAS,CAAC,CAAC,IACzC,cAAc,QAAQ,CACxB,GAEA;GAGF,MAAM,WAAmB,MAAM,KAAK,QAAQ;GAC5C,IAAI,CAAC,UAAU;GAEf,MAAM,YAAY,aAAa,KAAK,aAAa;GACjD,MAAM,cAAc,KAAK,eAAe,kBAAkB,QAAQ;GAElE,MAAM,EAAE,mBAAmB,KAAK,cAAc;GAE9C,MAAM,SAAS,mBAAmB,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,SAAS,MAAM,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,GAAG,SAAS,aAAa,WAAW,SAAS,EAAE,0BAA0B,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,KAC3I,EAAE,OAAO,QAAQ,CACnB;GACF,SAAS,OAAO;IACd,UACE,CACE,wCAAwC,aAAa,SAAS,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.mjs","names":[],"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,CAAC,0BAA0B,KAAK,SAAS,CAAC,CAAC,IACzC,cAAc,QAAQ,CACxB,GAEA;GAGF,MAAM,WAAmB,MAAM,KAAK,QAAQ;GAC5C,IAAI,CAAC,UAAU;GAEf,MAAM,YAAY,aAAa,KAAK,aAAa;GACjD,MAAM,cAAc,KAAK,eAAe,kBAAkB,QAAQ;GAElE,MAAM,EAAE,mBAAmB,KAAK,cAAc;GAE9C,MAAM,SAAS,mBAAmB,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,SAAS,MAAM,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,GAAG,SAAS,aAAa,WAAW,SAAS,EAAE,0BAA0B,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,KAC3I,EAAE,OAAO,QAAQ,CACnB;GACF,SAAS,OAAO;IACd,UACE,CACE,wCAAwC,aAAa,SAAS,KAAK,cAAc,OAAO,SAAS,QAAQ,CAAC,EAAE,IAC5G,KACF,GACA,EAAE,OAAO,QAAQ,CACnB;GACF;EACF,EACF,EACF;CACF;AACF;;;;;;;;;;AAWA,MAAa,6BACX"}
@@ -1,4 +1,4 @@
1
- import { unwrapAwait } from "./staticAstReaders.mjs";
1
+ import { resolveContentRoot, skipTransparentWrappers } from "./staticAstReaders.mjs";
2
2
  import { CHAINABLE_RUNTIME_METHOD_NAMES, NATIVE_CALLER_NAME_SET } from "./nativeCallers.mjs";
3
3
 
4
4
  //#region src/babel-plugin-intlayer-field-rename.ts
@@ -131,11 +131,13 @@ const serializeFieldRenameMap = (pruneContext) => {
131
131
  * as transparent pass-throughs: the rename map is kept unchanged and the walk
132
132
  * continues past the index. This means `content.field[0].sub` is handled
133
133
  * correctly — `field` and `sub` are both renamed while `[0]` is left intact.
134
+ * TypeScript casts are looked through as well: `(content.field as any).sub`.
134
135
  */
135
136
  const walkRenameChain = (babelTypes, startPath, currentRenameMap) => {
136
137
  let refPath = startPath;
137
138
  let renameMap = currentRenameMap;
138
139
  while (true) {
140
+ refPath = skipTransparentWrappers(babelTypes, refPath);
139
141
  const parentPath = refPath.parentPath;
140
142
  if (!parentPath) break;
141
143
  const parentNode = parentPath.node;
@@ -203,10 +205,7 @@ const renameObjectPatternProperties = (babelTypes, objectPattern, scopePath, ren
203
205
  if (babelTypes.isIdentifier(bindingTarget)) {
204
206
  const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);
205
207
  if (!localVarBinding) continue;
206
- for (const nestedRefPath of localVarBinding.referencePaths) {
207
- const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, nestedRefPath, renameEntry.children);
208
- walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
209
- }
208
+ for (const nestedRefPath of localVarBinding.referencePaths) renameFieldConsumers(babelTypes, nestedRefPath, renameEntry.children);
210
209
  }
211
210
  }
212
211
  };
@@ -234,6 +233,28 @@ const walkObjectDestructuring = (babelTypes, refPath, renameMap) => {
234
233
  renameObjectPatternProperties(babelTypes, parentNode.id, refPath, renameMap);
235
234
  };
236
235
  /**
236
+ * Renames the field accesses reachable from `refPath`, a reference to a
237
+ * content value: a member-access chain, then a destructuring assignment or an
238
+ * alias (`const table = content.summary.table`) of where the chain ends.
239
+ */
240
+ const renameFieldConsumers = (babelTypes, refPath, renameMap) => {
241
+ const chainEnd = walkRenameChain(babelTypes, refPath, renameMap);
242
+ walkObjectDestructuring(babelTypes, chainEnd.finalPath, chainEnd.finalRenameMap);
243
+ renameAliasReferences(babelTypes, chainEnd.finalPath, chainEnd.finalRenameMap);
244
+ return chainEnd;
245
+ };
246
+ /**
247
+ * Follows `const alias = <valuePath>` and renames the accesses made through
248
+ * `alias`, mirroring the analyser's alias tracking.
249
+ */
250
+ const renameAliasReferences = (babelTypes, valuePath, renameMap) => {
251
+ if (renameMap.size === 0) return;
252
+ const parentNode = valuePath.parent;
253
+ if (!babelTypes.isVariableDeclarator(parentNode) || !babelTypes.isIdentifier(parentNode.id) || parentNode.init !== valuePath.node) return;
254
+ const aliasBinding = valuePath.scope.getBinding(parentNode.id.name);
255
+ for (const aliasReferencePath of aliasBinding?.referencePaths ?? []) renameFieldConsumers(babelTypes, aliasReferencePath, renameMap);
256
+ };
257
+ /**
237
258
  * Renames every field access reachable from `rootPath`, whose value is the
238
259
  * dictionary content root.
239
260
  *
@@ -242,8 +263,7 @@ const walkObjectDestructuring = (babelTypes, refPath, renameMap) => {
242
263
  * `.onChange(cb)`.
243
264
  */
244
265
  const renameContentConsumers = (babelTypes, rootPath, renameMap) => {
245
- const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, rootPath, renameMap);
246
- walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
266
+ const { finalPath, finalRenameMap } = renameFieldConsumers(babelTypes, rootPath, renameMap);
247
267
  renameChainedRuntimeCall(babelTypes, finalPath, finalRenameMap);
248
268
  };
249
269
  /**
@@ -360,18 +380,16 @@ const renameIntlayerFieldAccesses = (babelTypes, programPath, pruneContext) => {
360
380
  if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;
361
381
  const fieldRenameMap = pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);
362
382
  if (!fieldRenameMap || fieldRenameMap.size === 0) return;
363
- const contentRootPath = unwrapAwait(babelTypes, callExpressionPath);
364
- const parentNode = contentRootPath.parent;
365
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
366
- walkObjectDestructuring(babelTypes, contentRootPath, fieldRenameMap);
367
- return;
368
- }
369
- if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === contentRootPath.node) {
370
- renameContentConsumers(babelTypes, contentRootPath, fieldRenameMap);
371
- return;
372
- }
373
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
374
- const variableBinding = callExpressionPath.scope.getBinding(parentNode.id.name);
383
+ const resolution = resolveContentRoot(babelTypes, callExpressionPath);
384
+ if (resolution.kind === "discarded") return;
385
+ const { rootPath: contentRootPath } = resolution;
386
+ if (resolution.kind === "binding") {
387
+ const { bindingTarget } = resolution;
388
+ if (babelTypes.isObjectPattern(bindingTarget)) {
389
+ renameObjectPatternProperties(babelTypes, bindingTarget, contentRootPath, fieldRenameMap);
390
+ return;
391
+ }
392
+ const variableBinding = contentRootPath.scope.getBinding(bindingTarget.name);
375
393
  if (!variableBinding) return;
376
394
  for (const variableReferencePath of variableBinding.referencePaths) {
377
395
  renameContentConsumers(babelTypes, variableReferencePath, fieldRenameMap);
@@ -381,7 +399,10 @@ const renameIntlayerFieldAccesses = (babelTypes, programPath, pruneContext) => {
381
399
  if (callPath) renameContentConsumers(babelTypes, callPath, fieldRenameMap);
382
400
  }
383
401
  }
402
+ return;
384
403
  }
404
+ const parentNode = contentRootPath.parent;
405
+ if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === contentRootPath.node) renameContentConsumers(babelTypes, contentRootPath, fieldRenameMap);
385
406
  } });
386
407
  };
387
408
  /**