button-is-loading-wrapper
Ast Rule: html element
button-is-loading-wrapper
const TYPES = {
string: getPropStringValue,
sequence: getPropSequenceValue,
array: getPropArrayValue,
object: getPropObjectValue,
object_element: getPropObjectValue,
functiondefinition: () => {},
};
function getTag(node) {
if (node && node.tag) {
return node.tag.value;
}
}
function getProp(attributes = [], prop = "") {
if (!prop) return;
return attributes.find((attribute) => {
if (attribute && attribute.name && attribute.name.value) {
return attribute.name.value === prop;
}
});
}
function getPropStringValue(value) {
if (value.value === "true") {
return true;
}
if (value.value === "false") {
return false;
}
return value.value.replace(/^\"/g, "").replace(/\"$/g, "");
}
function getPropArrayValue(value) {
const array = [];
const arrayElements = value.elements;
if (arrayElements.length) {
for (const element of arrayElements) {
array.push(TYPES[element.astType](element));
}
}
return array;
}
function getPropObjectValue(value) {
const object = {};
const objectElements = value.elements;
if (objectElements.length) {
for (const element of objectElements) {
object[element.name.value] = TYPES[element.value.astType](element.value);
}
}
return object;
}
function getPropSequenceValue(value) {
if (value.elements.length) {
const element = value.elements[0];
if (element) {
return TYPES[element.astType](element);
}
}
}
function extractValue(attribute, extractor) {
if (attribute) {
// Null valued attributes imply truthiness.
// For example: <div aria-hidden />
if (attribute.value === null) return true;
return extractor(attribute.value);
}
}
function getValue(value) {
return TYPES[value.astType](value);
}
function getPropValue(attribute) {
return extractValue(attribute, getValue);
}
function propHasValue(attribute) {
const value = getPropValue(attribute);
if (typeof value !== "boolean") {
return !!value;
}
return false;
}
function visit(node, filename, code) {
if (getTag(node) === "Button") {
const isLoading = getProp(node.attributes, "isLoading");
if (isLoading !== undefined) {
if (!node.htmlChildren.length) {
return;
}
if (node.htmlChildren.length > 1) {
const error = buildError(
node.start.line,
node.start.col,
node.end.line,
node.end.col,
"Chakra buttons with isLoading attribute must have only one wrapping children.",
"INFO",
"BEST_PRACTICES"
);
addError(error);
return;
}
const child = node.htmlChildren[0];
if (child.astType === "htmldata") {
const error = buildError(
node.start.line,
node.start.col,
node.end.line,
node.end.col,
"Chakra buttons with isLoading attribute must have a wrapping children.",
"INFO",
"BEST_PRACTICES"
);
addError(error);
return;
}
if (getTag(child) === "span") {
return;
}
const asProp = getProp(child.attributes, "as");
if (getPropValue(asProp) === "span") {
return;
}
const error = buildError(
node.start.line,
node.start.col,
node.end.line,
node.end.col,
"Chakra buttons with isLoading attribute should have a wrapping span child.",
"INFO",
"BEST_PRACTICES"
);
addError(error);
}
}
}
my-test.jsx
Expected test result: no error
no isLoading
my-test.jsx
Expected test result: no error
as wrapper
my-test.jsx
Expected test result: has error
button error multiple children
my-test.jsx
Expected test result: has error
as wrapper no span
my-test.jsx
Expected test result: no error
button without isLoading
my-test.jsx
Expected test result: no error
button with isLoading should have a span
my-test.jsx
Expected test result: has error
button with isLoading should have span