|
34 | 34 |
|
35 | 35 | """Checker for string formatting operations.
|
36 | 36 | """
|
37 |
| - |
| 37 | +import ast |
38 | 38 | import collections
|
39 | 39 | import numbers
|
40 | 40 | import re
|
|
151 | 151 | "E1310": (
|
152 | 152 | "Suspicious argument in %s.%s call",
|
153 | 153 | "bad-str-strip-call",
|
154 |
| - "The argument to a str.{l,r,}strip call contains a duplicate character, ", |
| 154 | + "The argument to a str.{l,r,}strip call contains a duplicate character, ", # pylint: disable=possible-forgotten-f-prefix |
155 | 155 | ),
|
156 | 156 | "W1302": (
|
157 | 157 | "Invalid format string",
|
|
189 | 189 | "W1307": (
|
190 | 190 | "Using invalid lookup key %r in format specifier %r",
|
191 | 191 | "invalid-format-index",
|
192 |
| - "Used when a PEP 3101 format string uses a lookup specifier " |
| 192 | + "Used when a PEP 3101 format string uses a lookup specifier " # pylint: disable=possible-forgotten-f-prefix |
193 | 193 | "({a[1]}), but the argument passed for formatting "
|
194 | 194 | "doesn't contain or doesn't have that key as an attribute.",
|
195 | 195 | ),
|
@@ -932,49 +932,31 @@ def _detect_possible_f_string(self, node: astroid.Const):
|
932 | 932 | """Check whether strings include local/global variables in '{}'
|
933 | 933 | Those should probably be f-strings's
|
934 | 934 | """
|
935 |
| - |
936 |
| - def detect_if_used_in_format(node: astroid.Const, assign_name: str) -> bool: |
937 |
| - """Check if the node is used in a call to format() if so return True""" |
938 |
| - # the skip_class is to make sure we don't go into inner scopes, but might not be needed per se |
939 |
| - for attr in node.scope().nodes_of_class( |
940 |
| - astroid.Attribute, skip_klass=(astroid.FunctionDef,) |
941 |
| - ): |
942 |
| - if isinstance(attr.expr, astroid.Name): |
943 |
| - if attr.expr.name == assign_name and attr.attrname == "format": |
944 |
| - return True |
945 |
| - return False |
946 |
| - |
947 |
| - if node.pytype() == "builtins.str" and not isinstance( |
948 |
| - node.parent, astroid.JoinedStr |
949 |
| - ): |
950 |
| - # Find all pairs of '{}' within a string |
951 |
| - inner_matches = re.findall(r"(?<=\{).*?(?=\})", node.value) |
952 |
| - if len(inner_matches) != len(set(inner_matches)): |
953 |
| - return |
954 |
| - if inner_matches: |
955 |
| - for match in inner_matches: |
956 |
| - # Check if match is a local or global variable |
957 |
| - if not ( |
958 |
| - node.scope().locals.get(match) or node.root().locals.get(match) |
959 |
| - ): |
960 |
| - return |
961 |
| - assign_node = node |
962 |
| - while not isinstance(assign_node, astroid.Assign): |
963 |
| - assign_node = assign_node.parent |
964 |
| - if isinstance(assign_node.value, astroid.Tuple): |
965 |
| - node_index = assign_node.value.elts.index(node) |
966 |
| - assign_name = assign_node.targets[0].elts[node_index].name |
967 |
| - else: |
968 |
| - assign_name = assign_node.targets[0].name |
969 |
| - if not detect_if_used_in_format(node, assign_name): |
970 |
| - self.add_message( |
971 |
| - "possible-forgotten-f-prefix", |
972 |
| - line=node.lineno, |
973 |
| - node=node, |
974 |
| - args=(f"{{{match}}}",), |
975 |
| - ) |
976 |
| - else: |
977 |
| - return |
| 935 | + # Find all pairs of '{}' within a string |
| 936 | + inner_matches = re.findall(r"(?<=\{).*?(?=\})", node.value) |
| 937 | + if len(inner_matches) != len(set(inner_matches)): |
| 938 | + return |
| 939 | + if inner_matches: |
| 940 | + for match in inner_matches: |
| 941 | + try: |
| 942 | + ast.parse(match, "<fstring>", "eval") |
| 943 | + except SyntaxError: |
| 944 | + # Not valid python |
| 945 | + continue |
| 946 | + # if not isinstance(parsed_match, ast.Expression): |
| 947 | + # # Not a proper expression, won't work in f-string |
| 948 | + # continue |
| 949 | + # for ast_node in ast.walk(parsed_match): |
| 950 | + # if isinstance(ast_node, ast.Name): |
| 951 | + # print( |
| 952 | + # f"TODO check that the name {ast_node.id} exists in the scope ?" |
| 953 | + # ) |
| 954 | + self.add_message( |
| 955 | + "possible-forgotten-f-prefix", |
| 956 | + line=node.lineno, |
| 957 | + node=node, |
| 958 | + args=(f"'{{{match}}}'",), |
| 959 | + ) |
978 | 960 |
|
979 | 961 | def _detect_u_string_prefix(self, node: astroid.Const):
|
980 | 962 | """Check whether strings include a 'u' prefix like u'String'"""
|
|
0 commit comments