11#!/usr/bin/env python3
22"""
3- Check for type:ignore regression in the codebase.
3+ Check for prohibited # type: comments in the codebase.
44
5- This script counts type:ignore comments and fails if the count increases
6- beyond the baseline. Run with --update to set a new baseline.
5+ This script scans for ANY # type: comments and fails if found.
6+ All type annotations must use Python 3.12+ inline syntax.
7+
8+ Prohibited patterns:
9+ # type: ignore
10+ # type: ignore[error-code]
11+ # type: SomeType (Python 2 style annotation)
712
813Usage:
9- ./qa/bin/check_type_ignores # Check against baseline
10- ./qa/bin/check_type_ignores --update # Update baseline
11- ./qa/bin/check_type_ignores --show # Show all type:ignore locations
14+ ./qa/bin/check_type_ignores # Check and fail if any found
15+ ./qa/bin/check_type_ignores --show # Show all locations
1216"""
1317
1418from __future__ import annotations
@@ -17,32 +21,29 @@ import argparse
1721import re
1822import sys
1923from pathlib import Path
20- from typing import Dict , List , Tuple
2124
22- # Baseline file location
23- BASELINE_FILE = Path (__file__ ).parent .parent / 'type_ignore_baseline.txt'
2425SRC_DIR = Path (__file__ ).parent .parent .parent / 'src' / 'exabgp'
2526
26- # Pattern to match type: ignore comments
27- TYPE_IGNORE_PATTERN = re .compile (r'#\s*type: \s*ignore ' )
27+ # Pattern to match ANY # type: comment ( ignore, type annotations, etc.)
28+ TYPE_COMMENT_PATTERN = re .compile (r'#\s*type\s*: ' )
2829
2930
30- def find_type_ignores () -> Dict [str , List [ Tuple [int , str ]]]:
31- """Find all type:ignore comments in the codebase."""
32- results : Dict [str , List [ Tuple [int , str ]]] = {}
31+ def find_type_comments () -> dict [str , list [ tuple [int , str ]]]:
32+ """Find all # type: comments in the codebase."""
33+ results : dict [str , list [ tuple [int , str ]]] = {}
3334
3435 for py_file in SRC_DIR .rglob ('*.py' ):
3536 # Skip vendored files
3637 if 'vendoring' in str (py_file ):
3738 continue
3839
3940 rel_path = str (py_file .relative_to (SRC_DIR .parent .parent ))
40- matches : List [ Tuple [int , str ]] = []
41+ matches : list [ tuple [int , str ]] = []
4142
4243 try :
4344 with open (py_file , 'r' , encoding = 'utf-8' ) as f :
4445 for line_num , line in enumerate (f , 1 ):
45- if TYPE_IGNORE_PATTERN .search (line ):
46+ if TYPE_COMMENT_PATTERN .search (line ):
4647 matches .append ((line_num , line .rstrip ()))
4748 except Exception as e :
4849 print (f'Error reading { py_file } : { e } ' , file = sys .stderr )
@@ -54,31 +55,13 @@ def find_type_ignores() -> Dict[str, List[Tuple[int, str]]]:
5455 return results
5556
5657
57- def count_ignores (results : Dict [str , List [ Tuple [int , str ]]]) -> int :
58- """Count total number of type:ignore comments."""
58+ def count_comments (results : dict [str , list [ tuple [int , str ]]]) -> int :
59+ """Count total number of # type: comments."""
5960 return sum (len (matches ) for matches in results .values ())
6061
6162
62- def read_baseline () -> int :
63- """Read the baseline count from file."""
64- if not BASELINE_FILE .exists ():
65- return - 1
66- try :
67- with open (BASELINE_FILE , 'r' ) as f :
68- return int (f .read ().strip ())
69- except (ValueError , IOError ):
70- return - 1
71-
72-
73- def write_baseline (count : int ) -> None :
74- """Write the baseline count to file."""
75- with open (BASELINE_FILE , 'w' ) as f :
76- f .write (f'{ count } \n ' )
77- print (f'Baseline updated: { count } type:ignore comments' )
78-
79-
80- def show_ignores (results : Dict [str , List [Tuple [int , str ]]]) -> None :
81- """Display all type:ignore locations."""
63+ def show_comments (results : dict [str , list [tuple [int , str ]]]) -> None :
64+ """Display all # type: comment locations."""
8265 for filepath in sorted (results .keys ()):
8366 matches = results [filepath ]
8467 print (f'\n { filepath } :' )
@@ -87,44 +70,34 @@ def show_ignores(results: Dict[str, List[Tuple[int, str]]]) -> None:
8770
8871
8972def main () -> int :
90- parser = argparse .ArgumentParser (description = 'Check type:ignore regression' )
91- parser .add_argument ('--update' , action = 'store_true' , help = 'Update baseline' )
73+ parser = argparse .ArgumentParser (description = 'Check for prohibited # type: comments' )
9274 parser .add_argument ('--show' , action = 'store_true' , help = 'Show all locations' )
9375 args = parser .parse_args ()
9476
95- results = find_type_ignores ()
96- current_count = count_ignores (results )
77+ results = find_type_comments ()
78+ current_count = count_comments (results )
9779
9880 if args .show :
99- show_ignores (results )
100- print (f'\n Total: { current_count } type:ignore comments in { len (results )} files' )
101- return 0
102-
103- if args .update :
104- write_baseline (current_count )
81+ if results :
82+ show_comments (results )
83+ print (f'\n Total: { current_count } # type: comments in { len (results )} files' )
84+ else :
85+ print ('No # type: comments found' )
10586 return 0
10687
107- # Check mode
108- baseline = read_baseline ()
109-
110- if baseline < 0 :
111- print (f'No baseline found. Current count: { current_count } ' )
112- print ('Run with --update to create baseline' )
113- return 1
114-
115- print (f'type:ignore count: { current_count } (baseline: { baseline } )' )
116-
117- if current_count > baseline :
118- print (f'FAIL: { current_count - baseline } new type:ignore comments added' )
88+ # Check mode - fail if ANY # type: comments found
89+ if current_count > 0 :
90+ print (f'FAIL: Found { current_count } prohibited # type: comments in { len (results )} files' )
91+ print ()
92+ print ('All # type: comments are prohibited. Use Python 3.12+ inline annotations instead:' )
93+ print (' Bad: x = value # type: int' )
94+ print (' Good: x: int = value' )
95+ print ()
11996 print ('Run with --show to see all locations' )
12097 return 1
121- elif current_count < baseline :
122- print (f'IMPROVEMENT: { baseline - current_count } type:ignore comments removed' )
123- print ('Consider running --update to lower the baseline' )
124- return 0
125- else :
126- print ('OK: No regression' )
127- return 0
98+
99+ print ('OK: No # type: comments found' )
100+ return 0
128101
129102
130103if __name__ == '__main__' :
0 commit comments