@@ -628,9 +628,12 @@ impl Rule for ExhaustiveDeps {
628628 if dep. chain . is_empty ( ) && is_symbol_declaration_referentially_unique ( symbol_id, ctx) {
629629 let name = ctx. scoping ( ) . symbol_name ( symbol_id) ;
630630 let decl_span = ctx. scoping ( ) . symbol_span ( symbol_id) ;
631- ctx. diagnostic ( dependency_changes_on_every_render_diagnostic (
632- hook_name, dep. span , name, decl_span,
633- ) ) ;
631+ ctx. diagnostic_with_dangerous_suggestion (
632+ dependency_changes_on_every_render_diagnostic (
633+ hook_name, dep. span , name, decl_span,
634+ ) ,
635+ |fixer| fix:: remove_dependency ( fixer, & dep, dependencies_node) ,
636+ ) ;
634637 }
635638 }
636639 }
@@ -1464,10 +1467,16 @@ fn is_inside_effect_cleanup(stack: &[AstType]) -> bool {
14641467mod fix {
14651468 use super :: Name ;
14661469 use oxc_allocator:: { Allocator , CloneIn } ;
1467- use oxc_ast:: { AstBuilder , ast:: ArrayExpression } ;
1468- use oxc_span:: { Atom , SPAN } ;
1470+ use oxc_ast:: {
1471+ AstBuilder ,
1472+ ast:: { ArrayExpression , Expression } ,
1473+ } ;
1474+ use oxc_span:: { Atom , GetSpan , SPAN } ;
14691475
1470- use crate :: fixer:: { RuleFix , RuleFixer } ;
1476+ use crate :: {
1477+ fixer:: { RuleFix , RuleFixer } ,
1478+ rules:: react:: exhaustive_deps:: Dependency ,
1479+ } ;
14711480
14721481 pub fn append_dependencies < ' c , ' a : ' c > (
14731482 fixer : RuleFixer < ' c , ' a > ,
@@ -1492,6 +1501,29 @@ mod fix {
14921501 codegen. print_expression ( & ast_builder. expression_array ( SPAN , vec) ) ;
14931502 fixer. replace ( deps. span , codegen. into_source_text ( ) )
14941503 }
1504+
1505+ pub fn remove_dependency < ' c , ' a : ' c > (
1506+ fixer : RuleFixer < ' c , ' a > ,
1507+ dependency : & Dependency ,
1508+ deps : & ArrayExpression < ' a > ,
1509+ ) -> RuleFix < ' a > {
1510+ let mut codegen = fixer. codegen ( ) ;
1511+
1512+ let alloc = Allocator :: default ( ) ;
1513+ let ast_builder = AstBuilder :: new ( & alloc) ;
1514+
1515+ let new_deps = deps
1516+ . elements
1517+ . iter ( )
1518+ . filter ( |el| ( * el) . span ( ) != dependency. span )
1519+ . map ( |el| el. clone_in ( & alloc) ) ;
1520+
1521+ codegen. print_expression ( & Expression :: ArrayExpression ( ast_builder. alloc_array_expression (
1522+ deps. span ,
1523+ oxc_allocator:: Vec :: from_iter_in ( new_deps, & alloc) ,
1524+ ) ) ) ;
1525+ fixer. replace ( deps. span , codegen. into_source_text ( ) )
1526+ }
14951527}
14961528
14971529#[ test]
@@ -4128,6 +4160,43 @@ fn test() {
41284160 // // None,
41294161 // // FixKind::DangerousSuggestion,
41304162 // ),
4163+ // Test missing dependency fixes
4164+ (
4165+ "function MyComponent() { const local = someFunc(); useEffect(() => { console.log(local); }, []); }" ,
4166+ "function MyComponent() { const local = someFunc(); useEffect(() => { console.log(local); }, [local]); }" ,
4167+ ) ,
4168+ (
4169+ "function MyComponent(props) { useEffect(() => { console.log(props.foo); }, []); }" ,
4170+ "function MyComponent(props) { useEffect(() => { console.log(props.foo); }, [props.foo]); }" ,
4171+ ) ,
4172+ (
4173+ "function MyComponent(props) { useEffect(() => { console.log(props.foo, props.bar); }, []); }" ,
4174+ "function MyComponent(props) { useEffect(() => { console.log(props.foo, props.bar); }, [props.foo, props.bar]); }" ,
4175+ ) ,
4176+ // Test adding to existing dependencies
4177+ (
4178+ "function MyComponent(props) { const local = someFunc(); useEffect(() => { console.log(props.foo, local); }, [props.foo]); }" ,
4179+ "function MyComponent(props) { const local = someFunc(); useEffect(() => { console.log(props.foo, local); }, [props.foo, local]); }" ,
4180+ ) ,
4181+ // Test dependency array creation for hooks that require it
4182+ (
4183+ "function MyComponent() { const fn = useCallback(() => { alert('foo'); }); }" ,
4184+ "function MyComponent() { const fn = useCallback(() => { alert('foo'); }, []); }" ,
4185+ ) ,
4186+ (
4187+ "function MyComponent() { const value = useMemo(() => { return 2*2; }); }" ,
4188+ "function MyComponent() { const value = useMemo(() => { return 2*2; }, []); }" ,
4189+ ) ,
4190+ // Test unnecessary dependency removal for non-effect hooks
4191+ (
4192+ "function MyComponent() { const local1 = {}; useCallback(() => {}, [local1]); }" ,
4193+ "function MyComponent() { const local1 = {}; useCallback(() => {}, []); }" ,
4194+ ) ,
4195+ // Test duplicate dependency removal
4196+ (
4197+ "function MyComponent() { const local = {}; useEffect(() => { console.log(local); }, [local, local]); }" ,
4198+ "function MyComponent() { const local = {}; useEffect(() => { console.log(local); }, [local]); }" ,
4199+ ) ,
41314200 ] ;
41324201
41334202 Tester :: new (
0 commit comments