diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e505e3b..0664e26 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -10,4 +10,4 @@ the workflow documented at: http://wiki.openstack.org/GerritWorkflow -Pull requests submitted through GitHub will be ignored. \ No newline at end of file +Pull requests submitted through GitHub will be ignored. diff --git a/HACKING.rst b/HACKING.rst index 97c6879..b889622 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -1,4 +1,4 @@ doc8 Style Commandments =============================================== -Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ \ No newline at end of file +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/README.rst b/README.rst index 3df598e..8b71665 100644 --- a/README.rst +++ b/README.rst @@ -43,6 +43,7 @@ Command line usage - no trailing whitespace - D002 - no tabulation for indentation - D003 - no carriage returns (use unix newlines) - D004 + - no newline at end of file - D005 positional arguments: path path to scan for doc files (default: os.getcwd()) diff --git a/doc8/checks.py b/doc8/checks.py index fc2e911..4637e9e 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -73,6 +73,17 @@ class CheckCarriageReturn(LineCheck): yield ('D004', 'Found literal carriage return') +class CheckNewlineEndOfFile(ContentCheck): + REPORTS = frozenset(["D005"]) + + def __init__(self, cfg): + super(CheckNewlineEndOfFile, self).__init__(cfg) + + def report_iter(self, parsed_file): + if parsed_file.lines and not parsed_file.lines[-1].endswith('\n'): + yield (len(parsed_file.lines), 'D005', 'No newline at end of file') + + class CheckValidity(ContentCheck): REPORTS = frozenset(["D000"]) EXT_MATCHER = re.compile(r"(.*)[.]rst", re.I) diff --git a/doc8/main.py b/doc8/main.py index 85228d8..7de933a 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -27,6 +27,7 @@ What is checked: - no trailing whitespace - D002 - no tabulation for indentation - D003 - no carriage returns (use unix newlines) - D004 + - no newline at end of file - D005 """ import argparse @@ -131,6 +132,7 @@ def fetch_checks(cfg): checks.CheckIndentationNoTab(cfg), checks.CheckCarriageReturn(cfg), checks.CheckMaxLineLength(cfg), + checks.CheckNewlineEndOfFile(cfg), ] mgr = extension.ExtensionManager( namespace='doc8.extension.check', diff --git a/doc8/parser.py b/doc8/parser.py index 2778af0..0c1c575 100644 --- a/doc8/parser.py +++ b/doc8/parser.py @@ -96,6 +96,11 @@ class ParsedFile(object): line = line[0:-1] yield line + @property + def lines(self): + self._read() + return self._lines + @property def extension(self): return self._extension diff --git a/doc8/tests/test_checks.py b/doc8/tests/test_checks.py index 1530fc5..0a88794 100644 --- a/doc8/tests/test_checks.py +++ b/doc8/tests/test_checks.py @@ -120,3 +120,20 @@ test check = checks.CheckMaxLineLength(conf) errors = list(check.report_iter(parsed_file)) self.assertEqual(expected_errors, len(errors)) + + +class TestNewlineEndOfFile(testtools.TestCase): + def test_newline(self): + tests = [(1, "testing"), + (1, "testing\ntesting"), + (0, "testing\n"), + (0, "testing\ntesting\n")] + + for expected_errors, line in tests: + with tempfile.NamedTemporaryFile() as fh: + fh.write(line) + fh.flush() + parsed_file = parser.ParsedFile(fh.name) + check = checks.CheckNewlineEndOfFile({}) + errors = list(check.report_iter(parsed_file)) + self.assertEqual(expected_errors, len(errors))