UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

232 lines (228 loc) 8.3 kB
import { isNodeOfType } from 'eslint-codemod-utils'; var getScope = function getScope(context, node) { if ('getScope' in context.sourceCode) { return context.sourceCode.getScope(node); } // this is needed for jira's eslint update, as otherwise there is type errors only in jira typechecking return context.getScope(node); }; var unsupportedSelectors = [':', // pseudo-classes/elements '[', // attribute selectors '>', // child combinator '+', // adjacent sibling combinator '~', // general sibling combinator ' ', // descendant combinator '*', // universal selector '#', // ID selector '.', // class selector '@', // at-rules '&', // parent selector '|', // namespace separator '^', // starts with '$', // ends with '=' // equals ]; function checkForPseudoClasses(node, objectExpression, context) { if (!isNodeOfType(objectExpression, 'ObjectExpression')) { return; } objectExpression.properties.forEach(function (prop) { if (isNodeOfType(prop, 'Property')) { // Check if property key is a pseudo-class var keyValue = null; if (isNodeOfType(prop.key, 'Literal')) { keyValue = prop.key.value; } else if (isNodeOfType(prop.key, 'Identifier')) { keyValue = prop.key.name; } if (keyValue && typeof keyValue === 'string' && unsupportedSelectors.some(function (selector) { return keyValue.includes(selector); })) { context.report({ node: prop.key, messageId: 'noPseudoClass', data: { pseudo: keyValue } }); } // Recursively check nested objects (for function return values) if (isNodeOfType(prop.value, 'ArrowFunctionExpression') || isNodeOfType(prop.value, 'FunctionExpression')) { // Check the function body for returned object expressions var body = prop.value.body; if (isNodeOfType(body, 'ObjectExpression')) { checkForPseudoClasses(node, body, context); } else if (isNodeOfType(body, 'BlockStatement')) { // Look for return statements body.body.forEach(function (stmt) { if (isNodeOfType(stmt, 'ReturnStatement') && stmt.argument && isNodeOfType(stmt.argument, 'ObjectExpression')) { checkForPseudoClasses(node, stmt.argument, context); } }); } } else if (isNodeOfType(prop.value, 'ObjectExpression')) { checkForPseudoClasses(node, prop.value, context); } } else if (isNodeOfType(prop, 'SpreadElement')) { // Handle spread elements like ...styles or ...conditionalStyles checkSpreadElement(node, prop, context); } }); } function checkSpreadElement(node, spreadElement, context) { if (!isNodeOfType(spreadElement, 'SpreadElement')) { return; } var argument = spreadElement.argument; // Handle direct identifier (e.g., ...styles) if (isNodeOfType(argument, 'Identifier')) { var scope = getScope(context, argument); var variable = null; var currentScope = scope; // Search through scope chain while (currentScope && !variable) { variable = currentScope.variables.find(function (v) { return v.name === argument.name; }); currentScope = currentScope.upper; } if (variable && variable.defs.length > 0) { var def = variable.defs[0]; if (isNodeOfType(def.node, 'VariableDeclarator') && def.node.init) { if (isNodeOfType(def.node.init, 'ObjectExpression')) { checkForPseudoClasses(node, def.node.init, context); } } } } // Handle conditional expressions (e.g., ...(condition ? { ':hover': ... } : undefined)) else if (isNodeOfType(argument, 'ConditionalExpression')) { // Check both consequent and alternate if (isNodeOfType(argument.consequent, 'ObjectExpression')) { checkForPseudoClasses(node, argument.consequent, context); } if (argument.alternate && isNodeOfType(argument.alternate, 'ObjectExpression')) { checkForPseudoClasses(node, argument.alternate, context); } } // Handle logical expressions (e.g., ...condition && { ':hover': ... }) else if (isNodeOfType(argument, 'LogicalExpression')) { if (isNodeOfType(argument.right, 'ObjectExpression')) { checkForPseudoClasses(node, argument.right, context); } if (isNodeOfType(argument.left, 'ObjectExpression')) { checkForPseudoClasses(node, argument.left, context); } } // Handle direct object expressions (e.g., ...{ ':hover': ... }) else if (isNodeOfType(argument, 'ObjectExpression')) { checkForPseudoClasses(node, argument, context); } } export function checkStylesObject(node, stylesValue, context) { if (!stylesValue) { return; } if (isNodeOfType(stylesValue, 'ObjectExpression')) { stylesValue.properties.forEach(function (prop) { if (!isNodeOfType(prop, 'Property') || !prop.value || !isNodeOfType(prop.value, 'ArrowFunctionExpression') && !isNodeOfType(prop.value, 'FunctionExpression')) { return; } var body = prop.value.body; if (isNodeOfType(body, 'ObjectExpression')) { checkForPseudoClasses(node, body, context); } else if (isNodeOfType(body, 'BlockStatement')) { var visitor = { ReturnStatement: function ReturnStatement(returnStmt) { if (returnStmt.argument && isNodeOfType(returnStmt.argument, 'ObjectExpression')) { checkForPseudoClasses(node, returnStmt.argument, context); } }, AssignmentExpression: function AssignmentExpression(assignExpr) { // Handle cases like styles[':hover'] = { ... } var left = assignExpr.left; if (!isNodeOfType(left, 'MemberExpression')) { return; } var property = left.property; if (!isNodeOfType(property, 'Literal')) { return; } var value = property.value; if (typeof value !== 'string') { return; } if (unsupportedSelectors.some(function (selector) { return value.includes(selector); })) { context.report({ node: property, messageId: 'noPseudoClass', data: { pseudo: value } }); } } }; body.body.forEach(function (stmt) { if (isNodeOfType(stmt, 'ReturnStatement')) { visitor.ReturnStatement(stmt); } else if (isNodeOfType(stmt, 'ExpressionStatement') && isNodeOfType(stmt.expression, 'AssignmentExpression')) { visitor.AssignmentExpression(stmt.expression); } else if (isNodeOfType(stmt, 'IfStatement')) { var checkBlock = function checkBlock(block) { if (isNodeOfType(block, 'BlockStatement')) { block.body.forEach(function (innerStmt) { if (isNodeOfType(innerStmt, 'ExpressionStatement') && isNodeOfType(innerStmt.expression, 'AssignmentExpression')) { visitor.AssignmentExpression(innerStmt.expression); } }); } else if (isNodeOfType(block, 'ExpressionStatement') && isNodeOfType(block.expression, 'AssignmentExpression')) { visitor.AssignmentExpression(block.expression); } }; if (stmt.consequent) { checkBlock(stmt.consequent); } if (stmt.alternate) { checkBlock(stmt.alternate); } } }); } }); } else if (isNodeOfType(stylesValue, 'Identifier')) { // track the variable var scope = getScope(context, stylesValue); var variable = null; var currentScope = scope; // Search through scope chain while (currentScope && !variable) { variable = currentScope.variables.find(function (v) { return v.name === stylesValue.name; }); currentScope = currentScope.upper; } if (variable && variable.defs.length > 0) { var def = variable.defs[0]; if (isNodeOfType(def.node, 'VariableDeclarator') && def.node.init) { checkStylesObject(node, def.node.init, context); } } } }