UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

116 lines 4.58 kB
import { isNodeOfType } from 'eslint-codemod-utils'; import { createLintRule } from '../utils/create-lint-rule'; var MODAL_DIALOG_IMPORT_SOURCES = new Set(['@atlaskit/modal-dialog', '@atlaskit/modal-dialog/modal-dialog']); /** * Modal entry-point triggers forward modal dialog props via `modalProps`. * Do not flag top-level `label` on these components — IconButton-based triggers * use `label` for the trigger button itself. */ var MODAL_ENTRY_POINT_TRIGGERS = [{ source: '@atlassian/entry-points/modal-trigger', namedImport: 'ModalTrigger' }, { source: '@atlassian/entry-points/modal-button-trigger', namedImport: 'ModalButtonTrigger' }, { source: '@atlassian/entry-points/modal-dropdown-item-trigger', namedImport: 'ModalDropdownItemTrigger' }, { source: '@atlassian/entry-points/modal-icon-button-trigger', namedImport: 'ModalIconButtonTrigger' }, { source: '@atlassian/entry-points/full-screen-modal-trigger', namedImport: 'FullScreenModalTrigger' }]; var hasLabelInObjectExpression = function hasLabelInObjectExpression(expression) { if (!isNodeOfType(expression, 'ObjectExpression')) { return false; } return expression.properties.some(function (property) { if (!isNodeOfType(property, 'Property') || property.computed) { return false; } if (isNodeOfType(property.key, 'Identifier')) { return property.key.name === 'label'; } return isNodeOfType(property.key, 'Literal') && property.key.value === 'label'; }); }; var rule = createLintRule({ meta: { name: 'no-modal-label', type: 'suggestion', docs: { description: 'Disallows usage of the `label` prop on Atlassian Design System modal dialog and modal entry-point triggers.', recommended: true, severity: 'error' }, messages: { noModalLabel: 'Do not use the `label` prop on modal dialog. Use the `ModalTitle` component within the `ModalHeader`, or the `titleId` prop from `useModal()` on your heading element instead.' } }, create: function create(context) { var modalDefaultImports = new Set(); var modalEntryPointTriggerImports = new Set(); return { ImportDeclaration: function ImportDeclaration(node) { var source = String(node.source.value); if (MODAL_DIALOG_IMPORT_SOURCES.has(source)) { node.specifiers.forEach(function (specifier) { if (isNodeOfType(specifier, 'ImportDefaultSpecifier')) { modalDefaultImports.add(specifier.local.name); } }); return; } var supportedImport = MODAL_ENTRY_POINT_TRIGGERS.find(function (component) { return component.source === source; }); if (!supportedImport) { return; } node.specifiers.forEach(function (specifier) { if (isNodeOfType(specifier, 'ImportSpecifier') && isNodeOfType(specifier.imported, 'Identifier') && specifier.imported.name === supportedImport.namedImport) { modalEntryPointTriggerImports.add(specifier.local.name); } }); }, JSXOpeningElement: function JSXOpeningElement(node) { if (!isNodeOfType(node, 'JSXOpeningElement')) { return; } if (!isNodeOfType(node.name, 'JSXIdentifier')) { return; } var componentName = node.name.name; if (modalDefaultImports.has(componentName)) { var labelProp = node.attributes.find(function (attribute) { return isNodeOfType(attribute, 'JSXAttribute') && isNodeOfType(attribute.name, 'JSXIdentifier') && attribute.name.name === 'label'; }); if (!labelProp) { return; } context.report({ node: node, messageId: 'noModalLabel' }); return; } if (!modalEntryPointTriggerImports.has(componentName)) { return; } var modalPropsAttribute = node.attributes.find(function (attribute) { return isNodeOfType(attribute, 'JSXAttribute') && isNodeOfType(attribute.name, 'JSXIdentifier') && attribute.name.name === 'modalProps'; }); if (!modalPropsAttribute || !isNodeOfType(modalPropsAttribute, 'JSXAttribute') || !modalPropsAttribute.value || !isNodeOfType(modalPropsAttribute.value, 'JSXExpressionContainer') || !hasLabelInObjectExpression(modalPropsAttribute.value.expression)) { return; } context.report({ node: node, messageId: 'noModalLabel' }); } }; } }); export default rule;