UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

139 lines (130 loc) 5.78 kB
import { isNodeOfType } from 'eslint-codemod-utils'; import { createLintRule } from '../utils/create-lint-rule'; import { getFormImportLocalNames } from '../utils/get-form-import-local-names'; import { topLevelAttributeNames } from './top-level-attribute-names'; export var convertForm = 'Convert form to simple form'; var rule = createLintRule({ meta: { name: 'use-simple-form', type: 'suggestion', hasSuggestions: true, docs: { description: 'Encourage use of simple form for better developer experience and accessibility.', recommended: true, severity: 'warn' }, messages: { useSimpleForm: 'The simplified form implementation can be used here.' } }, create: function create(context) { var formImport; return { ImportDeclaration: function ImportDeclaration(node) { var _getFormImportLocalNa, _getFormImportLocalNa2; formImport = (_getFormImportLocalNa = (_getFormImportLocalNa2 = getFormImportLocalNames(node).Form) === null || _getFormImportLocalNa2 === void 0 ? void 0 : _getFormImportLocalNa2[0]) !== null && _getFormImportLocalNa !== void 0 ? _getFormImportLocalNa : formImport; }, JSXElement: function JSXElement(node) { if (!isNodeOfType(node, 'JSXElement')) { return; } if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) { return; } // if no form import exists, skip if (!formImport) { return; } // if component is not field, skip if (node.openingElement.name.name !== formImport) { return; } // if children is not a render prop, skip var renderProps = node.children.find(function (child) { return isNodeOfType(child, 'JSXExpressionContainer'); }); if (!renderProps) { return; } var renderPropExpression = renderProps.expression; // If not an arrow func, ignore if (!isNodeOfType(renderPropExpression, 'ArrowFunctionExpression')) { return; } var renderPropParams = renderPropExpression.params; // if it is not an object pattern, skip if (!isNodeOfType(renderPropParams[0], 'ObjectPattern')) { return; } // if component uses more than just formProps in render props, skip var renderPropArgProperties = renderPropParams[0].properties; var formPropsProp = renderPropArgProperties.find(function (property) { return isNodeOfType(property, 'Property') && isNodeOfType(property.key, 'Identifier') && property.key.name === 'formProps'; }); if (renderPropArgProperties.length > 1 || !formPropsProp) { return; } var attributes = node.openingElement.attributes; var lastProp = attributes.slice(-1)[0] || node.openingElement.name; var sourceCode = context.sourceCode; // if html form is not first child, don't give a fix var renderPropBody = renderPropExpression.body; var firstChildIsHTMLForm = isNodeOfType(renderPropBody, 'JSXElement') && isNodeOfType(renderPropBody.openingElement.name, 'JSXIdentifier') && renderPropBody.openingElement.name.name === 'form'; // If this is not an HTML form. skip because we can't convert if (!firstChildIsHTMLForm) { return; } context.report({ node: node, messageId: 'useSimpleForm', suggest: [{ desc: convertForm, fix: function fix(fixer) { var fixes = []; // add each attribute in the HTML form inside to the AK form var attrs = { topLevel: [], formProps: [] }; var htmlFormAttributes = renderPropBody.openingElement.attributes; htmlFormAttributes.forEach(function (attr) { if (isNodeOfType(attr, 'JSXAttribute') && isNodeOfType(attr.name, 'JSXIdentifier')) { if (topLevelAttributeNames.includes(attr.name.name)) { attrs.topLevel.push(attr); } else { attrs.formProps.push(attr); } } }); attrs.topLevel.forEach(function (attr) { fixes.push(fixer.insertTextAfter(lastProp, " ".concat(sourceCode.getText(attr)))); }); if (attrs.formProps.length > 0) { var formPropsText = attrs.formProps.map(function (attr) { return sourceCode.getText(attr).replace(/^([^=]*)/, "'$1'").replace('=', ': '); }).join(',\n'); fixes.push(fixer.insertTextAfter(lastProp, " formProps={{\n".concat(formPropsText, "\n}} "))); } // remove the body of the AK form fixes.push(fixer.remove(renderProps)); // Get all children text of HTML form to be added to AK form var childrenText = '<>'; renderPropBody.children.forEach(function (child) { // Doing `as any` because it doesn't like spread props. I // added a test that verifies that this does indeed work, // though, so it's fine. childrenText += sourceCode.getText(child); }); childrenText += '</>'; // Add the children of the HTML form to the children of the AK form node.closingElement && fixes.push(fixer.insertTextBefore(node.closingElement, childrenText)); return fixes; } }] }); } }; } }); export default rule; export { topLevelAttributeNames } from './top-level-attribute-names';