UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

135 lines (133 loc) 5.87 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import { isNodeOfType } from 'eslint-codemod-utils'; import { JSXElementHelper as _JSXElement } from '../../ast-nodes/jsx-element-helper'; import { createLintRule } from '../utils/create-lint-rule'; import { getAffectedPackageConfig } from '../utils/get-affected-package-config'; import { AFFECTED_ATLASKIT_PACKAGES } from './affected-atlaskit-packages'; import { AFFECTED_HTML_ELEMENTS } from './affected-html-elements'; import { UNWANTED_ATLASKIT_ATTRIBUTES } from './unwanted-atlaskit-attributes'; import { UNWANTED_HTML_ATTRIBUTES } from './unwanted-html-attributes'; var isUnwantedHTMLAtrribute = function isUnwantedHTMLAtrribute(s) { var attrs = UNWANTED_HTML_ATTRIBUTES; return attrs.includes(s); }; var isUnwantedAtlaskitAtrribute = function isUnwantedAtlaskitAtrribute(s) { var attrs = UNWANTED_ATLASKIT_ATTRIBUTES; return attrs.includes(s); }; var isUnwantedAttribute = function isUnwantedAttribute(s) { return isUnwantedHTMLAtrribute(s) || isUnwantedAtlaskitAtrribute(s); }; var hasUnwantedAttributes = function hasUnwantedAttributes(node, type) { var attrNames = _toConsumableArray(type === 'html' ? UNWANTED_HTML_ATTRIBUTES : UNWANTED_ATLASKIT_ATTRIBUTES); var unwantedAttrs = node.openingElement.attributes.filter(function (attr) { return isNodeOfType(attr, 'JSXAttribute'); }).map(function (attr) { return String(attr.name.name); }).filter(isUnwantedAttribute).filter(function (unwantedAttribute) { return attrNames.includes(unwantedAttribute); }); return unwantedAttrs; }; var rule = createLintRule({ meta: { name: 'no-readonly-or-disabled-inputs', type: 'problem', docs: { description: 'Inputs should almost always be interactive. Disabled and read-only inputs can usually be replaced by a more user-friendly design pattern.', recommended: true, severity: 'warn' }, messages: { noDisabled: 'Inputs should almost always be interactive. Instead of disabling an input, consider removing it altogether, as it cannot be used anyway. Disabled inputs are much less accessible than a well labeled and described input with clear error, valid, and/or helper messages.', noReadOnly: 'Inputs should almost always be interactive. Instead of making an input read-only, consider making it normal text. Read-only inputs do the same thing as normal text but appear as interactive elements and add themselves to the tab-order, which can be confusing for users.' } }, create: function create(context) { var localComponentNames = []; return { ImportDeclaration: function ImportDeclaration(node) { var source = node.source.value; var affectedPackage = getAffectedPackageConfig(source, AFFECTED_ATLASKIT_PACKAGES); // Ignore non-atlaskit input packages if (!affectedPackage) { return; } if (!node.specifiers.length) { return; } var defaultImport = node.specifiers.filter(function (spec) { return spec.type === 'ImportDefaultSpecifier'; }); var namedImport = node.specifiers.filter(function (spec) { return spec.type === 'ImportSpecifier'; }); var importNames = affectedPackage.importNames; var usesDefaultImport = importNames.includes('default'); var possibleNamedImports = importNames.filter(function (importName) { return importName !== 'default'; }); if (usesDefaultImport && defaultImport.length && defaultImport[0].local) { localComponentNames.push(defaultImport[0].local.name); } else if (possibleNamedImports.length >= 1 && namedImport.length) { namedImport.forEach(function (imp) { if (imp.type === 'ImportSpecifier' && 'name' in imp.imported && possibleNamedImports.includes(imp.imported.name)) { localComponentNames.push(imp.local.name); } }); } }, JSXElement: function JSXElement(node) { if (!isNodeOfType(node, 'JSXElement')) { return false; } var elName = _JSXElement.getName(node); if (!elName) { return false; } var fixes = []; // If it is one of the affected native HTML elements if (AFFECTED_HTML_ELEMENTS.includes(elName)) { var unwantedAttributes = hasUnwantedAttributes(node, 'html'); if (unwantedAttributes.includes('disabled')) { fixes.push(context.report({ node: node.openingElement, messageId: 'noDisabled' })); } if (unwantedAttributes.includes('readonly')) { fixes.push(context.report({ node: node.openingElement, messageId: 'noReadOnly' })); } // Else, it is a React component } else { // if none of the affected packages is imported, return if (localComponentNames.length === 0) { return; } // if component name is not in the list, exit if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier') || !localComponentNames.includes(node.openingElement.name.name)) { return; } var _unwantedAttributes = hasUnwantedAttributes(node, 'atlaskit'); if (_unwantedAttributes.includes('isDisabled')) { fixes.push(context.report({ node: node.openingElement, messageId: 'noDisabled' })); } if (_unwantedAttributes.includes('isReadOnly')) { fixes.push(context.report({ node: node.openingElement, messageId: 'noReadOnly' })); } } return fixes; } }; } }); export default rule;