src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py

branch
eric7
changeset 10361
e6ff9a4f6ee5
parent 10360
9ffdb1490bd2
child 10362
cfa7034cccf6
equal deleted inserted replaced
10360:9ffdb1490bd2 10361:e6ff9a4f6ee5
11 import builtins 11 import builtins
12 import contextlib 12 import contextlib
13 import copy 13 import copy
14 import itertools 14 import itertools
15 import re 15 import re
16 import sys
16 import tokenize 17 import tokenize
17 18
18 from collections import defaultdict, namedtuple 19 from collections import defaultdict, namedtuple
19 from keyword import iskeyword 20 from keyword import iskeyword
20 from string import Formatter 21 from string import Formatter
22
23 try:
24 # Python 3.10+
25 from itertools import pairwise
26 except ImportError:
27 # replacement for Python < 3.10
28 from itertools import tee
29
30 def pairwise(iterable):
31 a, b = tee(iterable)
32 next(b, None)
33 return zip(a, b)
34
21 35
22 import AstUtilities 36 import AstUtilities
23 37
24 from .eradicate import Eradicator 38 from .eradicate import Eradicator
25 from .MiscellaneousDefaults import MiscellaneousCheckerDefaultArgs 39 from .MiscellaneousDefaults import MiscellaneousCheckerDefaultArgs
62 "M182", 76 "M182",
63 "M183", 77 "M183",
64 "M184", 78 "M184",
65 "M185", 79 "M185",
66 "M186", 80 "M186",
67 "M187",
68 "M188", 81 "M188",
69 "M189", 82 "M189",
83 "M189a",
84 "M189b",
70 "M190", 85 "M190",
86 "M190a",
87 "M190b",
71 "M191", 88 "M191",
72 "M192",
73 "M193", 89 "M193",
90 "M193a",
91 "M193b",
92 "M193c",
74 "M194", 93 "M194",
75 "M195", 94 "M195",
95 "M196",
96 "M197",
97 "M198",
98 "M199",
76 ## Dictionaries with sorted keys 99 ## Dictionaries with sorted keys
77 "M201", 100 "M201",
78 ## Property 101 ## Property
79 "M210", 102 "M210",
80 "M211", 103 "M211",
182 "M832", 205 "M832",
183 "M833", 206 "M833",
184 "M834", 207 "M834",
185 ## line continuation 208 ## line continuation
186 "M841", 209 "M841",
210 ## implicitly concatenated strings
211 "M851",
212 "M852",
213 "M853",
187 ## commented code 214 ## commented code
188 "M891", 215 "M891",
189 ] 216 ]
190 217
191 Formatter = Formatter() 218 Formatter = Formatter()
225 self.__filename = filename 252 self.__filename = filename
226 self.__source = source[:] 253 self.__source = source[:]
227 self.__tree = copy.deepcopy(tree) 254 self.__tree = copy.deepcopy(tree)
228 self.__args = args 255 self.__args = args
229 256
257 linesIterator = iter(self.__source)
258 self.__tokens = list(tokenize.generate_tokens(lambda: next(linesIterator)))
259
230 self.__pep3101FormatRegex = re.compile( 260 self.__pep3101FormatRegex = re.compile(
231 r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%' 261 r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%'
232 ) 262 )
233 263
234 self.__builtins = [b for b in dir(builtins) if b not in self.BuiltinsWhiteList] 264 self.__builtins = [b for b in dir(builtins) if b not in self.BuiltinsWhiteList]
253 "M182", 283 "M182",
254 "M183", 284 "M183",
255 "M184", 285 "M184",
256 "M185", 286 "M185",
257 "M186", 287 "M186",
258 "M187",
259 "M188", 288 "M188",
260 "M189", 289 "M189",
290 "M189a",
291 "M189b",
261 "M190", 292 "M190",
293 "M190a",
294 "M190b",
262 "M191", 295 "M191",
263 "M192",
264 "M193", 296 "M193",
297 "M193a",
298 "M193b",
299 "M193c",
265 "M194", 300 "M194",
266 "M195", 301 "M195",
302 "M196",
303 "M197",
304 "M198",
305 "M199",
267 ), 306 ),
268 ), 307 ),
269 (self.__checkDictWithSortedKeys, ("M201",)), 308 (self.__checkDictWithSortedKeys, ("M201",)),
270 ( 309 (
271 self.__checkProperties, 310 self.__checkProperties,
367 (self.__checkPrintStatements, ("M801",)), 406 (self.__checkPrintStatements, ("M801",)),
368 (self.__checkTuple, ("M811",)), 407 (self.__checkTuple, ("M811",)),
369 (self.__checkMutableDefault, ("M821", "M822")), 408 (self.__checkMutableDefault, ("M821", "M822")),
370 (self.__checkReturn, ("M831", "M832", "M833", "M834")), 409 (self.__checkReturn, ("M831", "M832", "M833", "M834")),
371 (self.__checkLineContinuation, ("M841",)), 410 (self.__checkLineContinuation, ("M841",)),
411 (self.__checkImplicitStringConcat, ("M851", "M852")),
412 (self.__checkExplicitStringConcat, ("M853",)),
372 (self.__checkCommentedCode, ("M891",)), 413 (self.__checkCommentedCode, ("M891",)),
373 ] 414 ]
374 415
375 # the eradicate whitelist 416 # the eradicate whitelist
376 commentedCodeCheckerArgs = self.__args.get( 417 commentedCodeCheckerArgs = self.__args.get(
546 def __checkLineContinuation(self): 587 def __checkLineContinuation(self):
547 """ 588 """
548 Private method to check line continuation using backslash. 589 Private method to check line continuation using backslash.
549 """ 590 """
550 # generate source lines without comments 591 # generate source lines without comments
551 linesIterator = iter(self.__source) 592 comments = [tok for tok in self.__tokens if tok[0] == tokenize.COMMENT]
552 tokens = tokenize.generate_tokens(lambda: next(linesIterator))
553 comments = [token for token in tokens if token[0] == tokenize.COMMENT]
554 stripped = self.__source[:] 593 stripped = self.__source[:]
555 for comment in comments: 594 for comment in comments:
556 lineno = comment[3][0] 595 lineno = comment[3][0]
557 start = comment[2][1] 596 start = comment[2][1]
558 stop = comment[3][1] 597 stop = comment[3][1]
906 ): 945 ):
907 errorCode = { 946 errorCode = {
908 "tuple": "M189a", 947 "tuple": "M189a",
909 "list": "M190a", 948 "list": "M190a",
910 }[node.func.id] 949 }[node.func.id]
911 ##suffix = "remove the outer call to {func}()."
912 self.__error( 950 self.__error(
913 node.lineno - 1, 951 node.lineno - 1,
914 node.col_offset, 952 node.col_offset,
915 errorCode, 953 errorCode,
916 type(node.args[0]).__name__.lower(), 954 type(node.args[0]).__name__.lower(),
946 for elt in node.args[0].elts 984 for elt in node.args[0].elts
947 ) 985 )
948 ) 986 )
949 ) 987 )
950 ): 988 ):
951 ##suffix = "rewrite as a {func} literal."
952 errorCode = { 989 errorCode = {
953 "tuple": "M189b", 990 "tuple": "M189b",
954 "list": "M190b", 991 "list": "M190b",
955 "set": "M185", 992 "set": "M185",
956 "dict": "M186", 993 "dict": "M186",
1008 "M193b", 1045 "M193b",
1009 node.func.id, 1046 node.func.id,
1010 node.args[0].func.id, 1047 node.args[0].func.id,
1011 not reverseFlagValue, 1048 not reverseFlagValue,
1012 ) 1049 )
1013 ##if reverse_flag_value is None:
1014 ##remediation = " - toggle reverse argument to sorted()"
1015 ##else:
1016 ##remediation = " - use sorted(..., reverse={!r})".format(
1017 ##not reverse_flag_value
1018 ##)
1019 1050
1020 else: 1051 else:
1021 self.__error( 1052 self.__error(
1022 node.lineno - 1, 1053 node.lineno - 1,
1023 node.col_offset, 1054 node.col_offset,
1069 node.func.id == "map" 1100 node.func.id == "map"
1070 and node not in visitedMapCalls 1101 and node not in visitedMapCalls
1071 and len(node.args) == 2 1102 and len(node.args) == 2
1072 and isinstance(node.args[0], ast.Lambda) 1103 and isinstance(node.args[0], ast.Lambda)
1073 ): 1104 ):
1074 self.__error(node.lineno - 1, node.col_offset, "M197", "generator expression") 1105 self.__error(
1106 node.lineno - 1, node.col_offset, "M197", "generator expression"
1107 )
1075 1108
1076 elif ( 1109 elif (
1077 node.func.id in ("list", "set", "dict") 1110 node.func.id in ("list", "set", "dict")
1078 and len(node.args) == 1 1111 and len(node.args) == 1
1079 and isinstance(node.args[0], ast.Call) 1112 and isinstance(node.args[0], ast.Call)
1097 ): 1130 ):
1098 rewriteable = False 1131 rewriteable = False
1099 1132
1100 if rewriteable: 1133 if rewriteable:
1101 comprehensionType = f"{node.func.id} comprehension" 1134 comprehensionType = f"{node.func.id} comprehension"
1102 self.__error(node.lineno - 1, node.col_offset, "M197", comprehensionType) 1135 self.__error(
1136 node.lineno - 1, node.col_offset, "M197", comprehensionType
1137 )
1103 1138
1104 elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)) and ( 1139 elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)) and (
1105 len(node.generators) == 1 1140 len(node.generators) == 1
1106 and not node.generators[0].ifs 1141 and not node.generators[0].ifs
1107 and not node.generators[0].is_async 1142 and not node.generators[0].is_async
1398 len(node.args.args), 1433 len(node.args.args),
1399 ) 1434 )
1400 1435
1401 if propertyCount > 1: 1436 if propertyCount > 1:
1402 self.__error(node.lineno - 1, node.col_offset, "M217", node.name) 1437 self.__error(node.lineno - 1, node.col_offset, "M217", node.name)
1438
1439 #######################################################################
1440 ## The following method check for implicitly concatenated strings
1441 ##
1442 ## These methods are adapted from: flake8-implicit-str-concat v0.4.0
1443 ## Original: Copyright (c) 2023 Dylan Turner
1444 #######################################################################
1445
1446 if sys.version_info < (3, 12):
1447 def __isImplicitStringConcat(self, first, second):
1448 """
1449 Private method to check, if the given strings indicate an implicit string
1450 concatenation.
1451
1452 @param first first token
1453 @type tuple
1454 @param second second token
1455 @type tuple
1456 @return flag indicating an implicit string concatenation
1457 """
1458 return first.type == second.type == tokenize.STRING
1459
1460 else:
1461
1462 def __isImplicitStringConcat(self, first, second):
1463 """
1464 Private method to check, if the given strings indicate an implicit string
1465 concatenation.
1466
1467 @param first first token
1468 @type tuple
1469 @param second second token
1470 @type tuple
1471 @return flag indicating an implicit string concatenation
1472 """
1473 return (
1474 (first.type == second.type == tokenize.STRING)
1475 or (
1476 first.type == tokenize.STRING
1477 and second.type == tokenize.FSTRING_START
1478 )
1479 or (
1480 first.type == tokenize.FSTRING_END
1481 and second.type == tokenize.STRING
1482 )
1483 or (
1484 first.type == tokenize.FSTRING_END
1485 and second.type == tokenize.FSTRING_START
1486 )
1487 )
1488
1489 def __checkImplicitStringConcat(self):
1490 """
1491 Private method to check for implicitly concatenated strings.
1492 """
1493 tokensWithoutWhitespace = (
1494 tok
1495 for tok in self.__tokens
1496 if tok.type
1497 not in (
1498 tokenize.NL,
1499 tokenize.NEWLINE,
1500 tokenize.INDENT,
1501 tokenize.DEDENT,
1502 tokenize.COMMENT,
1503 )
1504 )
1505 for a, b in pairwise(tokensWithoutWhitespace):
1506 if self.__isImplicitStringConcat(a, b):
1507 self.__error(
1508 a.end[0] - 1, a.end[1], "M851" if a.end[0] == b.start[0] else "M852"
1509 )
1510
1511 def __checkExplicitStringConcat(self):
1512 """
1513 Private method to check for explicitly concatenated strings.
1514 """
1515 for node in ast.walk(self.__tree):
1516 if (
1517 isinstance(node, ast.BinOp)
1518 and isinstance(node.op, ast.Add)
1519 and all(
1520 AstUtilities.isBaseString(operand)
1521 or isinstance(operand, ast.JoinedStr)
1522 for operand in (node.left, node.right)
1523 )
1524 ):
1525 self.__error(node.lineno - 1, node.col_offset, "M853")
1403 1526
1404 1527
1405 class TextVisitor(ast.NodeVisitor): 1528 class TextVisitor(ast.NodeVisitor):
1406 """ 1529 """
1407 Class implementing a node visitor for bytes and str instances. 1530 Class implementing a node visitor for bytes and str instances.

eric ide

mercurial