@salesforce-ux/eslint-plugin-slds
Version:
ESLint plugin provides custom linting rules specifically built for Salesforce Lightning Design System 2 (SLDS 2 beta)
191 lines (190 loc) • 10.4 kB
JavaScript
;
const node_1 = require("./utils/node");
const rule_1 = require("./utils/rule");
module.exports = {
meta: {
type: "problem",
docs: {
category: "Best Practices",
recommended: true,
description: "Update component attributes or CSS classes for the modal close button to comply with the modal component blueprint.",
url: "https://developer.salesforce.com/docs/platform/slds-linter/guide/reference-rules.html#modal-close-button-issue"
},
fixable: "code",
schema: [],
},
create(context) {
function check(node) {
if ((0, node_1.isAttributesEmpty)(node)) {
return;
}
const tagName = node.name;
// ✅ Scenario 1: Remove 'slds-button_icon-inverse' from <button>
// (optional) when the parent of the button has class name `slds-modal`
// and also button should have class `slds-modal__close`
if (tagName === "button") {
const classAttr = (0, node_1.findAttr)(node, "class");
if (classAttr && classAttr.value) {
const classList = classAttr.value.value.split(/\s+/);
// ✅ Ensure button has "slds-modal__close" before proceeding
if (!classList.includes("slds-modal__close")) {
return; // Stop execution if the class is missing
}
if (classList.includes("slds-button_icon-inverse") || classList.includes("slds-button--icon-inverse")) {
const newClassList = classList
.filter((cls) => (cls !== "slds-button_icon-inverse" && cls !== "slds-button--icon-inverse"))
.join(" ");
context.report({
node,
loc: classAttr.loc,
message: rule_1.messages["removeClass"],
fix(fixer) {
return fixer.replaceText(classAttr, // Replace the full attribute
`class="${newClassList}"` // Updated class list
);
},
});
}
}
}
// ✅ Scenario 2: Fix <lightning-button-icon> and this should have class `slds-modal__close`
if (tagName === "lightning-button-icon" || tagName === "lightning:buttonIcon") {
const variantAttr = (0, node_1.findAttr)(node, "variant");
const sizeAttr = (0, node_1.findAttr)(node, "size");
const classAttr = (0, node_1.findAttr)(node, "class");
const iconClassAttr = (0, node_1.findAttr)(node, "icon-class"); // 🔍 Check for icon-class attribute
function validateClassAttr(attribute, attrName) {
if (attribute && attribute.value) {
const classList = attribute.value.value.split(/\s+/);
// Irrespective of whether we are checking for class or icon-class we need to check whether the attribute is present or not.
// ✅ Ensure "slds-modal__close" exists before proceeding
if (!classAttr?.value?.value?.includes("slds-modal__close")) {
return;
}
// ✅ Ensure "slds-modal__close" exists before proceeding
// if (!classList.includes("slds-modal__close")) {
// return; // Stop execution if the class is missing
// }
// Remove inverse classes
if (classList.includes("slds-button_icon-inverse") || classList.includes("slds-button--icon-inverse")) {
const newClassList = classList
.filter((cls) => cls !== "slds-button_icon-inverse" && cls !== "slds-button--icon-inverse")
.join(" ");
context.report({
node,
loc: attribute.loc,
message: rule_1.messages["removeClass"],
fix(fixer) {
return fixer.replaceText(attribute, // Replace the full attribute
`${attrName}="${newClassList}"` // Correctly modifies the respective attribute
);
},
});
}
// Ensure 'slds-button' and 'slds-button_icon' exist
if (!classList.includes("slds-button") || !classList.includes("slds-button_icon")) {
let newClassList;
if (attrName === 'icon-class') {
newClassList = [
...classList.filter((cls) => cls !== "slds-button_icon-inverse"),
].join(" ");
}
else {
newClassList = [
"slds-button",
"slds-button_icon",
...classList.filter((cls) => cls !== "slds-button_icon-inverse"),
].join(" ");
}
context.report({
node: attribute,
loc: attribute.value.loc,
message: rule_1.messages["ensureButtonClasses"],
fix(fixer) {
return fixer.replaceText(attribute.value, `${newClassList}`);
},
});
}
// Fix variant="bare-inverse" to "bare"
if (variantAttr && variantAttr.value && variantAttr.value.value === "bare-inverse") {
context.report({
node: variantAttr,
message: rule_1.messages["changeVariant"],
loc: variantAttr.value.loc,
fix(fixer) {
return fixer.replaceText(variantAttr.value, `bare`);
},
});
}
// Ensure size="large" exists
// if (!sizeAttr) {
// context.report({
// node,
// message: messages["ensureSizeAttribute"],
// fix(fixer) {
// if (variantAttr) {
// return fixer.insertTextAfterRange([variantAttr.range[1], variantAttr.range[1]], ' size="large"');
// }
// },
// });
// }
}
}
// ✅ Validate `class` and `icon-class` separately, maintaining their own attribute names
validateClassAttr(classAttr, "class");
validateClassAttr(iconClassAttr, "icon-class");
}
// ✅ Scenario 3: Fix <lightning-icon> inside <button> & the class name of the parent name as button and it should have `slds-modal__close`
if ((tagName === "lightning-icon" || tagName === "lightning:icon") && node.parent?.name === "button") {
const parentClassAttr = (0, node_1.findAttr)(node.parent, "class");
if (parentClassAttr && parentClassAttr.value) {
const parentClassList = parentClassAttr.value.value.split(/\s+/);
// ✅ Ensure the parent <button> has "slds-modal__close" before proceeding
if (!parentClassList.includes("slds-modal__close")) {
return; // Stop execution if the class is missing
}
const variantAttr = (0, node_1.findAttr)(node, "variant");
const sizeAttr = (0, node_1.findAttr)(node, "size");
// Fix variant="bare-inverse" to "bare"
if (variantAttr && variantAttr.value && variantAttr.value.value === "bare-inverse") {
context.report({
node: variantAttr,
message: rule_1.messages["changeVariant"],
loc: variantAttr.value.loc,
fix(fixer) {
return fixer.replaceText(variantAttr.value, `bare`);
},
});
}
// // Remove variant attribute completely
// if (variantAttr) {
// context.report({
// node: variantAttr,
// messageId: "removeVariant",
// fix(fixer) {
// return fixer.remove(variantAttr);
// },
// });
// }
//Ensure size="large" is set
// if (!sizeAttr) {
// context.report({
// node,
// message: messages["ensureSizeAttribute"],
// fix(fixer) {
// //return fixer.insertTextAfter(node, ' size="large"');
// if(variantAttr)
// {
// return fixer.insertTextAfterRange([variantAttr.range[1], variantAttr.range[1]], ' size="large"')
// }
// },
// });
// }
}
}
}
return {
Tag: check,
};
},
};