eslint-plugin-mobx
Version:
ESLint rules for MobX
551 lines (536 loc) • 22 kB
JavaScript
;
var require$$0 = require('fs');
var require$$1 = require('path');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
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;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray(r);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
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 _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
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 _objectSpread2(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 _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : i + "";
}
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;
}
}
var mobxDecorators = new Set(['observable', 'computed', 'action', 'flow', 'override']);
function isMobxDecorator$2(decorator) {
var _decorator$expression, _decorator$expression2;
return mobxDecorators.has(decorator.expression.name) // @foo
|| mobxDecorators.has((_decorator$expression = decorator.expression.callee) === null || _decorator$expression === void 0 ? void 0 : _decorator$expression.name) // @foo()
|| mobxDecorators.has((_decorator$expression2 = decorator.expression.object) === null || _decorator$expression2 === void 0 ? void 0 : _decorator$expression2.name); // @foo.bar
}
function findAncestor$3(node, match) {
var parent = node.parent;
if (!parent) return;
if (match(parent)) return parent;
return findAncestor$3(parent, match);
}
var utils = {
findAncestor: findAncestor$3,
isMobxDecorator: isMobxDecorator$2
};
var findAncestor$2 = utils.findAncestor,
isMobxDecorator$1 = utils.isMobxDecorator;
// TODO support this.foo = 5; in constructor
// TODO? report on field as well
function create$4(context) {
var _context$options$0$au, _context$options$;
var sourceCode = context.getSourceCode();
var autofixAnnotation = (_context$options$0$au = (_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.autofixAnnotation) !== null && _context$options$0$au !== void 0 ? _context$options$0$au : true;
function fieldToKey(field) {
// TODO cache on field?
var key = sourceCode.getText(field.key);
return field.computed ? "[".concat(key, "]") : key;
}
return {
'CallExpression[callee.name="makeObservable"]': function CallExpressionCalleeNameMakeObservable(makeObservable) {
// Only interested about makeObservable(this, ...) in constructor or makeObservable({}, ...)
// ClassDeclaration
// ClassBody
// MethodDefinition[kind="constructor"]
// FunctionExpression
// BlockStatement
// ExpressionStatement
// CallExpression[callee.name="makeObservable"]
var _makeObservable$argum = _slicedToArray(makeObservable.arguments, 2),
firstArg = _makeObservable$argum[0],
secondArg = _makeObservable$argum[1];
if (!firstArg) return;
var members;
if (firstArg.type === "ThisExpression") {
var _closestFunction$pare;
var closestFunction = findAncestor$2(makeObservable, function (node) {
return node.type === "FunctionExpression" || node.type === "FunctionDeclaration";
});
if ((closestFunction === null || closestFunction === void 0 || (_closestFunction$pare = closestFunction.parent) === null || _closestFunction$pare === void 0 ? void 0 : _closestFunction$pare.kind) !== "constructor") return;
members = closestFunction.parent.parent.parent.body.body;
} else if (firstArg.type === "ObjectExpression") {
members = firstArg.properties;
} else {
return;
}
var annotationProps = (secondArg === null || secondArg === void 0 ? void 0 : secondArg.properties) || [];
var nonAnnotatedMembers = [];
var hasAnyDecorator = false;
members.forEach(function (member) {
var _member$decorators;
if (member["static"]) return;
if (member.kind === "constructor") return;
//if (member.type !== 'MethodDefinition' && member.type !== 'ClassProperty') return;
hasAnyDecorator = hasAnyDecorator || ((_member$decorators = member.decorators) === null || _member$decorators === void 0 ? void 0 : _member$decorators.some(isMobxDecorator$1)) || false;
if (!annotationProps.some(function (prop) {
return fieldToKey(prop) === fieldToKey(member);
})) {
// TODO optimize?
nonAnnotatedMembers.push(member);
}
});
/*
// With decorators, second arg must be null/undefined or not provided
if (hasAnyDecorator && secondArg && secondArg.name !== "undefined" && secondArg.value !== null) {
context.report({
node: makeObservable,
message: 'When using decorators, second arg must be `null`, `undefined` or not provided.',
})
}
// Without decorators, in constructor, second arg must be object literal
if (!hasAnyDecorator && firstArg.type === 'ThisExpression' && (!secondArg || secondArg.type !== 'ObjectExpression')) {
context.report({
node: makeObservable,
message: 'Second argument must be object in form of `{ key: annotation }`.',
})
}
*/
if (!hasAnyDecorator && nonAnnotatedMembers.length) {
// Set avoids reporting twice for setter+getter pair or actual duplicates
var keys = _toConsumableArray(new Set(nonAnnotatedMembers.map(fieldToKey)));
var keyList = keys.map(function (key) {
return "`".concat(key, "`");
}).join(", ");
var fix = function fix(fixer) {
var annotationList = keys.map(function (key) {
return "".concat(key, ": ").concat(autofixAnnotation);
}).join(", ") + ",";
if (!secondArg) {
return fixer.insertTextAfter(firstArg, ", { ".concat(annotationList, " }"));
} else if (secondArg.type !== "ObjectExpression") {
return fixer.replaceText(secondArg, "{ ".concat(annotationList, " }"));
} else {
var openingBracket = sourceCode.getFirstToken(secondArg);
return fixer.insertTextAfter(openingBracket, " ".concat(annotationList, " "));
}
};
context.report({
node: makeObservable,
messageId: "missingAnnotation",
data: {
keyList: keyList
},
fix: fix
});
}
}
};
}
var exhaustiveMakeObservable$1 = {
meta: {
type: "suggestion",
fixable: "code",
schema: [{
type: "object",
properties: {
autofixAnnotation: {
type: "boolean"
}
},
additionalProperties: false
}],
docs: {
description: "enforce all fields being listen in `makeObservable`",
recommended: true,
suggestion: false
},
messages: {
missingAnnotation: "Missing annotation for {{ keyList }}. To exclude a field, use `false` as annotation."
}
},
create: create$4
};
var findAncestor$1 = utils.findAncestor;
function create$3(context) {
return {
'CallExpression[callee.name=/(makeObservable|makeAutoObservable)/]': function CallExpressionCalleeNameMakeObservableMakeAutoObservable(makeObservable) {
var _closestFunction$pare;
// Only iterested about makeObservable(this, ...) inside constructor and not inside nested bindable function
var _makeObservable$argum = _slicedToArray(makeObservable.arguments, 1),
firstArg = _makeObservable$argum[0];
if (!firstArg) return;
if (firstArg.type !== 'ThisExpression') return;
// MethodDefinition[key.name="constructor"][kind="constructor"]
// FunctionExpression
// BlockStatement
// ExpressionStatement
// CallExpression[callee.name="makeObservable"]
var closestFunction = findAncestor$1(makeObservable, function (node) {
return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
});
if ((closestFunction === null || closestFunction === void 0 || (_closestFunction$pare = closestFunction.parent) === null || _closestFunction$pare === void 0 ? void 0 : _closestFunction$pare.kind) !== 'constructor') return;
if (makeObservable.parent.parent.parent !== closestFunction) {
context.report({
node: makeObservable,
messageId: 'mustCallUnconditionally',
data: {
name: makeObservable.callee.name
}
});
}
}
};
}
var unconditionalMakeObservable$1 = {
meta: {
type: 'problem',
docs: {
description: 'disallows calling `makeObservable(this)` conditionally inside constructors',
recommended: true
},
messages: {
mustCallUnconditionally: '`{{ name }}` must be called unconditionally inside constructor.'
}
},
create: create$3
};
var findAncestor = utils.findAncestor,
isMobxDecorator = utils.isMobxDecorator;
function create$2(context) {
var sourceCode = context.getSourceCode();
return {
'Decorator': function Decorator(decorator) {
var _clazz$body$body$find, _constructor$value$bo;
if (!isMobxDecorator(decorator)) return;
var clazz = findAncestor(decorator, function (node) {
return node.type === 'ClassDeclaration' || node.type === 'ClassExpression';
});
if (!clazz) return;
// ClassDeclaration > ClassBody > []
var constructor = (_clazz$body$body$find = clazz.body.body.find(function (node) {
return node.kind === 'constructor' && node.value.type === 'FunctionExpression';
})) !== null && _clazz$body$body$find !== void 0 ? _clazz$body$body$find : clazz.body.body.find(function (node) {
return node.kind === 'constructor';
});
// MethodDefinition > FunctionExpression > BlockStatement > []
var isMakeObservable = function isMakeObservable(node) {
var _node$expression, _node$expression2;
return ((_node$expression = node.expression) === null || _node$expression === void 0 || (_node$expression = _node$expression.callee) === null || _node$expression === void 0 ? void 0 : _node$expression.name) === 'makeObservable' && ((_node$expression2 = node.expression) === null || _node$expression2 === void 0 || (_node$expression2 = _node$expression2.arguments[0]) === null || _node$expression2 === void 0 ? void 0 : _node$expression2.type) === 'ThisExpression';
};
var makeObservable = constructor === null || constructor === void 0 || (_constructor$value$bo = constructor.value.body) === null || _constructor$value$bo === void 0 || (_constructor$value$bo = _constructor$value$bo.body.find(isMakeObservable)) === null || _constructor$value$bo === void 0 ? void 0 : _constructor$value$bo.expression;
if (makeObservable) {
// make sure second arg is nullish
var secondArg = makeObservable.arguments[1];
if (secondArg && secondArg.value !== null && secondArg.name !== 'undefined') {
context.report({
node: makeObservable,
messageId: 'secondArgMustBeNullish'
});
}
} else {
var fix = function fix(fixer) {
if ((constructor === null || constructor === void 0 ? void 0 : constructor.value.type) === 'TSEmptyBodyFunctionExpression') {
// constructor() - yes this a thing
var closingBracket = sourceCode.getLastToken(constructor.value);
return fixer.insertTextAfter(closingBracket, ' { makeObservable(this); }');
} else if (constructor) {
// constructor() {}
var _closingBracket = sourceCode.getLastToken(constructor.value.body);
return fixer.insertTextBefore(_closingBracket, ';makeObservable(this);');
} else {
// class C {}
var openingBracket = sourceCode.getFirstToken(clazz.body);
return fixer.insertTextAfter(openingBracket, '\nconstructor() { makeObservable(this); }');
}
};
context.report({
node: clazz,
messageId: 'missingMakeObservable',
fix: fix
});
}
}
};
}
var missingMakeObservable$1 = {
meta: {
type: 'problem',
fixable: 'code',
docs: {
description: 'prevents missing `makeObservable(this)` when using decorators',
recommended: true,
suggestion: false
},
messages: {
missingMakeObservable: "Constructor is missing `makeObservable(this)`.",
secondArgMustBeNullish: "`makeObservable`'s second argument must be nullish or not provided when using decorators."
}
},
create: create$2
};
function create$1(context) {
var sourceCode = context.getSourceCode();
return {
"FunctionDeclaration,FunctionExpression,ArrowFunctionExpression,ClassDeclaration,ClassExpression": function FunctionDeclarationFunctionExpressionArrowFunctionExpressionClassDeclarationClassExpression(cmp) {
var _cmp$id, _cmpOrForwardRef$pare;
if (cmp.parent && cmp.parent.type === "CallExpression" && cmp.parent.callee.name === "observer") {
// observer(...)
return;
}
var forwardRef = cmp.parent && cmp.parent.type === "CallExpression" && cmp.parent.callee.name === "forwardRef" ? cmp.parent : undefined;
if (forwardRef && forwardRef.parent && forwardRef.parent.type === "CallExpression" && forwardRef.parent.callee.name === "observer") {
// forwardRef(observer(...))
return;
}
var cmpOrForwardRef = forwardRef || cmp;
var name = (_cmp$id = cmp.id) === null || _cmp$id === void 0 ? void 0 : _cmp$id.name;
// If anonymous try to infer name from variable declaration
if (!name && ((_cmpOrForwardRef$pare = cmpOrForwardRef.parent) === null || _cmpOrForwardRef$pare === void 0 ? void 0 : _cmpOrForwardRef$pare.type) === "VariableDeclarator") {
name = cmpOrForwardRef.parent.id.name;
}
if (cmp.type.startsWith("Class")) {
// Must extend Component or React.Component
var superClass = cmp.superClass;
if (!superClass) {
// not a component
return;
}
var superClassText = sourceCode.getText(superClass);
if (superClassText !== "Component" && superClassText !== "React.Component") {
// not a component
return;
}
} else {
var _name;
// Name must start with uppercase letter
if (!((_name = name) !== null && _name !== void 0 && _name.charAt(0).match(/^[A-Z]$/))) {
// not a component
return;
}
}
var fix = function fix(fixer) {
return [fixer.insertTextBefore(sourceCode.getFirstToken(cmpOrForwardRef), (name && cmp.type.endsWith("Declaration") ? "const ".concat(name, " = ") : "") + "observer("), fixer.insertTextAfter(sourceCode.getLastToken(cmpOrForwardRef), ")")];
};
context.report({
node: cmp,
messageId: "missingObserver",
data: {
name: name || "<anonymous>"
},
fix: fix
});
}
};
}
var missingObserver$1 = {
meta: {
type: "problem",
fixable: "code",
docs: {
description: "prevents missing `observer` on react component",
recommended: true
},
messages: {
missingObserver: "Component `{{ name }}` is missing `observer`."
}
},
create: create$1
};
function create(context) {
var sourceCode = context.getSourceCode();
return {
'CallExpression[callee.name="observer"]': function CallExpressionCalleeNameObserver(observer) {
var _cmp$id;
var cmp = observer.arguments[0];
if (!cmp) return;
if (cmp !== null && cmp !== void 0 && (_cmp$id = cmp.id) !== null && _cmp$id !== void 0 && _cmp$id.name) return;
var fix = function fix(fixer) {
var _observer$parent;
// Use name from variable for autofix
var name = ((_observer$parent = observer.parent) === null || _observer$parent === void 0 ? void 0 : _observer$parent.type) === "VariableDeclarator" ? observer.parent.id.name : undefined;
if (!name) return;
if (cmp.type === "ArrowFunctionExpression") {
var arrowToken = sourceCode.getTokenBefore(cmp.body);
var fixes = [fixer.replaceText(arrowToken, ""), fixer.insertTextBefore(cmp, "function ".concat(name))];
if (cmp.body.type !== "BlockStatement") {
fixes.push(fixer.insertTextBefore(cmp.body, "{ return "), fixer.insertTextAfter(cmp.body, " }"));
}
return fixes;
}
if (cmp.type === "FunctionExpression") {
var functionToken = sourceCode.getFirstToken(cmp);
return fixer.replaceText(functionToken, "function ".concat(name));
}
if (cmp.type === "ClassExpression") {
var classToken = sourceCode.getFirstToken(cmp);
return fixer.replaceText(classToken, "class ".concat(name));
}
};
context.report({
node: cmp,
messageId: "observerComponentMustHaveName",
fix: fix
});
}
};
}
var noAnonymousObserver$1 = {
meta: {
type: "problem",
fixable: "code",
docs: {
description: "forbids anonymous functions or classes as `observer` components",
recommended: true
},
messages: {
observerComponentMustHaveName: "`observer` component must have a name."
}
},
create: create
};
var fs = require$$0__default["default"];
var path = require$$1__default["default"];
var exhaustiveMakeObservable = exhaustiveMakeObservable$1;
var unconditionalMakeObservable = unconditionalMakeObservable$1;
var missingMakeObservable = missingMakeObservable$1;
var missingObserver = missingObserver$1;
var noAnonymousObserver = noAnonymousObserver$1;
var pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
var pluginMobx = {
meta: {
name: pkg.name,
version: pkg.version
},
rules: {
"exhaustive-make-observable": exhaustiveMakeObservable,
"unconditional-make-observable": unconditionalMakeObservable,
"missing-make-observable": missingMakeObservable,
"missing-observer": missingObserver,
"no-anonymous-observer": noAnonymousObserver
}
};
var recommendedRules = {
"mobx/exhaustive-make-observable": "warn",
"mobx/unconditional-make-observable": "error",
"mobx/missing-make-observable": "error",
"mobx/missing-observer": "warn"
};
var src = _objectSpread2(_objectSpread2({}, pluginMobx), {}, {
configs: {
recommended: {
plugins: ["mobx"],
rules: recommendedRules
}
},
flatConfigs: {
recommended: {
name: "mobx/recommended",
plugins: {
mobx: pluginMobx
},
rules: recommendedRules
}
}
});
module.exports = src;