Thu, 30 Nov 2023 11:59:40 +0100
Added checks for implicit string concatenation (based on flake8-implicit-str-concat 0.4.0) to the Miscellaneous checker.
--- a/docs/ThirdParty.md Wed Nov 29 18:07:53 2023 +0100 +++ b/docs/ThirdParty.md Thu Nov 30 11:59:40 2023 +0100 @@ -26,6 +26,7 @@ | flake8-bugbear | 23.11.26 | MIT License (MIT) | | flake8-comprehensions | 3.14.0 | MIT License (MIT) | | flake8-future-annotations | 1.1.0 | MIT License (MIT) | +| flake8-implicit-str-concat | 0.4.0 | MIT License (MIT) | | flake8-local-import | 1.0.6 | MIT License (MIT) | | flake8-pep585 | 0.1.7 | Mozilla Public License Version 2.0 | | flake8-pep604 | 1.1.0 | MIT License (MIT) |
--- a/eric7.epj Wed Nov 29 18:07:53 2023 +0100 +++ b/eric7.epj Thu Nov 30 11:59:40 2023 +0100 @@ -68,7 +68,7 @@ "DocstringType": "eric_black", "EnabledCheckerCategories": "C, D, E, I, M, NO, N, Y, U, W", "ExcludeFiles": "*/ThirdParty/*, */coverage/*, */Ui_*.py, */Examples/*, */pycodestyle.py,*/pyflakes/checker.py,*/mccabe.py,*/eradicate.py,*/ast_unparse.py,*/piplicenses.py,*/pipdeptree.py,*/MCUScripts/*,*/MicroPython/Tools/*", - "ExcludeMessages": "C101,E203,E265,E266,E305,E402,M201,M701,M702,M811,M834,N802,N803,N807,N808,N821,W293,W503,Y119,Y401,Y402", + "ExcludeMessages": "C101,E203,E265,E266,E305,E402,M201,M701,M702,M811,M834,M852,N802,N803,N807,N808,N821,W293,W503,Y119,Y401,Y402", "FixCodes": "", "FixIssues": false, "FutureChecker": "",
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py Wed Nov 29 18:07:53 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py Thu Nov 30 11:59:40 2023 +0100 @@ -13,12 +13,26 @@ import copy import itertools import re +import sys import tokenize from collections import defaultdict, namedtuple from keyword import iskeyword from string import Formatter +try: + # Python 3.10+ + from itertools import pairwise +except ImportError: + # replacement for Python < 3.10 + from itertools import tee + + def pairwise(iterable): + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + import AstUtilities from .eradicate import Eradicator @@ -64,15 +78,24 @@ "M184", "M185", "M186", - "M187", "M188", "M189", + "M189a", + "M189b", "M190", + "M190a", + "M190b", "M191", - "M192", "M193", + "M193a", + "M193b", + "M193c", "M194", "M195", + "M196", + "M197", + "M198", + "M199", ## Dictionaries with sorted keys "M201", ## Property @@ -184,6 +207,10 @@ "M834", ## line continuation "M841", + ## implicitly concatenated strings + "M851", + "M852", + "M853", ## commented code "M891", ] @@ -227,6 +254,9 @@ self.__tree = copy.deepcopy(tree) self.__args = args + linesIterator = iter(self.__source) + self.__tokens = list(tokenize.generate_tokens(lambda: next(linesIterator))) + self.__pep3101FormatRegex = re.compile( r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%' ) @@ -255,15 +285,24 @@ "M184", "M185", "M186", - "M187", "M188", "M189", + "M189a", + "M189b", "M190", + "M190a", + "M190b", "M191", - "M192", "M193", + "M193a", + "M193b", + "M193c", "M194", "M195", + "M196", + "M197", + "M198", + "M199", ), ), (self.__checkDictWithSortedKeys, ("M201",)), @@ -369,6 +408,8 @@ (self.__checkMutableDefault, ("M821", "M822")), (self.__checkReturn, ("M831", "M832", "M833", "M834")), (self.__checkLineContinuation, ("M841",)), + (self.__checkImplicitStringConcat, ("M851", "M852")), + (self.__checkExplicitStringConcat, ("M853",)), (self.__checkCommentedCode, ("M891",)), ] @@ -548,9 +589,7 @@ Private method to check line continuation using backslash. """ # generate source lines without comments - linesIterator = iter(self.__source) - tokens = tokenize.generate_tokens(lambda: next(linesIterator)) - comments = [token for token in tokens if token[0] == tokenize.COMMENT] + comments = [tok for tok in self.__tokens if tok[0] == tokenize.COMMENT] stripped = self.__source[:] for comment in comments: lineno = comment[3][0] @@ -908,7 +947,6 @@ "tuple": "M189a", "list": "M190a", }[node.func.id] - ##suffix = "remove the outer call to {func}()." self.__error( node.lineno - 1, node.col_offset, @@ -948,7 +986,6 @@ ) ) ): - ##suffix = "rewrite as a {func} literal." errorCode = { "tuple": "M189b", "list": "M190b", @@ -1010,12 +1047,6 @@ node.args[0].func.id, not reverseFlagValue, ) - ##if reverse_flag_value is None: - ##remediation = " - toggle reverse argument to sorted()" - ##else: - ##remediation = " - use sorted(..., reverse={!r})".format( - ##not reverse_flag_value - ##) else: self.__error( @@ -1071,7 +1102,9 @@ and len(node.args) == 2 and isinstance(node.args[0], ast.Lambda) ): - self.__error(node.lineno - 1, node.col_offset, "M197", "generator expression") + self.__error( + node.lineno - 1, node.col_offset, "M197", "generator expression" + ) elif ( node.func.id in ("list", "set", "dict") @@ -1099,7 +1132,9 @@ if rewriteable: comprehensionType = f"{node.func.id} comprehension" - self.__error(node.lineno - 1, node.col_offset, "M197", comprehensionType) + self.__error( + node.lineno - 1, node.col_offset, "M197", comprehensionType + ) elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)) and ( len(node.generators) == 1 @@ -1401,6 +1436,94 @@ if propertyCount > 1: self.__error(node.lineno - 1, node.col_offset, "M217", node.name) + ####################################################################### + ## The following method check for implicitly concatenated strings + ## + ## These methods are adapted from: flake8-implicit-str-concat v0.4.0 + ## Original: Copyright (c) 2023 Dylan Turner + ####################################################################### + + if sys.version_info < (3, 12): + def __isImplicitStringConcat(self, first, second): + """ + Private method to check, if the given strings indicate an implicit string + concatenation. + + @param first first token + @type tuple + @param second second token + @type tuple + @return flag indicating an implicit string concatenation + """ + return first.type == second.type == tokenize.STRING + + else: + + def __isImplicitStringConcat(self, first, second): + """ + Private method to check, if the given strings indicate an implicit string + concatenation. + + @param first first token + @type tuple + @param second second token + @type tuple + @return flag indicating an implicit string concatenation + """ + return ( + (first.type == second.type == tokenize.STRING) + or ( + first.type == tokenize.STRING + and second.type == tokenize.FSTRING_START + ) + or ( + first.type == tokenize.FSTRING_END + and second.type == tokenize.STRING + ) + or ( + first.type == tokenize.FSTRING_END + and second.type == tokenize.FSTRING_START + ) + ) + + def __checkImplicitStringConcat(self): + """ + Private method to check for implicitly concatenated strings. + """ + tokensWithoutWhitespace = ( + tok + for tok in self.__tokens + if tok.type + not in ( + tokenize.NL, + tokenize.NEWLINE, + tokenize.INDENT, + tokenize.DEDENT, + tokenize.COMMENT, + ) + ) + for a, b in pairwise(tokensWithoutWhitespace): + if self.__isImplicitStringConcat(a, b): + self.__error( + a.end[0] - 1, a.end[1], "M851" if a.end[0] == b.start[0] else "M852" + ) + + def __checkExplicitStringConcat(self): + """ + Private method to check for explicitly concatenated strings. + """ + for node in ast.walk(self.__tree): + if ( + isinstance(node, ast.BinOp) + and isinstance(node.op, ast.Add) + and all( + AstUtilities.isBaseString(operand) + or isinstance(operand, ast.JoinedStr) + for operand in (node.left, node.right) + ) + ): + self.__error(node.lineno - 1, node.col_offset, "M853") + class TextVisitor(ast.NodeVisitor): """
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/translations.py Wed Nov 29 18:07:53 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/translations.py Thu Nov 30 11:59:40 2023 +0100 @@ -132,10 +132,6 @@ "Unnecessary list comprehension passed to {0}() prevents short-circuiting" " - rewrite as a generator" ), - - - - ## Dictionaries with sorted keys "M201": QCoreApplication.translate( "MiscellaneousChecker", @@ -599,6 +595,19 @@ "prefer implied line continuation inside parentheses, " "brackets and braces as opposed to a backslash", ), + ## implicitly concatenated strings + "M851": QCoreApplication.translate( + "MiscellaneousChecker", + "implicitly concatenated string or bytes literals on one line", + ), + "M852": QCoreApplication.translate( + "MiscellaneousChecker", + "implicitly concatenated string or bytes literals over continuation line", + ), + "M853": QCoreApplication.translate( + "MiscellaneousChecker", + "explicitly concatenated string or bytes should be implicitly concatenated", + ), ## commented code "M891": QCoreApplication.translate( "MiscellaneousChecker", @@ -607,9 +616,12 @@ } _miscellaneousMessagesSampleArgs = { + ## Coding line "M102": ["enc42"], + ## Shadowed Builtins "M131": ["list"], "M132": ["list"], + ## Comprehensions "M185": ["list", "set"], "M186": ["list", "dict"], "M188": ["list"], @@ -626,7 +638,9 @@ "M197": ["list"], "M198": ["dict comprehension"], "M199": ["any"], + ## Dictionaries with sorted keys "M201": ["bar", "foo"], + ## Property "M210": [2], "M211": [1], "M212": [2], @@ -635,6 +649,7 @@ "M215": ["foo", "bar"], "M216": ["foo", "bar"], "M217": ["foo"], + ## Bugbear "M507": ["x"], "M513": ["Exception"], "M514": ["OSError, IOError", " as err", "OSError"], @@ -645,14 +660,18 @@ "M533": ["foo"], "M534": ["split", "maxsplit"], "M535": ["foo"], + ## Format Strings "M601": ["%s"], "M621": [5], "M622": ["foo"], "M631": [5], "M632": ["foo"], + ## Future statements "M701": ["print_function, unicode_literals", "print_function"], "M702": ["print_function, unicode_literals"], + ## Gettext "M711": ["lgettext"], + ## Mutable Defaults "M821": ["Dict"], "M822": ["Call"], "M823": ["dict"],