@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
152 lines (150 loc) • 7.27 kB
JavaScript
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 { FORM_PACKAGE, getFormImportLocalNames } from '../utils/get-form-import-local-names';
import { AFFECTED_ATLASKIT_PACKAGES } from './affected-atlaskit-packages';
import { AFFECTED_HTML_ELEMENTS } from './affected-html-elements';
var ATLASKIT_FIELD_IMPORT = 'Field';
var rule = createLintRule({
meta: {
name: 'no-placeholder',
type: 'problem',
docs: {
description: 'Placeholders should not be used. If information should be given to the user about the proper type or formatting of a value, this should be included using a helper message that is associated to the input instead.',
recommended: true,
severity: 'warn'
},
messages: {
noPlaceholder: 'Placeholders should not be used. Use separate information to help users associated using `aria-describedby` instead.',
noPlaceholderOnSimpleField: 'Placeholders should not be used. Use the `helperMessage` prop instead.',
noPlaceholderOnComplexField: 'Placeholders should not be used. Use the `HelperMessage` component instead.'
}
},
create: function create(context) {
var hasFieldImport = false;
var fieldLocalName = null;
var localComponentNames = [];
return {
ImportDeclaration: function ImportDeclaration(node) {
var source = node.source.value;
var affectedPackage = getAffectedPackageConfig(source, AFFECTED_ATLASKIT_PACKAGES);
// Ignore non-atlaskit input/form packages
if (!affectedPackage && source !== FORM_PACKAGE && source !== "".concat(FORM_PACKAGE, "/field")) {
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';
});
if (source === FORM_PACKAGE || source === "".concat(FORM_PACKAGE, "/field")) {
var _formImports$ATLASKIT, _formImports$ATLASKIT2;
var formImports = getFormImportLocalNames(node);
var matchedFieldImport = (_formImports$ATLASKIT = (_formImports$ATLASKIT2 = formImports[ATLASKIT_FIELD_IMPORT]) === null || _formImports$ATLASKIT2 === void 0 ? void 0 : _formImports$ATLASKIT2[0]) !== null && _formImports$ATLASKIT !== void 0 ? _formImports$ATLASKIT : null;
if (matchedFieldImport) {
hasFieldImport = true;
fieldLocalName = matchedFieldImport;
}
} else {
var _affectedPackage$impo;
var importNames = (_affectedPackage$impo = affectedPackage === null || affectedPackage === void 0 ? void 0 : affectedPackage.importNames) !== null && _affectedPackage$impo !== void 0 ? _affectedPackage$impo : [];
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);
// or if popup and using a named import
} 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;
}
// If it is one of the affected native HTML elements
if (AFFECTED_HTML_ELEMENTS.includes(elName)) {
// if has a placeholder attribute
var hasPlaceholderAttribute = node.openingElement.attributes.find(function (attr) {
return isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'placeholder';
});
if (hasPlaceholderAttribute) {
return context.report({
node: node.openingElement,
messageId: 'noPlaceholder'
});
}
// 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;
}
if (hasFieldImport && fieldLocalName) {
var _node = node;
var hasParentField = false;
var fieldType = 'Complex';
// If node is a Field element or if
while (isNodeOfType(_node, 'JSXElement') && isNodeOfType(_node.openingElement.name, 'JSXIdentifier') && !hasParentField) {
var name = _node.openingElement.name.name;
hasParentField = hasParentField || name === fieldLocalName;
_node = _node.parent;
// Skip up until a JSXElement or JSXAttribute is reached
if (isNodeOfType(_node, 'JSXFragment') || isNodeOfType(_node, 'ArrowFunctionExpression') || isNodeOfType(_node, 'JSXExpressionContainer') || isNodeOfType(_node, 'JSXAttribute')) {
while (_node && !isNodeOfType(_node, 'JSXElement') && !isNodeOfType(_node, 'Program')) {
if (isNodeOfType(_node, 'JSXAttribute')) {
fieldType = 'Simple';
}
_node = _node.parent;
}
}
}
if (hasParentField) {
// if has a placeholder attribute
var _hasPlaceholderAttribute = node.openingElement.attributes.find(function (attr) {
return isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'placeholder';
});
if (_hasPlaceholderAttribute) {
return context.report({
node: node.openingElement,
messageId: "noPlaceholderOn".concat(fieldType, "Field")
});
}
}
} else {
// if has a placeholder attribute
var _hasPlaceholderAttribute2 = node.openingElement.attributes.find(function (attr) {
return isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'placeholder';
});
if (_hasPlaceholderAttribute2) {
return context.report({
node: node.openingElement,
messageId: 'noPlaceholder'
});
}
}
}
}
};
}
});
export default rule;