UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

152 lines (150 loc) 7.27 kB
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;