diff --git a/scripts/enabled_test_modules.py b/scripts/enabled_test_modules.py index 48b6c87..468ee5c 100644 --- a/scripts/enabled_test_modules.py +++ b/scripts/enabled_test_modules.py @@ -15,19 +15,16 @@ MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_arg EXTERNAL_MODULES = ['psycopg2', 'PIL', 'selenium', 'oracle', 'mysql', 'sqlite3', 'sqlparse', 'tblib', 'numpy', 'bcrypt', 'argon2', 'xml.dom'] IGNORED_ERRORS = { - '__new_common__': [ + '__common__': [ *MOCK_OBJECTS, *EXTERNAL_MODULES, 'Need type annotation for', 'has no attribute "getvalue"', 'Cannot assign to a method', - 'Cannot infer type of lambda', 'already defined', 'Cannot assign to a type', - '"HttpResponse" has no attribute', '"HttpResponseBase" has no attribute', '"object" has no attribute', - 'defined in the current module', re.compile(r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" ' r'has no attribute'), 'has no attribute "deconstruct"', @@ -48,7 +45,6 @@ IGNORED_ERRORS = { 'gets multiple values for keyword argument', '"Handler" has no attribute', 'Module has no attribute', - "No installed app with label 'missing'", 'namedtuple', 'Lookups not supported yet', # TODO: see test in managers/test_managers.yml @@ -116,7 +112,6 @@ IGNORED_ERRORS = { ], 'custom_managers': [ 'Unsupported dynamic base class', - '"Book" has no attribute "favorite_avg"', 'Incompatible types in assignment (expression has type "CharField', 'Item "Book" of "Optional[Book]" has no attribute "favorite_avg"' ], @@ -151,13 +146,8 @@ IGNORED_ERRORS = { 'has incompatible type "str"' ], 'file_uploads': [ - '"Iterable[Any]" has no attribute', - '"IO[Any]" has no attribute', 'has no attribute "content_type"', ], - 'file_storage': [ - 'Incompatible types in assignment (expression has type "Callable"' - ], 'files': [ 'Incompatible types in assignment (expression has type "IOBase", variable has type "File")', ], @@ -196,9 +186,6 @@ IGNORED_ERRORS = { 'middleware': [ '"HttpRequest" has no attribute' ], - 'managers_regress': [ - '"Type[AbstractBase3]" has no attribute "objects"' - ], 'many_to_one': [ 'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")', 'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")' @@ -208,7 +195,6 @@ IGNORED_ERRORS = { ], 'model_fields': [ 'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute', - 'has no attribute "field"', 'Incompatible types in assignment (expression has type "Type[Person', 'base class "IntegerFieldTests"', 'ImageFieldTestMixin', @@ -252,9 +238,6 @@ IGNORED_ERRORS = { re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", ' r'base class "(UnaccentTest|TrigramTest)" defined the type as "Type\[CharFieldModel\]"\)'), '("None" and "SearchQuery")', - # TODO: - 'django.contrib.postgres.forms', - 'django.contrib.postgres.aggregates', ], 'properties': [ re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"') @@ -278,8 +261,6 @@ IGNORED_ERRORS = { 'Unsupported operand types for & ("Manager[Author]" and "Manager[Tag]")', 'Unsupported operand types for | ("Manager[Author]" and "Manager[Tag]")', 'ObjectA', - 'ObjectB', - 'ObjectC', "'flat' and 'named' can't be used together", '"Collection[Any]" has no attribute "explain"' ], @@ -337,7 +318,6 @@ IGNORED_ERRORS = { 'Argument 1 to "get_callable" has incompatible type "int"' ], 'utils_tests': [ - 'Too few arguments for "__init__"', 'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"', 'Incompatible types in assignment (expression has type "None", base class "object" defined the type as', 'Class', @@ -352,3 +332,19 @@ IGNORED_ERRORS = { "Module 'django.views.debug' has no attribute 'Path'" ] } + + +def check_if_custom_ignores_are_covered_by_common() -> None: + from scripts.typecheck_tests import is_pattern_fits + + common_ignore_patterns = IGNORED_ERRORS['__common__'] + for module_name, patterns in IGNORED_ERRORS.items(): + if module_name == '__common__': + continue + for pattern in patterns: + for common_pattern in common_ignore_patterns: + if isinstance(pattern, str) and is_pattern_fits(common_pattern, pattern): + print(f'pattern "{module_name}: {pattern!r}" is covered by pattern {common_pattern!r}') + + +check_if_custom_ignores_are_covered_by_common() diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index df3b639..04209fb 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -4,7 +4,7 @@ import subprocess import sys from collections import defaultdict from pathlib import Path -from typing import Dict, Pattern, Union +from typing import Dict, List, Pattern, Union from scripts.enabled_test_modules import ( EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS, @@ -13,12 +13,14 @@ from scripts.enabled_test_modules import ( PROJECT_DIRECTORY = Path(__file__).parent.parent -def print_unused_ignores(ignored_message_freq): +def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], int]]) -> List[str]: + unused_ignores = [] for root_key, patterns in IGNORED_ERRORS.items(): for pattern in patterns: if (ignored_message_freq[root_key][pattern] == 0 and pattern not in itertools.chain(EXTERNAL_MODULES, MOCK_OBJECTS)): - print(f'{root_key}: {pattern}') + unused_ignores.append(f'{root_key}: {pattern}') + return unused_ignores def is_pattern_fits(pattern: Union[Pattern, str], line: str): @@ -38,16 +40,16 @@ def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: Dict[ if test_folder_name in IGNORED_MODULES: return True - for pattern in IGNORED_ERRORS['__new_common__']: - if is_pattern_fits(pattern, line): - ignored_message_freqs['__new_common__'][pattern] += 1 - return True - for pattern in IGNORED_ERRORS.get(test_folder_name, []): if is_pattern_fits(pattern, line): ignored_message_freqs[test_folder_name][pattern] += 1 return True + for pattern in IGNORED_ERRORS['__common__']: + if is_pattern_fits(pattern, line): + ignored_message_freqs['__common__'][pattern] += 1 + return True + return False @@ -108,8 +110,11 @@ if __name__ == '__main__': global_rc = 1 print(line) - print('UNUSED IGNORES ------------------------------------------------') - print_unused_ignores(ignored_message_freqs) + unused_ignores = get_unused_ignores(ignored_message_freqs) + if unused_ignores: + print('UNUSED IGNORES ------------------------------------------------') + print('\n'.join(unused_ignores)) + sys.exit(global_rc) except BaseException as exc: