@@ -848,6 +848,63 @@ def _is_metaclass(node: ast.ClassDef) -> bool:
848
848
return any (_is_metaclass_base (base ) for base in node .bases )
849
849
850
850
851
+ def _check_import_or_attribute (
852
+ node : ast .Attribute | ast .ImportFrom , module_name : str , object_name : str
853
+ ) -> str | None :
854
+ """If `node` represents a bad import, return the approriate error message.
855
+
856
+ Else, return None.
857
+ """
858
+ fullname = f"{ module_name } .{ object_name } "
859
+
860
+ # Y057 errors
861
+ if fullname in {"typing.ByteString" , "collections.abc.ByteString" }:
862
+ return Y057 .format (module = module_name )
863
+
864
+ # Y024 errors
865
+ if fullname == "collections.namedtuple" :
866
+ return Y024
867
+
868
+ if module_name in _TYPING_MODULES :
869
+ # Y022 errors
870
+ if object_name in _BAD_Y022_IMPORTS :
871
+ good_cls_name , slice_contents = _BAD_Y022_IMPORTS [object_name ]
872
+ params = "" if slice_contents is None else f"[{ slice_contents } ]"
873
+ return Y022 .format (
874
+ good_syntax = f'"{ good_cls_name } { params } "' ,
875
+ bad_syntax = f'"{ fullname } { params } "' ,
876
+ )
877
+
878
+ # Y037 errors
879
+ if object_name == "Optional" :
880
+ return Y037 .format (
881
+ old_syntax = fullname , example = '"int | None" instead of "Optional[int]"'
882
+ )
883
+ if object_name == "Union" :
884
+ return Y037 .format (
885
+ old_syntax = fullname , example = '"int | str" instead of "Union[int, str]"'
886
+ )
887
+
888
+ # Y039 errors
889
+ if object_name == "Text" :
890
+ return Y039 .format (module = module_name )
891
+
892
+ # Y023 errors
893
+ if module_name == "typing_extensions" :
894
+ if object_name in _BAD_TYPINGEXTENSIONS_Y023_IMPORTS :
895
+ return Y023 .format (
896
+ good_syntax = f'"typing.{ object_name } "' ,
897
+ bad_syntax = f'"typing_extensions.{ object_name } "' ,
898
+ )
899
+ if object_name == "ClassVar" :
900
+ return Y023 .format (
901
+ good_syntax = '"typing.ClassVar[T]"' ,
902
+ bad_syntax = '"typing_extensions.ClassVar[T]"' ,
903
+ )
904
+
905
+ return None
906
+
907
+
851
908
@dataclass
852
909
class NestingCounter :
853
910
"""Class to help the PyiVisitor keep track of internal state"""
@@ -916,67 +973,12 @@ def __init__(self, filename: str) -> None:
916
973
def __repr__ (self ) -> str :
917
974
return f"{ self .__class__ .__name__ } (filename={ self .filename !r} )"
918
975
919
- def _check_import_or_attribute (
920
- self , node : ast .Attribute | ast .ImportFrom , module_name : str , object_name : str
921
- ) -> None :
922
- fullname = f"{ module_name } .{ object_name } "
923
-
924
- # Y057 errors
925
- if fullname in {"typing.ByteString" , "collections.abc.ByteString" }:
926
- error_message = Y057 .format (module = module_name )
927
-
928
- # Y022 errors
929
- elif module_name in _TYPING_MODULES and object_name in _BAD_Y022_IMPORTS :
930
- good_cls_name , slice_contents = _BAD_Y022_IMPORTS [object_name ]
931
- params = "" if slice_contents is None else f"[{ slice_contents } ]"
932
- error_message = Y022 .format (
933
- good_syntax = f'"{ good_cls_name } { params } "' ,
934
- bad_syntax = f'"{ fullname } { params } "' ,
935
- )
936
-
937
- # Y037 errors
938
- elif module_name in _TYPING_MODULES and object_name == "Optional" :
939
- error_message = Y037 .format (
940
- old_syntax = fullname , example = '"int | None" instead of "Optional[int]"'
941
- )
942
- elif module_name in _TYPING_MODULES and object_name == "Union" :
943
- error_message = Y037 .format (
944
- old_syntax = fullname , example = '"int | str" instead of "Union[int, str]"'
945
- )
946
-
947
- # Y039 errors
948
- elif module_name in _TYPING_MODULES and object_name == "Text" :
949
- error_message = Y039 .format (module = module_name )
950
-
951
- # Y023 errors
952
- elif module_name == "typing_extensions" :
953
- if object_name in _BAD_TYPINGEXTENSIONS_Y023_IMPORTS :
954
- error_message = Y023 .format (
955
- good_syntax = f'"typing.{ object_name } "' ,
956
- bad_syntax = f'"typing_extensions.{ object_name } "' ,
957
- )
958
- elif object_name == "ClassVar" :
959
- error_message = Y023 .format (
960
- good_syntax = '"typing.ClassVar[T]"' ,
961
- bad_syntax = '"typing_extensions.ClassVar[T]"' ,
962
- )
963
- else :
964
- return
965
-
966
- # Y024 errors
967
- elif fullname == "collections.namedtuple" :
968
- error_message = Y024
969
-
970
- else :
971
- return
972
-
973
- self .error (node , error_message )
974
-
975
976
def visit_Attribute (self , node : ast .Attribute ) -> None :
976
977
self .generic_visit (node )
977
- self . _check_import_or_attribute (
978
+ if error_msg := _check_import_or_attribute (
978
979
node = node , module_name = unparse (node .value ), object_name = node .attr
979
- )
980
+ ):
981
+ self .error (node , error_msg )
980
982
981
983
def visit_ImportFrom (self , node : ast .ImportFrom ) -> None :
982
984
self .generic_visit (node )
@@ -1000,7 +1002,8 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
1000
1002
self .error (node , Y025 )
1001
1003
1002
1004
for object_name in imported_names :
1003
- self ._check_import_or_attribute (node , module_name , object_name )
1005
+ if error_msg := _check_import_or_attribute (node , module_name , object_name ):
1006
+ self .error (node , error_msg )
1004
1007
1005
1008
if module_name in _TYPING_MODULES and "AbstractSet" in imported_names :
1006
1009
self .error (node , Y038 .format (module = module_name ))
0 commit comments