Skip to content

Commit 1ee296b

Browse files
committed
Report E402 for imports not on top of file; issue #264. Also add ability to keep local state for each checker plugin. #ep14boat
1 parent 4c5bf00 commit 1ee296b

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Changelog
1616
* Report E266 instead of E265 when the block comment starts with
1717
multiple ``#``. (Issue #270)
1818

19+
* Report E402 for import statements not at the top of the file. (Issue #264)
20+
1921

2022
1.5.7 (2014-05-29)
2123
------------------

docs/developer.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ additional information with extra arguments. All attributes of the
5959
* ``previous_indent_level``: indentation on previous line
6060
* ``previous_logical``: previous logical line
6161

62+
Check plugins can also maintain per-file state. If you need this, declare
63+
a parameter named ``checker_state``. You will be passed a dict, which will be
64+
the same one for all lines in the same file but a different one for different
65+
files. Each check plugin gets its own dict, so you don't need to worry about
66+
clobbering the state of other plugins.
67+
6268
The docstring of each check function shall be the relevant part of
6369
text from `PEP 8`_. It is printed if the user enables ``--show-pep8``.
6470
Several docstrings contain examples directly from the `PEP 8`_ document.

pep8.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,52 @@ def imports_on_separate_lines(logical_line):
835835
yield found, "E401 multiple imports on one line"
836836

837837

838+
def imports_on_top_of_file(logical_line, indent_level, checker_state, noqa):
839+
r"""Imports are always put at the top of the file, just after any module
840+
comments and docstrings, and before module globals and constants.
841+
842+
Okay: import os
843+
Okay: # this is a comment\nimport os
844+
Okay: '''this is a module docstring'''\nimport os
845+
Okay: r'''this is a module docstring'''\nimport os
846+
Okay: __version__ = "123"\nimport os
847+
E402: a=1\nimport os
848+
E402: 'One string'\n"Two string"\nimport os
849+
E402: a=1\nfrom sys import x
850+
851+
Okay: if x:\n import os
852+
"""
853+
def is_string_literal(line):
854+
if line[0] in 'uUbB':
855+
line = line[1:]
856+
if line and line[0] in 'rR':
857+
line = line[1:]
858+
return line and (line[0] == '"' or line[0] == "'")
859+
860+
if indent_level: # Allow imports in conditional statements or functions
861+
return
862+
if not logical_line: # Allow empty lines or comments
863+
return
864+
if noqa:
865+
return
866+
line = logical_line
867+
if line.startswith('import ') or line.startswith('from '):
868+
if checker_state.get('seen_non_imports', False):
869+
yield 0, "E402 import not at top of file"
870+
elif line.startswith('__version__ '):
871+
# These lines should be included after the module's docstring, before
872+
# any other code, separated by a blank line above and below.
873+
return
874+
elif is_string_literal(line):
875+
# The first literal is a docstring, allow it. Otherwise, report error.
876+
if checker_state.get('seen_docstring', False):
877+
checker_state['seen_non_imports'] = True
878+
else:
879+
checker_state['seen_docstring'] = True
880+
else:
881+
checker_state['seen_non_imports'] = True
882+
883+
838884
def compound_statements(logical_line):
839885
r"""Compound statements (on the same line) are generally discouraged.
840886
@@ -1239,6 +1285,8 @@ def __init__(self, filename=None, lines=None,
12391285
self.hang_closing = options.hang_closing
12401286
self.verbose = options.verbose
12411287
self.filename = filename
1288+
# Dictionary where a checker can store its custom state.
1289+
self._checker_states = {}
12421290
if filename is None:
12431291
self.filename = 'stdin'
12441292
self.lines = lines or []
@@ -1294,10 +1342,16 @@ def run_check(self, check, argument_names):
12941342
arguments.append(getattr(self, name))
12951343
return check(*arguments)
12961344

1345+
def init_checker_state(self, name, argument_names):
1346+
""" Prepares a custom state for the specific checker plugin."""
1347+
if 'checker_state' in argument_names:
1348+
self.checker_state = self._checker_states.setdefault(name, {})
1349+
12971350
def check_physical(self, line):
12981351
"""Run all physical checks on a raw input line."""
12991352
self.physical_line = line
13001353
for name, check, argument_names in self._physical_checks:
1354+
self.init_checker_state(name, argument_names)
13011355
result = self.run_check(check, argument_names)
13021356
if result is not None:
13031357
(offset, text) = result
@@ -1352,6 +1406,7 @@ def check_logical(self):
13521406
for name, check, argument_names in self._logical_checks:
13531407
if self.verbose >= 4:
13541408
print(' ' + name)
1409+
self.init_checker_state(name, argument_names)
13551410
for offset, text in self.run_check(check, argument_names) or ():
13561411
if not isinstance(offset, tuple):
13571412
for token_offset, pos in mapping:

testsuite/E12not.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ def other_example():
632632
else 0,
633633
}
634634
#
635-
from textwrap import dedent
635+
from textwrap import dedent # noqa
636636

637637

638638
print dedent(

0 commit comments

Comments
 (0)