UNPKG

eslint-plugin-styled-component-jsx-attributes

Version:

A simple plugin for enforcing the use of the id attribute on styled components.

164 lines (143 loc) 5.84 kB
'use strict'; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } var _require = require('../constants'), DEFAULT_TARGET_OPTION = _require.DEFAULT_TARGET_OPTION, DEFAULT_TARGET_CUSTOM_OPTION = _require.DEFAULT_TARGET_CUSTOM_OPTION, DEFAULT_PRIORITY_OVER_SPREAD_OPTION = _require.DEFAULT_PRIORITY_OVER_SPREAD_OPTION, DEFAULT_SUGGESTIONS_ENABLED = _require.DEFAULT_SUGGESTIONS_ENABLED; var _require2 = require('../helpers'), capitalizeWord = _require2.capitalizeWord, getAttribute = _require2.getAttribute, getAttributeValue = _require2.getAttributeValue; var formElements = ['button', 'input', 'select', 'textarea', 'option']; var materialElements = [// Form Elements 'NativeSelect', 'Select', 'MenuItem', 'Button', 'IconButton', 'Checkbox', 'Radio', 'Slider', 'Switch', 'TextField', 'Input', 'OutlinedInput', // Layout elements 'Modal']; module.exports = { meta: { type: 'suggestion', fixable: 'code', schema: { items: { type: 'object', properties: { target: { type: 'array', items: { "enum": ['all', 'form', 'material', 'none'] }, minItems: 1, uniqueItems: true, additionalItems: false }, targetCustom: { type: 'array', minItems: 1, uniqueItems: true }, suggestionsEnabled: { type: 'boolean' }, priorityOverSpread: { type: 'boolean' } }, additionalProperties: false } } }, create: function create(context, styledComponent) { var options = context.options[0]; var target = (options === null || options === void 0 ? void 0 : options.target) || DEFAULT_TARGET_OPTION; var targetCustom = (options === null || options === void 0 ? void 0 : options.targetCustom) || DEFAULT_TARGET_CUSTOM_OPTION; var priorityOverSpread = (options === null || options === void 0 ? void 0 : options.priorityOverSpread) !== undefined ? options === null || options === void 0 ? void 0 : options.priorityOverSpread : DEFAULT_PRIORITY_OVER_SPREAD_OPTION; return { JSXOpeningElement: function JSXOpeningElement(node) { var getNodeAttribute = function getNodeAttribute(attrName) { return getAttribute(node, attrName); }; // Safely extract the node type to handle different styled component structures var nodeType; try { if (node.name) { if (node.name.name) { // Standard JSX element: <Button /> nodeType = node.name.name; } else if (node.name.type === 'JSXMemberExpression') { // Component with namespace: <Namespace.Component /> nodeType = node.name.property && node.name.property.name; } } // If nodeType is still undefined, use styledComponent info or a default if (!nodeType && styledComponent) { nodeType = styledComponent.name || styledComponent.tag || 'UnknownElement'; } // Still no nodeType? Use a safe default if (!nodeType) { nodeType = 'UnknownElement'; return; // Skip processing for unknown elements } } catch (err) { // Fail gracefully in case of unexpected node structure return; } var spreadAttributes = node.attributes.find(function (_ref) { var type = _ref.type; return type === 'JSXSpreadAttribute'; }); if (!priorityOverSpread && spreadAttributes) { return; } if (!target.includes('all')) { var isNodeTargeted = false; var targetElements = { form: formElements, material: materialElements }; var finalTarget = target.includes('none') ? targetCustom : [].concat(_toConsumableArray(target), _toConsumableArray(targetCustom)); finalTarget.some(function (value) { if (targetElements[value] && targetElements[value].includes(nodeType) || value === nodeType) { isNodeTargeted = true; } }); if (!isNodeTargeted) { return; } } var idAttribute = getNodeAttribute('id'); if (!getAttributeValue(idAttribute)) { try { // Safely generate the error message with fallbacks for undefined values var componentName = styledComponent && styledComponent.name || nodeType || 'Component'; var elementType = nodeType || 'element'; context.report({ node: node, message: "".concat(componentName, " is missing \"id\" attribute. All ").concat(elementType, "s require an \"id\" attribute.") }); } catch (err) { // Last resort fallback message if we encounter unexpected structure context.report({ node: node, message: 'Component is missing required "id" attribute.' }); } } } }; } };