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 |
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] |
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] |
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. |