22import  os .path 
33import  sys 
44import  traceback 
5+ from  collections  import  OrderedDict , defaultdict 
56
6- from  typing  import  Tuple , List , TypeVar , Set 
7+ from  typing  import  Tuple , List , TypeVar , Set ,  Dict ,  Optional 
78
89
910T  =  TypeVar ('T' )
@@ -79,8 +80,11 @@ class Errors:
7980    # Stack of short names of current functions or members (or None). 
8081    function_or_member  =  None   # type: List[str] 
8182
82-     # Ignore errors on these lines. 
83-     ignored_lines  =  None   # type: Set[int] 
83+     # Ignore errors on these lines of each file. 
84+     ignored_lines  =  None   # type: Dict[str, Set[int]] 
85+ 
86+     # Lines on which an error was actually ignored. 
87+     used_ignored_lines  =  None   # type: Dict[str, Set[int]] 
8488
8589    # Collection of reported only_once messages. 
8690    only_once_messages  =  None   # type: Set[str] 
@@ -90,7 +94,8 @@ def __init__(self) -> None:
9094        self .import_ctx  =  []
9195        self .type_name  =  [None ]
9296        self .function_or_member  =  [None ]
93-         self .ignored_lines  =  set ()
97+         self .ignored_lines  =  OrderedDict ()
98+         self .used_ignored_lines  =  defaultdict (set )
9499        self .only_once_messages  =  set ()
95100
96101    def  copy (self ) ->  'Errors' :
@@ -109,13 +114,26 @@ def set_ignore_prefix(self, prefix: str) -> None:
109114            prefix  +=  os .sep 
110115        self .ignore_prefix  =  prefix 
111116
112-     def  set_file (self , file : str ) ->  None :
113-         """Set the path of the current file.""" 
117+     def  simplify_path (self , file : str ) ->  str :
114118        file  =  os .path .normpath (file )
115-         self .file  =  remove_path_prefix (file , self .ignore_prefix )
119+         return  remove_path_prefix (file , self .ignore_prefix )
120+ 
121+     def  set_file (self , file : str , ignored_lines : Set [int ] =  None ) ->  None :
122+         """Set the path of the current file.""" 
123+         # The path will be simplified later, in render_messages. That way 
124+         #  * 'file' is always a key that uniquely identifies a source file 
125+         #    that mypy read (simplified paths might not be unique); and 
126+         #  * we only have to simplify in one place, while still supporting 
127+         #    reporting errors for files other than the one currently being 
128+         #    processed. 
129+         self .file  =  file 
130+ 
131+     def  set_file_ignored_lines (self , file : str , ignored_lines : Set [int ] =  None ) ->  None :
132+         self .ignored_lines [file ] =  ignored_lines 
116133
117-     def  set_ignored_lines (self , ignored_lines : Set [int ]) ->  None :
118-         self .ignored_lines  =  ignored_lines 
134+     def  mark_file_ignored_lines_used (self , file : str , used_ignored_lines : Set [int ] =  None 
135+                                      ) ->  None :
136+         self .used_ignored_lines [file ] |=  used_ignored_lines 
119137
120138    def  push_function (self , name : str ) ->  None :
121139        """Set the current function or member short name (it can be None).""" 
@@ -170,15 +188,25 @@ def report(self, line: int, message: str, blocker: bool = False,
170188        self .add_error_info (info )
171189
172190    def  add_error_info (self , info : ErrorInfo ) ->  None :
173-         if  info .line  in  self .ignored_lines :
191+         if  info .file   in   self . ignored_lines   and   info . line  in  self .ignored_lines [ info . file ] :
174192            # Annotation requests us to ignore all errors on this line. 
193+             self .used_ignored_lines [info .file ].add (info .line )
175194            return 
176195        if  info .only_once :
177196            if  info .message  in  self .only_once_messages :
178197                return 
179198            self .only_once_messages .add (info .message )
180199        self .error_info .append (info )
181200
201+     def  generate_unused_ignore_notes (self ) ->  None :
202+         for  file , ignored_lines  in  self .ignored_lines .items ():
203+             for  line  in  ignored_lines  -  self .used_ignored_lines [file ]:
204+                 # Don't use report since add_error_info will ignore the error! 
205+                 info  =  ErrorInfo (self .import_context (), file , None , None ,
206+                                  line , 'note' , "unused 'type: ignore' comment" ,
207+                                  False , False )
208+                 self .error_info .append (info )
209+ 
182210    def  num_messages (self ) ->  int :
183211        """Return the number of generated messages.""" 
184212        return  len (self .error_info )
@@ -254,32 +282,34 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[str, int,
254282                    result .append ((None , - 1 , 'note' , fmt .format (path , line )))
255283                    i  -=  1 
256284
285+             file  =  self .simplify_path (e .file )
286+ 
257287            # Report context within a source file. 
258288            if  (e .function_or_member  !=  prev_function_or_member  or 
259289                    e .type  !=  prev_type ):
260290                if  e .function_or_member  is  None :
261291                    if  e .type  is  None :
262-                         result .append ((e . file , - 1 , 'note' , 'At top level:' ))
292+                         result .append ((file , - 1 , 'note' , 'At top level:' ))
263293                    else :
264-                         result .append ((e . file , - 1 , 'note' , 'In class "{}":' .format (
294+                         result .append ((file , - 1 , 'note' , 'In class "{}":' .format (
265295                            e .type )))
266296                else :
267297                    if  e .type  is  None :
268-                         result .append ((e . file , - 1 , 'note' ,
298+                         result .append ((file , - 1 , 'note' ,
269299                                       'In function "{}":' .format (
270300                                           e .function_or_member )))
271301                    else :
272-                         result .append ((e . file , - 1 , 'note' ,
302+                         result .append ((file , - 1 , 'note' ,
273303                                       'In member "{}" of class "{}":' .format (
274304                                           e .function_or_member , e .type )))
275305            elif  e .type  !=  prev_type :
276306                if  e .type  is  None :
277-                     result .append ((e . file , - 1 , 'note' , 'At top level:' ))
307+                     result .append ((file , - 1 , 'note' , 'At top level:' ))
278308                else :
279-                     result .append ((e . file , - 1 , 'note' ,
309+                     result .append ((file , - 1 , 'note' ,
280310                                   'In class "{}":' .format (e .type )))
281311
282-             result .append ((e . file , e .line , e .severity , e .message ))
312+             result .append ((file , e .line , e .severity , e .message ))
283313
284314            prev_import_context  =  e .import_ctx 
285315            prev_function_or_member  =  e .function_or_member 
0 commit comments