2039 lines
85 KiB
JavaScript
2039 lines
85 KiB
JavaScript
/**
|
|
* @license React
|
|
* eslint-plugin-react-hooks.development.js
|
|
*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
"use strict";
|
|
"production" !== process.env.NODE_ENV &&
|
|
(function () {
|
|
function _unsupportedIterableToArray(o, minLen) {
|
|
if (o) {
|
|
if ("string" === typeof o) return _arrayLikeToArray(o, minLen);
|
|
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
"Object" === n && o.constructor && (n = o.constructor.name);
|
|
if ("Map" === n || "Set" === n) return Array.from(o);
|
|
if (
|
|
"Arguments" === n ||
|
|
/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)
|
|
)
|
|
return _arrayLikeToArray(o, minLen);
|
|
}
|
|
}
|
|
function _arrayLikeToArray(arr, len) {
|
|
if (null == len || len > arr.length) len = arr.length;
|
|
for (var i = 0, arr2 = Array(len); i < len; i++) arr2[i] = arr[i];
|
|
return arr2;
|
|
}
|
|
function _createForOfIteratorHelper(o, allowArrayLike) {
|
|
var it;
|
|
if ("undefined" === typeof Symbol || null == o[Symbol.iterator]) {
|
|
if (
|
|
Array.isArray(o) ||
|
|
(it = _unsupportedIterableToArray(o)) ||
|
|
(allowArrayLike && o && "number" === typeof o.length)
|
|
) {
|
|
it && (o = it);
|
|
var i = 0;
|
|
allowArrayLike = function () {};
|
|
return {
|
|
s: allowArrayLike,
|
|
n: function () {
|
|
return i >= o.length ? { done: !0 } : { done: !1, value: o[i++] };
|
|
},
|
|
e: function (e) {
|
|
throw e;
|
|
},
|
|
f: allowArrayLike
|
|
};
|
|
}
|
|
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 normalCompletion = !0,
|
|
didErr = !1,
|
|
err;
|
|
return {
|
|
s: function () {
|
|
it = o[Symbol.iterator]();
|
|
},
|
|
n: function () {
|
|
var step = it.next();
|
|
normalCompletion = step.done;
|
|
return step;
|
|
},
|
|
e: function (e) {
|
|
didErr = !0;
|
|
err = e;
|
|
},
|
|
f: function () {
|
|
try {
|
|
normalCompletion || null == it.return || it.return();
|
|
} finally {
|
|
if (didErr) throw err;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function isHook(node) {
|
|
if ("Identifier" === node.type)
|
|
return (node = node.name), "use" === node || /^use[A-Z0-9]/.test(node);
|
|
if (
|
|
"MemberExpression" === node.type &&
|
|
!node.computed &&
|
|
isHook(node.property)
|
|
) {
|
|
node = node.object;
|
|
var isPascalCaseNameSpace = /^[A-Z].*/;
|
|
return (
|
|
"Identifier" === node.type && isPascalCaseNameSpace.test(node.name)
|
|
);
|
|
}
|
|
return !1;
|
|
}
|
|
function isComponentName(node) {
|
|
return "Identifier" === node.type && /^[A-Z]/.test(node.name);
|
|
}
|
|
function isReactFunction(node, functionName) {
|
|
return (
|
|
node.name === functionName ||
|
|
("MemberExpression" === node.type &&
|
|
"React" === node.object.name &&
|
|
node.property.name === functionName)
|
|
);
|
|
}
|
|
function isForwardRefCallback(node) {
|
|
return !!(
|
|
node.parent &&
|
|
node.parent.callee &&
|
|
isReactFunction(node.parent.callee, "forwardRef")
|
|
);
|
|
}
|
|
function isMemoCallback(node) {
|
|
return !!(
|
|
node.parent &&
|
|
node.parent.callee &&
|
|
isReactFunction(node.parent.callee, "memo")
|
|
);
|
|
}
|
|
function isInsideComponentOrHook(node) {
|
|
for (; node; ) {
|
|
var functionName = getFunctionName(node);
|
|
if (
|
|
(functionName &&
|
|
(isComponentName(functionName) || isHook(functionName))) ||
|
|
isForwardRefCallback(node) ||
|
|
isMemoCallback(node)
|
|
)
|
|
return !0;
|
|
node = node.parent;
|
|
}
|
|
return !1;
|
|
}
|
|
function getFunctionName(node) {
|
|
if (
|
|
"FunctionDeclaration" === node.type ||
|
|
("FunctionExpression" === node.type && node.id)
|
|
)
|
|
return node.id;
|
|
if (
|
|
"FunctionExpression" === node.type ||
|
|
"ArrowFunctionExpression" === node.type
|
|
)
|
|
return "VariableDeclarator" === node.parent.type &&
|
|
node.parent.init === node
|
|
? node.parent.id
|
|
: "AssignmentExpression" === node.parent.type &&
|
|
node.parent.right === node &&
|
|
"=" === node.parent.operator
|
|
? node.parent.left
|
|
: "Property" !== node.parent.type ||
|
|
node.parent.value !== node ||
|
|
node.parent.computed
|
|
? "AssignmentPattern" !== node.parent.type ||
|
|
node.parent.right !== node ||
|
|
node.parent.computed
|
|
? void 0
|
|
: node.parent.left
|
|
: node.parent.key;
|
|
}
|
|
function collectRecommendations(_ref6) {
|
|
function createDepTree() {
|
|
return {
|
|
isUsed: !1,
|
|
isSatisfiedRecursively: !1,
|
|
isSubtreeUsed: !1,
|
|
children: new Map()
|
|
};
|
|
}
|
|
function getOrCreateNodeByPath(rootNode, path) {
|
|
path = path.split(".");
|
|
path = _createForOfIteratorHelper(path);
|
|
var _step4;
|
|
try {
|
|
for (path.s(); !(_step4 = path.n()).done; ) {
|
|
var key = _step4.value,
|
|
child = rootNode.children.get(key);
|
|
child ||
|
|
((child = createDepTree()), rootNode.children.set(key, child));
|
|
rootNode = child;
|
|
}
|
|
} catch (err) {
|
|
path.e(err);
|
|
} finally {
|
|
path.f();
|
|
}
|
|
return rootNode;
|
|
}
|
|
function markAllParentsByPath(rootNode, path, fn) {
|
|
path = path.split(".");
|
|
path = _createForOfIteratorHelper(path);
|
|
var _step5;
|
|
try {
|
|
for (path.s(); !(_step5 = path.n()).done; ) {
|
|
var child = rootNode.children.get(_step5.value);
|
|
if (!child) break;
|
|
fn(child);
|
|
rootNode = child;
|
|
}
|
|
} catch (err) {
|
|
path.e(err);
|
|
} finally {
|
|
path.f();
|
|
}
|
|
}
|
|
function scanTreeRecursively(
|
|
node,
|
|
missingPaths,
|
|
satisfyingPaths,
|
|
keyToPath
|
|
) {
|
|
node.children.forEach(function (child, key) {
|
|
var path = keyToPath(key);
|
|
child.isSatisfiedRecursively
|
|
? child.isSubtreeUsed && satisfyingPaths.add(path)
|
|
: child.isUsed
|
|
? missingPaths.add(path)
|
|
: scanTreeRecursively(
|
|
child,
|
|
missingPaths,
|
|
satisfyingPaths,
|
|
function (childKey) {
|
|
return path + "." + childKey;
|
|
}
|
|
);
|
|
});
|
|
}
|
|
var dependencies = _ref6.dependencies,
|
|
declaredDependencies = _ref6.declaredDependencies,
|
|
stableDependencies = _ref6.stableDependencies,
|
|
externalDependencies = _ref6.externalDependencies,
|
|
isEffect = _ref6.isEffect,
|
|
depTree = createDepTree();
|
|
dependencies.forEach(function (_, key) {
|
|
getOrCreateNodeByPath(depTree, key).isUsed = !0;
|
|
markAllParentsByPath(depTree, key, function (parent) {
|
|
parent.isSubtreeUsed = !0;
|
|
});
|
|
});
|
|
declaredDependencies.forEach(function (_ref7) {
|
|
getOrCreateNodeByPath(depTree, _ref7.key).isSatisfiedRecursively = !0;
|
|
});
|
|
stableDependencies.forEach(function (key) {
|
|
getOrCreateNodeByPath(depTree, key).isSatisfiedRecursively = !0;
|
|
});
|
|
_ref6 = new Set();
|
|
var satisfyingDependencies = new Set();
|
|
scanTreeRecursively(
|
|
depTree,
|
|
_ref6,
|
|
satisfyingDependencies,
|
|
function (key) {
|
|
return key;
|
|
}
|
|
);
|
|
var suggestedDependencies = [],
|
|
unnecessaryDependencies = new Set(),
|
|
duplicateDependencies = new Set();
|
|
declaredDependencies.forEach(function (_ref8) {
|
|
_ref8 = _ref8.key;
|
|
satisfyingDependencies.has(_ref8)
|
|
? -1 === suggestedDependencies.indexOf(_ref8)
|
|
? suggestedDependencies.push(_ref8)
|
|
: duplicateDependencies.add(_ref8)
|
|
: !isEffect ||
|
|
_ref8.endsWith(".current") ||
|
|
externalDependencies.has(_ref8)
|
|
? unnecessaryDependencies.add(_ref8)
|
|
: -1 === suggestedDependencies.indexOf(_ref8) &&
|
|
suggestedDependencies.push(_ref8);
|
|
});
|
|
_ref6.forEach(function (key) {
|
|
suggestedDependencies.push(key);
|
|
});
|
|
return {
|
|
suggestedDependencies: suggestedDependencies,
|
|
unnecessaryDependencies: unnecessaryDependencies,
|
|
duplicateDependencies: duplicateDependencies,
|
|
missingDependencies: _ref6
|
|
};
|
|
}
|
|
function getConstructionExpressionType(node) {
|
|
switch (node.type) {
|
|
case "ObjectExpression":
|
|
return "object";
|
|
case "ArrayExpression":
|
|
return "array";
|
|
case "ArrowFunctionExpression":
|
|
case "FunctionExpression":
|
|
return "function";
|
|
case "ClassExpression":
|
|
return "class";
|
|
case "ConditionalExpression":
|
|
if (
|
|
null != getConstructionExpressionType(node.consequent) ||
|
|
null != getConstructionExpressionType(node.alternate)
|
|
)
|
|
return "conditional";
|
|
break;
|
|
case "LogicalExpression":
|
|
if (
|
|
null != getConstructionExpressionType(node.left) ||
|
|
null != getConstructionExpressionType(node.right)
|
|
)
|
|
return "logical expression";
|
|
break;
|
|
case "JSXFragment":
|
|
return "JSX fragment";
|
|
case "JSXElement":
|
|
return "JSX element";
|
|
case "AssignmentExpression":
|
|
if (null != getConstructionExpressionType(node.right))
|
|
return "assignment expression";
|
|
break;
|
|
case "NewExpression":
|
|
return "object construction";
|
|
case "Literal":
|
|
if (node.value instanceof RegExp) return "regular expression";
|
|
break;
|
|
case "TypeCastExpression":
|
|
case "AsExpression":
|
|
case "TSAsExpression":
|
|
return getConstructionExpressionType(node.expression);
|
|
}
|
|
return null;
|
|
}
|
|
function scanForConstructions(_ref9) {
|
|
var declaredDependenciesNode = _ref9.declaredDependenciesNode,
|
|
componentScope = _ref9.componentScope,
|
|
scope = _ref9.scope;
|
|
return _ref9.declaredDependencies
|
|
.map(function (_ref10) {
|
|
var key = _ref10.key;
|
|
_ref10 = componentScope.variables.find(function (v) {
|
|
return v.name === key;
|
|
});
|
|
if (null == _ref10) return null;
|
|
var node = _ref10.defs[0];
|
|
if (null == node) return null;
|
|
if (
|
|
"Variable" === node.type &&
|
|
"VariableDeclarator" === node.node.type &&
|
|
"Identifier" === node.node.id.type &&
|
|
null != node.node.init
|
|
) {
|
|
var constantExpressionType = getConstructionExpressionType(
|
|
node.node.init
|
|
);
|
|
if (null != constantExpressionType)
|
|
return [_ref10, constantExpressionType];
|
|
}
|
|
return "FunctionName" === node.type &&
|
|
"FunctionDeclaration" === node.node.type
|
|
? [_ref10, "function"]
|
|
: "ClassName" === node.type && "ClassDeclaration" === node.node.type
|
|
? [_ref10, "class"]
|
|
: null;
|
|
})
|
|
.filter(Boolean)
|
|
.map(function (_ref11) {
|
|
var ref = _ref11[0];
|
|
_ref11 = _ref11[1];
|
|
var JSCompiler_temp_const = ref.defs[0];
|
|
a: {
|
|
for (
|
|
var foundWriteExpr = !1, i = 0;
|
|
i < ref.references.length;
|
|
i++
|
|
) {
|
|
var reference = ref.references[i];
|
|
if (reference.writeExpr)
|
|
if (foundWriteExpr) {
|
|
ref = !0;
|
|
break a;
|
|
} else {
|
|
foundWriteExpr = !0;
|
|
continue;
|
|
}
|
|
for (
|
|
var currentScope = reference.from;
|
|
currentScope !== scope && null != currentScope;
|
|
|
|
)
|
|
currentScope = currentScope.upper;
|
|
if (
|
|
currentScope !== scope &&
|
|
!isAncestorNodeOf(
|
|
declaredDependenciesNode,
|
|
reference.identifier
|
|
)
|
|
) {
|
|
ref = !0;
|
|
break a;
|
|
}
|
|
}
|
|
ref = !1;
|
|
}
|
|
return {
|
|
construction: JSCompiler_temp_const,
|
|
depType: _ref11,
|
|
isUsedOutsideOfHook: ref
|
|
};
|
|
});
|
|
}
|
|
function getDependency(node) {
|
|
return ("MemberExpression" !== node.parent.type &&
|
|
"OptionalMemberExpression" !== node.parent.type) ||
|
|
node.parent.object !== node ||
|
|
"current" === node.parent.property.name ||
|
|
node.parent.computed ||
|
|
(null != node.parent.parent &&
|
|
("CallExpression" === node.parent.parent.type ||
|
|
"OptionalCallExpression" === node.parent.parent.type) &&
|
|
node.parent.parent.callee === node.parent)
|
|
? "MemberExpression" === node.type &&
|
|
node.parent &&
|
|
"AssignmentExpression" === node.parent.type &&
|
|
node.parent.left === node
|
|
? node.object
|
|
: node
|
|
: getDependency(node.parent);
|
|
}
|
|
function markNode(node, optionalChains, result) {
|
|
optionalChains &&
|
|
(node.optional
|
|
? optionalChains.has(result) || optionalChains.set(result, !0)
|
|
: optionalChains.set(result, !1));
|
|
}
|
|
function analyzePropertyChain(node, optionalChains) {
|
|
if ("Identifier" === node.type || "JSXIdentifier" === node.type)
|
|
return (
|
|
(node = node.name),
|
|
optionalChains && optionalChains.set(node, !1),
|
|
node
|
|
);
|
|
if ("MemberExpression" !== node.type || node.computed) {
|
|
if ("OptionalMemberExpression" !== node.type || node.computed) {
|
|
if ("ChainExpression" !== node.type || node.computed)
|
|
throw Error("Unsupported node type: " + node.type);
|
|
node = node.expression;
|
|
if ("CallExpression" === node.type)
|
|
throw Error("Unsupported node type: " + node.type);
|
|
var _object2 = analyzePropertyChain(node.object, optionalChains),
|
|
_property2 = analyzePropertyChain(node.property, null);
|
|
_object2 = _object2 + "." + _property2;
|
|
markNode(node, optionalChains, _object2);
|
|
return _object2;
|
|
}
|
|
_object2 = analyzePropertyChain(node.object, optionalChains);
|
|
_property2 = analyzePropertyChain(node.property, null);
|
|
_object2 = _object2 + "." + _property2;
|
|
markNode(node, optionalChains, _object2);
|
|
return _object2;
|
|
}
|
|
_object2 = analyzePropertyChain(node.object, optionalChains);
|
|
_property2 = analyzePropertyChain(node.property, null);
|
|
_object2 = _object2 + "." + _property2;
|
|
markNode(node, optionalChains, _object2);
|
|
return _object2;
|
|
}
|
|
function getNodeWithoutReactNamespace(node) {
|
|
return "MemberExpression" !== node.type ||
|
|
"Identifier" !== node.object.type ||
|
|
"React" !== node.object.name ||
|
|
"Identifier" !== node.property.type ||
|
|
node.computed
|
|
? node
|
|
: node.property;
|
|
}
|
|
function getReactiveHookCallbackIndex(calleeNode, options) {
|
|
var node = getNodeWithoutReactNamespace(calleeNode);
|
|
if ("Identifier" !== node.type) return -1;
|
|
switch (node.name) {
|
|
case "useEffect":
|
|
case "useLayoutEffect":
|
|
case "useCallback":
|
|
case "useMemo":
|
|
return 0;
|
|
case "useImperativeHandle":
|
|
return 1;
|
|
default:
|
|
if (node === calleeNode && options && options.additionalHooks) {
|
|
try {
|
|
var name = analyzePropertyChain(node, null);
|
|
} catch (error) {
|
|
if (/Unsupported node type/.test(error.message)) return 0;
|
|
throw error;
|
|
}
|
|
return options.additionalHooks.test(name) ? 0 : -1;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
function fastFindReferenceWithParent(start, target) {
|
|
for (var queue = [start], item = null; queue.length; ) {
|
|
item = queue.shift();
|
|
if (
|
|
("Identifier" === item.type || "JSXIdentifier" === item.type) &&
|
|
item.type === target.type &&
|
|
item.name === target.name &&
|
|
item.range[0] === target.range[0] &&
|
|
item.range[1] === target.range[1]
|
|
)
|
|
return item;
|
|
if (isAncestorNodeOf(item, target)) {
|
|
start = 0;
|
|
for (
|
|
var _Object$entries = Object.entries(item);
|
|
start < _Object$entries.length;
|
|
start++
|
|
) {
|
|
var _Object$entries$_i = _Object$entries[start],
|
|
value = _Object$entries$_i[1];
|
|
"parent" !== _Object$entries$_i[0] &&
|
|
(isNodeLike(value)
|
|
? ((value.parent = item), queue.push(value))
|
|
: Array.isArray(value) &&
|
|
value.forEach(function (val) {
|
|
isNodeLike(val) && ((val.parent = item), queue.push(val));
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function joinEnglish(arr) {
|
|
for (var s = "", i = 0; i < arr.length; i++)
|
|
(s += arr[i]),
|
|
0 === i && 2 === arr.length
|
|
? (s += " and ")
|
|
: i === arr.length - 2 && 2 < arr.length
|
|
? (s += ", and ")
|
|
: i < arr.length - 1 && (s += ", ");
|
|
return s;
|
|
}
|
|
function isNodeLike(val) {
|
|
return (
|
|
"object" === typeof val &&
|
|
null !== val &&
|
|
!Array.isArray(val) &&
|
|
"string" === typeof val.type
|
|
);
|
|
}
|
|
function isAncestorNodeOf(a, b) {
|
|
return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
|
|
}
|
|
exports.configs = {
|
|
recommended: {
|
|
plugins: ["react-hooks"],
|
|
rules: {
|
|
"react-hooks/rules-of-hooks": "error",
|
|
"react-hooks/exhaustive-deps": "warn"
|
|
}
|
|
}
|
|
};
|
|
exports.rules = {
|
|
"rules-of-hooks": {
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
description: "enforces the Rules of Hooks",
|
|
recommended: !0,
|
|
url: "https://reactjs.org/docs/hooks-rules.html"
|
|
}
|
|
},
|
|
create: function (context) {
|
|
function recordAllUseEffectEventFunctions(scope) {
|
|
scope = _createForOfIteratorHelper(scope.references);
|
|
try {
|
|
for (scope.s(); !scope.n().done; );
|
|
} catch (err$0) {
|
|
scope.e(err$0);
|
|
} finally {
|
|
scope.f();
|
|
}
|
|
}
|
|
var lastEffect = null,
|
|
codePathReactHooksMapStack = [],
|
|
codePathSegmentStack = [],
|
|
useEffectEventFunctions = new WeakSet(),
|
|
getSource =
|
|
"function" === typeof context.getSource
|
|
? function (node) {
|
|
return context.getSource(node);
|
|
}
|
|
: function (node) {
|
|
return context.sourceCode.getText(node);
|
|
},
|
|
getScope =
|
|
"function" === typeof context.getScope
|
|
? function () {
|
|
return context.getScope();
|
|
}
|
|
: function (node) {
|
|
return context.sourceCode.getScope(node);
|
|
};
|
|
return {
|
|
onCodePathSegmentStart: function (segment) {
|
|
return codePathSegmentStack.push(segment);
|
|
},
|
|
onCodePathSegmentEnd: function () {
|
|
return codePathSegmentStack.pop();
|
|
},
|
|
onCodePathStart: function () {
|
|
return codePathReactHooksMapStack.push(new Map());
|
|
},
|
|
onCodePathEnd: function (codePath, codePathNode) {
|
|
function countPathsFromStart(segment, pathHistory) {
|
|
var cache = countPathsFromStart.cache,
|
|
paths = cache.get(segment.id);
|
|
pathHistory = new Set(pathHistory);
|
|
if (pathHistory.has(segment.id)) {
|
|
cache = [].concat(pathHistory);
|
|
segment = cache.slice(cache.indexOf(segment.id) + 1);
|
|
segment = _createForOfIteratorHelper(segment);
|
|
var _step3;
|
|
try {
|
|
for (segment.s(); !(_step3 = segment.n()).done; )
|
|
cyclic.add(_step3.value);
|
|
} catch (err) {
|
|
segment.e(err);
|
|
} finally {
|
|
segment.f();
|
|
}
|
|
return BigInt("0");
|
|
}
|
|
pathHistory.add(segment.id);
|
|
if (void 0 !== paths) return paths;
|
|
if (codePath.thrownSegments.includes(segment))
|
|
paths = BigInt("0");
|
|
else if (0 === segment.prevSegments.length) paths = BigInt("1");
|
|
else {
|
|
paths = BigInt("0");
|
|
_step3 = _createForOfIteratorHelper(segment.prevSegments);
|
|
var _step4;
|
|
try {
|
|
for (_step3.s(); !(_step4 = _step3.n()).done; )
|
|
paths += countPathsFromStart(_step4.value, pathHistory);
|
|
} catch (err$1) {
|
|
_step3.e(err$1);
|
|
} finally {
|
|
_step3.f();
|
|
}
|
|
}
|
|
segment.reachable && paths === BigInt("0")
|
|
? cache.delete(segment.id)
|
|
: cache.set(segment.id, paths);
|
|
return paths;
|
|
}
|
|
function countPathsToEnd(segment, pathHistory) {
|
|
var cache = countPathsToEnd.cache,
|
|
paths = cache.get(segment.id);
|
|
pathHistory = new Set(pathHistory);
|
|
if (pathHistory.has(segment.id)) {
|
|
cache = Array.from(pathHistory);
|
|
segment = cache.slice(cache.indexOf(segment.id) - 1);
|
|
segment = _createForOfIteratorHelper(segment);
|
|
var _step5;
|
|
try {
|
|
for (segment.s(); !(_step5 = segment.n()).done; )
|
|
cyclic.add(_step5.value);
|
|
} catch (err) {
|
|
segment.e(err);
|
|
} finally {
|
|
segment.f();
|
|
}
|
|
return BigInt("0");
|
|
}
|
|
pathHistory.add(segment.id);
|
|
if (void 0 !== paths) return paths;
|
|
if (codePath.thrownSegments.includes(segment))
|
|
paths = BigInt("0");
|
|
else if (0 === segment.nextSegments.length) paths = BigInt("1");
|
|
else {
|
|
paths = BigInt("0");
|
|
_step5 = _createForOfIteratorHelper(segment.nextSegments);
|
|
var _step6;
|
|
try {
|
|
for (_step5.s(); !(_step6 = _step5.n()).done; )
|
|
paths += countPathsToEnd(_step6.value, pathHistory);
|
|
} catch (err$2) {
|
|
_step5.e(err$2);
|
|
} finally {
|
|
_step5.f();
|
|
}
|
|
}
|
|
cache.set(segment.id, paths);
|
|
return paths;
|
|
}
|
|
function shortestPathLengthToStart(segment) {
|
|
var cache = shortestPathLengthToStart.cache,
|
|
length = cache.get(segment.id);
|
|
if (null === length) return Infinity;
|
|
if (void 0 !== length) return length;
|
|
cache.set(segment.id, null);
|
|
if (0 === segment.prevSegments.length) length = 1;
|
|
else {
|
|
length = Infinity;
|
|
var _iterator7 = _createForOfIteratorHelper(
|
|
segment.prevSegments
|
|
),
|
|
_step7;
|
|
try {
|
|
for (_iterator7.s(); !(_step7 = _iterator7.n()).done; ) {
|
|
var prevLength = shortestPathLengthToStart(_step7.value);
|
|
prevLength < length && (length = prevLength);
|
|
}
|
|
} catch (err) {
|
|
_iterator7.e(err);
|
|
} finally {
|
|
_iterator7.f();
|
|
}
|
|
length += 1;
|
|
}
|
|
cache.set(segment.id, length);
|
|
return length;
|
|
}
|
|
var reactHooksMap = codePathReactHooksMapStack.pop();
|
|
if (0 !== reactHooksMap.size) {
|
|
var cyclic = new Set();
|
|
countPathsFromStart.cache = new Map();
|
|
countPathsToEnd.cache = new Map();
|
|
shortestPathLengthToStart.cache = new Map();
|
|
var allPathsFromStartToEnd = countPathsToEnd(
|
|
codePath.initialSegment
|
|
),
|
|
codePathFunctionName = getFunctionName(codePathNode),
|
|
isSomewhereInsideComponentOrHook =
|
|
isInsideComponentOrHook(codePathNode),
|
|
isDirectlyInsideComponentOrHook = codePathFunctionName
|
|
? isComponentName(codePathFunctionName) ||
|
|
isHook(codePathFunctionName)
|
|
: isForwardRefCallback(codePathNode) ||
|
|
isMemoCallback(codePathNode),
|
|
shortestFinalPathLength = Infinity,
|
|
_iterator8 = _createForOfIteratorHelper(
|
|
codePath.finalSegments
|
|
),
|
|
_step8;
|
|
try {
|
|
for (_iterator8.s(); !(_step8 = _iterator8.n()).done; ) {
|
|
var finalSegment = _step8.value;
|
|
if (finalSegment.reachable) {
|
|
var length$jscomp$0 =
|
|
shortestPathLengthToStart(finalSegment);
|
|
length$jscomp$0 < shortestFinalPathLength &&
|
|
(shortestFinalPathLength = length$jscomp$0);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
_iterator8.e(err);
|
|
} finally {
|
|
_iterator8.f();
|
|
}
|
|
reactHooksMap = _createForOfIteratorHelper(reactHooksMap);
|
|
var _step9;
|
|
try {
|
|
for (
|
|
reactHooksMap.s();
|
|
!(_step9 = reactHooksMap.n()).done;
|
|
|
|
) {
|
|
var _step9$value = _step9.value,
|
|
segment$jscomp$0 = _step9$value[0],
|
|
reactHooks = _step9$value[1];
|
|
if (segment$jscomp$0.reachable) {
|
|
var possiblyHasEarlyReturn =
|
|
0 === segment$jscomp$0.nextSegments.length
|
|
? shortestFinalPathLength <=
|
|
shortestPathLengthToStart(segment$jscomp$0)
|
|
: shortestFinalPathLength <
|
|
shortestPathLengthToStart(segment$jscomp$0),
|
|
pathsFromStartToEnd =
|
|
countPathsFromStart(segment$jscomp$0) *
|
|
countPathsToEnd(segment$jscomp$0),
|
|
cycled = cyclic.has(segment$jscomp$0.id),
|
|
_iterator10 = _createForOfIteratorHelper(reactHooks),
|
|
_step10;
|
|
try {
|
|
for (
|
|
_iterator10.s();
|
|
!(_step10 = _iterator10.n()).done;
|
|
|
|
) {
|
|
var hook = _step10.value;
|
|
cycled &&
|
|
!isReactFunction(hook, "use") &&
|
|
context.report({
|
|
node: hook,
|
|
message:
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render.'
|
|
});
|
|
if (isDirectlyInsideComponentOrHook) {
|
|
if (
|
|
(codePathNode.async &&
|
|
context.report({
|
|
node: hook,
|
|
message:
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" cannot be called in an async function.'
|
|
}),
|
|
!cycled &&
|
|
pathsFromStartToEnd !==
|
|
allPathsFromStartToEnd &&
|
|
!isReactFunction(hook, "use"))
|
|
) {
|
|
var message =
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" is called conditionally. React Hooks must be called in the exact same order in every component render.' +
|
|
(possiblyHasEarlyReturn
|
|
? " Did you accidentally call a React Hook after an early return?"
|
|
: "");
|
|
context.report({ node: hook, message: message });
|
|
}
|
|
} else if (
|
|
codePathNode.parent &&
|
|
("MethodDefinition" === codePathNode.parent.type ||
|
|
"ClassProperty" === codePathNode.parent.type) &&
|
|
codePathNode.parent.value === codePathNode
|
|
) {
|
|
var _message =
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.';
|
|
context.report({ node: hook, message: _message });
|
|
} else if (codePathFunctionName) {
|
|
var _message2 =
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" is called in function "' +
|
|
(getSource(codePathFunctionName) +
|
|
'" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".');
|
|
context.report({ node: hook, message: _message2 });
|
|
} else if ("Program" === codePathNode.type) {
|
|
var _message3 =
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.';
|
|
context.report({ node: hook, message: _message3 });
|
|
} else if (
|
|
isSomewhereInsideComponentOrHook &&
|
|
!isReactFunction(hook, "use")
|
|
) {
|
|
var _message4 =
|
|
'React Hook "' +
|
|
getSource(hook) +
|
|
'" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.';
|
|
context.report({ node: hook, message: _message4 });
|
|
}
|
|
}
|
|
} catch (err$3) {
|
|
_iterator10.e(err$3);
|
|
} finally {
|
|
_iterator10.f();
|
|
}
|
|
}
|
|
}
|
|
} catch (err$4) {
|
|
reactHooksMap.e(err$4);
|
|
} finally {
|
|
reactHooksMap.f();
|
|
}
|
|
}
|
|
},
|
|
CallExpression: function (node) {
|
|
if (isHook(node.callee)) {
|
|
var reactHooksMap =
|
|
codePathReactHooksMapStack[
|
|
codePathReactHooksMapStack.length - 1
|
|
],
|
|
codePathSegment =
|
|
codePathSegmentStack[codePathSegmentStack.length - 1],
|
|
reactHooks = reactHooksMap.get(codePathSegment);
|
|
reactHooks ||
|
|
((reactHooks = []),
|
|
reactHooksMap.set(codePathSegment, reactHooks));
|
|
reactHooks.push(node.callee);
|
|
}
|
|
"Identifier" === node.callee.type &&
|
|
"useEffect" === node.callee.name &&
|
|
0 < node.arguments.length &&
|
|
(lastEffect = node);
|
|
},
|
|
Identifier: function (node) {
|
|
null == lastEffect &&
|
|
useEffectEventFunctions.has(node) &&
|
|
"CallExpression" !== node.parent.type &&
|
|
context.report({
|
|
node: node,
|
|
message:
|
|
"`" +
|
|
getSource(node) +
|
|
'` is a function created with React Hook "useEffectEvent", and can only be called from the same component. They cannot be assigned to variables or passed down.'
|
|
});
|
|
},
|
|
"CallExpression:exit": function (node) {
|
|
node === lastEffect && (lastEffect = null);
|
|
},
|
|
FunctionDeclaration: function (node) {
|
|
isInsideComponentOrHook(node) &&
|
|
recordAllUseEffectEventFunctions(getScope(node));
|
|
},
|
|
ArrowFunctionExpression: function (node) {
|
|
isInsideComponentOrHook(node) &&
|
|
recordAllUseEffectEventFunctions(getScope(node));
|
|
}
|
|
};
|
|
}
|
|
},
|
|
"exhaustive-deps": {
|
|
meta: {
|
|
type: "suggestion",
|
|
docs: {
|
|
description:
|
|
"verifies the list of dependencies for Hooks like useEffect and similar",
|
|
recommended: !0,
|
|
url: "https://github.com/facebook/react/issues/14920"
|
|
},
|
|
fixable: "code",
|
|
hasSuggestions: !0,
|
|
schema: [
|
|
{
|
|
type: "object",
|
|
additionalProperties: !1,
|
|
enableDangerousAutofixThisMayCauseInfiniteLoops: !1,
|
|
properties: {
|
|
additionalHooks: { type: "string" },
|
|
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
|
type: "boolean"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
create: function (context) {
|
|
function reportProblem(problem) {
|
|
enableDangerousAutofixThisMayCauseInfiniteLoops &&
|
|
Array.isArray(problem.suggest) &&
|
|
0 < problem.suggest.length &&
|
|
(problem.fix = problem.suggest[0].fix);
|
|
context.report(problem);
|
|
}
|
|
function memoizeWithWeakMap(fn, map) {
|
|
return function (arg) {
|
|
if (map.has(arg)) return map.get(arg);
|
|
var result = fn(arg);
|
|
map.set(arg, result);
|
|
return result;
|
|
};
|
|
}
|
|
function visitFunctionWithDependencies(
|
|
node,
|
|
declaredDependenciesNode,
|
|
reactiveHook,
|
|
reactiveHookName,
|
|
isEffect
|
|
) {
|
|
function gatherDependenciesRecursively(currentScope) {
|
|
var _iterator2 = _createForOfIteratorHelper(
|
|
currentScope.references
|
|
),
|
|
_step2;
|
|
try {
|
|
for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
|
|
var reference = _step2.value;
|
|
if (
|
|
reference.resolved &&
|
|
pureScopes.has(reference.resolved.scope)
|
|
) {
|
|
var referenceNode = fastFindReferenceWithParent(
|
|
node,
|
|
reference.identifier
|
|
),
|
|
dependencyNode = getDependency(referenceNode),
|
|
dependency = analyzePropertyChain(
|
|
dependencyNode,
|
|
optionalChains
|
|
),
|
|
JSCompiler_temp;
|
|
if (
|
|
(JSCompiler_temp =
|
|
isEffect &&
|
|
"Identifier" === dependencyNode.type &&
|
|
("MemberExpression" === dependencyNode.parent.type ||
|
|
"OptionalMemberExpression" ===
|
|
dependencyNode.parent.type) &&
|
|
!dependencyNode.parent.computed &&
|
|
"Identifier" === dependencyNode.parent.property.type &&
|
|
"current" === dependencyNode.parent.property.name)
|
|
) {
|
|
for (
|
|
var curScope = reference.from,
|
|
isInReturnedFunction = !1;
|
|
curScope.block !== node;
|
|
|
|
)
|
|
"function" === curScope.type &&
|
|
(isInReturnedFunction =
|
|
null != curScope.block.parent &&
|
|
"ReturnStatement" === curScope.block.parent.type),
|
|
(curScope = curScope.upper);
|
|
JSCompiler_temp = isInReturnedFunction;
|
|
}
|
|
JSCompiler_temp &&
|
|
currentRefsInEffectCleanup.set(dependency, {
|
|
reference: reference,
|
|
dependencyNode: dependencyNode
|
|
});
|
|
if (
|
|
"TSTypeQuery" !== dependencyNode.parent.type &&
|
|
"TSTypeReference" !== dependencyNode.parent.type
|
|
) {
|
|
var def = reference.resolved.defs[0];
|
|
if (
|
|
null != def &&
|
|
(null == def.node || def.node.init !== node.parent) &&
|
|
"TypeParameter" !== def.type
|
|
)
|
|
if (dependencies.has(dependency))
|
|
dependencies
|
|
.get(dependency)
|
|
.references.push(reference);
|
|
else {
|
|
var resolved = reference.resolved,
|
|
isStable =
|
|
memoizedIsStableKnownHookValue(resolved) ||
|
|
memoizedIsFunctionWithoutCapturedValues(resolved);
|
|
dependencies.set(dependency, {
|
|
isStable: isStable,
|
|
references: [reference]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
_iterator2.e(err);
|
|
} finally {
|
|
_iterator2.f();
|
|
}
|
|
currentScope = _createForOfIteratorHelper(
|
|
currentScope.childScopes
|
|
);
|
|
var _step3;
|
|
try {
|
|
for (currentScope.s(); !(_step3 = currentScope.n()).done; )
|
|
gatherDependenciesRecursively(_step3.value);
|
|
} catch (err$5) {
|
|
currentScope.e(err$5);
|
|
} finally {
|
|
currentScope.f();
|
|
}
|
|
}
|
|
function formatDependency(path) {
|
|
path = path.split(".");
|
|
for (var finalPath = "", i = 0; i < path.length; i++) {
|
|
if (0 !== i) {
|
|
var pathSoFar = path.slice(0, i + 1).join(".");
|
|
pathSoFar = !0 === optionalChains.get(pathSoFar);
|
|
finalPath += pathSoFar ? "?." : ".";
|
|
}
|
|
finalPath += path[i];
|
|
}
|
|
return finalPath;
|
|
}
|
|
function getWarningMessage(deps, singlePrefix, label, fixVerb) {
|
|
return 0 === deps.size
|
|
? null
|
|
: (1 < deps.size ? "" : singlePrefix + " ") +
|
|
label +
|
|
" " +
|
|
(1 < deps.size ? "dependencies" : "dependency") +
|
|
": " +
|
|
joinEnglish(
|
|
Array.from(deps)
|
|
.sort()
|
|
.map(function (name) {
|
|
return "'" + formatDependency(name) + "'";
|
|
})
|
|
) +
|
|
(". Either " +
|
|
fixVerb +
|
|
" " +
|
|
(1 < deps.size ? "them" : "it") +
|
|
" or remove the dependency array.");
|
|
}
|
|
isEffect &&
|
|
node.async &&
|
|
reportProblem({
|
|
node: node,
|
|
message:
|
|
"Effect callbacks are synchronous to prevent race conditions. Put the async function inside:\n\nuseEffect(() => {\n async function fetchData() {\n // You can await here\n const response = await MyAPI.getData(someId);\n // ...\n }\n fetchData();\n}, [someId]); // Or [] if effect doesn't need props or state\n\nLearn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching"
|
|
});
|
|
for (
|
|
var scope = scopeManager.acquire(node),
|
|
pureScopes = new Set(),
|
|
componentScope = null,
|
|
currentScope = scope.upper;
|
|
currentScope;
|
|
|
|
) {
|
|
pureScopes.add(currentScope);
|
|
if ("function" === currentScope.type) break;
|
|
currentScope = currentScope.upper;
|
|
}
|
|
if (currentScope) {
|
|
componentScope = currentScope;
|
|
var isArray = Array.isArray,
|
|
memoizedIsStableKnownHookValue = memoizeWithWeakMap(function (
|
|
resolved
|
|
) {
|
|
if (!isArray(resolved.defs)) return !1;
|
|
var def = resolved.defs[0];
|
|
if (null == def || "VariableDeclarator" !== def.node.type)
|
|
return !1;
|
|
var init = def.node.init;
|
|
if (null == init) return !1;
|
|
for (
|
|
;
|
|
"TSAsExpression" === init.type ||
|
|
"AsExpression" === init.type;
|
|
|
|
)
|
|
init = init.expression;
|
|
var declaration = def.node.parent;
|
|
if (
|
|
null == declaration &&
|
|
(fastFindReferenceWithParent(
|
|
componentScope.block,
|
|
def.node.id
|
|
),
|
|
(declaration = def.node.parent),
|
|
null == declaration)
|
|
)
|
|
return !1;
|
|
if (
|
|
"const" === declaration.kind &&
|
|
"Literal" === init.type &&
|
|
("string" === typeof init.value ||
|
|
"number" === typeof init.value ||
|
|
null === init.value)
|
|
)
|
|
return !0;
|
|
if ("CallExpression" !== init.type) return !1;
|
|
init = init.callee;
|
|
"MemberExpression" !== init.type ||
|
|
"React" !== init.object.name ||
|
|
null == init.property ||
|
|
init.computed ||
|
|
(init = init.property);
|
|
if ("Identifier" !== init.type) return !1;
|
|
def = def.node.id;
|
|
init = init.name;
|
|
if ("useRef" === init && "Identifier" === def.type) return !0;
|
|
if (
|
|
"useState" === init ||
|
|
"useReducer" === init ||
|
|
"useActionState" === init
|
|
) {
|
|
if (
|
|
"ArrayPattern" === def.type &&
|
|
2 === def.elements.length &&
|
|
isArray(resolved.identifiers)
|
|
) {
|
|
if (def.elements[1] === resolved.identifiers[0]) {
|
|
if ("useState" === init)
|
|
for (
|
|
resolved = resolved.references,
|
|
declaration = init = 0;
|
|
declaration < resolved.length;
|
|
declaration++
|
|
) {
|
|
resolved[declaration].isWrite() && init++;
|
|
if (1 < init) return !1;
|
|
setStateCallSites.set(
|
|
resolved[declaration].identifier,
|
|
def.elements[0]
|
|
);
|
|
}
|
|
return !0;
|
|
}
|
|
if (
|
|
def.elements[0] === resolved.identifiers[0] &&
|
|
"useState" === init
|
|
)
|
|
for (
|
|
resolved = resolved.references, def = 0;
|
|
def < resolved.length;
|
|
def++
|
|
)
|
|
stateVariables.add(resolved[def].identifier);
|
|
}
|
|
} else if (
|
|
"useTransition" === init &&
|
|
"ArrayPattern" === def.type &&
|
|
2 === def.elements.length &&
|
|
Array.isArray(resolved.identifiers) &&
|
|
def.elements[1] === resolved.identifiers[0]
|
|
)
|
|
return !0;
|
|
return !1;
|
|
}, stableKnownValueCache),
|
|
memoizedIsFunctionWithoutCapturedValues = memoizeWithWeakMap(
|
|
function (resolved) {
|
|
if (!isArray(resolved.defs)) return !1;
|
|
resolved = resolved.defs[0];
|
|
if (
|
|
null == resolved ||
|
|
null == resolved.node ||
|
|
null == resolved.node.id
|
|
)
|
|
return !1;
|
|
var fnNode = resolved.node,
|
|
childScopes = componentScope.childScopes;
|
|
resolved = null;
|
|
var i;
|
|
for (i = 0; i < childScopes.length; i++) {
|
|
var childScope = childScopes[i],
|
|
childScopeBlock = childScope.block;
|
|
if (
|
|
("FunctionDeclaration" === fnNode.type &&
|
|
childScopeBlock === fnNode) ||
|
|
("VariableDeclarator" === fnNode.type &&
|
|
childScopeBlock.parent === fnNode)
|
|
) {
|
|
resolved = childScope;
|
|
break;
|
|
}
|
|
}
|
|
if (null == resolved) return !1;
|
|
for (i = 0; i < resolved.through.length; i++)
|
|
if (
|
|
((fnNode = resolved.through[i]),
|
|
null != fnNode.resolved &&
|
|
pureScopes.has(fnNode.resolved.scope) &&
|
|
!memoizedIsStableKnownHookValue(fnNode.resolved))
|
|
)
|
|
return !1;
|
|
return !0;
|
|
},
|
|
functionWithoutCapturedValueCache
|
|
),
|
|
currentRefsInEffectCleanup = new Map(),
|
|
dependencies = new Map(),
|
|
optionalChains = new Map();
|
|
gatherDependenciesRecursively(scope);
|
|
currentRefsInEffectCleanup.forEach(function (_ref, dependency) {
|
|
var dependencyNode = _ref.dependencyNode;
|
|
_ref = _ref.reference.resolved.references;
|
|
for (
|
|
var foundCurrentAssignment = !1, i = 0;
|
|
i < _ref.length;
|
|
i++
|
|
) {
|
|
var parent = _ref[i].identifier.parent;
|
|
if (
|
|
null != parent &&
|
|
"MemberExpression" === parent.type &&
|
|
!parent.computed &&
|
|
"Identifier" === parent.property.type &&
|
|
"current" === parent.property.name &&
|
|
"AssignmentExpression" === parent.parent.type &&
|
|
parent.parent.left === parent
|
|
) {
|
|
foundCurrentAssignment = !0;
|
|
break;
|
|
}
|
|
}
|
|
foundCurrentAssignment ||
|
|
reportProblem({
|
|
node: dependencyNode.parent.property,
|
|
message:
|
|
"The ref value '" +
|
|
dependency +
|
|
".current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy '" +
|
|
(dependency +
|
|
".current' to a variable inside the effect, and use that variable in the cleanup function.")
|
|
});
|
|
});
|
|
var staleAssignments = new Set(),
|
|
stableDependencies = new Set();
|
|
dependencies.forEach(function (_ref2, key) {
|
|
var references = _ref2.references;
|
|
_ref2.isStable && stableDependencies.add(key);
|
|
references.forEach(function (reference) {
|
|
reference.writeExpr &&
|
|
((reference = reference.writeExpr),
|
|
staleAssignments.has(key) ||
|
|
(staleAssignments.add(key),
|
|
reportProblem({
|
|
node: reference,
|
|
message:
|
|
"Assignments to the '" +
|
|
key +
|
|
"' variable from inside React Hook " +
|
|
(getSource(reactiveHook) +
|
|
" will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside ") +
|
|
(getSource(reactiveHook) + ".")
|
|
})));
|
|
});
|
|
});
|
|
if (!(0 < staleAssignments.size))
|
|
if (declaredDependenciesNode) {
|
|
var declaredDependencies = [],
|
|
externalDependencies = new Set();
|
|
currentScope =
|
|
"TSAsExpression" === declaredDependenciesNode.type &&
|
|
"ArrayExpression" ===
|
|
declaredDependenciesNode.expression.type;
|
|
"ArrayExpression" === declaredDependenciesNode.type ||
|
|
currentScope
|
|
? (currentScope
|
|
? declaredDependenciesNode.expression
|
|
: declaredDependenciesNode
|
|
).elements.forEach(function (declaredDependencyNode) {
|
|
if (null !== declaredDependencyNode)
|
|
if ("SpreadElement" === declaredDependencyNode.type)
|
|
reportProblem({
|
|
node: declaredDependencyNode,
|
|
message:
|
|
"React Hook " +
|
|
getSource(reactiveHook) +
|
|
" has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies."
|
|
});
|
|
else {
|
|
useEffectEventVariables.has(
|
|
declaredDependencyNode
|
|
) &&
|
|
reportProblem({
|
|
node: declaredDependencyNode,
|
|
message:
|
|
"Functions returned from `useEffectEvent` must not be included in the dependency array. Remove `" +
|
|
(getSource(declaredDependencyNode) +
|
|
"` from the list."),
|
|
suggest: [
|
|
{
|
|
desc:
|
|
"Remove the dependency `" +
|
|
getSource(declaredDependencyNode) +
|
|
"`",
|
|
fix: function (fixer) {
|
|
return fixer.removeRange(
|
|
declaredDependencyNode.range
|
|
);
|
|
}
|
|
}
|
|
]
|
|
});
|
|
try {
|
|
var declaredDependency = analyzePropertyChain(
|
|
declaredDependencyNode,
|
|
null
|
|
);
|
|
} catch (error) {
|
|
if (/Unsupported node type/.test(error.message)) {
|
|
"Literal" === declaredDependencyNode.type
|
|
? dependencies.has(
|
|
declaredDependencyNode.value
|
|
)
|
|
? reportProblem({
|
|
node: declaredDependencyNode,
|
|
message:
|
|
"The " +
|
|
declaredDependencyNode.raw +
|
|
" literal is not a valid dependency because it never changes. Did you mean to include " +
|
|
(declaredDependencyNode.value +
|
|
" in the array instead?")
|
|
})
|
|
: reportProblem({
|
|
node: declaredDependencyNode,
|
|
message:
|
|
"The " +
|
|
declaredDependencyNode.raw +
|
|
" literal is not a valid dependency because it never changes. You can safely remove it."
|
|
})
|
|
: reportProblem({
|
|
node: declaredDependencyNode,
|
|
message:
|
|
"React Hook " +
|
|
getSource(reactiveHook) +
|
|
" has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked."
|
|
});
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
for (
|
|
var maybeID = declaredDependencyNode;
|
|
"MemberExpression" === maybeID.type ||
|
|
"OptionalMemberExpression" === maybeID.type ||
|
|
"ChainExpression" === maybeID.type;
|
|
|
|
)
|
|
maybeID =
|
|
maybeID.object || maybeID.expression.object;
|
|
var isDeclaredInComponent =
|
|
!componentScope.through.some(function (ref) {
|
|
return ref.identifier === maybeID;
|
|
});
|
|
declaredDependencies.push({
|
|
key: declaredDependency,
|
|
node: declaredDependencyNode
|
|
});
|
|
isDeclaredInComponent ||
|
|
externalDependencies.add(declaredDependency);
|
|
}
|
|
})
|
|
: reportProblem({
|
|
node: declaredDependenciesNode,
|
|
message:
|
|
"React Hook " +
|
|
getSource(reactiveHook) +
|
|
" was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies."
|
|
});
|
|
var _collectRecommendatio2 = collectRecommendations({
|
|
dependencies: dependencies,
|
|
declaredDependencies: declaredDependencies,
|
|
stableDependencies: stableDependencies,
|
|
externalDependencies: externalDependencies,
|
|
isEffect: isEffect
|
|
});
|
|
currentScope = _collectRecommendatio2.unnecessaryDependencies;
|
|
var missingDependencies =
|
|
_collectRecommendatio2.missingDependencies,
|
|
duplicateDependencies =
|
|
_collectRecommendatio2.duplicateDependencies,
|
|
suggestedDeps =
|
|
_collectRecommendatio2.suggestedDependencies;
|
|
if (
|
|
0 ===
|
|
duplicateDependencies.size +
|
|
missingDependencies.size +
|
|
currentScope.size
|
|
)
|
|
scanForConstructions({
|
|
declaredDependencies: declaredDependencies,
|
|
declaredDependenciesNode: declaredDependenciesNode,
|
|
componentScope: componentScope,
|
|
scope: scope
|
|
}).forEach(function (_ref4) {
|
|
var construction = _ref4.construction,
|
|
isUsedOutsideOfHook = _ref4.isUsedOutsideOfHook;
|
|
_ref4 = _ref4.depType;
|
|
var wrapperHook =
|
|
"function" === _ref4 ? "useCallback" : "useMemo",
|
|
constructionType =
|
|
"function" === _ref4
|
|
? "definition"
|
|
: "initialization",
|
|
defaultAdvice =
|
|
"wrap the " +
|
|
constructionType +
|
|
" of '" +
|
|
construction.name.name +
|
|
"' in its own " +
|
|
wrapperHook +
|
|
"() Hook.";
|
|
defaultAdvice =
|
|
"The '" +
|
|
construction.name.name +
|
|
"' " +
|
|
_ref4 +
|
|
" " +
|
|
("conditional" === _ref4 ||
|
|
"logical expression" === _ref4
|
|
? "could make"
|
|
: "makes") +
|
|
" the dependencies of " +
|
|
(reactiveHookName +
|
|
" Hook (at line " +
|
|
declaredDependenciesNode.loc.start.line +
|
|
") change on every render. ") +
|
|
(isUsedOutsideOfHook
|
|
? "To fix this, " + defaultAdvice
|
|
: "Move it inside the " +
|
|
reactiveHookName +
|
|
" callback. Alternatively, " +
|
|
defaultAdvice);
|
|
var suggest;
|
|
isUsedOutsideOfHook &&
|
|
"Variable" === construction.type &&
|
|
"function" === _ref4 &&
|
|
(suggest = [
|
|
{
|
|
desc:
|
|
"Wrap the " +
|
|
constructionType +
|
|
" of '" +
|
|
construction.name.name +
|
|
"' in its own " +
|
|
wrapperHook +
|
|
"() Hook.",
|
|
fix: function (fixer) {
|
|
var _ref5 =
|
|
"useMemo" === wrapperHook
|
|
? ["useMemo(() => { return ", "; })"]
|
|
: ["useCallback(", ")"],
|
|
after = _ref5[1];
|
|
return [
|
|
fixer.insertTextBefore(
|
|
construction.node.init,
|
|
_ref5[0]
|
|
),
|
|
fixer.insertTextAfter(
|
|
construction.node.init,
|
|
after
|
|
)
|
|
];
|
|
}
|
|
}
|
|
]);
|
|
reportProblem({
|
|
node: construction.node,
|
|
message: defaultAdvice,
|
|
suggest: suggest
|
|
});
|
|
});
|
|
else {
|
|
!isEffect &&
|
|
0 < missingDependencies.size &&
|
|
(suggestedDeps = collectRecommendations({
|
|
dependencies: dependencies,
|
|
declaredDependencies: [],
|
|
stableDependencies: stableDependencies,
|
|
externalDependencies: externalDependencies,
|
|
isEffect: isEffect
|
|
}).suggestedDependencies);
|
|
(function () {
|
|
if (0 === declaredDependencies.length) return !0;
|
|
var declaredDepKeys = declaredDependencies.map(
|
|
function (dep) {
|
|
return dep.key;
|
|
}
|
|
),
|
|
sortedDeclaredDepKeys = declaredDepKeys.slice().sort();
|
|
return (
|
|
declaredDepKeys.join(",") ===
|
|
sortedDeclaredDepKeys.join(",")
|
|
);
|
|
})() && suggestedDeps.sort();
|
|
_collectRecommendatio2 = "";
|
|
if (0 < currentScope.size) {
|
|
var badRef = null;
|
|
Array.from(currentScope.keys()).forEach(function (key) {
|
|
null === badRef &&
|
|
key.endsWith(".current") &&
|
|
(badRef = key);
|
|
});
|
|
if (null !== badRef)
|
|
_collectRecommendatio2 =
|
|
" Mutable values like '" +
|
|
badRef +
|
|
"' aren't valid dependencies because mutating them doesn't re-render the component.";
|
|
else if (0 < externalDependencies.size) {
|
|
var dep = Array.from(externalDependencies)[0];
|
|
scope.set.has(dep) ||
|
|
(_collectRecommendatio2 =
|
|
" Outer scope values like '" +
|
|
dep +
|
|
"' aren't valid dependencies because mutating them doesn't re-render the component.");
|
|
}
|
|
}
|
|
if (
|
|
!_collectRecommendatio2 &&
|
|
missingDependencies.has("props")
|
|
) {
|
|
scope = dependencies.get("props");
|
|
if (null == scope) return;
|
|
scope = scope.references;
|
|
if (!Array.isArray(scope)) return;
|
|
dep = !0;
|
|
for (
|
|
var i$jscomp$0 = 0;
|
|
i$jscomp$0 < scope.length;
|
|
i$jscomp$0++
|
|
) {
|
|
var id = fastFindReferenceWithParent(
|
|
componentScope.block,
|
|
scope[i$jscomp$0].identifier
|
|
);
|
|
if (!id) {
|
|
dep = !1;
|
|
break;
|
|
}
|
|
id = id.parent;
|
|
if (null == id) {
|
|
dep = !1;
|
|
break;
|
|
}
|
|
if (
|
|
"MemberExpression" !== id.type &&
|
|
"OptionalMemberExpression" !== id.type
|
|
) {
|
|
dep = !1;
|
|
break;
|
|
}
|
|
}
|
|
dep &&
|
|
(_collectRecommendatio2 =
|
|
" However, 'props' will change when *any* prop changes, so the preferred fix is to destructure the 'props' object outside of the " +
|
|
(reactiveHookName +
|
|
" call and refer to those specific props inside ") +
|
|
(getSource(reactiveHook) + "."));
|
|
}
|
|
if (
|
|
!_collectRecommendatio2 &&
|
|
0 < missingDependencies.size
|
|
) {
|
|
var missingCallbackDep = null;
|
|
missingDependencies.forEach(function (missingDep) {
|
|
if (!missingCallbackDep) {
|
|
var topScopeRef = componentScope.set.get(missingDep),
|
|
usedDep = dependencies.get(missingDep);
|
|
if (
|
|
usedDep.references[0].resolved === topScopeRef &&
|
|
((topScopeRef = topScopeRef.defs[0]),
|
|
null != topScopeRef &&
|
|
null != topScopeRef.name &&
|
|
"Parameter" === topScopeRef.type)
|
|
) {
|
|
topScopeRef = !1;
|
|
for (
|
|
var id, _i2 = 0;
|
|
_i2 < usedDep.references.length;
|
|
_i2++
|
|
)
|
|
if (
|
|
((id = usedDep.references[_i2].identifier),
|
|
null != id &&
|
|
null != id.parent &&
|
|
("CallExpression" === id.parent.type ||
|
|
"OptionalCallExpression" ===
|
|
id.parent.type) &&
|
|
id.parent.callee === id)
|
|
) {
|
|
topScopeRef = !0;
|
|
break;
|
|
}
|
|
topScopeRef && (missingCallbackDep = missingDep);
|
|
}
|
|
}
|
|
});
|
|
null !== missingCallbackDep &&
|
|
(_collectRecommendatio2 =
|
|
" If '" +
|
|
missingCallbackDep +
|
|
"' changes too often, find the parent component that defines it and wrap that definition in useCallback.");
|
|
}
|
|
if (
|
|
!_collectRecommendatio2 &&
|
|
0 < missingDependencies.size
|
|
) {
|
|
var setStateRecommendation = null;
|
|
missingDependencies.forEach(function (missingDep) {
|
|
if (null === setStateRecommendation)
|
|
for (
|
|
var references =
|
|
dependencies.get(missingDep).references,
|
|
id,
|
|
maybeCall,
|
|
_i3 = 0;
|
|
_i3 < references.length;
|
|
_i3++
|
|
) {
|
|
id = references[_i3].identifier;
|
|
for (
|
|
maybeCall = id.parent;
|
|
null != maybeCall &&
|
|
maybeCall !== componentScope.block;
|
|
|
|
) {
|
|
if ("CallExpression" === maybeCall.type) {
|
|
var correspondingStateVariable =
|
|
setStateCallSites.get(maybeCall.callee);
|
|
if (null != correspondingStateVariable) {
|
|
correspondingStateVariable.name === missingDep
|
|
? (setStateRecommendation = {
|
|
missingDep: missingDep,
|
|
setter: maybeCall.callee.name,
|
|
form: "updater"
|
|
})
|
|
: stateVariables.has(id)
|
|
? (setStateRecommendation = {
|
|
missingDep: missingDep,
|
|
setter: maybeCall.callee.name,
|
|
form: "reducer"
|
|
})
|
|
: ((id = references[_i3].resolved),
|
|
null != id &&
|
|
((id = id.defs[0]),
|
|
null != id &&
|
|
"Parameter" === id.type &&
|
|
(setStateRecommendation = {
|
|
missingDep: missingDep,
|
|
setter: maybeCall.callee.name,
|
|
form: "inlineReducer"
|
|
})));
|
|
break;
|
|
}
|
|
}
|
|
maybeCall = maybeCall.parent;
|
|
}
|
|
if (null !== setStateRecommendation) break;
|
|
}
|
|
});
|
|
if (null !== setStateRecommendation)
|
|
switch (setStateRecommendation.form) {
|
|
case "reducer":
|
|
_collectRecommendatio2 =
|
|
" You can also replace multiple useState variables with useReducer if '" +
|
|
(setStateRecommendation.setter +
|
|
"' needs the current value of '") +
|
|
(setStateRecommendation.missingDep + "'.");
|
|
break;
|
|
case "inlineReducer":
|
|
_collectRecommendatio2 =
|
|
" If '" +
|
|
setStateRecommendation.setter +
|
|
"' needs the current value of '" +
|
|
(setStateRecommendation.missingDep +
|
|
"', you can also switch to useReducer instead of useState and read '") +
|
|
(setStateRecommendation.missingDep +
|
|
"' in the reducer.");
|
|
break;
|
|
case "updater":
|
|
_collectRecommendatio2 =
|
|
" You can also do a functional update '" +
|
|
setStateRecommendation.setter +
|
|
"(" +
|
|
setStateRecommendation.missingDep.slice(0, 1) +
|
|
" => ...)' if you only need '" +
|
|
setStateRecommendation.missingDep +
|
|
"' in the '" +
|
|
(setStateRecommendation.setter + "' call.");
|
|
break;
|
|
default:
|
|
throw Error("Unknown case.");
|
|
}
|
|
}
|
|
reportProblem({
|
|
node: declaredDependenciesNode,
|
|
message:
|
|
"React Hook " +
|
|
getSource(reactiveHook) +
|
|
" has " +
|
|
(getWarningMessage(
|
|
missingDependencies,
|
|
"a",
|
|
"missing",
|
|
"include"
|
|
) ||
|
|
getWarningMessage(
|
|
currentScope,
|
|
"an",
|
|
"unnecessary",
|
|
"exclude"
|
|
) ||
|
|
getWarningMessage(
|
|
duplicateDependencies,
|
|
"a",
|
|
"duplicate",
|
|
"omit"
|
|
)) +
|
|
_collectRecommendatio2,
|
|
suggest: [
|
|
{
|
|
desc:
|
|
"Update the dependencies array to be: [" +
|
|
suggestedDeps.map(formatDependency).join(", ") +
|
|
"]",
|
|
fix: function (fixer) {
|
|
return fixer.replaceText(
|
|
declaredDependenciesNode,
|
|
"[" +
|
|
suggestedDeps.map(formatDependency).join(", ") +
|
|
"]"
|
|
);
|
|
}
|
|
}
|
|
]
|
|
});
|
|
}
|
|
} else {
|
|
var setStateInsideEffectWithoutDeps = null;
|
|
dependencies.forEach(function (_ref3, key) {
|
|
setStateInsideEffectWithoutDeps ||
|
|
_ref3.references.forEach(function (reference) {
|
|
if (
|
|
!setStateInsideEffectWithoutDeps &&
|
|
setStateCallSites.has(reference.identifier)
|
|
) {
|
|
for (
|
|
reference = reference.from;
|
|
"function" !== reference.type;
|
|
|
|
)
|
|
reference = reference.upper;
|
|
reference.block === node &&
|
|
(setStateInsideEffectWithoutDeps = key);
|
|
}
|
|
});
|
|
});
|
|
if (setStateInsideEffectWithoutDeps) {
|
|
var _suggestedDependencies = collectRecommendations({
|
|
dependencies: dependencies,
|
|
declaredDependencies: [],
|
|
stableDependencies: stableDependencies,
|
|
externalDependencies: new Set(),
|
|
isEffect: !0
|
|
}).suggestedDependencies;
|
|
reportProblem({
|
|
node: reactiveHook,
|
|
message:
|
|
"React Hook " +
|
|
reactiveHookName +
|
|
" contains a call to '" +
|
|
setStateInsideEffectWithoutDeps +
|
|
"'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [" +
|
|
_suggestedDependencies.join(", ") +
|
|
("] as a second argument to the " +
|
|
reactiveHookName +
|
|
" Hook."),
|
|
suggest: [
|
|
{
|
|
desc:
|
|
"Add dependencies array: [" +
|
|
_suggestedDependencies.join(", ") +
|
|
"]",
|
|
fix: function (fixer) {
|
|
return fixer.insertTextAfter(
|
|
node,
|
|
", [" + _suggestedDependencies.join(", ") + "]"
|
|
);
|
|
}
|
|
}
|
|
]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var enableDangerousAutofixThisMayCauseInfiniteLoops =
|
|
(context.options &&
|
|
context.options[0] &&
|
|
context.options[0]
|
|
.enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
|
!1,
|
|
options = {
|
|
additionalHooks:
|
|
context.options &&
|
|
context.options[0] &&
|
|
context.options[0].additionalHooks
|
|
? new RegExp(context.options[0].additionalHooks)
|
|
: void 0,
|
|
enableDangerousAutofixThisMayCauseInfiniteLoops:
|
|
enableDangerousAutofixThisMayCauseInfiniteLoops
|
|
},
|
|
getSource =
|
|
"function" === typeof context.getSource
|
|
? function (node) {
|
|
return context.getSource(node);
|
|
}
|
|
: function (node) {
|
|
return context.sourceCode.getText(node);
|
|
},
|
|
getScope =
|
|
"function" === typeof context.getScope
|
|
? function () {
|
|
return context.getScope();
|
|
}
|
|
: function (node) {
|
|
return context.sourceCode.getScope(node);
|
|
},
|
|
scopeManager = context.getSourceCode().scopeManager,
|
|
setStateCallSites = new WeakMap(),
|
|
stateVariables = new WeakSet(),
|
|
stableKnownValueCache = new WeakMap(),
|
|
functionWithoutCapturedValueCache = new WeakMap(),
|
|
useEffectEventVariables = new WeakSet();
|
|
return {
|
|
CallExpression: function (node) {
|
|
var callbackIndex = getReactiveHookCallbackIndex(
|
|
node.callee,
|
|
options
|
|
);
|
|
if (-1 !== callbackIndex) {
|
|
var callback = node.arguments[callbackIndex],
|
|
reactiveHook = node.callee,
|
|
reactiveHookName =
|
|
getNodeWithoutReactNamespace(reactiveHook).name;
|
|
node = node.arguments[callbackIndex + 1];
|
|
var declaredDependenciesNode =
|
|
!node ||
|
|
("Identifier" === node.type && "undefined" === node.name)
|
|
? void 0
|
|
: node;
|
|
node = /Effect($|[^a-z])/g.test(reactiveHookName);
|
|
if (callback)
|
|
if (declaredDependenciesNode || node) {
|
|
switch (callback.type) {
|
|
case "FunctionExpression":
|
|
case "ArrowFunctionExpression":
|
|
visitFunctionWithDependencies(
|
|
callback,
|
|
declaredDependenciesNode,
|
|
reactiveHook,
|
|
reactiveHookName,
|
|
node
|
|
);
|
|
return;
|
|
case "TSAsExpression":
|
|
visitFunctionWithDependencies(
|
|
callback.expression,
|
|
declaredDependenciesNode,
|
|
reactiveHook,
|
|
reactiveHookName,
|
|
node
|
|
);
|
|
return;
|
|
case "Identifier":
|
|
if (
|
|
!declaredDependenciesNode ||
|
|
(declaredDependenciesNode.elements &&
|
|
declaredDependenciesNode.elements.some(
|
|
function (el) {
|
|
return (
|
|
el &&
|
|
"Identifier" === el.type &&
|
|
el.name === callback.name
|
|
);
|
|
}
|
|
))
|
|
)
|
|
return;
|
|
callbackIndex = getScope(callback).set.get(
|
|
callback.name
|
|
);
|
|
if (null == callbackIndex || null == callbackIndex.defs)
|
|
return;
|
|
callbackIndex = callbackIndex.defs[0];
|
|
if (!callbackIndex || !callbackIndex.node) break;
|
|
if (
|
|
"Variable" !== callbackIndex.type &&
|
|
"FunctionName" !== callbackIndex.type
|
|
)
|
|
break;
|
|
switch (callbackIndex.node.type) {
|
|
case "FunctionDeclaration":
|
|
visitFunctionWithDependencies(
|
|
callbackIndex.node,
|
|
declaredDependenciesNode,
|
|
reactiveHook,
|
|
reactiveHookName,
|
|
node
|
|
);
|
|
return;
|
|
case "VariableDeclarator":
|
|
if ((callbackIndex = callbackIndex.node.init))
|
|
switch (callbackIndex.type) {
|
|
case "ArrowFunctionExpression":
|
|
case "FunctionExpression":
|
|
visitFunctionWithDependencies(
|
|
callbackIndex,
|
|
declaredDependenciesNode,
|
|
reactiveHook,
|
|
reactiveHookName,
|
|
node
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
reportProblem({
|
|
node: reactiveHook,
|
|
message:
|
|
"React Hook " +
|
|
reactiveHookName +
|
|
" received a function whose dependencies are unknown. Pass an inline function instead."
|
|
});
|
|
return;
|
|
}
|
|
reportProblem({
|
|
node: reactiveHook,
|
|
message:
|
|
"React Hook " +
|
|
reactiveHookName +
|
|
" has a missing dependency: '" +
|
|
callback.name +
|
|
"'. Either include it or remove the dependency array.",
|
|
suggest: [
|
|
{
|
|
desc:
|
|
"Update the dependencies array to be: [" +
|
|
callback.name +
|
|
"]",
|
|
fix: function (fixer) {
|
|
return fixer.replaceText(
|
|
declaredDependenciesNode,
|
|
"[" + callback.name + "]"
|
|
);
|
|
}
|
|
}
|
|
]
|
|
});
|
|
} else
|
|
("useMemo" !== reactiveHookName &&
|
|
"useCallback" !== reactiveHookName) ||
|
|
reportProblem({
|
|
node: reactiveHook,
|
|
message:
|
|
"React Hook " +
|
|
reactiveHookName +
|
|
" does nothing when called with only one argument. Did you forget to pass an array of dependencies?"
|
|
});
|
|
else
|
|
reportProblem({
|
|
node: reactiveHook,
|
|
message:
|
|
"React Hook " +
|
|
reactiveHookName +
|
|
" requires an effect callback. Did you forget to pass a callback to the hook?"
|
|
});
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
};
|
|
})();
|