summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--CHANGES.txt7
-rw-r--r--MANIFEST.in1
-rw-r--r--docs/intro.rst4
-rwxr-xr-xpycodestyle.py67
-rw-r--r--setup.cfg5
-rw-r--r--testsuite/E12not.py8
-rw-r--r--testsuite/W60.py16
-rw-r--r--testsuite/python3.py21
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):
diff --git a/setup.cfg b/setup.cfg
index 803bc10..91ea674 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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] = []