diff --git a/source/components/JsxParser.test.tsx b/source/components/JsxParser.test.tsx
index 14f3458..5402d13 100644
--- a/source/components/JsxParser.test.tsx
+++ b/source/components/JsxParser.test.tsx
@@ -958,6 +958,24 @@ describe('JsxParser Component', () => {
expect(node.childNodes[0].textContent).toEqual(bindings.array[bindings.index].of)
expect(instance.ParsedChildren[0].props.foo).toEqual(bindings.array[bindings.index].of)
})
+ it('can evaluate a[b]', () => {
+ const { node } = render(
+ ,
+ )
+ expect(node.innerHTML).toMatch('hello')
+ })
+ it('handles optional chaining', () => {
+ const { node } = render(
+ ,
+ )
+ expect(node.innerHTML).toMatch('baz')
+ })
/* eslint-enable dot-notation,no-useless-concat */
})
})
@@ -1264,5 +1282,15 @@ describe('JsxParser Component', () => {
)
expect(node.outerHTML).toEqual('
from-container
')
})
+
+ it('supports math with scope', () => {
+ const { node } = render()
+ expect(node.innerHTML).toEqual('246')
+ })
+
+ it('supports conditional with scope', () => {
+ const { node } = render()
+ expect(node.innerHTML).toEqual('1-13')
+ })
})
})
diff --git a/source/components/JsxParser.tsx b/source/components/JsxParser.tsx
index 1efe906..9930fc5 100644
--- a/source/components/JsxParser.tsx
+++ b/source/components/JsxParser.tsx
@@ -94,27 +94,29 @@ export default class JsxParser extends React.Component {
case 'ArrayExpression':
return expression.elements.map(ele => this.#parseExpression(ele, scope)) as ParsedTree
case 'BinaryExpression':
+ const binaryLeft = this.#parseExpression(expression.left, scope)
+ const binaryRight = this.#parseExpression(expression.right, scope)
/* eslint-disable eqeqeq,max-len */
switch (expression.operator) {
- case '-': return this.#parseExpression(expression.left) - this.#parseExpression(expression.right)
- case '!=': return this.#parseExpression(expression.left) != this.#parseExpression(expression.right)
- case '!==': return this.#parseExpression(expression.left) !== this.#parseExpression(expression.right)
- case '*': return this.#parseExpression(expression.left) * this.#parseExpression(expression.right)
- case '**': return this.#parseExpression(expression.left) ** this.#parseExpression(expression.right)
- case '/': return this.#parseExpression(expression.left) / this.#parseExpression(expression.right)
- case '%': return this.#parseExpression(expression.left) % this.#parseExpression(expression.right)
- case '+': return this.#parseExpression(expression.left) + this.#parseExpression(expression.right)
- case '<': return this.#parseExpression(expression.left) < this.#parseExpression(expression.right)
- case '<=': return this.#parseExpression(expression.left) <= this.#parseExpression(expression.right)
- case '==': return this.#parseExpression(expression.left) == this.#parseExpression(expression.right)
- case '===': return this.#parseExpression(expression.left) === this.#parseExpression(expression.right)
- case '>': return this.#parseExpression(expression.left) > this.#parseExpression(expression.right)
- case '>=': return this.#parseExpression(expression.left) >= this.#parseExpression(expression.right)
+ case '-': return binaryLeft - binaryRight
+ case '!=': return binaryLeft != binaryRight
+ case '!==': return binaryLeft !== binaryRight
+ case '*': return binaryLeft * binaryRight
+ case '**': return binaryLeft ** binaryRight
+ case '/': return binaryLeft / binaryRight
+ case '%': return binaryLeft % binaryRight
+ case '+': return binaryLeft + binaryRight
+ case '<': return binaryLeft < binaryRight
+ case '<=': return binaryLeft <= binaryRight
+ case '==': return binaryLeft == binaryRight
+ case '===': return binaryLeft === binaryRight
+ case '>': return binaryLeft > binaryRight
+ case '>=': return binaryLeft >= binaryRight
/* eslint-enable eqeqeq,max-len */
}
return undefined
case 'CallExpression':
- const parsedCallee = this.#parseExpression(expression.callee)
+ const parsedCallee = this.#parseExpression(expression.callee, scope)
if (parsedCallee === undefined) {
this.props.onError!(new Error(`The expression '${expression.callee}' could not be resolved, resulting in an undefined return value.`))
return undefined
@@ -123,11 +125,11 @@ export default class JsxParser extends React.Component {
arg => this.#parseExpression(arg, expression.callee),
))
case 'ConditionalExpression':
- return this.#parseExpression(expression.test)
- ? this.#parseExpression(expression.consequent)
- : this.#parseExpression(expression.alternate)
+ return this.#parseExpression(expression.test, scope)
+ ? this.#parseExpression(expression.consequent, scope)
+ : this.#parseExpression(expression.alternate, scope)
case 'ExpressionStatement':
- return this.#parseExpression(expression.expression)
+ return this.#parseExpression(expression.expression, scope)
case 'Identifier':
if (scope && expression.name in scope) {
return scope[expression.name]
@@ -137,10 +139,10 @@ export default class JsxParser extends React.Component {
case 'Literal':
return expression.value
case 'LogicalExpression':
- const left = this.#parseExpression(expression.left)
+ const left = this.#parseExpression(expression.left, scope)
if (expression.operator === '||' && left) return left
if ((expression.operator === '&&' && left) || (expression.operator === '||' && !left)) {
- return this.#parseExpression(expression.right)
+ return this.#parseExpression(expression.right, scope)
}
return false
case 'MemberExpression':
@@ -148,7 +150,7 @@ export default class JsxParser extends React.Component {
case 'ObjectExpression':
const object: Record = {}
expression.properties.forEach(prop => {
- object[prop.key.name! || prop.key.value!] = this.#parseExpression(prop.value)
+ object[prop.key.name! || prop.key.value!] = this.#parseExpression(prop.value, scope)
})
return object
case 'TemplateElement':
@@ -159,7 +161,7 @@ export default class JsxParser extends React.Component {
if (a.start < b.start) return -1
return 1
})
- .map(item => this.#parseExpression(item))
+ .map(item => this.#parseExpression(item, scope))
.join('')
case 'UnaryExpression':
switch (expression.operator) {
@@ -183,37 +185,25 @@ export default class JsxParser extends React.Component {
}
#parseMemberExpression = (expression: AcornJSX.MemberExpression, scope?: Scope): any => {
- // eslint-disable-next-line prefer-destructuring
- let { object } = expression
- const path = [expression.property?.name ?? JSON.parse(expression.property?.raw ?? '""')]
-
- if (expression.object.type !== 'Literal') {
- while (object && ['MemberExpression', 'Literal'].includes(object?.type)) {
- const { property } = (object as AcornJSX.MemberExpression)
- if ((object as AcornJSX.MemberExpression).computed) {
- path.unshift(this.#parseExpression(property!, scope))
- } else {
- path.unshift(property?.name ?? JSON.parse(property?.raw ?? '""'))
- }
+ const object = this.#parseExpression(expression.object, scope)
- object = (object as AcornJSX.MemberExpression).object
- }
+ let property
+ if (expression.computed) {
+ property = this.#parseExpression(expression.property, scope)
+ } else if (expression.property.type === 'Identifier') {
+ property = expression.property.name
+ } else {
+ this.props.onError!(new Error('Only simple MemberExpressions are supported.'))
+ return undefined
}
- const target = this.#parseExpression(object, scope)
- try {
- let parent = target
- const member = path.reduce((value, next) => {
- parent = value
- return value[next]
- }, target)
- if (typeof member === 'function') return member.bind(parent)
-
- return member
- } catch {
- const name = (object as AcornJSX.MemberExpression)?.name || 'unknown'
- this.props.onError!(new Error(`Unable to parse ${name}["${path.join('"]["')}"]}`))
+ if (expression.optional) {
+ if (object === null || object === undefined) return undefined
}
+
+ const member = object[property]
+ if (typeof member === 'function') return member.bind(object)
+ return member
}
#parseName = (element: AcornJSX.JSXIdentifier | AcornJSX.JSXMemberExpression): string => {
diff --git a/source/types/acorn-jsx.d.ts b/source/types/acorn-jsx.d.ts
index 5af688c..2477f57 100644
--- a/source/types/acorn-jsx.d.ts
+++ b/source/types/acorn-jsx.d.ts
@@ -122,9 +122,10 @@ declare module 'acorn-jsx' {
export interface MemberExpression extends BaseExpression {
type: 'MemberExpression';
computed: boolean;
+ optional: boolean;
name?: string;
- object: Literal | MemberExpression;
- property?: MemberExpression;
+ object: Expression;
+ property: Expression;
raw?: string;
}