function lintStyleFunction()

in packages/eslint-plugin-baseui/src/deprecated-theme-api.js [301:434]


function lintStyleFunction(context, node) {
  // This should handle the shared logic for validating a "style function"
  // passed to styled, withStyle, or an overrides style property.
  //   - Ex: styled('div', () => {})
  //   - Ex: withStyle(<Foo />, () => {})
  //   - Ex: <Foo overrides={{ Root: { style: () => {} }}} />
  // Return true if current node should be flagged, false otherwise.

  const ancestors = context.getAncestors();
  const scope = context.getScope();
  const themeProperty = deprecatedThemeProperties[node.name];

  const passedToStyledOrWithStyle =
    scope.type === 'function' &&
    scope.block.parent.type === 'CallExpression' &&
    ['styled', 'withStyle'].includes(scope.block.parent.callee.name) &&
    scope.block.parent.arguments[1] === scope.block;

  const passedToOverrides =
    scope.type === 'function' &&
    scope.block.parent.type === 'Property' &&
    scope.block.parent.key.name === 'style' &&
    ancestors.some((node) => node.type === 'JSXAttribute' && node.name.name === 'overrides');

  if (!passedToStyledOrWithStyle && !passedToOverrides) {
    return false;
  }

  // Only one parameter should be passed to a style function.
  const parameter = scope.block.params[0];

  // nothing is passed to the `styled` function
  if (!parameter) {
    return false;
  }

  // Option 1. No destructuring.
  // Ex: props => ({ color: props.$theme.colors.foreground })
  if (parameter.type === 'Identifier') {
    if (
      node.parent.type === 'MemberExpression' &&
      node.parent.object.type === 'MemberExpression' &&
      node.parent.object.property.name === themeProperty.concern &&
      node.parent.object.object.type === 'MemberExpression' &&
      node.parent.object.object.property.name === '$theme' &&
      node.parent.object.object.object.type === 'Identifier' &&
      node.parent.object.object.object.name === parameter.name
    ) {
      // We have verified that the identifier accesses the theme.
      // Ex: props.$theme.colors.foreground
      return true;
    }
  }

  if (parameter.type === 'ObjectPattern') {
    // Our parameter is being destructured.
    const $themeProperty = parameter.properties.find((property) => property.key.name === '$theme');

    // the styled function is not using the theme
    if (!$themeProperty) {
      return false;
    }

    // Option 2. Destructuring $theme in parameters.
    // ({$theme}) => ({ color: $theme.colors.foreground })
    if ($themeProperty.value.type === 'Identifier' && $themeProperty.value.name === '$theme') {
      if (
        node.parent.type === 'MemberExpression' &&
        node.parent.object.type === 'MemberExpression' &&
        node.parent.object.property.name === themeProperty.concern &&
        node.parent.object.object.type === 'Identifier' &&
        node.parent.object.object.name === '$theme'
      ) {
        // We have verified that the identifier accesses $theme.
        // Ex: $theme.colors.foreground
        return true;
      }
    }

    // Account for nested destructuring.
    if ($themeProperty.value.type === 'ObjectPattern') {
      const concernPropertyNode = $themeProperty.value.properties.find(
        (property) => property.key.name === themeProperty.concern
      );

      // Option 3. Nested destructuring of a "concern" in parameters.
      // ({$theme: {colors}}) => ({ color: colors.foreground })
      if (
        concernPropertyNode &&
        concernPropertyNode.value.type === 'Identifier' &&
        node.parent.type === 'MemberExpression' &&
        node.parent.object.type === 'Identifier' &&
        node.parent.object.name === themeProperty.concern
      ) {
        // We have verified that the identifier accesses the "concern".
        // Ex: colors.foreground
        return true;
      }

      // Option 4. Nested destructuring of the deprecated theme property.
      // ({$theme: {colors: {foreground}}}) => ({ color: foreground })
      if (concernPropertyNode && concernPropertyNode.value.type === 'ObjectPattern') {
        const deprecatedProperty = concernPropertyNode.value.properties.find(
          (property) => property.key.name === node.name
        );
        if (deprecatedProperty) {
          // We have verified that the identifier is destructured in
          // the parameters of this function.

          // Here is a map of the possible destructuring:
          // ({$theme: {colors: {foreground: foreground}}}) => ({ color: foreground })
          //                     ^^^^^^^^^^  ^^^^^^^^^^                  ^^^^^^^^^^
          //                     key         value                       reference

          // Given the above map, here is the final criteria to consider before we flag the node:
          //   - If the current node is the key, we want to flag it.
          //   - If the current node is the value, we ignore it.
          //   - Reaching here means the node is a reference.
          //   - If the current node is part of a member expression, we ignore it. (foo.foreground)
          //   - Finally! We can flag this node.
          if (
            node === deprecatedProperty.key ||
            (node !== deprecatedProperty.value && node.parent.type !== 'MemberExpression')
          ) {
            return true;
          }
        }
      }
    }
  }

  // If we've reached here then we can't flag anything.
  return false;
}