@intlayer/babel 9.3.1 → 9.3.3
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/babel-plugin-intlayer-field-rename.cjs +66 -8
- package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +142 -53
- package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
- package/dist/cjs/extractContent/babelProcessor.cjs +7 -8
- package/dist/cjs/extractContent/babelProcessor.cjs.map +1 -1
- package/dist/cjs/extractContent/extractContent.cjs +3 -4
- package/dist/cjs/extractContent/extractContent.cjs.map +1 -1
- package/dist/cjs/extractContent/index.cjs +0 -1
- package/dist/cjs/extractContent/utils/constants.cjs +0 -13
- package/dist/cjs/extractContent/utils/constants.cjs.map +1 -1
- package/dist/cjs/extractContent/utils/index.cjs +1 -4
- package/dist/cjs/index.cjs +1 -1
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs +67 -9
- package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +142 -54
- package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
- package/dist/esm/extractContent/babelProcessor.mjs +1 -2
- package/dist/esm/extractContent/babelProcessor.mjs.map +1 -1
- package/dist/esm/extractContent/extractContent.mjs +1 -2
- package/dist/esm/extractContent/extractContent.mjs.map +1 -1
- package/dist/esm/extractContent/index.mjs +2 -2
- package/dist/esm/extractContent/utils/constants.mjs +1 -13
- package/dist/esm/extractContent/utils/constants.mjs.map +1 -1
- package/dist/esm/extractContent/utils/index.mjs +2 -3
- package/dist/esm/index.mjs +3 -3
- package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +16 -1
- package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
- package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
- package/dist/types/extractContent/extractContent.d.ts.map +1 -1
- package/dist/types/extractContent/index.d.ts +2 -2
- package/dist/types/extractContent/utils/constants.d.ts +1 -7
- package/dist/types/extractContent/utils/constants.d.ts.map +1 -1
- package/dist/types/extractContent/utils/index.d.ts +2 -3
- package/dist/types/index.d.ts +3 -3
- package/package.json +10 -10
- package/dist/cjs/extractContent/utils/shouldExtract.cjs +0 -38
- package/dist/cjs/extractContent/utils/shouldExtract.cjs.map +0 -1
- package/dist/esm/extractContent/utils/shouldExtract.mjs +0 -37
- package/dist/esm/extractContent/utils/shouldExtract.mjs.map +0 -1
- package/dist/types/extractContent/utils/shouldExtract.d.ts +0 -15
- package/dist/types/extractContent/utils/shouldExtract.d.ts.map +0 -1
|
@@ -234,6 +234,69 @@ const walkObjectDestructuring = (babelTypes, refPath, renameMap) => {
|
|
|
234
234
|
renameObjectPatternProperties(babelTypes, parentNode.id, refPath, renameMap);
|
|
235
235
|
};
|
|
236
236
|
/**
|
|
237
|
+
* Renames every field access reachable from `rootPath`, whose value is the
|
|
238
|
+
* dictionary content root.
|
|
239
|
+
*
|
|
240
|
+
* Combines the three ways a content root is consumed: a member-access chain,
|
|
241
|
+
* a destructuring assignment, and a chainable runtime helper such as
|
|
242
|
+
* `.onChange(cb)`.
|
|
243
|
+
*/
|
|
244
|
+
const renameContentConsumers = (babelTypes, rootPath, renameMap) => {
|
|
245
|
+
const { finalPath, finalRenameMap } = walkRenameChain(babelTypes, rootPath, renameMap);
|
|
246
|
+
walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
|
|
247
|
+
renameChainedRuntimeCall(babelTypes, finalPath, finalRenameMap);
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Handles `content.onChange(cb)` — a runtime subscription rather than a field
|
|
251
|
+
* access (see {@link CHAINABLE_RUNTIME_METHOD_NAMES}).
|
|
252
|
+
*
|
|
253
|
+
* `walkRenameChain` stops at `onChange` because it is absent from the rename
|
|
254
|
+
* map, which would leave the callback body reading the original field names
|
|
255
|
+
* while the compiled JSON has already been renamed. This restarts the rename
|
|
256
|
+
* walk on both sides of the call:
|
|
257
|
+
*
|
|
258
|
+
* - the callback's first parameter receives the same content root, so its
|
|
259
|
+
* accesses are renamed with the same map;
|
|
260
|
+
* - the helper returns the content object, so the chain continues past it.
|
|
261
|
+
*
|
|
262
|
+
* @param refPath - Path whose value is the content root (the object the
|
|
263
|
+
* chainable method would be called on).
|
|
264
|
+
*/
|
|
265
|
+
const renameChainedRuntimeCall = (babelTypes, refPath, renameMap) => {
|
|
266
|
+
if (renameMap.size === 0) return;
|
|
267
|
+
const memberPath = refPath.parentPath;
|
|
268
|
+
if (!memberPath) return;
|
|
269
|
+
const memberNode = memberPath.node;
|
|
270
|
+
if (!babelTypes.isMemberExpression(memberNode) && !babelTypes.isOptionalMemberExpression(memberNode) || memberNode.object !== refPath.node) return;
|
|
271
|
+
const propertyNode = memberNode.property;
|
|
272
|
+
const methodName = memberNode.computed ? babelTypes.isStringLiteral(propertyNode) ? propertyNode.value : void 0 : babelTypes.isIdentifier(propertyNode) ? propertyNode.name : void 0;
|
|
273
|
+
if (!methodName || !require_babel_plugin_intlayer_usage_analyzer.CHAINABLE_RUNTIME_METHOD_NAMES.has(methodName)) return;
|
|
274
|
+
const callPath = memberPath.parentPath;
|
|
275
|
+
if (!callPath) return;
|
|
276
|
+
if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) return;
|
|
277
|
+
if (callPath.node.callee !== memberNode) return;
|
|
278
|
+
const argumentPaths = callPath.get("arguments");
|
|
279
|
+
for (const argumentPath of argumentPaths) {
|
|
280
|
+
if (!argumentPath.isArrowFunctionExpression() && !argumentPath.isFunctionExpression()) continue;
|
|
281
|
+
const [firstParameterPath] = argumentPath.get("params");
|
|
282
|
+
if (!firstParameterPath) continue;
|
|
283
|
+
if (firstParameterPath.isObjectPattern()) {
|
|
284
|
+
renameObjectPatternProperties(babelTypes, firstParameterPath.node, argumentPath, renameMap);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (!firstParameterPath.isIdentifier()) continue;
|
|
288
|
+
const parameterBinding = argumentPath.scope.getBinding(firstParameterPath.node.name);
|
|
289
|
+
if (!parameterBinding) continue;
|
|
290
|
+
for (const parameterRefPath of parameterBinding.referencePaths) renameContentConsumers(babelTypes, parameterRefPath, renameMap);
|
|
291
|
+
}
|
|
292
|
+
renameContentConsumers(babelTypes, callPath, renameMap);
|
|
293
|
+
const declaratorNode = callPath.parent;
|
|
294
|
+
if (babelTypes.isVariableDeclarator(declaratorNode) && babelTypes.isIdentifier(declaratorNode.id) && declaratorNode.init === callPath.node) {
|
|
295
|
+
const resultBinding = callPath.scope.getBinding(declaratorNode.id.name);
|
|
296
|
+
for (const resultRefPath of resultBinding?.referencePaths ?? []) renameContentConsumers(babelTypes, resultRefPath, renameMap);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
/**
|
|
237
300
|
* Collects the local aliases under which `useIntlayer` / `getIntlayer` are
|
|
238
301
|
* imported in the given program (e.g. `import { useIntlayer as useI } …`
|
|
239
302
|
* yields `useI` → `useIntlayer`).
|
|
@@ -303,23 +366,18 @@ const renameIntlayerFieldAccesses = (babelTypes, programPath, pruneContext) => {
|
|
|
303
366
|
return;
|
|
304
367
|
}
|
|
305
368
|
if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === callExpressionPath.node) {
|
|
306
|
-
|
|
307
|
-
walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
|
|
369
|
+
renameContentConsumers(babelTypes, callExpressionPath, fieldRenameMap);
|
|
308
370
|
return;
|
|
309
371
|
}
|
|
310
372
|
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
|
|
311
373
|
const variableBinding = callExpressionPath.scope.getBinding(parentNode.id.name);
|
|
312
374
|
if (!variableBinding) return;
|
|
313
375
|
for (const variableReferencePath of variableBinding.referencePaths) {
|
|
314
|
-
|
|
315
|
-
walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);
|
|
376
|
+
renameContentConsumers(babelTypes, variableReferencePath, fieldRenameMap);
|
|
316
377
|
const refParent = variableReferencePath.parent;
|
|
317
378
|
if ((babelTypes.isCallExpression(refParent) || babelTypes.isOptionalCallExpression(refParent)) && refParent.callee === variableReferencePath.node) {
|
|
318
379
|
const callPath = variableReferencePath.parentPath;
|
|
319
|
-
if (callPath)
|
|
320
|
-
const { finalPath: signalFinalPath, finalRenameMap: signalFinalRenameMap } = walkRenameChain(babelTypes, callPath, fieldRenameMap);
|
|
321
|
-
walkObjectDestructuring(babelTypes, signalFinalPath, signalFinalRenameMap);
|
|
322
|
-
}
|
|
380
|
+
if (callPath) renameContentConsumers(babelTypes, callPath, fieldRenameMap);
|
|
323
381
|
}
|
|
324
382
|
}
|
|
325
383
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-field-rename.cjs","names":["INTLAYER_CALLER_NAMES"],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"sourcesContent":["import type { NodePath, PluginObject } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n INTLAYER_CALLER_NAMES,\n type IntlayerCallerName,\n type NestedRenameEntry,\n type NestedRenameMap,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\n\n// ── Field-name helpers ────────────────────────────────────────────────────────\n\n/**\n * Intlayer internal property names that must never be used as short-name\n * targets. These appear inside content-node values (e.g. `{ nodeType:\n * \"translation\", … }`) and are read by the intlayer runtime.\n */\nconst RESERVED_CONTENT_FIELD_NAMES = new Set(['nodeType']);\n\n/** Alphabet used to derive short field aliases (`a`, `b`, …, `aa`, `ab`, …). */\nconst SHORT_NAME_ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n\n/**\n * Converts a zero-based index to a short alphabetic identifier.\n * 0 → 'a', 1 → 'b', …, 25 → 'z', 26 → 'aa', 27 → 'ab', …\n */\nexport const generateShortFieldName = (index: number): string => {\n if (!Number.isInteger(index) || index < 0) {\n throw new RangeError(\n `generateShortFieldName expects a non-negative integer index, received: ${index}`\n );\n }\n\n const remainder = index % SHORT_NAME_ALPHABET.length;\n const quotient = Math.floor(index / SHORT_NAME_ALPHABET.length);\n\n return quotient === 0 && SHORT_NAME_ALPHABET[remainder]\n ? SHORT_NAME_ALPHABET[remainder]\n : generateShortFieldName(quotient - 1) + SHORT_NAME_ALPHABET[remainder];\n};\n\n/**\n * Recursively builds a `NestedRenameMap` from a compiled dictionary content\n * value by traversing the intlayer node structure.\n *\n * Rules:\n * - If the value has `nodeType: 'translation'`, user-defined fields live\n * inside `translation[locale]`. Recurse into the first locale's value.\n * - All other intlayer runtime nodes (enumeration, condition, gender, …) are\n * treated as leaves — their internal keys must never be renamed.\n * - Arrays produce an empty children map. Array elements are not traversed\n * because consumers may access them via `.map()` / `.filter()` callbacks,\n * which the source-code rename walk cannot enter. Renaming element fields\n * in the JSON without the matching source-code rename would produce\n * mismatched key names and runtime crashes.\n * The `[0]` pass-through in `walkRenameChain` is preserved so that direct\n * indexed access (`field[0].sub`) silently terminates at the empty children\n * map without breaking anything.\n * - Plain objects are user-defined records: ALL non-reserved keys are renamed\n * with short alphabetic aliases (a, b, c, …) and each value is recursed into.\n * - Primitives produce an empty map (no further renaming).\n *\n * The rename map is built from ALL user-defined fields (not just consumed ones).\n * Both the JSON rename and the source-code rename use the same map, so the\n * short names are always consistent regardless of which fields are pruned.\n *\n * @param contentValue - The dictionary content value to analyse.\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown\n): NestedRenameMap => {\n if (\n !contentValue ||\n typeof contentValue !== 'object' ||\n Array.isArray(contentValue)\n ) {\n return new Map();\n }\n\n const record = contentValue as Record<string, unknown>;\n\n // Any object with `nodeType: string` is an intlayer runtime node.\n if (typeof record.nodeType === 'string') {\n // Translation node: user-defined fields live inside translation[locale].\n if (\n record.nodeType === 'translation' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const firstLocaleValue = Object.values(\n record.translation as Record<string, unknown>\n )[0];\n return buildNestedRenameMapFromContent(firstLocaleValue);\n }\n\n // All other intlayer nodes (enumeration, pluralization, conditions, …)\n // resolve to a single value at runtime through a call the source-code\n // rename walk cannot traverse (e.g. `content.myEnum(count)`), and the\n // JSON renamers only descend into translation nodes. Treat them as\n // leaves so the source and JSON sides always stay consistent.\n return new Map();\n }\n\n // Exclude React elements from being treated as user-defined records.\n // JSX elements in the compiled JSON have a $$typeof property (usually Symbol(react.element)).\n if (\n record.$$typeof ||\n (record.type && record.props && Object.hasOwn(record, 'ref'))\n ) {\n return new Map();\n }\n\n // User-defined record: rename ALL non-reserved keys with stable alphabetic\n // aliases sorted alphabetically so the mapping is deterministic.\n const sortedKeys = Object.keys(record)\n .filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key))\n .sort();\n\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n\n if (!key) continue;\n\n const children = buildNestedRenameMapFromContent(record[key]);\n\n renameMap.set(key, { shortName: generateShortFieldName(i), children });\n }\n\n return renameMap;\n};\n\n/**\n * Walks `fieldPath` through a {@link NestedRenameMap} and returns the entry at\n * the final path segment, or `undefined` when any segment is missing (e.g. the\n * path crosses an array or leaf value whose children are not mapped).\n *\n * Used to locate the rename entry of an opaquely-consumed content value so its\n * children map can be cleared before the JSON rename is applied.\n *\n * @param renameMap - The root rename map to walk.\n * @param fieldPath - Original (pre-rename) field names from the content root.\n */\nexport const getNestedRenameEntryAtPath = (\n renameMap: NestedRenameMap,\n fieldPath: readonly string[]\n): NestedRenameEntry | undefined => {\n let currentRenameMap = renameMap;\n let renameEntry: NestedRenameEntry | undefined;\n\n for (const fieldName of fieldPath) {\n renameEntry = currentRenameMap.get(fieldName);\n if (!renameEntry) return undefined;\n currentRenameMap = renameEntry.children;\n }\n\n return renameEntry;\n};\n\n// ── Serialisation for non-Babel consumers ─────────────────────────────────────\n\n/**\n * JSON-serialisable form of a {@link NestedRenameEntry}.\n *\n * Mirrors `FieldRenameNode` in the `@intlayer/swc` crate — both sides must stay\n * in sync.\n */\nexport type SerializedFieldRenameEntry = {\n /** Short alphabetic alias the field is renamed to (`'a'`, `'b'`, …). */\n shortName: string;\n /** Rename table of the fields nested inside this one. */\n children: SerializedFieldRenameMap;\n};\n\n/** JSON-serialisable form of a {@link NestedRenameMap}. */\nexport type SerializedFieldRenameMap = Record<\n string,\n SerializedFieldRenameEntry\n>;\n\n/** Recursively converts a {@link NestedRenameMap} to its serialisable form. */\nconst serializeNestedRenameMap = (\n renameMap: NestedRenameMap\n): SerializedFieldRenameMap => {\n const serialized: SerializedFieldRenameMap = {};\n\n for (const [fieldName, renameEntry] of renameMap) {\n serialized[fieldName] = {\n shortName: renameEntry.shortName,\n children: serializeNestedRenameMap(renameEntry.children),\n };\n }\n\n return serialized;\n};\n\n/**\n * Converts the field-rename tables of a {@link PruneContext} into a plain JSON\n * object, keyed by dictionary key.\n *\n * Consumed by bundler integrations that cannot run the Babel rename plugin and\n * must forward the tables to another transformer — currently the `@intlayer/swc`\n * Wasm plugin used by Next.js, which rewrites the source accesses itself.\n *\n * Dictionaries flagged as structural edge cases are omitted: the JSON pipeline\n * leaves them untouched, so their source accesses must stay untouched too.\n * Empty tables are omitted as well, so the result is empty when there is\n * nothing to rename.\n */\nexport const serializeFieldRenameMap = (\n pruneContext: PruneContext\n): Record<string, SerializedFieldRenameMap> => {\n const serialized: Record<string, SerializedFieldRenameMap> = {};\n\n for (const [\n dictionaryKey,\n renameMap,\n ] of pruneContext.dictionaryKeyToFieldRenameMap) {\n if (renameMap.size === 0) continue;\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) continue;\n\n serialized[dictionaryKey] = serializeNestedRenameMap(renameMap);\n }\n\n return serialized;\n};\n\n// ── Field-rename Babel plugin ─────────────────────────────────────────────────\n\n/**\n * Walks a MemberExpression chain starting from `startPath`, renaming each\n * property found in `currentRenameMap` at the corresponding nesting level.\n *\n * Numeric computed accesses (array indices such as `[0]`, `[1]`) are treated\n * as transparent pass-throughs: the rename map is kept unchanged and the walk\n * continues past the index. This means `content.field[0].sub` is handled\n * correctly — `field` and `sub` are both renamed while `[0]` is left intact.\n */\nconst walkRenameChain = (\n babelTypes: typeof BabelTypes,\n startPath: NodePath<BabelTypes.Node>,\n currentRenameMap: NestedRenameMap\n): {\n finalPath: NodePath<BabelTypes.Node>;\n finalRenameMap: NestedRenameMap;\n} => {\n let refPath: NodePath<BabelTypes.Node> = startPath;\n let renameMap = currentRenameMap;\n\n while (true) {\n const parentPath = refPath.parentPath;\n if (!parentPath) break;\n\n const parentNode = parentPath.node;\n\n if (\n (!babelTypes.isMemberExpression(parentNode) &&\n !babelTypes.isOptionalMemberExpression(parentNode)) ||\n (parentNode as BabelTypes.MemberExpression).object !== refPath.node\n ) {\n break;\n }\n\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …): advance past the array accessor\n // without touching the rename map. The next iteration will attempt to\n // rename the property that follows the index.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n refPath = parentPath;\n continue;\n }\n\n // Nothing left to rename at this level — stop.\n if (renameMap.size === 0) break;\n\n let fieldName: string | undefined;\n\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n fieldName = memberNode.property.name;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n fieldName = memberNode.property.value;\n } else {\n break; // dynamic computed key – stop\n }\n\n const renameEntry = renameMap.get(fieldName);\n if (!renameEntry) break; // not in map – stop\n\n // Apply the rename\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n memberNode.property.name = renameEntry.shortName;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n memberNode.property.value = renameEntry.shortName;\n }\n\n refPath = parentPath;\n renameMap = renameEntry.children;\n }\n\n return { finalPath: refPath, finalRenameMap: renameMap };\n};\n\n/**\n * Renames the properties of a destructuring `ObjectPattern` using `renameMap`,\n * recursing into nested patterns and following the references of bound local\n * variables.\n *\n * Handled property shapes:\n *\n * { fieldA } → { shortA: fieldA }\n * { fieldA: localVar } → { shortA: localVar }\n * { fieldA = fallback } → { shortA: fieldA = fallback }\n * { fieldA: { nested } } → { shortA: { shortN: nested } }\n * { ['fieldA']: x } → { ['shortA']: x }\n * { [dynamicKey]: x } → left untouched (dynamic key – not renamable)\n *\n * @param babelTypes - Babel types helper.\n * @param objectPattern - The destructuring pattern to rename.\n * @param scopePath - A path whose scope resolves the pattern's bindings.\n * @param renameMap - Rename table for the current nesting level.\n */\nconst renameObjectPatternProperties = (\n babelTypes: typeof BabelTypes,\n objectPattern: BabelTypes.ObjectPattern,\n scopePath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n for (const property of objectPattern.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Computed keys: a static string key ({ ['title']: x }) is renamable in\n // place; any other computed key is dynamic and must be left untouched —\n // replacing it with an identifier would create a variable reference.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\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 renameEntry = renameMap.get(keyName);\n if (!renameEntry) continue;\n\n if (property.computed) {\n // { ['fieldA']: x } → { ['shortA']: x }\n (property.key as BabelTypes.StringLiteral).value = renameEntry.shortName;\n } else {\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n }\n property.key = babelTypes.identifier(renameEntry.shortName);\n }\n\n if (renameEntry.children.size === 0) continue;\n\n // A default value ({ fieldA = fallback }) wraps the actual binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n // Nested pattern: const { fieldA: { nested } } = … — rename its keys\n // with this entry's children map.\n if (babelTypes.isObjectPattern(bindingTarget)) {\n renameObjectPatternProperties(\n babelTypes,\n bindingTarget,\n scopePath,\n renameEntry.children\n );\n continue;\n }\n\n // Identifier binding: recursively walk references to the local variable.\n if (babelTypes.isIdentifier(bindingTarget)) {\n const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);\n if (!localVarBinding) continue;\n\n for (const nestedRefPath of localVarBinding.referencePaths) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n nestedRefPath,\n renameEntry.children\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n }\n }\n }\n};\n\n/**\n * Walks an object-destructuring assignment whose right-hand side is `refPath`,\n * renaming each destructured key that is found in `renameMap`.\n *\n * Handles the \"secondary destructuring\" pattern that `walkRenameChain` cannot\n * reach because the reference is not a MemberExpression:\n *\n * const { webhooksSection } = useIntlayer('build-settings');\n * const { modal, validationErrors } = webhooksSection;\n * → const { a: modal, b: validationErrors } = webhooksSection;\n *\n * After renaming each key, {@link renameObjectPatternProperties} recursively\n * walks references to the newly-bound local variables, calling both\n * `walkRenameChain` (for subsequent member-access chains like\n * `validationErrors.invalidUrl`) and this function again (for further levels\n * of secondary destructuring).\n */\nconst walkObjectDestructuring = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const parentNode = refPath.parent;\n\n // Only handle: const { a, b } = refVar\n if (\n !babelTypes.isVariableDeclarator(parentNode) ||\n !babelTypes.isObjectPattern(parentNode.id) ||\n parentNode.init !== refPath.node\n ) {\n return;\n }\n\n renameObjectPatternProperties(babelTypes, parentNode.id, refPath, renameMap);\n};\n\n/**\n * Collects the local aliases under which `useIntlayer` / `getIntlayer` are\n * imported in the given program (e.g. `import { useIntlayer as useI } …`\n * yields `useI` → `useIntlayer`).\n *\n * Import declarations can only appear at the top level of a module, so the\n * program body is scanned directly instead of traversing the whole AST.\n */\nconst collectIntlayerCallerLocalNames = (\n babelTypes: typeof BabelTypes,\n programNode: BabelTypes.Program\n): Map<string, string> => {\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n for (const statement of programNode.body) {\n if (!babelTypes.isImportDeclaration(statement)) continue;\n\n for (const importSpecifier of statement.specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(importSpecifier.imported)\n ? importSpecifier.imported.name\n : importSpecifier.imported.value;\n\n if (INTLAYER_CALLER_NAMES.includes(importedName as IntlayerCallerName)) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n }\n\n return intlayerCallerLocalNameMap;\n};\n\n/**\n * Rewrites dictionary content field accesses in the given program to the\n * short aliases defined in `pruneContext.dictionaryKeyToFieldRenameMap`.\n *\n * Handled patterns (mirrors the usage analyser):\n *\n * const { fieldA, fieldB } = useIntlayer('key')\n * → const { shortA: fieldA, shortB: fieldB } = useIntlayer('key')\n *\n * useIntlayer('key').fieldA\n * → useIntlayer('key').shortA\n *\n * const result = useIntlayer('key'); result.fieldA\n * → const result = useIntlayer('key'); result.shortA\n *\n * const { fieldA } = useIntlayer('key');\n * const { nested } = fieldA; // secondary destructuring\n * → const { shortA: fieldA } = useIntlayer('key');\n * const { shortN: nested } = fieldA;\n *\n * Exposed as a standalone function so that both\n * {@link makeFieldRenameBabelPlugin} and `intlayerMinifyBabelPlugin` can\n * invoke it from their own `Program.exit` visitors.\n */\nexport const renameIntlayerFieldAccesses = (\n babelTypes: typeof BabelTypes,\n programPath: NodePath<BabelTypes.Program>,\n pruneContext: PruneContext\n): void => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;\n\n const intlayerCallerLocalNameMap = collectIntlayerCallerLocalNames(\n babelTypes,\n programPath.node\n );\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Visit all useIntlayer / getIntlayer call-sites and rename field accesses\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (!localCallerName || !intlayerCallerLocalNameMap.has(localCallerName))\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1 &&\n firstArgument.quasis[0]\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return;\n\n // Dictionaries flagged as structural edge cases are left completely\n // untouched by the JSON pipeline — keep their source accesses untouched\n // too so both sides stay consistent.\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (!fieldRenameMap || fieldRenameMap.size === 0) return;\n\n const parentNode = callExpressionPath.parent;\n\n // ── Case 1: const { fieldA, fieldB } = useIntlayer('key') ────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n walkObjectDestructuring(babelTypes, callExpressionPath, fieldRenameMap);\n return;\n }\n\n // ── Case 2: useIntlayer('key').fieldA.nested ─────────────────────\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n callExpressionPath,\n fieldRenameMap\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n return;\n }\n\n // ── Case 3: const result = useIntlayer('key'); result.fieldA ─────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableBinding = callExpressionPath.scope.getBinding(\n parentNode.id.name\n );\n if (!variableBinding) return;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n // Direct access: result.fieldA or const { fieldA } = result\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n\n // Signal accessor: result().fieldA or const { fieldA } = result()\n // walkRenameChain stops at a CallExpression parent, so we need to\n // start a new walk from the call-expression node itself.\n const refParent = variableReferencePath.parent;\n if (\n (babelTypes.isCallExpression(refParent) ||\n babelTypes.isOptionalCallExpression(refParent)) &&\n (refParent as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callPath = variableReferencePath.parentPath;\n if (callPath) {\n const {\n finalPath: signalFinalPath,\n finalRenameMap: signalFinalRenameMap,\n } = walkRenameChain(babelTypes, callPath, fieldRenameMap);\n walkObjectDestructuring(\n babelTypes,\n signalFinalPath,\n signalFinalRenameMap\n );\n }\n }\n }\n }\n },\n });\n};\n\n/**\n * Creates a Babel plugin that rewrites dictionary content field accesses in\n * source files to their short aliases via\n * {@link renameIntlayerFieldAccesses}.\n *\n * This plugin must run in a separate `transformAsync` pass **before**\n * `intlayerOptimizeBabelPlugin`, because the latter replaces `useIntlayer`\n * with `useDictionary`, erasing the dictionary-key information needed here.\n */\nexport const makeFieldRenameBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => ({\n name: 'intlayer-field-rename',\n visitor: {\n Program: {\n exit: (programPath) => {\n renameIntlayerFieldAccesses(babelTypes, programPath, pruneContext);\n },\n },\n },\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-field-rename.cjs","names":["CHAINABLE_RUNTIME_METHOD_NAMES","INTLAYER_CALLER_NAMES"],"sources":["../../src/babel-plugin-intlayer-field-rename.ts"],"sourcesContent":["import type { NodePath, PluginObject } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n CHAINABLE_RUNTIME_METHOD_NAMES,\n INTLAYER_CALLER_NAMES,\n type IntlayerCallerName,\n type NestedRenameEntry,\n type NestedRenameMap,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\n\n// ── Field-name helpers ────────────────────────────────────────────────────────\n\n/**\n * Intlayer internal property names that must never be used as short-name\n * targets. These appear inside content-node values (e.g. `{ nodeType:\n * \"translation\", … }`) and are read by the intlayer runtime.\n */\nconst RESERVED_CONTENT_FIELD_NAMES = new Set(['nodeType']);\n\n/** Alphabet used to derive short field aliases (`a`, `b`, …, `aa`, `ab`, …). */\nconst SHORT_NAME_ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n\n/**\n * Converts a zero-based index to a short alphabetic identifier.\n * 0 → 'a', 1 → 'b', …, 25 → 'z', 26 → 'aa', 27 → 'ab', …\n */\nexport const generateShortFieldName = (index: number): string => {\n if (!Number.isInteger(index) || index < 0) {\n throw new RangeError(\n `generateShortFieldName expects a non-negative integer index, received: ${index}`\n );\n }\n\n const remainder = index % SHORT_NAME_ALPHABET.length;\n const quotient = Math.floor(index / SHORT_NAME_ALPHABET.length);\n\n return quotient === 0 && SHORT_NAME_ALPHABET[remainder]\n ? SHORT_NAME_ALPHABET[remainder]\n : generateShortFieldName(quotient - 1) + SHORT_NAME_ALPHABET[remainder];\n};\n\n/**\n * Recursively builds a `NestedRenameMap` from a compiled dictionary content\n * value by traversing the intlayer node structure.\n *\n * Rules:\n * - If the value has `nodeType: 'translation'`, user-defined fields live\n * inside `translation[locale]`. Recurse into the first locale's value.\n * - All other intlayer runtime nodes (enumeration, condition, gender, …) are\n * treated as leaves — their internal keys must never be renamed.\n * - Arrays produce an empty children map. Array elements are not traversed\n * because consumers may access them via `.map()` / `.filter()` callbacks,\n * which the source-code rename walk cannot enter. Renaming element fields\n * in the JSON without the matching source-code rename would produce\n * mismatched key names and runtime crashes.\n * The `[0]` pass-through in `walkRenameChain` is preserved so that direct\n * indexed access (`field[0].sub`) silently terminates at the empty children\n * map without breaking anything.\n * - Plain objects are user-defined records: ALL non-reserved keys are renamed\n * with short alphabetic aliases (a, b, c, …) and each value is recursed into.\n * - Primitives produce an empty map (no further renaming).\n *\n * The rename map is built from ALL user-defined fields (not just consumed ones).\n * Both the JSON rename and the source-code rename use the same map, so the\n * short names are always consistent regardless of which fields are pruned.\n *\n * @param contentValue - The dictionary content value to analyse.\n */\nexport const buildNestedRenameMapFromContent = (\n contentValue: unknown\n): NestedRenameMap => {\n if (\n !contentValue ||\n typeof contentValue !== 'object' ||\n Array.isArray(contentValue)\n ) {\n return new Map();\n }\n\n const record = contentValue as Record<string, unknown>;\n\n // Any object with `nodeType: string` is an intlayer runtime node.\n if (typeof record.nodeType === 'string') {\n // Translation node: user-defined fields live inside translation[locale].\n if (\n record.nodeType === 'translation' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const firstLocaleValue = Object.values(\n record.translation as Record<string, unknown>\n )[0];\n return buildNestedRenameMapFromContent(firstLocaleValue);\n }\n\n // All other intlayer nodes (enumeration, pluralization, conditions, …)\n // resolve to a single value at runtime through a call the source-code\n // rename walk cannot traverse (e.g. `content.myEnum(count)`), and the\n // JSON renamers only descend into translation nodes. Treat them as\n // leaves so the source and JSON sides always stay consistent.\n return new Map();\n }\n\n // Exclude React elements from being treated as user-defined records.\n // JSX elements in the compiled JSON have a $$typeof property (usually Symbol(react.element)).\n if (\n record.$$typeof ||\n (record.type && record.props && Object.hasOwn(record, 'ref'))\n ) {\n return new Map();\n }\n\n // User-defined record: rename ALL non-reserved keys with stable alphabetic\n // aliases sorted alphabetically so the mapping is deterministic.\n const sortedKeys = Object.keys(record)\n .filter((key) => !RESERVED_CONTENT_FIELD_NAMES.has(key))\n .sort();\n\n const renameMap: NestedRenameMap = new Map();\n\n for (let i = 0; i < sortedKeys.length; i++) {\n const key = sortedKeys[i];\n\n if (!key) continue;\n\n const children = buildNestedRenameMapFromContent(record[key]);\n\n renameMap.set(key, { shortName: generateShortFieldName(i), children });\n }\n\n return renameMap;\n};\n\n/**\n * Walks `fieldPath` through a {@link NestedRenameMap} and returns the entry at\n * the final path segment, or `undefined` when any segment is missing (e.g. the\n * path crosses an array or leaf value whose children are not mapped).\n *\n * Used to locate the rename entry of an opaquely-consumed content value so its\n * children map can be cleared before the JSON rename is applied.\n *\n * @param renameMap - The root rename map to walk.\n * @param fieldPath - Original (pre-rename) field names from the content root.\n */\nexport const getNestedRenameEntryAtPath = (\n renameMap: NestedRenameMap,\n fieldPath: readonly string[]\n): NestedRenameEntry | undefined => {\n let currentRenameMap = renameMap;\n let renameEntry: NestedRenameEntry | undefined;\n\n for (const fieldName of fieldPath) {\n renameEntry = currentRenameMap.get(fieldName);\n if (!renameEntry) return undefined;\n currentRenameMap = renameEntry.children;\n }\n\n return renameEntry;\n};\n\n// ── Serialisation for non-Babel consumers ─────────────────────────────────────\n\n/**\n * JSON-serialisable form of a {@link NestedRenameEntry}.\n *\n * Mirrors `FieldRenameNode` in the `@intlayer/swc` crate — both sides must stay\n * in sync.\n */\nexport type SerializedFieldRenameEntry = {\n /** Short alphabetic alias the field is renamed to (`'a'`, `'b'`, …). */\n shortName: string;\n /** Rename table of the fields nested inside this one. */\n children: SerializedFieldRenameMap;\n};\n\n/** JSON-serialisable form of a {@link NestedRenameMap}. */\nexport type SerializedFieldRenameMap = Record<\n string,\n SerializedFieldRenameEntry\n>;\n\n/** Recursively converts a {@link NestedRenameMap} to its serialisable form. */\nconst serializeNestedRenameMap = (\n renameMap: NestedRenameMap\n): SerializedFieldRenameMap => {\n const serialized: SerializedFieldRenameMap = {};\n\n for (const [fieldName, renameEntry] of renameMap) {\n serialized[fieldName] = {\n shortName: renameEntry.shortName,\n children: serializeNestedRenameMap(renameEntry.children),\n };\n }\n\n return serialized;\n};\n\n/**\n * Converts the field-rename tables of a {@link PruneContext} into a plain JSON\n * object, keyed by dictionary key.\n *\n * Consumed by bundler integrations that cannot run the Babel rename plugin and\n * must forward the tables to another transformer — currently the `@intlayer/swc`\n * Wasm plugin used by Next.js, which rewrites the source accesses itself.\n *\n * Dictionaries flagged as structural edge cases are omitted: the JSON pipeline\n * leaves them untouched, so their source accesses must stay untouched too.\n * Empty tables are omitted as well, so the result is empty when there is\n * nothing to rename.\n */\nexport const serializeFieldRenameMap = (\n pruneContext: PruneContext\n): Record<string, SerializedFieldRenameMap> => {\n const serialized: Record<string, SerializedFieldRenameMap> = {};\n\n for (const [\n dictionaryKey,\n renameMap,\n ] of pruneContext.dictionaryKeyToFieldRenameMap) {\n if (renameMap.size === 0) continue;\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) continue;\n\n serialized[dictionaryKey] = serializeNestedRenameMap(renameMap);\n }\n\n return serialized;\n};\n\n// ── Field-rename Babel plugin ─────────────────────────────────────────────────\n\n/**\n * Walks a MemberExpression chain starting from `startPath`, renaming each\n * property found in `currentRenameMap` at the corresponding nesting level.\n *\n * Numeric computed accesses (array indices such as `[0]`, `[1]`) are treated\n * as transparent pass-throughs: the rename map is kept unchanged and the walk\n * continues past the index. This means `content.field[0].sub` is handled\n * correctly — `field` and `sub` are both renamed while `[0]` is left intact.\n */\nconst walkRenameChain = (\n babelTypes: typeof BabelTypes,\n startPath: NodePath<BabelTypes.Node>,\n currentRenameMap: NestedRenameMap\n): {\n finalPath: NodePath<BabelTypes.Node>;\n finalRenameMap: NestedRenameMap;\n} => {\n let refPath: NodePath<BabelTypes.Node> = startPath;\n let renameMap = currentRenameMap;\n\n while (true) {\n const parentPath = refPath.parentPath;\n if (!parentPath) break;\n\n const parentNode = parentPath.node;\n\n if (\n (!babelTypes.isMemberExpression(parentNode) &&\n !babelTypes.isOptionalMemberExpression(parentNode)) ||\n (parentNode as BabelTypes.MemberExpression).object !== refPath.node\n ) {\n break;\n }\n\n const memberNode = parentNode as BabelTypes.MemberExpression;\n\n // Numeric index access ([0], [1], …): advance past the array accessor\n // without touching the rename map. The next iteration will attempt to\n // rename the property that follows the index.\n if (\n memberNode.computed &&\n babelTypes.isNumericLiteral(memberNode.property)\n ) {\n refPath = parentPath;\n continue;\n }\n\n // Nothing left to rename at this level — stop.\n if (renameMap.size === 0) break;\n\n let fieldName: string | undefined;\n\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n fieldName = memberNode.property.name;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n fieldName = memberNode.property.value;\n } else {\n break; // dynamic computed key – stop\n }\n\n const renameEntry = renameMap.get(fieldName);\n if (!renameEntry) break; // not in map – stop\n\n // Apply the rename\n if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {\n memberNode.property.name = renameEntry.shortName;\n } else if (\n memberNode.computed &&\n babelTypes.isStringLiteral(memberNode.property)\n ) {\n memberNode.property.value = renameEntry.shortName;\n }\n\n refPath = parentPath;\n renameMap = renameEntry.children;\n }\n\n return { finalPath: refPath, finalRenameMap: renameMap };\n};\n\n/**\n * Renames the properties of a destructuring `ObjectPattern` using `renameMap`,\n * recursing into nested patterns and following the references of bound local\n * variables.\n *\n * Handled property shapes:\n *\n * { fieldA } → { shortA: fieldA }\n * { fieldA: localVar } → { shortA: localVar }\n * { fieldA = fallback } → { shortA: fieldA = fallback }\n * { fieldA: { nested } } → { shortA: { shortN: nested } }\n * { ['fieldA']: x } → { ['shortA']: x }\n * { [dynamicKey]: x } → left untouched (dynamic key – not renamable)\n *\n * @param babelTypes - Babel types helper.\n * @param objectPattern - The destructuring pattern to rename.\n * @param scopePath - A path whose scope resolves the pattern's bindings.\n * @param renameMap - Rename table for the current nesting level.\n */\nconst renameObjectPatternProperties = (\n babelTypes: typeof BabelTypes,\n objectPattern: BabelTypes.ObjectPattern,\n scopePath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n for (const property of objectPattern.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n\n // Computed keys: a static string key ({ ['title']: x }) is renamable in\n // place; any other computed key is dynamic and must be left untouched —\n // replacing it with an identifier would create a variable reference.\n if (property.computed && !babelTypes.isStringLiteral(property.key)) {\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 renameEntry = renameMap.get(keyName);\n if (!renameEntry) continue;\n\n if (property.computed) {\n // { ['fieldA']: x } → { ['shortA']: x }\n (property.key as BabelTypes.StringLiteral).value = renameEntry.shortName;\n } else {\n // { fieldA } → { shortA: fieldA }\n // { fieldA: localVar } → { shortA: localVar }\n if (property.shorthand) {\n property.shorthand = false;\n }\n property.key = babelTypes.identifier(renameEntry.shortName);\n }\n\n if (renameEntry.children.size === 0) continue;\n\n // A default value ({ fieldA = fallback }) wraps the actual binding target.\n const bindingTarget = babelTypes.isAssignmentPattern(property.value)\n ? property.value.left\n : property.value;\n\n // Nested pattern: const { fieldA: { nested } } = … — rename its keys\n // with this entry's children map.\n if (babelTypes.isObjectPattern(bindingTarget)) {\n renameObjectPatternProperties(\n babelTypes,\n bindingTarget,\n scopePath,\n renameEntry.children\n );\n continue;\n }\n\n // Identifier binding: recursively walk references to the local variable.\n if (babelTypes.isIdentifier(bindingTarget)) {\n const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);\n if (!localVarBinding) continue;\n\n for (const nestedRefPath of localVarBinding.referencePaths) {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n nestedRefPath,\n renameEntry.children\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n }\n }\n }\n};\n\n/**\n * Walks an object-destructuring assignment whose right-hand side is `refPath`,\n * renaming each destructured key that is found in `renameMap`.\n *\n * Handles the \"secondary destructuring\" pattern that `walkRenameChain` cannot\n * reach because the reference is not a MemberExpression:\n *\n * const { webhooksSection } = useIntlayer('build-settings');\n * const { modal, validationErrors } = webhooksSection;\n * → const { a: modal, b: validationErrors } = webhooksSection;\n *\n * After renaming each key, {@link renameObjectPatternProperties} recursively\n * walks references to the newly-bound local variables, calling both\n * `walkRenameChain` (for subsequent member-access chains like\n * `validationErrors.invalidUrl`) and this function again (for further levels\n * of secondary destructuring).\n */\nconst walkObjectDestructuring = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const parentNode = refPath.parent;\n\n // Only handle: const { a, b } = refVar\n if (\n !babelTypes.isVariableDeclarator(parentNode) ||\n !babelTypes.isObjectPattern(parentNode.id) ||\n parentNode.init !== refPath.node\n ) {\n return;\n }\n\n renameObjectPatternProperties(babelTypes, parentNode.id, refPath, renameMap);\n};\n\n/**\n * Renames every field access reachable from `rootPath`, whose value is the\n * dictionary content root.\n *\n * Combines the three ways a content root is consumed: a member-access chain,\n * a destructuring assignment, and a chainable runtime helper such as\n * `.onChange(cb)`.\n */\nconst renameContentConsumers = (\n babelTypes: typeof BabelTypes,\n rootPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n const { finalPath, finalRenameMap } = walkRenameChain(\n babelTypes,\n rootPath,\n renameMap\n );\n walkObjectDestructuring(babelTypes, finalPath, finalRenameMap);\n renameChainedRuntimeCall(babelTypes, finalPath, finalRenameMap);\n};\n\n/**\n * Handles `content.onChange(cb)` — a runtime subscription rather than a field\n * access (see {@link CHAINABLE_RUNTIME_METHOD_NAMES}).\n *\n * `walkRenameChain` stops at `onChange` because it is absent from the rename\n * map, which would leave the callback body reading the original field names\n * while the compiled JSON has already been renamed. This restarts the rename\n * walk on both sides of the call:\n *\n * - the callback's first parameter receives the same content root, so its\n * accesses are renamed with the same map;\n * - the helper returns the content object, so the chain continues past it.\n *\n * @param refPath - Path whose value is the content root (the object the\n * chainable method would be called on).\n */\nconst renameChainedRuntimeCall = (\n babelTypes: typeof BabelTypes,\n refPath: NodePath<BabelTypes.Node>,\n renameMap: NestedRenameMap\n): void => {\n if (renameMap.size === 0) return;\n\n const memberPath = refPath.parentPath;\n if (!memberPath) return;\n\n const memberNode = memberPath.node;\n if (\n (!babelTypes.isMemberExpression(memberNode) &&\n !babelTypes.isOptionalMemberExpression(memberNode)) ||\n (memberNode as BabelTypes.MemberExpression).object !== refPath.node\n ) {\n return;\n }\n\n const propertyNode = (memberNode as BabelTypes.MemberExpression).property;\n const methodName = (memberNode as BabelTypes.MemberExpression).computed\n ? babelTypes.isStringLiteral(propertyNode)\n ? propertyNode.value\n : undefined\n : babelTypes.isIdentifier(propertyNode)\n ? propertyNode.name\n : undefined;\n\n if (!methodName || !CHAINABLE_RUNTIME_METHOD_NAMES.has(methodName)) return;\n\n const callPath = memberPath.parentPath;\n if (!callPath) return;\n if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) {\n return;\n }\n if ((callPath.node as BabelTypes.CallExpression).callee !== memberNode) {\n return;\n }\n\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;\n }\n\n const [firstParameterPath] = argumentPath.get(\n 'params'\n ) as NodePath<BabelTypes.Node>[];\n if (!firstParameterPath) continue;\n\n // ({ title }) => … — destructured directly in the parameter list.\n if (firstParameterPath.isObjectPattern()) {\n renameObjectPatternProperties(\n babelTypes,\n firstParameterPath.node,\n argumentPath,\n renameMap\n );\n continue;\n }\n\n // (content) => … — rename each access on the parameter.\n if (!firstParameterPath.isIdentifier()) continue;\n\n const parameterBinding = argumentPath.scope.getBinding(\n firstParameterPath.node.name\n );\n if (!parameterBinding) continue;\n\n for (const parameterRefPath of parameterBinding.referencePaths) {\n renameContentConsumers(babelTypes, parameterRefPath, renameMap);\n }\n }\n\n // The helper returns the content object itself, so keep walking past it —\n // both when the result is used inline and when it is stored in a variable\n // (`const content = useIntlayer('key').onChange(cb)`).\n renameContentConsumers(babelTypes, callPath, renameMap);\n\n const declaratorNode = callPath.parent;\n if (\n babelTypes.isVariableDeclarator(declaratorNode) &&\n babelTypes.isIdentifier(declaratorNode.id) &&\n declaratorNode.init === callPath.node\n ) {\n const resultBinding = callPath.scope.getBinding(declaratorNode.id.name);\n for (const resultRefPath of resultBinding?.referencePaths ?? []) {\n renameContentConsumers(babelTypes, resultRefPath, renameMap);\n }\n }\n};\n\n/**\n * Collects the local aliases under which `useIntlayer` / `getIntlayer` are\n * imported in the given program (e.g. `import { useIntlayer as useI } …`\n * yields `useI` → `useIntlayer`).\n *\n * Import declarations can only appear at the top level of a module, so the\n * program body is scanned directly instead of traversing the whole AST.\n */\nconst collectIntlayerCallerLocalNames = (\n babelTypes: typeof BabelTypes,\n programNode: BabelTypes.Program\n): Map<string, string> => {\n const intlayerCallerLocalNameMap = new Map<string, string>();\n\n for (const statement of programNode.body) {\n if (!babelTypes.isImportDeclaration(statement)) continue;\n\n for (const importSpecifier of statement.specifiers) {\n if (!babelTypes.isImportSpecifier(importSpecifier)) continue;\n\n const importedName = babelTypes.isIdentifier(importSpecifier.imported)\n ? importSpecifier.imported.name\n : importSpecifier.imported.value;\n\n if (INTLAYER_CALLER_NAMES.includes(importedName as IntlayerCallerName)) {\n intlayerCallerLocalNameMap.set(\n importSpecifier.local.name,\n importedName\n );\n }\n }\n }\n\n return intlayerCallerLocalNameMap;\n};\n\n/**\n * Rewrites dictionary content field accesses in the given program to the\n * short aliases defined in `pruneContext.dictionaryKeyToFieldRenameMap`.\n *\n * Handled patterns (mirrors the usage analyser):\n *\n * const { fieldA, fieldB } = useIntlayer('key')\n * → const { shortA: fieldA, shortB: fieldB } = useIntlayer('key')\n *\n * useIntlayer('key').fieldA\n * → useIntlayer('key').shortA\n *\n * const result = useIntlayer('key'); result.fieldA\n * → const result = useIntlayer('key'); result.shortA\n *\n * const { fieldA } = useIntlayer('key');\n * const { nested } = fieldA; // secondary destructuring\n * → const { shortA: fieldA } = useIntlayer('key');\n * const { shortN: nested } = fieldA;\n *\n * Exposed as a standalone function so that both\n * {@link makeFieldRenameBabelPlugin} and `intlayerMinifyBabelPlugin` can\n * invoke it from their own `Program.exit` visitors.\n */\nexport const renameIntlayerFieldAccesses = (\n babelTypes: typeof BabelTypes,\n programPath: NodePath<BabelTypes.Program>,\n pruneContext: PruneContext\n): void => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return;\n\n const intlayerCallerLocalNameMap = collectIntlayerCallerLocalNames(\n babelTypes,\n programPath.node\n );\n if (intlayerCallerLocalNameMap.size === 0) return;\n\n // Visit all useIntlayer / getIntlayer call-sites and rename field accesses\n programPath.traverse({\n CallExpression: (callExpressionPath) => {\n const calleeNode = callExpressionPath.node.callee;\n let localCallerName: string | undefined;\n\n if (babelTypes.isIdentifier(calleeNode)) {\n localCallerName = calleeNode.name;\n } else if (\n babelTypes.isMemberExpression(calleeNode) &&\n babelTypes.isIdentifier(calleeNode.property)\n ) {\n localCallerName = calleeNode.property.name;\n }\n\n if (!localCallerName || !intlayerCallerLocalNameMap.has(localCallerName))\n return;\n\n const callArguments = callExpressionPath.node.arguments;\n if (callArguments.length === 0) return;\n\n const firstArgument = callArguments[0];\n let dictionaryKey: string | undefined;\n\n if (babelTypes.isStringLiteral(firstArgument)) {\n dictionaryKey = firstArgument.value;\n } else if (\n babelTypes.isTemplateLiteral(firstArgument) &&\n firstArgument.expressions.length === 0 &&\n firstArgument.quasis.length === 1 &&\n firstArgument.quasis[0]\n ) {\n dictionaryKey =\n firstArgument.quasis[0].value.cooked ??\n firstArgument.quasis[0].value.raw;\n }\n\n if (!dictionaryKey) return;\n\n // Dictionaries flagged as structural edge cases are left completely\n // untouched by the JSON pipeline — keep their source accesses untouched\n // too so both sides stay consistent.\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) return;\n\n const fieldRenameMap =\n pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey);\n if (!fieldRenameMap || fieldRenameMap.size === 0) return;\n\n const parentNode = callExpressionPath.parent;\n\n // ── Case 1: const { fieldA, fieldB } = useIntlayer('key') ────────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isObjectPattern(parentNode.id)\n ) {\n walkObjectDestructuring(babelTypes, callExpressionPath, fieldRenameMap);\n return;\n }\n\n // ── Case 2: useIntlayer('key').fieldA.nested ─────────────────────\n // (also useIntlayer('key').onChange(cb))\n if (\n (babelTypes.isMemberExpression(parentNode) ||\n babelTypes.isOptionalMemberExpression(parentNode)) &&\n (parentNode as BabelTypes.MemberExpression).object ===\n callExpressionPath.node\n ) {\n renameContentConsumers(babelTypes, callExpressionPath, fieldRenameMap);\n return;\n }\n\n // ── Case 3: const result = useIntlayer('key'); result.fieldA ─────\n if (\n babelTypes.isVariableDeclarator(parentNode) &&\n babelTypes.isIdentifier(parentNode.id)\n ) {\n const variableBinding = callExpressionPath.scope.getBinding(\n parentNode.id.name\n );\n if (!variableBinding) return;\n\n for (const variableReferencePath of variableBinding.referencePaths) {\n // Direct access: result.fieldA, const { fieldA } = result, or the\n // chainable result.onChange(cb)\n renameContentConsumers(\n babelTypes,\n variableReferencePath,\n fieldRenameMap\n );\n\n // Signal accessor: result().fieldA or const { fieldA } = result()\n // walkRenameChain stops at a CallExpression parent, so we need to\n // start a new walk from the call-expression node itself.\n const refParent = variableReferencePath.parent;\n if (\n (babelTypes.isCallExpression(refParent) ||\n babelTypes.isOptionalCallExpression(refParent)) &&\n (refParent as BabelTypes.CallExpression).callee ===\n variableReferencePath.node\n ) {\n const callPath = variableReferencePath.parentPath;\n if (callPath) {\n renameContentConsumers(babelTypes, callPath, fieldRenameMap);\n }\n }\n }\n }\n },\n });\n};\n\n/**\n * Creates a Babel plugin that rewrites dictionary content field accesses in\n * source files to their short aliases via\n * {@link renameIntlayerFieldAccesses}.\n *\n * This plugin must run in a separate `transformAsync` pass **before**\n * `intlayerOptimizeBabelPlugin`, because the latter replaces `useIntlayer`\n * with `useDictionary`, erasing the dictionary-key information needed here.\n */\nexport const makeFieldRenameBabelPlugin =\n (pruneContext: PruneContext) =>\n ({ types: babelTypes }: { types: typeof BabelTypes }): PluginObject => ({\n name: 'intlayer-field-rename',\n visitor: {\n Program: {\n exit: (programPath) => {\n renameIntlayerFieldAccesses(babelTypes, programPath, pruneContext);\n },\n },\n },\n });\n"],"mappings":"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"}
|
|
@@ -54,6 +54,21 @@ const preserveNestedDictionaryFields = (pruneContext, nestedDictionaryReferences
|
|
|
54
54
|
for (const nestedField of nestedFields) currentFieldUsage.add(nestedField);
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Methods the runtime attaches to a resolved content object rather than fields
|
|
59
|
+
* declared in a `.content` file.
|
|
60
|
+
*
|
|
61
|
+
* `vanilla-intlayer`'s `useIntlayer` / `useDictionary` assign `onChange` onto
|
|
62
|
+
* the content they return, so `useIntlayer('app').onChange(cb)` is a
|
|
63
|
+
* subscription — not a read of a field named `onChange`. Without this list the
|
|
64
|
+
* analyser records `onChange` as the only consumed field and the purge pass
|
|
65
|
+
* strips every real field from the compiled dictionary.
|
|
66
|
+
*
|
|
67
|
+
* The analyser looks *through* these calls: the callback parameter is analysed
|
|
68
|
+
* as a fresh content root, and the call result is analysed again because the
|
|
69
|
+
* helpers return the content object for chaining.
|
|
70
|
+
*/
|
|
71
|
+
const CHAINABLE_RUNTIME_METHOD_NAMES = /* @__PURE__ */ new Set(["onChange"]);
|
|
57
72
|
/** Canonical intlayer caller names that trigger usage analysis. */
|
|
58
73
|
const INTLAYER_CALLER_NAMES = ["useIntlayer", "getIntlayer"];
|
|
59
74
|
/**
|
|
@@ -98,7 +113,6 @@ const recordFieldUsage = (pruneContext, dictionaryKey, fieldUsage) => {
|
|
|
98
113
|
* const result = useIntlayer('key') → records 'all' (untracked binding)
|
|
99
114
|
*/
|
|
100
115
|
const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile) => {
|
|
101
|
-
const parentNode = callExpressionPath.parent;
|
|
102
116
|
/** Mark the dictionary key as having an untracked binding in this file. */
|
|
103
117
|
const markUntrackedBinding = () => {
|
|
104
118
|
const existingPaths = pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];
|
|
@@ -226,32 +240,64 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
|
|
|
226
240
|
}
|
|
227
241
|
return true;
|
|
228
242
|
};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Returns the call path when `memberExprPath` is a chainable runtime helper
|
|
245
|
+
* being invoked (`content.onChange(cb)`), otherwise `undefined`.
|
|
246
|
+
*
|
|
247
|
+
* See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`
|
|
248
|
+
* reference that is never called is treated as an ordinary field access, so
|
|
249
|
+
* only the invoked form is looked through.
|
|
250
|
+
*/
|
|
251
|
+
const getChainedRuntimeCallPath = (memberExprPath, fieldName) => {
|
|
252
|
+
if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return void 0;
|
|
253
|
+
if (!memberExprPath) return void 0;
|
|
254
|
+
const callPath = memberExprPath.parentPath;
|
|
255
|
+
if (!callPath) return void 0;
|
|
256
|
+
if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) return;
|
|
257
|
+
if (callPath.node.callee !== memberExprPath.node) return;
|
|
258
|
+
return callPath;
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Analyses the callbacks handed to a chainable runtime helper.
|
|
262
|
+
*
|
|
263
|
+
* The callback receives the freshly-resolved content root, so its parameter
|
|
264
|
+
* is walked exactly like `const content = useIntlayer('key')` — either as a
|
|
265
|
+
* plain binding or as a destructuring pattern.
|
|
266
|
+
*/
|
|
267
|
+
const analyzeChainedCallbackArguments = (callPath) => {
|
|
268
|
+
const argumentPaths = callPath.get("arguments");
|
|
269
|
+
for (const argumentPath of argumentPaths) {
|
|
270
|
+
if (!argumentPath.isArrowFunctionExpression() && !argumentPath.isFunctionExpression()) continue;
|
|
271
|
+
const [firstParameterPath] = argumentPath.get("params");
|
|
272
|
+
if (!firstParameterPath) continue;
|
|
273
|
+
if (firstParameterPath.isIdentifier()) {
|
|
274
|
+
const parameterBinding = argumentPath.scope.getBinding(firstParameterPath.node.name);
|
|
275
|
+
if (!parameterBinding) {
|
|
276
|
+
markUntrackedBinding();
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const accessedFieldNames = /* @__PURE__ */ new Set();
|
|
280
|
+
if (analyzeContentBindingReferences(parameterBinding, accessedFieldNames)) markUntrackedBinding();
|
|
281
|
+
else recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (firstParameterPath.isObjectPattern()) {
|
|
285
|
+
const accessedFieldNames = /* @__PURE__ */ new Set();
|
|
286
|
+
if (collectFieldsFromObjectPattern(firstParameterPath.node, argumentPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
|
|
287
|
+
else recordFieldUsage(pruneContext, dictionaryKey, "all");
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
250
290
|
markUntrackedBinding();
|
|
251
|
-
return;
|
|
252
291
|
}
|
|
253
|
-
|
|
254
|
-
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Walks every reference of a local binding holding the content root,
|
|
295
|
+
* collecting the top-level fields it reads into `accessedTopLevelFieldNames`.
|
|
296
|
+
*
|
|
297
|
+
* @returns `true` when a reference escaped static tracking, meaning the whole
|
|
298
|
+
* dictionary must be kept.
|
|
299
|
+
*/
|
|
300
|
+
const analyzeContentBindingReferences = (variableBinding, accessedTopLevelFieldNames) => {
|
|
255
301
|
for (const variableReferencePath of variableBinding.referencePaths) {
|
|
256
302
|
const referenceParentNode = variableReferencePath.parent;
|
|
257
303
|
if ((babelTypes.isMemberExpression(referenceParentNode) || babelTypes.isOptionalMemberExpression(referenceParentNode)) && referenceParentNode.object === variableReferencePath.node) {
|
|
@@ -260,17 +306,18 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
|
|
|
260
306
|
if (!memberExpressionNode.computed && babelTypes.isIdentifier(memberExpressionNode.property)) fieldName = memberExpressionNode.property.name;
|
|
261
307
|
else if (memberExpressionNode.computed && babelTypes.isStringLiteral(memberExpressionNode.property)) fieldName = memberExpressionNode.property.value;
|
|
262
308
|
if (fieldName) {
|
|
263
|
-
accessedTopLevelFieldNames.add(fieldName);
|
|
264
309
|
const memberExprPath = variableReferencePath.parentPath;
|
|
310
|
+
const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
|
|
311
|
+
if (chainedCallPath) {
|
|
312
|
+
analyzeChainedCallbackArguments(chainedCallPath);
|
|
313
|
+
analyzeContentRoot(chainedCallPath);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
accessedTopLevelFieldNames.add(fieldName);
|
|
265
317
|
if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
|
|
266
|
-
} else
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
} else if (babelTypes.isArrayExpression(referenceParentNode)) {
|
|
271
|
-
hasUntrackedReferenceAccess = true;
|
|
272
|
-
break;
|
|
273
|
-
} else if ((babelTypes.isCallExpression(referenceParentNode) || babelTypes.isOptionalCallExpression(referenceParentNode)) && referenceParentNode.callee === variableReferencePath.node) {
|
|
318
|
+
} else return true;
|
|
319
|
+
} else if (babelTypes.isArrayExpression(referenceParentNode)) return true;
|
|
320
|
+
else if ((babelTypes.isCallExpression(referenceParentNode) || babelTypes.isOptionalCallExpression(referenceParentNode)) && referenceParentNode.callee === variableReferencePath.node) {
|
|
274
321
|
const callExprPath = variableReferencePath.parentPath;
|
|
275
322
|
const callParent = callExprPath?.parent;
|
|
276
323
|
if (callParent && (babelTypes.isMemberExpression(callParent) || babelTypes.isOptionalMemberExpression(callParent)) && callParent.object === callExprPath?.node) {
|
|
@@ -279,30 +326,71 @@ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath
|
|
|
279
326
|
if (!memberExpr.computed && babelTypes.isIdentifier(memberExpr.property)) fieldName = memberExpr.property.name;
|
|
280
327
|
else if (memberExpr.computed && babelTypes.isStringLiteral(memberExpr.property)) fieldName = memberExpr.property.value;
|
|
281
328
|
if (fieldName) {
|
|
282
|
-
accessedTopLevelFieldNames.add(fieldName);
|
|
283
329
|
const memberExprPath = callExprPath?.parentPath;
|
|
330
|
+
const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
|
|
331
|
+
if (chainedCallPath) {
|
|
332
|
+
analyzeChainedCallbackArguments(chainedCallPath);
|
|
333
|
+
analyzeContentRoot(chainedCallPath);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
accessedTopLevelFieldNames.add(fieldName);
|
|
284
337
|
if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
|
|
285
|
-
} else
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
338
|
+
} else return true;
|
|
339
|
+
} else if (callParent && babelTypes.isVariableDeclarator(callParent) && babelTypes.isObjectPattern(callParent.id) && callExprPath && collectFieldsFromObjectPattern(callParent.id, callExprPath, accessedTopLevelFieldNames)) {} else return true;
|
|
340
|
+
} else return true;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* Analyses how the content root referenced by `rootPath` is consumed.
|
|
346
|
+
*
|
|
347
|
+
* `rootPath` is the `useIntlayer('key')` call itself, or any expression that
|
|
348
|
+
* evaluates back to the same content object — currently the result of a
|
|
349
|
+
* chainable runtime helper such as `.onChange(…)`.
|
|
350
|
+
*/
|
|
351
|
+
const analyzeContentRoot = (rootPath) => {
|
|
352
|
+
const parentNode = rootPath.parent;
|
|
353
|
+
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
|
|
354
|
+
const accessedFieldNames = /* @__PURE__ */ new Set();
|
|
355
|
+
if (collectFieldsFromObjectPattern(parentNode.id, rootPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
|
|
356
|
+
else recordFieldUsage(pruneContext, dictionaryKey, "all");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === rootPath.node) {
|
|
360
|
+
let fieldName;
|
|
361
|
+
if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) fieldName = parentNode.property.name;
|
|
362
|
+
else if (parentNode.computed && babelTypes.isStringLiteral(parentNode.property)) fieldName = parentNode.property.value;
|
|
363
|
+
if (fieldName) {
|
|
364
|
+
const memberExprPath = rootPath.parentPath;
|
|
365
|
+
const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
|
|
366
|
+
if (chainedCallPath) {
|
|
367
|
+
analyzeChainedCallbackArguments(chainedCallPath);
|
|
368
|
+
analyzeContentRoot(chainedCallPath);
|
|
369
|
+
return;
|
|
292
370
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
371
|
+
recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([fieldName]));
|
|
372
|
+
if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
|
|
373
|
+
} else markUntrackedBinding();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
|
|
377
|
+
const variableName = parentNode.id.name;
|
|
378
|
+
const variableBinding = rootPath.scope.getBinding(variableName);
|
|
379
|
+
if (!variableBinding) {
|
|
380
|
+
markUntrackedBinding();
|
|
381
|
+
return;
|
|
296
382
|
}
|
|
383
|
+
const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
|
|
384
|
+
if (analyzeContentBindingReferences(variableBinding, accessedTopLevelFieldNames)) markUntrackedBinding();
|
|
385
|
+
else if (isSfcFile) deferFrameworkAnalysis(variableName);
|
|
386
|
+
else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
|
|
387
|
+
else recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
|
|
388
|
+
return;
|
|
297
389
|
}
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
if (babelTypes.isExpressionStatement(parentNode)) return;
|
|
305
|
-
markUntrackedBinding();
|
|
390
|
+
if (babelTypes.isExpressionStatement(parentNode)) return;
|
|
391
|
+
markUntrackedBinding();
|
|
392
|
+
};
|
|
393
|
+
analyzeContentRoot(callExpressionPath);
|
|
306
394
|
};
|
|
307
395
|
/**
|
|
308
396
|
* Reads a fully-static string from an AST node. Returns `undefined` for
|
|
@@ -646,6 +734,7 @@ const makeUsageAnalyzerBabelPlugin = (pruneContext, options) => ({ types: babelT
|
|
|
646
734
|
};
|
|
647
735
|
|
|
648
736
|
//#endregion
|
|
737
|
+
exports.CHAINABLE_RUNTIME_METHOD_NAMES = CHAINABLE_RUNTIME_METHOD_NAMES;
|
|
649
738
|
exports.DEFAULT_COMPAT_CALLERS = DEFAULT_COMPAT_CALLERS;
|
|
650
739
|
exports.INTLAYER_CALLER_NAMES = INTLAYER_CALLER_NAMES;
|
|
651
740
|
exports.createPruneContext = createPruneContext;
|