def test_DocBaseClass_extraction_CPP(self): data = load_testdata('data.cpp') # No built-in documentation for C++. with self.assertRaises(KeyError): tuple(DocBaseClass.extract(data, 'CPP', 'default')) docstyle_CPP_doxygen = DocstyleDefinition.load('CPP', 'doxygen') self.assertEqual( tuple(DocBaseClass.extract(data, 'CPP', 'doxygen')), (DocumentationComment( ('\n' ' This is the main function.\n' ' @returns Exit code.\n' ' Or any other number.\n'), docstyle_CPP_doxygen, '', docstyle_CPP_doxygen.markers[0], TextPosition(4, 1)), DocumentationComment( (' foobar\n' ' @param xyz\n'), docstyle_CPP_doxygen, '', docstyle_CPP_doxygen.markers[0], TextPosition(15, 1)), DocumentationComment(' Some alternate style of documentation\n', docstyle_CPP_doxygen, '', docstyle_CPP_doxygen.markers[4], TextPosition(22, 1)), DocumentationComment(' ends instantly', docstyle_CPP_doxygen, '\t', docstyle_CPP_doxygen.markers[0], TextPosition(26, 2)), DocumentationComment( (' Should work\n' '\n' ' even without a function standing below.\n' '\n' ' @param foo WHAT PARAM PLEASE!?\n'), docstyle_CPP_doxygen, '', docstyle_CPP_doxygen.markers[4], TextPosition(32, 1))))
def test_not_implemented(self): raw_docstyle = DocstyleDefinition('nolang', 'nostyle', ('', '', ''), self.Metadata('', '', '')) not_implemented = DocumentationComment( 'some docs', raw_docstyle, None, None, None) with self.assertRaises(NotImplementedError): not_implemented.parse()
def test_not_implemented(self): raw_docstyle = DocstyleDefinition("nolang", "nostyle", ('', '', ''), self.Metadata('', '', '')) not_implemented = DocumentationComment( "some docs", raw_docstyle, None, None, None) with self.assertRaises(NotImplementedError): not_implemented.parse()
def test_fields(self): c_doxygen = DocstyleDefinition.load('C', 'doxygen') uut = DocumentationComment('my doc', c_doxygen, ' ', ('/**', '*', '*/'), TextPosition(3, 1)) self.assertEqual(uut.documentation, 'my doc') self.assertEqual(uut.language, 'c') self.assertEqual(uut.docstyle, 'doxygen') self.assertEqual(uut.indent, ' ') self.assertEqual(str(uut), 'my doc') self.assertEqual(uut.marker, ('/**', '*', '*/')) self.assertEqual(uut.position, TextPosition(3, 1)) python_doxygen = DocstyleDefinition.load('python', 'doxygen') python_doxygen_metadata = self.Metadata('@param ', ' ', '@raises ', ' ', '@return ') uut = DocumentationComment('qwertzuiop', python_doxygen, '\t', ('##', '#', '#'), None) self.assertEqual(uut.documentation, 'qwertzuiop') self.assertEqual(uut.language, 'python') self.assertEqual(uut.docstyle, 'doxygen') self.assertEqual(uut.indent, '\t') self.assertEqual(str(uut), 'qwertzuiop') self.assertEqual(uut.marker, ('##', '#', '#')) self.assertEqual(uut.range, None) self.assertEqual(uut.position, None) self.assertEqual(uut.metadata, python_doxygen_metadata)
def test_extract_documentation_CPP(self): data = DocumentationExtractionTest.load_testdata("data.cpp") # No built-in documentation for C++. with self.assertRaises(KeyError): tuple(extract_documentation(data, "CPP", "default")) docstyle_CPP_doxygen = DocstyleDefinition.load("CPP", "doxygen") self.assertEqual( tuple(extract_documentation(data, "CPP", "doxygen")), (DocumentationComment(("\n" " This is the main function.\n" " @returns Exit code.\n" " Or any other number.\n"), docstyle_CPP_doxygen.markers[0], TextRange.from_values(4, 1, 8, 4)), DocumentationComment( (" foobar\n" " @param xyz\n"), docstyle_CPP_doxygen.markers[0], TextRange.from_values(15, 1, 17, 4)), DocumentationComment(" Some alternate style of documentation\n", docstyle_CPP_doxygen.markers[4], TextRange.from_values(22, 1, 23, 1)), DocumentationComment(" ends instantly", docstyle_CPP_doxygen.markers[0], TextRange.from_values(26, 5, 26, 26)), DocumentationComment((" Should work\n" "\n" " even without a function standing below.\n" "\n" " @param foo WHAT PARAM PLEASE!?\n"), docstyle_CPP_doxygen.markers[4], TextRange.from_values(32, 1, 37, 1))))
def test_not_implemented(self): raw_docstyle = DocstyleDefinition('nolang', 'nostyle', ('', '', ''), self.Metadata('', '', '', '', ''), self.ClassPadding('', ''), self.FunctionPadding('', ''), self.DocstringTypeRegex('', ''), '') not_implemented = DocumentationComment( 'some docs', raw_docstyle, None, None, None) with self.assertRaises(NotImplementedError): not_implemented.parse()
def check_docstring(self, docstring, expected=[]): self.assertIsInstance(docstring, str, "expected needs to be a string for this test.") self.assertIsInstance(expected, list, "expected needs to be a list for this test.") doc_comment = DocumentationComment(docstring, "python", "default", None, None, None) parsed_metadata = doc_comment.parse() self.assertEqual(parsed_metadata, expected)
def check_docstring(self, docstring, expected=[]): self.assertIsInstance(docstring, str, 'expected needs to be a string for this test.') self.assertIsInstance(expected, list, 'expected needs to be a list for this test.') python_default = DocstyleDefinition.load('python', 'default') doc_comment = DocumentationComment(docstring, python_default, None, None, None) parsed_metadata = doc_comment.parse() self.assertEqual(parsed_metadata, expected)
def test_fields(self): uut = DocumentationComment("my doc", ("/**", "*", "*/"), (25, 45)) self.assertEqual(uut.documentation, "my doc") self.assertEqual(str(uut), "my doc") self.assertEqual(uut.marker, ("/**", "*", "*/")) self.assertEqual(uut.range, (25, 45)) uut = DocumentationComment("qwertzuiop", ("##", "#", "#"), None) self.assertEqual(uut.documentation, "qwertzuiop") self.assertEqual(str(uut), "qwertzuiop") self.assertEqual(uut.marker, ("##", "#", "#")) self.assertEqual(uut.range, None)
def _extract_doc_comment_from_line(content, line, column, regex, marker_dict, docstyle_definition): cur_line = content[line] begin_match = regex.search(cur_line, column) if begin_match: indent = cur_line[:begin_match.start()] column = begin_match.end() for marker in marker_dict[begin_match.group()]: doc_comment = _extract_doc_comment(content, line, column, marker) if doc_comment is not None: end_line, end_column, documentation = doc_comment position = TextPosition(line + 1, len(indent) + 1) doc = DocumentationComment(documentation, docstyle_definition, indent, marker, position) break if doc_comment: return end_line, end_column, doc else: malformed_comment = MalformedComment( dedent("""\ Please check the docstring for faulty markers. A starting marker has been found, but no instance of DocComment is returned."""), line) return line + 1, 0, malformed_comment return line + 1, 0, None
def test_extract_documentation_PYTHON3(self): data = DocumentationExtractionTest.load_testdata("data.py") docstyle_PYTHON3_default = DocstyleDefinition.load( "PYTHON3", "default") docstyle_PYTHON3_doxygen = DocstyleDefinition.load( "PYTHON3", "doxygen") expected = (DocumentationComment(("\n" "Module description.\n" "\n" "Some more foobar-like text.\n"), docstyle_PYTHON3_default.markers[0], TextRange.from_values(1, 1, 5, 4)), DocumentationComment( ("\n" "A nice and neat way of documenting code.\n" ":param radius: The explosion radius.\n"), docstyle_PYTHON3_default.markers[0], TextRange.from_values(8, 5, 11, 8)), DocumentationComment( ("\n" "Docstring with layouted text.\n" "\n" " layouts inside docs are preserved for these " "documentation styles.\n" "this is intended.\n"), docstyle_PYTHON3_default.markers[0], TextRange.from_values(14, 1, 19, 4)), DocumentationComment( (" Docstring directly besides triple quotes.\n" " Continues here. "), docstyle_PYTHON3_default.markers[0], TextRange.from_values(21, 1, 22, 24)), DocumentationComment(("super\n" " nicely\n" "short"), docstyle_PYTHON3_default.markers[0], TextRange.from_values(35, 1, 37, 9))) self.assertEqual( tuple(extract_documentation(data, "PYTHON3", "default")), expected) # Change only the docstyle in expected results. expected = list( DocumentationComment(r.documentation, r.marker, r.range) for r in expected) expected.insert( 4, DocumentationComment( (" Alternate documentation style in doxygen.\n" " Subtext\n" " More subtext (not correctly aligned)\n" " sub-sub-text\n" "\n"), docstyle_PYTHON3_doxygen.markers[1], TextRange.from_values(25, 1, 30, 1))) self.assertEqual( list(extract_documentation(data, "PYTHON3", "doxygen")), expected)
def test_extract_documentation_C(self): data = load_testdata("data.c") # No built-in documentation for C. with self.assertRaises(KeyError): tuple(extract_documentation(data, "C", "default")) docstyle_C_doxygen = DocstyleDefinition.load("C", "doxygen") expected_results = (DocumentationComment( ("\n" " This is the main function.\n" "\n" " @returns Your favorite number.\n"), docstyle_C_doxygen, "", docstyle_C_doxygen.markers[0], TextRange.from_values(3, 1, 7, 4)), DocumentationComment( ("\n" " Preserves alignment\n" " - Main item\n" " - sub item\n" " - sub sub item\n"), docstyle_C_doxygen, "", docstyle_C_doxygen.markers[2], TextRange.from_values(15, 1, 20, 4)), DocumentationComment( (" ABC\n" " Another type of comment\n" "\n" " ..."), docstyle_C_doxygen, "", docstyle_C_doxygen.markers[1], TextRange.from_values(23, 1, 26, 11)), DocumentationComment( (" foobar = barfoo.\n" " @param x whatever...\n"), docstyle_C_doxygen, "", docstyle_C_doxygen.markers[0], TextRange.from_values(28, 1, 30, 4))) self.assertEqual(tuple( extract_documentation(data, "C", "doxygen")), expected_results)
def test_DocBaseClass_extraction_C(self): data = load_testdata('data.c') # No built-in documentation for C. with self.assertRaises(KeyError): tuple(DocBaseClass.extract(data, 'C', 'default')) docstyle_C_doxygen = DocstyleDefinition.load('C', 'doxygen') expected_results = (DocumentationComment( ('\n' ' This is the main function.\n' '\n' ' @returns Your favorite number.\n'), docstyle_C_doxygen, '', docstyle_C_doxygen.markers[0], TextPosition(3, 1)), DocumentationComment( ('\n' ' Preserves alignment\n' ' - Main item\n' ' - sub item\n' ' - sub sub item\n'), docstyle_C_doxygen, '', docstyle_C_doxygen.markers[2], TextPosition(15, 1)), DocumentationComment( (' ABC\n' ' Another type of comment\n' '\n' ' ...'), docstyle_C_doxygen, '', docstyle_C_doxygen.markers[1], TextPosition(23, 1)), DocumentationComment( (' foobar = barfoo.\n' ' @param x whatever...\n'), docstyle_C_doxygen, '', docstyle_C_doxygen.markers[0], TextPosition(28, 1))) self.assertEqual(tuple( DocBaseClass.extract(data, 'C', 'doxygen')), expected_results)
def test_extract_documentation_CPP_2(self): data = DocumentationExtractionTest.load_testdata("data2.cpp") docstyle_CPP_doxygen = DocstyleDefinition.load("CPP", "doxygen") self.assertEqual(tuple(extract_documentation( data, "CPP", "doxygen")), (DocumentationComment( ("module comment\n" " hello world\n"), docstyle_CPP_doxygen.markers[0], TextRange.from_values(1, 1, 3, 4)), ))
def test_extract_documentation_C_2(self): data = ['/** my main description\n', ' * continues here */'] docstyle_C_doxygen = DocstyleDefinition.load('C', 'doxygen') self.assertEqual(list(extract_documentation(data, 'C', 'doxygen')), [ DocumentationComment( ' my main description\n continues here', docstyle_C_doxygen, '', docstyle_C_doxygen.markers[0], TextPosition(1, 1)) ])
def test_extract_documentation_C_2(self): data = ['/** my main description\n', ' * continues here */'] docstyle_C_doxygen = DocstyleDefinition.load("C", "doxygen") self.assertEqual(list(extract_documentation(data, "C", "doxygen")), [ DocumentationComment(" my main description\n continues here", "C", "doxygen", "", docstyle_C_doxygen.markers[0], TextRange.from_values(1, 1, 2, 21)) ])
def test_extract_documentation_CPP_2(self): data = load_testdata('data2.cpp') docstyle_CPP_doxygen = DocstyleDefinition.load('CPP', 'doxygen') self.assertEqual(tuple(extract_documentation( data, 'CPP', 'doxygen')), (DocumentationComment( ('module comment\n' ' hello world\n'), docstyle_CPP_doxygen, '', docstyle_CPP_doxygen.markers[0], TextPosition(1, 1)), ))
def test_extract_documentation_PYTHON3_2(self): data = ['\n', '""" documentation in single line """\n', 'print(1)\n'] docstyle_PYTHON3_default = DocstyleDefinition.load("PYTHON3", "default") self.assertEqual( list(extract_documentation(data, "PYTHON3", "default")), [DocumentationComment(" documentation in single line ", docstyle_PYTHON3_default.markers[0], TextRange.from_values(2, 1, 2, 38))])
def test_DocBaseClass_extraction_PYTHON3_2(self): data = ['\n', '""" documentation in single line """\n', 'print(1)\n'] docstyle_PYTHON3_default = DocstyleDefinition.load('PYTHON3', 'default') self.assertEqual( list(DocBaseClass.extract(data, 'PYTHON3', 'default')), [DocumentationComment(' documentation in single line ', docstyle_PYTHON3_default, '', docstyle_PYTHON3_default.markers[0], TextPosition(2, 1))])
def test_DocBaseClass_extraction_PYTHON3_5(self): data = ['r"""\n', 'This is a raw docstring\n', '"""\n'] docstyle_PYTHON3_default = DocstyleDefinition.load('PYTHON3', 'default') self.assertEqual( list(DocBaseClass.extract(data, 'PYTHON3', 'default')), [DocumentationComment('\nThis is a raw docstring\n', docstyle_PYTHON3_default, 'r', docstyle_PYTHON3_default.markers[0], TextPosition(1, 2))])
def run(self, filename, file, language: str, docstyle: str = 'default', allow_missing_func_desc: str = False, indent_size: int = 4, expand_one_liners: str = False): """ Checks for certain in-code documentation styles. It currently checks for the following style: :: The first line needs to have no indentation. - Following lines can have indentation :param x: 4 space indent :return: also 4 space indent following lines are also 4 space indented :param language: The programming language of the file(s). :param docstyle: The docstyle to use. For example ``default`` or ``doxygen``. Docstyles are language dependent, meaning not every language is supported by a certain docstyle. :param allow_missing_func_desc: When set ``True`` this will allow functions with missing descriptions, allowing functions to start with params. :param indent_size: Number of spaces per indentation level. :param expand_one_liners: When set ``True`` this will expand one liner docstrings. """ for doc_comment in self.extract(file, language, docstyle): parsed = doc_comment.parse() (new_metadata, warning_desc) = self.process_documentation( parsed, allow_missing_func_desc, indent_size, expand_one_liners) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) if new_comment != doc_comment: # Something changed, let's apply a result. diff = self.generate_diff(file, doc_comment, new_comment) yield Result(origin=self, message=warning_desc, affected_code=(diff.range(filename), ), diffs={filename: diff})
def test_extract_documentation_PYTHON3_3(self): data = ['## documentation in single line without return at end.'] docstyle_PYTHON3_doxygen = DocstyleDefinition.load("PYTHON3", "doxygen") self.assertEqual( list(extract_documentation(data, "PYTHON3", "doxygen")), [DocumentationComment(" documentation in single line without " "return at end.", docstyle_PYTHON3_doxygen.markers[1], TextRange.from_values(1, 1, 1, 55))])
def test_extract_documentation_PYTHON3_3(self): data = ['## documentation in single line without return at end.'] docstyle_PYTHON3_doxygen = DocstyleDefinition.load( 'PYTHON3', 'doxygen') self.assertEqual( list(extract_documentation(data, 'PYTHON3', 'doxygen')), [ DocumentationComment( ' documentation in single line without ' 'return at end.', docstyle_PYTHON3_doxygen, '', docstyle_PYTHON3_doxygen.markers[1], TextPosition(1, 1)) ])
def test_fields(self): uut = DocumentationComment("my doc", "c", "default", " ", ("/**", "*", "*/"), (25, 45)) self.assertEqual(uut.documentation, "my doc") self.assertEqual(uut.language, "c") self.assertEqual(uut.docstyle, "default") self.assertEqual(uut.indent, " ") self.assertEqual(str(uut), "my doc") self.assertEqual(uut.marker, ("/**", "*", "*/")) self.assertEqual(uut.range, (25, 45)) uut = DocumentationComment("qwertzuiop", "python", "doxygen", "\t", ("##", "#", "#"), None) self.assertEqual(uut.documentation, "qwertzuiop") self.assertEqual(uut.language, "python") self.assertEqual(uut.docstyle, "doxygen") self.assertEqual(uut.indent, "\t") self.assertEqual(str(uut), "qwertzuiop") self.assertEqual(uut.marker, ("##", "#", "#")) self.assertEqual(uut.range, None)
def run(self, filename, file, language: str, docstyle: str='default', allow_missing_func_desc: str=False, indent_size: int=4, expand_one_liners: str=False): """ Checks for certain in-code documentation styles. It currently checks for the following style: :: The first line needs to have no indentation. - Following lines can have indentation :param x: 4 space indent :return: also 4 space indent following lines are also 4 space indented :param language: The programming language of the file(s). :param docstyle: The docstyle to use. For example ``default`` or ``doxygen``. Docstyles are language dependent, meaning not every language is supported by a certain docstyle. :param allow_missing_func_desc: When set ``True`` this will allow functions with missing descriptions, allowing functions to start with params. :param indent_size: Number of spaces per indentation level. :param expand_one_liners: When set ``True`` this will expand one liner docstrings. """ for doc_comment in self.extract(file, language, docstyle): parsed = doc_comment.parse() (new_metadata, warning_desc) = self.process_documentation( parsed, allow_missing_func_desc, indent_size, expand_one_liners) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) if new_comment != doc_comment: # Something changed, let's apply a result. diff = self.generate_diff(file, doc_comment, new_comment) yield Result( origin=self, message=warning_desc, affected_code=(diff.range(filename),), diffs={filename: diff})
def test_from_metadata(self): data = load_testdata("default.py") original = list(extract_documentation(data, "python", "default")) parsed_docs = [(doc.parse(), doc.marker, doc.indent, doc.range) for doc in original] docstyle_definition = DocstyleDefinition.load("python", "default") assembled_docs = [DocumentationComment.from_metadata( doc[0], docstyle_definition, doc[1], doc[2], doc[3]) for doc in parsed_docs] self.assertEqual(assembled_docs, original)
def test_from_metadata(self): data = load_testdata('default.py') original = list(DocBaseClass.extract(data, 'python', 'default')) parsed_docs = [(doc.parse(), doc.marker, doc.indent, doc.position) for doc in original] docstyle_definition = DocstyleDefinition.load('python', 'default') assembled_docs = [DocumentationComment.from_metadata( doc[0], docstyle_definition, doc[1], doc[2], doc[3]) for doc in parsed_docs] self.assertEqual(assembled_docs, original)
def run( self, filename, file, language: str, docstyle: str = 'default', locale: str = 'en-US', languagetool_disable_rules: typed_list(str) = (), ): """ Checks the main description and comments description of documentation with LanguageTool. LanguageTool finds many errors that a simple spell checker cannot detect and several grammar problems. A full list of rules for english language can be found here at: https://community.languagetool.org/rule/list?lang=en LanguageTool currently supports more than 25 languages. For further information, visit: https://www.languagetool.org/languages/ :param language: The programming language of the file(s). :param docstyle: The docstyle to use. For example ``default`` or ``doxygen``. Docstyles are language dependent, meaning not every language is supported by a certain docstyle. :param locale: A locale representing the language you want to have checked. Default is set to 'en-US'. :param languagetool_disable_rules: List of rules to disable checks for. """ for doc_comment in self.extract(file, language, docstyle): parsed = doc_comment.parse() (new_metadata, warning_desc) = self.process_documentation( parsed, locale, languagetool_disable_rules) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) if new_comment != doc_comment: # Something changed, let's apply a result. diff = self.generate_diff(file, doc_comment, new_comment) yield Result(origin=self, message=warning_desc, affected_code=(diff.range(filename), ), diffs={filename: diff})
def _extract_doc_comment_from_line(content, line, column, regex, marker_dict): begin_match = regex.search(content[line], column) if begin_match: column = begin_match.end() for marker in marker_dict[begin_match.group()]: doc_comment = _extract_doc_comment(content, line, column, marker) if doc_comment is not None: end_line, end_column, documentation = doc_comment rng = TextRange.from_values(line + 1, begin_match.start() + 1, end_line + 1, end_column + 1) doc = DocumentationComment(documentation, marker, rng) return end_line, end_column, doc return line + 1, 0, None
def test_from_metadata(self): data = load_testdata("default.py") original = list(extract_documentation(data, "python", "default")) parsed_docs = [(doc.parse(), doc.marker, doc.indent, doc.range) for doc in original] docstyle_definition = DocstyleDefinition.load("python", "default") assembled_docs = [ DocumentationComment.from_metadata(doc[0], docstyle_definition, doc[1], doc[2], doc[3]) for doc in parsed_docs ] self.assertEqual(assembled_docs, original)
def run(self, filename, file, language: str, docstyle: str = 'default', locale: str = 'en-US', languagetool_disable_rules: typed_list(str) = (), ): """ Checks the main description and comments description of documentation with LanguageTool. LanguageTool finds many errors that a simple spell checker cannot detect and several grammar problems. A full list of rules for english language can be found here at: https://community.languagetool.org/rule/list?lang=en LanguageTool currently supports more than 25 languages. For further information, visit: https://www.languagetool.org/languages/ :param language: The programming language of the file(s). :param docstyle: The docstyle to use. For example ``default`` or ``doxygen``. Docstyles are language dependent, meaning not every language is supported by a certain docstyle. :param locale: A locale representing the language you want to have checked. Default is set to 'en-US'. :param languagetool_disable_rules: List of rules to disable checks for. """ for doc_comment in self.extract(file, language, docstyle): parsed = doc_comment.parse() (new_metadata, warning_desc) = self.process_documentation( parsed, locale, languagetool_disable_rules) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) if new_comment != doc_comment: # Something changed, let's apply a result. diff = self.generate_diff(file, doc_comment, new_comment) yield Result( origin=self, message=warning_desc, affected_code=(diff.range(filename),), diffs={filename: diff})
def _extract_doc_comment_from_line(content, line, column, regex, marker_dict, docstyle_definition): cur_line = content[line] begin_match = regex.search(cur_line, column) if begin_match: column = begin_match.end() indent = begin_match.group('indent') for marker in marker_dict[begin_match.group('marker')]: doc_comment = _extract_doc_comment(content, line, column, marker) if doc_comment is not None: end_line, end_column, documentation = doc_comment position = TextPosition(line + 1, len(indent) + 1) doc = DocumentationComment(documentation, docstyle_definition, indent, marker, position) return end_line, end_column, doc return line + 1, 0, None
def test_not_implemented(self): not_implemented = DocumentationComment( "some docs", "nolang", "doxygen", None, None, None) with self.assertRaises(NotImplementedError): not_implemented.parse()
def process_documentation(self, doc_comment: DocumentationComment, allow_missing_func_desc: str = False, indent_size: int = 4, expand_one_liners: str = False): """ This fixes the parsed documentation comment. :param doc_comment: Contains instance of DocumentationComment. :param allow_missing_func_desc: When set ``True`` this will allow functions with missing descriptions, allowing functions to start with params. :param indent_size: Number of spaces per indentation level. :param expand_one_liners: When set ``True`` this will expand one liner docstrings. :return: A tuple of fixed parsed documentation comment and warning_desc. """ parsed = doc_comment.parse() # Assuming that the first element is always the only main # description. metadata = iter(parsed) main_description = next(metadata) if main_description.desc == '\n' and not allow_missing_func_desc: # Triple quoted string literals doesn't look good. It breaks # the line of flow. Hence we use dedent. warning_desc = dedent("""\ Missing function description. Please set allow_missing_func_desc = True to ignore this warning. """) else: warning_desc = 'Documentation does not have correct style.' # one empty line shall follow main description (except it's empty # or no annotations follow). if main_description.desc.strip() != '': if not expand_one_liners and len(parsed) == 1: main_description = main_description._replace( desc=main_description.desc.strip()) else: main_description = main_description._replace( desc='\n' + main_description.desc.strip() + '\n' * (1 if len(parsed) == 1 else 2)) new_metadata = [main_description] for m in metadata: # Split newlines and remove leading and trailing whitespaces. stripped_desc = list(map(str.strip, m.desc.splitlines())) if len(stripped_desc) == 0: # Annotations should be on their own line, though no # further description follows. stripped_desc.append('') else: # Wrap parameter description onto next line if it follows # annotation directly. if stripped_desc[0] != '': stripped_desc.insert(0, '') # Indent with 4 spaces. stripped_desc = ('' if line == '' else ' ' * indent_size + line for line in stripped_desc) new_desc = '\n'.join(stripped_desc) # Strip away trailing whitespaces and obsolete newlines (except # one newline which is mandatory). new_desc = new_desc.rstrip() + '\n' new_metadata.append(m._replace(desc=new_desc.lstrip(' '))) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) # Instantiate default padding. class_padding = doc_comment.docstyle_definition.class_padding function_padding = doc_comment.docstyle_definition.function_padding # Check if default padding exist in the coalang file. if (class_padding != DocstyleDefinition.ClassPadding('', '') and function_padding != DocstyleDefinition.FunctionPadding( '', '')): # Check docstring_type if doc_comment.docstring_type == 'class': new_comment.top_padding = class_padding.top_padding new_comment.bottom_padding = class_padding.bottom_padding elif doc_comment.docstring_type == 'function': new_comment.top_padding = function_padding.top_padding new_comment.bottom_padding = function_padding.bottom_padding else: # Keep paddings as they are originally. new_comment.top_padding = doc_comment.top_padding new_comment.bottom_padding = doc_comment.bottom_padding else: # If there's no default paddings defined. Keep padding as # they are originally. new_comment.top_padding = doc_comment.top_padding new_comment.bottom_padding = doc_comment.bottom_padding return (new_comment, warning_desc)
def process_documentation(self, doc_comment: DocumentationComment, allow_missing_func_desc: str = False, indent_size: int = 4, expand_one_liners: str = False, use_spaces: bool = True, ): """ This fixes the parsed documentation comment. :param doc_comment: Contains instance of DocumentationComment. :param allow_missing_func_desc: When set ``True`` this will allow functions with missing descriptions, allowing functions to start with params. :param indent_size: Number of spaces per indentation level. :param expand_one_liners: When set ``True`` this will expand one liner docstrings. :param use_spaces: Decides whether spaces are used for indentation or tabs. :return: An instance of a processed/fixed DocumentationComment. """ parsed = doc_comment.parse() # Assuming that the first element is always the only main # description. metadata = iter(parsed) main_description = next(metadata) if main_description.desc == '\n' and not allow_missing_func_desc: # Triple quoted string literals doesn't look good. It breaks # the line of flow. Hence we use dedent. warning_desc = dedent("""\ Missing function description. Please set allow_missing_func_desc = True to ignore this warning. """) else: warning_desc = 'Documentation does not have correct style.' # one empty line shall follow main description (except it's empty # or no annotations follow). if main_description.desc.strip() != '': if not expand_one_liners and len(parsed) == 1: main_description = main_description._replace( desc=main_description.desc.strip()) else: main_description = main_description._replace( desc='\n' + main_description.desc.strip() + '\n' * (1 if len(parsed) == 1 else 2)) new_metadata = [main_description] for m in metadata: # Split newlines and remove leading and trailing whitespaces. stripped_desc = list(map(str.strip, m.desc.splitlines())) if len(stripped_desc) == 0: # Annotations should be on their own line, though no # further description follows. stripped_desc.append('') else: # Wrap parameter description onto next line if it follows # annotation directly. if stripped_desc[0] != '': stripped_desc.insert(0, '') indent = ' ' if use_spaces else '\t' stripped_desc = ('' if line == '' else indent * indent_size + line for line in stripped_desc) new_desc = '\n'.join(stripped_desc) # Strip away trailing whitespaces and obsolete newlines (except # one newline which is mandatory). new_desc = new_desc.rstrip() + '\n' new_metadata.append(m._replace(desc=new_desc.lstrip(' '))) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) # Instantiate default padding. class_padding = doc_comment.docstyle_definition.class_padding function_padding = doc_comment.docstyle_definition.function_padding # Check if default padding exist in the coalang file. if (class_padding != DocstyleDefinition.ClassPadding('', '') and function_padding != DocstyleDefinition.FunctionPadding( '', '')): # Check docstring_type if doc_comment.docstring_type == 'class': new_comment.top_padding = class_padding.top_padding new_comment.bottom_padding = class_padding.bottom_padding elif doc_comment.docstring_type == 'function': new_comment.top_padding = function_padding.top_padding new_comment.bottom_padding = function_padding.bottom_padding else: # Keep paddings as they are originally. new_comment.top_padding = doc_comment.top_padding new_comment.bottom_padding = doc_comment.bottom_padding else: # If there's no default paddings defined. Keep padding as # they are originally. new_comment.top_padding = doc_comment.top_padding new_comment.bottom_padding = doc_comment.bottom_padding return (new_comment, warning_desc)
def run(self, filename, file, language: str, docstyle: str = 'default', allow_missing_func_desc: str = False, indent_size: int = 4): """ Checks for certain in-code documentation styles. It currently checks for the following style: :: The first line needs to have no indentation. - Following lines can have indentation :param x: 4 space indent :return: also 4 space indent following lines are also 4 space indented :param language: The programming language of the file(s). :param docstyle: The docstyle to use. For example ``default`` or ``doxygen``. Docstyles are language dependent, meaning not every language is supported by a certain docstyle. :param allow_missing_func_desc: When set ``True`` this will allow functions with missing descriptions, allowing functions to start with params. :param indent_size: Number of spaces per indentation level. """ for doc_comment in extract_documentation(file, language, docstyle): parsed = doc_comment.parse() metadata = iter(parsed) # Assuming that the first element is always the only main # description. main_description = next(metadata) if main_description.desc == '\n' and not allow_missing_func_desc: warning_desc = """ Missing function description. Please set allow_missing_func_desc = True to ignore this warning. """ else: warning_desc = 'Documentation does not have correct style.' # one empty line shall follow main description (except it's empty # or no annotations follow). if main_description.desc.strip() != '': main_description = main_description._replace( desc='\n' + main_description.desc.strip() + '\n' * (1 if len(parsed) == 1 else 2)) new_metadata = [main_description] for m in metadata: # Split newlines and remove leading and trailing whitespaces. stripped_desc = list(map(str.strip, m.desc.splitlines())) if len(stripped_desc) == 0: # Annotations should be on their own line, though no # further description follows. stripped_desc.append('') else: # Wrap parameter description onto next line if it follows # annotation directly. if stripped_desc[0] != '': stripped_desc.insert(0, '') # Indent with 4 spaces. stripped_desc = ('' if line == '' else ' ' * indent_size + line for line in stripped_desc) new_desc = '\n'.join(stripped_desc) # Strip away trailing whitespaces and obsolete newlines (except # one newline which is mandatory). new_desc = new_desc.rstrip() + '\n' new_metadata.append(m._replace(desc=new_desc.lstrip(' '))) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) if new_comment != doc_comment: # Something changed, let's apply a result. diff = Diff(file) # We need to update old comment positions, as `assemble()` # prepends indentation for first line. old_range = TextRange.from_values(doc_comment.range.start.line, 1, doc_comment.range.end.line, doc_comment.range.end.column) diff.replace(old_range, new_comment.assemble()) yield Result(origin=self, message=warning_desc, affected_code=(diff.range(filename), ), diffs={filename: diff})
def test_extract_documentation_PYTHON3(self): data = load_testdata('data.py') docstyle_PYTHON3_default = DocstyleDefinition.load( 'PYTHON3', 'default') docstyle_PYTHON3_doxygen = DocstyleDefinition.load( 'PYTHON3', 'doxygen') expected = ( DocumentationComment(('\n' 'Module description.\n' '\n' 'Some more foobar-like text.\n'), docstyle_PYTHON3_default, '', docstyle_PYTHON3_default.markers[0], TextPosition(1, 1)), DocumentationComment(('\n' 'A nice and neat way of documenting code.\n' ':param radius: The explosion radius.\n'), docstyle_PYTHON3_default, ' ' * 4, docstyle_PYTHON3_default.markers[0], TextPosition(8, 5)), DocumentationComment('\nA function that returns 55.\n', docstyle_PYTHON3_default, ' ' * 8, docstyle_PYTHON3_default.markers[0], TextPosition(13, 9)), DocumentationComment( ('\n' 'Docstring with layouted text.\n' '\n' ' layouts inside docs are preserved for these ' 'documentation styles.\n' 'this is intended.\n'), docstyle_PYTHON3_default, '', docstyle_PYTHON3_default.markers[0], TextPosition(19, 1)), DocumentationComment( (' Docstring directly besides triple quotes.\n' ' Continues here. '), docstyle_PYTHON3_default, '', docstyle_PYTHON3_default.markers[0], TextPosition(26, 1)), DocumentationComment(('super\n' ' nicely\n' 'short'), docstyle_PYTHON3_default, '', docstyle_PYTHON3_default.markers[0], TextPosition(40, 1)), DocumentationComment(('\n' 'A bad indented docstring\n' ' Improper indentation.\n' ':param impact: The force of Impact.\n'), docstyle_PYTHON3_default, ' ' * 4, docstyle_PYTHON3_default.markers[0], TextPosition(45, 5)), ) self.assertEqual( tuple(extract_documentation(data, 'PYTHON3', 'default')), expected) # Change only the docstyle in expected results. expected = list( DocumentationComment(r.documentation, docstyle_PYTHON3_doxygen, r.indent, r.marker, r.position) for r in expected) expected.insert( 5, DocumentationComment( (' Alternate documentation style in doxygen.\n' ' Subtext\n' ' More subtext (not correctly aligned)\n' ' sub-sub-text\n' '\n'), docstyle_PYTHON3_doxygen, '', docstyle_PYTHON3_doxygen.markers[1], TextPosition(30, 1))) self.assertEqual( list(extract_documentation(data, 'PYTHON3', 'doxygen')), expected)