UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

199 lines (196 loc) 9.87 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } import { isNodeOfType } from 'eslint-codemod-utils'; import { createLintRule } from '../utils/create-lint-rule'; import { FORM_PACKAGE, getFormImportLocalNames } from '../utils/get-form-import-local-names'; import { isImportFromPackage } from '../utils/is-import-from-package'; var specialFieldsByImport = { '@atlaskit/checkbox': { component: 'Checkbox', field: 'CheckboxField', local: undefined }, '@atlaskit/range': { component: 'Range', field: 'RangeField', local: undefined }, '@atlaskit/toggle': { component: 'Toggle', field: 'CheckboxField', local: undefined } }; var specialFieldPackages = Object.keys(specialFieldsByImport); export var useCheckboxFieldMessage = 'Convert Field to CheckboxField'; export var useRangeFieldMessage = 'Convert Field to RangeField'; var rule = createLintRule({ meta: { name: 'use-correct-field', type: 'suggestion', fixable: 'code', hasSuggestions: true, docs: { description: 'Ensure makers use appropriate field component for their respective form elements.', recommended: true, severity: 'warn' }, messages: { useCheckboxField: 'Checkbox components should use the `CheckboxField` component', useRangeField: 'Range components should use the `RangeField` component', useCheckboxFieldForToggle: 'Toggle components should use the `CheckboxField` component' } }, create: function create(context) { var fieldImport; var fieldImportSource; var allPackages = []; return { ImportDeclaration: function ImportDeclaration(node) { var _formImports$Field; var source = node.source.value; if (typeof source !== 'string') { return; } if (!node.specifiers.length) { return; } var defaultImport = node.specifiers.filter(function (spec) { return isNodeOfType(spec, 'ImportDefaultSpecifier'); }); var matchedSpecialFieldPackage = specialFieldPackages.find(function (packageName) { return isImportFromPackage(source, packageName); }); if (matchedSpecialFieldPackage) { allPackages.push(node); // set local to local value if (defaultImport.length && isNodeOfType(defaultImport[0], 'ImportDefaultSpecifier') && isNodeOfType(defaultImport[0].local, 'Identifier')) { specialFieldsByImport[matchedSpecialFieldPackage].local = defaultImport[0].local.name; } } var formImports = getFormImportLocalNames(node); if (!((_formImports$Field = formImports.Field) !== null && _formImports$Field !== void 0 && _formImports$Field.length)) { return; } fieldImportSource = source; var matchingSpecifier = node.specifiers.find(function (spec) { var _formImports$Field2; if (source === FORM_PACKAGE && isNodeOfType(spec, 'ImportSpecifier') && 'name' in spec.imported && spec.imported.name === 'Field') { return true; } return isNodeOfType(spec, 'ImportDefaultSpecifier') && spec.local.name === ((_formImports$Field2 = formImports.Field) === null || _formImports$Field2 === void 0 ? void 0 : _formImports$Field2[0]); }); if (matchingSpecifier !== null && matchingSpecifier !== void 0 && matchingSpecifier.local) { fieldImport = matchingSpecifier.local; } }, JSXElement: function JSXElement(node) { if (!isNodeOfType(node, 'JSXElement')) { return; } if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) { return; } var name = node.openingElement.name.name; // if it's not a field import, skip if (!fieldImport || name !== fieldImport.name) { return; } // If no special field packages were imported, exit early. if (!allPackages.length) { return; } var fieldRenderProp = node.children.find(function (c) { return isNodeOfType(c, 'JSXExpressionContainer'); }); if (!fieldRenderProp) { return; } // I'm not early exiting because it doesn't work with ts for some reason if (isNodeOfType(fieldRenderProp, 'JSXExpressionContainer')) { if (!isNodeOfType(fieldRenderProp.expression, 'ArrowFunctionExpression')) { return; } var q = [fieldRenderProp.expression.body]; var found; while (q.length > 0 && !found) { var child = q.pop(); if ('children' in child) { var _iterator = _createForOfIteratorHelper(child.children), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var innerChild = _step.value; q.push(innerChild); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } else if (isNodeOfType(child, 'BlockStatement') && isNodeOfType(child.body[0], 'ExpressionStatement')) { q.push(child.body[0].expression); } if (!isNodeOfType(child, 'JSXElement') || !isNodeOfType(child.openingElement.name, 'JSXIdentifier')) { continue; } var elementName = child.openingElement.name.name; for (var importName in specialFieldsByImport) { // if this child is one of the found component names // then break out of the while loop and use the found object var localName = specialFieldsByImport[importName].local; if (localName === elementName) { found = specialFieldsByImport[importName].component; break; } } } if (!found) { return; } // if checkbox is inside of the field's render prop if (found === 'Checkbox' || found === 'Toggle') { var suggestions = fieldImportSource === FORM_PACKAGE ? [{ desc: useCheckboxFieldMessage, fix: function fix(fixer) { var fixes = []; fixes.push(fixer.insertTextBefore(fieldImport, 'CheckboxField, ')); fixes.push(fixer.replaceText(node.openingElement.name, 'CheckboxField')); node.closingElement && fixes.push(fixer.replaceText(node.closingElement.name, 'CheckboxField')); return fixes; } }] : []; context.report(_objectSpread({ node: node, messageId: found === 'Checkbox' ? 'useCheckboxField' : 'useCheckboxFieldForToggle' }, suggestions.length ? { suggest: suggestions } : {})); } else if (found === 'Range') { var _suggestions = fieldImportSource === FORM_PACKAGE ? [{ desc: useRangeFieldMessage, fix: function fix(fixer) { var fixes = []; fixes.push(fixer.insertTextBefore(fieldImport, 'RangeField, ')); fixes.push(fixer.replaceText(node.openingElement.name, 'RangeField')); node.closingElement && fixes.push(fixer.replaceText(node.closingElement.name, 'RangeField')); return fixes; } }] : []; context.report(_objectSpread({ node: node, messageId: 'useRangeField' }, _suggestions.length ? { suggest: _suggestions } : {})); } } } }; } }); export default rule;