@intlayer/babel 9.4.4 → 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 (91) 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-field-rename.d.ts +2 -1
  67. package/dist/types/babel-plugin-intlayer-field-rename.d.ts.map +1 -1
  68. package/dist/types/babel-plugin-intlayer-optimize.d.ts +9 -15
  69. package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
  70. package/dist/types/babel-plugin-intlayer-purge.d.ts +3 -2
  71. package/dist/types/babel-plugin-intlayer-purge.d.ts.map +1 -1
  72. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts +20 -187
  73. package/dist/types/babel-plugin-intlayer-usage-analyzer.d.ts.map +1 -1
  74. package/dist/types/compat/namespaceResolution.d.ts +66 -0
  75. package/dist/types/compat/namespaceResolution.d.ts.map +1 -0
  76. package/dist/types/compat/optimizePass.d.ts +58 -0
  77. package/dist/types/compat/optimizePass.d.ts.map +1 -0
  78. package/dist/types/compat/usageAnalysis.d.ts +75 -0
  79. package/dist/types/compat/usageAnalysis.d.ts.map +1 -0
  80. package/dist/types/dictionaryImports.d.ts +46 -0
  81. package/dist/types/dictionaryImports.d.ts.map +1 -0
  82. package/dist/types/index.d.ts +5 -2
  83. package/dist/types/nativeCallers.d.ts +33 -0
  84. package/dist/types/nativeCallers.d.ts.map +1 -0
  85. package/dist/types/pruneContext.d.ts +152 -0
  86. package/dist/types/pruneContext.d.ts.map +1 -0
  87. package/dist/types/staticAstReaders.d.ts +84 -0
  88. package/dist/types/staticAstReaders.d.ts.map +1 -0
  89. package/dist/types/transformers.d.ts +2 -1
  90. package/dist/types/transformers.d.ts.map +1 -1
  91. package/package.json +10 -10
@@ -1,694 +1,62 @@
1
+ import { CHAINABLE_RUNTIME_METHOD_NAMES, INTLAYER_CALLER_NAMES, NATIVE_CALLER_NAME_SET } from "./nativeCallers.mjs";
2
+ import { createPruneContext, preserveNestedDictionaryFields } from "./pruneContext.mjs";
3
+ import { readStaticString, unwrapAwait } from "./staticAstReaders.mjs";
4
+ import { analyzeCallExpressionUsage } from "./analyzeContentUsage.mjs";
5
+ import { createCompatUsageAnalyzer } from "./compat/usageAnalysis.mjs";
6
+
1
7
  //#region src/babel-plugin-intlayer-usage-analyzer.ts
2
- const createPruneContext = () => ({
3
- dictionaryKeyToFieldUsageMap: /* @__PURE__ */ new Map(),
4
- dictionariesWithEdgeCases: /* @__PURE__ */ new Set(),
5
- hasUnparsableSourceFiles: false,
6
- dictionaryKeysWithUntrackedBindings: /* @__PURE__ */ new Map(),
7
- dictionaryKeyToFieldRenameMap: /* @__PURE__ */ new Map(),
8
- dictionaryKeysWithOpaqueFields: /* @__PURE__ */ new Map(),
9
- dictionariesSkippingFieldRename: /* @__PURE__ */ new Set(),
10
- pendingFrameworkAnalysis: /* @__PURE__ */ new Map()
11
- });
12
8
  /**
13
- * Records `nest()` references in a prune context so nest targets stay
14
- * resolvable — and no larger than they need to be — after purge and minify.
15
- *
16
- * A nested node stores the ORIGINAL dot path of the field it reads
17
- * (`nest('common', 'period')` → `'period'`) and `getNesting` resolves it at
18
- * runtime against the compiled JSON. That drives three adjustments:
19
- *
20
- * - Nest targets never take part in field renaming, since the stored path
21
- * would no longer match the renamed keys.
22
- * - A target already consumed by a call site keeps its analysed fields plus
23
- * the nested ones, so neither consumer loses content.
24
- * - A target reached *only* through `nest()` is given a usage entry holding
25
- * exactly the nested fields. Without one, the purge pass leaves the whole
26
- * dictionary untouched; with one, it trims the dictionary down to what the
27
- * nest references actually read. This matters most for `dynamic` / `fetch`
28
- * dictionaries, whose full static snapshot would otherwise be pulled into
29
- * the eager bundle just to satisfy a single nested field.
30
- *
31
- * Seeding is skipped when a source file failed to parse: that file could
32
- * reference the dictionary in a way the analysis never saw, which is the same
33
- * guard the purge pass applies to keys with no usage entry.
34
- *
35
- * @param pruneContext - Shared analysis state to update in place.
36
- * @param nestedDictionaryReferences - Nest-target key → referenced top-level
37
- * fields, as returned by `getNestedDictionaryReferences`.
38
- */
39
- const preserveNestedDictionaryFields = (pruneContext, nestedDictionaryReferences) => {
40
- for (const [dictionaryKey, nestedFields] of nestedDictionaryReferences.entries()) {
41
- pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);
42
- const currentFieldUsage = pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);
43
- if (nestedFields === "all") {
44
- pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, "all");
45
- continue;
46
- }
47
- if (!currentFieldUsage) {
48
- if (pruneContext.hasUnparsableSourceFiles) continue;
49
- pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, new Set(nestedFields));
50
- continue;
51
- }
52
- if (currentFieldUsage === "all") continue;
53
- for (const nestedField of nestedFields) currentFieldUsage.add(nestedField);
54
- }
55
- };
56
- /**
57
- * Methods the runtime attaches to a resolved content object rather than fields
58
- * declared in a `.content` file.
59
- *
60
- * `vanilla-intlayer`'s `useIntlayer` / `useDictionary` assign `onChange` onto
61
- * the content they return, so `useIntlayer('app').onChange(cb)` is a
62
- * subscription — not a read of a field named `onChange`. Without this list the
63
- * analyser records `onChange` as the only consumed field and the purge pass
64
- * strips every real field from the compiled dictionary.
65
- *
66
- * The analyser looks *through* these calls: the callback parameter is analysed
67
- * as a fresh content root, and the call result is analysed again because the
68
- * helpers return the content object for chaining.
69
- */
70
- const CHAINABLE_RUNTIME_METHOD_NAMES = /* @__PURE__ */ new Set(["onChange"]);
71
- /** Canonical intlayer caller names that trigger usage analysis. */
72
- const INTLAYER_CALLER_NAMES = [
73
- "useIntlayer",
74
- "getIntlayer",
75
- "getIntlayerAsync"
76
- ];
77
- /**
78
- * Default registry of compat namespace callers.
79
- *
80
- * Intentionally empty — compat-specific caller configurations belong in their
81
- * respective adapter packages (e.g. `@intlayer/react-i18next/plugin`,
82
- * `@intlayer/vue-i18n/plugin`) and are injected into `makeUsageAnalyzerBabelPlugin`
83
- * via the `compatCallers` option. Centralising them here would couple the
84
- * core `@intlayer/babel` package to every compat adapter, which violates the
85
- * design principle that compat logic lives in compat packages.
9
+ * @deprecated Always empty. Compat callers are injected per build through
10
+ * `options.compatCallers`; there is no default registry to fall back to.
86
11
  */
87
12
  const DEFAULT_COMPAT_CALLERS = [];
88
- /** Default namespace used by compat callers when no namespace argument is given. */
89
- const DEFAULT_COMPAT_NAMESPACE = "translation";
90
- /**
91
- * Climbs past an enclosing `await` expression so that
92
- * `const t = await getTranslations('ns')` — or `await getIntlayerAsync('key')`
93
- * — is resolved to its variable declarator the same way the synchronous form
94
- * is.
95
- */
96
- const unwrapAwait = (babelTypes, path) => {
97
- const parentPath = path.parentPath;
98
- if (parentPath && babelTypes.isAwaitExpression(parentPath.node)) return parentPath;
99
- return path;
100
- };
101
- /**
102
- * Records the usage of a specific dictionary key's fields into `pruneContext`.
103
- * Merges with any previously recorded usage for the same key.
104
- */
105
- const recordFieldUsage = (pruneContext, dictionaryKey, fieldUsage) => {
106
- const existingUsage = pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);
107
- if (existingUsage === "all") return;
108
- if (fieldUsage === "all") {
109
- pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, "all");
110
- return;
111
- }
112
- if (existingUsage instanceof Set) {
113
- for (const fieldName of fieldUsage) existingUsage.add(fieldName);
114
- return;
115
- }
116
- pruneContext.dictionaryKeyToFieldUsageMap.set(dictionaryKey, new Set(fieldUsage));
117
- };
118
- /**
119
- * Analyses how the result of a single `useIntlayer('key')` / `getIntlayer('key')`
120
- * call expression is consumed, then records the field usage into `pruneContext`.
121
- *
122
- * Recognised patterns:
123
- * const { fieldA, fieldB } = useIntlayer('key') → records {fieldA, fieldB}
124
- * useIntlayer('key').fieldA → records {fieldA}
125
- * useIntlayer('key')['fieldA'] → records {fieldA}
126
- * const { ...rest } = useIntlayer('key') → records 'all' (spread)
127
- * const result = useIntlayer('key') → records 'all' (untracked binding)
128
- */
129
- const analyzeCallExpressionUsage = (babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile) => {
130
- /** Mark the dictionary key as having an untracked binding in this file. */
131
- const markUntrackedBinding = () => {
132
- const existingPaths = pruneContext.dictionaryKeysWithUntrackedBindings.get(dictionaryKey) ?? [];
133
- if (!existingPaths.includes(currentSourceFilePath)) pruneContext.dictionaryKeysWithUntrackedBindings.set(dictionaryKey, [...existingPaths, currentSourceFilePath]);
134
- recordFieldUsage(pruneContext, dictionaryKey, "all");
135
- };
136
- /** Record that the content value at `fieldPath` is consumed opaquely. */
137
- const markOpaqueField = (fieldPath, line) => {
138
- const pathToOccurrence = pruneContext.dictionaryKeysWithOpaqueFields.get(dictionaryKey) ?? /* @__PURE__ */ new Map();
139
- const location = line !== void 0 ? `${currentSourceFilePath}:${line}` : currentSourceFilePath;
140
- const joinedFieldPath = fieldPath.join(".");
141
- const occurrence = pathToOccurrence.get(joinedFieldPath) ?? {
142
- fieldPath: [...fieldPath],
143
- locations: []
144
- };
145
- if (!occurrence.locations.includes(location)) occurrence.locations.push(location);
146
- pathToOccurrence.set(joinedFieldPath, occurrence);
147
- pruneContext.dictionaryKeysWithOpaqueFields.set(dictionaryKey, pathToOccurrence);
148
- };
149
- /** Register a plain variable binding in an SFC file for a second-pass analysis. */
150
- const deferFrameworkAnalysis = (variableName) => {
151
- const existing = pruneContext.pendingFrameworkAnalysis.get(currentSourceFilePath) ?? [];
152
- if (!existing.some((e) => e.variableName === variableName && e.dictionaryKey === dictionaryKey)) existing.push({
153
- variableName,
154
- dictionaryKey
155
- });
156
- pruneContext.pendingFrameworkAnalysis.set(currentSourceFilePath, existing);
157
- };
158
- /**
159
- * Analyses how the content value at `fieldPath` (currently referenced by
160
- * `refPath`) is consumed, to detect opaque consumption (passing a dictionary
161
- * value as-is to a prop or function argument).
162
- *
163
- * Member-access chains and destructuring are followed recursively with an
164
- * extended field path, so the *terminal* consumption of a chain like
165
- * `content.sections.hero` decides opacity — mirroring how far the
166
- * source-code renamer can rewrite accesses. When the terminal consumption
167
- * escapes static tracking, the deepest reached field path is marked opaque
168
- * so the children of that value keep their original key names.
169
- */
170
- const analyzeOpaqueUsage = (refPath, fieldPath) => {
171
- const parentNode = refPath.parent;
172
- const parentPath = refPath.parentPath;
173
- if (parentPath && (babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === refPath.node) {
174
- const memberNode = parentNode;
175
- if (memberNode.computed && babelTypes.isNumericLiteral(memberNode.property)) {
176
- analyzeOpaqueUsage(parentPath, fieldPath);
177
- return;
178
- }
179
- if (!memberNode.computed && babelTypes.isIdentifier(memberNode.property)) {
180
- analyzeOpaqueUsage(parentPath, [...fieldPath, memberNode.property.name]);
181
- return;
182
- }
183
- if (memberNode.computed && babelTypes.isStringLiteral(memberNode.property)) {
184
- analyzeOpaqueUsage(parentPath, [...fieldPath, memberNode.property.value]);
185
- return;
186
- }
187
- markOpaqueField(fieldPath, refPath.node.loc?.start.line);
188
- return;
189
- }
190
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id) && parentNode.init === refPath.node) {
191
- analyzeOpaqueDestructuring(parentNode.id, refPath, fieldPath);
192
- return;
193
- }
194
- if (babelTypes.isArrayExpression(parentNode)) return;
195
- markOpaqueField(fieldPath, refPath.node.loc?.start.line);
196
- };
197
- /**
198
- * Analyses a destructuring pattern applied to the content value at
199
- * `fieldPath`, recursively following each bound local variable's references
200
- * via {@link analyzeOpaqueUsage}.
201
- *
202
- * Patterns the source-code renamer cannot rewrite (rest elements, dynamic
203
- * computed keys, array patterns) mark the corresponding path opaque.
204
- */
205
- const analyzeOpaqueDestructuring = (objectPattern, scopePath, fieldPath) => {
206
- for (const property of objectPattern.properties) {
207
- if (babelTypes.isRestElement(property)) {
208
- markOpaqueField(fieldPath, property.loc?.start.line);
209
- continue;
210
- }
211
- if (!babelTypes.isObjectProperty(property)) continue;
212
- if (property.computed && !babelTypes.isStringLiteral(property.key)) {
213
- markOpaqueField(fieldPath, property.loc?.start.line);
214
- continue;
215
- }
216
- const keyName = babelTypes.isIdentifier(property.key) ? property.key.name : babelTypes.isStringLiteral(property.key) ? property.key.value : null;
217
- if (!keyName) continue;
218
- const childFieldPath = [...fieldPath, keyName];
219
- const bindingTarget = babelTypes.isAssignmentPattern(property.value) ? property.value.left : property.value;
220
- if (babelTypes.isObjectPattern(bindingTarget)) {
221
- analyzeOpaqueDestructuring(bindingTarget, scopePath, childFieldPath);
222
- continue;
223
- }
224
- if (babelTypes.isIdentifier(bindingTarget)) {
225
- const localVarBinding = scopePath.scope.getBinding(bindingTarget.name);
226
- if (!localVarBinding) continue;
227
- for (const referencePath of localVarBinding.referencePaths) analyzeOpaqueUsage(referencePath, childFieldPath);
228
- continue;
229
- }
230
- markOpaqueField(childFieldPath, property.loc?.start.line);
231
- }
232
- };
233
- /**
234
- * Helper to collect field names from an ObjectPattern (destructuring).
235
- * Returns true if successful, false when the accessed fields cannot be
236
- * determined statically (rest element or dynamic computed key → 'all').
237
- */
238
- const collectFieldsFromObjectPattern = (pattern, initPath, targetSet) => {
239
- if (pattern.properties.some((prop) => babelTypes.isRestElement(prop))) return false;
240
- for (const property of pattern.properties) {
241
- if (!babelTypes.isObjectProperty(property)) continue;
242
- if (property.computed && !babelTypes.isStringLiteral(property.key)) return false;
243
- let fieldName;
244
- if (!property.computed && babelTypes.isIdentifier(property.key)) fieldName = property.key.name;
245
- else if (babelTypes.isStringLiteral(property.key)) fieldName = property.key.value;
246
- if (!fieldName) continue;
247
- targetSet.add(fieldName);
248
- const bindingTarget = babelTypes.isAssignmentPattern(property.value) ? property.value.left : property.value;
249
- if (babelTypes.isIdentifier(bindingTarget)) {
250
- const variableBinding = initPath.scope.getBinding(bindingTarget.name);
251
- if (variableBinding) for (const refPath of variableBinding.referencePaths) analyzeOpaqueUsage(refPath, [fieldName]);
252
- } else if (babelTypes.isObjectPattern(bindingTarget)) analyzeOpaqueDestructuring(bindingTarget, initPath, [fieldName]);
253
- else markOpaqueField([fieldName], property.loc?.start.line);
254
- }
255
- return true;
256
- };
257
- /**
258
- * Returns the call path when `memberExprPath` is a chainable runtime helper
259
- * being invoked (`content.onChange(cb)`), otherwise `undefined`.
260
- *
261
- * See {@link CHAINABLE_RUNTIME_METHOD_NAMES}: a bare `content.onChange`
262
- * reference that is never called is treated as an ordinary field access, so
263
- * only the invoked form is looked through.
264
- */
265
- const getChainedRuntimeCallPath = (memberExprPath, fieldName) => {
266
- if (!CHAINABLE_RUNTIME_METHOD_NAMES.has(fieldName)) return void 0;
267
- if (!memberExprPath) return void 0;
268
- const callPath = memberExprPath.parentPath;
269
- if (!callPath) return void 0;
270
- if (!callPath.isCallExpression() && !callPath.isOptionalCallExpression()) return;
271
- if (callPath.node.callee !== memberExprPath.node) return;
272
- return callPath;
273
- };
274
- /**
275
- * Analyses the callbacks handed to a chainable runtime helper.
276
- *
277
- * The callback receives the freshly-resolved content root, so its parameter
278
- * is walked exactly like `const content = useIntlayer('key')` — either as a
279
- * plain binding or as a destructuring pattern.
280
- */
281
- const analyzeChainedCallbackArguments = (callPath) => {
282
- const argumentPaths = callPath.get("arguments");
283
- for (const argumentPath of argumentPaths) {
284
- if (!argumentPath.isArrowFunctionExpression() && !argumentPath.isFunctionExpression()) continue;
285
- const [firstParameterPath] = argumentPath.get("params");
286
- if (!firstParameterPath) continue;
287
- if (firstParameterPath.isIdentifier()) {
288
- const parameterBinding = argumentPath.scope.getBinding(firstParameterPath.node.name);
289
- if (!parameterBinding) {
290
- markUntrackedBinding();
291
- continue;
292
- }
293
- const accessedFieldNames = /* @__PURE__ */ new Set();
294
- if (analyzeContentBindingReferences(parameterBinding, accessedFieldNames)) markUntrackedBinding();
295
- else recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
296
- continue;
297
- }
298
- if (firstParameterPath.isObjectPattern()) {
299
- const accessedFieldNames = /* @__PURE__ */ new Set();
300
- if (collectFieldsFromObjectPattern(firstParameterPath.node, argumentPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
301
- else recordFieldUsage(pruneContext, dictionaryKey, "all");
302
- continue;
303
- }
304
- markUntrackedBinding();
305
- }
306
- };
307
- /**
308
- * Walks every reference of a local binding holding the content root,
309
- * collecting the top-level fields it reads into `accessedTopLevelFieldNames`.
310
- *
311
- * @returns `true` when a reference escaped static tracking, meaning the whole
312
- * dictionary must be kept.
313
- */
314
- const analyzeContentBindingReferences = (variableBinding, accessedTopLevelFieldNames) => {
315
- for (const variableReferencePath of variableBinding.referencePaths) {
316
- const referenceParentNode = variableReferencePath.parent;
317
- if ((babelTypes.isMemberExpression(referenceParentNode) || babelTypes.isOptionalMemberExpression(referenceParentNode)) && referenceParentNode.object === variableReferencePath.node) {
318
- const memberExpressionNode = referenceParentNode;
319
- let fieldName;
320
- if (!memberExpressionNode.computed && babelTypes.isIdentifier(memberExpressionNode.property)) fieldName = memberExpressionNode.property.name;
321
- else if (memberExpressionNode.computed && babelTypes.isStringLiteral(memberExpressionNode.property)) fieldName = memberExpressionNode.property.value;
322
- if (fieldName) {
323
- const memberExprPath = variableReferencePath.parentPath;
324
- const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
325
- if (chainedCallPath) {
326
- analyzeChainedCallbackArguments(chainedCallPath);
327
- analyzeContentRoot(chainedCallPath);
328
- continue;
329
- }
330
- accessedTopLevelFieldNames.add(fieldName);
331
- if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
332
- } else return true;
333
- } else if (babelTypes.isArrayExpression(referenceParentNode)) return true;
334
- else if ((babelTypes.isCallExpression(referenceParentNode) || babelTypes.isOptionalCallExpression(referenceParentNode)) && referenceParentNode.callee === variableReferencePath.node) {
335
- const callExprPath = variableReferencePath.parentPath;
336
- const callParent = callExprPath?.parent;
337
- if (callParent && (babelTypes.isMemberExpression(callParent) || babelTypes.isOptionalMemberExpression(callParent)) && callParent.object === callExprPath?.node) {
338
- const memberExpr = callParent;
339
- let fieldName;
340
- if (!memberExpr.computed && babelTypes.isIdentifier(memberExpr.property)) fieldName = memberExpr.property.name;
341
- else if (memberExpr.computed && babelTypes.isStringLiteral(memberExpr.property)) fieldName = memberExpr.property.value;
342
- if (fieldName) {
343
- const memberExprPath = callExprPath?.parentPath;
344
- const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
345
- if (chainedCallPath) {
346
- analyzeChainedCallbackArguments(chainedCallPath);
347
- analyzeContentRoot(chainedCallPath);
348
- continue;
349
- }
350
- accessedTopLevelFieldNames.add(fieldName);
351
- if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
352
- } else return true;
353
- } else if (callParent && babelTypes.isVariableDeclarator(callParent) && babelTypes.isObjectPattern(callParent.id) && callExprPath && collectFieldsFromObjectPattern(callParent.id, callExprPath, accessedTopLevelFieldNames)) {} else return true;
354
- } else return true;
355
- }
356
- return false;
357
- };
358
- /**
359
- * Analyses how the content root referenced by `rootPath` is consumed.
360
- *
361
- * `rootPath` is the `useIntlayer('key')` call itself, or any expression that
362
- * evaluates back to the same content object — currently the result of a
363
- * chainable runtime helper such as `.onChange(…)`.
364
- */
365
- const analyzeContentRoot = (rootPath) => {
366
- const parentNode = rootPath.parent;
367
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
368
- const accessedFieldNames = /* @__PURE__ */ new Set();
369
- if (collectFieldsFromObjectPattern(parentNode.id, rootPath, accessedFieldNames)) recordFieldUsage(pruneContext, dictionaryKey, accessedFieldNames);
370
- else recordFieldUsage(pruneContext, dictionaryKey, "all");
371
- return;
372
- }
373
- if ((babelTypes.isMemberExpression(parentNode) || babelTypes.isOptionalMemberExpression(parentNode)) && parentNode.object === rootPath.node) {
374
- let fieldName;
375
- if (!parentNode.computed && babelTypes.isIdentifier(parentNode.property)) fieldName = parentNode.property.name;
376
- else if (parentNode.computed && babelTypes.isStringLiteral(parentNode.property)) fieldName = parentNode.property.value;
377
- if (fieldName) {
378
- const memberExprPath = rootPath.parentPath;
379
- const chainedCallPath = getChainedRuntimeCallPath(memberExprPath, fieldName);
380
- if (chainedCallPath) {
381
- analyzeChainedCallbackArguments(chainedCallPath);
382
- analyzeContentRoot(chainedCallPath);
383
- return;
384
- }
385
- recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([fieldName]));
386
- if (memberExprPath) analyzeOpaqueUsage(memberExprPath, [fieldName]);
387
- } else markUntrackedBinding();
388
- return;
389
- }
390
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) {
391
- const variableName = parentNode.id.name;
392
- const variableBinding = rootPath.scope.getBinding(variableName);
393
- if (!variableBinding) {
394
- markUntrackedBinding();
395
- return;
396
- }
397
- const accessedTopLevelFieldNames = /* @__PURE__ */ new Set();
398
- if (analyzeContentBindingReferences(variableBinding, accessedTopLevelFieldNames)) markUntrackedBinding();
399
- else if (isSfcFile) deferFrameworkAnalysis(variableName);
400
- else if (variableBinding.referencePaths.length === 0) markUntrackedBinding();
401
- else recordFieldUsage(pruneContext, dictionaryKey, accessedTopLevelFieldNames);
402
- return;
403
- }
404
- if (babelTypes.isExpressionStatement(parentNode)) return;
405
- markUntrackedBinding();
406
- };
407
- analyzeContentRoot(unwrapAwait(babelTypes, callExpressionPath));
408
- };
409
- /**
410
- * Reads a fully-static string from an AST node. Returns `undefined` for
411
- * dynamic values (identifiers, expressions, template literals with
412
- * interpolations, …).
413
- */
414
- const readStaticString = (babelTypes, node) => {
415
- if (!node) return void 0;
416
- if (babelTypes.isStringLiteral(node)) return node.value;
417
- if (babelTypes.isTemplateLiteral(node) && node.expressions.length === 0 && node.quasis.length === 1) return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
418
- };
419
- /** Returns the first dot-path segment of a key, e.g. `'a.b.c'` → `'a'`. */
420
- const firstPathSegment = (path) => path.split(".")[0] ?? path;
421
- /**
422
- * Reads the static first dot-path segment from a `t()` first-argument node.
423
- *
424
- * t('counter.label') → 'counter'
425
- * t(`counter.${x}`) → 'counter' (static prefix before the first dot)
426
- * t(`${x}.label`) → undefined (dynamic first segment)
427
- * t(someVariable) → undefined
428
- */
429
- const readStaticFirstSegment = (babelTypes, node) => {
430
- const staticString = readStaticString(babelTypes, node);
431
- if (staticString !== void 0) return firstPathSegment(staticString);
432
- if (babelTypes.isTemplateLiteral(node) && node.quasis.length > 0) {
433
- const firstQuasi = node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
434
- if (firstQuasi?.includes(".")) return firstPathSegment(firstQuasi);
435
- }
436
- };
437
- /**
438
- * Reads a static string property from an object expression. Returns
439
- * `'__default__'` when the property is absent and `undefined` when present but
440
- * dynamic.
441
- */
442
- const readObjectProperty = (babelTypes, objectExpression, propertyName) => {
443
- for (const property of objectExpression.properties) {
444
- if (!babelTypes.isObjectProperty(property)) continue;
445
- if (!(babelTypes.isIdentifier(property.key) && property.key.name === propertyName || babelTypes.isStringLiteral(property.key) && property.key.value === propertyName)) continue;
446
- return readStaticString(babelTypes, property.value) ?? void 0;
447
- }
448
- return "__default__";
449
- };
450
- /**
451
- * Reads a named JSX attribute as a static string and returns it wrapped in a
452
- * `StringLiteral` node so the result can be fed to the same namespace resolver
453
- * as a call argument. Handles both `id="home.title"` and `id={'home.title'}`.
454
- * Returns `undefined` when the attribute is absent or dynamic (`id={expr}`).
455
- */
456
- const readJsxAttributeString = (babelTypes, openingElement, attributeName) => {
457
- for (const attribute of openingElement.attributes) {
458
- if (!babelTypes.isJSXAttribute(attribute)) continue;
459
- if (!babelTypes.isJSXIdentifier(attribute.name) || attribute.name.name !== attributeName) continue;
460
- const value = attribute.value;
461
- if (babelTypes.isStringLiteral(value)) return value;
462
- if (babelTypes.isJSXExpressionContainer(value)) {
463
- const staticString = readStaticString(babelTypes, value.expression);
464
- if (staticString !== void 0) return babelTypes.stringLiteral(staticString);
465
- }
466
- return;
467
- }
468
- };
469
- /**
470
- * Resolves the namespace (dictionary key) for a compat caller call-site from
471
- * one `CallerValueSource`. Returns the static key, or `'__default__'` when the
472
- * configured argument is absent (caller falls back to its default namespace),
473
- * or `undefined` when the value is present but dynamic.
474
- */
475
- const resolveCompatNamespace = (babelTypes, callArguments, source) => {
476
- if (source.from === "fixed") return source.value;
477
- if (source.from === "path-first-segment") {
478
- const firstArg = callArguments[0];
479
- if (firstArg === void 0) return void 0;
480
- const staticString = readStaticString(babelTypes, firstArg);
481
- if (staticString !== void 0) return firstPathSegment(staticString);
482
- if (babelTypes.isObjectExpression(firstArg)) {
483
- const idValue = readObjectProperty(babelTypes, firstArg, "id");
484
- if (idValue === "__default__") return "__default__";
485
- if (idValue !== void 0) return firstPathSegment(idValue);
486
- return;
487
- }
488
- return;
489
- }
490
- if (source.from === "argument") {
491
- const argument = callArguments[source.index];
492
- if (argument === void 0) return "__default__";
493
- const staticString = readStaticString(babelTypes, argument);
494
- if (staticString !== void 0) return staticString;
495
- if (babelTypes.isObjectExpression(argument)) return readObjectProperty(babelTypes, argument, "namespace");
496
- return;
497
- }
498
- const optionsArgument = callArguments[source.argumentIndex];
499
- if (optionsArgument === void 0) return "__default__";
500
- if (!babelTypes.isObjectExpression(optionsArgument)) return void 0;
501
- return readObjectProperty(babelTypes, optionsArgument, source.property);
502
- };
503
- /**
504
- * Resolves the namespace from a list of `CallerValueSource`s, tried in
505
- * declaration order. The first source yielding a static string wins.
506
- * Returns `'__default__'` when every source reports an absent value, and
507
- * `undefined` when the namespace is present somewhere but dynamic.
508
- */
509
- const resolveNamespaceFromSources = (babelTypes, callArguments, sources) => {
510
- let sawAbsentValue = false;
511
- for (const source of sources) {
512
- const resolved = resolveCompatNamespace(babelTypes, callArguments, source);
513
- if (typeof resolved === "string" && resolved !== "__default__") return resolved;
514
- if (resolved === "__default__") sawAbsentValue = true;
515
- }
516
- return sawAbsentValue ? "__default__" : void 0;
517
- };
518
- /**
519
- * Resolves an optional `keyPrefix` for a compat caller. Returns the static
520
- * prefix string, `null` when no prefix is configured/present, or `undefined`
521
- * when a prefix is present but dynamic.
522
- */
523
- const resolveCompatKeyPrefix = (babelTypes, callArguments, sources) => {
524
- if (!sources || sources.length === 0) return null;
525
- const resolved = resolveNamespaceFromSources(babelTypes, callArguments, sources);
526
- if (resolved === "__default__") return null;
527
- return resolved;
528
- };
529
- /**
530
- * Analyses how the translation function produced by a compat namespace caller
531
- * (`useTranslation`, `useTranslations`, `getTranslations`, `getFixedT`,
532
- * `useI18n`) is consumed, then records the accessed top-level dictionary fields
533
- * into `pruneContext`.
534
- *
535
- * Dictionaries consumed this way are always added to
536
- * `dictionariesSkippingFieldRename`: the field accesses are string-literal
537
- * dot-paths inside `t()` calls, which the field-rename plugin cannot rewrite,
538
- * so renaming the compiled JSON keys would break runtime lookups. Pruning
539
- * (top-level field removal) remains safe because it preserves field names.
540
- */
541
- const analyzeNamespaceCallerUsage = (babelTypes, pruneContext, callArguments, callerConfig, isSfcFile, currentSourceFilePath, callExpressionPath, namespaceOverride) => {
542
- const resolvedNamespace = namespaceOverride ?? resolveNamespaceFromSources(babelTypes, callArguments, callerConfig.namespaceSources);
543
- if (resolvedNamespace === void 0) return;
544
- const namespaceString = resolvedNamespace === "__default__" ? DEFAULT_COMPAT_NAMESPACE : resolvedNamespace;
545
- const namespaceSegments = namespaceString.split(".");
546
- const dictionaryKey = namespaceSegments[0] ?? namespaceString;
547
- const namespacePrefix = namespaceSegments.length > 1 ? namespaceSegments.slice(1).join(".") : null;
548
- pruneContext.dictionariesSkippingFieldRename.add(dictionaryKey);
549
- if (isSfcFile) {
550
- recordFieldUsage(pruneContext, dictionaryKey, "all");
551
- return;
552
- }
553
- if (callerConfig.translationFunction === "all") {
554
- recordFieldUsage(pruneContext, dictionaryKey, "all");
555
- return;
556
- }
557
- if (callerConfig.translationFunction === "content") {
558
- if (callExpressionPath) analyzeCallExpressionUsage(babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile);
559
- else recordFieldUsage(pruneContext, dictionaryKey, "all");
560
- return;
561
- }
562
- if (callerConfig.translationFunction === "self") {
563
- const firstArg = callArguments[0];
564
- if (!firstArg) {
565
- recordFieldUsage(pruneContext, dictionaryKey, "all");
566
- return;
567
- }
568
- if (callerConfig.namespaceSources.some((source) => source.from === "path-first-segment")) {
569
- let fullId;
570
- const staticString = readStaticString(babelTypes, firstArg);
571
- if (staticString !== void 0) fullId = staticString;
572
- else if (babelTypes.isObjectExpression(firstArg)) {
573
- const idValue = readObjectProperty(babelTypes, firstArg, "id");
574
- if (idValue !== void 0 && idValue !== "__default__") fullId = idValue;
575
- }
576
- if (fullId !== void 0) {
577
- const field = fullId.split(".")[1];
578
- if (field !== void 0) recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([field]));
579
- else recordFieldUsage(pruneContext, dictionaryKey, "all");
580
- return;
581
- }
582
- recordFieldUsage(pruneContext, dictionaryKey, "all");
583
- return;
584
- }
585
- const segment = callerConfig.flatKey ? readStaticString(babelTypes, firstArg) : readStaticFirstSegment(babelTypes, firstArg);
586
- if (segment !== void 0) {
587
- recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([segment]));
588
- return;
589
- }
590
- if (babelTypes.isObjectExpression(firstArg)) {
591
- const idValue = readObjectProperty(babelTypes, firstArg, "id");
592
- if (idValue !== void 0 && idValue !== "__default__") {
593
- recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([callerConfig.flatKey ? idValue : firstPathSegment(idValue)]));
594
- return;
595
- }
596
- }
597
- recordFieldUsage(pruneContext, dictionaryKey, "all");
598
- return;
599
- }
600
- if (!callExpressionPath) {
601
- recordFieldUsage(pruneContext, dictionaryKey, "all");
602
- return;
603
- }
604
- const explicitKeyPrefix = resolveCompatKeyPrefix(babelTypes, callArguments, callerConfig.keyPrefixSources);
605
- if (explicitKeyPrefix === void 0) {
606
- recordFieldUsage(pruneContext, dictionaryKey, "all");
607
- return;
608
- }
609
- const effectivePrefix = namespacePrefix ?? explicitKeyPrefix;
610
- if (effectivePrefix !== null) {
611
- recordFieldUsage(pruneContext, dictionaryKey, /* @__PURE__ */ new Set([firstPathSegment(effectivePrefix)]));
612
- return;
613
- }
614
- let translationBinding = null;
615
- if (callerConfig.translationFunction === "destructured-t") {
616
- const parentNode = callExpressionPath.parent;
617
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isObjectPattern(parentNode.id)) {
618
- for (const property of parentNode.id.properties) if (babelTypes.isObjectProperty(property) && babelTypes.isIdentifier(property.key) && property.key.name === "t" && babelTypes.isIdentifier(property.value)) translationBinding = callExpressionPath.scope.getBinding(property.value.name) ?? null;
619
- }
620
- } else {
621
- const parentNode = unwrapAwait(babelTypes, callExpressionPath).parent;
622
- if (babelTypes.isVariableDeclarator(parentNode) && babelTypes.isIdentifier(parentNode.id)) translationBinding = callExpressionPath.scope.getBinding(parentNode.id.name) ?? null;
623
- }
624
- if (!translationBinding) {
625
- recordFieldUsage(pruneContext, dictionaryKey, "all");
626
- return;
627
- }
628
- const accessedFields = /* @__PURE__ */ new Set();
629
- let hasUntrackedUsage = false;
630
- for (const referencePath of translationBinding.referencePaths) {
631
- const parentNode = referencePath.parent;
632
- if (!((babelTypes.isCallExpression(parentNode) || babelTypes.isOptionalCallExpression(parentNode)) && parentNode.callee === referencePath.node)) {
633
- hasUntrackedUsage = true;
634
- break;
635
- }
636
- const firstArgument = parentNode.arguments[0];
637
- const segment = readStaticFirstSegment(babelTypes, firstArgument);
638
- if (segment === void 0) {
639
- hasUntrackedUsage = true;
640
- break;
641
- }
642
- accessedFields.add(segment);
643
- }
644
- if (hasUntrackedUsage) {
645
- recordFieldUsage(pruneContext, dictionaryKey, "all");
646
- return;
647
- }
648
- if (accessedFields.size > 0) recordFieldUsage(pruneContext, dictionaryKey, accessedFields);
649
- };
650
13
  /**
651
14
  * Creates a Babel plugin that traverses source files and records which
652
- * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site —
653
- * and each configured compat namespace caller — accesses. Results are
654
- * accumulated into `pruneContext`.
15
+ * top-level dictionary fields each `useIntlayer` / `getIntlayer` call-site
16
+ * accesses. Results are accumulated into `pruneContext`.
17
+ *
18
+ * Compat-adapter callers (`useTranslation`, `useTranslations`, `formatMessage`,
19
+ * …) are *not* known to this plugin. Each compat package's bundler plugin
20
+ * injects its own descriptors through `options.compatCallers`, and the whole
21
+ * compat analysis then runs behind {@link createCompatUsageAnalyzer}. With no
22
+ * descriptors injected — the default — none of that code is reached, so a
23
+ * plain intlayer build behaves exactly as if the adapters did not exist.
655
24
  *
656
25
  * This plugin is analysis-only: it does not transform the code (`code: false`
657
26
  * should be passed to `transformAsync` when using it).
658
27
  *
659
28
  * @param pruneContext - Shared mutable state written by this plugin.
660
- * @param options - Optional overrides. `compatCallers` defaults to
661
- * {@link DEFAULT_COMPAT_CALLERS}; pass `[]` to disable
662
- * compat-adapter analysis entirely.
29
+ * @param options - Optional overrides. `compatCallers` defaults to an
30
+ * empty registry, disabling compat-adapter analysis.
663
31
  */
664
32
  const makeUsageAnalyzerBabelPlugin = (pruneContext, options) => ({ types: babelTypes }) => {
665
- const compatCallers = options?.compatCallers ?? DEFAULT_COMPAT_CALLERS;
33
+ const compatCallers = options?.compatCallers ?? [];
666
34
  return {
667
35
  name: "intlayer-usage-analyzer",
668
36
  visitor: { Program: { exit: (programPath, state) => {
669
- const currentSourceFilePath = state.file.opts.filename ?? "unknown file";
670
- const isSfcFile = currentSourceFilePath.endsWith(".vue") || currentSourceFilePath.endsWith(".svelte") || currentSourceFilePath.endsWith(".astro");
671
- const intlayerCallerLocalNameMap = /* @__PURE__ */ new Map();
672
- const compatCallerLocalNameMap = /* @__PURE__ */ new Map();
673
- const fileImportSources = /* @__PURE__ */ new Set();
674
- programPath.traverse({ ImportDeclaration: (importDeclarationPath) => {
675
- const importSource = importDeclarationPath.node.source.value;
676
- fileImportSources.add(importSource);
677
- for (const importSpecifier of importDeclarationPath.node.specifiers) {
37
+ const sourceFilePath = state.file.opts.filename ?? "unknown file";
38
+ const isSfcFile = sourceFilePath.endsWith(".vue") || sourceFilePath.endsWith(".svelte") || sourceFilePath.endsWith(".astro");
39
+ const compatAnalyzer = createCompatUsageAnalyzer(babelTypes, pruneContext, compatCallers, {
40
+ sourceFilePath,
41
+ isSfcFile
42
+ });
43
+ const nativeCallerLocalNames = /* @__PURE__ */ new Set();
44
+ for (const statement of programPath.node.body) {
45
+ if (!babelTypes.isImportDeclaration(statement)) continue;
46
+ const importSource = statement.source.value;
47
+ for (const importSpecifier of statement.specifiers) {
678
48
  if (!babelTypes.isImportSpecifier(importSpecifier)) continue;
679
49
  const importedName = babelTypes.isIdentifier(importSpecifier.imported) ? importSpecifier.imported.name : importSpecifier.imported.value;
680
- if (INTLAYER_CALLER_NAMES.includes(importedName)) {
681
- intlayerCallerLocalNameMap.set(importSpecifier.local.name, importedName);
50
+ const localName = importSpecifier.local.name;
51
+ if (NATIVE_CALLER_NAME_SET.has(importedName)) {
52
+ nativeCallerLocalNames.add(localName);
682
53
  continue;
683
54
  }
684
- const compatCaller = compatCallers.find((caller) => caller.callerName === importedName && caller.importSources.includes(importSource));
685
- if (compatCaller) compatCallerLocalNameMap.set(importSpecifier.local.name, compatCaller);
55
+ compatAnalyzer?.noteImport(importSource, importedName, localName);
686
56
  }
687
- } });
688
- const methodCompatCallers = compatCallers.filter((caller) => caller.matchAsMethod && (!caller.requiresImport || caller.importSources.some((source) => fileImportSources.has(source))));
689
- const hasNativeCallers = intlayerCallerLocalNameMap.size > 0;
690
- const hasCompatCallers = compatCallerLocalNameMap.size > 0 || methodCompatCallers.length > 0;
691
- if (!hasNativeCallers && !hasCompatCallers) return;
57
+ }
58
+ const hasCompatCallers = compatAnalyzer?.hasMatchableCallers() ?? false;
59
+ if (nativeCallerLocalNames.size === 0 && !hasCompatCallers) return;
692
60
  programPath.traverse({
693
61
  CallExpression: (callExpressionPath) => {
694
62
  const calleeNode = callExpressionPath.node.callee;
@@ -700,37 +68,18 @@ const makeUsageAnalyzerBabelPlugin = (pruneContext, options) => ({ types: babelT
700
68
  isMethodCall = true;
701
69
  }
702
70
  if (!localCallerName) return;
703
- if (intlayerCallerLocalNameMap.has(localCallerName)) {
71
+ if (nativeCallerLocalNames.has(localCallerName)) {
704
72
  const callArguments = callExpressionPath.node.arguments;
705
73
  if (callArguments.length === 0) return;
706
74
  const dictionaryKey = readStaticString(babelTypes, callArguments[0]);
707
75
  if (!dictionaryKey) return;
708
- analyzeCallExpressionUsage(babelTypes, pruneContext, callExpressionPath, dictionaryKey, currentSourceFilePath, isSfcFile);
709
- return;
710
- }
711
- const importedCompatCaller = compatCallerLocalNameMap.get(localCallerName);
712
- if (importedCompatCaller && !isMethodCall) {
713
- analyzeNamespaceCallerUsage(babelTypes, pruneContext, callExpressionPath.node.arguments, importedCompatCaller, isSfcFile, currentSourceFilePath, callExpressionPath);
76
+ analyzeCallExpressionUsage(babelTypes, pruneContext, callExpressionPath, dictionaryKey, sourceFilePath, isSfcFile);
714
77
  return;
715
78
  }
716
- if (isMethodCall) {
717
- const methodCaller = methodCompatCallers.find((caller) => caller.callerName === localCallerName);
718
- if (methodCaller) analyzeNamespaceCallerUsage(babelTypes, pruneContext, callExpressionPath.node.arguments, methodCaller, isSfcFile, currentSourceFilePath, callExpressionPath);
719
- }
79
+ compatAnalyzer?.analyzeCall(callExpressionPath, localCallerName, isMethodCall);
720
80
  },
721
81
  JSXOpeningElement: (jsxOpeningElementPath) => {
722
- const nameNode = jsxOpeningElementPath.node.name;
723
- if (!babelTypes.isJSXIdentifier(nameNode)) return;
724
- const jsxCaller = compatCallerLocalNameMap.get(nameNode.name);
725
- if (!jsxCaller?.jsxIdAttribute) return;
726
- const idNode = readJsxAttributeString(babelTypes, jsxOpeningElementPath.node, jsxCaller.jsxIdAttribute);
727
- let namespaceOverride;
728
- if (jsxCaller.jsxNamespaceAttribute) {
729
- const namespaceNode = readJsxAttributeString(babelTypes, jsxOpeningElementPath.node, jsxCaller.jsxNamespaceAttribute);
730
- if (!namespaceNode) return;
731
- namespaceOverride = namespaceNode.value;
732
- }
733
- analyzeNamespaceCallerUsage(babelTypes, pruneContext, idNode ? [idNode] : [], jsxCaller, isSfcFile, currentSourceFilePath, void 0, namespaceOverride);
82
+ compatAnalyzer?.analyzeJsxElement(jsxOpeningElementPath);
734
83
  }
735
84
  });
736
85
  } } }
@@ -738,5 +87,5 @@ const makeUsageAnalyzerBabelPlugin = (pruneContext, options) => ({ types: babelT
738
87
  };
739
88
 
740
89
  //#endregion
741
- export { CHAINABLE_RUNTIME_METHOD_NAMES, DEFAULT_COMPAT_CALLERS, INTLAYER_CALLER_NAMES, createPruneContext, makeUsageAnalyzerBabelPlugin, preserveNestedDictionaryFields, unwrapAwait };
90
+ export { CHAINABLE_RUNTIME_METHOD_NAMES, DEFAULT_COMPAT_CALLERS, INTLAYER_CALLER_NAMES, NATIVE_CALLER_NAME_SET, createPruneContext, makeUsageAnalyzerBabelPlugin, preserveNestedDictionaryFields, unwrapAwait };
742
91
  //# sourceMappingURL=babel-plugin-intlayer-usage-analyzer.mjs.map