import generate from '@babel/generator'
import * as t from '@babel/types'
import { accessSafe } from './accessSafe'
/**
* getPropValueFromAttributes gets a prop by name from a list of attributes and accounts for potential spread operators.
* Here's an example. Given this component:
* ```
* ```
* getPropValueFromAttributes will return the following:
* - for propName `coolProp`:
* ```
* accessSafe(spread1, 'coolProp') || accessSafe(spread2, 'coolProp') || 'wow'```
* - for propName `neatProp`:
* ```
* accessSafe(spread2, 'neatProp') || 'ok'```
* - for propName `notPresent`: `null`
*
* The returned value should (obviously) be placed after spread operators.
*/
export function getPropValueFromAttributes(
propName: string,
attrs: (t.JSXAttribute | t.JSXSpreadAttribute)[]
): t.Expression | null {
let propIndex = -1
let jsxAttr: t.JSXAttribute | null = null
for (let idx = -1, len = attrs.length; ++idx < len; ) {
const attr = attrs[idx]
if (t.isJSXAttribute(attr) && attr.name && attr.name.name === propName) {
propIndex = idx
jsxAttr = attr
break
}
}
if (!jsxAttr || jsxAttr.value == null) {
return null
}
let propValue:
| t.JSXElement
| t.JSXFragment
| t.StringLiteral
| t.JSXExpressionContainer
| t.JSXEmptyExpression
| t.Expression = jsxAttr.value
if (t.isJSXExpressionContainer(propValue)) {
propValue = propValue.expression
}
// TODO how to handle this??
if (t.isJSXEmptyExpression(propValue)) {
console.error('encountered JSXEmptyExpression')
return null
}
// filter out spread props that occur before propValue
const applicableSpreads = attrs
.filter(
// 1. idx is greater than propValue prop index
// 2. attr is a spread operator
(attr, idx): attr is t.JSXSpreadAttribute => {
if (t.isJSXSpreadAttribute(attr)) {
if (t.isIdentifier(attr.argument) || t.isMemberExpression(attr.argument)) {
return idx > propIndex
}
if (t.isLogicalExpression(attr.argument)) {
return false
}
throw new Error(
`unsupported spread of type "${attr.argument.type}": ${
// @ts-ignore
generate(attr as any).code
}`
)
}
return false
}
)
.map((attr) => attr.argument)
// if spread operators occur after propValue, create a binary expression for each operator
// i.e. before1.propValue || before2.propValue || propValue
// TODO: figure out how to do this without all the extra parens
if (applicableSpreads.length > 0) {
propValue = applicableSpreads.reduce(
(acc, val) => t.logicalExpression('||', accessSafe(val, propName), acc),
propValue
)
}
return propValue
}