diff options
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | CHANGES.txt | 7 | ||||
-rw-r--r-- | MANIFEST.in | 1 | ||||
-rw-r--r-- | docs/intro.rst | 4 | ||||
-rwxr-xr-x | pycodestyle.py | 67 | ||||
-rw-r--r-- | setup.cfg | 5 | ||||
-rw-r--r-- | testsuite/E12not.py | 8 | ||||
-rw-r--r-- | testsuite/W60.py | 16 | ||||
-rw-r--r-- | testsuite/python3.py | 21 |
9 files changed, 122 insertions, 8 deletions
diff --git a/.travis.yml b/.travis.yml index 3847bde..1ea5a73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +cache: pip sudo: false install: - pip install tox diff --git a/CHANGES.txt b/CHANGES.txt index 0977432..0957be8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,13 @@ Changelog ========= +UNRELEASED +---------- + +New checks: + +* Add W605 warning for invalid escape sequences in string literals + 2.3.1 (2017-01-31) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index 4532c06..fb8bc97 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include *.txt include *.rst +include LICENSE recursive-include docs * recursive-include testsuite * recursive-exclude docs *.pyc diff --git a/docs/intro.rst b/docs/intro.rst index fcdcf72..9af82da 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -196,8 +196,10 @@ Else if :envvar:`XDG_CONFIG_HOME` is not defined: Example:: [pycodestyle] + count = False ignore = E226,E302,E41 max-line-length = 160 + statistics = True At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if present. If none of these files have a ``[pycodestyle]`` section, no project @@ -413,6 +415,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | W604 | backticks are deprecated, use 'repr()' | +------------+----------------------------------------------------------------------+ +| W605 | invalid escape sequence '\x' | ++------------+----------------------------------------------------------------------+ **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, diff --git a/pycodestyle.py b/pycodestyle.py index bb96195..d31ac9e 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -58,6 +58,16 @@ import tokenize import warnings import bisect +try: + from functools import lru_cache +except ImportError: + def lru_cache(maxsize=128): # noqa as it's a fake implementation. + """Does not really need a real a lru_cache, it's just optimization, so + let's just do nothing here. Python 3.2+ will just get better + performances, time to upgrade? + """ + return lambda function: function + from fnmatch import fnmatch from optparse import OptionParser @@ -122,10 +132,10 @@ KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') -STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') +STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)\b') STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( - r'^\s*({0})'.format('|'.join(s.replace(' ', r'\s+') for s in ( + r'^\s*({0})\b'.format('|'.join(s.replace(' ', r'\s+') for s in ( 'def', 'async def', 'for', 'async for', 'if', 'elif', 'else', @@ -1378,6 +1388,57 @@ def python_3000_backticks(logical_line): yield pos, "W604 backticks are deprecated, use 'repr()'" +@register_check +def python_3000_invalid_escape_sequence(logical_line, tokens): + r"""Invalid escape sequences are deprecated in Python 3.6. + + Okay: regex = r'\.png$' + W605: regex = '\.png$' + """ + # https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals + valid = [ + '\n', + '\\', + '\'', + '"', + 'a', + 'b', + 'f', + 'n', + 'r', + 't', + 'v', + '0', '1', '2', '3', '4', '5', '6', '7', + 'x', + + # Escape sequences only recognized in string literals + 'N', + 'u', + 'U', + ] + + for token_type, text, start, end, line in tokens: + if token_type == tokenize.STRING: + quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1] + # Extract string modifiers (e.g. u or r) + quote_pos = text.index(quote) + prefix = text[:quote_pos].lower() + start = quote_pos + len(quote) + string = text[start:-len(quote)] + + if 'r' not in prefix: + pos = string.find('\\') + while pos >= 0: + pos += 1 + if string[pos] not in valid: + yield ( + pos, + "W605 invalid escape sequence '\\%s'" % + string[pos], + ) + pos = string.find('\\', pos + 1) + + ############################################################################## # Helper functions ############################################################################## @@ -1410,7 +1471,7 @@ else: """Read the value from stdin.""" return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() -noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search +noqa = lru_cache(512)(re.compile(r'# no(?:qa|pep8)\b', re.I).search) def expand_indent(line): @@ -1,6 +1,9 @@ -[wheel] +[bdist_wheel] universal = 1 +[metadata] +license_file = LICENSE + [pycodestyle] select = ignore = E226,E24 diff --git a/testsuite/E12not.py b/testsuite/E12not.py index 18c6a64..6528107 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -358,10 +358,10 @@ def qualify_by_address(self, cr, uid, ids, context=None, """ This gets called by the web server """ -_ipv4_re = re.compile('^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') +_ipv4_re = re.compile(r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$') fct(""" diff --git a/testsuite/W60.py b/testsuite/W60.py index 973d22f..cbe267d 100644 --- a/testsuite/W60.py +++ b/testsuite/W60.py @@ -13,3 +13,19 @@ if x <> 0: x = 0 #: W604 val = `1 + 2` +#: W605 +regex = '\.png$' +#: W605 +regex = ''' +\.png$ +''' +#: Okay +regex = r'\.png$' +regex = '\\.png$' +regex = r''' +\.png$ +''' +regex = r''' +\\.png$ +''' +s = '\\' diff --git a/testsuite/python3.py b/testsuite/python3.py index fde4281..be7c58f 100644 --- a/testsuite/python3.py +++ b/testsuite/python3.py @@ -12,7 +12,28 @@ CONST: int = 42 class Class: + # Camel-caes cls_var: ClassVar[str] + for_var: ClassVar[str] + while_var: ClassVar[str] + def_var: ClassVar[str] + if_var: ClassVar[str] + elif_var: ClassVar[str] + else_var: ClassVar[str] + try_var: ClassVar[str] + except_var: ClassVar[str] + finally_var: ClassVar[str] + with_var: ClassVar[str] + forVar: ClassVar[str] + whileVar: ClassVar[str] + defVar: ClassVar[str] + ifVar: ClassVar[str] + elifVar: ClassVar[str] + elseVar: ClassVar[str] + tryVar: ClassVar[str] + exceptVar: ClassVar[str] + finallyVar: ClassVar[str] + withVar: ClassVar[str] def m(self): xs: List[int] = [] |