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
JavaScript
; //------------------------------------------------------------------------------
// 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.'
});
}
}
}
};
}
};