import { Rule } from "eslint" ; import * as ESTree from "estree" ; import { TSESTree } from "@typescript-eslint/types" ; import { getExpressionMemoStatus , isComplexComponent , MemoStatus , } from "./common" ; const hookNameRegex = /^use[A-Z0-9].*$/ ; function isHook ( node ) { if ( node . type === "Identifier" ) { return hookNameRegex . test ( node . name ) ; } else if ( node . type === "MemberExpression" && ! node . computed && isHook ( node . property ) ) { const obj = node . object ; return obj . type === "Identifier" && obj . name === "React" ; } else { return false ; } } const messages = { "object-usememo-props" : "Object literal should be wrapped in React.useMemo() when used as a prop" , "object-usememo-deps" : "Object literal should be wrapped in React.useMemo() when used as a hook dependency" , "array-usememo-props" : "Array literal should be wrapped in React.useMemo() when used as a prop" , "array-usememo-deps" : "Array literal should be wrapped in React.useMemo() when used as a hook dependency" , "instance-usememo-props" : "Object instantiation should be wrapped in React.useMemo() when used as a prop" , "instance-usememo-deps" : "Object instantiation should be wrapped in React.useMemo() when used as a hook dependency" , "jsx-usememo-props" : "JSX should be wrapped in React.useMemo() when used as a prop" , "jsx-usememo-deps" : "JSX should be wrapped in React.useMemo() when used as a hook dependency" , "function-usecallback-props" : "Function definition should be wrapped in React.useCallback() when used as a prop" , "function-usecallback-deps" : "Function definition should be wrapped in React.useCallback() when used as a hook dependency" , "unknown-usememo-props" : "Unknown value may need to be wrapped in React.useMemo() when used as a prop" , "unknown-usememo-deps" : "Unknown value may need to be wrapped in React.useMemo() when used as a hook dependency" , "usememo-const" : "useMemo/useCallback return value should be assigned to a const to prevent reassignment" } ; const rule = { meta : { messages , schema : [ { type : "object" , properties : { strict : { type : "boolean" } } , additionalProperties : false } , ] } , create : ( context ) => { function report ( node , messageId ) { context . report ( { node , messageId : messageId as string } ) ; } return { JSXAttribute : ( node ) => { const { parent , value } = ( node as unknown ) as TSESTree . JSXAttribute & Rule . NodeParentExtension ; if ( value === null ) return ; if ( ! isComplexComponent ( parent ) ) return ; if ( value . type === "JSXExpressionContainer" ) { const { expression } = value ; if ( expression . type !== "JSXEmptyExpression" ) { switch ( getExpressionMemoStatus ( context , expression ) ) { case MemoStatus . UnmemoizedObject : report ( node , "object-usememo-props" ) ; break ; case MemoStatus . UnmemoizedArray : report ( node , "array-usememo-props" ) ; break ; case MemoStatus . UnmemoizedNew : report ( node , "instance-usememo-props" ) ; break ; case MemoStatus . UnmemoizedFunction : report ( node , "function-usecallback-props" ) ; break ; case MemoStatus . UnmemoizedFunctionCall : case MemoStatus . UnmemoizedOther : if ( context . options ?. [ 0 ] ?. strict ) { report ( node , "unknown-usememo-props" ) ; } break ; case MemoStatus . UnmemoizedJSX : report ( node , "jsx-usememo-props" ) ; break ; } } } } , CallExpression : ( node ) => { const { callee } = ( node as unknown ) as TSESTree . CallExpression & Rule . NodeParentExtension ; if ( ! isHook ( callee ) ) return ; const { arguments : [ , dependencies ] , } = ( node as unknown ) as TSESTree . CallExpression & Rule . NodeParentExtension ; if ( dependencies !== undefined && dependencies . type === "ArrayExpression" ) { for ( const dep of dependencies . elements ) { if ( dep !== null && dep . type === "Identifier" ) { switch ( getExpressionMemoStatus ( context , dep ) ) { case MemoStatus . UnmemoizedObject : report ( node , "object-usememo-deps" ) ; break ; case MemoStatus . UnmemoizedArray : report ( node , "array-usememo-deps" ) ; break ; case MemoStatus . UnmemoizedNew : report ( node , "instance-usememo-deps" ) ; break ; case MemoStatus . UnmemoizedFunction : report ( node , "function-usecallback-deps" ) ; break ; case MemoStatus . UnmemoizedFunctionCall : case MemoStatus . UnmemoizedOther : if ( context . options ?. [ 0 ] ?. strict ) { report ( node , "unknown-usememo-deps" ) ; } break ; case MemoStatus . UnmemoizedJSX : report ( node , "jsx-usememo-deps" ) ; break ; } } } } } } ; } } ; export default rule ;