@@ -724,3 +724,231 @@ fn invalid_exclude_pattern() -> anyhow::Result<()> {
724724
725725 Ok ( ( ) )
726726}
727+
728+ /// Test that ty works correctly with Bazel's symlinked file structure
729+ #[ test]
730+ #[ cfg( unix) ]
731+ fn bazel_symlinked_files ( ) -> anyhow:: Result < ( ) > {
732+ let case = CliTest :: with_files ( [
733+ // Original source files in the project
734+ (
735+ "main.py" ,
736+ r#"
737+ import library
738+
739+ result = library.process_data()
740+ print(undefined_var) # error: unresolved-reference
741+ "# ,
742+ ) ,
743+ (
744+ "library.py" ,
745+ r#"
746+ def process_data():
747+ return missing_value # error: unresolved-reference
748+ "# ,
749+ ) ,
750+ // Another source file that won't be symlinked
751+ (
752+ "other.py" ,
753+ r#"
754+ print(other_undefined) # error: unresolved-reference
755+ "# ,
756+ ) ,
757+ ] ) ?;
758+
759+ // Create Bazel-style symlinks pointing to the actual source files
760+ // Bazel typically creates symlinks in bazel-out/k8-fastbuild/bin/ that point to actual sources
761+ std:: fs:: create_dir_all ( case. project_dir . join ( "bazel-out/k8-fastbuild/bin" ) ) ?;
762+
763+ // Use absolute paths to ensure the symlinks work correctly
764+ case. write_symlink (
765+ case. project_dir . join ( "main.py" ) ,
766+ "bazel-out/k8-fastbuild/bin/main.py" ,
767+ ) ?;
768+ case. write_symlink (
769+ case. project_dir . join ( "library.py" ) ,
770+ "bazel-out/k8-fastbuild/bin/library.py" ,
771+ ) ?;
772+
773+ // Change to the bazel-out directory and run ty from there
774+ // The symlinks should be followed and errors should be found
775+ assert_cmd_snapshot ! ( case. command( ) . current_dir( case. project_dir. join( "bazel-out/k8-fastbuild/bin" ) ) , @r"
776+ success: false
777+ exit_code: 1
778+ ----- stdout -----
779+ error[unresolved-reference]: Name `missing_value` used when not defined
780+ --> library.py:3:12
781+ |
782+ 2 | def process_data():
783+ 3 | return missing_value # error: unresolved-reference
784+ | ^^^^^^^^^^^^^
785+ |
786+ info: rule `unresolved-reference` is enabled by default
787+
788+ error[unresolved-reference]: Name `undefined_var` used when not defined
789+ --> main.py:5:7
790+ |
791+ 4 | result = library.process_data()
792+ 5 | print(undefined_var) # error: unresolved-reference
793+ | ^^^^^^^^^^^^^
794+ |
795+ info: rule `unresolved-reference` is enabled by default
796+
797+ Found 2 diagnostics
798+
799+ ----- stderr -----
800+ WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
801+ " ) ;
802+
803+ // Test that when checking a specific symlinked file from the bazel-out directory, it works correctly
804+ assert_cmd_snapshot ! ( case. command( ) . current_dir( case. project_dir. join( "bazel-out/k8-fastbuild/bin" ) ) . arg( "main.py" ) , @r"
805+ success: false
806+ exit_code: 1
807+ ----- stdout -----
808+ error[unresolved-reference]: Name `undefined_var` used when not defined
809+ --> main.py:5:7
810+ |
811+ 4 | result = library.process_data()
812+ 5 | print(undefined_var) # error: unresolved-reference
813+ | ^^^^^^^^^^^^^
814+ |
815+ info: rule `unresolved-reference` is enabled by default
816+
817+ Found 1 diagnostic
818+
819+ ----- stderr -----
820+ WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
821+ " ) ;
822+
823+ Ok ( ( ) )
824+ }
825+
826+ /// Test that exclude patterns match on symlink source names, not target names
827+ #[ test]
828+ #[ cfg( unix) ]
829+ fn exclude_symlink_source_not_target ( ) -> anyhow:: Result < ( ) > {
830+ let case = CliTest :: with_files ( [
831+ // Target files with generic names
832+ (
833+ "src/module.py" ,
834+ r#"
835+ def process():
836+ return undefined_var # error: unresolved-reference
837+ "# ,
838+ ) ,
839+ (
840+ "src/utils.py" ,
841+ r#"
842+ def helper():
843+ return missing_value # error: unresolved-reference
844+ "# ,
845+ ) ,
846+ (
847+ "regular.py" ,
848+ r#"
849+ print(regular_undefined) # error: unresolved-reference
850+ "# ,
851+ ) ,
852+ ] ) ?;
853+
854+ // Create symlinks with names that differ from their targets
855+ // This simulates build systems that rename files during symlinking
856+ case. write_symlink ( "src/module.py" , "generated_module.py" ) ?;
857+ case. write_symlink ( "src/utils.py" , "generated_utils.py" ) ?;
858+
859+ // Exclude pattern should match on the symlink name (generated_*), not the target name
860+ assert_cmd_snapshot ! ( case. command( ) . arg( "--exclude" ) . arg( "generated_*.py" ) , @r"
861+ success: false
862+ exit_code: 1
863+ ----- stdout -----
864+ error[unresolved-reference]: Name `regular_undefined` used when not defined
865+ --> regular.py:2:7
866+ |
867+ 2 | print(regular_undefined) # error: unresolved-reference
868+ | ^^^^^^^^^^^^^^^^^
869+ |
870+ info: rule `unresolved-reference` is enabled by default
871+
872+ error[unresolved-reference]: Name `undefined_var` used when not defined
873+ --> src/module.py:3:12
874+ |
875+ 2 | def process():
876+ 3 | return undefined_var # error: unresolved-reference
877+ | ^^^^^^^^^^^^^
878+ |
879+ info: rule `unresolved-reference` is enabled by default
880+
881+ error[unresolved-reference]: Name `missing_value` used when not defined
882+ --> src/utils.py:3:12
883+ |
884+ 2 | def helper():
885+ 3 | return missing_value # error: unresolved-reference
886+ | ^^^^^^^^^^^^^
887+ |
888+ info: rule `unresolved-reference` is enabled by default
889+
890+ Found 3 diagnostics
891+
892+ ----- stderr -----
893+ WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
894+ " ) ;
895+
896+ // Exclude pattern on target path should not affect symlinks with different names
897+ assert_cmd_snapshot ! ( case. command( ) . arg( "--exclude" ) . arg( "src/*.py" ) , @r"
898+ success: false
899+ exit_code: 1
900+ ----- stdout -----
901+ error[unresolved-reference]: Name `undefined_var` used when not defined
902+ --> generated_module.py:3:12
903+ |
904+ 2 | def process():
905+ 3 | return undefined_var # error: unresolved-reference
906+ | ^^^^^^^^^^^^^
907+ |
908+ info: rule `unresolved-reference` is enabled by default
909+
910+ error[unresolved-reference]: Name `missing_value` used when not defined
911+ --> generated_utils.py:3:12
912+ |
913+ 2 | def helper():
914+ 3 | return missing_value # error: unresolved-reference
915+ | ^^^^^^^^^^^^^
916+ |
917+ info: rule `unresolved-reference` is enabled by default
918+
919+ error[unresolved-reference]: Name `regular_undefined` used when not defined
920+ --> regular.py:2:7
921+ |
922+ 2 | print(regular_undefined) # error: unresolved-reference
923+ | ^^^^^^^^^^^^^^^^^
924+ |
925+ info: rule `unresolved-reference` is enabled by default
926+
927+ Found 3 diagnostics
928+
929+ ----- stderr -----
930+ WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
931+ " ) ;
932+
933+ // Test that explicitly passing a symlink always checks it, even if excluded
934+ assert_cmd_snapshot ! ( case. command( ) . arg( "--exclude" ) . arg( "generated_*.py" ) . arg( "generated_module.py" ) , @r"
935+ success: false
936+ exit_code: 1
937+ ----- stdout -----
938+ error[unresolved-reference]: Name `undefined_var` used when not defined
939+ --> generated_module.py:3:12
940+ |
941+ 2 | def process():
942+ 3 | return undefined_var # error: unresolved-reference
943+ | ^^^^^^^^^^^^^
944+ |
945+ info: rule `unresolved-reference` is enabled by default
946+
947+ Found 1 diagnostic
948+
949+ ----- stderr -----
950+ WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
951+ " ) ;
952+
953+ Ok ( ( ) )
954+ }
0 commit comments