react-native-boost 0.6.2 → 0.7.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.
@@ -2,9 +2,9 @@
2
2
 
3
3
  var helperPluginUtils = require('@babel/helper-plugin-utils');
4
4
  var core = require('@babel/core');
5
- var helperModuleImports = require('@babel/helper-module-imports');
6
5
  var minimatch = require('minimatch');
7
6
  var nodePath = require('node:path');
7
+ var helperModuleImports = require('@babel/helper-module-imports');
8
8
 
9
9
  class PluginError extends Error {
10
10
  constructor(message) {
@@ -13,52 +13,6 @@ class PluginError extends Error {
13
13
  }
14
14
  }
15
15
 
16
- const RUNTIME_MODULE_NAME = "react-native-boost/runtime";
17
- const ACCESSIBILITY_PROPERTIES = /* @__PURE__ */ new Set([
18
- "accessibilityLabel",
19
- "aria-label",
20
- "accessibilityState",
21
- "aria-busy",
22
- "aria-checked",
23
- "aria-disabled",
24
- "aria-expanded",
25
- "aria-selected",
26
- "accessible"
27
- ]);
28
-
29
- function addFileImportHint({
30
- file,
31
- nameHint,
32
- path,
33
- importName,
34
- moduleName,
35
- importType = "named"
36
- }) {
37
- var _a;
38
- if (!((_a = file.__hasImports) == null ? void 0 : _a[nameHint])) {
39
- file.__hasImports = file.__hasImports || {};
40
- file.__hasImports[nameHint] = importType === "default" ? helperModuleImports.addDefault(path, moduleName, { nameHint }) : helperModuleImports.addNamed(path, importName, moduleName, { nameHint });
41
- }
42
- return file.__hasImports[nameHint];
43
- }
44
- const replaceWithNativeComponent = (path, parent, file, nativeComponentName) => {
45
- const nativeIdentifier = addFileImportHint({
46
- file,
47
- nameHint: nativeComponentName,
48
- path,
49
- importName: nativeComponentName,
50
- moduleName: RUNTIME_MODULE_NAME,
51
- importType: "named"
52
- });
53
- const currentName = path.node.name.name;
54
- const jsxName = path.node.name;
55
- jsxName.name = nativeIdentifier.name;
56
- if (!path.node.selfClosing && parent.closingElement && core.types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === currentName) {
57
- parent.closingElement.name.name = nativeIdentifier.name;
58
- }
59
- return nativeIdentifier;
60
- };
61
-
62
16
  const ensureArray = (value) => {
63
17
  if (Array.isArray(value)) return value;
64
18
  return [value];
@@ -159,6 +113,372 @@ const isReactNativeImport = (path, expectedImportedName) => {
159
113
  }
160
114
  return false;
161
115
  };
116
+ const hasUnsafeViewAncestor = (path, allowUnknownAncestors = false) => {
117
+ const classification = classifyViewAncestors(path);
118
+ if (classification === "text") return true;
119
+ if (classification === "unknown" && !allowUnknownAncestors) return true;
120
+ return false;
121
+ };
122
+ function classifyViewAncestors(path) {
123
+ const context = {
124
+ componentCache: /* @__PURE__ */ new WeakMap(),
125
+ componentInProgress: /* @__PURE__ */ new WeakSet(),
126
+ renderExpressionInProgress: /* @__PURE__ */ new WeakSet()
127
+ };
128
+ let classification = "safe";
129
+ let ancestorPath = path.parentPath.parentPath;
130
+ while (ancestorPath) {
131
+ if (ancestorPath.isJSXElement()) {
132
+ const ancestorClassification = classifyJSXElementAsAncestor(ancestorPath, context);
133
+ classification = mergeAncestorClassification(classification, ancestorClassification);
134
+ if (classification === "text") return classification;
135
+ }
136
+ ancestorPath = ancestorPath.parentPath;
137
+ }
138
+ return classification;
139
+ }
140
+ function classifyJSXElementAsAncestor(path, context) {
141
+ const openingElementName = path.node.openingElement.name;
142
+ if (core.types.isJSXIdentifier(openingElementName)) {
143
+ return classifyJSXIdentifierAsAncestor(path, openingElementName.name, context);
144
+ }
145
+ if (core.types.isJSXMemberExpression(openingElementName)) {
146
+ return classifyJSXMemberExpressionAsAncestor(path, openingElementName);
147
+ }
148
+ return "unknown";
149
+ }
150
+ function classifyJSXIdentifierAsAncestor(path, identifierName, context) {
151
+ if (identifierName === "Fragment") return "safe";
152
+ const binding = path.scope.getBinding(identifierName);
153
+ if (!binding) return "unknown";
154
+ return classifyBindingAsAncestor(binding, context);
155
+ }
156
+ function classifyJSXMemberExpressionAsAncestor(path, expression) {
157
+ if (!core.types.isJSXIdentifier(expression.object) || !core.types.isJSXIdentifier(expression.property)) {
158
+ return "unknown";
159
+ }
160
+ const binding = path.scope.getBinding(expression.object.name);
161
+ if (!binding || binding.kind !== "module" || !core.types.isImportNamespaceSpecifier(binding.path.node)) {
162
+ return "unknown";
163
+ }
164
+ const importDeclaration = binding.path.parent;
165
+ if (!core.types.isImportDeclaration(importDeclaration)) return "unknown";
166
+ if (importDeclaration.source.value === "react-native") {
167
+ return expression.property.name === "Text" ? "text" : "safe";
168
+ }
169
+ if (importDeclaration.source.value === "react" && expression.property.name === "Fragment") {
170
+ return "safe";
171
+ }
172
+ return "unknown";
173
+ }
174
+ function classifyBindingAsAncestor(binding, context) {
175
+ if (binding.kind === "module") {
176
+ return classifyModuleBindingAsAncestor(binding);
177
+ }
178
+ return classifyLocalBindingAsAncestor(binding, context);
179
+ }
180
+ function classifyModuleBindingAsAncestor(binding) {
181
+ const importDeclaration = binding.path.parent;
182
+ if (!core.types.isImportDeclaration(importDeclaration)) return "unknown";
183
+ const source = importDeclaration.source.value;
184
+ if (source === "react-native") {
185
+ if (core.types.isImportSpecifier(binding.path.node)) {
186
+ const importedName = getImportSpecifierImportedName(binding.path.node);
187
+ if (!importedName) return "unknown";
188
+ return importedName === "Text" ? "text" : "safe";
189
+ }
190
+ if (core.types.isImportNamespaceSpecifier(binding.path.node)) {
191
+ return "safe";
192
+ }
193
+ return "unknown";
194
+ }
195
+ if (source === "react" && core.types.isImportSpecifier(binding.path.node)) {
196
+ const importedName = getImportSpecifierImportedName(binding.path.node);
197
+ if (importedName === "Fragment") return "safe";
198
+ }
199
+ return "unknown";
200
+ }
201
+ function classifyLocalBindingAsAncestor(binding, context) {
202
+ const cacheKey = binding.path.node;
203
+ const cached = context.componentCache.get(cacheKey);
204
+ if (cached) return cached;
205
+ if (context.componentInProgress.has(cacheKey)) {
206
+ return "unknown";
207
+ }
208
+ context.componentInProgress.add(cacheKey);
209
+ let classification;
210
+ if (binding.path.isFunctionDeclaration()) {
211
+ classification = analyzeFunctionComponent(binding.path, context);
212
+ } else if (binding.path.isVariableDeclarator()) {
213
+ classification = analyzeVariableDeclaratorComponent(binding.path, context);
214
+ } else {
215
+ classification = "unknown";
216
+ }
217
+ context.componentInProgress.delete(cacheKey);
218
+ context.componentCache.set(cacheKey, classification);
219
+ return classification;
220
+ }
221
+ function analyzeVariableDeclaratorComponent(path, context) {
222
+ const initPath = path.get("init");
223
+ if (!initPath.node) return "unknown";
224
+ if (initPath.isArrowFunctionExpression() || initPath.isFunctionExpression()) {
225
+ return analyzeFunctionComponent(initPath, context);
226
+ }
227
+ if (initPath.isCallExpression()) {
228
+ return analyzeCallWrappedComponent(initPath, context);
229
+ }
230
+ if (initPath.isIdentifier()) {
231
+ const aliasBinding = path.scope.getBinding(initPath.node.name);
232
+ if (!aliasBinding) return "unknown";
233
+ return classifyBindingAsAncestor(aliasBinding, context);
234
+ }
235
+ return "unknown";
236
+ }
237
+ function analyzeCallWrappedComponent(path, context) {
238
+ if (!isReactMemoOrForwardRefCall(path)) return "unknown";
239
+ const [firstArgumentPath] = path.get("arguments");
240
+ if (!(firstArgumentPath == null ? void 0 : firstArgumentPath.node)) return "unknown";
241
+ if (firstArgumentPath.isArrowFunctionExpression() || firstArgumentPath.isFunctionExpression()) {
242
+ return analyzeFunctionComponent(firstArgumentPath, context);
243
+ }
244
+ if (firstArgumentPath.isIdentifier()) {
245
+ const wrappedComponentBinding = path.scope.getBinding(firstArgumentPath.node.name);
246
+ if (!wrappedComponentBinding) return "unknown";
247
+ return classifyBindingAsAncestor(wrappedComponentBinding, context);
248
+ }
249
+ if (firstArgumentPath.isCallExpression()) {
250
+ return analyzeCallWrappedComponent(firstArgumentPath, context);
251
+ }
252
+ return "unknown";
253
+ }
254
+ function isReactMemoOrForwardRefCall(path) {
255
+ const calleePath = path.get("callee");
256
+ if (calleePath.isIdentifier()) {
257
+ if (!isMemoOrForwardRefName(calleePath.node.name)) return false;
258
+ const binding = path.scope.getBinding(calleePath.node.name);
259
+ return isReactImportBinding(binding);
260
+ }
261
+ if (calleePath.isMemberExpression()) {
262
+ const objectPath = calleePath.get("object");
263
+ const propertyPath = calleePath.get("property");
264
+ if (!objectPath.isIdentifier() || !propertyPath.isIdentifier()) return false;
265
+ if (!isMemoOrForwardRefName(propertyPath.node.name)) return false;
266
+ const objectBinding = path.scope.getBinding(objectPath.node.name);
267
+ return isReactImportBinding(objectBinding);
268
+ }
269
+ return false;
270
+ }
271
+ function isMemoOrForwardRefName(name) {
272
+ return name === "memo" || name === "forwardRef";
273
+ }
274
+ function isReactImportBinding(binding) {
275
+ if (!binding || binding.kind !== "module") return false;
276
+ const importDeclaration = binding.path.parent;
277
+ return core.types.isImportDeclaration(importDeclaration) && importDeclaration.source.value === "react";
278
+ }
279
+ function analyzeFunctionComponent(path, context) {
280
+ const bodyPath = path.get("body");
281
+ if (!bodyPath.isBlockStatement()) {
282
+ return analyzeRenderExpression(bodyPath, context);
283
+ }
284
+ let classification = "safe";
285
+ for (const statementPath of bodyPath.get("body")) {
286
+ if (!statementPath.isReturnStatement()) continue;
287
+ const argumentPath = statementPath.get("argument");
288
+ if (!argumentPath.node) continue;
289
+ const returnClassification = analyzeRenderExpression(argumentPath, context);
290
+ classification = mergeAncestorClassification(classification, returnClassification);
291
+ if (classification === "text") return classification;
292
+ }
293
+ return classification;
294
+ }
295
+ function analyzeRenderExpression(path, context) {
296
+ if (path.isJSXFragment()) {
297
+ return analyzeJSXChildren(path.get("children"), context);
298
+ }
299
+ let classification = "safe";
300
+ let hasJSX = false;
301
+ path.traverse({
302
+ JSXOpeningElement(jsxPath) {
303
+ hasJSX = true;
304
+ const jsxElementPath = jsxPath.parentPath;
305
+ if (!jsxElementPath.isJSXElement()) {
306
+ classification = mergeAncestorClassification(classification, "unknown");
307
+ return;
308
+ }
309
+ const jsxClassification = classifyJSXElementAsAncestor(jsxElementPath, context);
310
+ classification = mergeAncestorClassification(classification, jsxClassification);
311
+ if (classification === "text") {
312
+ jsxPath.stop();
313
+ }
314
+ }
315
+ });
316
+ if (hasJSX) return classification;
317
+ if (path.isIdentifier()) {
318
+ return analyzeIdentifierRenderExpression(path, context);
319
+ }
320
+ if (path.isMemberExpression() && isPropsChildrenMemberExpression(path.node)) {
321
+ return "safe";
322
+ }
323
+ if (path.isNullLiteral() || path.isBooleanLiteral() || path.isNumericLiteral() || path.isStringLiteral() || path.isBigIntLiteral()) {
324
+ return "safe";
325
+ }
326
+ return "unknown";
327
+ }
328
+ function analyzeJSXChildren(children, context) {
329
+ let classification = "safe";
330
+ for (const childPath of children) {
331
+ if (childPath.isJSXElement()) {
332
+ const childClassification = classifyJSXElementAsAncestor(childPath, context);
333
+ classification = mergeAncestorClassification(classification, childClassification);
334
+ } else if (childPath.isJSXFragment()) {
335
+ const fragmentClassification = analyzeJSXChildren(childPath.get("children"), context);
336
+ classification = mergeAncestorClassification(classification, fragmentClassification);
337
+ } else if (childPath.isJSXExpressionContainer()) {
338
+ const expressionPath = childPath.get("expression");
339
+ if (!expressionPath.node || expressionPath.isJSXEmptyExpression()) continue;
340
+ const expressionClassification = analyzeRenderExpression(expressionPath, context);
341
+ classification = mergeAncestorClassification(classification, expressionClassification);
342
+ } else if (childPath.isJSXSpreadChild()) {
343
+ classification = mergeAncestorClassification(classification, "unknown");
344
+ }
345
+ if (classification === "text") {
346
+ return classification;
347
+ }
348
+ }
349
+ return classification;
350
+ }
351
+ function analyzeIdentifierRenderExpression(path, context) {
352
+ if (path.node.name === "children") return "safe";
353
+ const binding = path.scope.getBinding(path.node.name);
354
+ if (!binding) return "unknown";
355
+ if (binding.kind === "param") {
356
+ return binding.identifier.name === "children" ? "safe" : "unknown";
357
+ }
358
+ if (!binding.path.isVariableDeclarator()) return "unknown";
359
+ const cacheKey = binding.path.node;
360
+ if (context.renderExpressionInProgress.has(cacheKey)) {
361
+ return "unknown";
362
+ }
363
+ const initPath = binding.path.get("init");
364
+ if (!initPath.node) return "unknown";
365
+ context.renderExpressionInProgress.add(cacheKey);
366
+ const classification = analyzeRenderExpression(initPath, context);
367
+ context.renderExpressionInProgress.delete(cacheKey);
368
+ return classification;
369
+ }
370
+ function isPropsChildrenMemberExpression(expression) {
371
+ if (!core.types.isIdentifier(expression.object, { name: "props" })) return false;
372
+ if (!core.types.isIdentifier(expression.property, { name: "children" })) return false;
373
+ return !expression.computed;
374
+ }
375
+ function mergeAncestorClassification(current, next) {
376
+ if (current === "text" || next === "text") return "text";
377
+ if (current === "unknown" || next === "unknown") return "unknown";
378
+ return "safe";
379
+ }
380
+ function getImportSpecifierImportedName(specifier) {
381
+ if (core.types.isIdentifier(specifier.imported)) {
382
+ return specifier.imported.name;
383
+ }
384
+ if (core.types.isStringLiteral(specifier.imported)) {
385
+ return specifier.imported.value;
386
+ }
387
+ return void 0;
388
+ }
389
+ const hasExpoRouterLinkParentWithAsChild = (path) => {
390
+ const textElementPath = path.parentPath;
391
+ if (!textElementPath.isJSXElement()) return false;
392
+ let ancestorPath = textElementPath.parentPath;
393
+ while (ancestorPath) {
394
+ if (ancestorPath.isJSXElement()) {
395
+ if (!isExpoRouterLinkElement(ancestorPath)) return false;
396
+ return hasTruthyAsChildAttribute(ancestorPath.node.openingElement.attributes);
397
+ }
398
+ ancestorPath = ancestorPath.parentPath;
399
+ }
400
+ return false;
401
+ };
402
+ function isExpoRouterLinkElement(path) {
403
+ const openingElementName = path.node.openingElement.name;
404
+ if (core.types.isJSXIdentifier(openingElementName)) {
405
+ const binding = path.scope.getBinding(openingElementName.name);
406
+ if (!binding || binding.kind !== "module") return false;
407
+ if (!core.types.isImportSpecifier(binding.path.node)) return false;
408
+ const importDeclaration = binding.path.parent;
409
+ if (!core.types.isImportDeclaration(importDeclaration) || importDeclaration.source.value !== "expo-router") return false;
410
+ const imported = binding.path.node.imported;
411
+ return core.types.isIdentifier(imported, { name: "Link" }) || core.types.isStringLiteral(imported) && imported.value === "Link";
412
+ }
413
+ if (core.types.isJSXMemberExpression(openingElementName)) {
414
+ if (!core.types.isJSXIdentifier(openingElementName.object)) return false;
415
+ if (!core.types.isJSXIdentifier(openingElementName.property, { name: "Link" })) return false;
416
+ const namespaceBinding = path.scope.getBinding(openingElementName.object.name);
417
+ if (!namespaceBinding || namespaceBinding.kind !== "module") return false;
418
+ if (!core.types.isImportNamespaceSpecifier(namespaceBinding.path.node)) return false;
419
+ const importDeclaration = namespaceBinding.path.parent;
420
+ return core.types.isImportDeclaration(importDeclaration) && importDeclaration.source.value === "expo-router";
421
+ }
422
+ return false;
423
+ }
424
+ function hasTruthyAsChildAttribute(attributes) {
425
+ let asChildAttribute;
426
+ for (const attribute of attributes) {
427
+ if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "asChild" })) {
428
+ asChildAttribute = attribute;
429
+ }
430
+ }
431
+ if (!asChildAttribute) return false;
432
+ return isJSXAttributeValueTruthy(asChildAttribute.value);
433
+ }
434
+ function isJSXAttributeValueTruthy(value) {
435
+ if (!value) return true;
436
+ if (core.types.isStringLiteral(value)) return value.value.length > 0;
437
+ if (core.types.isJSXElement(value) || core.types.isJSXFragment(value)) return true;
438
+ if (core.types.isJSXExpressionContainer(value)) {
439
+ const staticTruthiness = getStaticExpressionTruthiness(value.expression);
440
+ return staticTruthiness != null ? staticTruthiness : true;
441
+ }
442
+ return true;
443
+ }
444
+ function getStaticExpressionTruthiness(expression) {
445
+ var _a, _b;
446
+ if (core.types.isJSXEmptyExpression(expression)) return false;
447
+ if (core.types.isBooleanLiteral(expression)) return expression.value;
448
+ if (core.types.isNullLiteral(expression)) return false;
449
+ if (core.types.isStringLiteral(expression)) return expression.value.length > 0;
450
+ if (core.types.isNumericLiteral(expression)) return expression.value !== 0 && !Number.isNaN(expression.value);
451
+ if (core.types.isBigIntLiteral(expression)) return expression.value !== "0";
452
+ if (core.types.isIdentifier(expression, { name: "undefined" })) return false;
453
+ if (core.types.isTemplateLiteral(expression) && expression.expressions.length === 0) {
454
+ return ((_b = (_a = expression.quasis[0]) == null ? void 0 : _a.value.cooked) != null ? _b : "").length > 0;
455
+ }
456
+ if (core.types.isUnaryExpression(expression, { operator: "!" })) {
457
+ const staticTruthiness = getStaticExpressionTruthiness(expression.argument);
458
+ return staticTruthiness === void 0 ? void 0 : !staticTruthiness;
459
+ }
460
+ return void 0;
461
+ }
462
+
463
+ const RUNTIME_MODULE_NAME = "react-native-boost/runtime";
464
+ const ACCESSIBILITY_PROPERTIES = /* @__PURE__ */ new Set([
465
+ "accessibilityLabel",
466
+ "aria-label",
467
+ "accessibilityState",
468
+ "aria-busy",
469
+ "aria-checked",
470
+ "aria-disabled",
471
+ "aria-expanded",
472
+ "aria-selected",
473
+ "accessible"
474
+ ]);
475
+ const USER_SELECT_STYLE_TO_SELECTABLE_PROP = {
476
+ auto: true,
477
+ text: true,
478
+ none: false,
479
+ contain: true,
480
+ all: true
481
+ };
162
482
 
163
483
  const hasBlacklistedProperty = (path, blacklist) => {
164
484
  return path.node.attributes.some((attribute) => {
@@ -190,6 +510,47 @@ const hasBlacklistedProperty = (path, blacklist) => {
190
510
  return false;
191
511
  });
192
512
  };
513
+ const addDefaultProperty = (path, key, value) => {
514
+ let propertyIsFound = false;
515
+ let hasUnresolvableSpread = false;
516
+ for (const attribute of path.node.attributes) {
517
+ if (core.types.isJSXAttribute(attribute) && attribute.name.name === key) {
518
+ propertyIsFound = true;
519
+ break;
520
+ }
521
+ if (core.types.isJSXSpreadAttribute(attribute)) {
522
+ if (core.types.isObjectExpression(attribute.argument)) {
523
+ const propertyInSpread = attribute.argument.properties.some(
524
+ (p) => core.types.isObjectProperty(p) && core.types.isIdentifier(p.key) && p.key.name === key || core.types.isObjectProperty(p) && core.types.isStringLiteral(p.key) && p.key.value === key
525
+ );
526
+ if (propertyInSpread) {
527
+ propertyIsFound = true;
528
+ break;
529
+ }
530
+ } else if (core.types.isIdentifier(attribute.argument)) {
531
+ const binding = path.scope.getBinding(attribute.argument.name);
532
+ if ((binding == null ? void 0 : binding.path.node) && core.types.isVariableDeclarator(binding.path.node) && core.types.isObjectExpression(binding.path.node.init)) {
533
+ const propertyInSpread = binding.path.node.init.properties.some(
534
+ (p) => core.types.isObjectProperty(p) && core.types.isIdentifier(p.key) && p.key.name === key || core.types.isObjectProperty(p) && core.types.isStringLiteral(p.key) && p.key.value === key
535
+ );
536
+ if (propertyInSpread) {
537
+ propertyIsFound = true;
538
+ break;
539
+ }
540
+ } else {
541
+ hasUnresolvableSpread = true;
542
+ break;
543
+ }
544
+ } else {
545
+ hasUnresolvableSpread = true;
546
+ break;
547
+ }
548
+ }
549
+ }
550
+ if (!propertyIsFound && !hasUnresolvableSpread) {
551
+ path.node.attributes.push(core.types.jsxAttribute(core.types.jsxIdentifier(key), core.types.jsxExpressionContainer(value)));
552
+ }
553
+ };
193
554
  const buildPropertiesFromAttributes = (attributes) => {
194
555
  const arguments_ = [];
195
556
  for (const attribute of attributes) {
@@ -255,7 +616,54 @@ const hasAccessibilityProperty = (path, attributes) => {
255
616
  }
256
617
  return false;
257
618
  };
258
-
619
+ function extractStyleAttribute(attributes) {
620
+ for (const attribute of attributes) {
621
+ if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "style" })) {
622
+ if (attribute.value && core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression)) {
623
+ return {
624
+ styleAttribute: attribute,
625
+ styleExpr: attribute.value.expression
626
+ };
627
+ }
628
+ return { styleAttribute: attribute };
629
+ }
630
+ }
631
+ return {};
632
+ }
633
+ function extractSelectableAndUpdateStyle(styleExpr) {
634
+ const handleObjectExpression = (objectExpr) => {
635
+ let selectableValue;
636
+ objectExpr.properties = objectExpr.properties.filter((property) => {
637
+ if (!core.types.isObjectProperty(property) || !core.types.isIdentifier(property.key, { name: "userSelect" }) && !(core.types.isStringLiteral(property.key) && property.key.value === "userSelect")) {
638
+ return true;
639
+ }
640
+ if (core.types.isStringLiteral(property.value)) {
641
+ const mapped = USER_SELECT_STYLE_TO_SELECTABLE_PROP[property.value.value];
642
+ if (mapped !== void 0) {
643
+ selectableValue = mapped;
644
+ }
645
+ }
646
+ return false;
647
+ });
648
+ return selectableValue;
649
+ };
650
+ if (core.types.isObjectExpression(styleExpr)) {
651
+ return handleObjectExpression(styleExpr);
652
+ }
653
+ if (core.types.isArrayExpression(styleExpr)) {
654
+ let selectableValue;
655
+ for (const element of styleExpr.elements) {
656
+ if (element && core.types.isObjectExpression(element)) {
657
+ const value = handleObjectExpression(element);
658
+ if (value !== void 0) {
659
+ selectableValue = value;
660
+ }
661
+ }
662
+ }
663
+ return selectableValue;
664
+ }
665
+ return void 0;
666
+ }
259
667
  const isStringNode = (path, child) => {
260
668
  if (core.types.isJSXText(child) || core.types.isStringLiteral(child)) return true;
261
669
  if (core.types.isJSXExpressionContainer(child)) {
@@ -272,63 +680,40 @@ const isStringNode = (path, child) => {
272
680
  return false;
273
681
  };
274
682
 
275
- function hasComponentAncestor(path, componentName, skipComponents = ["Fragment"]) {
276
- const directAncestor = path.findParent((parentPath) => {
277
- return core.types.isJSXElement(parentPath.node) && core.types.isJSXIdentifier(parentPath.node.openingElement.name, { name: componentName });
278
- });
279
- if (directAncestor) return true;
280
- return !!path.findParent((parentPath) => {
281
- if (!core.types.isJSXElement(parentPath.node)) return false;
282
- const openingElement = parentPath.node.openingElement;
283
- if (!core.types.isJSXIdentifier(openingElement.name)) return false;
284
- const ancestorComponentName = openingElement.name.name;
285
- if (ancestorComponentName === componentName) {
286
- return false;
287
- }
288
- if (skipComponents.includes(ancestorComponentName)) {
289
- return false;
290
- }
291
- if (ancestorComponentName[0] === ancestorComponentName[0].toLowerCase()) {
292
- return false;
293
- }
294
- const binding = parentPath.scope.getBinding(ancestorComponentName);
295
- if (!binding) return false;
296
- if (core.types.isVariableDeclarator(binding.path.node)) {
297
- const init = binding.path.node.init;
298
- if (core.types.isArrowFunctionExpression(init) || core.types.isFunctionExpression(init)) {
299
- return core.types.isBlockStatement(init.body) ? hasComponentInReturnStatement(init.body, componentName) : hasComponentInExpression(init.body, componentName);
300
- }
301
- } else if (core.types.isFunctionDeclaration(binding.path.node)) {
302
- return hasComponentInReturnStatement(binding.path.node.body, componentName);
303
- }
304
- return false;
305
- });
306
- }
307
- function hasComponentInReturnStatement(blockStatement, componentName) {
308
- for (const statement of blockStatement.body) {
309
- if (core.types.isReturnStatement(statement) && statement.argument && hasComponentInExpression(statement.argument, componentName)) {
310
- return true;
311
- }
683
+ function addFileImportHint({
684
+ file,
685
+ nameHint,
686
+ path,
687
+ importName,
688
+ moduleName,
689
+ importType = "named"
690
+ }) {
691
+ var _a;
692
+ if (!((_a = file.__hasImports) == null ? void 0 : _a[nameHint])) {
693
+ file.__hasImports = file.__hasImports || {};
694
+ file.__hasImports[nameHint] = importType === "default" ? helperModuleImports.addDefault(path, moduleName, { nameHint }) : helperModuleImports.addNamed(path, importName, moduleName, { nameHint });
312
695
  }
313
- return false;
696
+ return file.__hasImports[nameHint];
314
697
  }
315
- function hasComponentInExpression(expression, componentName) {
316
- if (core.types.isJSXElement(expression)) {
317
- if (core.types.isJSXIdentifier(expression.openingElement.name, { name: componentName })) {
318
- return true;
319
- }
320
- for (const child of expression.children) {
321
- if (core.types.isJSXElement(child) && core.types.isJSXIdentifier(child.openingElement.name, { name: componentName })) {
322
- return true;
323
- }
324
- }
698
+ const replaceWithNativeComponent = (path, parent, file, nativeComponentName) => {
699
+ const nativeIdentifier = addFileImportHint({
700
+ file,
701
+ nameHint: nativeComponentName,
702
+ path,
703
+ importName: nativeComponentName,
704
+ moduleName: RUNTIME_MODULE_NAME,
705
+ importType: "named"
706
+ });
707
+ const currentName = path.node.name.name;
708
+ const jsxName = path.node.name;
709
+ jsxName.name = nativeIdentifier.name;
710
+ if (!path.node.selfClosing && parent.closingElement && core.types.isJSXIdentifier(parent.closingElement.name) && parent.closingElement.name.name === currentName) {
711
+ parent.closingElement.name.name = nativeIdentifier.name;
325
712
  }
326
- return false;
327
- }
713
+ return nativeIdentifier;
714
+ };
328
715
 
329
716
  const textBlacklistedProperties = /* @__PURE__ */ new Set([
330
- "allowFontScaling",
331
- "ellipsizeMode",
332
717
  "id",
333
718
  "nativeID",
334
719
  "onLongPress",
@@ -343,8 +728,8 @@ const textBlacklistedProperties = /* @__PURE__ */ new Set([
343
728
  "onStartShouldSetResponder",
344
729
  "pressRetentionOffset",
345
730
  "suppressHighlighting",
346
- "selectable",
347
731
  "selectionColor"
732
+ // TODO: we can use react-native's internal `processColor` to process this at runtime
348
733
  ]);
349
734
  const textOptimizer = (path, log = () => {
350
735
  }) => {
@@ -353,6 +738,7 @@ const textOptimizer = (path, log = () => {
353
738
  if (!isValidJSXComponent(path, "Text")) return;
354
739
  if (!isReactNativeImport(path, "Text")) return;
355
740
  if (hasBlacklistedProperty(path, textBlacklistedProperties)) return;
741
+ if (hasExpoRouterLinkParentWithAsChild(path)) return;
356
742
  const parent = path.parent;
357
743
  if (hasInvalidChildren(path, parent)) return;
358
744
  const hub = path.hub;
@@ -363,9 +749,10 @@ const textOptimizer = (path, log = () => {
363
749
  const filename = ((_a = file.opts) == null ? void 0 : _a.filename) || "unknown file";
364
750
  const lineNumber = (_c = (_b = path.node.loc) == null ? void 0 : _b.start.line) != null ? _c : "unknown line";
365
751
  log(`Optimizing Text component in ${filename}:${lineNumber}`);
366
- const originalAttributes = [...path.node.attributes];
367
752
  fixNegativeNumberOfLines({ path, log });
368
- processProps(path, file, originalAttributes);
753
+ addDefaultProperty(path, "allowFontScaling", core.types.booleanLiteral(true));
754
+ addDefaultProperty(path, "ellipsizeMode", core.types.stringLiteral("tail"));
755
+ processProps(path, file);
369
756
  replaceWithNativeComponent(path, parent, file, "NativeText");
370
757
  };
371
758
  function hasInvalidChildren(path, parent) {
@@ -399,27 +786,16 @@ function fixNegativeNumberOfLines({
399
786
  }
400
787
  }
401
788
  }
402
- function extractStyleAttribute(attributes) {
403
- for (const attribute of attributes) {
404
- if (core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "style" })) {
405
- if (attribute.value && core.types.isJSXExpressionContainer(attribute.value) && !core.types.isJSXEmptyExpression(attribute.value.expression)) {
406
- return {
407
- styleAttribute: attribute,
408
- styleExpr: attribute.value.expression
409
- };
410
- }
411
- return { styleAttribute: attribute };
412
- }
413
- }
414
- return {};
415
- }
416
- function processProps(path, file, originalAttributes) {
417
- const { styleExpr } = extractStyleAttribute(originalAttributes);
418
- const hasA11y = hasAccessibilityProperty(path, originalAttributes);
419
- if (styleExpr && hasA11y) {
420
- const accessibilityAttributes = originalAttributes.filter(
421
- (attribute) => !(core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name, { name: "style" }))
422
- );
789
+ function processProps(path, file) {
790
+ const currentAttributes = [...path.node.attributes];
791
+ const { styleExpr, styleAttribute } = extractStyleAttribute(currentAttributes);
792
+ const hasA11y = hasAccessibilityProperty(path, currentAttributes);
793
+ const spreadAttributes = [];
794
+ if (hasA11y) {
795
+ const accessibilityAttributes = currentAttributes.filter((attribute) => {
796
+ if (!core.types.isJSXAttribute(attribute)) return false;
797
+ return core.types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name);
798
+ });
423
799
  const normalizeIdentifier = addFileImportHint({
424
800
  file,
425
801
  nameHint: "processAccessibilityProps",
@@ -429,6 +805,17 @@ function processProps(path, file, originalAttributes) {
429
805
  });
430
806
  const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
431
807
  const accessibilityExpr = core.types.callExpression(core.types.identifier(normalizeIdentifier.name), [accessibilityObject]);
808
+ spreadAttributes.push(core.types.jsxSpreadAttribute(accessibilityExpr));
809
+ }
810
+ let selectableAttribute;
811
+ if (styleExpr) {
812
+ const selectableValue = extractSelectableAndUpdateStyle(styleExpr);
813
+ if (selectableValue != null) {
814
+ selectableAttribute = core.types.jsxAttribute(
815
+ core.types.jsxIdentifier("selectable"),
816
+ core.types.jsxExpressionContainer(core.types.booleanLiteral(selectableValue))
817
+ );
818
+ }
432
819
  const flattenIdentifier = addFileImportHint({
433
820
  file,
434
821
  nameHint: "processTextStyle",
@@ -437,29 +824,19 @@ function processProps(path, file, originalAttributes) {
437
824
  moduleName: RUNTIME_MODULE_NAME
438
825
  });
439
826
  const flattenedStyleExpr = core.types.callExpression(core.types.identifier(flattenIdentifier.name), [styleExpr]);
440
- path.node.attributes = [core.types.jsxSpreadAttribute(accessibilityExpr), core.types.jsxSpreadAttribute(flattenedStyleExpr)];
441
- } else if (styleExpr) {
442
- const flattenIdentifier = addFileImportHint({
443
- file,
444
- nameHint: "processTextStyle",
445
- path,
446
- importName: "processTextStyle",
447
- moduleName: RUNTIME_MODULE_NAME
448
- });
449
- const flattened = core.types.callExpression(core.types.identifier(flattenIdentifier.name), [styleExpr]);
450
- path.node.attributes = [core.types.jsxSpreadAttribute(flattened)];
451
- } else if (hasA11y) {
452
- const normalizeIdentifier = addFileImportHint({
453
- file,
454
- nameHint: "processAccessibilityProps",
455
- path,
456
- importName: "processAccessibilityProps",
457
- moduleName: RUNTIME_MODULE_NAME
458
- });
459
- const propsObject = buildPropertiesFromAttributes(originalAttributes);
460
- const normalized = core.types.callExpression(core.types.identifier(normalizeIdentifier.name), [propsObject]);
461
- path.node.attributes = [core.types.jsxSpreadAttribute(normalized)];
827
+ spreadAttributes.push(core.types.jsxSpreadAttribute(flattenedStyleExpr));
828
+ }
829
+ const remainingAttributes = [];
830
+ for (const attribute of currentAttributes) {
831
+ if (styleAttribute && attribute === styleAttribute) continue;
832
+ if (hasA11y && core.types.isJSXAttribute(attribute) && core.types.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name)) {
833
+ continue;
834
+ }
835
+ remainingAttributes.push(attribute);
462
836
  }
837
+ path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes].filter(
838
+ (attribute) => attribute !== void 0
839
+ );
463
840
  }
464
841
 
465
842
  const log = (message) => {
@@ -467,46 +844,29 @@ const log = (message) => {
467
844
  };
468
845
 
469
846
  const viewBlacklistedProperties = /* @__PURE__ */ new Set([
847
+ // TODO: process a11y props at runtime
470
848
  "accessible",
471
849
  "accessibilityLabel",
472
850
  "accessibilityState",
473
- "allowFontScaling",
474
851
  "aria-busy",
475
852
  "aria-checked",
476
853
  "aria-disabled",
477
854
  "aria-expanded",
478
855
  "aria-label",
479
856
  "aria-selected",
480
- "ellipsizeMode",
481
- "disabled",
482
857
  "id",
483
858
  "nativeID",
484
- "numberOfLines",
485
- "onLongPress",
486
- "onPress",
487
- "onPressIn",
488
- "onPressOut",
489
- "onResponderGrant",
490
- "onResponderMove",
491
- "onResponderRelease",
492
- "onResponderTerminate",
493
- "onResponderTerminationRequest",
494
- "onStartShouldSetResponder",
495
- "pressRetentionOffset",
496
- "selectable",
497
- "selectionColor",
498
- "suppressHighlighting",
499
859
  "style"
860
+ // TODO: process style at runtime
500
861
  ]);
501
- const skipComponents = ["View", "Fragment", "ScrollView", "FlatList"];
502
862
  const viewOptimizer = (path, log = () => {
503
- }) => {
863
+ }, options) => {
504
864
  var _a, _b, _c;
505
865
  if (isIgnoredLine(path)) return;
506
866
  if (!isValidJSXComponent(path, "View")) return;
507
867
  if (!isReactNativeImport(path, "View")) return;
508
868
  if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
509
- if (hasComponentAncestor(path, "Text", skipComponents)) return;
869
+ if (hasUnsafeViewAncestor(path, (options == null ? void 0 : options.dangerouslyOptimizeViewWithUnknownAncestors) === true)) return;
510
870
  const hub = path.hub;
511
871
  const file = typeof hub === "object" && hub !== null && "file" in hub ? hub.file : void 0;
512
872
  if (!file) {
@@ -531,7 +891,7 @@ var index = helperPluginUtils.declare((api) => {
531
891
  };
532
892
  if (isIgnoredFile(path, (_b = options.ignores) != null ? _b : [])) return;
533
893
  if (((_c = options.optimizations) == null ? void 0 : _c.text) !== false) textOptimizer(path, logger);
534
- if (((_d = options.optimizations) == null ? void 0 : _d.view) !== false) viewOptimizer(path, logger);
894
+ if (((_d = options.optimizations) == null ? void 0 : _d.view) !== false) viewOptimizer(path, logger, options);
535
895
  }
536
896
  }
537
897
  };