UNPKG

react-magnetic-di

Version:
172 lines (147 loc) 6.96 kB
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } var _require = require('../utils'), getDiIdentifier = _require.getDiIdentifier, getDiStatements = _require.getDiStatements, getParentDiStatements = _require.getParentDiStatements, getDiVars = _require.getDiVars, isHookName = _require.isHookName, isComponentName = _require.isComponentName, isLocalVariable = _require.isLocalVariable; var getReactIdentifiers = function getReactIdentifiers(node) { if (node.source.value === 'react') { return node.specifiers.map(function (s) { return s.local; }).filter(function (n) { return !['useState', 'useContext', 'useReducer'].includes(n.name); }); } }; var isDefaultProp = function isDefaultProp(node, diStatement) { // we assume order rule is enabled, so if the variable is used in an assignment // defined before our di() statements, then it's probably default props return node.parent.type === 'AssignmentPattern' && node.range[0] < diStatement.range[0]; }; module.exports = { meta: { type: 'suggestion', docs: { description: 'Requires external components/hooks to be marked as injectable', category: 'Best Practices', recommended: false }, fixable: 'code', schema: [{ type: 'object', properties: { ignore: { type: 'array', items: { type: 'string' } } }, additionalProperties: false }], messages: { missingInject: "Dependency '{{name}}' has not being marked as injectable. " + 'Add it to the list of the injectable dependencies' } }, create: function create(context) { var diIdentifier; var reactVars; var userOptions = Object.assign({ ignore: [] }, context.options[0]); var isInjected = function isInjected(vars, n) { return vars.some(function (v) { return v.name === n.name; }); }; var isReactIgnored = function isReactIgnored(n) { return reactVars.some(function (v) { return v.name === n.name; }); }; var isOptionsIgnored = function isOptionsIgnored(n) { return userOptions.ignore.includes(n.name); }; var report = function report(node, diStatement) { return context.report({ node: diStatement, messageId: 'missingInject', data: { name: node.name }, fix: function fix(fixer) { var lastArg = diStatement.expression.arguments.slice(-1)[0]; if (!lastArg) { // if injection without args, let's add the var inside call var _diStatement$expressi = _slicedToArray(diStatement.expression.callee.range, 2), start = _diStatement$expressi[0], end = _diStatement$expressi[1]; return fixer.insertTextAfterRange([start, end + 1], node.name); } return fixer.insertTextAfter(lastArg, ", ".concat(node.name)); } }); }; return { ImportDeclaration: function ImportDeclaration(node) { if (!diIdentifier) diIdentifier = getDiIdentifier(node); if (!reactVars) reactVars = getReactIdentifiers(node); }, // this is to handle hooks and components recognised as used variables // it does not cover JSX variables BlockStatement: function BlockStatement(node) { if (!diIdentifier) return; var throughVars = context.getScope().through.map(function (v) { return v.identifier; }).filter(function (v) { return v.name !== diIdentifier.name; }); var diStatements = getDiStatements(node, diIdentifier); // ignore locations where di was not explicitly set if (!diStatements.length) return; var diVars = getDiVars(diStatements); throughVars.forEach(function (varNode) { var isInjectable = isHookName(varNode); if (!isInjectable || isInjected(diVars, varNode) || isReactIgnored(varNode) || isOptionsIgnored(varNode) || isDefaultProp(varNode, diStatements[0])) return; report(varNode, diStatements[diStatements.length - 1]); }); }, // as JSX elements are not treated as variables, for each JSX tag // we check if there is a block with di() above and if that includes it 'JSXOpeningElement:exit': function JSXOpeningElementExit(node) { if (!diIdentifier) return; // ignore if the component is declared locally if (isLocalVariable(node.name, context.getScope(), diIdentifier)) return; var varNode; switch (node.name.type) { case 'JSXIdentifier': { varNode = node.name; var isInjectable = isComponentName(varNode); if (!isInjectable || isReactIgnored(varNode) || isOptionsIgnored(varNode)) return; break; } case 'JSXNamespacedName': // TODO handle foo:Bar return; case 'JSXMemberExpression': // TODO handle foo.Bar (but ignoring this.Bar) return; default: return; } var diStatements = getParentDiStatements(varNode, diIdentifier); // ignore locations where di was not explicitly set if (!diStatements.length) return; var diVars = getDiVars(diStatements); if (isInjected(diVars, varNode)) return; report(varNode, diStatements[diStatements.length - 1]); } }; } };