@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
135 lines (133 loc) • 5.87 kB
JavaScript
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;