845 self.__error(arg.lineno - 1, arg.col_offset, "M132", arg.arg) |
845 self.__error(arg.lineno - 1, arg.col_offset, "M132", arg.arg) |
846 |
846 |
847 def __checkComprehensions(self): |
847 def __checkComprehensions(self): |
848 """ |
848 """ |
849 Private method to check some comprehension related things. |
849 Private method to check some comprehension related things. |
850 """ |
850 |
|
851 This method is adapted from: flake8-comprehensions v3.14.0 |
|
852 Original: Copyright (c) 2017 Adam Johnson |
|
853 """ |
|
854 visitedMapCalls = set() |
|
855 |
851 for node in ast.walk(self.__tree): |
856 for node in ast.walk(self.__tree): |
852 if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): |
857 if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): |
853 nArgs = len(node.args) |
858 numPositionalArgs = len(node.args) |
854 nKwArgs = len(node.keywords) |
859 numKeywordArgs = len(node.keywords) |
855 |
860 |
856 if ( |
861 if ( |
857 nArgs == 1 |
862 numPositionalArgs == 1 |
858 and isinstance(node.args[0], ast.GeneratorExp) |
863 and isinstance(node.args[0], ast.GeneratorExp) |
859 and node.func.id in ("list", "set") |
864 and node.func.id in ("list", "set") |
860 ): |
865 ): |
861 errorCode = { |
866 errorCode = { |
862 "list": "M181", |
867 "list": "M180", |
863 "set": "M182", |
868 "set": "M181", |
864 }[node.func.id] |
869 }[node.func.id] |
865 self.__error(node.lineno - 1, node.col_offset, errorCode) |
870 self.__error(node.lineno - 1, node.col_offset, errorCode) |
866 |
871 |
867 elif ( |
872 elif ( |
868 nArgs == 1 |
873 numPositionalArgs == 1 |
|
874 and node.func.id == "dict" |
|
875 and len(node.keywords) == 0 |
869 and isinstance(node.args[0], (ast.GeneratorExp, ast.ListComp)) |
876 and isinstance(node.args[0], (ast.GeneratorExp, ast.ListComp)) |
870 and isinstance(node.args[0].elt, ast.Tuple) |
877 and isinstance(node.args[0].elt, ast.Tuple) |
871 and len(node.args[0].elt.elts) == 2 |
878 and len(node.args[0].elt.elts) == 2 |
872 and node.func.id == "dict" |
|
873 ): |
879 ): |
874 if isinstance(node.args[0], ast.GeneratorExp): |
880 if isinstance(node.args[0], ast.GeneratorExp): |
875 errorCode = "M183" |
881 errorCode = "M182" |
876 else: |
882 else: |
877 errorCode = "M185" |
883 errorCode = "M184" |
878 self.__error(node.lineno - 1, node.col_offset, errorCode) |
884 self.__error(node.lineno - 1, node.col_offset, errorCode) |
879 |
885 |
880 elif ( |
886 elif ( |
881 nArgs == 1 |
887 numPositionalArgs == 1 |
882 and isinstance(node.args[0], ast.ListComp) |
888 and isinstance(node.args[0], ast.ListComp) |
883 and node.func.id in ("list", "set") |
889 and node.func.id in ("list", "set", "any", "all") |
884 ): |
890 ): |
885 errorCode = { |
891 errorCode = { |
886 "list": "M195", |
892 "list": "M191", |
887 "set": "M184", |
893 "set": "M183", |
|
894 "any": "M199", |
|
895 "all": "M199", |
888 }[node.func.id] |
896 }[node.func.id] |
889 self.__error(node.lineno - 1, node.col_offset, errorCode) |
897 self.__error( |
890 |
898 node.lineno - 1, node.col_offset, errorCode, node.func.id |
891 elif nArgs == 1 and ( |
899 ) |
|
900 |
|
901 elif numPositionalArgs == 1 and ( |
892 isinstance(node.args[0], ast.Tuple) |
902 isinstance(node.args[0], ast.Tuple) |
893 and node.func.id == "tuple" |
903 and node.func.id == "tuple" |
894 or isinstance(node.args[0], ast.List) |
904 or isinstance(node.args[0], ast.List) |
895 and node.func.id == "list" |
905 and node.func.id == "list" |
896 ): |
906 ): |
897 errorCode = { |
907 errorCode = { |
898 "tuple": "M197", |
908 "tuple": "M189a", |
899 "list": "M198", |
909 "list": "M190a", |
|
910 }[node.func.id] |
|
911 ##suffix = "remove the outer call to {func}()." |
|
912 self.__error( |
|
913 node.lineno - 1, |
|
914 node.col_offset, |
|
915 errorCode, |
|
916 type(node.args[0]).__name__.lower(), |
|
917 node.func.id, |
|
918 ) |
|
919 |
|
920 elif ( |
|
921 numPositionalArgs == 1 |
|
922 and numKeywordArgs == 0 |
|
923 and isinstance(node.args[0], (ast.Dict, ast.DictComp)) |
|
924 and node.func.id == "dict" |
|
925 ): |
|
926 if isinstance(node.args[0], ast.Dict): |
|
927 type_ = "dict" |
|
928 else: |
|
929 type_ = "dict comprehension" |
|
930 self.__error( |
|
931 node.lineno - 1, |
|
932 node.col_offset, |
|
933 "M198", |
|
934 type_, |
|
935 ) |
|
936 |
|
937 elif ( |
|
938 numPositionalArgs == 1 |
|
939 and isinstance(node.args[0], (ast.Tuple, ast.List)) |
|
940 and ( |
|
941 node.func.id in ("tuple", "list", "set") |
|
942 or ( |
|
943 node.func.id == "dict" |
|
944 and all( |
|
945 isinstance(elt, ast.Tuple) and len(elt.elts) == 2 |
|
946 for elt in node.args[0].elts |
|
947 ) |
|
948 ) |
|
949 ) |
|
950 ): |
|
951 ##suffix = "rewrite as a {func} literal." |
|
952 errorCode = { |
|
953 "tuple": "M189b", |
|
954 "list": "M190b", |
|
955 "set": "M185", |
|
956 "dict": "M186", |
900 }[node.func.id] |
957 }[node.func.id] |
901 self.__error( |
958 self.__error( |
902 node.lineno - 1, |
959 node.lineno - 1, |
903 node.col_offset, |
960 node.col_offset, |
904 errorCode, |
961 errorCode, |
905 type(node.args[0]).__name__.lower(), |
962 type(node.args[0]).__name__.lower(), |
906 node.func.id, |
963 node.func.id, |
907 ) |
964 ) |
908 |
965 |
909 elif ( |
966 elif ( |
910 nArgs == 1 |
967 numPositionalArgs == 0 |
911 and isinstance(node.args[0], (ast.Tuple, ast.List)) |
|
912 and node.func.id in ("tuple", "list", "set", "dict") |
|
913 ): |
|
914 errorCode = { |
|
915 "tuple": "M192", |
|
916 "list": "M193", |
|
917 "set": "M191", |
|
918 "dict": "M191", |
|
919 }[node.func.id] |
|
920 self.__error( |
|
921 node.lineno - 1, |
|
922 node.col_offset, |
|
923 errorCode, |
|
924 type(node.args[0]).__name__.lower(), |
|
925 node.func.id, |
|
926 ) |
|
927 |
|
928 elif ( |
|
929 nArgs == 0 |
|
930 and not any(isinstance(a, ast.Starred) for a in node.args) |
968 and not any(isinstance(a, ast.Starred) for a in node.args) |
931 and not any(k.arg is None for k in node.keywords) |
969 and not any(k.arg is None for k in node.keywords) |
932 and node.func.id == "dict" |
970 and node.func.id == "dict" |
933 ) or ( |
971 ) or ( |
934 nArgs == 0 and nKwArgs == 0 and node.func.id in ("tuple", "list") |
972 numKeywordArgs == 0 and numKeywordArgs == 0 and node.func.id in ( |
|
973 "tuple", "list" |
|
974 ) |
935 ): |
975 ): |
936 self.__error(node.lineno - 1, node.col_offset, "M186", node.func.id) |
976 self.__error(node.lineno - 1, node.col_offset, "M188", node.func.id) |
937 |
977 |
938 elif ( |
978 elif ( |
939 node.func.id in {"list", "reversed"} |
979 node.func.id in {"list", "reversed"} |
940 and nArgs > 0 |
980 and numPositionalArgs > 0 |
941 and isinstance(node.args[0], ast.Call) |
981 and isinstance(node.args[0], ast.Call) |
942 and isinstance(node.args[0].func, ast.Name) |
982 and isinstance(node.args[0].func, ast.Name) |
943 and node.args[0].func.id == "sorted" |
983 and node.args[0].func.id == "sorted" |
944 ): |
984 ): |
945 if node.func.id == "reversed": |
985 if node.func.id == "reversed": |
997 ) |
1044 ) |
998 ): |
1045 ): |
999 self.__error( |
1046 self.__error( |
1000 node.lineno - 1, |
1047 node.lineno - 1, |
1001 node.col_offset, |
1048 node.col_offset, |
1002 "M188", |
1049 "M194", |
1003 node.args[0].func.id, |
1050 node.args[0].func.id, |
1004 node.func.id, |
1051 node.func.id, |
1005 ) |
1052 ) |
1006 |
1053 |
1007 elif ( |
1054 elif ( |
1008 node.func.id in {"reversed", "set", "sorted"} |
1055 node.func.id in {"reversed", "set", "sorted"} |
1009 and nArgs > 0 |
1056 and numPositionalArgs > 0 |
1010 and isinstance(node.args[0], ast.Subscript) |
1057 and isinstance(node.args[0], ast.Subscript) |
1011 and isinstance(node.args[0].slice, ast.Slice) |
1058 and isinstance(node.args[0].slice, ast.Slice) |
1012 and node.args[0].slice.lower is None |
1059 and node.args[0].slice.lower is None |
1013 and node.args[0].slice.upper is None |
1060 and node.args[0].slice.upper is None |
1014 and isinstance(node.args[0].slice.step, ast.UnaryOp) |
1061 and isinstance(node.args[0].slice.step, ast.UnaryOp) |
1015 and isinstance(node.args[0].slice.step.op, ast.USub) |
1062 and isinstance(node.args[0].slice.step.op, ast.USub) |
1016 and isinstance(node.args[0].slice.step.operand, ast.Constant) |
1063 and isinstance(node.args[0].slice.step.operand, ast.Constant) |
1017 and node.args[0].slice.step.operand.n == 1 |
1064 and node.args[0].slice.step.operand.n == 1 |
1018 ): |
1065 ): |
1019 self.__error(node.lineno - 1, node.col_offset, "M189", node.func.id) |
1066 self.__error(node.lineno - 1, node.col_offset, "M195", node.func.id) |
1020 |
1067 |
1021 elif isinstance(node, (ast.ListComp, ast.SetComp)) and ( |
1068 elif ( |
|
1069 node.func.id == "map" |
|
1070 and node not in visitedMapCalls |
|
1071 and len(node.args) == 2 |
|
1072 and isinstance(node.args[0], ast.Lambda) |
|
1073 ): |
|
1074 self.__error(node.lineno - 1, node.col_offset, "M197", "generator expression") |
|
1075 |
|
1076 elif ( |
|
1077 node.func.id in ("list", "set", "dict") |
|
1078 and len(node.args) == 1 |
|
1079 and isinstance(node.args[0], ast.Call) |
|
1080 and isinstance(node.args[0].func, ast.Name) |
|
1081 and node.args[0].func.id == "map" |
|
1082 and len(node.args[0].args) == 2 |
|
1083 and isinstance(node.args[0].args[0], ast.Lambda) |
|
1084 ): |
|
1085 # To avoid raising M197 on the map() call inside the list/set/dict. |
|
1086 mapCall = node.args[0] |
|
1087 visitedMapCalls.add(mapCall) |
|
1088 |
|
1089 rewriteable = True |
|
1090 if node.func.id == "dict": |
|
1091 # For the generator expression to be rewriteable as a |
|
1092 # dict comprehension, its lambda must return a 2-tuple. |
|
1093 lambdaNode = node.args[0].args[0] |
|
1094 if ( |
|
1095 not isinstance(lambdaNode.body, (ast.List, ast.Tuple)) |
|
1096 or len(lambdaNode.body.elts) != 2 |
|
1097 ): |
|
1098 rewriteable = False |
|
1099 |
|
1100 if rewriteable: |
|
1101 comprehensionType = f"{node.func.id} comprehension" |
|
1102 self.__error(node.lineno - 1, node.col_offset, "M197", comprehensionType) |
|
1103 |
|
1104 elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)) and ( |
1022 len(node.generators) == 1 |
1105 len(node.generators) == 1 |
1023 and not node.generators[0].ifs |
1106 and not node.generators[0].ifs |
1024 and not node.generators[0].is_async |
1107 and not node.generators[0].is_async |
1025 and ( |
1108 and ( |
1026 isinstance(node.elt, ast.Name) |
1109 ( |
1027 and isinstance(node.generators[0].target, ast.Name) |
1110 isinstance(node, (ast.ListComp, ast.SetComp)) |
1028 and node.elt.id == node.generators[0].target.id |
1111 and isinstance(node.elt, ast.Name) |
|
1112 and isinstance(node.generators[0].target, ast.Name) |
|
1113 and node.elt.id == node.generators[0].target.id |
|
1114 ) |
|
1115 or ( |
|
1116 isinstance(node, ast.DictComp) |
|
1117 and isinstance(node.key, ast.Name) |
|
1118 and isinstance(node.value, ast.Name) |
|
1119 and isinstance(node.generators[0].target, ast.Tuple) |
|
1120 and len(node.generators[0].target.elts) == 2 |
|
1121 and isinstance(node.generators[0].target.elts[0], ast.Name) |
|
1122 and node.generators[0].target.elts[0].id == node.key.id |
|
1123 and isinstance(node.generators[0].target.elts[1], ast.Name) |
|
1124 and node.generators[0].target.elts[1].id == node.value.id |
|
1125 ) |
1029 ) |
1126 ) |
1030 ): |
1127 ): |
1031 compType = { |
1128 compType = { |
1032 ast.DictComp: "dict", |
1129 ast.DictComp: "dict", |
1033 ast.ListComp: "list", |
1130 ast.ListComp: "list", |