eslint-plugin-styled-components-a11y
Version:
This plugin adds the ability to lint styled components according to the rules outlined in eslint-plugin-jsx-a11y.
292 lines (276 loc) • 18.2 kB
JavaScript
"use strict";
/**
*
* @param {import('estree').Expression} node
* @returns {node is import('estree').CallExpression}
*/
var isStyledCallExpression = function isStyledCallExpression(tag) {
return (tag === null || tag === void 0 ? void 0 : tag.type) === 'CallExpression';
};
var isStyledFunc = function isStyledFunc(node) {
var _node$tag$callee;
return ((_node$tag$callee = node.tag.callee) === null || _node$tag$callee === void 0 ? void 0 : _node$tag$callee.name) === 'styled';
};
var isStyledFuncComponentArgument = function isStyledFuncComponentArgument(node) {
var _node$tag$arguments, _node$tag$arguments$;
return isStyledFunc(node) && ((_node$tag$arguments = node.tag.arguments) === null || _node$tag$arguments === void 0 ? void 0 : (_node$tag$arguments$ = _node$tag$arguments[0]) === null || _node$tag$arguments$ === void 0 ? void 0 : _node$tag$arguments$.type) === 'Identifier';
};
var isStyledFuncStringArgument = function isStyledFuncStringArgument(node) {
var _node$tag$arguments2, _node$tag$arguments2$;
return isStyledFunc(node) && ((_node$tag$arguments2 = node.tag.arguments) === null || _node$tag$arguments2 === void 0 ? void 0 : (_node$tag$arguments2$ = _node$tag$arguments2[0]) === null || _node$tag$arguments2$ === void 0 ? void 0 : _node$tag$arguments2$.type) === 'Literal';
};
var isStyledFuncWithAttrs = function isStyledFuncWithAttrs(node) {
var _node$tag$callee2, _node$tag$callee2$obj, _node$tag$callee2$obj2;
return ((_node$tag$callee2 = node.tag.callee) === null || _node$tag$callee2 === void 0 ? void 0 : (_node$tag$callee2$obj = _node$tag$callee2.object) === null || _node$tag$callee2$obj === void 0 ? void 0 : (_node$tag$callee2$obj2 = _node$tag$callee2$obj.callee) === null || _node$tag$callee2$obj2 === void 0 ? void 0 : _node$tag$callee2$obj2.name) === 'styled' && isAttrs(node);
};
var isStyledStringArgumentFuncWithAttrs = function isStyledStringArgumentFuncWithAttrs(node) {
var _node$tag$callee3, _node$tag$callee3$obj, _node$tag$callee3$obj2, _node$tag$callee3$obj3;
return isStyledFuncWithAttrs(node) && ((_node$tag$callee3 = node.tag.callee) === null || _node$tag$callee3 === void 0 ? void 0 : (_node$tag$callee3$obj = _node$tag$callee3.object) === null || _node$tag$callee3$obj === void 0 ? void 0 : (_node$tag$callee3$obj2 = _node$tag$callee3$obj.arguments) === null || _node$tag$callee3$obj2 === void 0 ? void 0 : (_node$tag$callee3$obj3 = _node$tag$callee3$obj2[0]) === null || _node$tag$callee3$obj3 === void 0 ? void 0 : _node$tag$callee3$obj3.type) === 'Literal';
};
var isStyledComponentArgumentFuncWithAttrs = function isStyledComponentArgumentFuncWithAttrs(node) {
var _node$tag$callee4, _node$tag$callee4$obj, _node$tag$callee4$obj2, _node$tag$callee4$obj3;
return isStyledFuncWithAttrs(node) && ((_node$tag$callee4 = node.tag.callee) === null || _node$tag$callee4 === void 0 ? void 0 : (_node$tag$callee4$obj = _node$tag$callee4.object) === null || _node$tag$callee4$obj === void 0 ? void 0 : (_node$tag$callee4$obj2 = _node$tag$callee4$obj.arguments) === null || _node$tag$callee4$obj2 === void 0 ? void 0 : (_node$tag$callee4$obj3 = _node$tag$callee4$obj2[0]) === null || _node$tag$callee4$obj3 === void 0 ? void 0 : _node$tag$callee4$obj3.type) === 'Identifier';
};
var styledCallElementObjectMapArgumentTag = function styledCallElementObjectMapArgumentTag(node) {
var _node$tag, _node$tag$arguments3, _node$tag$arguments3$, _node$tag$arguments3$2;
return (_node$tag = node.tag) === null || _node$tag === void 0 ? void 0 : (_node$tag$arguments3 = _node$tag.arguments) === null || _node$tag$arguments3 === void 0 ? void 0 : (_node$tag$arguments3$ = _node$tag$arguments3[0]) === null || _node$tag$arguments3$ === void 0 ? void 0 : (_node$tag$arguments3$2 = _node$tag$arguments3$.property) === null || _node$tag$arguments3$2 === void 0 ? void 0 : _node$tag$arguments3$2.name;
};
/**
*
* @param {import('eslint').Rule.Node | null | undefined} node
* @returns {node is import('estree').Identifier}
*/
var isIdentifier = function isIdentifier(node) {
return (node === null || node === void 0 ? void 0 : node.type) === 'Identifier';
};
/**
*
* @param {import('eslint').Rule.Node | null | undefined} node
* @returns {node is import('estree').Identifier}
*/
var isStyledIdentifier = function isStyledIdentifier(node) {
return isIdentifier(node) && node.name === 'styled';
};
/**
*
* @param {import('estree').TaggedTemplateExpression | null | undefined} node
* @returns {boolean}
*/
var isPlainSTE = function isPlainSTE(node) {
var _node$tag2;
return node.tag.type === 'MemberExpression' && isStyledIdentifier((_node$tag2 = node.tag) === null || _node$tag2 === void 0 ? void 0 : _node$tag2.object);
};
var isAttrs = function isAttrs(_ref) {
var _tag$callee, _tag$callee$property;
var tag = _ref.tag;
return ((_tag$callee = tag.callee) === null || _tag$callee === void 0 ? void 0 : (_tag$callee$property = _tag$callee.property) === null || _tag$callee$property === void 0 ? void 0 : _tag$callee$property.name) === 'attrs';
};
var getAttrsType = function getAttrsType(node) {
var _node$tag3, _node$tag3$arguments, _node$tag3$arguments$;
var type = (_node$tag3 = node.tag) === null || _node$tag3 === void 0 ? void 0 : (_node$tag3$arguments = _node$tag3.arguments) === null || _node$tag3$arguments === void 0 ? void 0 : (_node$tag3$arguments$ = _node$tag3$arguments[0]) === null || _node$tag3$arguments$ === void 0 ? void 0 : _node$tag3$arguments$.type;
return type === 'FunctionExpression' ? 'func' : type === 'ArrowFunctionExpression' ? 'arrow' : type === 'ObjectExpression' ? 'object' : '';
};
var _require = require('util'),
inspect = _require.inspect;
var _require2 = require('./constants'),
__UNKNOWN_IDENTIFER__ = _require2.__UNKNOWN_IDENTIFER__;
/**
*
* @param {Record<string, {name: string; attrs: any[]; tag: string}>} styledComponentsDict
* @param {import('eslint').Rule.RuleContext} context
* @param {string} name
* @returns {import('eslint').Rule.RuleListener}
*/
module.exports = function (styledComponentsDict, context, name) {
var _context$settings$jsx, _context$settings, _context$settings$jsx2;
/**
* enable checking custom components
* @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#usage
*/
var componentMap = (_context$settings$jsx = (_context$settings = context.settings) === null || _context$settings === void 0 ? void 0 : (_context$settings$jsx2 = _context$settings['jsx-a11y']) === null || _context$settings$jsx2 === void 0 ? void 0 : _context$settings$jsx2.components) !== null && _context$settings$jsx !== void 0 ? _context$settings$jsx : {};
return {
CallExpression: function CallExpression(node) {
try {
var _node$parent$id, _node$callee, _node$callee2;
// TODO: Consider supporting more complex parent name definitions (e.g. object keys)
var styledComponentName = node.parent && 'id' in node.parent && ((_node$parent$id = node.parent.id) === null || _node$parent$id === void 0 ? void 0 : _node$parent$id.type) === 'Identifier' ? node.parent.id.name : null;
if (!styledComponentName) {
var _node$parent$parent, _node$parent$parent$p;
// const Components = { Component: styled.div({ ... }) }
if (node.parent.type === 'Property' && isIdentifier(node.parent.key) && node.parent.key.name && ((_node$parent$parent = node.parent.parent) === null || _node$parent$parent === void 0 ? void 0 : (_node$parent$parent$p = _node$parent$parent.parent) === null || _node$parent$parent$p === void 0 ? void 0 : _node$parent$parent$p.type) === 'VariableDeclarator' && isIdentifier(node.parent.parent.parent.id) && node.parent.parent.parent.id.name) {
styledComponentName = "".concat(node.parent.parent.parent.id.name, ".").concat(node.parent.key.name);
} else {
return;
}
}
var tag = '';
if (!styledComponentName) return;
// styled.?({ ... })
if (((_node$callee = node.callee) === null || _node$callee === void 0 ? void 0 : _node$callee.type) === 'MemberExpression' && isStyledIdentifier(node.callee.object)) {
var _node$callee$property;
// styled.div({ ... })
if (((_node$callee$property = node.callee.property) === null || _node$callee$property === void 0 ? void 0 : _node$callee$property.type) === 'Identifier' && node.callee.property.name) {
tag = node.callee.property.name;
if (!tag) return;
styledComponentsDict[styledComponentName] = {
name: styledComponentName,
attrs: [],
tag: tag
};
}
}
// styled(...)(...)
if (((_node$callee2 = node.callee) === null || _node$callee2 === void 0 ? void 0 : _node$callee2.type) === 'CallExpression' && isStyledIdentifier(node.callee.callee)) {
var arg = node.callee.arguments[0];
if (!arg) return;
// styled('div')(...)
if (arg.type === 'Literal') {
tag = arg.value;
if (!tag) return;
styledComponentsDict[styledComponentName] = {
name: styledComponentName,
attrs: [],
tag: tag
};
}
// TODO: Consider supporting templates like styled(`div`)(...)
if (arg.type !== 'Identifier') return;
// styled(StyledComponent)({ ... })
var attrs = [];
var ancestorScName = arg.name;
if (styledComponentsDict[ancestorScName]) {
// Add attrs if the ancestor has them
attrs = styledComponentsDict[ancestorScName].attrs;
tag = styledComponentsDict[ancestorScName].tag;
}
// styled(CustomComponent)({ ...})
if (componentMap[ancestorScName]) {
tag = componentMap[ancestorScName];
}
if (!tag) return;
styledComponentsDict[styledComponentName] = {
name: styledComponentName,
attrs: attrs,
tag: tag
};
}
} catch (error) {
context.report({
message: 'Unable to parse styled component: {{ message }}',
node: node,
data: {
message: error.message,
stack: error.stack
}
});
}
},
TaggedTemplateExpression: function TaggedTemplateExpression(node) {
var _node$tag5;
var func = function func(inspectee) {
return name.includes('html-has-lang') && context.report(node, "made it here: ".concat(inspect(inspectee || node)));
};
var scName = node.parent.id && node.parent.id.name;
if (!scName) {
var _node$tag4, _node$parent$parent2, _node$parent$parent2$;
// const Components = { Component: styled.div`` }
if (node.tag.type === 'MemberExpression' && isStyledIdentifier((_node$tag4 = node.tag) === null || _node$tag4 === void 0 ? void 0 : _node$tag4.object) && node.parent.type === 'Property' && isIdentifier(node.parent.key) && node.parent.key.name && ((_node$parent$parent2 = node.parent.parent) === null || _node$parent$parent2 === void 0 ? void 0 : (_node$parent$parent2$ = _node$parent$parent2.parent) === null || _node$parent$parent2$ === void 0 ? void 0 : _node$parent$parent2$.type) === 'VariableDeclarator' && isIdentifier(node.parent.parent.parent.id) && node.parent.parent.parent.id.name) {
scName = "".concat(node.parent.parent.parent.id.name, ".").concat(node.parent.key.name);
} else {
return;
}
}
var attrs = [];
var tag = '';
// styled(Component)`` || styled.div.attrs(...)`` || styled('div')``
if (isStyledCallExpression(node.tag)) {
// styled(animated.div)``
if (styledCallElementObjectMapArgumentTag(node)) {
tag = styledCallElementObjectMapArgumentTag(node);
}
// styled('div')``;
else if (isStyledFuncStringArgument(node)) {
var _node$tag$arguments4, _node$tag$arguments4$;
tag = ((_node$tag$arguments4 = node.tag.arguments) === null || _node$tag$arguments4 === void 0 ? void 0 : (_node$tag$arguments4$ = _node$tag$arguments4[0]) === null || _node$tag$arguments4$ === void 0 ? void 0 : _node$tag$arguments4$.value) || '';
}
// styled(Component)`` || styled(Component).attrs(...)``
if (isStyledFuncComponentArgument(node) || isStyledComponentArgumentFuncWithAttrs(node)) {
var ancestorScName = isStyledFuncComponentArgument(node) ? node.tag.arguments[0].name : node.tag.callee.object.arguments[0].name;
// styled(StyledComponent)`` || styled(StyledComponent).attrs(...)``
if (styledComponentsDict[ancestorScName]) {
attrs = styledComponentsDict[ancestorScName].attrs;
tag = styledComponentsDict[ancestorScName].tag;
}
// styled(CustomComponent)`` || styled(CustomComponent).attrs(...)``
if (componentMap[ancestorScName]) {
tag = componentMap[ancestorScName];
}
}
// styled.div.attrs(...)`` || styled(Component).attrs(...)`` || styled('div').attrs(...)``
if (isAttrs(node) || isStyledFuncWithAttrs(node)) {
var attrsPropertiesArr = [];
var attrsNode = node.tag.arguments[0];
if (isStyledStringArgumentFuncWithAttrs(node)) {
var _node$tag$callee5, _node$tag$callee5$obj, _node$tag$callee5$obj2, _node$tag$callee5$obj3;
tag = (_node$tag$callee5 = node.tag.callee) === null || _node$tag$callee5 === void 0 ? void 0 : (_node$tag$callee5$obj = _node$tag$callee5.object) === null || _node$tag$callee5$obj === void 0 ? void 0 : (_node$tag$callee5$obj2 = _node$tag$callee5$obj.arguments) === null || _node$tag$callee5$obj2 === void 0 ? void 0 : (_node$tag$callee5$obj3 = _node$tag$callee5$obj2[0]) === null || _node$tag$callee5$obj3 === void 0 ? void 0 : _node$tag$callee5$obj3.value;
} else if (!isStyledComponentArgumentFuncWithAttrs(node)) {
var _node$tag$callee$obje;
tag = (_node$tag$callee$obje = node.tag.callee.object.property) === null || _node$tag$callee$obje === void 0 ? void 0 : _node$tag$callee$obje.name;
}
var attrsType = getAttrsType(node);
if (!tag || !attrsType) return;
// styled.div.attrs(function() { return {} })``
// TODO all these empty array defaults are a temp fix. Should get a better way of actually trying to see what
// is returned from function attrs in the case they aren't just simple immediate returns, e.g., if else statements
if (attrsType === 'arrow') {
var _attrsNode$body;
attrsPropertiesArr = (attrsNode === null || attrsNode === void 0 ? void 0 : (_attrsNode$body = attrsNode.body) === null || _attrsNode$body === void 0 ? void 0 : _attrsNode$body.properties) || [];
// styled.div.attrs(() => ({}))``
} else if (attrsType === 'func') {
var _attrsNode$body2, _attrsNode$body2$body, _attrsNode$body2$body2, _attrsNode$body2$body3;
attrsPropertiesArr = (attrsNode === null || attrsNode === void 0 ? void 0 : (_attrsNode$body2 = attrsNode.body) === null || _attrsNode$body2 === void 0 ? void 0 : (_attrsNode$body2$body = _attrsNode$body2.body) === null || _attrsNode$body2$body === void 0 ? void 0 : (_attrsNode$body2$body2 = _attrsNode$body2$body.find(function (x) {
return x.type === 'ReturnStatement';
})) === null || _attrsNode$body2$body2 === void 0 ? void 0 : (_attrsNode$body2$body3 = _attrsNode$body2$body2.argument) === null || _attrsNode$body2$body3 === void 0 ? void 0 : _attrsNode$body2$body3.properties) || [];
// styled.div.attrs({})``
} else if (attrsType === 'object') {
attrsPropertiesArr = (attrsNode === null || attrsNode === void 0 ? void 0 : attrsNode.properties) || [];
}
var arithmeticUnaryOperators = ['+', '-'];
// filter out spread elements (which have no key nor value)
attrs = attrs.concat(attrsPropertiesArr.filter(function (x) {
return x.key;
}).map(function (x) {
return {
key: x.key.name || x.key.value,
// this is pretty useless. would need to generate code from any template expression for this to really work
value: x.value.type === 'TemplateLiteral' ?
// need to grab falsy vals like empty strings, thus the x ? x : identifier instead of x|| identifier
typeof x.value.quasis[0].value.raw === 'undefined' ? __UNKNOWN_IDENTIFER__ : x.value.quasis[0].value.raw : x.value.type === 'UnaryExpression' && arithmeticUnaryOperators.includes(x.value.operator) ?
// if simple arithemetic, concat the symbol and the strings (like a negative) and then coerce to a number
+(x.value.operator + x.value.argument.value) : x.value.type === 'Identifier' ? x.value.name === 'undefined' ? undefined : __UNKNOWN_IDENTIFER__ : typeof x.value.value === 'undefined' ?
// if property exists, but no value found, just set it to our unknown identifier so it returns truthy and not something specific like a number or boolean or undefined as these are tested in specific ways for different linting rules
// too many options for what this could be, but this can approxinate what is needed for linting
// need to grab falsy vals like empty strings, thus the x ? x : identifier instead of x|| identifier
__UNKNOWN_IDENTIFER__ : x.value.value
};
}));
}
styledComponentsDict[scName] = {
name: scName,
attrs: attrs,
tag: tag
};
}
// const A = styled.div``
if (node.tag.type === 'MemberExpression' && isStyledIdentifier((_node$tag5 = node.tag) === null || _node$tag5 === void 0 ? void 0 : _node$tag5.object)) {
tag = 'name' in node.tag.property ? node.tag.property.name : '';
if (!tag) return;
styledComponentsDict[scName] = {
name: scName,
tag: tag,
attrs: attrs
};
}
}
};
};