UNPKG

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