Permalink
Browse files

Add DocGrammarBear for docstrings

This bear checks for spellings and grammatical mistakes
on the descriptions of documentation comments.
  • Loading branch information...
damngamerz committed Aug 12, 2017
1 parent 0a25352 commit 112fa7d4875e9279b4719fc571f505f1f496787b
Showing with 287 additions and 0 deletions.
  1. +116 −0 bears/documentation/DocGrammarBear.py
  2. +171 −0 tests/documentation/DocGrammarBearTest.py
@@ -0,0 +1,116 @@
import shutil
from coalib.bearlib.languages.documentation.DocumentationComment import (
DocumentationComment)
from coalib.bearlib.languages.documentation.DocstyleDefinition import (
DocstyleDefinition)
from coalib.bearlib.languages.documentation.DocBaseClass import (
DocBaseClass)
from dependency_management.requirements.PipRequirement import PipRequirement
from coalib.bears.LocalBear import LocalBear
from coalib.results.Result import Result
from coalib.settings.Setting import typed_list
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 = {'coala-devel@googlegroups.com'}
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})
@@ -0,0 +1,171 @@
from queue import Queue
from textwrap import dedent
import unittest
import shutil
import platform
from coalib.results.Diff import Diff
from coalib.settings.Section import Section
from coalib.testing.LocalBearTestHelper import execute_bear
from coalib.testing.BearTestHelper import generate_skip_decorator
from bears.documentation.DocGrammarBear import DocGrammarBear
def make_docstring(main_desc: str='',
param_desc: str='',
return_desc: str=''):
"""
This assembles a simple docstring having a main description, a parameter
description and a return description. This makes the tests readibilty
clean.
:param main_desc:
Contains the main description of the docstring.
:param param_desc:
Contatins the parameter description of the docstring.
:param return_desc:
Contains the return description of the docstring.
:return:
Returns an assembled docstring.
"""
docstring = dedent('"""\n'
'{}'
'\n'
':param xyz:{}'
':return:{}'
'"""\n')
return docstring.format(main_desc,
param_desc,
return_desc).splitlines(True)
def test(test_data, expected_data, optional_setting=None):
def test_function(self):
arguments = {'language': 'python', 'docstyle': 'default'}
if optional_setting:
arguments.update(optional_setting)
section = Section('test-section')
for key, value in arguments.items():
section[key] = value
with execute_bear(
DocGrammarBear(section, Queue()),
'dummy_filename',
test_data,
**arguments) as results:
diff = Diff(test_data)
for result in results:
# Only the given test file should contain a patch.
self.assertEqual(len(result.diffs), 1)
diff += result.diffs['dummy_filename']
self.assertEqual(expected_data, diff.modified)
return test_function
@generate_skip_decorator(DocGrammarBear)
class DocGrammarBearTest(unittest.TestCase):
def test_check_prerequisites(self):
_shutil_which = shutil.which
try:
shutil.which = lambda *args, **kwargs: None
self.assertEqual(DocGrammarBear.check_prerequisites(),
'java is not installed.')
shutil.which = lambda *args, **kwargs: 'path/to/java'
self.assertTrue(DocGrammarBear.check_prerequisites())
finally:
shutil.which = _shutil_which
test_spelling = test(
make_docstring(main_desc='Thiss is main descrpton.\n'),
make_docstring(main_desc='This is main description.\n'))
test_capitalize_sentence_start = test(
make_docstring(main_desc='this sentence starts with small letter\n'),
make_docstring(main_desc='This sentence starts with small letter\n'))
test_extra_whitespace = test(
make_docstring(main_desc='This sentence has extra white spaces\n'),
make_docstring(main_desc='This sentence has extra white spaces\n'))
test_apostrophe_comma = test(
make_docstring(main_desc='This sentence doesnt have an apostrophe\n'),
make_docstring(main_desc='This sentence doesn\'t have an '
'apostrophe\n'))
correct_docstring = make_docstring(
main_desc='This documentation has correct grammar.\n',
param_desc='Dummy description.\n',
return_desc='Return Nothing.\n')
test_correct_grammar = test(correct_docstring, correct_docstring)
test_disable_setting_UPPERCASE_SENTENCE_START = test(
make_docstring(main_desc='sentence starting with lowercase.\n',
param_desc='dummy description.\n',
return_desc='Nothing.\n'),
make_docstring(main_desc='sentence starting with lowercase.\n',
param_desc='dummy description.\n',
return_desc='Nothing.\n'),
{'languagetool_disable_rules': 'UPPERCASE_SENTENCE_START'})
# FRENCH_WHITESPACE adds a unicode space if it finds empty strings.
# which was breaking this test case.
test_language_french = unittest.skipIf(
platform.system() == 'Windows',
'language-check fails for different locale on windows')(
test(
make_docstring(main_desc='il monte en haut si il veut.\n'),
make_docstring(main_desc='Il monte s’il veut.\n'),
{'locale': 'fr',
'languagetool_disable_rules': 'FRENCH_WHITESPACE'}))
# explicit language test cases to check the breakage of DocGrammarBear.
test_java_explicit = test([
'class Square {\n',
' /**\n',
' * Returnss Area of a square.\n',
' *\n',
' *@param side side of squaree\n',
' *@return area of a square\n',
' */\n',
' public int Area(int side) {\n',
' return side * side;\n'
' }\n',
'}'], [
'class Square {\n',
' /**\n',
' * Returns Area of a square.\n',
' *\n',
' *@param side Side of square\n',
' *@return Area of a square\n',
' */\n',
' public int Area(int side) {\n',
' return side * side;\n'
' }\n',
'}'],
{'language': 'java'})
test_python_explicit = test([
'def improper_grammar(param1):\n',
' """\n',
' Documntation contains gramatical mistakess.DocGrammarBear\n',
' doesnt check for style.\n',
' :param param1: Contains parameter descrption.\n',
' :return: returns nothing. first letter small.\n',
' """\n',
' return None'], [
'def improper_grammar(param1):\n',
' """\n',
' Documentation contains grammatical mistakes. DocGrammarBear\n',
' doesn\'t check for style.\n',
' :param param1: Contains parameter description.\n',
' :return: Returns nothing. First letter small.\n',
' """\n',
' return None'])

0 comments on commit 112fa7d

Please sign in to comment.