@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
199 lines (196 loc) • 9.87 kB
JavaScript
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;