Index: frontend/node_modules/jsx-ast-utils/src/elementType.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/elementType.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/elementType.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,33 @@
+function resolveMemberExpressions(object = {}, property = {}) {
+  if (object.type === 'JSXMemberExpression') {
+    return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
+  }
+
+  return `${object.name}.${property.name}`;
+}
+
+/**
+ * Returns the tagName associated with a JSXElement.
+ */
+export default function elementType(node = {}) {
+  const { name } = node;
+
+  if (node.type === 'JSXOpeningFragment') {
+    return '<>';
+  }
+
+  if (!name) {
+    throw new Error('The argument provided is not a JSXElement node.');
+  }
+
+  if (name.type === 'JSXMemberExpression') {
+    const { object = {}, property = {} } = name;
+    return resolveMemberExpressions(object, property);
+  }
+
+  if (name.type === 'JSXNamespacedName') {
+    return `${name.namespace.name}:${name.name.name}`;
+  }
+
+  return node.name.name;
+}
Index: frontend/node_modules/jsx-ast-utils/src/eventHandlers.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/eventHandlers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/eventHandlers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,110 @@
+import flat from 'array.prototype.flat';
+import values from 'object.values';
+
+/**
+ * Common event handlers for JSX element event binding.
+ */
+
+const eventHandlersByType = {
+  clipboard: [
+    'onCopy',
+    'onCut',
+    'onPaste',
+  ],
+  composition: [
+    'onCompositionEnd',
+    'onCompositionStart',
+    'onCompositionUpdate',
+  ],
+  keyboard: [
+    'onKeyDown',
+    'onKeyPress',
+    'onKeyUp',
+  ],
+  focus: [
+    'onFocus',
+    'onBlur',
+  ],
+  form: [
+    'onChange',
+    'onInput',
+    'onSubmit',
+  ],
+  mouse: [
+    'onClick',
+    'onContextMenu',
+    'onDblClick',
+    'onDoubleClick',
+    'onDrag',
+    'onDragEnd',
+    'onDragEnter',
+    'onDragExit',
+    'onDragLeave',
+    'onDragOver',
+    'onDragStart',
+    'onDrop',
+    'onMouseDown',
+    'onMouseEnter',
+    'onMouseLeave',
+    'onMouseMove',
+    'onMouseOut',
+    'onMouseOver',
+    'onMouseUp',
+  ],
+  selection: [
+    'onSelect',
+  ],
+  touch: [
+    'onTouchCancel',
+    'onTouchEnd',
+    'onTouchMove',
+    'onTouchStart',
+  ],
+  ui: [
+    'onScroll',
+  ],
+  wheel: [
+    'onWheel',
+  ],
+  media: [
+    'onAbort',
+    'onCanPlay',
+    'onCanPlayThrough',
+    'onDurationChange',
+    'onEmptied',
+    'onEncrypted',
+    'onEnded',
+    'onError',
+    'onLoadedData',
+    'onLoadedMetadata',
+    'onLoadStart',
+    'onPause',
+    'onPlay',
+    'onPlaying',
+    'onProgress',
+    'onRateChange',
+    'onSeeked',
+    'onSeeking',
+    'onStalled',
+    'onSuspend',
+    'onTimeUpdate',
+    'onVolumeChange',
+    'onWaiting',
+  ],
+  image: [
+    'onLoad',
+    'onError',
+  ],
+  animation: [
+    'onAnimationStart',
+    'onAnimationEnd',
+    'onAnimationIteration',
+  ],
+  transition: [
+    'onTransitionEnd',
+  ],
+};
+
+export default flat(values(eventHandlersByType));
+
+export { eventHandlersByType };
Index: frontend/node_modules/jsx-ast-utils/src/getProp.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/getProp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/getProp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,91 @@
+import propName from './propName';
+
+const DEFAULT_OPTIONS = {
+  ignoreCase: true,
+};
+
+/**
+ * Returns the JSXAttribute itself or undefined, indicating the prop
+ * is not present on the JSXOpeningElement.
+ *
+ */
+export default function getProp(props = [], prop = '', options = DEFAULT_OPTIONS) {
+  function getName(name) { return options.ignoreCase ? name.toUpperCase() : name; }
+  const propToFind = getName(prop);
+  function isPropToFind(property) {
+    return property.type === 'Property'
+      && property.key.type === 'Identifier'
+      && propToFind === getName(property.key.name);
+  }
+
+  const foundAttribute = props.find((attribute) => {
+    // If the props contain a spread prop, try to find the property in the object expression.
+    if (attribute.type === 'JSXSpreadAttribute') {
+      return attribute.argument.type === 'ObjectExpression'
+        && propToFind !== getName('key') // https://github.com/reactjs/rfcs/pull/107
+        && attribute.argument.properties.some(isPropToFind);
+    }
+
+    return propToFind === getName(propName(attribute));
+  });
+
+  if (foundAttribute && foundAttribute.type === 'JSXSpreadAttribute') {
+    return propertyToJSXAttribute(foundAttribute.argument.properties.find(isPropToFind));
+  }
+
+  return foundAttribute;
+}
+
+function propertyToJSXAttribute(node) {
+  const { key, value } = node;
+  return {
+    type: 'JSXAttribute',
+    name: { type: 'JSXIdentifier', name: key.name, ...getBaseProps(key) },
+    value: value.type === 'Literal'
+      ? adjustRangeOfNode(value)
+      : { type: 'JSXExpressionContainer', expression: adjustExpressionRange(value), ...getBaseProps(value) },
+    ...getBaseProps(node),
+  };
+}
+
+function adjustRangeOfNode(node) {
+  const [start, end] = node.range || [node.start, node.end];
+
+  return {
+    ...node,
+    end: undefined,
+    range: [start, end],
+    start: undefined,
+  };
+}
+
+function adjustExpressionRange({ expressions, quasis, ...expression }) {
+  return {
+    ...adjustRangeOfNode(expression),
+    ...(expressions ? { expressions: expressions.map(adjustRangeOfNode) } : {}),
+    ...(quasis ? { quasis: quasis.map(adjustRangeOfNode) } : {}),
+  };
+}
+
+function getBaseProps({ loc, ...node }) {
+  const { range } = adjustRangeOfNode(node);
+
+  return {
+    loc: getBaseLocation(loc),
+    range,
+  };
+}
+
+function getBaseLocation({
+  start,
+  end,
+  source,
+  filename,
+}) {
+  return {
+    start,
+    end,
+    ...(source !== undefined ? { source } : {}),
+    ...(filename !== undefined ? { filename } : {}),
+  };
+}
Index: frontend/node_modules/jsx-ast-utils/src/getPropValue.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/getPropValue.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/getPropValue.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,45 @@
+import getValue, { getLiteralValue } from './values';
+
+const extractValue = (attribute, extractor) => {
+  if (attribute && attribute.type === 'JSXAttribute') {
+    if (attribute.value === null) {
+      // Null valued attributes imply truthiness.
+      // For example: <div aria-hidden />
+      // See: https://facebook.github.io/react/docs/jsx-in-depth.html#boolean-attributes
+      return true;
+    }
+
+    return extractor(attribute.value);
+  }
+
+  return undefined;
+};
+
+/**
+ * Returns the value of a given attribute.
+ * Different types of attributes have their associated
+ * values in different properties on the object.
+ *
+ * This function should return the most *closely* associated
+ * value with the intention of the JSX.
+ *
+ * @param attribute - The JSXAttribute collected by AST parser.
+ */
+export default function getPropValue(attribute) {
+  return extractValue(attribute, getValue);
+}
+
+/**
+ * Returns the value of a given attribute.
+ * Different types of attributes have their associated
+ * values in different properties on the object.
+ *
+ * This function should return a value only if we can extract
+ * a literal value from its attribute (i.e. values that have generic
+ * types in JavaScript - strings, numbers, booleans, etc.)
+ *
+ * @param attribute - The JSXAttribute collected by AST parser.
+ */
+export function getLiteralPropValue(attribute) {
+  return extractValue(attribute, getLiteralValue);
+}
Index: frontend/node_modules/jsx-ast-utils/src/hasProp.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/hasProp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/hasProp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+import propName from './propName';
+
+const DEFAULT_OPTIONS = {
+  spreadStrict: true,
+  ignoreCase: true,
+};
+
+/**
+ * Returns boolean indicating whether an prop exists on the props
+ * property of a JSX element node.
+ */
+export default function hasProp(props = [], prop = '', options = DEFAULT_OPTIONS) {
+  const propToCheck = options.ignoreCase ? prop.toUpperCase() : prop;
+
+  return props.some((attribute) => {
+    // If the props contain a spread prop, then refer to strict param.
+    if (attribute.type === 'JSXSpreadAttribute') {
+      return !options.spreadStrict;
+    }
+
+    const currentProp = options.ignoreCase
+      ? propName(attribute).toUpperCase()
+      : propName(attribute);
+
+    return propToCheck === currentProp;
+  });
+}
+
+/**
+ * Given the props on a node and a list of props to check, this returns a boolean
+ * indicating if any of them exist on the node.
+ */
+export function hasAnyProp(nodeProps = [], props = [], options = DEFAULT_OPTIONS) {
+  const propsToCheck = typeof props === 'string' ? props.split(' ') : props;
+
+  return propsToCheck.some((prop) => hasProp(nodeProps, prop, options));
+}
+
+/**
+ * Given the props on a node and a list of props to check, this returns a boolean
+ * indicating if all of them exist on the node
+ */
+export function hasEveryProp(nodeProps = [], props = [], options = DEFAULT_OPTIONS) {
+  const propsToCheck = typeof props === 'string' ? props.split(' ') : props;
+
+  return propsToCheck.every((prop) => hasProp(nodeProps, prop, options));
+}
Index: frontend/node_modules/jsx-ast-utils/src/index.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,19 @@
+import hasProp, { hasAnyProp, hasEveryProp } from './hasProp';
+import elementType from './elementType';
+import eventHandlers, { eventHandlersByType } from './eventHandlers';
+import getProp from './getProp';
+import getPropValue, { getLiteralPropValue } from './getPropValue';
+import propName from './propName';
+
+module.exports = {
+  hasProp,
+  hasAnyProp,
+  hasEveryProp,
+  elementType,
+  eventHandlers,
+  eventHandlersByType,
+  getProp,
+  getPropValue,
+  getLiteralPropValue,
+  propName,
+};
Index: frontend/node_modules/jsx-ast-utils/src/propName.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/propName.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/propName.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+/**
+ * Returns the name of the prop given the JSXAttribute object.
+ */
+export default function propName(prop = {}) {
+  if (!prop.type || prop.type !== 'JSXAttribute') {
+    throw new Error('The prop must be a JSXAttribute collected by the AST parser.');
+  }
+
+  if (prop.name.type === 'JSXNamespacedName') {
+    return `${prop.name.namespace.name}:${prop.name.name.name}`;
+  }
+
+  return prop.name.name;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/JSXElement.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/JSXElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/JSXElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,15 @@
+/**
+ * Extractor function for a JSXElement type value node.
+ *
+ * Returns self-closing element with correct name.
+ */
+export default function extractValueFromJSXElement(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+
+  const Tag = value.openingElement.name.name;
+  if (value.openingElement.selfClosing) {
+    return `<${Tag} />`;
+  }
+  return `<${Tag}>${[].concat(value.children).map((x) => getValue(x)).join('')}</${Tag}>`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/JSXFragment.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/JSXFragment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/JSXFragment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+/**
+ * Extractor function for a JSXFragment type value node.
+ *
+ * Returns self-closing element with correct name.
+ */
+export default function extractValueFromJSXFragment(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+
+  if (value.children.length === 0) {
+    return '<></>';
+  }
+  return `<>${[].concat(value.children).map((x) => getValue(x)).join('')}</>`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/JSXText.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/JSXText.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/JSXText.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+/**
+ * Extractor function for a JSXText type value node.
+ *
+ * Returns self-closing element with correct name.
+ */
+export default function extractValueFromJSXText(value) {
+  return value.raw;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/Literal.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/Literal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/Literal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,20 @@
+/**
+ * Extractor function for a Literal type value node.
+ *
+ * @param - value - AST Value object with type `Literal`
+ * @returns { String|Boolean } - The extracted value converted to correct type.
+ */
+export default function extractValueFromLiteral(value) {
+  const { value: extractedValue } = value;
+
+  const normalizedStringValue = typeof extractedValue === 'string' && extractedValue.toLowerCase();
+  if (normalizedStringValue === 'true') {
+    return true;
+  }
+
+  if (normalizedStringValue === 'false') {
+    return false;
+  }
+
+  return extractedValue;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/ArrayExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/ArrayExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/ArrayExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+/**
+ * Extractor function for an ArrayExpression type value node.
+ * An array expression is an expression with [] syntax.
+ *
+ * @returns - An array of the extracted elements.
+ */
+export default function extractValueFromArrayExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return value.elements.map((element) => {
+    if (element === null) return undefined;
+    return getValue(element);
+  });
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/AssignmentExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/AssignmentExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/AssignmentExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a AssignmentExpression type value node.
+ * An assignment expression looks like `x = y` or `x += y` in expression position.
+ * This will return the assignment as the value.
+ *
+ * @param - value - AST Value object with type `AssignmentExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromAssignmentExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return `${getValue(value.left)} ${value.operator} ${getValue(value.right)}`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/BinaryExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/BinaryExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/BinaryExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,69 @@
+/**
+ * Extractor function for a BinaryExpression type value node.
+ * A binary expression has a left and right side separated by an operator
+ * such as `a + b`.
+ *
+ * @param - value - AST Value object with type `BinaryExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromBinaryExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const { operator, left, right } = value;
+  const leftVal = getValue(left);
+  const rightVal = getValue(right);
+
+  switch (operator) {
+    case '==':
+      return leftVal == rightVal; // eslint-disable-line
+    case '!=':
+      return leftVal != rightVal; // eslint-disable-line
+    case '===':
+      return leftVal === rightVal;
+    case '!==':
+      return leftVal !== rightVal;
+    case '<':
+      return leftVal < rightVal;
+    case '<=':
+      return leftVal <= rightVal;
+    case '>':
+      return leftVal > rightVal;
+    case '>=':
+      return leftVal >= rightVal;
+    case '<<':
+      return leftVal << rightVal; // eslint-disable-line no-bitwise
+    case '>>':
+      return leftVal >> rightVal; // eslint-disable-line no-bitwise
+    case '>>>':
+      return leftVal >>> rightVal; // eslint-disable-line no-bitwise
+    case '+':
+      return leftVal + rightVal;
+    case '-':
+      return leftVal - rightVal;
+    case '*':
+      return leftVal * rightVal;
+    case '/':
+      return leftVal / rightVal;
+    case '%':
+      return leftVal % rightVal;
+    case '|':
+      return leftVal | rightVal; // eslint-disable-line no-bitwise
+    case '^':
+      return leftVal ^ rightVal; // eslint-disable-line no-bitwise
+    case '&':
+      return leftVal & rightVal; // eslint-disable-line no-bitwise
+    case 'in':
+      try {
+        return leftVal in rightVal;
+      } catch (err) {
+        return false;
+      }
+    case 'instanceof':
+      if (typeof rightVal !== 'function') {
+        return false;
+      }
+      return leftVal instanceof rightVal;
+    default:
+      return undefined;
+  }
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/BindExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/BindExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/BindExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,24 @@
+/**
+ * Extractor function for a BindExpression type value node.
+ * A bind expression looks like `::this.foo`
+ * This will return `this.foo.bind(this)` as the value to indicate its existence,
+ * since we can not execute the function this.foo.bind(this) in a static environment.
+ *
+ * @param - value - AST Value object with type `BindExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromBindExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const callee = getValue(value.callee);
+
+  // If value.object === null, the callee must be a MemberExpression.
+  // https://github.com/babel/babylon/blob/master/ast/spec.md#bindexpression
+  const object = value.object === null ? getValue(value.callee.object) : getValue(value.object);
+
+  if (value.object && value.object.property) {
+    return `${object}.${callee}.bind(${object})`;
+  }
+
+  return `${callee}.bind(${object})`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/CallExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/CallExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/CallExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,15 @@
+/**
+ * Extractor function for a CallExpression type value node.
+ * A call expression looks like `bar()`
+ * This will return `bar` as the value to indicate its existence,
+ * since we can not execute the function bar in a static environment.
+ *
+ * @param - value - AST Value object with type `CallExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromCallExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const args = Array.isArray(value.arguments) ? value.arguments.map((x) => getValue(x)).join(', ') : '';
+  return `${getValue(value.callee)}${value.optional ? '?.' : ''}(${args})`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/ChainExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/ChainExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/ChainExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a ChainExpression type value node.
+ * A member expression is accessing a property on an object `obj.property`.
+ *
+ * @param - value - AST Value object with type `ChainExpression`
+ * @returns - The extracted value converted to correct type
+ *  and maintaing `obj?.property` convention.
+ */
+export default function extractValueFromChainExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return getValue(value.expression || value);
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/ConditionalExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/ConditionalExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/ConditionalExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,17 @@
+/**
+ * Extractor function for a ConditionalExpression type value node.
+ *
+ * @param - value - AST Value object with type `ConditionalExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromConditionalExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const {
+    test,
+    alternate,
+    consequent,
+  } = value;
+
+  return getValue(test) ? getValue(consequent) : getValue(alternate);
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/FunctionExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/FunctionExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/FunctionExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,11 @@
+/**
+ * Extractor function for a FunctionExpression type value node.
+ * Statically, we can't execute the given function, so just return a function
+ * to indicate that the value is present.
+ *
+ * @param - value - AST Value object with type `FunctionExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromFunctionExpression(value) {
+  return () => value;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/Identifier.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/Identifier.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/Identifier.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,28 @@
+const JS_RESERVED = {
+  Array,
+  Date,
+  Infinity,
+  Math,
+  Number,
+  Object,
+  String,
+  undefined,
+};
+
+/**
+ * Extractor function for a Identifier type value node.
+ * An Identifier is usually a reference to a variable.
+ * Just return variable name to determine its existence.
+ *
+ * @param - value - AST Value object with type `Identifier`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromIdentifier(value) {
+  const { name } = value;
+
+  if (Object.hasOwnProperty.call(JS_RESERVED, name)) {
+    return JS_RESERVED[name];
+  }
+
+  return name;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/LogicalExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/LogicalExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/LogicalExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,24 @@
+/**
+ * Extractor function for a LogicalExpression type value node.
+ * A logical expression is `a && b` or `a || b`, so we evaluate both sides
+ * and return the extracted value of the expression.
+ *
+ * @param - value - AST Value object with type `LogicalExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromLogicalExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const { operator, left, right } = value;
+  const leftVal = getValue(left);
+  const rightVal = getValue(right);
+
+  if (operator === '&&') {
+    return leftVal && rightVal;
+  }
+  if (operator === '??') {
+    // return leftVal ?? rightVal; // TODO: update to babel 7
+    return (leftVal === null || typeof leftVal === 'undefined') ? rightVal : leftVal;
+  }
+  return leftVal || rightVal;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/MemberExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/MemberExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/MemberExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a MemberExpression type value node.
+ * A member expression is accessing a property on an object `obj.property`.
+ *
+ * @param - value - AST Value object with type `MemberExpression`
+ * @returns - The extracted value converted to correct type
+ *  and maintaing `obj.property` convention.
+ */
+export default function extractValueFromMemberExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return `${getValue(value.object)}${value.optional ? '?.' : '.'}${getValue(value.property)}`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/NewExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/NewExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/NewExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+/**
+ * Extractor function for a NewExpression type value node.
+ * A new expression instantiates an object with `new` keyword.
+ *
+ * @returns - an empty object.
+ */
+export default function extractValueFromNewExpression() {
+  return new Object(); // eslint-disable-line
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/ObjectExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/ObjectExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/ObjectExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+import assign from 'object.assign';
+
+/**
+ * Extractor function for an ObjectExpression type value node.
+ * An object expression is using {}.
+ *
+ * @returns - a representation of the object
+ */
+export default function extractValueFromObjectExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return value.properties.reduce((obj, property) => {
+    // Support types: SpreadProperty and ExperimentalSpreadProperty
+    if (/^(?:Experimental)?Spread(?:Property|Element)$/.test(property.type)) {
+      if (property.argument.type === 'ObjectExpression') {
+        return assign({}, obj, extractValueFromObjectExpression(property.argument));
+      }
+    } else {
+      return assign({}, obj, { [getValue(property.key)]: getValue(property.value) });
+    }
+    return obj;
+  }, {});
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/OptionalCallExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/OptionalCallExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/OptionalCallExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a OptionalCallExpression type value node.
+ * A member expression is accessing a property on an object `obj.property` and invoking it.
+ *
+ * @param - value - AST Value object with type `OptionalCallExpression`
+ * @returns - The extracted value converted to correct type
+ *  and maintaing `obj.property?.()` convention.
+ */
+export default function extractValueFromOptionalCallExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return `${getValue(value.callee)}?.(${value.arguments.map((x) => getValue(x)).join(', ')})`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/OptionalMemberExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/OptionalMemberExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/OptionalMemberExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a OptionalMemberExpression type value node.
+ * A member expression is accessing a property on an object `obj.property`.
+ *
+ * @param - value - AST Value object with type `OptionalMemberExpression`
+ * @returns - The extracted value converted to correct type
+ *  and maintaing `obj?.property` convention.
+ */
+export default function extractValueFromOptionalMemberExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return `${getValue(value.object)}?.${getValue(value.property)}`;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/SequenceExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/SequenceExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/SequenceExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a SequenceExpression type value node.
+ * A Sequence expression is an object with an attribute named
+ * expressions which contains an array of different types
+ *  of expression objects.
+ *
+ * @returns - An array of the extracted elements.
+ */
+export default function extractValueFromSequenceExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return value.expressions.map((element) => getValue(element));
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/SpreadElement.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/SpreadElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/SpreadElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,11 @@
+/**
+ * Extractor function for a SpreadElement type value node.
+ * We can't statically evaluate an array spread, so just return
+ * undefined.
+ *
+ * @param - value - AST Value object with type `SpreadElement`
+ * @returns - An prototypeless object.
+ */
+export default function extractValueFromSpreadElement() {
+  return undefined;
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/TSNonNullExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/TSNonNullExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/TSNonNullExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,97 @@
+const extractValueFromThisExpression = require('./ThisExpression').default;
+const extractValueFromCallExpression = require('./CallExpression').default;
+
+function navigate(obj, prop, value) {
+  if (value.computed) {
+    return value.optional ? `${obj}?.[${prop}]` : `${obj}[${prop}]`;
+  }
+  return value.optional ? `${obj}?.${prop}` : `${obj}.${prop}`;
+}
+
+/**
+ * Extractor function for a TSNonNullExpression type value node.
+ * A TSNonNullExpression is accessing a TypeScript Non-Null Assertion
+ * Operator !
+ *
+ * @param - value - AST Value object with type `TSNonNullExpression`
+ * @returns - The extracted value converted to correct type
+ *  and maintaing `obj.property` convention.
+ */
+export default function extractValueFromTSNonNullExpression(value) {
+  // eslint-disable-next-line global-require
+  // const getValue = require('.').default;
+  const errorMessage = 'The prop value with an expression type of TSNonNullExpression could not be resolved. Please file an issue ( https://github.com/jsx-eslint/jsx-ast-utils/issues/new ) to get this fixed immediately.';
+
+  // it's just the name
+  if (value.type === 'Identifier') {
+    const { name } = value;
+    return name;
+  }
+
+  if (value.type === 'Literal') {
+    return value.value;
+  }
+
+  if (value.type === 'TSAsExpression') {
+    return extractValueFromTSNonNullExpression(value.expression);
+  }
+
+  if (value.type === 'CallExpression') {
+    return extractValueFromCallExpression(value);
+  }
+
+  if (value.type === 'ThisExpression') {
+    return extractValueFromThisExpression();
+  }
+
+  // does not contains properties & is not parenthesized
+  if (value.type === 'TSNonNullExpression' && (!value.extra || value.extra.parenthesized === false)) {
+    const { expression } = value;
+    return `${extractValueFromTSNonNullExpression(expression)}${'!'}`;
+  }
+
+  // does not contains properties & is parenthesized
+  if (value.type === 'TSNonNullExpression' && value.extra && value.extra.parenthesized === true) {
+    const { expression } = value;
+    return `${'('}${extractValueFromTSNonNullExpression(expression)}${'!'}${')'}`;
+  }
+
+  if (value.type === 'MemberExpression') {
+    // contains a property & is not parenthesized
+    if ((!value.extra || value.extra.parenthesized === false)) {
+      return navigate(
+        extractValueFromTSNonNullExpression(value.object),
+        extractValueFromTSNonNullExpression(value.property),
+        value,
+      );
+    }
+
+    // contains a property & is parenthesized
+    if (value.extra && value.extra.parenthesized === true) {
+      const result = navigate(
+        extractValueFromTSNonNullExpression(value.object),
+        extractValueFromTSNonNullExpression(value.property),
+        value,
+      );
+      return `(${result})`;
+    }
+  }
+
+  // try to fail silently, if specs for TSNonNullExpression change
+  // not throw, only log error. Similar to how it was done previously
+  if (value.expression) {
+    let { expression } = value;
+    while (expression) {
+      if (expression.type === 'Identifier') {
+        // eslint-disable-next-line no-console
+        console.error(errorMessage);
+        return expression.name;
+      }
+      ({ expression } = expression);
+    }
+  }
+
+  // eslint-disable-next-line no-console
+  console.error(errorMessage);
+  return '';
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/TaggedTemplateExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/TaggedTemplateExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/TaggedTemplateExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+import extractValueFromTemplateLiteral from './TemplateLiteral';
+
+/**
+ * Returns the string value of a tagged template literal object.
+ * Redirects the bulk of the work to `TemplateLiteral`.
+ */
+export default function extractValueFromTaggedTemplateExpression(value) {
+  return extractValueFromTemplateLiteral(value.quasi);
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/TemplateLiteral.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/TemplateLiteral.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/TemplateLiteral.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,35 @@
+function sortStarts(a, b) {
+  return (a.range ? a.range[0] : a.start) - (b.range ? b.range[0] : b.start);
+}
+
+/**
+ * Returns the string value of a template literal object.
+ * Tries to build it as best as it can based on the passed
+ * prop. For instance `This is a ${prop}` will return 'This is a {prop}'.
+ *
+ * If the template literal builds to undefined (`${undefined}`), then
+ * this should return "undefined".
+ */
+export default function extractValueFromTemplateLiteral(value) {
+  const {
+    quasis,
+    expressions,
+  } = value;
+  const partitions = quasis.concat(expressions);
+
+  return partitions.sort(sortStarts).map(({ type, value: { raw } = {}, name }) => {
+    if (type === 'TemplateElement') {
+      return raw;
+    }
+
+    if (type === 'Identifier') {
+      return name === 'undefined' ? name : `{${name}}`;
+    }
+
+    if (type.indexOf('Expression') > -1) {
+      return `{${type}}`;
+    }
+
+    return '';
+  }).join('');
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/ThisExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/ThisExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/ThisExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+/**
+ * Extractor function for a ThisExpression type value node.
+ * A this expression is using `this` as an identifier.
+ *
+ * @returns - 'this' as a string.
+ */
+export default function extractValueFromThisExpression() {
+  return 'this';
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/TypeCastExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/TypeCastExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/TypeCastExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+/**
+ * Extractor function for a TypeCastExpression type value node.
+ * A type cast expression looks like `(this.handleClick: (event: MouseEvent) => void))`
+ * This will return the expression `this.handleClick`.
+ *
+ * @param - value - AST Value object with type `TypeCastExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromTypeCastExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  return getValue(value.expression);
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/UnaryExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/UnaryExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/UnaryExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,31 @@
+/**
+ * Extractor function for a UnaryExpression type value node.
+ * A unary expression is an expression with a unary operator.
+ * For example, !"foobar" will evaluate to false, so this will return false.
+ *
+ * @param - value - AST Value object with type `UnaryExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromUnaryExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const { operator, argument } = value;
+
+  switch (operator) {
+    case '-':
+      return -getValue(argument);
+    case '+':
+      return +getValue(argument); // eslint-disable-line no-implicit-coercion
+    case '!':
+      return !getValue(argument);
+    case '~':
+      return ~getValue(argument); // eslint-disable-line no-bitwise
+    case 'delete':
+      // I believe delete statements evaluate to true.
+      return true;
+    case 'typeof':
+    case 'void':
+    default:
+      return undefined;
+  }
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/UpdateExpression.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/UpdateExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/UpdateExpression.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,24 @@
+/**
+ * Extractor function for an UpdateExpression type value node.
+ * An update expression is an expression with an update operator.
+ * For example, foo++ will evaluate to foo + 1.
+ *
+ * @param - value - AST Value object with type `UpdateExpression`
+ * @returns - The extracted value converted to correct type.
+ */
+export default function extractValueFromUpdateExpression(value) {
+  // eslint-disable-next-line global-require
+  const getValue = require('.').default;
+  const { operator, argument, prefix } = value;
+
+  let val = getValue(argument);
+
+  switch (operator) {
+    case '++':
+      return prefix ? ++val : val++; // eslint-disable-line no-plusplus
+    case '--':
+      return prefix ? --val : val--; // eslint-disable-line no-plusplus
+    default:
+      return undefined;
+  }
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/expressions/index.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/expressions/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/expressions/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,184 @@
+import Literal from '../Literal';
+import JSXElement from '../JSXElement';
+import JSXFragment from '../JSXFragment';
+import JSXText from '../JSXText';
+import Identifier from './Identifier';
+import TaggedTemplateExpression from './TaggedTemplateExpression';
+import TemplateLiteral from './TemplateLiteral';
+import FunctionExpression from './FunctionExpression';
+import LogicalExpression from './LogicalExpression';
+import MemberExpression from './MemberExpression';
+import ChainExpression from './ChainExpression';
+import OptionalCallExpression from './OptionalCallExpression';
+import OptionalMemberExpression from './OptionalMemberExpression';
+import CallExpression from './CallExpression';
+import UnaryExpression from './UnaryExpression';
+import ThisExpression from './ThisExpression';
+import ConditionalExpression from './ConditionalExpression';
+import BinaryExpression from './BinaryExpression';
+import ObjectExpression from './ObjectExpression';
+import NewExpression from './NewExpression';
+import UpdateExpression from './UpdateExpression';
+import ArrayExpression from './ArrayExpression';
+import BindExpression from './BindExpression';
+import SpreadElement from './SpreadElement';
+import TypeCastExpression from './TypeCastExpression';
+import SequenceExpression from './SequenceExpression';
+import TSNonNullExpression from './TSNonNullExpression';
+import AssignmentExpression from './AssignmentExpression';
+
+// Composition map of types to their extractor functions.
+const TYPES = {
+  Identifier,
+  Literal,
+  JSXElement,
+  JSXFragment,
+  JSXText,
+  TaggedTemplateExpression,
+  TemplateLiteral,
+  ArrowFunctionExpression: FunctionExpression,
+  FunctionExpression,
+  LogicalExpression,
+  MemberExpression,
+  ChainExpression,
+  OptionalCallExpression,
+  OptionalMemberExpression,
+  CallExpression,
+  UnaryExpression,
+  ThisExpression,
+  ConditionalExpression,
+  BinaryExpression,
+  ObjectExpression,
+  NewExpression,
+  UpdateExpression,
+  ArrayExpression,
+  BindExpression,
+  SpreadElement,
+  TypeCastExpression,
+  SequenceExpression,
+  TSNonNullExpression,
+  AssignmentExpression,
+};
+
+const noop = () => null;
+
+const errorMessage = (expression) => `The prop value with an expression type of ${expression} could not be resolved. Please file an issue ( https://github.com/jsx-eslint/jsx-ast-utils/issues/new ) to get this fixed immediately.`;
+
+/**
+ * This function maps an AST value node
+ * to its correct extractor function for its
+ * given type.
+ *
+ * This will map correctly for *all* possible expression types.
+ *
+ * @param - value - AST Value object with type `JSXExpressionContainer`
+ * @returns The extracted value.
+ */
+export default function extract(value) {
+  // Value will not have the expression property when we recurse.
+  // The type for expression on ArrowFunctionExpression is a boolean.
+  let expression;
+  if (
+    typeof value.expression !== 'boolean'
+    && value.expression
+  ) {
+    expression = value.expression; // eslint-disable-line prefer-destructuring
+  } else {
+    expression = value;
+  }
+  let { type } = expression;
+
+  // Typescript NonNull Expression is wrapped & it would end up in the wrong extractor
+  if (expression.object && expression.object.type === 'TSNonNullExpression') {
+    type = 'TSNonNullExpression';
+  }
+
+  while (type === 'TSAsExpression') {
+    ({ type } = expression);
+    if (expression.expression) {
+      ({ expression } = expression);
+    }
+  }
+
+  if (TYPES[type] === undefined) {
+    // eslint-disable-next-line no-console
+    console.error(errorMessage(type));
+    return null;
+  }
+
+  return TYPES[type](expression);
+}
+
+// Composition map of types to their extractor functions to handle literals.
+const LITERAL_TYPES = {
+  ...TYPES,
+  Literal: (value) => {
+    const extractedVal = TYPES.Literal.call(undefined, value);
+    const isNull = extractedVal === null;
+    // This will be convention for attributes that have null
+    // value explicitly defined (<div prop={null} /> maps to 'null').
+    return isNull ? 'null' : extractedVal;
+  },
+  Identifier: (value) => {
+    const isUndefined = TYPES.Identifier.call(undefined, value) === undefined;
+    return isUndefined ? undefined : null;
+  },
+  JSXElement: noop,
+  JSXFragment: noop,
+  JSXText: noop,
+  ArrowFunctionExpression: noop,
+  FunctionExpression: noop,
+  LogicalExpression: noop,
+  MemberExpression: noop,
+  OptionalCallExpression: noop,
+  OptionalMemberExpression: noop,
+  CallExpression: noop,
+  UnaryExpression: (value) => {
+    const extractedVal = TYPES.UnaryExpression.call(undefined, value);
+    return extractedVal === undefined ? null : extractedVal;
+  },
+  UpdateExpression: (value) => {
+    const extractedVal = TYPES.UpdateExpression.call(undefined, value);
+    return extractedVal === undefined ? null : extractedVal;
+  },
+  ThisExpression: noop,
+  ConditionalExpression: noop,
+  BinaryExpression: noop,
+  ObjectExpression: noop,
+  NewExpression: noop,
+  ArrayExpression: (value) => {
+    const extractedVal = TYPES.ArrayExpression.call(undefined, value);
+    return extractedVal.filter((val) => val !== null);
+  },
+  BindExpression: noop,
+  SpreadElement: noop,
+  TSNonNullExpression: noop,
+  TSAsExpression: noop,
+  TypeCastExpression: noop,
+  SequenceExpression: noop,
+  ChainExpression: noop,
+};
+
+/**
+ * This function maps an AST value node
+ * to its correct extractor function for its
+ * given type.
+ *
+ * This will map correctly for *some* possible types that map to literals.
+ *
+ * @param - value - AST Value object with type `JSXExpressionContainer`
+ * @returns The extracted value.
+ */
+export function extractLiteral(value) {
+  // Value will not have the expression property when we recurse.
+  const expression = value.expression || value;
+  const { type } = expression;
+
+  if (LITERAL_TYPES[type] === undefined) {
+    // eslint-disable-next-line no-console
+    console.error(errorMessage(type));
+    return null;
+  }
+
+  return LITERAL_TYPES[type](expression);
+}
Index: frontend/node_modules/jsx-ast-utils/src/values/index.js
===================================================================
--- frontend/node_modules/jsx-ast-utils/src/values/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/jsx-ast-utils/src/values/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,48 @@
+import Literal from './Literal';
+import JSXElement from './JSXElement';
+import JSXText from './JSXText';
+import JSXFragment from './JSXFragment';
+import JSXExpressionContainer, { extractLiteral } from './expressions';
+
+// Composition map of types to their extractor functions.
+const TYPES = {
+  Literal,
+  JSXElement,
+  JSXExpressionContainer,
+  JSXText,
+  JSXFragment,
+};
+
+// Composition map of types to their extractor functions to handle literals.
+const LITERAL_TYPES = {
+  ...TYPES,
+  JSXElement: () => null,
+  JSXExpressionContainer: extractLiteral,
+};
+
+/**
+ * This function maps an AST value node
+ * to its correct extractor function for its
+ * given type.
+ *
+ * This will map correctly for *all* possible types.
+ *
+ * @param value - AST Value object on a JSX Attribute.
+ */
+export default function getValue(value) {
+  if (!TYPES[value.type]) console.log(value.type);
+  return TYPES[value.type](value);
+}
+
+/**
+ * This function maps an AST value node
+ * to its correct extractor function for its
+ * given type.
+ *
+ * This will map correctly for *some* possible types that map to literals.
+ *
+ * @param value - AST Value object on a JSX Attribute.
+ */
+export function getLiteralValue(value) {
+  return LITERAL_TYPES[value.type](value);
+}
