UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

140 lines (138 loc) 6.94 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'; const ATLASKIT_FIELD_IMPORT = 'Field'; const 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(context) { let hasFieldImport = false; let fieldLocalName = null; const localComponentNames = []; return { ImportDeclaration(node) { const source = node.source.value; const affectedPackage = getAffectedPackageConfig(source, AFFECTED_ATLASKIT_PACKAGES); // Ignore non-atlaskit input/form packages if (!affectedPackage && source !== FORM_PACKAGE && source !== `${FORM_PACKAGE}/field`) { return; } if (!node.specifiers.length) { return; } const defaultImport = node.specifiers.filter(spec => spec.type === 'ImportDefaultSpecifier'); const namedImport = node.specifiers.filter(spec => spec.type === 'ImportSpecifier'); if (source === FORM_PACKAGE || source === `${FORM_PACKAGE}/field`) { var _formImports$ATLASKIT, _formImports$ATLASKIT2; const formImports = getFormImportLocalNames(node); const 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; const importNames = (_affectedPackage$impo = affectedPackage === null || affectedPackage === void 0 ? void 0 : affectedPackage.importNames) !== null && _affectedPackage$impo !== void 0 ? _affectedPackage$impo : []; const usesDefaultImport = importNames.includes('default'); const possibleNamedImports = importNames.filter(importName => 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(imp => { if (imp.type === 'ImportSpecifier' && 'name' in imp.imported && possibleNamedImports.includes(imp.imported.name)) { localComponentNames.push(imp.local.name); } }); } } }, JSXElement(node) { if (!isNodeOfType(node, 'JSXElement')) { return false; } const 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 const hasPlaceholderAttribute = node.openingElement.attributes.find(attr => 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) { let _node = node; let hasParentField = false; let fieldType = 'Complex'; // If node is a Field element or if while (isNodeOfType(_node, 'JSXElement') && isNodeOfType(_node.openingElement.name, 'JSXIdentifier') && !hasParentField) { const 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 const hasPlaceholderAttribute = node.openingElement.attributes.find(attr => isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'placeholder'); if (hasPlaceholderAttribute) { return context.report({ node: node.openingElement, messageId: `noPlaceholderOn${fieldType}Field` }); } } } else { // if has a placeholder attribute const hasPlaceholderAttribute = node.openingElement.attributes.find(attr => isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'placeholder'); if (hasPlaceholderAttribute) { return context.report({ node: node.openingElement, messageId: 'noPlaceholder' }); } } } } }; } }); export default rule;