Skip to content
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 15, 2017
1 parent 0a25352 commit 112fa7d4875e9279b4719fc571f505f1f496787b
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 0 deletions.
@@ -0,0 +1,116 @@
import shutil

from coalib.bearlib.languages.documentation.DocumentationComment import (
from coalib.bearlib.languages.documentation.DocstyleDefinition import (
from coalib.bearlib.languages.documentation.DocBaseClass import (
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
REQUIREMENTS = {PipRequirement('language-check', '1.0')}
AUTHORS = {'The coala developers'}
CAN_FIX = {'Documentation', 'Spelling', 'Grammar'}

def check_prerequisites(cls):
if shutil.which('java') is None:
return 'java is not installed.'
from language_check import LanguageTool, correct
return True
except ImportError: # pragma: no cover
return 'Please install the `language-check` pip package.'

def process_documentation(self,
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.
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)

metadata = iter(parsed)

new_metadata = []
for comment in metadata:
matches = tool.check(comment.desc)
new_desc = correct(comment.desc, matches)

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:
LanguageTool currently supports more than 25 languages. For further
information, visit:
: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(
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
: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.
Returns an assembled docstring.
docstring = dedent('"""\n'
':param xyz:{}'
return docstring.format(main_desc,

def test(test_data, expected_data, optional_setting=None):
def test_function(self):
arguments = {'language': 'python', 'docstyle': 'default'}
if optional_setting:
section = Section('test-section')
for key, value in arguments.items():
section[key] = value

with execute_bear(
DocGrammarBear(section, Queue()),
**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

class DocGrammarBearTest(unittest.TestCase):

def test_check_prerequisites(self):
_shutil_which = shutil.which
shutil.which = lambda *args, **kwargs: None
'java is not installed.')

shutil.which = lambda *args, **kwargs: 'path/to/java'
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 '

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',
make_docstring(main_desc='sentence starting with lowercase.\n',
param_desc='dummy description.\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')(
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.