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;
}