diff --git a/lib/rules/sort/index.ts b/lib/rules/sort/index.ts index 5aa2226..3ba3414 100644 --- a/lib/rules/sort/index.ts +++ b/lib/rules/sort/index.ts @@ -74,7 +74,7 @@ module.exports = { .forEach((declarations: Node[]) => { let nodes: Node[] = [] - declarations.forEach?.(node => { + declarations?.forEach(node => { if (node['type'] === 'ExpressionStatement') { nodes.push(node['expression']) } @@ -85,46 +85,51 @@ module.exports = { }) const hooks = nodes - .map( + ?.map( ({ type, callee, init }) => (type === 'CallExpression' ? [type, callee] : type === 'VariableDeclarator' ? [type, init] - : []) as [string, Node], + : []) as [Node['type'], Node], ) .filter(node => node.length) .map(([type, declaration]) => { - const getHookName = (): string => - type === 'CallExpression' - ? declaration.name - : declaration.callee?.name - - const getHookNode = (): Node => - type === 'CallExpression' ? declaration : declaration.callee - - return [getHookName(), getHookNode()] as [string, Node] + switch (type) { + case 'MemberExpression': + return declaration.property + + case 'CallExpression': + return declaration.type === 'MemberExpression' + ? declaration.property + : declaration.callee || declaration + + case 'VariableDeclarator': + default: + return declaration.callee.property || declaration.callee + } }) + .filter(Boolean) .filter( - ([name]) => - name?.slice(0, 3) === 'use' && groups.includes(name), + hook => + hook.name.slice(0, 3) === 'use' && groups.includes(hook.name), ) - const correctOrdering: [string, Node][] = [...hooks].sort( - (a, b) => groups.indexOf(a[0]) - groups.indexOf(b[0]), + const correctOrdering: Node[] = [...hooks].sort( + (a, b) => groups.indexOf(a.name) - groups.indexOf(b.name), ) - hooks.forEach((hook, index) => { + hooks.forEach((hook, idx) => { const noMatching = (): boolean => correctOrdering.length > 1 && - correctOrdering[index][0] !== hook[0] + correctOrdering[idx].name !== hook.name if (noMatching()) { ctx.report( - hook[1], - `Non-matching declaration order. ${hook[0]} comes ${ - !index ? 'after' : 'before' - } ${correctOrdering[index][0]}.`, + hook, + `Non-matching declaration order. ${hook.name} comes ${ + !idx ? 'after' : 'before' + } ${correctOrdering[idx].name}.`, ) } }) diff --git a/lib/rules/sort/sort.spec.ts b/lib/rules/sort/sort.spec.ts index 1d714b2..356f413 100644 --- a/lib/rules/sort/sort.spec.ts +++ b/lib/rules/sort/sort.spec.ts @@ -25,6 +25,37 @@ const options = [ Tester.run('hooks/sort', rule as unknown as Rule.RuleModule, { valid: [ + { + code: ` + import React from 'react' + + export const App = () => { + return <> + } + `, + parserOptions, + options, + }, + { + code: ` + import * as React from 'react' + import { useRef } from 'react' + + export const ComponentA = () => { + const [count, setCount] = React.useState(0) + + const countRef = useRef(0) + + React.useEffect(() => { + console.log('Hello') + },[]) + + return <> + } + `, + parserOptions, + options, + }, { code: ` import { useState, useEffect, useReducer } from 'react' @@ -190,7 +221,6 @@ Tester.run('hooks/sort', rule as unknown as Rule.RuleModule, { export function ComponentA() { const [count, setCount] = useState(0) - const locale = useContext(LocaleContext) } @@ -223,5 +253,35 @@ Tester.run('hooks/sort', rule as unknown as Rule.RuleModule, { parserOptions, options, }, + { + code: ` + import * as React from 'react' + import { useRef } from 'react' + + export const ComponentA = () => { + React.useEffect(() => { + console.log('Hello') + },[]) + + const countRef = useRef(0) + + const [count, setCount] = React.useState(0) + + return <> + } + `, + errors: [ + { + message: + 'Non-matching declaration order. useEffect comes after useState.', + }, + { + message: + 'Non-matching declaration order. useState comes before useEffect.', + }, + ], + parserOptions, + options, + }, ], }) diff --git a/lib/rules/sort/types.ts b/lib/rules/sort/types.ts index f24251e..4a4e5b0 100644 --- a/lib/rules/sort/types.ts +++ b/lib/rules/sort/types.ts @@ -1,5 +1,6 @@ export type Node = { type: + | 'Identifier' | 'FunctionDeclaration' | 'VariableDeclaration' | 'ExportNamedDeclaration' @@ -8,6 +9,7 @@ export type Node = { | 'VariableDeclaration' | 'CallExpression' | 'VariableDeclarator' + | 'MemberExpression' name: string body: { body: any[] @@ -15,6 +17,7 @@ export type Node = { init: Node[] callee: Node expression: Node + property: Node declaration: { declarations: { init: Node