@tamagui/static 1.0.1-beta.56 → 1.0.1-beta.60

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 (41) hide show
  1. package/dist/cjs/extractor/createExtractor.js +18 -1
  2. package/dist/cjs/extractor/createExtractor.js.map +2 -2
  3. package/dist/cjs/patchReactNativeWeb.js +1 -1
  4. package/dist/cjs/patchReactNativeWeb.js.map +2 -2
  5. package/dist/esm/extractor/createExtractor.js +18 -1
  6. package/dist/esm/extractor/createExtractor.js.map +2 -2
  7. package/dist/esm/patchReactNativeWeb.js +1 -1
  8. package/dist/esm/patchReactNativeWeb.js.map +2 -2
  9. package/dist/jsx/extractor/createExtractor.js +18 -1
  10. package/dist/jsx/patchReactNativeWeb.js +1 -1
  11. package/package.json +9 -7
  12. package/src/constants.ts +13 -0
  13. package/src/extractor/accessSafe.ts +18 -0
  14. package/src/extractor/babelParse.ts +27 -0
  15. package/src/extractor/buildClassName.ts +61 -0
  16. package/src/extractor/createEvaluator.ts +69 -0
  17. package/src/extractor/createExtractor.ts +1696 -0
  18. package/src/extractor/ensureImportingConcat.ts +39 -0
  19. package/src/extractor/evaluateAstNode.ts +121 -0
  20. package/src/extractor/extractHelpers.ts +119 -0
  21. package/src/extractor/extractMediaStyle.ts +190 -0
  22. package/src/extractor/extractToClassNames.ts +455 -0
  23. package/src/extractor/findTopmostFunction.ts +22 -0
  24. package/src/extractor/generatedUid.ts +43 -0
  25. package/src/extractor/getPrefixLogs.ts +6 -0
  26. package/src/extractor/getPropValueFromAttributes.ts +92 -0
  27. package/src/extractor/getSourceModule.ts +101 -0
  28. package/src/extractor/getStaticBindingsForScope.ts +183 -0
  29. package/src/extractor/hoistClassNames.ts +45 -0
  30. package/src/extractor/literalToAst.ts +84 -0
  31. package/src/extractor/loadTamagui.ts +139 -0
  32. package/src/extractor/logLines.ts +16 -0
  33. package/src/extractor/normalizeTernaries.ts +63 -0
  34. package/src/extractor/removeUnusedHooks.ts +76 -0
  35. package/src/extractor/timer.ts +18 -0
  36. package/src/extractor/validHTMLAttributes.ts +99 -0
  37. package/src/index.ts +9 -0
  38. package/src/patchReactNativeWeb.ts +165 -0
  39. package/src/types.ts +97 -0
  40. package/types/extractor/createExtractor.d.ts +14 -1
  41. package/types/extractor/createExtractor.d.ts.map +1 -1
@@ -0,0 +1,455 @@
1
+ import * as path from 'path'
2
+ import { basename } from 'path'
3
+ import * as util from 'util'
4
+
5
+ import generate from '@babel/generator'
6
+ import * as t from '@babel/types'
7
+ import { getStylesAtomic } from '@tamagui/core-node'
8
+ import { concatClassName } from '@tamagui/helpers'
9
+ import invariant from 'invariant'
10
+ import { ViewStyle } from 'react-native'
11
+ import { LoaderContext } from 'webpack'
12
+
13
+ import { CONCAT_CLASSNAME_IMPORT } from '../constants'
14
+ import { ClassNameObject, StyleObject, TamaguiOptions, Ternary } from '../types'
15
+ import { babelParse } from './babelParse'
16
+ import { buildClassName } from './buildClassName'
17
+ import { Extractor } from './createExtractor'
18
+ import { ensureImportingConcat } from './ensureImportingConcat'
19
+ import { isSimpleSpread } from './extractHelpers'
20
+ import { extractMediaStyle } from './extractMediaStyle'
21
+ import { getPrefixLogs } from './getPrefixLogs'
22
+ import { hoistClassNames } from './hoistClassNames'
23
+ import { literalToAst } from './literalToAst'
24
+ import { logLines } from './logLines'
25
+ import { timer } from './timer'
26
+
27
+ const mergeStyleGroups = {
28
+ shadowOpacity: true,
29
+ shadowRadius: true,
30
+ shadowColor: true,
31
+ shadowOffset: true,
32
+ }
33
+
34
+ export type ExtractedResponse = {
35
+ js: string | Buffer
36
+ styles: string
37
+ stylesPath?: string
38
+ ast: t.File
39
+ map: any // RawSourceMap from 'source-map'
40
+ }
41
+
42
+ export function extractToClassNames({
43
+ loader,
44
+ extractor,
45
+ source,
46
+ sourcePath,
47
+ options,
48
+ shouldPrintDebug,
49
+ cssLoaderPath,
50
+ threaded,
51
+ cssPath,
52
+ }: {
53
+ loader: LoaderContext<any>
54
+ cssLoaderPath: string
55
+ extractor: Extractor
56
+ source: string | Buffer
57
+ sourcePath: string
58
+ options: TamaguiOptions
59
+ shouldPrintDebug: boolean | 'verbose'
60
+ cssPath: string
61
+ threaded?: boolean
62
+ }): ExtractedResponse | null {
63
+ const tm = timer()
64
+
65
+ if (typeof source !== 'string') {
66
+ throw new Error('`source` must be a string of javascript')
67
+ }
68
+ invariant(
69
+ typeof sourcePath === 'string' && path.isAbsolute(sourcePath),
70
+ '`sourcePath` must be an absolute path to a .js file'
71
+ )
72
+
73
+ const shouldLogTiming = options.logTimings ?? true
74
+ const start = Date.now()
75
+ const mem = shouldLogTiming ? process.memoryUsage() : null
76
+
77
+ // Using a map for (officially supported) guaranteed insertion order
78
+ let ast: t.File
79
+
80
+ try {
81
+ // @ts-ignore
82
+ ast = babelParse(source)
83
+ } catch (err) {
84
+ console.error('babel parse error:', sourcePath)
85
+ throw err
86
+ }
87
+
88
+ tm.mark(`babel-parse`, shouldPrintDebug === 'verbose')
89
+
90
+ const cssMap = new Map<string, { css: string; commentTexts: string[] }>()
91
+ const existingHoists: { [key: string]: t.Identifier } = {}
92
+
93
+ let hasFlattened = false
94
+
95
+ const res = extractor.parse(ast, {
96
+ sourcePath,
97
+ shouldPrintDebug,
98
+ ...options,
99
+ target: 'html',
100
+ getFlattenedNode: ({ tag }) => {
101
+ hasFlattened = true
102
+ return tag
103
+ },
104
+ onExtractTag: ({
105
+ attrs,
106
+ node,
107
+ attemptEval,
108
+ jsxPath,
109
+ originalNodeName,
110
+ filePath,
111
+ lineNumbers,
112
+ programPath,
113
+ isFlattened,
114
+ }) => {
115
+ let finalClassNames: ClassNameObject[] = []
116
+ let finalAttrs: (t.JSXAttribute | t.JSXSpreadAttribute)[] = []
117
+ let finalStyles: StyleObject[] = []
118
+
119
+ let viewStyles = {}
120
+ for (const attr of attrs) {
121
+ if (attr.type === 'style') {
122
+ viewStyles = {
123
+ ...viewStyles,
124
+ ...attr.value,
125
+ }
126
+ }
127
+ }
128
+
129
+ const ensureNeededPrevStyle = (style: ViewStyle) => {
130
+ // ensure all group keys are merged
131
+ const keys = Object.keys(style)
132
+ if (!keys.some((key) => mergeStyleGroups[key])) {
133
+ return style
134
+ }
135
+ for (const k in mergeStyleGroups) {
136
+ if (k in viewStyles) {
137
+ style[k] = style[k] ?? viewStyles[k]
138
+ }
139
+ }
140
+ return style
141
+ }
142
+
143
+ const addStyles = (style: ViewStyle | null): StyleObject[] => {
144
+ if (!style) return []
145
+ const styleWithPrev = ensureNeededPrevStyle(style)
146
+ const res = getStylesAtomic(styleWithPrev)
147
+ if (res.length) {
148
+ finalStyles = [...finalStyles, ...res]
149
+ }
150
+ return res
151
+ }
152
+
153
+ // 1 to start above any :hover styles
154
+ let lastMediaImportance = 1
155
+ for (const attr of attrs) {
156
+ switch (attr.type) {
157
+ case 'style':
158
+ if (!isFlattened) {
159
+ // only ever one at a time i believe so we can be lazy with this access
160
+ const { hoverStyle, pressStyle, focusStyle } = attr.value
161
+ const pseudos = [
162
+ ['hoverStyle', hoverStyle],
163
+ ['pressStyle', pressStyle],
164
+ ['focusStyle', focusStyle],
165
+ ] as const
166
+
167
+ const styles = getStylesAtomic(attr.value, {
168
+ splitTransforms: true,
169
+ })
170
+ finalStyles = [...finalStyles, ...styles]
171
+
172
+ for (const [key, value] of pseudos) {
173
+ if (value && Object.keys(value).length) {
174
+ finalAttrs.push(
175
+ t.jsxAttribute(
176
+ t.jsxIdentifier(key),
177
+ t.jsxExpressionContainer(literalToAst(value))
178
+ )
179
+ )
180
+ }
181
+ }
182
+
183
+ for (const style of styles) {
184
+ if (style.pseudo) {
185
+ continue
186
+ }
187
+ // leave them as attributes
188
+ finalAttrs.push(
189
+ t.jsxAttribute(t.jsxIdentifier(style.property), t.stringLiteral(style.identifier))
190
+ )
191
+ }
192
+
193
+ // for (const key in attr.value) {
194
+ // if (pseudos.some((x) => x[0] === key)) {
195
+ // // we handled pseudos above...
196
+ // continue
197
+ // }
198
+ // // leave them as attributes
199
+ // finalAttrs.push(
200
+ // t.jsxAttribute(t.jsxIdentifier(key), t.stringLiteral(attr.value[key]))
201
+ // )
202
+ // }
203
+ } else {
204
+ const styles = addStyles(attr.value)
205
+ const newClassNames = concatClassName(styles.map((x) => x.identifier).join(' '))
206
+ const existing = finalClassNames.find(
207
+ (x) => x.type == 'StringLiteral'
208
+ ) as t.StringLiteral | null
209
+
210
+ // const newClassNames = concatClassName(Object.values(attr.value))
211
+ // const existing = finalClassNames.find(
212
+ // (x) => x.type == 'StringLiteral'
213
+ // ) as t.StringLiteral | null
214
+
215
+ if (existing) {
216
+ existing.value = `${existing.value} ${newClassNames}`
217
+ } else {
218
+ finalClassNames = [...finalClassNames, t.stringLiteral(newClassNames)]
219
+ }
220
+ }
221
+
222
+ break
223
+ case 'attr':
224
+ const val = attr.value
225
+ if (t.isJSXSpreadAttribute(val)) {
226
+ if (isSimpleSpread(val)) {
227
+ finalClassNames.push(
228
+ t.logicalExpression(
229
+ '&&',
230
+ val.argument,
231
+ t.memberExpression(val.argument, t.identifier('className'))
232
+ )
233
+ )
234
+ }
235
+ } else if (val.name.name === 'className') {
236
+ const value = val.value
237
+ if (value) {
238
+ try {
239
+ const evaluatedValue = attemptEval(value)
240
+ finalClassNames.push(t.stringLiteral(evaluatedValue))
241
+ } catch (e) {
242
+ finalClassNames.push(value['expression'])
243
+ }
244
+ }
245
+ continue
246
+ }
247
+ finalAttrs.push(val)
248
+ break
249
+ case 'ternary':
250
+ const mediaExtraction = extractMediaStyle(
251
+ attr.value,
252
+ jsxPath,
253
+ extractor.getTamagui(),
254
+ sourcePath,
255
+ lastMediaImportance,
256
+ shouldPrintDebug
257
+ )
258
+ if (shouldPrintDebug) {
259
+ if (mediaExtraction) {
260
+ // prettier-ignore
261
+ console.log('ternary (mediaStyles)', mediaExtraction.ternaryWithoutMedia?.inlineMediaQuery ?? '', mediaExtraction.mediaStyles.map((x) => x.identifier).join('.'))
262
+ }
263
+ }
264
+ if (!mediaExtraction) {
265
+ addTernaryStyle(
266
+ attr.value,
267
+ addStyles(attr.value.consequent),
268
+ addStyles(attr.value.alternate)
269
+ )
270
+ continue
271
+ }
272
+ lastMediaImportance++
273
+ if (mediaExtraction.mediaStyles) {
274
+ finalStyles = [...finalStyles, ...mediaExtraction.mediaStyles]
275
+ }
276
+ if (mediaExtraction.ternaryWithoutMedia) {
277
+ addTernaryStyle(mediaExtraction.ternaryWithoutMedia, mediaExtraction.mediaStyles, [])
278
+ } else {
279
+ finalClassNames = [
280
+ ...finalClassNames,
281
+ ...mediaExtraction.mediaStyles.map((x) => t.stringLiteral(x.identifier)),
282
+ ]
283
+ }
284
+ break
285
+ }
286
+ }
287
+
288
+ function addTernaryStyle(ternary: Ternary, a: any, b: any) {
289
+ const cCN = a.map((x) => x.identifier).join(' ')
290
+ const aCN = b.map((x) => x.identifier).join(' ')
291
+ if (a.length && b.length) {
292
+ finalClassNames.push(
293
+ t.conditionalExpression(ternary.test, t.stringLiteral(cCN), t.stringLiteral(aCN))
294
+ )
295
+ } else {
296
+ finalClassNames.push(
297
+ t.conditionalExpression(
298
+ ternary.test,
299
+ t.stringLiteral(' ' + cCN),
300
+ t.stringLiteral(' ' + aCN)
301
+ )
302
+ )
303
+ }
304
+ }
305
+
306
+ if (shouldPrintDebug) {
307
+ // prettier-ignore
308
+ console.log(' finalClassNames\n', logLines(finalClassNames.map(x => x['value']).join(' ')))
309
+ }
310
+
311
+ node.attributes = finalAttrs
312
+
313
+ if (finalClassNames.length) {
314
+ // inserts the _cn variable and uses it for className
315
+ let names = buildClassName(finalClassNames)
316
+ if (t.isStringLiteral(names)) {
317
+ names.value = concatClassName(names.value)
318
+ }
319
+ const nameExpr = names ? hoistClassNames(jsxPath, existingHoists, names) : null
320
+ let expr = nameExpr
321
+
322
+ // if has some spreads, use concat helper
323
+ if (nameExpr && !t.isIdentifier(nameExpr)) {
324
+ if (!hasFlattened) {
325
+ // not flat
326
+ } else {
327
+ ensureImportingConcat(programPath)
328
+ const simpleSpreads = attrs.filter(
329
+ (x) => t.isJSXSpreadAttribute(x.value) && isSimpleSpread(x.value)
330
+ )
331
+ expr = t.callExpression(t.identifier(CONCAT_CLASSNAME_IMPORT), [
332
+ expr,
333
+ ...simpleSpreads.map((val) => val.value['argument']),
334
+ ])
335
+ }
336
+ }
337
+
338
+ node.attributes.push(
339
+ t.jsxAttribute(t.jsxIdentifier('className'), t.jsxExpressionContainer(expr))
340
+ )
341
+ }
342
+
343
+ const comment = util.format('/* %s:%s (%s) */', filePath, lineNumbers, originalNodeName)
344
+
345
+ for (const { className, rules, identifier, value } of finalStyles) {
346
+ // console.log('backvalue', identifier, value)
347
+ // ast.program.body.unshift(
348
+ // t.expressionStatement(
349
+ // t.callExpression(
350
+ // t.memberExpression(
351
+ // t.callExpression(t.identifier('require'), [t.stringLiteral('@tamagui/core')]),
352
+ // t.identifier('setIdentifierValue')
353
+ // ),
354
+ // [t.stringLiteral(identifier), t.stringLiteral(value)]
355
+ // )
356
+ // )
357
+ // )
358
+
359
+ if (cssMap.has(className)) {
360
+ if (comment) {
361
+ const val = cssMap.get(className)!
362
+ val.commentTexts.push(comment)
363
+ cssMap.set(className, val)
364
+ }
365
+ } else if (rules.length) {
366
+ if (rules.length > 1) {
367
+ console.log(' rules error', { rules })
368
+ throw new Error(`Shouldn't have more than one rule`)
369
+ }
370
+ cssMap.set(className, {
371
+ css: rules[0],
372
+ commentTexts: [comment],
373
+ })
374
+ }
375
+ }
376
+ },
377
+ })
378
+
379
+ if (!res || (!res.modified && !res.optimized && !res.flattened)) {
380
+ if (shouldPrintDebug) {
381
+ console.log('no res or none modified', res)
382
+ }
383
+ return null
384
+ }
385
+
386
+ const styles = Array.from(cssMap.values())
387
+ .map((x) => x.css)
388
+ .join('\n')
389
+ .trim()
390
+
391
+ if (styles) {
392
+ const cssQuery = threaded
393
+ ? `cssData=${Buffer.from(styles).toString('base64')}`
394
+ : `cssPath=${cssPath}`
395
+ const remReq = loader.remainingRequest
396
+ const importPath = `${cssPath}!=!${cssLoaderPath}?${cssQuery}!${remReq}`
397
+ ast.program.body.unshift(t.importDeclaration([], t.stringLiteral(importPath)))
398
+ }
399
+
400
+ const result = generate(
401
+ ast,
402
+ {
403
+ concise: false,
404
+ filename: sourcePath,
405
+ // this makes the debug output terrible, and i think sourcemap works already
406
+ retainLines: false,
407
+ sourceFileName: sourcePath,
408
+ sourceMaps: true,
409
+ },
410
+ source
411
+ )
412
+
413
+ if (shouldPrintDebug) {
414
+ console.log(
415
+ '\n -------- output code ------- \n\n',
416
+ result.code
417
+ .split('\n')
418
+ .filter((x) => !x.startsWith('//'))
419
+ .join('\n')
420
+ )
421
+ console.log('\n -------- output style -------- \n\n', styles)
422
+ }
423
+
424
+ if (shouldLogTiming) {
425
+ const memUsed = mem
426
+ ? Math.round(((process.memoryUsage().heapUsed - mem.heapUsed) / 1024 / 1204) * 10) / 10
427
+ : 0
428
+ const path = basename(sourcePath)
429
+ .replace(/\.[jt]sx?$/, '')
430
+ .slice(0, 22)
431
+ .trim()
432
+ .padStart(24)
433
+ const numOptimized = `${res.optimized}`.padStart(3)
434
+ const numFound = `${res.found}`.padStart(3)
435
+ const numFlattened = `${res.flattened}`.padStart(3)
436
+ const memory = process.env.DEBUG && memUsed > 10 ? ` ${memUsed}MB` : ''
437
+ const timing = Date.now() - start
438
+ const timingWarning = timing > 50 ? '⚠️' : timing > 150 ? '☢️' : ''
439
+ const timingStr = `${timing}ms${timingWarning}`.padStart(6)
440
+ console.log(
441
+ `${getPrefixLogs(
442
+ options
443
+ )} ${path} ${numFound} · ${numOptimized} · ${numFlattened} ${timingStr} ${
444
+ memory ? `(${memory})` : ''
445
+ }`
446
+ )
447
+ }
448
+
449
+ return {
450
+ ast,
451
+ styles,
452
+ js: result.code,
453
+ map: result.map,
454
+ }
455
+ }
@@ -0,0 +1,22 @@
1
+ import { NodePath } from '@babel/traverse'
2
+ import * as t from '@babel/types'
3
+
4
+ export function findTopmostFunction(jsxPath: NodePath<t.JSXElement>) {
5
+ // get topmost fn
6
+ const isFunction = (path: NodePath<any>) =>
7
+ path.isArrowFunctionExpression() || path.isFunctionDeclaration() || path.isFunctionExpression()
8
+ let compFn: NodePath<any> | null = jsxPath.findParent(isFunction)
9
+ while (compFn) {
10
+ const parent = compFn.findParent(isFunction)
11
+ if (parent) {
12
+ compFn = parent
13
+ } else {
14
+ break
15
+ }
16
+ }
17
+ if (!compFn) {
18
+ // console.error(`Couldn't find a topmost function for media query extraction`)
19
+ return null
20
+ }
21
+ return compFn
22
+ }
@@ -0,0 +1,43 @@
1
+ import * as t from '@babel/types'
2
+ import invariant from 'invariant'
3
+
4
+ // TODO: open a PR upstream
5
+ declare module '@babel/types' {
6
+ export function toIdentifier(input: string): string
7
+ }
8
+
9
+ // A clone of path.scope.generateUid that doesn't prepend underscores
10
+ export function generateUid(scope: any, name: string): string {
11
+ invariant(typeof scope === 'object', 'generateUid expects a scope object as its first parameter')
12
+ invariant(
13
+ typeof name === 'string' && name !== '',
14
+ 'generateUid expects a valid name as its second parameter'
15
+ )
16
+
17
+ name = t
18
+ .toIdentifier(name)
19
+ .replace(/^_+/, '')
20
+ .replace(/[0-9]+$/g, '')
21
+
22
+ let uid
23
+ let i = 0
24
+ do {
25
+ if (i > 1) {
26
+ uid = name + i
27
+ } else {
28
+ uid = name
29
+ }
30
+ i++
31
+ } while (
32
+ scope.hasLabel(uid) ||
33
+ scope.hasBinding(uid) ||
34
+ scope.hasGlobal(uid) ||
35
+ scope.hasReference(uid)
36
+ )
37
+
38
+ const program = scope.getProgramParent()
39
+ program.references[uid] = true
40
+ program.uids[uid] = true
41
+
42
+ return uid
43
+ }
@@ -0,0 +1,6 @@
1
+ import { TamaguiOptions } from '../types'
2
+
3
+ export function getPrefixLogs(options?: TamaguiOptions) {
4
+ const { TAMAGUI_TARGET } = process.env
5
+ return options?.prefixLogs ?? ` ${TAMAGUI_TARGET} | `
6
+ }
@@ -0,0 +1,92 @@
1
+ import generate from '@babel/generator'
2
+ import * as t from '@babel/types'
3
+
4
+ import { accessSafe } from './accessSafe'
5
+
6
+ /**
7
+ * getPropValueFromAttributes gets a prop by name from a list of attributes and accounts for potential spread operators.
8
+ * Here's an example. Given this component:
9
+ * ```
10
+ * <Block coolProp="wow" {...spread1} neatProp="ok" {...spread2} />```
11
+ * getPropValueFromAttributes will return the following:
12
+ * - for propName `coolProp`:
13
+ * ```
14
+ * accessSafe(spread1, 'coolProp') || accessSafe(spread2, 'coolProp') || 'wow'```
15
+ * - for propName `neatProp`:
16
+ * ```
17
+ * accessSafe(spread2, 'neatProp') || 'ok'```
18
+ * - for propName `notPresent`: `null`
19
+ *
20
+ * The returned value should (obviously) be placed after spread operators.
21
+ */
22
+ export function getPropValueFromAttributes(
23
+ propName: string,
24
+ attrs: (t.JSXAttribute | t.JSXSpreadAttribute)[]
25
+ ): t.Expression | null {
26
+ let propIndex: number = -1
27
+ let jsxAttr: t.JSXAttribute | null = null
28
+ for (let idx = -1, len = attrs.length; ++idx < len; ) {
29
+ const attr = attrs[idx]
30
+ if (t.isJSXAttribute(attr) && attr.name && attr.name.name === propName) {
31
+ propIndex = idx
32
+ jsxAttr = attr
33
+ break
34
+ }
35
+ }
36
+
37
+ if (!jsxAttr || jsxAttr.value == null) {
38
+ return null
39
+ }
40
+
41
+ let propValue:
42
+ | t.JSXElement
43
+ | t.JSXFragment
44
+ | t.StringLiteral
45
+ | t.JSXExpressionContainer
46
+ | t.JSXEmptyExpression
47
+ | t.Expression = jsxAttr.value
48
+
49
+ if (t.isJSXExpressionContainer(propValue)) {
50
+ propValue = propValue.expression
51
+ }
52
+
53
+ // TODO how to handle this??
54
+ if (t.isJSXEmptyExpression(propValue)) {
55
+ console.error('encountered JSXEmptyExpression')
56
+ return null
57
+ }
58
+
59
+ // filter out spread props that occur before propValue
60
+ const applicableSpreads = attrs
61
+ .filter(
62
+ // 1. idx is greater than propValue prop index
63
+ // 2. attr is a spread operator
64
+ (attr, idx): attr is t.JSXSpreadAttribute => {
65
+ if (t.isJSXSpreadAttribute(attr)) {
66
+ if (t.isIdentifier(attr.argument) || t.isMemberExpression(attr.argument)) {
67
+ return idx > propIndex
68
+ }
69
+ if (t.isLogicalExpression(attr.argument)) {
70
+ return false
71
+ }
72
+ throw new Error(
73
+ `unsupported spread of type "${attr.argument.type}": ${generate(attr).code}`
74
+ )
75
+ }
76
+ return false
77
+ }
78
+ )
79
+ .map((attr) => attr.argument)
80
+
81
+ // if spread operators occur after propValue, create a binary expression for each operator
82
+ // i.e. before1.propValue || before2.propValue || propValue
83
+ // TODO: figure out how to do this without all the extra parens
84
+ if (applicableSpreads.length > 0) {
85
+ propValue = applicableSpreads.reduce<t.Expression>(
86
+ (acc, val) => t.logicalExpression('||', accessSafe(val, propName), acc),
87
+ propValue
88
+ )
89
+ }
90
+
91
+ return propValue
92
+ }