UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

233 lines (227 loc) 7.84 kB
/* eslint-disable @repo/internal/react/require-jsdoc */ import { isNodeOfType } from 'eslint-codemod-utils'; import { getSourceCode } from '@atlaskit/eslint-utils/context-compat'; import { JSXElementHelper } from '../../../ast-nodes/jsx-element-helper'; import { Root } from '../../../ast-nodes/root'; import { addColorInheritAttributeFix } from './add-color-inherit-attribute-fix'; import { allowedAttrs } from './allowed-attrs'; import { updateTestIdAttributeFix } from './update-test-id-attribute-fix'; export const ParagraphElements = { lint(node, { context, config }) { if (!isNodeOfType(node, 'JSXElement')) { return; } // Check whether all criteria needed to make a transformation are met const { success, autoFixable, refs } = ParagraphElements._check(node, { context, config }); if (success && autoFixable) { if (refs.siblings.length > 1) { var _refs$siblings$0$loc, _refs$siblings$loc; /** * Highlighting from first opening element to last closing element * to indicate fix will change all p elements and wrap them in a Stack, * falls back to first opening element. */ const startLoc = (_refs$siblings$0$loc = refs.siblings[0].loc) === null || _refs$siblings$0$loc === void 0 ? void 0 : _refs$siblings$0$loc.start; const endLoc = (_refs$siblings$loc = refs.siblings[refs.siblings.length - 1].loc) === null || _refs$siblings$loc === void 0 ? void 0 : _refs$siblings$loc.end; const fix = ParagraphElements._fixMultiple(node, { context, config, refs }); context.report({ loc: startLoc && endLoc && { start: startLoc, end: endLoc }, node: node.openingElement, messageId: 'preferPrimitivesStackedText', ...(config.enableUnsafeAutofix ? { fix } : { suggest: [{ desc: `Convert to Text and Stack`, fix }] }) }); } else { const fix = ParagraphElements._fixSingle(node, { context, config }); context.report({ node: node.openingElement, messageId: 'preferPrimitivesText', ...(config.enableUnsafeAutofix ? { fix } : { suggest: [{ desc: `Convert to Text`, fix }] }) }); } } else if (success && config.enableUnsafeReport) { context.report({ node: node.openingElement, messageId: 'preferPrimitivesText' }); } }, _check(node, { context, config }) { if (!config.patterns.includes('paragraph-elements')) { return { success: false, refs: { siblings: [] } }; } const elementName = JSXElementHelper.getName(node); if (elementName !== 'p') { return { success: false, refs: { siblings: [] } }; } if (!node.children.length) { return { success: false, refs: { siblings: [] } }; } // All siblings have to be paragraph elements with no unallowed props if (!isNodeOfType(node.parent, 'JSXElement')) { return { success: true, autoFixable: false, refs: { siblings: [] } }; } const siblings = JSXElementHelper.getChildren(node.parent); if (siblings.length > 1) { var _siblings$0$range, _node$range, _siblings$0$range2, _node$range2; // Only report for the first p element by comparing node location if (((_siblings$0$range = siblings[0].range) === null || _siblings$0$range === void 0 ? void 0 : _siblings$0$range[0]) !== ((_node$range = node.range) === null || _node$range === void 0 ? void 0 : _node$range[0]) || ((_siblings$0$range2 = siblings[0].range) === null || _siblings$0$range2 === void 0 ? void 0 : _siblings$0$range2[1]) !== ((_node$range2 = node.range) === null || _node$range2 === void 0 ? void 0 : _node$range2[1])) { return { success: true, autoFixable: false, refs: { siblings } }; } // Only report when every sibling is a p element const siblingsMatch = siblings.every(child => { if (!isNodeOfType(child, 'JSXElement')) { return false; } if (JSXElementHelper.getName(child) !== 'p') { return false; } return JSXElementHelper.hasAllowedAttrsOnly(child, allowedAttrs); }); if (!siblingsMatch) { return { success: true, autoFixable: false, refs: { siblings } }; } } else if (!JSXElementHelper.hasAllowedAttrsOnly(node, allowedAttrs)) { return { success: true, autoFixable: false, refs: { siblings } }; } const importDeclaration = Root.findImportsByModule(getSourceCode(context).ast.body, '@atlaskit/primitives'); // If there is more than one `@atlaskit/primitives` import, then it becomes difficult to determine which import to transform if (importDeclaration.length > 1) { return { success: true, autoFixable: false, refs: { siblings } }; } return { success: true, autoFixable: true, refs: { siblings } }; }, _fixSingle(node, { context, config }) { return fixer => { const importFix = Root.upsertNamedImportDeclaration({ module: '@atlaskit/primitives', specifiers: ['Text'] }, context, fixer); const elementNameFixes = JSXElementHelper.updateName(node, 'Text', fixer); const asAttributeFix = JSXElementHelper.addAttribute(node, 'as', 'p', fixer); const colorAttributeFix = addColorInheritAttributeFix(node, config, fixer); const testAttributeFix = updateTestIdAttributeFix(node, fixer); return [importFix, ...elementNameFixes, asAttributeFix, colorAttributeFix, testAttributeFix].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out }; }, _fixMultiple(node, { context, config, refs }) { return fixer => { if (!isNodeOfType(node.parent, 'JSXElement') || !node.parent.closingElement) { return []; } const importFix = Root.upsertNamedImportDeclaration({ module: '@atlaskit/primitives', specifiers: ['Text', 'Stack'] }, context, fixer); // Update all siblings elements and their attributes const siblingFixes = refs.siblings.map(sibling => { if (isNodeOfType(sibling, 'JSXElement')) { const elementNameFixes = JSXElementHelper.updateName(sibling, 'Text', fixer); const asAttributeFix = JSXElementHelper.addAttribute(sibling, 'as', 'p', fixer); const colorAttributeFix = addColorInheritAttributeFix(sibling, config, fixer); const testAttributeFix = updateTestIdAttributeFix(sibling, fixer); return [...elementNameFixes, asAttributeFix, colorAttributeFix, testAttributeFix]; } return undefined; }).flat(); // Wrap in <Stack /> when more than 1 sibling const wrapperOpenElementFix = fixer.insertTextAfter(node.parent.openingElement, `<Stack space='space.150'>`); const wrapperCloseElementFix = fixer.insertTextBefore(node.parent.closingElement, '</Stack>'); return [importFix, ...siblingFixes, wrapperOpenElementFix, wrapperCloseElementFix].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out }; } };