diff --git a/.coafile b/.coafile index c5f6f7a747..5e226f3b84 100644 --- a/.coafile +++ b/.coafile @@ -35,5 +35,7 @@ max_lines_per_file = 1000 enabled = False bears = KeywordBear -ci_keywords, keywords_case_insensitive = \#TODO, \# TODO, \#FIXME, \# FIXME +# Note that the ci_keywords and cs_keywords are only used here because coala +# 0.8.x (current stable release) needs them. +ci_keywords, keywords = \#TODO, \# TODO, \#FIXME, \# FIXME cs_keywords = diff --git a/Gemfile b/Gemfile index bacf1ba3ed..48d3cb7d45 100644 --- a/Gemfile +++ b/Gemfile @@ -5,3 +5,4 @@ gem "rubocop" gem "sqlint" gem 'scss_lint', require: false# require flag is necessary https://github.com/brigade/scss-lint#installation gem "reek" +gem "puppet-lint" diff --git a/bears/configfiles/PuppetLintBear.py b/bears/configfiles/PuppetLintBear.py new file mode 100644 index 0000000000..86a4210c2d --- /dev/null +++ b/bears/configfiles/PuppetLintBear.py @@ -0,0 +1,26 @@ +from coalib.bearlib.abstractions.Linter import linter +from coalib.bears.requirements.GemRequirement import GemRequirement + + +@linter(executable='puppet-lint', + output_format='regex', + output_regex=r'(?P\d+):(?P\d+):' + r'(?Pwarning|error):(?P.+)') +class PuppetLintBear: + ''' + Check and correct puppet configuration files using ``puppet-lint``. + + See for details about the tool. + ''' + + LANGUAGES = {"Puppet"} + REQUIREMENTS = {GemRequirement('puppet-lint', '2')} + AUTHORS = {'The coala developers'} + AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} + LICENSE = 'AGPL-3.0' + CAN_FIX = {'Syntax'} + + @staticmethod + def create_arguments(filename, file, config_file): + return ('--log-format', "%{line}:%{column}:%{kind}:%{message}", + filename) diff --git a/bears/css/CSSLintBear.py b/bears/css/CSSLintBear.py index e2b614b077..61b9dce121 100644 --- a/bears/css/CSSLintBear.py +++ b/bears/css/CSSLintBear.py @@ -13,7 +13,7 @@ class CSSLintBear: problems or inefficiencies. """ LANGUAGES = {"CSS"} - REQUIREMENTS = {NpmRequirement('csslint', '0')} + REQUIREMENTS = {NpmRequirement('csslint', '1')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} LICENSE = 'AGPL-3.0' diff --git a/bears/general/KeywordBear.py b/bears/general/KeywordBear.py index 6f4aeb6d6c..d12b26b357 100644 --- a/bears/general/KeywordBear.py +++ b/bears/general/KeywordBear.py @@ -1,3 +1,5 @@ +import re + from coalib.bearlib import deprecate_settings from coalib.bears.LocalBear import LocalBear from coalib.results.Result import RESULT_SEVERITY, Result @@ -10,53 +12,28 @@ class KeywordBear(LocalBear): LICENSE = 'AGPL-3.0' CAN_DETECT = {'Documentation'} - @deprecate_settings(keywords_case_sensitive='cs_keywords') - def run(self, - filename, - file, - keywords_case_insensitive: list, - keywords_case_sensitive: list=()): + @deprecate_settings(keywords='ci_keywords') + def run(self, filename, file, keywords: list): ''' Checks the code files for given keywords. - :param keywords_case_insensitive: + :param keywords: A list of keywords to search for (case insensitive). Usual examples are TODO and FIXME. - :param keywords_case_sensitive: - A list of keywords to search for (case sensitive). ''' - results = list() - - for i in range(len(keywords_case_insensitive)): - keywords_case_insensitive[i] = keywords_case_insensitive[i].lower() + keywords_regex = re.compile( + '(' + '|'.join(re.escape(key) for key in keywords) + ')', + re.IGNORECASE) for line_number, line in enumerate(file): - for keyword in keywords_case_sensitive: - results += self.check_line_for_keyword(line, - filename, - line_number, - keyword) - - for keyword in keywords_case_insensitive: - results += self.check_line_for_keyword(line.lower(), - filename, - line_number, - keyword) - - return results - - def check_line_for_keyword(self, line, filename, line_number, keyword): - pos = line.find(keyword) - if pos != -1: - return [Result.from_values( - origin=self, - message="The line contains the keyword `{}`." - .format(keyword), - file=filename, - line=line_number+1, - column=pos+1, - end_line=line_number+1, - end_column=pos+len(keyword)+1, - severity=RESULT_SEVERITY.INFO)] - - return [] + for keyword in keywords_regex.finditer(line): + yield Result.from_values( + origin=self, + message="The line contains the keyword '{}'." + .format(keyword.group()), + file=filename, + line=line_number + 1, + column=keyword.start() + 1, + end_line=line_number + 1, + end_column=keyword.end() + 1, + severity=RESULT_SEVERITY.INFO) diff --git a/bears/js/ESLintBear.py b/bears/js/ESLintBear.py index a8075e6df2..d5b7a2273e 100644 --- a/bears/js/ESLintBear.py +++ b/bears/js/ESLintBear.py @@ -17,7 +17,7 @@ class ESLintBear: """ LANGUAGES = {"JavaScript", "JSX"} - REQUIREMENTS = {NpmRequirement('eslint', '2')} + REQUIREMENTS = {NpmRequirement('eslint', '3')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} LICENSE = 'AGPL-3.0' @@ -48,7 +48,7 @@ def generate_config(filename, file): return '{"extends": "eslint:recommended"}' def process_output(self, output, filename, file): - if not file: + if not file or not output: return output = json.loads(output) diff --git a/bears/js/JSHintBear.py b/bears/js/JSHintBear.py index 60ae485649..1c0e75209c 100644 --- a/bears/js/JSHintBear.py +++ b/bears/js/JSHintBear.py @@ -42,7 +42,11 @@ class JSHintBear: CAN_DETECT = {'Formatting', 'Syntax', 'Complexity', 'Unused Code'} @staticmethod - @deprecate_settings(cyclomatic_complexity='maxcomplexity', + @deprecate_settings(es_version='use_es6_syntax', + javascript_strictness=( + "allow_global_strict", + lambda x: "global" if x else True), + cyclomatic_complexity='maxcomplexity', allow_unused_variables=('prohibit_unused', negate), max_parameters='maxparams', allow_missing_semicolon='allow_missing_semicol', @@ -89,7 +93,6 @@ def generate_config(filename, file, allow_debugger: bool=False, allow_assignment_comparisions: bool=False, allow_eval: bool=False, - allow_global_strict: bool=False, allow_increment: bool=False, allow_proto: bool=False, allow_scripturls: bool=False, @@ -97,12 +100,12 @@ def generate_config(filename, file, allow_this_statements: bool=False, allow_with_statements: bool=False, use_mozilla_extension: bool=False, + javascript_strictness: bool_or_str=True, allow_noyield: bool=False, allow_eqnull: bool=False, allow_last_semicolon: bool=False, allow_func_in_loop: bool=False, allow_expr_in_assignments: bool=False, - use_es6_syntax: bool=False, use_es3_array: bool=False, environment_mootools: bool=False, environment_couch: bool=False, @@ -132,7 +135,7 @@ def generate_config(filename, file, allow_variable_shadowing: bool_or_str=False, allow_unused_variables: bool_or_str=False, allow_latedef: bool_or_str=False, - es_version: int=5, + es_version: bool_or_int=5, jshint_config: str=""): """ :param allow_bitwise_operators: @@ -184,9 +187,6 @@ def generate_config(filename, file, :param allow_eval: This options suppresses warnings about the use of ``eval`` function. - :param allow_global_strict: - This option suppresses warnings about the use of global strict - mode. :param allow_increment: This option suppresses warnings about the use of unary increment and decrement operators. @@ -209,6 +209,14 @@ def generate_config(filename, file, :param use_mozilla_extension: This options tells JSHint that your code uses Mozilla JavaScript extensions. + :param javascript_strictness: + Determines what sort of strictness to use in the JavaScript code. + The possible options are: + + - "global" - there must be a ``"use strict";`` at global level + - "implied" - lint the code as if there is a ``"use strict";`` + - "False" - disable warnings about strict mode + - "True" - there must be a ``"use strict";`` at function level :param allow_noyield: This option suppresses warnings about generator functions with no ``yield`` statement in them. @@ -225,8 +233,6 @@ def generate_config(filename, file, :param use_es3_array: This option tells JSHintBear ES3 array elision elements, or empty elements are used. - :param use_es3_array: - This option tells JSHint ECMAScript 6 specific syntax is used. :param environment_mootools: This option defines globals exposed by the Mootools. :param environment_couch: @@ -308,6 +314,12 @@ def generate_config(filename, file, This option is used to specify the ECMAScript version to which the code must adhere to. """ + # Assume that when es_version is bool, it is intended for the + # deprecated use_es6_version + if es_version is True: + es_version = 6 + elif es_version is False: + es_version = 5 if not jshint_config: options = {"bitwise": not allow_bitwise_operators, "freeze": not allow_prototype_overwrite, @@ -329,7 +341,7 @@ def generate_config(filename, file, "debug": allow_debugger, "boss": allow_assignment_comparisions, "evil": allow_eval, - "globalstrict": allow_global_strict, + "strict": javascript_strictness, "plusplus": allow_increment, "proto": allow_proto, "scripturl": allow_scripturls, @@ -342,7 +354,6 @@ def generate_config(filename, file, "lastsemic": allow_last_semicolon, "loopfunc": allow_func_in_loop, "expr": allow_expr_in_assignments, - "esnext": use_es6_syntax, "elision": use_es3_array, "mootools": environment_mootools, "couch": environment_couch, diff --git a/bears/markdown/MarkdownBear.py b/bears/markdown/MarkdownBear.py index e770ddd15f..a54b93ac8d 100644 --- a/bears/markdown/MarkdownBear.py +++ b/bears/markdown/MarkdownBear.py @@ -17,7 +17,7 @@ class MarkdownBear: """ LANGUAGES = {"Markdown"} - REQUIREMENTS = {NpmRequirement('remark', '3')} + REQUIREMENTS = {NpmRequirement('remark-cli', '2')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} LICENSE = 'AGPL-3.0' diff --git a/bears/natural_language/AlexBear.py b/bears/natural_language/AlexBear.py index 3842e0fe47..24d815a5f5 100644 --- a/bears/natural_language/AlexBear.py +++ b/bears/natural_language/AlexBear.py @@ -1,3 +1,6 @@ +import subprocess +import sys + from coalib.bearlib.abstractions.Linter import linter from coalib.bears.requirements.NpmRequirement import NpmRequirement @@ -13,11 +16,35 @@ class AlexBear: writing. """ LANGUAGES = {"Natural Language"} - REQUIREMENTS = {NpmRequirement('alex', '2')} + REQUIREMENTS = {NpmRequirement('alex', '3')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} LICENSE = 'AGPL-3.0' + @classmethod + def check_prerequisites(cls): + parent_prereqs = super().check_prerequisites() + if parent_prereqs is not True: # pragma: no cover + return parent_prereqs + + incorrect_pkg_msg = ( + "Please ensure that the package that has been installed is the " + "one to 'Catch insensitive, inconsiderate writing'. This can be " + "verified by running `alex --help` and seeing what it does.") + try: + output = subprocess.check_output(("alex", "--help"), + stderr=subprocess.STDOUT) + except (OSError, subprocess.CalledProcessError): + return ("The `alex` package could not be verified. " + + incorrect_pkg_msg) + else: + output = output.decode(sys.getfilesystemencoding()) + if "Catch insensitive, inconsiderate writing" in output: + return True + else: + return ("The `alex` package that's been installed seems to " + "be incorrect. " + incorrect_pkg_msg) + @staticmethod def create_arguments(filename, file, config_file): return filename, diff --git a/bears/natural_language/SpellCheckBear.py b/bears/natural_language/SpellCheckBear.py new file mode 100644 index 0000000000..4313645770 --- /dev/null +++ b/bears/natural_language/SpellCheckBear.py @@ -0,0 +1,24 @@ +from coalib.bearlib.abstractions.Linter import linter +from coalib.bears.requirements.PipRequirement import PipRequirement + + +@linter(executable='scspell', + use_stderr=True, + output_format='regex', + output_regex=r'(?P.*):(?P.\d*):\s*(?P.*)') +class SpellCheckBear: + """ + Lints files to check for incorrect spellings using ``scspell``. + + See for more information. + """ + LANGUAGES = {"Natural Language"} + REQUIREMENTS = {PipRequirement('scspell3k', '2.0')} + AUTHORS = {'The coala developers'} + AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} + LICENSE = 'AGPL-3.0' + CAN_DETECT = {'Spelling'} + + @staticmethod + def create_arguments(filename, file, config_file): + return '--report-only', filename diff --git a/bears/python/YapfBear.py b/bears/python/YapfBear.py index bdde5b0c44..03d686596b 100644 --- a/bears/python/YapfBear.py +++ b/bears/python/YapfBear.py @@ -44,7 +44,8 @@ def run(self, filename, file, split_before_logical_operator: bool=False, split_before_named_assigns: bool=True, use_spaces: bool=True, - based_on_style: str='pep8'): + based_on_style: str='pep8', + prefer_line_break_after_opening_bracket: bool=True): """ :param max_line_length: Maximum number of characters for a line. @@ -112,6 +113,9 @@ def run(self, filename, file, Uses spaces for indentation. :param based_on_style: The formatting style to be used as reference. + :param prefer_line_break_after_opening_bracket: + If True, splitting right after a open bracket will not be + preferred. """ if not file: # Yapf cannot handle zero-byte files well, and adds a redundent @@ -141,14 +145,28 @@ def run(self, filename, file, space_between_ending_comma_and_closing_bracket= \ {space_between_ending_comma_and_closing_bracket} """ - options += 'use_tabs = ' + str(not use_spaces) + options += 'use_tabs = ' + str(not use_spaces) + "\n" + options += ('split_penalty_after_opening_bracket = ' + + ('30' if prefer_line_break_after_opening_bracket + else '0') + "\n") options = options.format(**locals()) - with prepare_file(options.splitlines(keepends=True), - None) as (file_, fname): - corrected = FormatFile(filename, - style_config=fname, - verify=False)[0].splitlines(True) + try: + with prepare_file(options.splitlines(keepends=True), + None) as (file_, fname): + corrected = FormatFile(filename, + style_config=fname, + verify=False)[0].splitlines(True) + except SyntaxError as err: + if isinstance(err, IndentationError): + error_type = "indentation errors (" + err.args[0] + ')' + else: + error_type = "syntax errors" + yield Result.from_values( + self, + "The code cannot be parsed due to {0}.".format(error_type), + filename, line=err.lineno, column=err.offset) + return diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, diff --git a/bears/vcs/git/GitCommitBear.py b/bears/vcs/git/GitCommitBear.py index 58b24842fe..f2a1fe92e1 100644 --- a/bears/vcs/git/GitCommitBear.py +++ b/bears/vcs/git/GitCommitBear.py @@ -10,6 +10,7 @@ from coalib.results.Result import Result from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY from coalib.settings.FunctionMetadata import FunctionMetadata +from coalib.settings.Setting import typed_list class GitCommitBear(GlobalBear): @@ -170,15 +171,18 @@ def check_imperative(self, paragraph): def check_body(self, body, body_line_length: int=72, - force_body: bool=False): + force_body: bool=False, + ignore_length_regex: typed_list(str)=()): """ Checks the given commit body. - :param body: The commit body splitted by lines. - :param body_line_length: The maximum line-length of the body. The - newline character at each line end does not - count to the length. - :param force_body: Whether a body shall exist or not. + :param body: The commit body splitted by lines. + :param body_line_length: The maximum line-length of the body. The + newline character at each line end does not + count to the length. + :param force_body: Whether a body shall exist or not. + :param ignore_length_regex: Lines matching each of the regular + expressions in this list will be ignored. """ if len(body) == 0: if force_body: @@ -189,5 +193,8 @@ def check_body(self, body, yield Result(self, "No newline between shortlog and body at HEAD.") return - if any(len(line) > body_line_length for line in body[1:]): + ignore_regexes = [re.compile(regex) for regex in ignore_length_regex] + if any((len(line) > body_line_length and + not any(regex.search(line) for regex in ignore_regexes)) + for line in body[1:]): yield Result(self, "Body of HEAD commit contains too long lines.") diff --git a/package.json b/package.json index 3d827f4387..740a0a06f2 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,18 @@ "name": "coala-bears", "version":"0.8.0", "dependencies": { - "alex": "~2", + "alex": "~3", "autoprefixer": "~6", "bootlint": "~0", "coffeelint": "~1", "complexity-report": "~2.0.0-alpha", - "csslint": "~0", + "csslint": "~1", "dockerfile_lint": "~0", - "eslint": "~2", + "eslint": "~3", "happiness":"~7.1.2", "jshint": "~2", "postcss-cli": "~2", - "remark": "~3", + "remark-cli": "~2", "tslint": "~3", "ramllint": "~1.2.2", "write-good": "~0.9.1" diff --git a/requirements.txt b/requirements.txt index 0c7b21534b..558d442e12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ # Use >= for development versions so that source builds always work coala>=0.8.0 -setuptools>=19.2 munkres3==1.* pylint==1.* language-check==0.8.* @@ -23,11 +22,10 @@ cppclean==0.9.* pydocstyle==1.* cmakelint==1.* vim-vint==0.3.* -# Stable version 3.2 is problematic in Windows. -# Use >=3.3,==3.* once nltk-3.3 is released. nltk==3.2.* appdirs==1.* pyyaml==3.* vulture==0.10.* nbformat>=4.* pyflakes==1.2.* # Although we don't need this directly, solves a dep conflict +scspell3k==2.* diff --git a/tests/LocalBearTestHelper.py b/tests/LocalBearTestHelper.py index 77d75b0c9d..9dcfd8b9bb 100644 --- a/tests/LocalBearTestHelper.py +++ b/tests/LocalBearTestHelper.py @@ -89,6 +89,7 @@ def check_validity(self, msg = ("The local bear '{}' yields no result although it " "should.".format(local_bear.__class__.__name__)) self.assertNotEqual(len(bear_output), 0, msg=msg) + return bear_output def check_results(self, local_bear, diff --git a/tests/configfiles/PuppetLintBearTest.py b/tests/configfiles/PuppetLintBearTest.py new file mode 100644 index 0000000000..3776986271 --- /dev/null +++ b/tests/configfiles/PuppetLintBearTest.py @@ -0,0 +1,17 @@ +from bears.configfiles.PuppetLintBear import PuppetLintBear +from tests.LocalBearTestHelper import verify_local_bear + +good_file = """ +file { '/some.conf': + ensure => present, +} +""" + +bad_file = """ +# foo +class test::foo { } +""" + +PuppetLintBearTest = verify_local_bear(PuppetLintBear, + valid_files=(good_file,), + invalid_files=(bad_file,)) diff --git a/tests/css/CSSLintBearTest.py b/tests/css/CSSLintBearTest.py index 70cf6c53f9..7c710461d5 100644 --- a/tests/css/CSSLintBearTest.py +++ b/tests/css/CSSLintBearTest.py @@ -3,16 +3,16 @@ good_file = """ .class { - font-weight: 400; font-size: 5px; + font-weight: 400; } """ bad_file = """ .class { - font-weight: 400 - font-size: 5px; + font-size: 5px + font-weight: 400; } """ diff --git a/tests/general/KeywordBearTest.py b/tests/general/KeywordBearTest.py index b41d98f78a..d793dd72b1 100644 --- a/tests/general/KeywordBearTest.py +++ b/tests/general/KeywordBearTest.py @@ -11,11 +11,6 @@ KeywordBearTest = verify_local_bear( KeywordBear, valid_files=(test_file,), - invalid_files=("test line FIXME", - "test line todo", - "test line warNING", - "test line ERROR"), - settings={ - "keywords_case_sensitive": "FIXME, ERROR", - "keywords_case_insensitive": "todo, warning" - }) + invalid_files=("test line todo", + "test line warNING"), + settings={"keywords": "todo, warning"}) diff --git a/tests/generate_packageTest.py b/tests/generate_packageTest.py index 4b27bd950f..2d58ae20b2 100644 --- a/tests/generate_packageTest.py +++ b/tests/generate_packageTest.py @@ -9,7 +9,7 @@ create_upload_parser) -class touchTest(unittest.TestCase): +class TouchTest(unittest.TestCase): def setUp(self): if os.path.exists('TestFile.py'): @@ -24,7 +24,7 @@ def tearDown(self): os.remove('TestFile.py') -class create_file_from_templateTest(unittest.TestCase): +class CreateFileFromTemplateTest(unittest.TestCase): SUBST_FILE = os.path.join( 'tests', 'generate_package_input_files', 'substituted_file.py') @@ -42,7 +42,7 @@ def tearDown(self): os.remove(self.SUBST_FILE) -class create_file_structure_for_packagesTest(unittest.TestCase): +class CreateFileStructureForPackagesTest(unittest.TestCase): INIT_FILE_PATH = os.path.join('folder', 'Test', 'coalaTest', '__init__.py') BEAR_FILE_PATH = os.path.join('folder', 'Test', 'coalaTest', 'Test.py') @@ -57,13 +57,13 @@ def tearDown(self): shutil.rmtree('folder') -class create_upload_parserTest(unittest.TestCase): +class CreateUploadParserTest(unittest.TestCase): def test_parser(self): self.assertTrue(create_upload_parser().parse_args(['--upload']).upload) -class perform_registerTest(unittest.TestCase): +class PerformRegisterTest(unittest.TestCase): @patch('subprocess.call') def test_command(self, call_mock): @@ -74,7 +74,7 @@ def test_command(self, call_mock): 'MarkdownBear-0.8.0.dev20160623094115-py3-none-any.whl')]) -class perform_uploadTest(unittest.TestCase): +class PerformUploadTest(unittest.TestCase): @patch('subprocess.call') def test_command(self, call_mock): @@ -82,7 +82,7 @@ def test_command(self, call_mock): call_mock.assert_called_with(['twine', 'upload', './dist/*']) -class mainTest(unittest.TestCase): +class MainTest(unittest.TestCase): CSS_BEAR_SETUP_PATH = os.path.join( 'bears', 'upload', 'CSSLintBear', 'setup.py') diff --git a/tests/js/ESLintBearTest.py b/tests/js/ESLintBearTest.py index bd4766a760..2bacf4901b 100644 --- a/tests/js/ESLintBearTest.py +++ b/tests/js/ESLintBearTest.py @@ -38,3 +38,12 @@ ESLintBear, valid_files=(test_good, ''), invalid_files=(test_syntax_error, test_bad)) + +# If there is an invalid config file, the results cannot be found. So, no +# file gives a result. +ESLintBearWithUnloadablePluginTest = verify_local_bear( + ESLintBear, + valid_files=(test_bad, test_good), + invalid_files=(), + settings={"eslint_config": os.path.join(test_dir, + "eslintconfig_badplugin.json")}) diff --git a/tests/js/JSHintBearTest.py b/tests/js/JSHintBearTest.py index 1e95c996a7..85a3b4df7a 100644 --- a/tests/js/JSHintBearTest.py +++ b/tests/js/JSHintBearTest.py @@ -21,6 +21,18 @@ }()); """ +# Test strictness and ES6 +test_file4 = """ +"use strict"; + +var foo = { + bar: 1, + baz: 2 +}; +var { bar, baz } = foo; +console.log(bar, baz); +""" + jshintconfig = os.path.join(os.path.dirname(__file__), "test_files", @@ -34,7 +46,8 @@ "shadow": "False", "allow_last_semicolon": "True", "es_version": 3, - "allow_latedef": "no_func"} + "allow_latedef": "no_func", + "javascript_strictness": "False"} JSHintBearTest = verify_local_bear(JSHintBear, @@ -55,3 +68,15 @@ invalid_files=(), valid_files=(test_file3, ), settings=settings) + +JSHintBearDeprecationTest = verify_local_bear( + JSHintBear, + valid_files=(), + invalid_files=(test_file4,), + settings={"use_es6_syntax": 'False', "allow_global_strict": 'False'}) + +JSHintBearDeprecation2Test = verify_local_bear( + JSHintBear, + valid_files=(test_file4,), + invalid_files=(), + settings={"use_es6_syntax": 'True', "allow_global_strict": 'True'}) diff --git a/tests/js/test_files/eslintconfig_badplugin.json b/tests/js/test_files/eslintconfig_badplugin.json new file mode 100644 index 0000000000..868c9786c3 --- /dev/null +++ b/tests/js/test_files/eslintconfig_badplugin.json @@ -0,0 +1,14 @@ +{ + "extends": "eslint:recommended", + "plugins": ["invalid_plugin_should_throw_error"], + "rules": { + "consistent-return": 2, + "indent" : [1, 4], + "no-else-return" : 1, + "semi" : [1, "always"], + "space-unary-ops" : [2, { + "words": true, + "nonwords": true + }] + } +} diff --git a/tests/natural_language/AlexBearTest.py b/tests/natural_language/AlexBearTest.py index eb6333d708..28dccfd027 100644 --- a/tests/natural_language/AlexBearTest.py +++ b/tests/natural_language/AlexBearTest.py @@ -1,4 +1,8 @@ +import unittest +from unittest.mock import patch + from bears.natural_language.AlexBear import AlexBear +from tests.BearTestHelper import generate_skip_decorator from tests.LocalBearTestHelper import verify_local_bear good_file = "Their network looks good." @@ -9,3 +13,26 @@ AlexBearTest = verify_local_bear(AlexBear, valid_files=(good_file,), invalid_files=(bad_file,)) + + +@generate_skip_decorator(AlexBear) +@patch('bears.natural_language.AlexBear.subprocess.check_output') +class AlexBearPrereqTest(unittest.TestCase): + + def test_unverified_alex_installed(self, check_output_mock): + check_output_mock.side_effect = OSError + self.assertIn('The `alex` package could not be verified', + AlexBear.check_prerequisites()) + + def test_wrong_alex_installed(self, check_output_mock): + check_output_mock.return_value = b'Unexpected output from package' + self.assertIn("The `alex` package that's been installed seems to " + "be incorrect", + AlexBear.check_prerequisites()) + + def test_right_alex_installed(self, check_output_mock): + check_output_mock.return_value = ( + b'Some text here\n' + b' Catch insensitive, inconsiderate writing\n' + b'Usage instructions and examples here ....') + self.assertTrue(AlexBear.check_prerequisites()) diff --git a/tests/natural_language/SpellCheckBearTest.py b/tests/natural_language/SpellCheckBearTest.py new file mode 100644 index 0000000000..8ab402647a --- /dev/null +++ b/tests/natural_language/SpellCheckBearTest.py @@ -0,0 +1,17 @@ +import platform +import unittest + +from bears.natural_language.SpellCheckBear import SpellCheckBear +from tests.LocalBearTestHelper import verify_local_bear + +good_file = "This is correct spelling." + +bad_file = "tihs si surly som incoreclt speling." + + +SpellCheckLintBearTest = unittest.skipIf( + platform.system() == "Windows", + "SpellCheckBear doesn't work on windows")( + verify_local_bear(SpellCheckBear, + valid_files=(good_file,), + invalid_files=(bad_file,))) diff --git a/tests/python/YapfBearTest.py b/tests/python/YapfBearTest.py index 0b967e1f7c..ccdc88bf41 100644 --- a/tests/python/YapfBearTest.py +++ b/tests/python/YapfBearTest.py @@ -31,6 +31,23 @@ def test_eof_handling(self): self.check_validity(self.uut, ['a = 2'], valid=True) self.check_validity(self.uut, ['\n'], valid=True) + def test_invalid_python(self): + results = self.check_validity( + self.uut, ['def a():', ' b=1', ' bad indent'], valid=False) + self.assertEqual(len(results), 1, str(results)) + self.assertIn('unexpected indent', results[0].message) + + results = self.check_validity( + self.uut, ['def a():', ' b=1', '\ttab error'], valid=False) + self.assertEqual(len(results), 1, str(results)) + self.assertIn('inconsistent use of tabs and spaces in indentation', + results[0].message) + + results = self.check_validity( + self.uut, ['def a(:', ' b=1', '\ttab error'], valid=False) + self.assertEqual(len(results), 1, str(results)) + self.assertIn('syntax errors', results[0].message) + def test_valid_python_2(self): self.check_validity(self.uut, ['print 1\n'], valid=True) diff --git a/tests/vcs/git/GitCommitBearTest.py b/tests/vcs/git/GitCommitBearTest.py index 01b279f490..9d4082a79c 100644 --- a/tests/vcs/git/GitCommitBearTest.py +++ b/tests/vcs/git/GitCommitBearTest.py @@ -44,6 +44,16 @@ def run_uut(self, *args, **kwargs): """ return list(result.message for result in self.uut.run(*args, **kwargs)) + def assert_no_msgs(self): + """ + Assert that there are no messages in the message queue of the bear, and + show the messages in the failure messgae if it is not empty. + """ + self.assertTrue( + self.msg_queue.empty(), + "Expected no messages in bear message queue, but got: " + + str(list(str(i) for i in self.msg_queue.queue))) + def setUp(self): self.msg_queue = Queue() self.section = Section("") @@ -100,41 +110,41 @@ def test_git_failure(self): git_error = self.msg_queue.get().message self.assertEqual(git_error[:4], "git:") - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() def test_empty_message(self): self.git_commit("") self.assertEqual(self.run_uut(), ["HEAD commit has no message."]) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() self.assertEqual(self.run_uut(allow_empty_commit_message=True), []) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() @unittest.mock.patch("bears.vcs.git.GitCommitBear.run_shell_command", return_value=("one-liner-message\n", "")) def test_pure_oneliner_message(self, patch): self.assertEqual(self.run_uut(), []) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() def test_shortlog_checks_length(self): self.git_commit("Commit messages that nearly exceed default limit..") self.assertEqual(self.run_uut(), []) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() self.assertEqual(self.run_uut(shortlog_length=17), ["Shortlog of HEAD commit is 33 character(s) " "longer than the limit (50 > 17)."]) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() self.git_commit("Add a very long shortlog for a bad project history.") self.assertEqual(self.run_uut(), ["Shortlog of HEAD commit is 1 character(s) longer " "than the limit (51 > 50)."]) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() def test_shortlog_checks_shortlog_trailing_period(self): self.git_commit("Shortlog with dot.") @@ -213,24 +223,24 @@ def test_body_checks(self): "haaaaaands") self.assertEqual(self.run_uut(), []) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() self.git_commit("Shortlog only") self.assertEqual(self.run_uut(), []) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() # Force a body. self.git_commit("Shortlog only ...") self.assertEqual(self.run_uut(force_body=True), ["No commit message body at HEAD."]) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() # Miss a newline between shortlog and body. self.git_commit("Shortlog\nOops, body too early") self.assertEqual(self.run_uut(), ["No newline between shortlog and body at HEAD."]) - self.assertTrue(self.msg_queue.empty()) + self.assert_no_msgs() # And now too long lines. self.git_commit("Shortlog\n\n" @@ -239,6 +249,15 @@ def test_body_checks(self): "This one too, blablablablablablablablabla.") self.assertEqual(self.run_uut(body_line_length=41), ["Body of HEAD commit contains too long lines."]) + self.assert_no_msgs() + + # Allow long lines with ignore regex + self.git_commit("Shortlog\n\n" + "This line is ok.\n" + "This line is by far too long (in this case).") + self.assertEqual(self.run_uut(body_line_length=41, + ignore_length_regex=("^.*too long",)), + []) self.assertTrue(self.msg_queue.empty()) def test_different_path(self):