UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

239 lines (227 loc) 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useCloseButtonOrNewProp = exports.setHasCloseButtonPropToTrue = exports.ruleName = exports.default = exports.addHasCloseButtonProp = void 0; var _eslintCodemodUtils = require("eslint-codemod-utils"); var _jsxAttribute = require("../../ast-nodes/jsx-attribute"); var _jsxElementHelper = require("../../ast-nodes/jsx-element-helper"); var _createLintRule = require("../utils/create-lint-rule"); function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var PROP_NAME = 'hasCloseButton'; var MODAL_DIALOG_PACKAGE = '@atlaskit/modal-dialog'; var MODAL_DIALOG_IMPORT_SOURCE = '@atlaskit/modal-dialog/modal-dialog'; var MODAL_HEADER_IMPORT_SOURCE = '@atlaskit/modal-dialog/modal-header'; var CLOSE_BUTTON_IMPORT_SOURCE = '@atlaskit/modal-dialog/close-button'; // Lint rule message var message = '`hasCloseButton` should be set to `true` or the `CloseButton` component should be used to make modal dialog accessible.'; // Fix messages var addHasCloseButtonProp = exports.addHasCloseButtonProp = 'Add `hasCloseButton` prop.'; var setHasCloseButtonPropToTrue = exports.setHasCloseButtonPropToTrue = 'Set `hasCloseButton` prop to `true`.'; var useCloseButtonOrNewProp = exports.useCloseButtonOrNewProp = 'Set `hasCloseButton` prop to `true` in `ModalHeader` or use `CloseButton` export if customization is desired.'; var ruleName = exports.ruleName = __dirname.split('/').slice(-1)[0]; var rule = (0, _createLintRule.createLintRule)({ meta: { name: ruleName, type: 'suggestion', fixable: 'code', hasSuggestions: true, docs: { description: "Encourages makers to use close button in Atlassian Design System's modal dialog component.", recommended: true, severity: 'warn' }, messages: { modalHeaderMissingHasCloseButtonProp: message, modalHeaderHasCloseButtonPropIsFalse: message, noCloseButtonExists: message } }, create: function create(context) { // List of component's locally imported names that match var defaultImportLocalName; var modalHeaderLocalName; var closeButtonLocalName; return { // Only run rule in files where the package is imported ImportDeclaration: function ImportDeclaration(node) { node.specifiers.forEach(function (identifier) { if ((node.source.value === MODAL_DIALOG_PACKAGE || node.source.value === MODAL_DIALOG_IMPORT_SOURCE) && (0, _eslintCodemodUtils.isNodeOfType)(identifier, 'ImportDefaultSpecifier')) { defaultImportLocalName = identifier.local.name; } else if (node.source.value === MODAL_HEADER_IMPORT_SOURCE && (0, _eslintCodemodUtils.isNodeOfType)(identifier, 'ImportDefaultSpecifier')) { modalHeaderLocalName = identifier.local.name; } else if ((node.source.value === MODAL_DIALOG_PACKAGE || node.source.value === CLOSE_BUTTON_IMPORT_SOURCE) && (0, _eslintCodemodUtils.isNodeOfType)(identifier, 'ImportSpecifier') && 'name' in identifier.imported) { var importName = identifier.imported.name; var localName = identifier.local.name; if (importName === 'ModalHeader') { modalHeaderLocalName = localName; } else if (importName === 'CloseButton') { closeButtonLocalName = localName; } } else if (node.source.value === CLOSE_BUTTON_IMPORT_SOURCE && (0, _eslintCodemodUtils.isNodeOfType)(identifier, 'ImportDefaultSpecifier')) { closeButtonLocalName = identifier.local.name; } }); }, JSXElement: function JSXElement(node) { if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) { return; } if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) { return; } var name = node.openingElement.name.name; if (![defaultImportLocalName, modalHeaderLocalName].includes(name)) { return; } var modalHeaderNode = null; var closeButtonNode = null; var checkNode = function checkNode(node) { if (modalHeaderNode && closeButtonNode) { return; } // Add expression container's body if an expression container if ((0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXExpressionContainer')) { var expression = node.expression; if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'ArrowFunctionExpression') || (0, _eslintCodemodUtils.isNodeOfType)(expression, 'FunctionExpression')) { if ((0, _eslintCodemodUtils.isNodeOfType)(expression.body, 'JSXElement') || (0, _eslintCodemodUtils.isNodeOfType)(expression.body, 'JSXFragment')) { searchExpression(expression.body); } else if ((0, _eslintCodemodUtils.isNodeOfType)(expression.body, 'BlockStatement')) { expression.body.body.forEach(function (statement) { if ((0, _eslintCodemodUtils.isNodeOfType)(statement, 'ReturnStatement') && statement.argument) { searchExpression(statement.argument); } }); } } else if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'LogicalExpression')) { var left = expression.left, right = expression.right; [left, right].forEach(function (e) { searchExpression(e); }); } else { searchExpression(expression); } return; } // Skip if not a JSX Element if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) { return; } // Skip if opening element is not an identifier if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) { return; } // if child is CloseButton, return true if (node.openingElement.name.name === closeButtonLocalName) { closeButtonNode = node; } else if (node.openingElement.name.name === modalHeaderLocalName) { modalHeaderNode = node; } if (node.children) { searchNode(node); } }; var searchExpression = function searchExpression(expression) { if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'JSXElement')) { searchNode(expression, true); } else if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'JSXFragment')) { searchJSXFragment(expression); } }; var searchNode = function searchNode(node) { var searchSelf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (searchSelf) { checkNode(node); } var _iterator = _createForOfIteratorHelper(node.children), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var child = _step.value; checkNode(child); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } }; var searchJSXFragment = function searchJSXFragment(node) { if ((0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXFragment')) { var _iterator2 = _createForOfIteratorHelper(node.children), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var child = _step2.value; checkNode(child); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } } }; if (name === defaultImportLocalName) { searchNode(node); // If there is a close button, skip the rest, as this satisfies the // rule. If there is a modal header, it will be recognized in later // scans, so don't add duplicate errors if (closeButtonNode || modalHeaderNode) { return; // No close button or modal header exists } else { return context.report({ node: node, messageId: 'noCloseButtonExists' }); } } else if (name === modalHeaderLocalName) { modalHeaderNode = node; searchNode(node); // If there is a close button, skip the rest, as this satisfies the rule. if (closeButtonNode) { return; } // No close button exists, so check the modal header var prop = _jsxElementHelper.JSXElementHelper.getAttributeByName(modalHeaderNode, PROP_NAME); // If the prop exists if (prop) { var attrValue = _jsxAttribute.JSXAttribute.getValue(prop); // If the value is a boolean with value `false` if ((attrValue === null || attrValue === void 0 ? void 0 : attrValue.type) === 'ExpressionStatement Literal' && (attrValue === null || attrValue === void 0 ? void 0 : attrValue.value) === false) { return context.report({ node: modalHeaderNode, messageId: 'modalHeaderHasCloseButtonPropIsFalse', suggest: [{ desc: setHasCloseButtonPropToTrue, // Set to true by setting to boolean HTML/JSX attribute fix: function fix(fixer) { return [fixer.replaceText(prop, PROP_NAME)]; } }] }); } // If the prop does not exist } else { return context.report({ node: modalHeaderNode, messageId: 'modalHeaderMissingHasCloseButtonProp', suggest: [{ desc: addHasCloseButtonProp, fix: function fix(fixer) { return [fixer.insertTextAfter(modalHeaderNode.openingElement.name, " ".concat(PROP_NAME))]; } }] }); } } } }; } }); // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports var _default = exports.default = rule;