def test_get_available_definitions_on_wrong_files(self, confparser_mock, iglob_mock): # Test the case when a coalang was provided with uppercase letters. confparser_instance_mock = confparser_mock.return_value confparser_instance_mock.parse.return_value = ["X"] iglob_mock.return_value = ['some/CUSTOMSTYLE.coalang', 'SOME/xlang.coalang'] self.assertEqual(list(DocstyleDefinition.get_available_definitions()), [('xlang', 'x')])
def test_get_available_definitions_on_wrong_files(self, confparser_mock, iglob_mock): # Test the case when a coalang was provided with uppercase letters. confparser_instance_mock = confparser_mock.return_value confparser_instance_mock.parse.return_value = ['X'] iglob_mock.return_value = [ 'some/CUSTOMSTYLE.coalang', 'SOME/xlang.coalang' ] self.assertEqual(list(DocstyleDefinition.get_available_definitions()), [('xlang', 'x')])
def test_get_available_definitions(self): # Test if the basic supported docstyle-language pairs exist. expected = {('default', 'python'), ('default', 'python3'), ('default', 'java'), ('doxygen', 'c'), ('doxygen', 'cpp'), ('doxygen', 'cs'), ('doxygen', 'fortran'), ('doxygen', 'java'), ('doxygen', 'python'), ('doxygen', 'python3'), ('doxygen', 'tcl'), ('doxygen', 'vhdl'), ('doxygen', 'php'), ('doxygen', 'objective-c')} real = set(DocstyleDefinition.get_available_definitions()) self.assertTrue(expected.issubset(real))
def test_get_available_definitions(self): # Test if the basic supported docstyle-language pairs exist. expected = {('default', 'python'), ('default', 'python3'), ('default', 'java'), ('doxygen', 'c'), ('doxygen', 'cpp'), ('doxygen', 'cs'), ('doxygen', 'fortran'), ('doxygen', 'java'), ('doxygen', 'python'), ('doxygen', 'python3'), ('doxygen', 'tcl'), ('doxygen', 'vhdl'), ('doxygen', 'php'), ('doxygen', 'objective-c')} real = set(DocstyleDefinition.get_available_definitions()) self.assertTrue(expected.issubset(real))
class DocumentationStyleBear(DocBaseClass, LocalBear): LANGUAGES = { language for docstyle, language in DocstyleDefinition.get_available_definitions() } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/7sfk3i9oxs1ixg2ncsu3pym0u' CAN_DETECT = {'Documentation'} CAN_FIX = {'Documentation'} 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 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): if isinstance(doc_comment, MalformedComment): yield Result.from_values(origin=self, message=doc_comment.message, file=filename, line=doc_comment.line + 1) else: (new_comment, warning_desc) = self.process_documentation( doc_comment, allow_missing_func_desc, indent_size, expand_one_liners) # Cache cleared so a fresh docstring is assembled doc_comment.assemble.cache_clear() # Assembled docstring check, because paddings are only # amended after assemble() if new_comment.assemble() != doc_comment.assemble(): # 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})
class DocumentationStyleBear(LocalBear): LANGUAGES = { language for docstyle, language in DocstyleDefinition.get_available_definitions() } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/7sfk3i9oxs1ixg2ncsu3pym0u' CAN_DETECT = {'Documentation'} CAN_FIX = {'Documentation'} 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})
class DocGrammarBear(DocBaseClass, LocalBear): LANGUAGES = { language for docstyle, language in DocstyleDefinition.get_available_definitions() } REQUIREMENTS = {PipRequirement('language-check', '1.0')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/132564' CAN_FIX = {'Documentation', 'Spelling', 'Grammar'} @classmethod def check_prerequisites(cls): if shutil.which('java') is None: return 'java is not installed.' else: try: from language_check import LanguageTool, correct LanguageTool correct return True except ImportError: # pragma: no cover return 'Please install the `language-check` pip package.' def process_documentation(self, parsed, locale, languagetool_disable_rules): """ This fixes the parsed documentation comment by applying spell checking and grammatic rules via LanguageTool. :param parsed: Contains parsed documentation comment. :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. :return: A tuple of fixed parsed documentation comment and warning_desc. """ # Defer import so the check_prerequisites can be run without # language_check being there. from language_check import LanguageTool, correct tool = LanguageTool(locale) tool.disabled.update(languagetool_disable_rules) metadata = iter(parsed) new_metadata = [] for comment in metadata: matches = tool.check(comment.desc) new_desc = correct(comment.desc, matches) new_metadata.append(comment._replace(desc=new_desc)) return (new_metadata, 'Documentation has invalid Grammar/Spelling') 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})