def _lint_lines(errors: List[Tuple[str, int, int, str]], fragment: ChangelogFragment, section: str, lines: Any) -> None: """ Lint lines of a changelog fragment. """ if isinstance(lines, list): for line in lines: if not isinstance(line, str): errors.append((fragment.path, 0, 0, 'section "%s" list items must be type str ' 'not %s' % (section, type(line).__name__))) continue results = rstcheck.check( line, filename=fragment.path, report_level=docutils.utils.Reporter.WARNING_LEVEL) errors += [(fragment.path, 0, 0, result[1]) for result in results] elif isinstance(lines, str): results = rstcheck.check( lines, filename=fragment.path, report_level=docutils.utils.Reporter.WARNING_LEVEL) errors += [(fragment.path, 0, 0, result[1]) for result in results]
def lint(self, fragment): """Lint a ChangelogFragment. :type fragment: ChangelogFragment :rtype: list[(str, int, int, str)] """ errors = [] for section, lines in fragment.content.items(): if section == self.config.prelude_name: if not isinstance(lines, string_types): errors.append((fragment.path, 0, 0, 'section "%s" must be type str not %s' % (section, type(lines).__name__))) else: # doesn't account for prelude but only the RM should be adding those if not isinstance(lines, list): errors.append((fragment.path, 0, 0, 'section "%s" must be type list not %s' % (section, type(lines).__name__))) if section not in self.config.sections: errors.append((fragment.path, 0, 0, 'invalid section: %s' % section)) if isinstance(lines, list): for line in lines: if not isinstance(line, string_types): errors.append((fragment.path, 0, 0, 'section "%s" list items must be type str not %s' % (section, type(line).__name__))) continue results = rstcheck.check(line, filename=fragment.path, report_level=docutils.utils.Reporter.WARNING_LEVEL) errors += [(fragment.path, 0, 0, result[1]) for result in results] elif isinstance(lines, string_types): results = rstcheck.check(lines, filename=fragment.path, report_level=docutils.utils.Reporter.WARNING_LEVEL) errors += [(fragment.path, 0, 0, result[1]) for result in results] return errors
def lint(self, fragment): """Lint a ChangelogFragment. :type fragment: ChangelogFragment :rtype: list[(str, int, int, str)] """ errors = [] for section, lines in fragment.content.items(): if section not in self.config.sections: errors.append( (fragment.path, 0, 0, 'invalid section: %s' % section)) if isinstance(lines, list): for line in lines: results = rstcheck.check( line, filename=fragment.path, report_level=docutils.utils.Reporter.WARNING_LEVEL) errors += [(fragment.path, 0, 0, result[1]) for result in results] else: results = rstcheck.check( lines, filename=fragment.path, report_level=docutils.utils.Reporter.WARNING_LEVEL) errors += [(fragment.path, 0, 0, result[1]) for result in results] return errors
def test_rst(rst_file): with open(rst_file) as input_file: contents = input_file.read() all_errors = [] errors = rstcheck.check(contents, report_level=2, ignore={"languages": ["python", "bash"]}) for line_number, error in errors: # report only warnings and higher, ignore Python and Bash pseudocode examples if "Title underline too short" in error: # These are caused by unicode en dashes and can be ignored continue # Ignore to text roles provided via Sphinx extensions erroneously marked as unrecognized m = re.search('Unknown interpreted text role "([^"]+)"', error) if m and m.group(1) in ["program", "paramref"]: continue m = re.search('Unknown directive type "([^"]+)"', error) if m and m.group(1) in ["automodule"]: continue all_errors.append((line_number, error)) assert len(all_errors) == 0
def test_readme_rst(): import rstcheck with open('README.rst') as f: readme = f.read() errors = list(rstcheck.check(readme)) assert errors == []
def test_rst(rst_file): with open(rst_file) as input_file: contents = input_file.read() all_errors = [] errors = rstcheck.check( contents, report_level=2, ignore={ 'languages': ['python', 'bash'] } ) for line_number, error in errors: # report only warnings and higher, ignore Python and Bash pseudocode examples if 'Title underline too short' in error: # These are caused by unicode en dashes and can be ignored continue # Ignore to text roles provided via Sphinx extensions erroneously marked as unrecognized m = re.search('Unknown interpreted text role "([^"]+)"', error) if m and m.group(1) in ['program', 'paramref']: continue m = re.search('Unknown directive type "([^"]+)"', error) if m and m.group(1) in ['automodule']: continue all_errors.append((line_number, error)) assert len(all_errors) == 0
def visit_FunctionDef(self, node: ast.FunctionDef): # clean=True removes all leading spaces far more reliably than # textwrap.dedent, not to mention expanding tabs to spaces. docstring = ast.get_docstring(node, clean=True) if docstring: original_line_numbers = {} for line_no, line in enumerate(docstring.split('\n')): original_line_numbers[line.strip()] = line_no parsed_docstring = GoogleDocstring(docstring).__str__() print(parsed_docstring) # TODO: How do I correctly map line numbers back to the original # source? parsed_line_numbers = {} for line_no, line in enumerate(parsed_docstring.split('\n')): line_without_directives = re.sub('^:[a-zA-Z0-9_-]+:', '', line.strip()) parsed_line_numbers[line.strip()] = line_no # Line number, column offset, RST error for local_line_no, rst_error in rstcheck.check(parsed_docstring): self.problems.append((node.lineno + local_line_no + 1, node.col_offset, "NAP001 " + rst_error)) self.generic_visit(node)
def test_check_rst_report_level(self): self.assert_lines_equal([], rstcheck.check("""\ Test === """, report_level=5))
def test_check_rst(self): self.assert_lines_equal( [2], rstcheck.check( """\ Test === """))
def test_readme_is_proper_rst(): parent = pathlib.Path(__file__).parent.resolve().parent path_to_readme = parent / "README.rst" with path_to_readme.open() as f: rst = f.read() errors = list(rstcheck.check(rst)) assert len(errors) == 0, "; ".join(str(e) for e in errors)
def _check_docstring(text): split_docstring = text.split('\n') docstring = '\n'.join( [split_docstring[0]] + textwrap.dedent('\n'.join(split_docstring[1:])).split('\n')) parsed_docstring = GoogleDocstring(docstring, config).__str__() return rstcheck.check(docstring)
def action(files): results = [] for file in files: with open(file) as fh: lines = fh.read() result = list(rstcheck.check(lines)) if result: results.append(file, *result) return results
def test_check_rst_report_level(self): self.assert_lines_equal( [], rstcheck.check( """\ Test === """, report_level=5))
def test_check_code_block(self): self.assert_lines_equal([6], rstcheck.check("""\ Test ==== .. code-block:: python print( """))
def test_check_doctest(self): self.assert_lines_equal([5], rstcheck.check("""\ Testing ======= >>> x = 1 >>>> x 1 """))
def test_readme_valid(self): import rstcheck with open('README.rst') as f: rst = f.read() errors = list(rstcheck.check(rst)) for line, error in errors: print('{}: {}'.format(line, error)) self.assertTrue(len(errors) == 0)
def test_check_doctest_do_not_crash_when_indented(self): """docutils does not provide line number when indented.""" list(rstcheck.check( """\ Testing ======= >>> x = 1 >>>> x 1 """))
def test_check_regex(self): self.assert_lines_equal([6], rstcheck.check("""\ Test ==== .. code-block:: regex [0-9]++ """))
def __processCommands(self, commands): content = "\n".join(commands) results=[] with rstcheck.enable_sphinx_if_possible(): results = list(rstcheck.check(content)) for result in results: logger.debug(f"{result[0]}:{result[1]}") print(f"{result[0]}:{result[1]}") print(f"{self.endSeq}", flush=True)
def test_check(self): self.assert_lines_equal( [6], rstcheck.check( """\ Test ==== .. code-block:: python print( """))
def test_check_doctest(self): self.assert_lines_equal( [5], rstcheck.check( """\ Testing ======= >>> x = 1 >>>> x 1 """))
def lint_optional_conditions( content: str, path: str, collection_name: str) -> t.List[t.Tuple[int, int, str]]: '''Check a extra docs RST file's content for whether it satisfied the required conditions. Return a list of errors. ''' results = rstcheck.check( content, filename=path, report_level=docutils.utils.Reporter.WARNING_LEVEL) return [(result[0], 0, result[1]) for result in results]
def test_check_regex_with_bad_ignore(self): self.assert_lines_equal([6, 8], rstcheck.check("""\ Test ==== .. code-block:: regex [0-9]++ .. rstcheck: ignore-language regex,python,rst """))
def test_check_doctest_in_code_block(self): self.assert_lines_equal([7], rstcheck.check("""\ Testing ======= .. code-block:: doctest >>> x = 1 >>>> x 1 """))
def test_check_json(self): self.assert_lines_equal([7], rstcheck.check("""\ Test ==== .. code-block:: json { 'abc': 123 } """))
def test_check_regex_with_unmatched_ignores_only(self): self.assert_lines_equal([6], rstcheck.check("""\ Test ==== .. code-block:: regex [0-9]++ .. rstcheck: ignore-language=cpp,python,rst """))
def test_check_doctest_with_ignore(self): self.assert_lines_equal([], rstcheck.check("""\ Testing ======= >>> x = 1 >>>> x 1 .. rstcheck: ignore-language=doctest """))
def test_check_with_extra_blank_lines_before(self): self.assert_lines_equal([8], rstcheck.check("""\ Test ==== .. code-block:: python print( """))
def test_check_doctest_in_python_code_block(self): """I'm not sure if this is correct, but I've seen people do it.""" self.assert_lines_equal([7], rstcheck.check("""\ Testing ======= .. code-block:: python >>> x = 1 >>>> x 1 """))
def test_check_xml(self): self.assert_lines_equal([8], rstcheck.check("""\ Test ==== .. code-block:: xml <?xml version="1.0" encoding="UTF-8"?> <root> </abc>123<abc> </root> """))
def visit_ClassDef(self, node: ast.ClassDef): # clean=True removes all leading spaces far more reliably than # textwrap.dedent, not to mention expanding tabs to spaces. docstring = ast.get_docstring(node, clean=True) if docstring: parsed_docstring = GoogleDocstring(docstring).__str__() # Line number, column offset, RST error for local_line_no, rst_error in rstcheck.check(parsed_docstring): self.problems.append((node.lineno + local_line_no + 1, node.col_offset, "NAP001 " + rst_error)) self.generic_visit(node)
def test_check_with_extra_blank_lines_before(self): self.assert_lines_equal( [8], rstcheck.check( """\ Test ==== .. code-block:: python print( """))
def test_check_json_with_bad_ignore(self): self.assert_lines_equal([7, 10], rstcheck.check("""\ Test ==== .. code-block:: json { 'abc': 123 } .. rstcheck: ignore-language json,python,rst """))
def test_check_doctest_with_ignore(self): self.assert_lines_equal( [], rstcheck.check( """\ Testing ======= >>> x = 1 >>>> x 1 .. rstcheck: ignore-language=doctest """))
def test_check_doctest_in_code_block(self): self.assert_lines_equal( [7], rstcheck.check( """\ Testing ======= .. code-block:: doctest >>> x = 1 >>>> x 1 """))
def test_check_json(self): self.assert_lines_equal( [7], rstcheck.check( """\ Test ==== .. code-block:: json { 'abc': 123 } """))
def test_ignore_sphinx_directives(self): self.assert_lines_equal( [], rstcheck.check( """\ .. toctree:: :maxdepth: 2 intro strings datatypes numeric (many more documents listed here) """))
def visit_functiondef(self, node): """ Check if docstring always starts and ends from/by triple double-quotes and they are on the new line. """ if hasattr(node, "doc") and node.doc: out = list(rstcheck.check(node.doc.strip())) if out: messages = [] for errcode, msg in out: messages.append(msg) self.add_message("docstring-rst-format", node=node, args=(", ".join(messages), ))
def test_check_json_with_unmatched_ignores_only(self): self.assert_lines_equal([7], rstcheck.check("""\ Test ==== .. code-block:: json { 'abc': 123 } .. rstcheck: ignore-language=cpp,python,rst """))
def test_check_xml_with_bad_ignore(self): self.assert_lines_equal([8, 11], rstcheck.check("""\ Test ==== .. code-block:: xml <?xml version="1.0" encoding="UTF-8"?> <root> </abc>123<abc> </root> .. rstcheck: ignore-language xml,python,rst """))
def test_check_doctest_in_python_code_block(self): """I'm not sure if this is correct, but I've seen people do it.""" self.assert_lines_equal( [7], rstcheck.check( """\ Testing ======= .. code-block:: python >>> x = 1 >>>> x 1 """))
def test_check_json_with_unmatched_ignores_only(self): self.assert_lines_equal( [7], rstcheck.check( """\ Test ==== .. code-block:: json { 'abc': 123 } .. rstcheck: ignore-language=cpp,python,rst """))
def test_check_json_with_bad_ignore(self): self.assert_lines_equal( [7, 10], rstcheck.check( """\ Test ==== .. code-block:: json { 'abc': 123 } .. rstcheck: ignore-language json,python,rst """))
def check_readme(file='README.rst'): """ Checks readme rst file, to ensure it will upload to pypi and be formatted correctly. :param file: :return: """ # Get the long description from the relevant file with open(file, encoding='utf-8') as f: readme_content = f.read() errors = list(rstcheck.check(readme_content)) if errors: msg = 'There are errors in {}, errors \n {}'.format(file, str(errors[0])) raise SystemExit(msg) else: msg = 'No errors in {}'.format(file) print(msg)
def test_ignore_sphinx_directives(self): self.assert_lines_equal( [], rstcheck.check( """\ .. toctree:: :maxdepth: 2 intro strings datatypes numeric (many more documents listed here) .. highlight:: python :linenothreshold: 5 :: print('Hello') .. code-block:: ruby :linenos: puts "Hello!" .. code-block:: python :linenos: :emphasize-lines: 3,5 def some_function(): interesting = False print('This line is highlighted.') print('This one is not...') print('...but this one is.') .. literalinclude:: rstcheck.py :language: python :linenos: """))
def test_check_nested_rst(self): self.assert_lines_equal( [32], rstcheck.check( """\ Test ==== .. code-block:: rst Test ==== .. code-block:: rst Test ==== .. code-block:: rst Test ==== .. code-block:: rst Test ==== .. code-block:: rst Test ==== .. code-block:: python print( """))