@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.
- package/dist/cjs/analyzeContentUsage.cjs +54 -26
- package/dist/cjs/analyzeContentUsage.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-extract.cjs +11 -1
- package/dist/cjs/babel-plugin-intlayer-extract.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +39 -18
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +11 -1
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-purge.cjs +2 -0
- package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -1
- package/dist/cjs/getExtractPluginOptions.cjs.map +1 -1
- package/dist/cjs/staticAstReaders.cjs +96 -0
- package/dist/cjs/staticAstReaders.cjs.map +1 -1
- package/dist/cjs/transformers.cjs +36 -7
- package/dist/cjs/transformers.cjs.map +1 -1
- package/dist/esm/analyzeContentUsage.mjs +55 -27
- package/dist/esm/analyzeContentUsage.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-extract.mjs +11 -1
- package/dist/esm/babel-plugin-intlayer-extract.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs +40 -19
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-optimize.mjs +11 -1
- package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-purge.mjs +2 -0
- package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -1
- package/dist/esm/getExtractPluginOptions.mjs +1 -1
- package/dist/esm/getExtractPluginOptions.mjs.map +1 -1
- package/dist/esm/staticAstReaders.mjs +94 -1
- package/dist/esm/staticAstReaders.mjs.map +1 -1
- package/dist/esm/transformers.mjs +36 -7
- package/dist/esm/transformers.mjs.map +1 -1
- package/dist/types/analyzeContentUsage.d.ts +6 -1
- package/dist/types/analyzeContentUsage.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-extract.d.ts +8 -22
- package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-optimize.d.ts +8 -112
- package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -1
- package/dist/types/staticAstReaders.d.ts +41 -0
- package/dist/types/staticAstReaders.d.ts.map +1 -1
- package/dist/types/transformers.d.ts +8 -1
- package/dist/types/transformers.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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 = (
|
|
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
|
|
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
|
-
*
|
|
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
|
-
* `
|
|
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 = (
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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(
|
|
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
|
|
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 */\
|
|
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 {
|
|
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 } =
|
|
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
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
/**
|