1- import {
2- DEFAULT_COMPONENT_HINT ,
3- isReactHookCallWithNameLoose ,
4- isUseStateCall ,
5- useComponentCollector ,
6- } from "@eslint-react/core" ;
1+ import { _ } from "@eslint-react/eff" ;
72import type { RuleFeature } from "@eslint-react/shared" ;
8- import { getSettingsFromContext } from "@eslint-react/shared" ;
3+ import * as VAR from "@eslint-react/var" ;
4+ import type { TSESTree } from "@typescript-eslint/types" ;
95import { AST_NODE_TYPES as T } from "@typescript-eslint/types" ;
10- import { capitalize } from "string-ts" ;
6+ import { snakeCase } from "string-ts" ;
7+ import { match } from "ts-pattern" ;
118
129import { createRule } from "../utils" ;
1310
@@ -17,15 +14,7 @@ export const RULE_FEATURES = [
1714 "CHK" ,
1815] as const satisfies RuleFeature [ ] ;
1916
20- export type MessageID = "unexpected" ;
21-
22- function isSetterNameLoose ( name : string ) {
23- // eslint-disable-next-line @typescript-eslint/no-misused-spread
24- const fourthChar = [ ...name ] [ 3 ] ;
25-
26- return name . startsWith ( "set" )
27- && fourthChar === fourthChar ?. toUpperCase ( ) ;
28- }
17+ export type MessageID = "badValueOrSetterName" ;
2918
3019export default createRule < [ ] , MessageID > ( {
3120 meta : {
@@ -35,66 +24,49 @@ export default createRule<[], MessageID>({
3524 [ Symbol . for ( "rule_features" ) ] : RULE_FEATURES ,
3625 } ,
3726 messages : {
38- unexpected : "An useState call is not destructured into value + setter pair." ,
27+ badValueOrSetterName : "An useState call is not destructured into value + setter pair." ,
3928 } ,
4029 schema : [ ] ,
4130 } ,
4231 name : RULE_NAME ,
4332 create ( context ) {
44- const alias = getSettingsFromContext ( context ) . additionalHooks . useState ?? [ ] ;
45- const {
46- ctx,
47- listeners,
48- } = useComponentCollector (
49- context ,
50- {
51- collectDisplayName : false ,
52- collectHookCalls : true ,
53- hint : DEFAULT_COMPONENT_HINT ,
54- } ,
55- ) ;
56-
5733 return {
58- ...listeners ,
59- "Program:exit" ( node ) {
60- const components = ctx . getAllComponents ( node ) ;
61- for ( const { hookCalls } of components . values ( ) ) {
62- if ( hookCalls . length === 0 ) {
63- continue ;
64- }
65- for ( const hookCall of hookCalls ) {
66- if ( ! isUseStateCall ( context , hookCall ) && ! alias . some ( isReactHookCallWithNameLoose ( hookCall ) ) ) {
67- continue ;
68- }
69- if ( hookCall . parent . type !== T . VariableDeclarator ) {
70- continue ;
71- }
72- const { id } = hookCall . parent ;
73- switch ( id . type ) {
74- case T . Identifier : {
75- context . report ( { messageId : "unexpected" , node : id } ) ;
76- break ;
77- }
78- case T . ArrayPattern : {
79- const [ state , setState ] = id . elements ;
80- if ( state ?. type === T . ObjectPattern && setState ?. type === T . Identifier ) {
81- if ( ! isSetterNameLoose ( setState . name ) ) {
82- context . report ( { messageId : "unexpected" , node : id } ) ;
83- }
84- break ;
85- }
86- if ( state ?. type !== T . Identifier || setState ?. type !== T . Identifier ) {
87- return ;
88- }
89- const [ stateName , setStateName ] = [ state . name , setState . name ] ;
90- const expectedSetterName = `set${ capitalize ( stateName ) } ` ;
91- if ( setStateName === expectedSetterName ) {
92- return ;
93- }
94- context . report ( { messageId : "unexpected" , node : id } ) ;
34+ "CallExpression[callee.name='useState']" ( node : TSESTree . CallExpression ) {
35+ if ( node . parent . type !== T . VariableDeclarator ) {
36+ context . report ( { messageId : "badValueOrSetterName" , node } ) ;
37+ }
38+ const id = VAR . getVariableId ( node ) ;
39+ if ( id ?. type !== T . ArrayPattern ) {
40+ context . report ( { messageId : "badValueOrSetterName" , node } ) ;
41+ return ;
42+ }
43+ const [ value , setter ] = id . elements ;
44+ if ( value == null || setter == null ) {
45+ context . report ( { messageId : "badValueOrSetterName" , node } ) ;
46+ return ;
47+ }
48+ const setterName = match ( setter )
49+ . with ( { type : T . Identifier } , ( id ) => id . name )
50+ . otherwise ( ( ) => _ ) ;
51+ if ( setterName == null || ! setterName . startsWith ( "set" ) ) {
52+ context . report ( { messageId : "badValueOrSetterName" , node } ) ;
53+ return ;
54+ }
55+ const valueName = match ( value )
56+ . with ( { type : T . Identifier } , ( id ) => id . name )
57+ . with ( { type : T . ObjectPattern } , ( { properties } ) => {
58+ const values = properties . reduce < string [ ] > ( ( acc , prop ) => {
59+ if ( prop . type === T . Property && prop . key . type === T . Identifier ) {
60+ return [ ...acc , prop . key . name ] ;
9561 }
96- }
97- }
62+ return acc ;
63+ } , [ ] ) ;
64+ return values . join ( "_" ) ;
65+ } )
66+ . otherwise ( ( ) => _ ) ;
67+ if ( valueName == null || `set_${ valueName } ` !== snakeCase ( setterName ) ) {
68+ context . report ( { messageId : "badValueOrSetterName" , node } ) ;
69+ return ;
9870 }
9971 } ,
10072 } ;
0 commit comments