@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
232 lines (228 loc) • 8.3 kB
JavaScript
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);
}
}
}
}