@@ -661,6 +661,171 @@ mod tests {
661661 ) ;
662662 }
663663
664+ // ── unused functions in tests/ and inst/tinytest/ ───────────────
665+
666+ #[ test]
667+ fn test_unused_function_in_tests_flagged ( ) {
668+ let dir = TempDir :: new ( ) . unwrap ( ) ;
669+ let r_dir = dir. path ( ) . join ( "R" ) ;
670+ fs:: create_dir ( & r_dir) . unwrap ( ) ;
671+ let tests_dir = dir. path ( ) . join ( "tests" ) . join ( "testthat" ) ;
672+ fs:: create_dir_all ( & tests_dir) . unwrap ( ) ;
673+ fs:: write ( dir. path ( ) . join ( "DESCRIPTION" ) , "Package: test" ) . unwrap ( ) ;
674+ fs:: write ( dir. path ( ) . join ( "NAMESPACE" ) , "export(public_fn)\n " ) . unwrap ( ) ;
675+
676+ let file_a = r_dir. join ( "public.R" ) ;
677+ fs:: write ( & file_a, "public_fn <- function() 1\n " ) . unwrap ( ) ;
678+
679+ // Helper defined in tests/ but never used
680+ let test_helper = tests_dir. join ( "helper.R" ) ;
681+ fs:: write ( & test_helper, "unused_test_helper <- function() 42\n " ) . unwrap ( ) ;
682+
683+ let mut shared = scan_r_package_paths ( std:: slice:: from_ref ( & file_a) , true ) ;
684+ shared. extend ( scan_extra_package_paths ( & [ test_helper] , dir. path ( ) ) ) ;
685+ let result =
686+ compute_unused_from_shared ( & shared, & default_options ( ) , & read_namespace ( dir. path ( ) ) ) ;
687+
688+ let has_unused = result
689+ . values ( )
690+ . any ( |v| v. iter ( ) . any ( |( n, _, _) | n == "unused_test_helper" ) ) ;
691+ assert ! (
692+ has_unused,
693+ "unused_test_helper is defined in tests/ but never called, should be flagged"
694+ ) ;
695+ }
696+
697+ #[ test]
698+ fn test_used_function_in_tests_not_flagged ( ) {
699+ let dir = TempDir :: new ( ) . unwrap ( ) ;
700+ let r_dir = dir. path ( ) . join ( "R" ) ;
701+ fs:: create_dir ( & r_dir) . unwrap ( ) ;
702+ let tests_dir = dir. path ( ) . join ( "tests" ) . join ( "testthat" ) ;
703+ fs:: create_dir_all ( & tests_dir) . unwrap ( ) ;
704+ fs:: write ( dir. path ( ) . join ( "DESCRIPTION" ) , "Package: test" ) . unwrap ( ) ;
705+ fs:: write ( dir. path ( ) . join ( "NAMESPACE" ) , "export(public_fn)\n " ) . unwrap ( ) ;
706+
707+ let file_a = r_dir. join ( "public.R" ) ;
708+ fs:: write ( & file_a, "public_fn <- function() 1\n " ) . unwrap ( ) ;
709+
710+ // Helper defined in tests/ and used in another test file
711+ let test_helper = tests_dir. join ( "helper.R" ) ;
712+ fs:: write ( & test_helper, "test_helper <- function() 42\n " ) . unwrap ( ) ;
713+
714+ let test_file = tests_dir. join ( "test-foo.R" ) ;
715+ fs:: write ( & test_file, "test_that('works', { test_helper() })\n " ) . unwrap ( ) ;
716+
717+ let mut shared = scan_r_package_paths ( std:: slice:: from_ref ( & file_a) , true ) ;
718+ shared. extend ( scan_extra_package_paths (
719+ & [ test_helper, test_file] ,
720+ dir. path ( ) ,
721+ ) ) ;
722+ let result =
723+ compute_unused_from_shared ( & shared, & default_options ( ) , & read_namespace ( dir. path ( ) ) ) ;
724+
725+ let has_helper = result
726+ . values ( )
727+ . any ( |v| v. iter ( ) . any ( |( n, _, _) | n == "test_helper" ) ) ;
728+ assert ! (
729+ !has_helper,
730+ "test_helper is used in another test file, should not be flagged"
731+ ) ;
732+ }
733+
734+ #[ test]
735+ fn test_unused_function_in_inst_tinytest_flagged ( ) {
736+ let dir = TempDir :: new ( ) . unwrap ( ) ;
737+ let r_dir = dir. path ( ) . join ( "R" ) ;
738+ fs:: create_dir ( & r_dir) . unwrap ( ) ;
739+ let inst_dir = dir. path ( ) . join ( "inst" ) . join ( "tinytest" ) ;
740+ fs:: create_dir_all ( & inst_dir) . unwrap ( ) ;
741+ fs:: write ( dir. path ( ) . join ( "DESCRIPTION" ) , "Package: test" ) . unwrap ( ) ;
742+ fs:: write ( dir. path ( ) . join ( "NAMESPACE" ) , "export(public_fn)\n " ) . unwrap ( ) ;
743+
744+ let file_a = r_dir. join ( "public.R" ) ;
745+ fs:: write ( & file_a, "public_fn <- function() 1\n " ) . unwrap ( ) ;
746+
747+ // Helper defined in inst/tinytest/ but never used
748+ let inst_helper = inst_dir. join ( "helper.R" ) ;
749+ fs:: write ( & inst_helper, "unused_inst_helper <- function() 42\n " ) . unwrap ( ) ;
750+
751+ let mut shared = scan_r_package_paths ( std:: slice:: from_ref ( & file_a) , true ) ;
752+ shared. extend ( scan_extra_package_paths ( & [ inst_helper] , dir. path ( ) ) ) ;
753+ let result =
754+ compute_unused_from_shared ( & shared, & default_options ( ) , & read_namespace ( dir. path ( ) ) ) ;
755+
756+ let has_unused = result
757+ . values ( )
758+ . any ( |v| v. iter ( ) . any ( |( n, _, _) | n == "unused_inst_helper" ) ) ;
759+ assert ! (
760+ has_unused,
761+ "unused_inst_helper is defined in inst/tinytest/ but never called, should be flagged"
762+ ) ;
763+ }
764+
765+ #[ test]
766+ fn test_unused_function_in_inst_tests_flagged ( ) {
767+ let dir = TempDir :: new ( ) . unwrap ( ) ;
768+ let r_dir = dir. path ( ) . join ( "R" ) ;
769+ fs:: create_dir ( & r_dir) . unwrap ( ) ;
770+ let inst_dir = dir. path ( ) . join ( "inst" ) . join ( "tests" ) ;
771+ fs:: create_dir_all ( & inst_dir) . unwrap ( ) ;
772+ fs:: write ( dir. path ( ) . join ( "DESCRIPTION" ) , "Package: test" ) . unwrap ( ) ;
773+ fs:: write ( dir. path ( ) . join ( "NAMESPACE" ) , "export(public_fn)\n " ) . unwrap ( ) ;
774+
775+ let file_a = r_dir. join ( "public.R" ) ;
776+ fs:: write ( & file_a, "public_fn <- function() 1\n " ) . unwrap ( ) ;
777+
778+ // Helper defined in inst/tests/ but never used
779+ let inst_helper = inst_dir. join ( "helper.R" ) ;
780+ fs:: write ( & inst_helper, "unused_inst_helper <- function() 42\n " ) . unwrap ( ) ;
781+
782+ let mut shared = scan_r_package_paths ( std:: slice:: from_ref ( & file_a) , true ) ;
783+ shared. extend ( scan_extra_package_paths ( & [ inst_helper] , dir. path ( ) ) ) ;
784+ let result =
785+ compute_unused_from_shared ( & shared, & default_options ( ) , & read_namespace ( dir. path ( ) ) ) ;
786+
787+ let has_unused = result
788+ . values ( )
789+ . any ( |v| v. iter ( ) . any ( |( n, _, _) | n == "unused_inst_helper" ) ) ;
790+ assert ! (
791+ has_unused,
792+ "unused_inst_helper is defined in inst/tests/ but never called, should be flagged"
793+ ) ;
794+ }
795+
796+ #[ test]
797+ fn test_cross_scope_independence ( ) {
798+ let dir = TempDir :: new ( ) . unwrap ( ) ;
799+ let r_dir = dir. path ( ) . join ( "R" ) ;
800+ fs:: create_dir ( & r_dir) . unwrap ( ) ;
801+ let tests_dir = dir. path ( ) . join ( "tests" ) . join ( "testthat" ) ;
802+ fs:: create_dir_all ( & tests_dir) . unwrap ( ) ;
803+ fs:: write ( dir. path ( ) . join ( "DESCRIPTION" ) , "Package: test" ) . unwrap ( ) ;
804+ fs:: write ( dir. path ( ) . join ( "NAMESPACE" ) , "export(public_fn)\n " ) . unwrap ( ) ;
805+
806+ // R/ file references `test_only_helper` but it shouldn't save the
807+ // test-scoped definition from being flagged unused within tests/.
808+ let file_a = r_dir. join ( "public.R" ) ;
809+ fs:: write ( & file_a, "public_fn <- function() test_only_helper()\n " ) . unwrap ( ) ;
810+
811+ let test_helper = tests_dir. join ( "helper.R" ) ;
812+ fs:: write ( & test_helper, "test_only_helper <- function() 42\n " ) . unwrap ( ) ;
813+
814+ let mut shared = scan_r_package_paths ( std:: slice:: from_ref ( & file_a) , true ) ;
815+ shared. extend ( scan_extra_package_paths ( & [ test_helper] , dir. path ( ) ) ) ;
816+ let result =
817+ compute_unused_from_shared ( & shared, & default_options ( ) , & read_namespace ( dir. path ( ) ) ) ;
818+
819+ let has_test_helper = result
820+ . values ( )
821+ . any ( |v| v. iter ( ) . any ( |( n, _, _) | n == "test_only_helper" ) ) ;
822+ assert ! (
823+ has_test_helper,
824+ "test_only_helper is only referenced in R/ but defined in tests/, \
825+ should be flagged as unused within tests/ scope"
826+ ) ;
827+ }
828+
664829 #[ test]
665830 fn test_threshold_not_exceeded_shows_diagnostics ( ) {
666831 let dir = TempDir :: new ( ) . unwrap ( ) ;
0 commit comments