Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ UNRELEASED
New checks:

* Add W605 warning for invalid escape sequences in string literals
* Add W606 warning for 'async' and 'await' reserved keywords being introduced
in Python 3.7

2.3.1 (2017-01-31)
------------------
Expand Down
2 changes: 2 additions & 0 deletions docs/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ This is the current list of error and warning codes:
+------------+----------------------------------------------------------------------+
| W605 | invalid escape sequence '\x' |
+------------+----------------------------------------------------------------------+
| W606 | 'async' and 'await' are reserved keywords starting with Python 3.7 |
+------------+----------------------------------------------------------------------+


**(*)** In the default configuration, the checks **E121**, **E123**, **E126**,
Expand Down
54 changes: 54 additions & 0 deletions pycodestyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,60 @@ def python_3000_invalid_escape_sequence(logical_line, tokens):
pos = string.find('\\', pos + 1)


@register_check
def python_3000_async_await_keywords(logical_line, tokens):
"""'async' and 'await' are reserved keywords starting with Python 3.7

W606: async = 42
W606: await = 42
Okay: async def read_data(db):\n data = await db.fetch('SELECT ...')
"""
# The Python tokenize library before Python 3.5 recognizes async/await as a
# NAME token. Therefore, use a state machine to look for the possible
# async/await constructs as defined by the Python grammar:
# https://docs.python.org/3/reference/grammar.html

state = None
for token_type, text, start, end, line in tokens:
error = False

if state is None:
if token_type == tokenize.NAME:
if text == 'async':
state = ('async_stmt', start)
elif text == 'await':
state = ('await', start)
elif state[0] == 'async_stmt':
if token_type == tokenize.NAME and text in ('def', 'with', 'for'):
# One of funcdef, with_stmt, or for_stmt. Return to looking
# for async/await names.
state = None
else:
error = True
elif state[0] == 'await':
if token_type in (tokenize.NAME, tokenize.NUMBER, tokenize.STRING):
# An await expression. Return to looking for async/await names.
state = None
else:
error = True

if error:
yield (
state[1],
"W606 'async' and 'await' are reserved keywords starting with "
"Python 3.7",
)
state = None

# Last token
if state is not None:
yield (
state[1],
"W606 'async' and 'await' are reserved keywords starting with "
"Python 3.7",
)


##############################################################################
# Helper functions
##############################################################################
Expand Down
45 changes: 45 additions & 0 deletions testsuite/W60.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,48 @@
\\.png$
'''
s = '\\'
#: W606
async = 42
#: W606
await = 42
#: W606
def async():
pass
#: W606
def await():
pass
#: W606
class async:
pass
#: W606
class await:
pass
#: Okay
async def read_data(db):
data = await db.fetch('SELECT ...')
#: Okay
if await fut:
pass
if (await fut):
pass
if await fut + 1:
pass
if (await fut) + 1:
pass
pair = await fut, 'spam'
pair = (await fut), 'spam'
with await fut, open():
pass
with (await fut), open():
pass
await foo()['spam'].baz()()
return await coro()
return (await coro())
res = await coro() ** 2
res = (await coro()) ** 2
func(a1=await coro(), a2=0)
func(a1=(await coro()), a2=0)
await foo() + await bar()
(await foo()) + (await bar())
-await foo()
-(await foo())