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.
- package/dist/plugin/esm/index.mjs +528 -168
- package/dist/plugin/esm/index.mjs.map +1 -1
- package/dist/plugin/index.js +528 -168
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/esm/index.mjs.map +1 -1
- package/dist/runtime/esm/index.web.mjs.map +1 -1
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/index.web.d.ts +2 -1
- package/dist/runtime/index.web.js.map +1 -1
- package/package.json +12 -13
- package/src/plugin/index.ts +1 -1
- package/src/plugin/optimizers/text/index.ts +71 -72
- package/src/plugin/optimizers/view/index.ts +5 -25
- package/src/plugin/types/index.ts +12 -1
- package/src/plugin/utils/common/attributes.ts +165 -0
- package/src/plugin/utils/common/index.ts +1 -3
- package/src/plugin/utils/common/validation.ts +515 -0
- package/src/plugin/utils/constants.ts +9 -0
- package/src/plugin/utils/format-test-result.ts +29 -0
- package/src/plugin/utils/generate-test-plugin.ts +3 -3
- package/src/plugin/utils/common/ancestors.ts +0 -120
- package/src/plugin/utils/common/node-types.ts +0 -22
|
@@ -2,6 +2,7 @@ import { NodePath, types as t } from '@babel/core';
|
|
|
2
2
|
import { HubFile, Optimizer } from '../../types';
|
|
3
3
|
import PluginError from '../../utils/plugin-error';
|
|
4
4
|
import {
|
|
5
|
+
addDefaultProperty,
|
|
5
6
|
addFileImportHint,
|
|
6
7
|
buildPropertiesFromAttributes,
|
|
7
8
|
hasAccessibilityProperty,
|
|
@@ -11,12 +12,13 @@ import {
|
|
|
11
12
|
isReactNativeImport,
|
|
12
13
|
replaceWithNativeComponent,
|
|
13
14
|
isStringNode,
|
|
15
|
+
hasExpoRouterLinkParentWithAsChild,
|
|
14
16
|
} from '../../utils/common';
|
|
15
17
|
import { RUNTIME_MODULE_NAME } from '../../utils/constants';
|
|
18
|
+
import { ACCESSIBILITY_PROPERTIES } from '../../utils/constants';
|
|
19
|
+
import { extractStyleAttribute, extractSelectableAndUpdateStyle } from '../../utils/common';
|
|
16
20
|
|
|
17
21
|
export const textBlacklistedProperties = new Set([
|
|
18
|
-
'allowFontScaling',
|
|
19
|
-
'ellipsizeMode',
|
|
20
22
|
'id',
|
|
21
23
|
'nativeID',
|
|
22
24
|
'onLongPress',
|
|
@@ -31,8 +33,7 @@ export const textBlacklistedProperties = new Set([
|
|
|
31
33
|
'onStartShouldSetResponder',
|
|
32
34
|
'pressRetentionOffset',
|
|
33
35
|
'suppressHighlighting',
|
|
34
|
-
'
|
|
35
|
-
'selectionColor',
|
|
36
|
+
'selectionColor', // TODO: we can use react-native's internal `processColor` to process this at runtime
|
|
36
37
|
]);
|
|
37
38
|
|
|
38
39
|
export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
@@ -40,6 +41,7 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
40
41
|
if (!isValidJSXComponent(path, 'Text')) return;
|
|
41
42
|
if (!isReactNativeImport(path, 'Text')) return;
|
|
42
43
|
if (hasBlacklistedProperty(path, textBlacklistedProperties)) return;
|
|
44
|
+
if (hasExpoRouterLinkParentWithAsChild(path)) return;
|
|
43
45
|
|
|
44
46
|
// Verify that the Text only has string children
|
|
45
47
|
const parent = path.parent as t.JSXElement;
|
|
@@ -57,9 +59,10 @@ export const textOptimizer: Optimizer = (path, log = () => {}) => {
|
|
|
57
59
|
log(`Optimizing Text component in ${filename}:${lineNumber}`);
|
|
58
60
|
|
|
59
61
|
// Process props
|
|
60
|
-
const originalAttributes = [...path.node.attributes];
|
|
61
62
|
fixNegativeNumberOfLines({ path, log });
|
|
62
|
-
|
|
63
|
+
addDefaultProperty(path, 'allowFontScaling', t.booleanLiteral(true));
|
|
64
|
+
addDefaultProperty(path, 'ellipsizeMode', t.stringLiteral('tail'));
|
|
65
|
+
processProps(path, file);
|
|
63
66
|
|
|
64
67
|
// Replace the Text component with NativeText
|
|
65
68
|
replaceWithNativeComponent(path, parent, file, 'NativeText');
|
|
@@ -129,49 +132,29 @@ function fixNegativeNumberOfLines({
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
/**
|
|
133
|
-
* Extracts the style attribute from JSX attributes.
|
|
134
|
-
*/
|
|
135
|
-
function extractStyleAttribute(attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute>): {
|
|
136
|
-
styleAttribute?: t.JSXAttribute;
|
|
137
|
-
styleExpr?: t.Expression;
|
|
138
|
-
} {
|
|
139
|
-
for (const attribute of attributes) {
|
|
140
|
-
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
141
|
-
if (
|
|
142
|
-
attribute.value &&
|
|
143
|
-
t.isJSXExpressionContainer(attribute.value) &&
|
|
144
|
-
!t.isJSXEmptyExpression(attribute.value.expression)
|
|
145
|
-
) {
|
|
146
|
-
return {
|
|
147
|
-
styleAttribute: attribute,
|
|
148
|
-
styleExpr: attribute.value.expression,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
return { styleAttribute: attribute };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return {};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
135
|
/**
|
|
158
136
|
* Processes style and accessibility attributes, replacing them with optimized versions.
|
|
159
137
|
*/
|
|
160
|
-
function processProps(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
138
|
+
function processProps(path: NodePath<t.JSXOpeningElement>, file: HubFile) {
|
|
139
|
+
// Grab the up-to-date list of attributes
|
|
140
|
+
const currentAttributes = [...path.node.attributes];
|
|
141
|
+
|
|
142
|
+
const { styleExpr, styleAttribute } = extractStyleAttribute(currentAttributes);
|
|
143
|
+
const hasA11y = hasAccessibilityProperty(path, currentAttributes);
|
|
144
|
+
|
|
145
|
+
// ============================================
|
|
146
|
+
// 1. Prepare spread attributes (style / a11y)
|
|
147
|
+
// ============================================
|
|
148
|
+
|
|
149
|
+
const spreadAttributes: t.JSXSpreadAttribute[] = [];
|
|
150
|
+
|
|
151
|
+
// --- Accessibility ---
|
|
152
|
+
if (hasA11y) {
|
|
153
|
+
const accessibilityAttributes = currentAttributes.filter((attribute) => {
|
|
154
|
+
if (!t.isJSXAttribute(attribute)) return false;
|
|
155
|
+
return t.isJSXIdentifier(attribute.name) && ACCESSIBILITY_PROPERTIES.has(attribute.name.name as string);
|
|
156
|
+
});
|
|
157
|
+
|
|
175
158
|
const normalizeIdentifier = addFileImportHint({
|
|
176
159
|
file,
|
|
177
160
|
nameHint: 'processAccessibilityProps',
|
|
@@ -179,10 +162,25 @@ function processProps(
|
|
|
179
162
|
importName: 'processAccessibilityProps',
|
|
180
163
|
moduleName: RUNTIME_MODULE_NAME,
|
|
181
164
|
});
|
|
165
|
+
|
|
182
166
|
const accessibilityObject = buildPropertiesFromAttributes(accessibilityAttributes);
|
|
183
167
|
const accessibilityExpr = t.callExpression(t.identifier(normalizeIdentifier.name), [accessibilityObject]);
|
|
168
|
+
spreadAttributes.push(t.jsxSpreadAttribute(accessibilityExpr));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// --- Style ---
|
|
172
|
+
let selectableAttribute: t.JSXAttribute | undefined;
|
|
173
|
+
if (styleExpr) {
|
|
174
|
+
// Attempt a compile-time extraction of `userSelect`
|
|
175
|
+
const selectableValue = extractSelectableAndUpdateStyle(styleExpr);
|
|
176
|
+
|
|
177
|
+
if (selectableValue != null) {
|
|
178
|
+
selectableAttribute = t.jsxAttribute(
|
|
179
|
+
t.jsxIdentifier('selectable'),
|
|
180
|
+
t.jsxExpressionContainer(t.booleanLiteral(selectableValue))
|
|
181
|
+
);
|
|
182
|
+
}
|
|
184
183
|
|
|
185
|
-
// Set up the style import
|
|
186
184
|
const flattenIdentifier = addFileImportHint({
|
|
187
185
|
file,
|
|
188
186
|
nameHint: 'processTextStyle',
|
|
@@ -191,31 +189,32 @@ function processProps(
|
|
|
191
189
|
moduleName: RUNTIME_MODULE_NAME,
|
|
192
190
|
});
|
|
193
191
|
const flattenedStyleExpr = t.callExpression(t.identifier(flattenIdentifier.name), [styleExpr]);
|
|
192
|
+
spreadAttributes.push(t.jsxSpreadAttribute(flattenedStyleExpr));
|
|
193
|
+
}
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
moduleName: RUNTIME_MODULE_NAME,
|
|
216
|
-
});
|
|
217
|
-
const propsObject = buildPropertiesFromAttributes(originalAttributes);
|
|
218
|
-
const normalized = t.callExpression(t.identifier(normalizeIdentifier.name), [propsObject]);
|
|
219
|
-
path.node.attributes = [t.jsxSpreadAttribute(normalized)];
|
|
195
|
+
// ============================================
|
|
196
|
+
// 2. Collect the remaining (non-processed) attributes
|
|
197
|
+
// ============================================
|
|
198
|
+
const remainingAttributes: (t.JSXAttribute | t.JSXSpreadAttribute)[] = [];
|
|
199
|
+
|
|
200
|
+
for (const attribute of currentAttributes) {
|
|
201
|
+
// Skip the style attribute (we have replaced it with a spread)
|
|
202
|
+
if (styleAttribute && attribute === styleAttribute) continue;
|
|
203
|
+
|
|
204
|
+
// Skip accessibility attributes if we processed them
|
|
205
|
+
if (
|
|
206
|
+
hasA11y &&
|
|
207
|
+
t.isJSXAttribute(attribute) &&
|
|
208
|
+
t.isJSXIdentifier(attribute.name) &&
|
|
209
|
+
ACCESSIBILITY_PROPERTIES.has(attribute.name.name as string)
|
|
210
|
+
) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
remainingAttributes.push(attribute);
|
|
220
215
|
}
|
|
216
|
+
|
|
217
|
+
path.node.attributes = [...spreadAttributes, selectableAttribute, ...remainingAttributes].filter(
|
|
218
|
+
(attribute): attribute is t.JSXAttribute | t.JSXSpreadAttribute => attribute !== undefined
|
|
219
|
+
);
|
|
221
220
|
}
|
|
@@ -7,51 +7,31 @@ import {
|
|
|
7
7
|
isValidJSXComponent,
|
|
8
8
|
isReactNativeImport,
|
|
9
9
|
replaceWithNativeComponent,
|
|
10
|
-
|
|
10
|
+
hasUnsafeViewAncestor,
|
|
11
11
|
} from '../../utils/common';
|
|
12
12
|
|
|
13
13
|
export const viewBlacklistedProperties = new Set([
|
|
14
|
+
// TODO: process a11y props at runtime
|
|
14
15
|
'accessible',
|
|
15
16
|
'accessibilityLabel',
|
|
16
17
|
'accessibilityState',
|
|
17
|
-
'allowFontScaling',
|
|
18
18
|
'aria-busy',
|
|
19
19
|
'aria-checked',
|
|
20
20
|
'aria-disabled',
|
|
21
21
|
'aria-expanded',
|
|
22
22
|
'aria-label',
|
|
23
23
|
'aria-selected',
|
|
24
|
-
'ellipsizeMode',
|
|
25
|
-
'disabled',
|
|
26
24
|
'id',
|
|
27
25
|
'nativeID',
|
|
28
|
-
'
|
|
29
|
-
'onLongPress',
|
|
30
|
-
'onPress',
|
|
31
|
-
'onPressIn',
|
|
32
|
-
'onPressOut',
|
|
33
|
-
'onResponderGrant',
|
|
34
|
-
'onResponderMove',
|
|
35
|
-
'onResponderRelease',
|
|
36
|
-
'onResponderTerminate',
|
|
37
|
-
'onResponderTerminationRequest',
|
|
38
|
-
'onStartShouldSetResponder',
|
|
39
|
-
'pressRetentionOffset',
|
|
40
|
-
'selectable',
|
|
41
|
-
'selectionColor',
|
|
42
|
-
'suppressHighlighting',
|
|
43
|
-
'style',
|
|
26
|
+
'style', // TODO: process style at runtime
|
|
44
27
|
]);
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
const skipComponents = ['View', 'Fragment', 'ScrollView', 'FlatList'];
|
|
48
|
-
|
|
49
|
-
export const viewOptimizer: Optimizer = (path, log = () => {}) => {
|
|
29
|
+
export const viewOptimizer: Optimizer = (path, log = () => {}, options) => {
|
|
50
30
|
if (isIgnoredLine(path)) return;
|
|
51
31
|
if (!isValidJSXComponent(path, 'View')) return;
|
|
52
32
|
if (!isReactNativeImport(path, 'View')) return;
|
|
53
33
|
if (hasBlacklistedProperty(path, viewBlacklistedProperties)) return;
|
|
54
|
-
if (
|
|
34
|
+
if (hasUnsafeViewAncestor(path, options?.dangerouslyOptimizeViewWithUnknownAncestors === true)) return;
|
|
55
35
|
|
|
56
36
|
// Extract the file from the Babel hub
|
|
57
37
|
const hub = path.hub as unknown;
|
|
@@ -25,9 +25,20 @@ export interface PluginOptions {
|
|
|
25
25
|
*/
|
|
26
26
|
view?: boolean;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Opt-in flag that allows View optimization when ancestor components cannot be statically resolved.
|
|
30
|
+
*
|
|
31
|
+
* This may introduce behavioral changes when unresolved ancestors render react-native Text wrappers.
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
dangerouslyOptimizeViewWithUnknownAncestors?: boolean;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
export type Optimizer = (
|
|
37
|
+
export type Optimizer = (
|
|
38
|
+
path: NodePath<t.JSXOpeningElement>,
|
|
39
|
+
log?: (message: string) => void,
|
|
40
|
+
options?: PluginOptions
|
|
41
|
+
) => void;
|
|
31
42
|
|
|
32
43
|
export type HubFile = t.File & {
|
|
33
44
|
opts: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NodePath, types as t } from '@babel/core';
|
|
2
2
|
import { ACCESSIBILITY_PROPERTIES } from '../constants';
|
|
3
|
+
import { USER_SELECT_STYLE_TO_SELECTABLE_PROP } from '../constants';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Checks if the JSX element has a blacklisted property.
|
|
@@ -46,6 +47,67 @@ export const hasBlacklistedProperty = (path: NodePath<t.JSXOpeningElement>, blac
|
|
|
46
47
|
});
|
|
47
48
|
};
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Adds a default property to a JSX element if it's not already defined. It avoids adding a default
|
|
52
|
+
* if it cannot statically determine whether the property is already set.
|
|
53
|
+
*
|
|
54
|
+
* @param path - The path to the JSXOpeningElement.
|
|
55
|
+
* @param key - The property key.
|
|
56
|
+
* @param value - The default value expression.
|
|
57
|
+
*/
|
|
58
|
+
export const addDefaultProperty = (path: NodePath<t.JSXOpeningElement>, key: string, value: t.Expression) => {
|
|
59
|
+
let propertyIsFound = false;
|
|
60
|
+
let hasUnresolvableSpread = false;
|
|
61
|
+
|
|
62
|
+
for (const attribute of path.node.attributes) {
|
|
63
|
+
if (t.isJSXAttribute(attribute) && attribute.name.name === key) {
|
|
64
|
+
propertyIsFound = true;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (t.isJSXSpreadAttribute(attribute)) {
|
|
69
|
+
if (t.isObjectExpression(attribute.argument)) {
|
|
70
|
+
const propertyInSpread = attribute.argument.properties.some(
|
|
71
|
+
(p) =>
|
|
72
|
+
(t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === key) ||
|
|
73
|
+
(t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key)
|
|
74
|
+
);
|
|
75
|
+
if (propertyInSpread) {
|
|
76
|
+
propertyIsFound = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
} else if (t.isIdentifier(attribute.argument)) {
|
|
80
|
+
const binding = path.scope.getBinding(attribute.argument.name);
|
|
81
|
+
if (
|
|
82
|
+
binding?.path.node &&
|
|
83
|
+
t.isVariableDeclarator(binding.path.node) &&
|
|
84
|
+
t.isObjectExpression(binding.path.node.init)
|
|
85
|
+
) {
|
|
86
|
+
const propertyInSpread = binding.path.node.init.properties.some(
|
|
87
|
+
(p) =>
|
|
88
|
+
(t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === key) ||
|
|
89
|
+
(t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key)
|
|
90
|
+
);
|
|
91
|
+
if (propertyInSpread) {
|
|
92
|
+
propertyIsFound = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
hasUnresolvableSpread = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
hasUnresolvableSpread = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!propertyIsFound && !hasUnresolvableSpread) {
|
|
107
|
+
path.node.attributes.push(t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(value)));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
49
111
|
/**
|
|
50
112
|
* Helper that builds an Object.assign expression out of the existing JSX attributes.
|
|
51
113
|
* It handles both plain JSXAttributes and spread attributes.
|
|
@@ -142,3 +204,106 @@ export const hasAccessibilityProperty = (
|
|
|
142
204
|
}
|
|
143
205
|
return false;
|
|
144
206
|
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Extracts the `style` attribute from a JSX attributes list.
|
|
210
|
+
*
|
|
211
|
+
* @returns An object containing the attribute node itself (if found) and the expression inside
|
|
212
|
+
*/
|
|
213
|
+
export function extractStyleAttribute(attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute>): {
|
|
214
|
+
styleAttribute?: t.JSXAttribute;
|
|
215
|
+
styleExpr?: t.Expression;
|
|
216
|
+
} {
|
|
217
|
+
for (const attribute of attributes) {
|
|
218
|
+
if (t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: 'style' })) {
|
|
219
|
+
if (
|
|
220
|
+
attribute.value &&
|
|
221
|
+
t.isJSXExpressionContainer(attribute.value) &&
|
|
222
|
+
!t.isJSXEmptyExpression(attribute.value.expression)
|
|
223
|
+
) {
|
|
224
|
+
return {
|
|
225
|
+
styleAttribute: attribute,
|
|
226
|
+
styleExpr: attribute.value.expression,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { styleAttribute: attribute };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Attempts to statically extract the `userSelect` style property from a style expression.
|
|
237
|
+
*
|
|
238
|
+
* If the `userSelect` value can be resolved at compile-time, the property is removed from the
|
|
239
|
+
* object literal (or array element) and its mapped boolean value for the native `selectable`
|
|
240
|
+
* prop is returned. When the value is unknown or the expression is not statically analysable,
|
|
241
|
+
* `undefined` is returned and no modification is made.
|
|
242
|
+
*/
|
|
243
|
+
export function extractSelectableAndUpdateStyle(styleExpr: t.Expression): boolean | undefined {
|
|
244
|
+
// Helper to process a single ObjectExpression
|
|
245
|
+
const handleObjectExpression = (objectExpr: t.ObjectExpression): boolean | undefined => {
|
|
246
|
+
let selectableValue: boolean | undefined;
|
|
247
|
+
|
|
248
|
+
objectExpr.properties = objectExpr.properties.filter((property) => {
|
|
249
|
+
if (
|
|
250
|
+
!t.isObjectProperty(property) ||
|
|
251
|
+
(!t.isIdentifier(property.key, { name: 'userSelect' }) &&
|
|
252
|
+
!(t.isStringLiteral(property.key) && property.key.value === 'userSelect'))
|
|
253
|
+
) {
|
|
254
|
+
return true; // keep property
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (t.isStringLiteral(property.value)) {
|
|
258
|
+
const mapped = USER_SELECT_STYLE_TO_SELECTABLE_PROP[property.value.value];
|
|
259
|
+
if (mapped !== undefined) {
|
|
260
|
+
selectableValue = mapped;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Remove the `userSelect` property
|
|
265
|
+
return false;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return selectableValue;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (t.isObjectExpression(styleExpr)) {
|
|
272
|
+
return handleObjectExpression(styleExpr);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (t.isArrayExpression(styleExpr)) {
|
|
276
|
+
let selectableValue: boolean | undefined;
|
|
277
|
+
for (const element of styleExpr.elements) {
|
|
278
|
+
if (element && t.isObjectExpression(element)) {
|
|
279
|
+
const value = handleObjectExpression(element);
|
|
280
|
+
if (value !== undefined) {
|
|
281
|
+
selectableValue = value; // prefer last defined value
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return selectableValue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return undefined; // not statically analysable
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Checks if a node represents a string value.
|
|
293
|
+
*/
|
|
294
|
+
export const isStringNode = (path: NodePath<t.JSXOpeningElement>, child: t.Node): boolean => {
|
|
295
|
+
if (t.isJSXText(child) || t.isStringLiteral(child)) return true;
|
|
296
|
+
|
|
297
|
+
if (t.isJSXExpressionContainer(child)) {
|
|
298
|
+
const expression = child.expression;
|
|
299
|
+
if (t.isIdentifier(expression)) {
|
|
300
|
+
const binding = path.scope.getBinding(expression.name);
|
|
301
|
+
if (binding && binding.path.node && t.isVariableDeclarator(binding.path.node)) {
|
|
302
|
+
return !!binding.path.node.init && t.isStringLiteral(binding.path.node.init);
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
if (t.isStringLiteral(expression)) return true;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
};
|