Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@arthurgeron/eslint-plugin-react-usememo

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@arthurgeron/eslint-plugin-react-usememo - npm Package Compare versions

Comparing version 1.0.1 to 1.1.0--beta0

107

dist/index.js

@@ -45,2 +45,3 @@ 'use strict';

MemoStatus[MemoStatus["UnmemoizedOther"] = 7] = "UnmemoizedOther";
MemoStatus[MemoStatus["UnsafeLet"] = 8] = "UnsafeLet";
})(MemoStatus || (MemoStatus = {}));

@@ -78,7 +79,3 @@ function isComponentName(name) {

if (node.parent.kind === "let") {
context.report({ node: node, messageId: "usememo-const" });
if (!node.init) {
// Rely on usememo-const reported error to fail this identifier
return MemoStatus.Memoized;
}
return MemoStatus.UnsafeLet;
}

@@ -182,11 +179,29 @@ return getExpressionMemoStatus(context, node.init);

/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var _a, _b, _c, _d;
var ValidExpressions = {
'ArrowFunctionExpression': true,
'ObjectExpression': true,
'ArrayExpression': true,
'LogicalExpression': true,
'Identifier': true,
'JSXEmptyExpression': false
};
var jsxEmptyExpressionClassData = (_a = {},

@@ -199,2 +214,3 @@ _a[MemoStatus.UnmemoizedObject.toString()] = "object-class-memo-props",

_a[MemoStatus.UnmemoizedOther.toString()] = "unknown-class-memo-props",
_a[MemoStatus.UnsafeLet.toString()] = "usememo-const",
_a);

@@ -209,2 +225,3 @@ var jsxEmptyExpressionData = (_b = {},

_b[MemoStatus.UnmemoizedJSX.toString()] = "jsx-usememo-props",
_b[MemoStatus.UnsafeLet.toString()] = "usememo-const",
_b);

@@ -219,2 +236,3 @@ var hookReturnExpressionData = (_c = {},

_c[MemoStatus.UnmemoizedJSX.toString()] = "jsx-usememo-hook",
_c[MemoStatus.UnsafeLet.toString()] = "usememo-const",
_c);

@@ -229,3 +247,25 @@ var callExpressionData = (_d = {},

_d[MemoStatus.UnmemoizedJSX.toString()] = "jsx-usememo-deps",
_d[MemoStatus.UnsafeLet.toString()] = "usememo-const",
_d);
var defaultReactHookNames = {
"useContext": true,
"useState": true,
"useReducer": true,
"useRef": true,
"useLayoutEffect": true,
"useEffect": true,
"useImperativeHandle": true,
"useCallback": true,
"useMemo": true,
"useDebugValue": true,
"useDeferredValue": true,
"useTransition": true,
"useId": true,
"useInsertionEffect": true,
"useSyncExternalStore": true,
"useQuery": true,
"useMutation": true,
"useQueryClient": true,
"useInfiniteQuery": true
};

@@ -256,3 +296,3 @@ var MessagesRequireUseMemo = {

"unknown-usememo-deps": "Unknown value may need to be wrapped in useMemo() when used as a hook dependency",
"usememo-const": "useMemo/useCallback return value should be assigned to a const to prevent reassignment"
"usememo-const": "useMemo/useCallback return value should be assigned to a `const` to prevent reassignment"
};

@@ -266,5 +306,11 @@ var MessagesRequireUseMemoChildren = {

"unknown-usememo-children": "Unknown value may need to be wrapped in React.useMemo() when used as children",
"usememo-const": "useMemo/useCallback return value should be assigned to a const to prevent reassignment"
"usememo-const": "useMemo/useCallback return value should be assigned to a `const` to prevent reassignment"
};
function shouldIgnoreNode(node, ignoredNames) {
var _a, _b;
return !!ignoredNames[node === null || node === void 0 ? void 0 : node.name]
|| !!ignoredNames[node.callee.name]
|| !!ignoredNames[(_b = (_a = node === null || node === void 0 ? void 0 : node.callee) === null || _a === void 0 ? void 0 : _a.property) === null || _b === void 0 ? void 0 : _b.name];
}
function checkForErrors(data, expressionType, context, node, report) {

@@ -306,3 +352,3 @@ var _a, _b;

type: "object",
properties: { strict: { type: "boolean" }, checkHookReturnObject: { type: "boolean" } },
properties: { strict: { type: "boolean" }, checkHookReturnObject: { type: "boolean" }, checkHookCalls: { type: "boolean" }, ignoredHookCallsNames: { type: "object" } },
additionalProperties: false

@@ -348,7 +394,10 @@ },

ReturnStatement: function (node) {
var _a, _b;
if (node.parent.parent.type === 'FunctionDeclaration' && getIsHook(node.parent.parent.id) && node.argument) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
var functionDeclarationNode = ((_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.type) === 'FunctionDeclaration' && ((_d = (_c = node === null || node === void 0 ? void 0 : node.parent) === null || _c === void 0 ? void 0 : _c.parent) === null || _d === void 0 ? void 0 : _d.id);
var anonFuncVariableDeclarationNode = ((_f = (_e = node.parent) === null || _e === void 0 ? void 0 : _e.parent) === null || _f === void 0 ? void 0 : _f.type) === 'ArrowFunctionExpression' && ((_j = (_h = (_g = node === null || node === void 0 ? void 0 : node.parent) === null || _g === void 0 ? void 0 : _g.parent) === null || _h === void 0 ? void 0 : _h.parent) === null || _j === void 0 ? void 0 : _j.type) === 'VariableDeclarator' && ((_m = (_l = (_k = node === null || node === void 0 ? void 0 : node.parent) === null || _k === void 0 ? void 0 : _k.parent) === null || _l === void 0 ? void 0 : _l.parent) === null || _m === void 0 ? void 0 : _m.id);
var validNode = functionDeclarationNode || anonFuncVariableDeclarationNode;
if (validNode && getIsHook(validNode) && node.argument) {
if (node.argument.type === 'ObjectExpression') {
if ((_b = (_a = context.options) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.checkHookReturnObject) {
context.report({ node: node, messageId: "object-usememo-hook" });
if ((_p = (_o = context.options) === null || _o === void 0 ? void 0 : _o[0]) === null || _p === void 0 ? void 0 : _p.checkHookReturnObject) {
report(node, "object-usememo-hook");
return;

@@ -366,12 +415,14 @@ }

CallExpression: function (node) {
var _a, _b, _c, _d, _e;
var callee = node.callee;
if (!getIsHook(callee))
var ignoredNames = __assign(__assign({}, defaultReactHookNames), ((_c = (_b = (_a = context.options) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.ignoredHookCallsNames) !== null && _c !== void 0 ? _c : {}));
if (((_e = (_d = context.options) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.checkHookCalls) === false
|| !getIsHook(callee)) {
return;
var _a = node.arguments, dependencies = _a[1];
if (dependencies !== undefined &&
dependencies.type === "ArrayExpression") {
for (var _i = 0, _b = dependencies.elements; _i < _b.length; _i++) {
var dep = _b[_i];
if (dep !== null && ValidExpressions[dep.type]) {
checkForErrors(callExpressionData, getExpressionMemoStatus(context, dep), context, node, report);
}
if (!shouldIgnoreNode(node, ignoredNames)) {
for (var _i = 0, _f = node.arguments; _i < _f.length; _i++) {
var argument = _f[_i];
if (argument.type !== 'SpreadElement') {
checkForErrors(callExpressionData, getExpressionMemoStatus(context, argument), context, node, report);
}

@@ -378,0 +429,0 @@ }

{
"name": "@arthurgeron/eslint-plugin-react-usememo",
"version": "1.0.1",
"version": "1.1.0--beta0",
"description": "",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -42,37 +42,42 @@ # eslint-plugin-react-usememo

- `{strict: true}`: Fails even in cases where it is difficult to determine if the value in question is a primitive (string or number) or a complex value (object, array, etc.);
- `{checkHookReturnObject: true}`: Will require Object Expressions passed in return statements (e.g. `return {someFunc}`) to also be memoised (e.g. `return useMemo(() => ({someFunc}), [someFunc])`); **Disabled** by default.
- `{checkHookReturnObject: true}`: Will require Object Expressions passed in return statements (e.g. `return {someFunc}`) to also be memoised (e.g. `return useMemo(() => ({someFunc}), [someFunc])`); **Disabled** by default;
- `{checkHookCalls: true}`: Will require objects/data passed to a non-native/Custom hook to be memoized (e.g. in this code `const data = useParse(unparsedData)` it will check `unparsedData` for memo status), this check can be ignored by a hook/name basis, check next item for details; **Enabled** by default;
- `{ignoredHookCallsNames: Record<string, boolean>}`: You can add specific hooks names here, individually disabling or enabling them to be checked when used, e.g. if you have a custom hook named `useX` and it should be called with unmemoized parameters you can set `{ ignoredHookCallsNames: { useX: false } }` and then have somethning like `const data = {}; const x = useX(data);` not generate any errors.
### Function Components
#### **Incorrect**
```JavaScript
function Component() {
function Component() {
const [data, setData] = useState([]);
// This will be redeclared each render
function renderItem({ item }) {
return (<Text>item.name</Text>);
const [data, setData] = useState([]);
// This will be redeclared each render
function renderItem({ item }) {
return (<Text>item.name</Text>);
}
// Data isn't redeclared each ender but `[]` is
return (<FlatList renderItem={renderItem} data={data ?? []} />);
}
// Data isn't redeclared each ender but `[]` is
return (<FlatList renderItem={renderItem} data={data ?? []} />);
}
```
#### **Correct**
```JavaScript
// Has no dynamic dependencies therefore should be static, will be declared only once.
function renderItem({ item }) {
return <Text>item.name</Text>;
}
// Has no dynamic dependencies therefore should be static, will be declared only once.
function renderItem({ item }) {
return <Text>item.name</Text>;
}
const EMPTY_ARRAY = [];
const EMPTY_ARRAY = [];
function Component() {
function Component() {
const [data, setData] = useState(EMPTY_ARRAY);
// Will only render again if data changes
return (<FlatList renderItem={renderItem} data={data ?? EMPTY_ARRAY} />);
}
const [data, setData] = useState(EMPTY_ARRAY);
// Will only render again if data changes
return (<FlatList renderItem={renderItem} data={data ?? EMPTY_ARRAY} />);
}
```

@@ -82,34 +87,34 @@ ### Class Components

```JavaScript
class Component() {
class Component() {
constructor(props) {
super(props);
this.state = {
data: undefined,
propDrivenData: props.,
};
}
// This will NOT be redeclared each render
getItemName(item) {
return item.name;
}
constructor(props) {
super(props);
this.state = {
data: undefined,
propDrivenData: props.,
};
}
// This will NOT be redeclared each render
getItemName(item) {
return item.name;
}
render() {
// This function will be redeclared each render
function renderItem({ item }) {
return (<Text>{this.getItemName(item)}</Text>);
render() {
// This function will be redeclared each render
function renderItem({ item }) {
return (<Text>{this.getItemName(item)}</Text>);
}
// Data isn't redeclared each ender but [] is
// Extradata has a exponential complexity (will iterate the entire array for each render, could render once or several times in a second)
// Outcome will be that any new render on this component will cause the entire FlatList to render again, including children components, even if the data hasn't changed.
return (<FlatList
renderItem={renderItem}
data={data ?? []}
extraData={dataArray.filter(id => !!id)}
/>);
}
// Data isn't redeclared each ender but [] is
// Extradata has a exponential complexity (will iterate the entire array for each render, could render once or several times in a second)
// Outcome will be that any new render on this component will cause the entire FlatList to render again, including children components, even if the data hasn't changed.
return (<FlatList
renderItem={renderItem}
data={data ?? []}
extraData={dataArray.filter(id => !!id)}
/>);
}
}
```

@@ -121,53 +126,53 @@ In the previous example there are two issues, a function and a object that will be dynamically redeclared each time the component renders, which will cause FlatList to keep re-rendering even when the input data hasn't changed.

// Static therefore is only declared once
const EMPTY_ARRAY = [];
// Static therefore is only declared once
const EMPTY_ARRAY = [];
class Component() {
constructor(props) {
super(props);
this.state = {
data: undefined,
propDrivenData: props.dataArray.filter(id => !!id),
};
}
// Properly regenerate state driven data only when props change instead of during each render
static getDerivedStateFromProps(props) {
if (props.propDrivenData !== this.props.propDrivenData) {
return {
class Component() {
constructor(props) {
super(props);
this.state = {
data: undefined,
propDrivenData: props.dataArray.filter(id => !!id),
};
}
return null;
}
// Properly regenerate state driven data only when props change instead of during each render
static getDerivedStateFromProps(props) {
if (props.propDrivenData !== this.props.propDrivenData) {
return {
propDrivenData: props.dataArray.filter(id => !!id),
};
}
return null;
}
// Will be declared only once.
getItemName({item}) {
const { data } = this.state;
const dataLength = data ? data.length : 0;
return (<Text>{item.name} {dataLength}</Text>);
}
// Will be declared only once.
getItemName({item}) {
const { data } = this.state;
const dataLength = data ? data.length : 0;
return (<Text>{item.name} {dataLength}</Text>);
}
render() {
const { data } = this.state;
// Will only cause a new render if data changes
return (<FlatList renderItem={this.renderItem} data={data ?? EMPTY_ARRAY} />);
render() {
const { data } = this.state;
// Will only cause a new render if data changes
return (<FlatList renderItem={this.renderItem} data={data ?? EMPTY_ARRAY} />);
}
}
}
```
#### **Correct**
```JavaScript
const EMPTY_ARRAY = [];
const EMPTY_ARRAY = [];
function Component() {
const [data, setData] = useState(EMPTY_ARRAY);
const [isEditing, setIsEditing] = useState(false);
function Component() {
const [data, setData] = useState(EMPTY_ARRAY);
const [isEditing, setIsEditing] = useState(false);
// Has dynamic dependencies but will only be re-declared when isEditing or the input data changes
const renderItem = useCallback(({ item }) => {
return (<Text>{isEditing ? 'item.name' : 'Editing'}</Text>);
}, [isEditing]);
// Has dynamic dependencies but will only be re-declared when isEditing or the input data changes
const renderItem = useCallback(({ item }) => {
return (<Text>{isEditing ? 'item.name' : 'Editing'}</Text>);
}, [isEditing]);
return (<FlatList renderItem={renderItem} data={data ?? EMPTY_ARRAY} />);
}
return (<FlatList renderItem={renderItem} data={data ?? EMPTY_ARRAY} />);
}
```

@@ -179,24 +184,37 @@ ### Hooks

```JavaScript
function useData() {
function useData() {
let otherData = {};
function getData() {
// Doing something
let otherData = {};
function getData() {}
return { getData, otherData};
}
return { getData, otherData};
}
```
#### **Incorrect** (checkHookReturnObject: true)
```JavaScript
function getData() {
// Doing something
function getData() {}
function useData() {
return { getData };
}
function useData() {
return { getData };
}
```
#### **Incorrect** Calling a custom hook
```JavaScript
function doSomething() {}
function useStateManagement(someFlag) {
useEffect(() => {
doSomething(someFlag);
}, [someFlag]);
}
function useData() {
const someFlag = {};
// Some flag is memoized, so not unwanted side effects are generated in the other hook
const data = useStateManagement(someFlag);
}
```
#### **Correct**

@@ -206,7 +224,7 @@ ```JavaScript

function useData() {
const getData = useCallback(() => { // Or declare statically
}, []);
return {getData, otherData};
}
function useData() {
const getData = useCallback(() => { // Or declare statically
}, []);
return {getData, otherData};
}
```

@@ -219,9 +237,52 @@

}
function useData() {
return useMemo(() => ({ getData }), []);
}
function useData() {
return useMemo(() => ({ getData }), []);
}
```
#### **Correct** Passing a dependency to a hook
```JavaScript
function getData() {}
function useData() {
return useMemo(() => ({ getData }), []);
}
```
#### **Correct** Calling a custom hook
```JavaScript
function doSomething() {}
function useStateManagement(someFlag) {
useEffect(() => {
doSomething(someFlag);
}, [someFlag]);
}
function useData() {
const someFlag = useMemo(() => ({}), []);
// Some flag is memoized, so not unwanted side effects are generated in the other hook
const data = useStateManagement(someFlag);
}
```
#### **"Correct"** Calling a custom hook (checkHookCalls: true) or (ignoredHookCallsNames: { useStateManagement: false })
```JavaScript
function doSomething() {
}
function useStateManagement(someFlag) {
useEffect(() => {
doSomething(someFlag);
}, [someFlag]);
}
function useData() {
const someFlag = useMemo(() => ({}), []);
// Some flag is regenerated each render, hence useEffect in "useStateManagement" will execute a new cycle each render
// Either way this will not generate a error/warning because of the options passed
const data = useStateManagement(someFlag);
}
```
## `require-memo`

@@ -234,5 +295,5 @@

```JavaScript
export default function Component() {
return (<Text>This is a component</Text>);
}
export default function Component() {
return (<Text>This is a component</Text>);
}
```

@@ -242,5 +303,5 @@

```JavaScript
export default memo(function Component() {
return (<Text>This is a component</Text>);
});
export default memo(function Component() {
return (<Text>This is a component</Text>);
});
```

@@ -258,10 +319,10 @@

```JavaScript
function Component() {
function Component() {
return (<View>
<>
<OtherComponent />
</>
</View>);
}
return (<View>
<>
<OtherComponent />
</>
</View>);
}
```

@@ -271,9 +332,9 @@

```JavaScript
function Component() {
const children = useMemo(() => (<OtherComponent />), []);
return (<View>
{children}
</View>);
}
function Component() {
const children = useMemo(() => (<OtherComponent />), []);
return (<View>
{children}
</View>);
}
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc