@intlayer/babel 9.4.3 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +0 -2
  2. package/dist/cjs/analyzeContentUsage.cjs +301 -0
  3. package/dist/cjs/analyzeContentUsage.cjs.map +1 -0
  4. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs +5 -4
  5. package/dist/cjs/babel-plugin-intlayer-field-rename.cjs.map +1 -1
  6. package/dist/cjs/babel-plugin-intlayer-optimize.cjs +173 -282
  7. package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
  8. package/dist/cjs/babel-plugin-intlayer-purge.cjs +4 -3
  9. package/dist/cjs/babel-plugin-intlayer-purge.cjs.map +1 -1
  10. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs +49 -699
  11. package/dist/cjs/babel-plugin-intlayer-usage-analyzer.cjs.map +1 -1
  12. package/dist/cjs/compat/namespaceResolution.cjs +124 -0
  13. package/dist/cjs/compat/namespaceResolution.cjs.map +1 -0
  14. package/dist/cjs/compat/optimizePass.cjs +144 -0
  15. package/dist/cjs/compat/optimizePass.cjs.map +1 -0
  16. package/dist/cjs/compat/usageAnalysis.cjs +195 -0
  17. package/dist/cjs/compat/usageAnalysis.cjs.map +1 -0
  18. package/dist/cjs/dictionaryImports.cjs +40 -0
  19. package/dist/cjs/dictionaryImports.cjs.map +1 -0
  20. package/dist/cjs/extractContent/utils/extractDictionaryInfo.cjs +1 -1
  21. package/dist/cjs/getExtractPluginOptions.cjs +1 -1
  22. package/dist/cjs/getOptimizePluginOptions.cjs +1 -1
  23. package/dist/cjs/getPurgePluginOptions.cjs +1 -1
  24. package/dist/cjs/index.cjs +9 -5
  25. package/dist/cjs/nativeCallers.cjs +41 -0
  26. package/dist/cjs/nativeCallers.cjs.map +1 -0
  27. package/dist/cjs/pruneContext.cjs +79 -0
  28. package/dist/cjs/pruneContext.cjs.map +1 -0
  29. package/dist/cjs/staticAstReaders.cjs +140 -0
  30. package/dist/cjs/staticAstReaders.cjs.map +1 -0
  31. package/dist/cjs/transformers.cjs +2 -1
  32. package/dist/cjs/transformers.cjs.map +1 -1
  33. package/dist/esm/analyzeContentUsage.mjs +300 -0
  34. package/dist/esm/analyzeContentUsage.mjs.map +1 -0
  35. package/dist/esm/babel-plugin-intlayer-field-rename.mjs +3 -2
  36. package/dist/esm/babel-plugin-intlayer-field-rename.mjs.map +1 -1
  37. package/dist/esm/babel-plugin-intlayer-optimize.mjs +173 -282
  38. package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
  39. package/dist/esm/babel-plugin-intlayer-purge.mjs +3 -2
  40. package/dist/esm/babel-plugin-intlayer-purge.mjs.map +1 -1
  41. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs +43 -694
  42. package/dist/esm/babel-plugin-intlayer-usage-analyzer.mjs.map +1 -1
  43. package/dist/esm/compat/namespaceResolution.mjs +120 -0
  44. package/dist/esm/compat/namespaceResolution.mjs.map +1 -0
  45. package/dist/esm/compat/optimizePass.mjs +143 -0
  46. package/dist/esm/compat/optimizePass.mjs.map +1 -0
  47. package/dist/esm/compat/usageAnalysis.mjs +193 -0
  48. package/dist/esm/compat/usageAnalysis.mjs.map +1 -0
  49. package/dist/esm/dictionaryImports.mjs +38 -0
  50. package/dist/esm/dictionaryImports.mjs.map +1 -0
  51. package/dist/esm/extractContent/utils/extractDictionaryInfo.mjs +1 -1
  52. package/dist/esm/getExtractPluginOptions.mjs +1 -1
  53. package/dist/esm/getOptimizePluginOptions.mjs +1 -1
  54. package/dist/esm/getPurgePluginOptions.mjs +1 -1
  55. package/dist/esm/index.mjs +5 -2
  56. package/dist/esm/nativeCallers.mjs +38 -0
  57. package/dist/esm/nativeCallers.mjs.map +1 -0
  58. package/dist/esm/pruneContext.mjs +76 -0
  59. package/dist/esm/pruneContext.mjs.map +1 -0
  60. package/dist/esm/staticAstReaders.mjs +129 -0
  61. package/dist/esm/staticAstReaders.mjs.map +1 -0
  62. package/dist/esm/transformers.mjs +2 -1
  63. package/dist/esm/transformers.mjs.map +1 -1
  64. package/dist/types/analyzeContentUsage.d.ts +18 -0
  65. package/dist/types/analyzeContentUsage.d.ts.map +1 -0
  66. package/dist/types/babel-plugin-intlayer-extract.d.ts +3 -4
  67. package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
  68. package/dist/types/babel-plugin-intlayer-field-rename.d.ts +10 -10
  69. package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
  70. package/dist/types/babel-plugin-intlayer-minify.d.ts +2 -3
  71. package/dist/types/babel-plugin-intlayer-minify.d.ts.map +1 -1
  72. package/dist/types/babel-plugin-intlayer-optimize.d.ts +11 -18
  73. package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
  74. package/dist/types/babel-plugin-intlayer-purge.d.ts +7 -7
  75. package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -1
  76. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +22 -189
  77. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
  78. package/dist/types/babelParser.d.ts +3 -4
  79. package/dist/types/babelParser.d.ts.map +1 -1
  80. package/dist/types/compat/namespaceResolution.d.ts +66 -0
  81. package/dist/types/compat/namespaceResolution.d.ts.map +1 -0
  82. package/dist/types/compat/optimizePass.d.ts +58 -0
  83. package/dist/types/compat/optimizePass.d.ts.map +1 -0
  84. package/dist/types/compat/usageAnalysis.d.ts +75 -0
  85. package/dist/types/compat/usageAnalysis.d.ts.map +1 -0
  86. package/dist/types/dictionaryImports.d.ts +46 -0
  87. package/dist/types/dictionaryImports.d.ts.map +1 -0
  88. package/dist/types/extractContent/babelProcessor.d.ts +4 -5
  89. package/dist/types/extractContent/babelProcessor.d.ts.map +1 -1
  90. package/dist/types/extractContent/contentWriter.d.ts +3 -4
  91. package/dist/types/extractContent/contentWriter.d.ts.map +1 -1
  92. package/dist/types/extractContent/extractContent.d.ts +3 -4
  93. package/dist/types/extractContent/extractContent.d.ts.map +1 -1
  94. package/dist/types/extractContent/processTsxFile.d.ts +2 -3
  95. package/dist/types/extractContent/processTsxFile.d.ts.map +1 -1
  96. package/dist/types/extractContent/utils/constants.d.ts +3 -4
  97. package/dist/types/extractContent/utils/constants.d.ts.map +1 -1
  98. package/dist/types/extractContent/utils/detectPackageName.d.ts +1 -2
  99. package/dist/types/extractContent/utils/detectPackageName.d.ts.map +1 -1
  100. package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts +5 -6
  101. package/dist/types/extractContent/utils/extractDictionaryInfo.d.ts.map +1 -1
  102. package/dist/types/extractContent/utils/extractDictionaryKey.d.ts +2 -3
  103. package/dist/types/extractContent/utils/extractDictionaryKey.d.ts.map +1 -1
  104. package/dist/types/extractContent/utils/generateKey.d.ts +1 -2
  105. package/dist/types/extractContent/utils/generateKey.d.ts.map +1 -1
  106. package/dist/types/extractContent/utils/getComponentName.d.ts +1 -2
  107. package/dist/types/extractContent/utils/getComponentName.d.ts.map +1 -1
  108. package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts +2 -3
  109. package/dist/types/extractContent/utils/getExistingIntlayerInfo.d.ts.map +1 -1
  110. package/dist/types/extractContent/utils/getOrGenerateKey.d.ts +1 -2
  111. package/dist/types/extractContent/utils/getOrGenerateKey.d.ts.map +1 -1
  112. package/dist/types/extractContent/utils/resolveDictionaryKey.d.ts +1 -2
  113. package/dist/types/extractContent/utils/resolveDictionaryKey.d.ts.map +1 -1
  114. package/dist/types/extractScriptBlocks.d.ts +3 -4
  115. package/dist/types/extractScriptBlocks.d.ts.map +1 -1
  116. package/dist/types/getExtractPluginOptions.d.ts +2 -3
  117. package/dist/types/getExtractPluginOptions.d.ts.map +1 -1
  118. package/dist/types/getOptimizePluginOptions.d.ts +1 -2
  119. package/dist/types/getOptimizePluginOptions.d.ts.map +1 -1
  120. package/dist/types/getPurgePluginOptions.d.ts +2 -3
  121. package/dist/types/getPurgePluginOptions.d.ts.map +1 -1
  122. package/dist/types/index.d.ts +5 -2
  123. package/dist/types/nativeCallers.d.ts +33 -0
  124. package/dist/types/nativeCallers.d.ts.map +1 -0
  125. package/dist/types/normalizedFilesList.d.ts +1 -2
  126. package/dist/types/normalizedFilesList.d.ts.map +1 -1
  127. package/dist/types/pruneContext.d.ts +152 -0
  128. package/dist/types/pruneContext.d.ts.map +1 -0
  129. package/dist/types/staticAstReaders.d.ts +84 -0
  130. package/dist/types/staticAstReaders.d.ts.map +1 -0
  131. package/dist/types/transformers.d.ts +11 -11
  132. package/dist/types/transformers.d.ts.map +1 -1
  133. package/package.json +12 -12
@@ -0,0 +1,140 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region src/staticAstReaders.ts
3
+ /**
4
+ * Static-value readers shared by every pass that has to decide, at build time,
5
+ * which dictionary a call site reads.
6
+ *
7
+ * They were previously duplicated — with small behavioural drifts — between
8
+ * `babel-plugin-intlayer-optimize` and `babel-plugin-intlayer-usage-analyzer`.
9
+ * Both now read through this module so a call site the analyser can attribute
10
+ * is exactly the one the optimizer can rewrite.
11
+ */
12
+ /**
13
+ * Reads a fully-static string from an AST node. Returns `undefined` for
14
+ * dynamic values (identifiers, expressions, template literals with
15
+ * interpolations, …).
16
+ */
17
+ const readStaticString = (babelTypes, node) => {
18
+ if (!node) return void 0;
19
+ if (babelTypes.isStringLiteral(node)) return node.value;
20
+ if (babelTypes.isTemplateLiteral(node) && node.expressions.length === 0 && node.quasis.length === 1) return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
21
+ };
22
+ /** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */
23
+ const firstPathSegment = (path) => path.split(".")[0] ?? path;
24
+ /**
25
+ * Reads the static first dot-path segment from a message-id node.
26
+ *
27
+ * t('counter.label') → 'counter'
28
+ * t(`counter.${x}`) → 'counter' (static prefix before the first dot)
29
+ * t(`${x}.label`) → undefined (dynamic first segment)
30
+ * t(someVariable) → undefined
31
+ */
32
+ const readStaticFirstSegment = (babelTypes, node) => {
33
+ const staticString = readStaticString(babelTypes, node);
34
+ if (staticString !== void 0) return firstPathSegment(staticString);
35
+ if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {
36
+ const firstQuasi = node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
37
+ if (firstQuasi?.includes(".")) return firstPathSegment(firstQuasi);
38
+ }
39
+ };
40
+ /** Whether an object-property key (identifier or string literal) reads as `name`. */
41
+ const propertyKeyMatches = (babelTypes, property, name) => babelTypes.isIdentifier(property.key) && property.key.name === name || babelTypes.isStringLiteral(property.key) && property.key.value === name;
42
+ /**
43
+ * Returns the `ObjectProperty` named `propertyName`, or `undefined` when the
44
+ * object has no such property. Callers that need to mutate the property in
45
+ * place (rewriting or deleting it) use this rather than a value reader.
46
+ */
47
+ const findObjectProperty = (babelTypes, objectExpression, propertyName) => {
48
+ for (const property of objectExpression.properties) {
49
+ if (!babelTypes.isObjectProperty(property)) continue;
50
+ if (propertyKeyMatches(babelTypes, property, propertyName)) return property;
51
+ }
52
+ };
53
+ /**
54
+ * Marker returned by the reader helpers when the requested property or
55
+ * argument is simply absent — distinct from present-but-dynamic, which the
56
+ * callers must treat conservatively.
57
+ */
58
+ const ABSENT_VALUE = "__default__";
59
+ /**
60
+ * Reads a static string property from an object expression. Returns
61
+ * {@link ABSENT_VALUE} when the property is absent and `undefined` when
62
+ * present but dynamic.
63
+ */
64
+ const readObjectProperty = (babelTypes, objectExpression, propertyName) => {
65
+ const property = findObjectProperty(babelTypes, objectExpression, propertyName);
66
+ if (!property) return ABSENT_VALUE;
67
+ return readStaticString(babelTypes, property.value);
68
+ };
69
+ /**
70
+ * Returns the value *node* of a named property, so callers can apply their own
71
+ * reader to it (a full static string, or just its leading path segment).
72
+ * Returns {@link ABSENT_VALUE} when the property is absent.
73
+ */
74
+ const readObjectPropertyNode = (babelTypes, objectExpression, propertyName) => {
75
+ const property = findObjectProperty(babelTypes, objectExpression, propertyName);
76
+ if (!property) return ABSENT_VALUE;
77
+ return property.value;
78
+ };
79
+ /**
80
+ * Reads a named JSX attribute as a static string and returns it wrapped in a
81
+ * `StringLiteral` node so the result can be fed to the same namespace resolver
82
+ * as a call argument. Handles both `id="home.title"` and `id={'home.title'}`.
83
+ * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).
84
+ */
85
+ const readJsxAttributeString = (babelTypes, openingElement, attributeName) => {
86
+ for (const attribute of openingElement.attributes) {
87
+ if (!babelTypes.isJSXAttribute(attribute)) continue;
88
+ if (!babelTypes.isJSXIdentifier(attribute.name) || attribute.name.name !== attributeName) continue;
89
+ const value = attribute.value;
90
+ if (babelTypes.isStringLiteral(value)) return value;
91
+ if (babelTypes.isJSXExpressionContainer(value)) {
92
+ const staticString = readStaticString(babelTypes, value.expression);
93
+ if (staticString !== void 0) return babelTypes.stringLiteral(staticString);
94
+ }
95
+ return;
96
+ }
97
+ };
98
+ /**
99
+ * Splits a compat namespace at the first `.` to separate the dictionary key
100
+ * from an optional key prefix, mirroring the SWC plugin's `split_namespace`.
101
+ *
102
+ * `'about'` → `{ dictionaryKey: 'about', keyPrefix: '' }`
103
+ * `'about.counter'` → `{ dictionaryKey: 'about', keyPrefix: 'counter' }`
104
+ */
105
+ const splitNamespace = (namespace) => {
106
+ const dotPosition = namespace.indexOf(".");
107
+ if (dotPosition === -1) return {
108
+ dictionaryKey: namespace,
109
+ keyPrefix: ""
110
+ };
111
+ return {
112
+ dictionaryKey: namespace.slice(0, dotPosition),
113
+ keyPrefix: namespace.slice(dotPosition + 1)
114
+ };
115
+ };
116
+ /**
117
+ * Climbs past an enclosing `await` expression so that
118
+ * `const t = await getTranslations('ns')` — or `await getIntlayerAsync('key')`
119
+ * — is resolved to its variable declarator the same way the synchronous form
120
+ * is.
121
+ */
122
+ const unwrapAwait = (babelTypes, path) => {
123
+ const parentPath = path.parentPath;
124
+ if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) return parentPath;
125
+ return path;
126
+ };
127
+
128
+ //#endregion
129
+ exports.ABSENT_VALUE = ABSENT_VALUE;
130
+ exports.findObjectProperty = findObjectProperty;
131
+ exports.firstPathSegment = firstPathSegment;
132
+ exports.propertyKeyMatches = propertyKeyMatches;
133
+ exports.readJsxAttributeString = readJsxAttributeString;
134
+ exports.readObjectProperty = readObjectProperty;
135
+ exports.readObjectPropertyNode = readObjectPropertyNode;
136
+ exports.readStaticFirstSegment = readStaticFirstSegment;
137
+ exports.readStaticString = readStaticString;
138
+ exports.splitNamespace = splitNamespace;
139
+ exports.unwrapAwait = unwrapAwait;
140
+ //# sourceMappingURL=staticAstReaders.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staticAstReaders.cjs","names":[],"sources":["../../src/staticAstReaders.ts"],"sourcesContent":["import type { NodePath } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\n\n/**\n * Static-value readers shared by every pass that has to decide, at build time,\n * which dictionary a call site reads.\n *\n * They were previously duplicated — with small behavioural drifts — between\n * `babel-plugin-intlayer-optimize` and `babel-plugin-intlayer-usage-analyzer`.\n * Both now read through this module so a call site the analyser can attribute\n * is exactly the one the optimizer can rewrite.\n */\n\n/**\n * Reads a fully-static string from an AST node. Returns `undefined` for\n * dynamic values (identifiers, expressions, template literals with\n * interpolations, …).\n */\nexport const readStaticString = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n if (!node) return undefined;\n if (babelTypes.isStringLiteral(node)) return node.value;\n if (\n babelTypes.isTemplateLiteral(node) &&\n node.expressions.length === 0 &&\n node.quasis.length === 1\n ) {\n return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n }\n return undefined;\n};\n\n/** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */\nexport const firstPathSegment = (path: string): string =>\n path.split('.')[0] ?? path;\n\n/**\n * Reads the static first dot-path segment from a message-id node.\n *\n * t('counter.label') → 'counter'\n * t(`counter.${x}`) → 'counter' (static prefix before the first dot)\n * t(`${x}.label`) → undefined (dynamic first segment)\n * t(someVariable) → undefined\n */\nexport const readStaticFirstSegment = (\n babelTypes: typeof BabelTypes,\n node: BabelTypes.Node | null | undefined\n): string | undefined => {\n const staticString = readStaticString(babelTypes, node);\n if (staticString !== undefined) return firstPathSegment(staticString);\n\n // Template literal whose first quasi already contains the dot delimiter, e.g.\n // `counter.${index}` → the leading `counter` segment is statically known.\n if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {\n const firstQuasi =\n node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;\n if (firstQuasi?.includes('.')) {\n return firstPathSegment(firstQuasi);\n }\n }\n return undefined;\n};\n\n/** Whether an object-property key (identifier or string literal) reads as `name`. */\nexport const propertyKeyMatches = (\n babelTypes: typeof BabelTypes,\n property: BabelTypes.ObjectProperty,\n name: string\n): boolean =>\n (babelTypes.isIdentifier(property.key) && property.key.name === name) ||\n (babelTypes.isStringLiteral(property.key) && property.key.value === name);\n\n/**\n * Returns the `ObjectProperty` named `propertyName`, or `undefined` when the\n * object has no such property. Callers that need to mutate the property in\n * place (rewriting or deleting it) use this rather than a value reader.\n */\nexport const findObjectProperty = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): BabelTypes.ObjectProperty | undefined => {\n for (const property of objectExpression.properties) {\n if (!babelTypes.isObjectProperty(property)) continue;\n if (propertyKeyMatches(babelTypes, property, propertyName)) return property;\n }\n return undefined;\n};\n\n/**\n * Marker returned by the reader helpers when the requested property or\n * argument is simply absent — distinct from present-but-dynamic, which the\n * callers must treat conservatively.\n */\nexport const ABSENT_VALUE = '__default__' as const;\n\n/** Either a statically-known value, {@link ABSENT_VALUE}, or `undefined` (dynamic). */\nexport type StaticOrAbsent<T> = T | typeof ABSENT_VALUE | undefined;\n\n/**\n * Reads a static string property from an object expression. Returns\n * {@link ABSENT_VALUE} when the property is absent and `undefined` when\n * present but dynamic.\n */\nexport const readObjectProperty = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): StaticOrAbsent<string> => {\n const property = findObjectProperty(\n babelTypes,\n objectExpression,\n propertyName\n );\n if (!property) return ABSENT_VALUE;\n return readStaticString(babelTypes, property.value); // present but dynamic → undefined\n};\n\n/**\n * Returns the value *node* of a named property, so callers can apply their own\n * reader to it (a full static string, or just its leading path segment).\n * Returns {@link ABSENT_VALUE} when the property is absent.\n */\nexport const readObjectPropertyNode = (\n babelTypes: typeof BabelTypes,\n objectExpression: BabelTypes.ObjectExpression,\n propertyName: string\n): StaticOrAbsent<BabelTypes.Node> => {\n const property = findObjectProperty(\n babelTypes,\n objectExpression,\n propertyName\n );\n if (!property) return ABSENT_VALUE;\n return property.value;\n};\n\n/**\n * Reads a named JSX attribute as a static string and returns it wrapped in a\n * `StringLiteral` node so the result can be fed to the same namespace resolver\n * as a call argument. Handles both `id=\"home.title\"` and `id={'home.title'}`.\n * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).\n */\nexport const readJsxAttributeString = (\n babelTypes: typeof BabelTypes,\n openingElement: BabelTypes.JSXOpeningElement,\n attributeName: string\n): BabelTypes.StringLiteral | undefined => {\n for (const attribute of openingElement.attributes) {\n if (!babelTypes.isJSXAttribute(attribute)) continue;\n if (\n !babelTypes.isJSXIdentifier(attribute.name) ||\n attribute.name.name !== attributeName\n ) {\n continue;\n }\n\n const value = attribute.value;\n // id=\"home.title\"\n if (babelTypes.isStringLiteral(value)) return value;\n // id={'home.title'} / id={`home.title`}\n if (babelTypes.isJSXExpressionContainer(value)) {\n const staticString = readStaticString(babelTypes, value.expression);\n if (staticString !== undefined) {\n return babelTypes.stringLiteral(staticString);\n }\n }\n return undefined; // attribute present but dynamic\n }\n return undefined; // attribute absent\n};\n\n/**\n * Splits a compat namespace at the first `.` to separate the dictionary key\n * from an optional key prefix, mirroring the SWC plugin's `split_namespace`.\n *\n * `'about'` → `{ dictionaryKey: 'about', keyPrefix: '' }`\n * `'about.counter'` → `{ dictionaryKey: 'about', keyPrefix: 'counter' }`\n */\nexport const splitNamespace = (\n namespace: string\n): { dictionaryKey: string; keyPrefix: string } => {\n const dotPosition = namespace.indexOf('.');\n if (dotPosition === -1) return { dictionaryKey: namespace, keyPrefix: '' };\n return {\n dictionaryKey: namespace.slice(0, dotPosition),\n keyPrefix: namespace.slice(dotPosition + 1),\n };\n};\n\n/**\n * Climbs past an enclosing `await` expression so that\n * `const t = await getTranslations('ns')` — or `await getIntlayerAsync('key')`\n * — is resolved to its variable declarator the same way the synchronous form\n * is.\n */\nexport const unwrapAwait = (\n babelTypes: typeof BabelTypes,\n path: NodePath<BabelTypes.Node>\n): NodePath<BabelTypes.Node> => {\n const parentPath = path.parentPath;\n if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) {\n return parentPath;\n }\n return path;\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,oBACX,YACA,SACuB;CACvB,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,WAAW,gBAAgB,IAAI,GAAG,OAAO,KAAK;CAClD,IACE,WAAW,kBAAkB,IAAI,KACjC,KAAK,YAAY,WAAW,KAC5B,KAAK,OAAO,WAAW,GAEvB,OAAO,KAAK,OAAO,EAAE,EAAE,MAAM,UAAU,KAAK,OAAO,EAAE,EAAE,MAAM;AAGjE;;AAGA,MAAa,oBAAoB,SAC/B,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM;;;;;;;;;AAUxB,MAAa,0BACX,YACA,SACuB;CACvB,MAAM,eAAe,iBAAiB,YAAY,IAAI;CACtD,IAAI,iBAAiB,QAAW,OAAO,iBAAiB,YAAY;CAIpE,IAAI,WAAW,kBAAkB,IAAI,KAAK,KAAK,OAAO,SAAS,GAAG;EAChE,MAAM,aACJ,KAAK,OAAO,EAAE,EAAE,MAAM,UAAU,KAAK,OAAO,EAAE,EAAE,MAAM;EACxD,IAAI,YAAY,SAAS,GAAG,GAC1B,OAAO,iBAAiB,UAAU;CAEtC;AAEF;;AAGA,MAAa,sBACX,YACA,UACA,SAEC,WAAW,aAAa,SAAS,GAAG,KAAK,SAAS,IAAI,SAAS,QAC/D,WAAW,gBAAgB,SAAS,GAAG,KAAK,SAAS,IAAI,UAAU;;;;;;AAOtE,MAAa,sBACX,YACA,kBACA,iBAC0C;CAC1C,KAAK,MAAM,YAAY,iBAAiB,YAAY;EAClD,IAAI,CAAC,WAAW,iBAAiB,QAAQ,GAAG;EAC5C,IAAI,mBAAmB,YAAY,UAAU,YAAY,GAAG,OAAO;CACrE;AAEF;;;;;;AAOA,MAAa,eAAe;;;;;;AAU5B,MAAa,sBACX,YACA,kBACA,iBAC2B;CAC3B,MAAM,WAAW,mBACf,YACA,kBACA,YACF;CACA,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,iBAAiB,YAAY,SAAS,KAAK;AACpD;;;;;;AAOA,MAAa,0BACX,YACA,kBACA,iBACoC;CACpC,MAAM,WAAW,mBACf,YACA,kBACA,YACF;CACA,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,SAAS;AAClB;;;;;;;AAQA,MAAa,0BACX,YACA,gBACA,kBACyC;CACzC,KAAK,MAAM,aAAa,eAAe,YAAY;EACjD,IAAI,CAAC,WAAW,eAAe,SAAS,GAAG;EAC3C,IACE,CAAC,WAAW,gBAAgB,UAAU,IAAI,KAC1C,UAAU,KAAK,SAAS,eAExB;EAGF,MAAM,QAAQ,UAAU;EAExB,IAAI,WAAW,gBAAgB,KAAK,GAAG,OAAO;EAE9C,IAAI,WAAW,yBAAyB,KAAK,GAAG;GAC9C,MAAM,eAAe,iBAAiB,YAAY,MAAM,UAAU;GAClE,IAAI,iBAAiB,QACnB,OAAO,WAAW,cAAc,YAAY;EAEhD;EACA;CACF;AAEF;;;;;;;;AASA,MAAa,kBACX,cACiD;CACjD,MAAM,cAAc,UAAU,QAAQ,GAAG;CACzC,IAAI,gBAAgB,IAAI,OAAO;EAAE,eAAe;EAAW,WAAW;CAAG;CACzE,OAAO;EACL,eAAe,UAAU,MAAM,GAAG,WAAW;EAC7C,WAAW,UAAU,MAAM,cAAc,CAAC;CAC5C;AACF;;;;;;;AAQA,MAAa,eACX,YACA,SAC8B;CAC9B,MAAM,aAAa,KAAK;CACxB,IAAI,cAAc,WAAW,kBAAkB,WAAW,IAAI,GAC5D,OAAO;CAET,OAAO;AACT"}
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_nativeCallers = require('./nativeCallers.cjs');
2
3
  const require_babel_plugin_intlayer_usage_analyzer = require('./babel-plugin-intlayer-usage-analyzer.cjs');
3
4
  const require_extractScriptBlocks = require('./extractScriptBlocks.cjs');
4
5
  const require_babel_plugin_intlayer_field_rename = require('./babel-plugin-intlayer-field-rename.cjs');
@@ -48,7 +49,7 @@ const BABEL_PARSER_OPTIONS = {
48
49
  * @returns A `RegExp` matching any of the native or provided caller names.
49
50
  */
50
51
  const buildUsageCheckRegex = (extraCallerNames) => {
51
- const callerNames = [...require_babel_plugin_intlayer_usage_analyzer.INTLAYER_CALLER_NAMES, ...extraCallerNames ?? []];
52
+ const callerNames = [...require_nativeCallers.INTLAYER_CALLER_NAMES, ...extraCallerNames ?? []];
52
53
  const uniqueNames = [...new Set(callerNames)];
53
54
  return new RegExp(`\\b(${uniqueNames.join("|")})\\b`);
54
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"transformers.cjs","names":["INTLAYER_CALLER_NAMES","transformAsync","makeUsageAnalyzerBabelPlugin","extractScriptBlocks","makeFieldRenameBabelPlugin","injectScriptBlocks","intlayerOptimizeBabelPlugin"],"sources":["../../src/transformers.ts"],"sourcesContent":["import { transformAsync } from '@babel/core';\n// Typed from `@babel/parser` rather than `@babel/core`'s\n// `InputOptions['parserOpts']` (Babel 8's rename of `TransformOptions`): the\n// two describe the same shape, but naming `@babel/core` in an exported type\n// pulls its declaration file into every consumer's type-check, and that file\n// needs `@types/convert-source-map` to resolve.\nimport type { ParserOptions } from '@babel/parser';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport {\n intlayerOptimizeBabelPlugin,\n type OptimizePluginOptions,\n} from './babel-plugin-intlayer-optimize';\nimport {\n type CompatCallerConfig,\n INTLAYER_CALLER_NAMES,\n makeUsageAnalyzerBabelPlugin,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks, injectScriptBlocks } from './extractScriptBlocks';\n\n// ── Shared Babel parser configuration ─────────────────────────────────────────\n\n/**\n * Babel parser options covering the superset of syntaxes used across all\n * supported frameworks (React / Vue / Svelte / Angular / …).\n */\nexport const BABEL_PARSER_OPTIONS: ParserOptions = {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n};\n\n/**\n * Builds a fast pre-check regex that matches files potentially containing\n * native intlayer calls and/or the given compat namespace caller names.\n *\n * The result is used to skip files that definitely contain none of the\n * relevant function names, avoiding unnecessary Babel parsing.\n *\n * The native names come from {@link INTLAYER_CALLER_NAMES} rather than being\n * spelled out here: a name missing from this regex silently opts every file\n * using it out of the optimize, purge and minify passes, leaving its calls to\n * resolve through the dictionary registry — empty in browser bundles.\n *\n * Compat caller names are NOT hardcoded here — they are provided at runtime by\n * the compat adapter packages that configure the usage-analysis pipeline.\n *\n * @param extraCallerNames - Additional caller function names to match\n * (e.g. `['useTranslation', 'getFixedT']` from `@intlayer/react-i18next`).\n * @returns A `RegExp` matching any of the native or provided caller names.\n */\nexport const buildUsageCheckRegex = (extraCallerNames?: string[]): RegExp => {\n const callerNames = [...INTLAYER_CALLER_NAMES, ...(extraCallerNames ?? [])];\n const uniqueNames = [...new Set(callerNames)];\n return new RegExp(`\\\\b(${uniqueNames.join('|')})\\\\b`);\n};\n\n/**\n * Fast pre-check: matches files that could contain native intlayer calls\n * (`useIntlayer` / `getIntlayer` / `getIntlayerAsync`). Used by the\n * optimize/transform pass, which only rewrites native calls.\n */\nexport const INTLAYER_USAGE_REGEX = buildUsageCheckRegex();\n\n/** Usage-check regex covering only the native callers (no compat callers). */\nconst NATIVE_USAGE_CHECK_REGEX = INTLAYER_USAGE_REGEX;\n\n/** Cache of usage-check regexes, keyed by the compat-caller array reference. */\nconst usageCheckRegexCache = new WeakMap<\n readonly CompatCallerConfig[],\n RegExp\n>();\n\n/**\n * Returns the usage-check regex for the given compat callers, memoised by the\n * array reference. Plugins pass the same options array for every analysed\n * file, so the regex is built once per build instead of once per file.\n */\nexport const getUsageCheckRegex = (\n compatCallers?: readonly CompatCallerConfig[]\n): RegExp => {\n if (!compatCallers || compatCallers.length === 0) {\n return NATIVE_USAGE_CHECK_REGEX;\n }\n\n let usageCheckRegex = usageCheckRegexCache.get(compatCallers);\n if (!usageCheckRegex) {\n usageCheckRegex = buildUsageCheckRegex(\n compatCallers.map((caller) => caller.callerName)\n );\n usageCheckRegexCache.set(compatCallers, usageCheckRegex);\n }\n return usageCheckRegex;\n};\n\n/**\n * Matches source files that are valid targets for usage analysis and Babel\n * transformation. Excludes sourcemap files, declaration files, and other\n * non-source extensions.\n */\nexport const SOURCE_FILE_REGEX = /\\.(tsx?|[mc]?jsx?|vue|svelte|astro)$/;\n\n// ── High-level transformer functions ──────────────────────────────────────────\n\n/**\n * Runs the usage-analysis Babel plugin on a single JS/TS code string.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the content.\n */\nconst analyzeScriptContent = async (\n scriptContent: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n await transformAsync(scriptContent, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false, // analysis only – no output needed\n });\n};\n\n/**\n * Runs the usage-analysis Babel plugin on a source file, accumulating\n * field-usage data into `pruneContext`.\n *\n * For Vue / Svelte SFC files, script blocks are extracted before analysis so\n * Babel does not attempt to parse the full SFC syntax (templates, styles, …).\n * For plain JS/TS files, the whole file content is analysed directly.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the file (caller should handle and flag\n * `pruneContext.hasUnparsableSourceFiles`).\n */\nexport const analyzeFieldUsageInFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n // For SFC files (Vue / Svelte): scriptBlocks[0].contentStartOffset > 0\n // means we extracted actual <script> tags from the file.\n // For plain JS/TS: extractScriptBlocks returns the whole file as a single\n // block with offset 0, so we fall through to the same path.\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n await analyzeScriptContent(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n/**\n * Applies field-renaming to a single JS/TS code string (not an SFC).\n *\n * Returns the renamed code string, or `null` if nothing changed or if\n * Babel failed to parse the input (caller should fall back to original code).\n */\nexport const renameFieldsInCode = async (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n try {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [makeFieldRenameBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n });\n return result?.code ?? null;\n } catch {\n return null; // parse failure – caller falls back to original code\n }\n};\n\n/**\n * Applies field-renaming to a source file, correctly handling both plain\n * JS/TS files and SFC files (Vue / Svelte) by operating on each script block\n * individually and injecting the results back into the original source.\n *\n * Returns the renamed code string, or `null` if nothing changed.\n */\nexport const renameFieldsInSourceFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return null;\n if (!INTLAYER_USAGE_REGEX.test(code)) return null;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n const isSFC =\n scriptBlocks.length > 0 &&\n ((scriptBlocks[0]?.contentStartOffset ?? 0) > 0 || scriptBlocks.length > 1);\n\n if (isSFC) {\n // Raw SFC: rename each script block individually and inject back.\n const modifications: Array<{\n block: (typeof scriptBlocks)[number];\n modifiedContent: string;\n }> = [];\n\n for (const block of scriptBlocks) {\n if (!INTLAYER_USAGE_REGEX.test(block.content)) continue;\n\n const renamedCode = await renameFieldsInCode(\n block.content,\n sourceFilePath,\n pruneContext\n );\n if (renamedCode && renamedCode !== block.content) {\n modifications.push({ block, modifiedContent: renamedCode });\n }\n }\n\n if (modifications.length === 0) return null;\n return injectScriptBlocks(code, modifications);\n }\n\n // Plain JS/TS or compiled SFC (no block delimiters) – rename the whole file.\n return renameFieldsInCode(code, sourceFilePath, pruneContext);\n};\n\n/**\n * Runs the intlayer optimize Babel plugin on a source file, transforming\n * `useIntlayer('key')` / `getIntlayer('key')` calls into `useDictionary(_hash)`\n * / `getDictionary(_hash)` and injecting the corresponding dictionary imports.\n *\n * Returns `{ code, map }` on success, or `null` if the transformation produced\n * no output.\n */\nexport const optimizeSourceFile = async (\n code: string,\n sourceFilePath: string,\n options: OptimizePluginOptions\n): Promise<{\n code: string;\n map: string | object | null | undefined;\n} | null> => {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [[intlayerOptimizeBabelPlugin, options]],\n parserOpts: BABEL_PARSER_OPTIONS,\n });\n\n if (!result?.code) return null;\n\n return { code: result.code, map: result.map };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"transformers.cjs","names":["INTLAYER_CALLER_NAMES","transformAsync","makeUsageAnalyzerBabelPlugin","extractScriptBlocks","makeFieldRenameBabelPlugin","injectScriptBlocks","intlayerOptimizeBabelPlugin"],"sources":["../../src/transformers.ts"],"sourcesContent":["import { transformAsync } from '@babel/core';\n// Typed from `@babel/parser` rather than `@babel/core`'s\n// `InputOptions['parserOpts']` (Babel 8's rename of `TransformOptions`): the\n// two describe the same shape, but naming `@babel/core` in an exported type\n// pulls its declaration file into every consumer's type-check, and that file\n// needs `@types/convert-source-map` to resolve.\nimport type { ParserOptions } from '@babel/parser';\nimport { makeFieldRenameBabelPlugin } from './babel-plugin-intlayer-field-rename';\nimport {\n intlayerOptimizeBabelPlugin,\n type OptimizePluginOptions,\n} from './babel-plugin-intlayer-optimize';\nimport {\n type CompatCallerConfig,\n INTLAYER_CALLER_NAMES,\n makeUsageAnalyzerBabelPlugin,\n type PruneContext,\n} from './babel-plugin-intlayer-usage-analyzer';\nimport { extractScriptBlocks, injectScriptBlocks } from './extractScriptBlocks';\n\n// ── Shared Babel parser configuration ─────────────────────────────────────────\n\n/**\n * Babel parser options covering the superset of syntaxes used across all\n * supported frameworks (React / Vue / Svelte / Angular / …).\n */\nexport const BABEL_PARSER_OPTIONS: ParserOptions = {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n};\n\n/**\n * Builds a fast pre-check regex that matches files potentially containing\n * native intlayer calls and/or the given compat namespace caller names.\n *\n * The result is used to skip files that definitely contain none of the\n * relevant function names, avoiding unnecessary Babel parsing.\n *\n * The native names come from {@link INTLAYER_CALLER_NAMES} rather than being\n * spelled out here: a name missing from this regex silently opts every file\n * using it out of the optimize, purge and minify passes, leaving its calls to\n * resolve through the dictionary registry — empty in browser bundles.\n *\n * Compat caller names are NOT hardcoded here — they are provided at runtime by\n * the compat adapter packages that configure the usage-analysis pipeline.\n *\n * @param extraCallerNames - Additional caller function names to match\n * (e.g. `['useTranslation', 'getFixedT']` from `@intlayer/react-i18next`).\n * @returns A `RegExp` matching any of the native or provided caller names.\n */\nexport const buildUsageCheckRegex = (extraCallerNames?: string[]): RegExp => {\n const callerNames = [...INTLAYER_CALLER_NAMES, ...(extraCallerNames ?? [])];\n const uniqueNames = [...new Set(callerNames)];\n return new RegExp(`\\\\b(${uniqueNames.join('|')})\\\\b`);\n};\n\n/**\n * Fast pre-check: matches files that could contain native intlayer calls\n * (`useIntlayer` / `getIntlayer` / `getIntlayerAsync`). Used by the\n * optimize/transform pass, which only rewrites native calls.\n */\nexport const INTLAYER_USAGE_REGEX = buildUsageCheckRegex();\n\n/** Usage-check regex covering only the native callers (no compat callers). */\nconst NATIVE_USAGE_CHECK_REGEX = INTLAYER_USAGE_REGEX;\n\n/** Cache of usage-check regexes, keyed by the compat-caller array reference. */\nconst usageCheckRegexCache = new WeakMap<\n readonly CompatCallerConfig[],\n RegExp\n>();\n\n/**\n * Returns the usage-check regex for the given compat callers, memoised by the\n * array reference. Plugins pass the same options array for every analysed\n * file, so the regex is built once per build instead of once per file.\n */\nexport const getUsageCheckRegex = (\n compatCallers?: readonly CompatCallerConfig[]\n): RegExp => {\n if (!compatCallers || compatCallers.length === 0) {\n return NATIVE_USAGE_CHECK_REGEX;\n }\n\n let usageCheckRegex = usageCheckRegexCache.get(compatCallers);\n if (!usageCheckRegex) {\n usageCheckRegex = buildUsageCheckRegex(\n compatCallers.map((caller) => caller.callerName)\n );\n usageCheckRegexCache.set(compatCallers, usageCheckRegex);\n }\n return usageCheckRegex;\n};\n\n/**\n * Matches source files that are valid targets for usage analysis and Babel\n * transformation. Excludes sourcemap files, declaration files, and other\n * non-source extensions.\n */\nexport const SOURCE_FILE_REGEX = /\\.(tsx?|[mc]?jsx?|vue|svelte|astro)$/;\n\n// ── High-level transformer functions ──────────────────────────────────────────\n\n/**\n * Runs the usage-analysis Babel plugin on a single JS/TS code string.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the content.\n */\nconst analyzeScriptContent = async (\n scriptContent: string,\n sourceFilePath: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n await transformAsync(scriptContent, {\n filename: sourceFilePath,\n plugins: [makeUsageAnalyzerBabelPlugin(pruneContext, { compatCallers })],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n code: false, // analysis only – no output needed\n });\n};\n\n/**\n * Runs the usage-analysis Babel plugin on a source file, accumulating\n * field-usage data into `pruneContext`.\n *\n * For Vue / Svelte SFC files, script blocks are extracted before analysis so\n * Babel does not attempt to parse the full SFC syntax (templates, styles, …).\n * For plain JS/TS files, the whole file content is analysed directly.\n *\n * This is analysis-only: the transformed code output is discarded.\n * Throws if Babel cannot parse the file (caller should handle and flag\n * `pruneContext.hasUnparsableSourceFiles`).\n */\nexport const analyzeFieldUsageInFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext,\n compatCallers?: CompatCallerConfig[]\n): Promise<void> => {\n const usageCheckRegex = getUsageCheckRegex(compatCallers);\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n // For SFC files (Vue / Svelte): scriptBlocks[0].contentStartOffset > 0\n // means we extracted actual <script> tags from the file.\n // For plain JS/TS: extractScriptBlocks returns the whole file as a single\n // block with offset 0, so we fall through to the same path.\n for (const block of scriptBlocks) {\n if (!usageCheckRegex.test(block.content)) continue;\n await analyzeScriptContent(\n block.content,\n sourceFilePath,\n pruneContext,\n compatCallers\n );\n }\n};\n\n/**\n * Applies field-renaming to a single JS/TS code string (not an SFC).\n *\n * Returns the renamed code string, or `null` if nothing changed or if\n * Babel failed to parse the input (caller should fall back to original code).\n */\nexport const renameFieldsInCode = async (\n code: string,\n sourceFilePath: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n try {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [makeFieldRenameBabelPlugin(pruneContext)],\n parserOpts: BABEL_PARSER_OPTIONS,\n ast: false,\n });\n return result?.code ?? null;\n } catch {\n return null; // parse failure – caller falls back to original code\n }\n};\n\n/**\n * Applies field-renaming to a source file, correctly handling both plain\n * JS/TS files and SFC files (Vue / Svelte) by operating on each script block\n * individually and injecting the results back into the original source.\n *\n * Returns the renamed code string, or `null` if nothing changed.\n */\nexport const renameFieldsInSourceFile = async (\n sourceFilePath: string,\n code: string,\n pruneContext: PruneContext\n): Promise<string | null> => {\n if (pruneContext.dictionaryKeyToFieldRenameMap.size === 0) return null;\n if (!INTLAYER_USAGE_REGEX.test(code)) return null;\n\n const scriptBlocks = extractScriptBlocks(sourceFilePath, code);\n\n const isSFC =\n scriptBlocks.length > 0 &&\n ((scriptBlocks[0]?.contentStartOffset ?? 0) > 0 || scriptBlocks.length > 1);\n\n if (isSFC) {\n // Raw SFC: rename each script block individually and inject back.\n const modifications: Array<{\n block: (typeof scriptBlocks)[number];\n modifiedContent: string;\n }> = [];\n\n for (const block of scriptBlocks) {\n if (!INTLAYER_USAGE_REGEX.test(block.content)) continue;\n\n const renamedCode = await renameFieldsInCode(\n block.content,\n sourceFilePath,\n pruneContext\n );\n if (renamedCode && renamedCode !== block.content) {\n modifications.push({ block, modifiedContent: renamedCode });\n }\n }\n\n if (modifications.length === 0) return null;\n return injectScriptBlocks(code, modifications);\n }\n\n // Plain JS/TS or compiled SFC (no block delimiters) – rename the whole file.\n return renameFieldsInCode(code, sourceFilePath, pruneContext);\n};\n\n/**\n * Runs the intlayer optimize Babel plugin on a source file, transforming\n * `useIntlayer('key')` / `getIntlayer('key')` calls into `useDictionary(_hash)`\n * / `getDictionary(_hash)` and injecting the corresponding dictionary imports.\n *\n * Returns `{ code, map }` on success, or `null` if the transformation produced\n * no output.\n */\nexport const optimizeSourceFile = async (\n code: string,\n sourceFilePath: string,\n options: OptimizePluginOptions\n): Promise<{\n code: string;\n map: string | object | null | undefined;\n} | null> => {\n const result = await transformAsync(code, {\n filename: sourceFilePath,\n plugins: [[intlayerOptimizeBabelPlugin, options]],\n parserOpts: BABEL_PARSER_OPTIONS,\n });\n\n if (!result?.code) return null;\n\n return { code: result.code, map: result.map };\n};\n"],"mappings":"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"}
@@ -0,0 +1,300 @@
1
+ import { CHAINABLE_RUNTIME_METHOD_NAMES } from "./nativeCallers.mjs";
2
+ import { recordFieldUsage } from "./pruneContext.mjs";
3
+ import { unwrapAwait } from "./staticAstReaders.mjs";
4
+
5
+ //#region src/analyzeContentUsage.ts
6
+ /**
7
+ * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`
8
+ * call expression is consumed, then records the field usage into `pruneContext`.
9
+ *
10
+ * Recognised patterns:
11
+ * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}
12
+ * useIntlayer('key').fieldA → records {fieldA}
13
+ * useIntlayer('key')['fieldA'] → records {fieldA}
14
+ * const { ...rest } = useIntlayer('key') → records 'all' (spread)
15
+ * const result = useIntlayer('key') → records 'all' (untracked binding)
16
+ */
17
+ const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile) => {
18
+ /** Mark the dictionary key as having an untracked binding in this file. */
19
+ const markUntrackedBinding = () => {
20
+ const existingPaths = pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];
21
+ if (!existingPaths.includes(currentSourceFilePath)) pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [...existingPaths, currentSourceFilePath]);
22
+ recordFieldUsage(pruneContext, dictionaryKey, "all");
23
+ };
24
+ /** Record that the content value at `fieldPath` is consumed opaquely. */
25
+ const markOpaqueField = (fieldPath, line) => {
26
+ const pathToOccurrence = pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey) ?? /* @__PURE__ */ new Map();
27
+ const location = line !== void 0 ? `${currentSourceFilePath}:${line}` : currentSourceFilePath;
28
+ const joinedFieldPath = fieldPath.join(".");
29
+ const occurrence = pathToOccurrence.get(joinedFieldPath) ?? {
30
+ fieldPath: [...fieldPath],
31
+ locations: []
32
+ };
33
+ if (!occurrence.locations.includes(location)) occurrence.locations.push(location);
34
+ pathToOccurrence.set(joinedFieldPath, occurrence);
35
+ pruneContext.dictionaryKeysWithOpaqueFields.set(dictionaryKey, pathToOccurrence);
36
+ };
37
+ /** Register a plain variable binding in an SFC file for a second-pass analysis. */
38
+ const deferFrameworkAnalysis = (variableName) => {
39
+ const existing = pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];
40
+ if (!existing.some((e) => e.variableName === variableName && e.dictionaryKey === dictionaryKey)) existing.push({
41
+ variableName,
42
+ dictionaryKey
43
+ });
44
+ pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);
45
+ };
46
+ /**
47
+ * Analyses how the content value at `fieldPath` (currently referenced by
48
+ * `refPath`) is consumed, to detect opaque consumption (passing a dictionary
49
+ * value as-is to a prop or function argument).
50
+ *
51
+ * Member-access chains and destructuring are followed recursively with an
52
+ * extended field path, so the *terminal* consumption of a chain like
53
+ * `content.sections.hero` decides opacity — mirroring how far the
54
+ * source-code renamer can rewrite accesses. When the terminal consumption
55
+ * escapes static tracking, the deepest reached field path is marked opaque
56
+ * so the children of that value keep their original key names.
57
+ */
58
+ const analyzeOpaqueUsage = (refPath, fieldPath) => {
59
+ const parentNode = refPath.parent;
60
+ const parentPath = refPath.parentPath;
61
+ if (parentPath && (babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === refPath.node) {
62
+ const memberNode = parentNode;
63
+ if (memberNode.computed && babelTypes.isNumericLiteral(memberNode.property)) {
64
+ analyzeOpaqueUsage(parentPath, fieldPath);
65
+ return;
66
+ }
67
+ if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {
68
+ analyzeOpaqueUsage(parentPath, [...fieldPath, memberNode.property.name]);
69
+ return;
70
+ }
71
+ if (memberNode.computed && babelTypes.isStringLiteral(memberNode.property)) {
72
+ analyzeOpaqueUsage(parentPath, [...fieldPath, memberNode.property.value]);
73
+ return;
74
+ }
75
+ markOpaqueField(fieldPath, refPath.node.loc?.start.line);
76
+ return;
77
+ }
78
+ if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id) && parentNode.init === refPath.node) {
79
+ analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);
80
+ return;
81
+ }
82
+ if (babelTypes.isArrayExpression(parentNode)) return;
83
+ markOpaqueField(fieldPath, refPath.node.loc?.start.line);
84
+ };
85
+ /**
86
+ * Analyses a destructuring pattern applied to the content value at
87
+ * `fieldPath`, recursively following each bound local variable's references
88
+ * via {@link analyzeOpaqueUsage}.
89
+ *
90
+ * Patterns the source-code renamer cannot rewrite (rest elements, dynamic
91
+ * computed keys, array patterns) mark the corresponding path opaque.
92
+ */
93
+ const analyzeOpaqueDestructuring = (objectPattern, scopePath, fieldPath) => {
94
+ for (const property of objectPattern.properties) {
95
+ if (babelTypes.isRestElement(property)) {
96
+ markOpaqueField(fieldPath, property.loc?.start.line);
97
+ continue;
98
+ }
99
+ if (!babelTypes.isObjectProperty(property)) continue;
100
+ if (property.computed && !babelTypes.isStringLiteral(property.key)) {
101
+ markOpaqueField(fieldPath, property.loc?.start.line);
102
+ continue;
103
+ }
104
+ const keyName = babelTypes.isIdentifier(property.key) ? property.key.name : babelTypes.isStringLiteral(property.key) ? property.key.value : null;
105
+ if (!keyName) continue;
106
+ const childFieldPath = [...fieldPath, keyName];
107
+ const bindingTarget = babelTypes.isAssignmentPattern(property.value) ? property.value.left : property.value;
108
+ if (babelTypes.isObjectPattern(bindingTarget)) {
109
+ analyzeOpaqueDestructuring(bindingTarget, scopePath, childFieldPath);
110
+ continue;
111
+ }
112
+ if (babelTypes.isIdentifier(bindingTarget)) {
113
+ const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);
114
+ if (!localVarBinding) continue;
115
+ for (const referencePath of localVarBinding.referencePaths) analyzeOpaqueUsage(referencePath, childFieldPath);
116
+ continue;
117
+ }
118
+ markOpaqueField(childFieldPath, property.loc?.start.line);
119
+ }
120
+ };
121
+ /**
122
+ * Helper to collect field names from an ObjectPattern (destructuring).
123
+ * Returns true if successful, false when the accessed fields cannot be
124
+ * determined statically (rest element or dynamic computed key → 'all').
125
+ */
126
+ const collectFieldsFromObjectPattern = (pattern, initPath, targetSet) => {
127
+ if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) return false;
128
+ for (const property of pattern.properties) {
129
+ if (!babelTypes.isObjectProperty(property)) continue;
130
+ if (property.computed && !babelTypes.isStringLiteral(property.key)) return false;
131
+ let fieldName;
132
+ if (!property.computed && babelTypes.isIdentifier(property.key)) fieldName = property.key.name;
133
+ else if (babelTypes.isStringLiteral(property.key)) fieldName = property.key.value;
134
+ if (!fieldName) continue;
135
+ targetSet.add(fieldName);
136
+ const bindingTarget = babelTypes.isAssignmentPattern(property.value) ? property.value.left : property.value;
137
+ if (babelTypes.isIdentifier(bindingTarget)) {
138
+ const variableBinding = initPath.scope.getBinding(bindingTarget.name);
139
+ if (variableBinding) for (const refPath of variableBinding.referencePaths) analyzeOpaqueUsage(refPath, [fieldName]);
140
+ } else if (babelTypes.isObjectPattern(bindingTarget)) analyzeOpaqueDestructuring(bindingTarget, initPath, [fieldName]);
141
+ else markOpaqueField([fieldName], property.loc?.start.line);
142
+ }
143
+ return true;
144
+ };
145
+ /**
146
+ * Returns the call path when `memberExprPath` is a chainable runtime helper
147
+ * being invoked (`content.onChange(cb)`), otherwise `undefined`.
148
+ *
149
+ * See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`
150
+ * reference that is never called is treated as an ordinary field access, so
151
+ * only the invoked form is looked through.
152
+ */
153
+ const getChainedRuntimeCallPath = (memberExprPath, fieldName) => {
154
+ if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return void 0;
155
+ if (!memberExprPath) return void 0;
156
+ const callPath = memberExprPath.parentPath;
157
+ if (!callPath) return void 0;
158
+ if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) return;
159
+ if (callPath.node.callee !== memberExprPath.node) return;
160
+ return callPath;
161
+ };
162
+ /**
163
+ * Analyses the callbacks handed to a chainable runtime helper.
164
+ *
165
+ * The callback receives the freshly-resolved content root, so its parameter
166
+ * is walked exactly like `const content = useIntlayer('key')` — either as a
167
+ * plain binding or as a destructuring pattern.
168
+ */
169
+ const analyzeChainedCallbackArguments = (callPath) => {
170
+ const argumentPaths = callPath.get("arguments");
171
+ for (const argumentPath of argumentPaths) {
172
+ if (!argumentPath.isArrowFunctionExpression() && !argumentPath.isFunctionExpression()) continue;
173
+ const [firstParameterPath] = argumentPath.get("params");
174
+ if (!firstParameterPath) continue;
175
+ if (firstParameterPath.isIdentifier()) {
176
+ const parameterBinding = argumentPath.scope.getBinding(firstParameterPath.node.name);
177
+ if (!parameterBinding) {
178
+ markUntrackedBinding();
179
+ continue;
180
+ }
181
+ const accessedFieldNames = /* @__PURE__ */ new Set();
182
+ if (analyzeContentBindingReferences(parameterBinding, accessedFieldNames)) markUntrackedBinding();
183
+ else recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
184
+ continue;
185
+ }
186
+ if (firstParameterPath.isObjectPattern()) {
187
+ const accessedFieldNames = /* @__PURE__ */ new Set();
188
+ if (collectFieldsFromObjectPattern(firstParameterPath.node, argumentPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
189
+ else recordFieldUsage(pruneContext, dictionaryKey, "all");
190
+ continue;
191
+ }
192
+ markUntrackedBinding();
193
+ }
194
+ };
195
+ /**
196
+ * Walks every reference of a local binding holding the content root,
197
+ * collecting the top-level fields it reads into `accessedTopLevelFieldNames`.
198
+ *
199
+ * @returns `true` when a reference escaped static tracking, meaning the whole
200
+ * dictionary must be kept.
201
+ */
202
+ const analyzeContentBindingReferences = (variableBinding, accessedTopLevelFieldNames) => {
203
+ for (const variableReferencePath of variableBinding.referencePaths) {
204
+ const referenceParentNode = variableReferencePath.parent;
205
+ if ((babelTypes.isMemberExpression(referenceParentNode) || babelTypes.isOptionalMemberExpression(referenceParentNode)) && referenceParentNode.object === variableReferencePath.node) {
206
+ const memberExpressionNode = referenceParentNode;
207
+ let fieldName;
208
+ if (!memberExpressionNode.computed && babelTypes.isIdentifier(memberExpressionNode.property)) fieldName = memberExpressionNode.property.name;
209
+ else if (memberExpressionNode.computed && babelTypes.isStringLiteral(memberExpressionNode.property)) fieldName = memberExpressionNode.property.value;
210
+ if (fieldName) {
211
+ const memberExprPath = variableReferencePath.parentPath;
212
+ const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
213
+ if (chainedCallPath) {
214
+ analyzeChainedCallbackArguments(chainedCallPath);
215
+ analyzeContentRoot(chainedCallPath);
216
+ continue;
217
+ }
218
+ accessedTopLevelFieldNames.add(fieldName);
219
+ if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
220
+ } else return true;
221
+ } else if (babelTypes.isArrayExpression(referenceParentNode)) return true;
222
+ else if ((babelTypes.isCallExpression(referenceParentNode) || babelTypes.isOptionalCallExpression(referenceParentNode)) && referenceParentNode.callee === variableReferencePath.node) {
223
+ const callExprPath = variableReferencePath.parentPath;
224
+ const callParent = callExprPath?.parent;
225
+ if (callParent && (babelTypes.isMemberExpression(callParent) || babelTypes.isOptionalMemberExpression(callParent)) && callParent.object === callExprPath?.node) {
226
+ const memberExpr = callParent;
227
+ let fieldName;
228
+ if (!memberExpr.computed && babelTypes.isIdentifier(memberExpr.property)) fieldName = memberExpr.property.name;
229
+ else if (memberExpr.computed && babelTypes.isStringLiteral(memberExpr.property)) fieldName = memberExpr.property.value;
230
+ if (fieldName) {
231
+ const memberExprPath = callExprPath?.parentPath;
232
+ const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
233
+ if (chainedCallPath) {
234
+ analyzeChainedCallbackArguments(chainedCallPath);
235
+ analyzeContentRoot(chainedCallPath);
236
+ continue;
237
+ }
238
+ accessedTopLevelFieldNames.add(fieldName);
239
+ if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
240
+ } else return true;
241
+ } else if (callParent && babelTypes.isVariableDeclarator(callParent) && babelTypes.isObjectPattern(callParent.id) && callExprPath && collectFieldsFromObjectPattern(callParent.id, callExprPath, accessedTopLevelFieldNames)) {} else return true;
242
+ } else return true;
243
+ }
244
+ return false;
245
+ };
246
+ /**
247
+ * Analyses how the content root referenced by `rootPath` is consumed.
248
+ *
249
+ * `rootPath` is the `useIntlayer('key')` call itself, or any expression that
250
+ * evaluates back to the same content object — currently the result of a
251
+ * chainable runtime helper such as `.onChange(…)`.
252
+ */
253
+ const analyzeContentRoot = (rootPath) => {
254
+ const parentNode = rootPath.parent;
255
+ if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
256
+ const accessedFieldNames = /* @__PURE__ */ new Set();
257
+ if (collectFieldsFromObjectPattern(parentNode.id, rootPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
258
+ else recordFieldUsage(pruneContext, dictionaryKey, "all");
259
+ return;
260
+ }
261
+ if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === rootPath.node) {
262
+ let fieldName;
263
+ if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) fieldName = parentNode.property.name;
264
+ else if (parentNode.computed && babelTypes.isStringLiteral(parentNode.property)) fieldName = parentNode.property.value;
265
+ if (fieldName) {
266
+ const memberExprPath = rootPath.parentPath;
267
+ const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
268
+ if (chainedCallPath) {
269
+ analyzeChainedCallbackArguments(chainedCallPath);
270
+ analyzeContentRoot(chainedCallPath);
271
+ return;
272
+ }
273
+ recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([fieldName]));
274
+ if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
275
+ } else markUntrackedBinding();
276
+ return;
277
+ }
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
+ if (babelTypes.isExpressionStatement(parentNode)) return;
293
+ markUntrackedBinding();
294
+ };
295
+ analyzeContentRoot(unwrapAwait(babelTypes, callExpressionPath));
296
+ };
297
+
298
+ //#endregion
299
+ export { analyzeCallExpressionUsage };
300
+ //# sourceMappingURL=analyzeContentUsage.mjs.map
@@ -0,0 +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,4 +1,5 @@
1
- import { CHAINABLE_RUNTIME_METHOD_NAMES, INTLAYER_CALLER_NAMES, unwrapAwait } from "./babel-plugin-intlayer-usage-analyzer.mjs";
1
+ import { CHAINABLE_RUNTIME_METHOD_NAMES, NATIVE_CALLER_NAME_SET } from "./nativeCallers.mjs";
2
+ import { unwrapAwait } from "./staticAstReaders.mjs";
2
3
 
3
4
  //#region src/babel-plugin-intlayer-field-rename.ts
4
5
  /**
@@ -310,7 +311,7 @@ const collectIntlayerCallerLocalNames = (babelTypes, programNode) => {
310
311
  for (const importSpecifier of statement.specifiers) {
311
312
  if (!babelTypes.isImportSpecifier(importSpecifier)) continue;
312
313
  const importedName = babelTypes.isIdentifier(importSpecifier.imported) ? importSpecifier.imported.name : importSpecifier.imported.value;
313
- if (INTLAYER_CALLER_NAMES.includes(importedName)) intlayerCallerLocalNameMap.set(importSpecifier.local.name, importedName);
314
+ if (NATIVE_CALLER_NAME_SET.has(importedName)) intlayerCallerLocalNameMap.set(importSpecifier.local.name, importedName);
314
315
  }
315
316
  }
316
317
  return intlayerCallerLocalNameMap;