@@ -380,19 +380,28 @@ fn update_ignore_comment_with_used_codes(
380380/// - "Unused `# pyrefly: ignore` comment for code(s): X, Y" -> remove entire comment
381381/// - "Unused error code(s) in `# pyrefly: ignore`: X, Y" -> remove only those codes
382382pub fn remove_unused_ignores ( unused_ignore_errors : Vec < Error > ) -> usize {
383+ let serialized: Vec < SerializedError > = unused_ignore_errors
384+ . iter ( )
385+ . filter_map ( SerializedError :: from_error)
386+ . collect ( ) ;
387+ remove_unused_ignores_from_serialized ( serialized)
388+ }
389+
390+ /// Removes unused ignore comments from source files using SerializedError.
391+ /// This is similar to remove_unused_ignores but works with SerializedError instead of Error,
392+ /// allowing it to be used with errors parsed from JSON.
393+ pub fn remove_unused_ignores_from_serialized ( unused_ignore_errors : Vec < SerializedError > ) -> usize {
383394 if unused_ignore_errors. is_empty ( ) {
384395 return 0 ;
385396 }
386397
387398 // Group errors by file path
388- let mut errors_by_path: SmallMap < PathBuf , Vec < & Error > > = SmallMap :: new ( ) ;
399+ let mut errors_by_path: SmallMap < PathBuf , Vec < & SerializedError > > = SmallMap :: new ( ) ;
389400 for error in & unused_ignore_errors {
390- if let ModulePathDetails :: FileSystem ( path) = error. path ( ) . details ( ) {
391- errors_by_path
392- . entry ( ( * * path) . clone ( ) )
393- . or_default ( )
394- . push ( error) ;
395- }
401+ errors_by_path
402+ . entry ( error. path . clone ( ) )
403+ . or_default ( )
404+ . push ( error) ;
396405 }
397406
398407 let regex = Regex :: new ( r"#\s*pyrefly:\s*ignore.*$" ) . unwrap ( ) ;
@@ -401,14 +410,9 @@ pub fn remove_unused_ignores(unused_ignore_errors: Vec<Error>) -> usize {
401410
402411 for ( path, path_errors) in & errors_by_path {
403412 // Build a map from line number to the error
404- let mut line_errors: SmallMap < usize , & Error > = SmallMap :: new ( ) ;
413+ let mut line_errors: SmallMap < usize , & SerializedError > = SmallMap :: new ( ) ;
405414 for error in path_errors {
406- let line = error
407- . display_range ( )
408- . start
409- . line_within_file ( )
410- . to_zero_indexed ( ) ;
411- line_errors. insert ( line as usize , error) ;
415+ line_errors. insert ( error. line , * error) ;
412416 }
413417
414418 if let Ok ( file) = read_and_validate_file ( path) {
@@ -421,7 +425,7 @@ pub fn remove_unused_ignores(unused_ignore_errors: Vec<Error>) -> usize {
421425 if let Some ( error) = line_errors. get ( & idx)
422426 && regex. is_match ( line)
423427 {
424- let msg = error. msg ( ) ;
428+ let msg = & error. message ;
425429
426430 // Determine action based on error message
427431 if msg. starts_with ( "Unused `# pyrefly: ignore` comment" ) {
@@ -1000,4 +1004,157 @@ a: int = "" # pyrefly: ignore [bad-assignment]
10001004 let after = "\r \n # pyrefly: ignore [bad-assignment]\r \n x: str = 1\r \n " ;
10011005 assert_suppress_errors ( before, after) ;
10021006 }
1007+
1008+ // Helper function to test remove_unused_ignores_from_serialized
1009+ fn assert_remove_ignores_from_serialized (
1010+ file_content : & str ,
1011+ mut serialized_errors : Vec < SerializedError > ,
1012+ expected_content : & str ,
1013+ expected_removals : usize ,
1014+ ) {
1015+ let tdir = tempfile:: tempdir ( ) . unwrap ( ) ;
1016+ let path = get_path ( & tdir) ;
1017+ fs_anyhow:: write ( & path, file_content) . unwrap ( ) ;
1018+
1019+ // Update error paths to point to the temp file
1020+ for error in & mut serialized_errors {
1021+ error. path = path. clone ( ) ;
1022+ }
1023+
1024+ let removals = suppress:: remove_unused_ignores_from_serialized ( serialized_errors) ;
1025+
1026+ let got_file = fs_anyhow:: read_to_string ( & path) . unwrap ( ) ;
1027+ assert_eq ! ( expected_content, got_file) ;
1028+ assert_eq ! ( removals, expected_removals) ;
1029+ }
1030+
1031+ #[ test]
1032+ fn test_remove_unused_ignores_from_serialized_blanket ( ) {
1033+ let input = r#"def g() -> str:
1034+ return "hello" # pyrefly: ignore
1035+ "# ;
1036+ let want = r#"def g() -> str:
1037+ return "hello"
1038+ "# ;
1039+ let errors = vec ! [ SerializedError {
1040+ path: PathBuf :: from( "test.py" ) ,
1041+ line: 1 ,
1042+ name: "unused-ignore" . to_owned( ) ,
1043+ message: "Unused `# pyrefly: ignore` comment" . to_owned( ) ,
1044+ } ] ;
1045+ assert_remove_ignores_from_serialized ( input, errors, want, 1 ) ;
1046+ }
1047+
1048+ #[ test]
1049+ fn test_remove_unused_ignores_from_serialized_partial ( ) {
1050+ let before = r#"
1051+ # pyrefly: ignore[bad-assignment,bad-override]
1052+ a: int = ""
1053+ "# ;
1054+ let after = r#"
1055+ # pyrefly: ignore [bad-assignment]
1056+ a: int = ""
1057+ "# ;
1058+ let errors = vec ! [ SerializedError {
1059+ path: PathBuf :: from( "test.py" ) ,
1060+ line: 1 ,
1061+ name: "unused-ignore" . to_owned( ) ,
1062+ message: "Unused error code(s) in `# pyrefly: ignore`: bad-override" . to_owned( ) ,
1063+ } ] ;
1064+ assert_remove_ignores_from_serialized ( before, errors, after, 1 ) ;
1065+ }
1066+
1067+ #[ test]
1068+ fn test_remove_unused_ignores_from_serialized_multiple_codes ( ) {
1069+ let before = r#"
1070+ def foo() -> str:
1071+ # pyrefly: ignore [bad-return, unsupported-operation, bad-assignment]
1072+ return 1 + []
1073+ "# ;
1074+ let after = r#"
1075+ def foo() -> str:
1076+ # pyrefly: ignore [bad-return, unsupported-operation]
1077+ return 1 + []
1078+ "# ;
1079+ let errors = vec ! [ SerializedError {
1080+ path: PathBuf :: from( "test.py" ) ,
1081+ line: 2 ,
1082+ name: "unused-ignore" . to_owned( ) ,
1083+ message: "Unused error code(s) in `# pyrefly: ignore`: bad-assignment" . to_owned( ) ,
1084+ } ] ;
1085+ assert_remove_ignores_from_serialized ( before, errors, after, 1 ) ;
1086+ }
1087+
1088+ #[ test]
1089+ fn test_remove_unused_ignores_from_serialized_inline ( ) {
1090+ let input = r#"
1091+ def g() -> str:
1092+ return "hello" # pyrefly: ignore [bad-return]
1093+ "# ;
1094+ let want = r#"
1095+ def g() -> str:
1096+ return "hello"
1097+ "# ;
1098+ let errors = vec ! [ SerializedError {
1099+ path: PathBuf :: from( "test.py" ) ,
1100+ line: 2 ,
1101+ name: "unused-ignore" . to_owned( ) ,
1102+ message: "Unused `# pyrefly: ignore` comment for code(s): bad-return" . to_owned( ) ,
1103+ } ] ;
1104+ assert_remove_ignores_from_serialized ( input, errors, want, 1 ) ;
1105+ }
1106+
1107+ #[ test]
1108+ fn test_remove_unused_ignores_from_serialized_multiple_files ( ) {
1109+ let tdir = tempfile:: tempdir ( ) . unwrap ( ) ;
1110+ let path1 = tdir. path ( ) . join ( "file1.py" ) ;
1111+ let path2 = tdir. path ( ) . join ( "file2.py" ) ;
1112+
1113+ let content1 = "x = 1 # pyrefly: ignore\n " ;
1114+ let content2 = "y = 2 # pyrefly: ignore\n " ;
1115+
1116+ fs_anyhow:: write ( & path1, content1) . unwrap ( ) ;
1117+ fs_anyhow:: write ( & path2, content2) . unwrap ( ) ;
1118+
1119+ let errors = vec ! [
1120+ SerializedError {
1121+ path: path1. clone( ) ,
1122+ line: 0 ,
1123+ name: "unused-ignore" . to_owned( ) ,
1124+ message: "Unused `# pyrefly: ignore` comment" . to_owned( ) ,
1125+ } ,
1126+ SerializedError {
1127+ path: path2. clone( ) ,
1128+ line: 0 ,
1129+ name: "unused-ignore" . to_owned( ) ,
1130+ message: "Unused `# pyrefly: ignore` comment" . to_owned( ) ,
1131+ } ,
1132+ ] ;
1133+
1134+ let removals = suppress:: remove_unused_ignores_from_serialized ( errors) ;
1135+
1136+ assert_eq ! ( fs_anyhow:: read_to_string( & path1) . unwrap( ) , "x = 1\n " ) ;
1137+ assert_eq ! ( fs_anyhow:: read_to_string( & path2) . unwrap( ) , "y = 2\n " ) ;
1138+ assert_eq ! ( removals, 2 ) ;
1139+ }
1140+
1141+ #[ test]
1142+ fn test_remove_unused_ignores_from_serialized_empty_list ( ) {
1143+ let errors: Vec < SerializedError > = vec ! [ ] ;
1144+ let removals = suppress:: remove_unused_ignores_from_serialized ( errors) ;
1145+ assert_eq ! ( removals, 0 ) ;
1146+ }
1147+
1148+ #[ test]
1149+ fn test_remove_unused_ignores_from_serialized_preserves_crlf ( ) {
1150+ let input = "def g() -> str:\r \n return \" hello\" # pyrefly: ignore [bad-return]\r \n " ;
1151+ let want = "def g() -> str:\r \n return \" hello\" \r \n " ;
1152+ let errors = vec ! [ SerializedError {
1153+ path: PathBuf :: from( "test.py" ) ,
1154+ line: 1 ,
1155+ name: "unused-ignore" . to_owned( ) ,
1156+ message: "Unused `# pyrefly: ignore` comment" . to_owned( ) ,
1157+ } ] ;
1158+ assert_remove_ignores_from_serialized ( input, errors, want, 1 ) ;
1159+ }
10031160}
0 commit comments