Skip to content
Snippets Groups Projects
Commit 5d31e7ee authored by Adi Roiban's avatar Adi Roiban Committed by Ian Stapleton Cordasco
Browse files

Add variables so blank lines may be configures

This adds some module level configuration points for users to define
how many blank lines they want in their code. It paves the way for
someone to develop a flake8 plugin to configure this in pycodestyle.

Fixes #732
parent 9f225ac8
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -66,6 +66,44 @@ At this point you can create a pull request back to the official pycodestyles
repository for review! For more information on how to make a pull request,
GitHub has an excellent `guide`_.
 
The current tests are written in 2 styles:
* standard xUnit based only on stdlib unittest
(can be executed with nose)
* functional test using a custom framework and executed by the
pycodestyle itself when installed in dev mode.
Running unittest
~~~~~~~~~~~~~~~~
While the tests are writted using stdlib `unittest` module, the existing
test include unit, integration and functional tests.
There are a couple of ways to run the tests::
$ python setup.py test
$ # Use nose to run specific test
$ nosetests \
> testsuite.test_blank_lines:TestBlankLinesDefault.test_initial_no_blank
$ # Use nose to run a subset and check coverage, and check the resulting
$ $ cover/pycodestyle_py.html in your browser
$ nosetests --with-coverage --cover-html -s testsuite.test_blank_lines
Running functional
~~~~~~~~~~~~~~~~~~
When installed in dev mode, pycodestyle will have the `--testsuite`
option which can be used to run the tests::
$ pip install -e .
$ # Run all tests.
$ pycodestyle --testsuite testsuite
$ # Run a subset of the tests.
$ pycodestyle --testsuite testsuite/E30.py
.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
.. _guide: https://guides.github.com/activities/forking/
.. _tox: https://tox.readthedocs.io/en/latest/
Loading
Loading
Loading
Loading
@@ -95,6 +95,13 @@ except ImportError:
PROJECT_CONFIG = ('setup.cfg', 'tox.ini')
TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite')
MAX_LINE_LENGTH = 79
# Number of blank lines between various code parts.
BLANK_LINES_CONFIG = {
# Top level class and function.
'top_level': 2,
# Methods and nested class and function.
'method': 1,
}
REPORT_FORMAT = {
'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s',
'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s',
Loading
Loading
@@ -332,37 +339,50 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number,
E305: def a():\n pass\na()
E306: def a():\n def b():\n pass\n def c():\n pass
"""
if line_number < 3 and not previous_logical:
top_level_lines = BLANK_LINES_CONFIG['top_level']
method_lines = BLANK_LINES_CONFIG['method']
if line_number < top_level_lines + 1 and not previous_logical:
return # Don't expect blank lines before the first line
if previous_logical.startswith('@'):
if blank_lines:
yield 0, "E304 blank lines found after function decorator"
elif blank_lines > 2 or (indent_level and blank_lines == 2):
elif (blank_lines > top_level_lines or
(indent_level and blank_lines == method_lines + 1)
):
yield 0, "E303 too many blank lines (%d)" % blank_lines
elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line):
if indent_level:
if not (blank_before or previous_indent_level < indent_level or
DOCSTRING_REGEX.match(previous_logical)):
if not (blank_before == method_lines or
previous_indent_level < indent_level or
DOCSTRING_REGEX.match(previous_logical)
):
ancestor_level = indent_level
nested = False
# Search backwards for a def ancestor or tree root (top level).
for line in lines[line_number - 2::-1]:
for line in lines[line_number - top_level_lines::-1]:
if line.strip() and expand_indent(line) < ancestor_level:
ancestor_level = expand_indent(line)
nested = line.lstrip().startswith('def ')
if nested or ancestor_level == 0:
break
if nested:
yield 0, "E306 expected 1 blank line before a " \
"nested definition, found 0"
yield 0, "E306 expected %s blank line before a " \
"nested definition, found 0" % (method_lines,)
else:
yield 0, "E301 expected 1 blank line, found 0"
elif blank_before != 2:
yield 0, "E302 expected 2 blank lines, found %d" % blank_before
elif (logical_line and not indent_level and blank_before != 2 and
previous_unindented_logical_line.startswith(('def ', 'class '))):
yield 0, "E305 expected 2 blank lines after " \
"class or function definition, found %d" % blank_before
yield 0, "E301 expected %s blank line, found 0" % (
method_lines,)
elif blank_before != top_level_lines:
yield 0, "E302 expected %s blank lines, found %d" % (
top_level_lines, blank_before)
elif (logical_line and
not indent_level and
blank_before != top_level_lines and
previous_unindented_logical_line.startswith(('def ', 'class '))
):
yield 0, "E305 expected %s blank lines after " \
"class or function definition, found %d" % (
top_level_lines, blank_before)
 
 
@register_check
Loading
Loading
Loading
Loading
@@ -83,6 +83,26 @@ class TestReport(StandardReport):
print("Test failed." if self.total_errors else "Test passed.")
 
 
class InMemoryReport(BaseReport):
"""
Collect the results in memory, without printing anything.
"""
def __init__(self, options):
super(InMemoryReport, self).__init__(options)
self.in_memory_errors = []
def error(self, line_number, offset, text, check):
"""
Report an error, according to options.
"""
code = text[:4]
self.in_memory_errors.append('%s:%s:%s' % (
code, line_number, offset + 1))
return super(InMemoryReport, self).error(
line_number, offset, text, check)
def selftest(options):
"""
Test all check functions with test cases in docstrings.
Loading
Loading
Loading
Loading
@@ -48,11 +48,19 @@ class PycodestyleTestCase(unittest.TestCase):
 
 
def suite():
from testsuite import test_api, test_parser, test_shell, test_util
from testsuite import (
test_api,
test_blank_lines,
test_parser,
test_shell,
test_util,
)
 
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(PycodestyleTestCase))
suite.addTest(unittest.makeSuite(test_api.APITestCase))
suite.addTest(unittest.makeSuite(test_blank_lines.TestBlankLinesDefault))
suite.addTest(unittest.makeSuite(test_blank_lines.TestBlankLinesTwisted))
suite.addTest(unittest.makeSuite(test_parser.ParserTestCase))
suite.addTest(unittest.makeSuite(test_shell.ShellTestCase))
suite.addTest(unittest.makeSuite(test_util.UtilTestCase))
Loading
Loading
"""
Tests for the blank_lines checker.
It uses dedicated assertions which work with TestReport.
"""
import unittest
import pycodestyle
from testsuite.support import InMemoryReport
class BlankLinesTestCase(unittest.TestCase):
"""
Common code for running blank_lines tests.
"""
def check(self, content):
"""
Run checks on `content` and return the the list of errors.
"""
sut = pycodestyle.StyleGuide()
reporter = sut.init_report(InMemoryReport)
sut.input_file(
filename='in-memory-test-file.py',
lines=content.splitlines(True),
)
return reporter.in_memory_errors
def assertNoErrors(self, actual):
"""
Check that the actual result from the checker has no errors.
"""
self.assertEqual([], actual)
class TestBlankLinesDefault(BlankLinesTestCase):
"""
Tests for default blank with 2 blank lines for top level and 1 blank line
for methods.
"""
def test_initial_no_blank(self):
"""
It will accept no blank lines at the start of the file.
"""
result = self.check("""def some_function():
pass
""")
self.assertNoErrors(result)
def test_initial_lines_one_blank(self):
"""
It will accept 1 blank lines before the first line of actual code,
even if in other places it asks for 2
"""
result = self.check("""
def some_function():
pass
""")
self.assertNoErrors(result)
def test_initial_lines_two_blanks(self):
"""
It will accept 2 blank lines before the first line of actual code,
as normal.
"""
result = self.check("""
def some_function():
pass
""")
self.assertNoErrors(result)
def test_method_less_blank_lines(self):
"""
It will trigger an error when less than 1 blank lin is found before
method definitions.
"""
result = self.check("""# First comment line.
class X:
def a():
pass
def b():
pass
""")
self.assertEqual([
'E301:6:5', # b() call
], result)
def test_method_less_blank_lines_comment(self):
"""
It will trigger an error when less than 1 blank lin is found before
method definition, ignoring comments.
"""
result = self.check("""# First comment line.
class X:
def a():
pass
# A comment will not make it better.
def b():
pass
""")
self.assertEqual([
'E301:7:5', # b() call
], result)
def test_top_level_fewer_blank_lines(self):
"""
It will trigger an error when less 2 blank lines are found before top
level definitions.
"""
result = self.check("""# First comment line.
# Second line of comment.
def some_function():
pass
async def another_function():
pass
def this_one_is_good():
pass
class SomeCloseClass(object):
pass
async def this_async_is_good():
pass
class AFarEnoughClass(object):
pass
""")
self.assertEqual([
'E302:4:1', # some_function
'E302:7:1', # another_function
'E302:14:1', # SomeCloseClass
], result)
def test_top_level_more_blank_lines(self):
"""
It will trigger an error when more 2 blank lines are found before top
level definitions.
"""
result = self.check("""# First comment line.
# Second line of comment.
def some_function():
pass
def this_one_is_good():
pass
class SomeFarClass(object):
pass
class AFarEnoughClass(object):
pass
""")
self.assertEqual([
'E303:6:1', # some_function
'E303:15:1', # SomeFarClass
], result)
def test_method_more_blank_lines(self):
"""
It will trigger an error when more than 1 blank line is found before
method definition
"""
result = self.check("""# First comment line.
class SomeCloseClass(object):
def oneMethod(self):
pass
def anotherMethod(self):
pass
def methodOK(self):
pass
def veryFar(self):
pass
""")
self.assertEqual([
'E303:7:5', # oneMethod
'E303:11:5', # anotherMethod
'E303:19:5', # veryFar
], result)
def test_initial_lines_more_blank(self):
"""
It will trigger an error for more than 2 blank lines before the first
line of actual code.
"""
result = self.check("""
def some_function():
pass
""")
self.assertEqual(['E303:4:1'], result)
def test_blank_line_between_decorator(self):
"""
It will trigger an error when the decorator is followed by a blank
line.
"""
result = self.check("""# First line.
@some_decorator
def some_function():
pass
class SomeClass(object):
@method_decorator
def some_method(self):
pass
""")
self.assertEqual(['E304:6:1', 'E304:14:5'], result)
def test_blank_line_decorator(self):
"""
It will accept the decorators which are adjacent to the function and
method definition.
"""
result = self.check("""# First line.
@another_decorator
@some_decorator
def some_function():
pass
class SomeClass(object):
@method_decorator
def some_method(self):
pass
""")
self.assertNoErrors(result)
def test_top_level_fewer_follow_lines(self):
"""
It will trigger an error when less than 2 blank lines are
found between a top level definitions and other top level code.
"""
result = self.check("""
def a():
print('Something')
a()
""")
self.assertEqual([
'E305:5:1', # a call
], result)
def test_top_level_fewer_follow_lines_comments(self):
"""
It will trigger an error when less than 2 blank lines are
found between a top level definitions and other top level code,
even if we have comments before
"""
result = self.check("""
def a():
print('Something')
# comment
# another comment
# With comment still needs 2 spaces before,
# as comments are ignored.
a()
""")
self.assertEqual([
'E305:11:1', # a call
], result)
def test_top_level_good_follow_lines(self):
"""
It not trigger an error when 2 blank lines are
found between a top level definitions and other top level code.
"""
result = self.check("""
def a():
print('Something')
# Some comments in other parts.
# More comments.
# With the right spaces,
# It will work, even when we have comments.
a()
""")
self.assertNoErrors(result)
def test_method_fewer_follow_lines(self):
"""
It will trigger an error when less than 1 blank line is
found between a method and previous definitions.
"""
result = self.check("""
def a():
x = 1
def b():
pass
""")
self.assertEqual([
'E306:4:5', # b() call
], result)
def test_method_nested_fewer_follow_lines(self):
"""
It will trigger an error when less than 1 blank line is
found between a method and previous definitions, even when nested.
"""
result = self.check("""
def a():
x = 2
def b():
x = 1
def c():
pass
""")
self.assertEqual([
'E306:7:9', # c() call
], result)
def test_method_nested_less_class(self):
"""
It will trigger an error when less than 1 blank line is found
between a method and previous definitions, even when used to
define a class.
"""
result = self.check("""
def a():
x = 1
class C:
pass
""")
self.assertEqual([
'E306:4:5', # class C definition.
], result)
def test_method_nested_ok(self):
"""
Will not trigger an error when 1 blank line is found
found between a method and previous definitions, even when nested.
"""
result = self.check("""
def a():
x = 2
def b():
x = 1
def c():
pass
class C:
pass
""")
self.assertNoErrors(result)
class TestBlankLinesTwisted(BlankLinesTestCase):
"""
Tests for blank_lines with 3 blank lines for top level and 2 blank line
for methods as used by the Twisted coding style.
"""
def setUp(self):
self._original_lines_config = pycodestyle.BLANK_LINES_CONFIG.copy()
pycodestyle.BLANK_LINES_CONFIG['top_level'] = 3
pycodestyle.BLANK_LINES_CONFIG['method'] = 2
def tearDown(self):
pycodestyle.BLANK_LINES_CONFIG = self._original_lines_config
def test_initial_lines_one_blanks(self):
"""
It will accept less than 3 blank lines before the first line of actual
code.
"""
result = self.check("""
def some_function():
pass
""")
self.assertNoErrors(result)
def test_initial_lines_tree_blanks(self):
"""
It will accept 3 blank lines before the first line of actual code,
as normal.
"""
result = self.check("""
def some_function():
pass
""")
self.assertNoErrors(result)
def test_top_level_fewer_blank_lines(self):
"""
It will trigger an error when less 2 blank lines are found before top
level definitions.
"""
result = self.check("""# First comment line.
# Second line of comment.
def some_function():
pass
async def another_function():
pass
def this_one_is_good():
pass
class SomeCloseClass(object):
pass
async def this_async_is_good():
pass
class AFarEnoughClass(object):
pass
""")
self.assertEqual([
'E302:5:1', # some_function
'E302:9:1', # another_function
'E302:17:1', # SomeCloseClass
], result)
def test_top_level_more_blank_lines(self):
"""
It will trigger an error when more 2 blank lines are found before top
level definitions.
"""
result = self.check("""# First comment line.
# Second line of comment.
def some_function():
pass
def this_one_is_good():
pass
class SomeVeryFarClass(object):
pass
class AFarEnoughClass(object):
pass
""")
self.assertEqual([
'E303:7:1', # some_function
'E303:18:1', # SomeVeryFarClass
], result)
def test_the_right_blanks(self):
"""
It will accept 3 blank for top level and 2 for nested.
"""
result = self.check("""
def some_function():
pass
# With comments.
some_other = code_here
class SomeClass:
'''
Docstring here.
'''
def some_method():
pass
def another_method():
pass
# More methods.
def another_method_with_comment():
pass
@decorator
def another_method_with_comment():
pass
""")
self.assertNoErrors(result)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment