class RadonBear(LocalBear): LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = {PipRequirement('radon', '1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Complexity'} def run(self, filename, file, radon_ranks_info: typed_list(str) = (), radon_ranks_normal: typed_list(str) = ('C', 'D'), radon_ranks_major: typed_list(str) = ('E', 'F')): """ Uses radon to compute complexity of a given file. :param radon_ranks_info: The ranks (given by radon) to treat as severity INFO. :param radon_ranks_normal: The ranks (given by radon) to treat as severity NORMAL. :param radon_ranks_major: The ranks (given by radon) to treat as severity MAJOR. """ severity_map = { RESULT_SEVERITY.INFO: radon_ranks_info, RESULT_SEVERITY.NORMAL: radon_ranks_normal, RESULT_SEVERITY.MAJOR: radon_ranks_major } for visitor in radon.complexity.cc_visit("".join(file)): rank = radon.complexity.cc_rank(visitor.complexity) severity = None for result_severity, rank_list in severity_map.items(): if rank in rank_list: severity = result_severity if severity is None: continue col = visitor.col_offset if visitor.col_offset else None visitor_range = SourceRange.from_values(filename, visitor.lineno, col, visitor.endline) message = "{} has a cyclomatic complexity of {}".format( visitor.name, rank) yield Result(self, message, severity=severity, affected_code=(visitor_range, ))
class SpellCheckBear: """ Lints files to check for incorrect spellings using ``scspell``. See <https://pypi.python.org/pypi/scspell> for more information. """ LANGUAGES = {"Natural Language"} REQUIREMENTS = {PipRequirement('scspell3k', '2.0')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Spelling'} @staticmethod def create_arguments(filename, file, config_file): return '--report-only', filename
class YAMLLintBear: """ Check yaml code for errors and possible problems. You can read more about capabilities at <http://yamllint.readthedocs.org/en/latest/rules.html>. """ LANGUAGES = {"YAML"} REQUIREMENTS = {PipRequirement('yamllint', '1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Syntax', 'Formatting'} @staticmethod def generate_config(filename, file, document_start: bool=False): """ :param document_start: Use this rule to require or forbid the use of document start marker (---). """ yamllint_configs = { 'extends': 'default', 'rules': { 'document-start': { 'present': False } } } if document_start: yamllint_configs['rules']['document-start']['present'] = True return yaml.dump(yamllint_configs) @staticmethod def create_arguments(filename, file, config_file, yamllint_config: str=''): """ :param yamllint_config: Path to a custom configuration file. """ args = ('-f', 'parsable', filename) if yamllint_config: args += ('--config=' + yamllint_config,) else: args += ('--config-file=' + config_file,) return args
class PEP8Bear(LocalBear): LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = {PipRequirement('autopep8', '1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_FIX = {'Formatting'} @deprecate_settings(indent_size='tab_width') def run(self, filename, file, max_line_length: int = 79, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, pep_ignore: typed_list(str) = (), pep_select: typed_list(str) = (), local_pep8_config: bool = False): """ Detects and fixes PEP8 incompliant code. This bear will not change functionality of the code in any way. :param max_line_length: Maximum number of characters for a line. :param indent_size: Number of spaces per indentation level. :param pep_ignore: A list of errors/warnings to ignore. :param pep_select: A list of errors/warnings to exclusively apply. :param local_pep8_config: Set to true if autopep8 should use a config file as if run normally from this directory. """ options = { "ignore": pep_ignore, "select": pep_select, "max_line_length": max_line_length, "indent_size": indent_size } corrected = autopep8.fix_code(''.join(file), apply_config=local_pep8_config, options=options).splitlines(True) diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, "The code does not comply to PEP8.", affected_code=(diff.range(filename), ), diffs={filename: diff})
class PyFlakesBear: """ Checks Python files for errors using ``pyflakes``. See https://github.com/PyCQA/pyflakes for more info. """ LANGUAGES = {'Python', 'Python 3'} REQUIREMENTS = {PipRequirement('pyflakes', '1.3.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/92503' CAN_DETECT = {'Syntax', 'Unused Code', 'Undefined Element'} @staticmethod def create_arguments(filename, file, config_file): return filename,
class VintBear: """ Check vimscript code for possible style problems. See <https://github.com/Kuniwak/vint> for more information. """ LANGUAGES = {"VimScript"} REQUIREMENTS = {PipRequirement('vim-vint', '0.3.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Formatting'} @staticmethod def create_arguments(filename, file, config_file): return filename,
class VultureBear: """ Checks Python code for unused variables and functions using ``vulture``. See <https://bitbucket.org/jendrikseipp/vulture> for more information. """ LANGUAGES = {"Python", "Python 3"} REQUIREMENTS = {PipRequirement('happiness', '0.10.0')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/82256' CAN_DETECT = {'Unused Code'} @staticmethod def create_arguments(filename, file, config_file): return filename,
class PyLintBear: """ Checks the code with pylint. This will run pylint over each file separately. """ LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = {PipRequirement('pylint', '1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = { 'Unused Code', 'Formatting', 'Duplication', 'Security', 'Syntax' } @staticmethod def create_arguments(filename, file, config_file, pylint_disable: typed_list(str) = None, pylint_enable: typed_list(str) = None, pylint_cli_options: str = "", pylint_rcfile: str = ""): """ :param pylint_disable: Disable the message, report, category or checker with the given id(s). :param pylint_enable: Enable the message, report, category or checker with the given id(s). :param pylint_cli_options: Any command line options you wish to be passed to pylint. :param pylint_rcfile: The rcfile for PyLint. """ args = ('--reports=n', '--persistent=n', '--msg-template="L{line}C{column}: {msg_id} - {msg}"') if pylint_disable: args += ('--disable=' + ','.join(pylint_disable), ) if pylint_enable: args += ('--enable=' + ','.join(pylint_enable), ) if pylint_cli_options: args += tuple(shlex.split(pylint_cli_options)) if pylint_rcfile: args += ('--rcfile=' + pylint_rcfile, ) else: args += ('--rcfile=' + os.devnull, ) return args + (filename, )
class CPPCleanBear: """ Find problems in C++ source code that slow down development in large code bases. This includes finding unused code, among other features. Read more about available routines at <https://github.com/myint/cppclean#features>. """ LANGUAGES = {'C++'} REQUIREMENTS = {PipRequirement('cppclean', '0.12')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Smell', 'Unused Code', 'Security'} @staticmethod def create_arguments(filename, file, config_file): return filename,
class PyCommentedCodeBear(LocalBear): LANGUAGES = {'Python', 'Python 2', 'Python 3'} REQUIREMENTS = {PipRequirement('eradicate', '0.1')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Commented Code'} def run(self, filename, file): """ Detects commented out source code in Python. """ corrected = tuple(eradicate.filter_commented_out_code(''.join(file))) for diff in Diff.from_string_arrays(file, corrected).split_diff(): yield Result(self, 'This file contains commented out source code.', affected_code=(diff.range(filename), ), diffs={filename: diff})
class PycodestyleBear: """ A wrapper for the tool ``pycodestyle`` formerly known as ``pep8``. """ LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = {PipRequirement('pycodestyle')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Formatting'} @staticmethod def create_arguments(filename, file, config_file, pycodestyle_ignore: str = "", pycodestyle_select: str = "", max_line_length: int = 79): """ :param pycodestyle_ignore: Comma separated list of errors to ignore. See ``pydocstyle`` documentation for a complete list of errors. :param pycodestyle_select: Comma separated list of errors to detect. If given only these errors are going to be detected. See ``pydocstyle`` documentation for a complete list of errors. :param max_line_length: Limit lines to this length. """ arguments = [r"--format='%(row)d %(col)d %(code)s %(text)s'"] if pycodestyle_ignore: arguments.append("--ignore=" + pycodestyle_ignore) if pycodestyle_select: arguments.append("--select=" + pycodestyle_select) arguments.append("--max-line-length=" + str(max_line_length)) arguments.append(filename) return arguments
class PyDocStyleBear(LocalBear, Lint): executable = 'pydocstyle' output_regex = r'(.*\.py):(?P<line>\d+) (.+):\n\s+(?P<message>.*)' use_stderr = True LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = {PipRequirement('pydocstyle', '1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Formatting', 'Documentation'} def run(self, filename, file, pydocstyle_select: typed_list(str) = (), pydocstyle_ignore: typed_list(str) = ()): ''' Checks python docstrings. :param pydocstyle_select: List of checked errors by specifying which errors to check for. :param pydocstyle_ignore: List of checked errors by specifying which errors to ignore. Note: pydocstyle_select and pydocstyle_ignore are mutually exclusive. They cannot be used together. ''' self.arguments = '{filename}' if pydocstyle_ignore and pydocstyle_select: self.err("The arguments pydocstyle_select and pydocstyle_ignore " "are both given but mutually exclusive.") return elif pydocstyle_ignore: ignore = ','.join(part.strip() for part in pydocstyle_ignore) self.arguments += " --ignore={}".format(ignore) elif pydocstyle_select: select = ','.join(part.strip() for part in pydocstyle_select) self.arguments += " --select={} ".format(select) return self.lint(filename, file)
class PyUnusedCodeBear(LocalBear): LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = {PipRequirement('autoflake', '0.6.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Unused Code'} def run(self, filename, file): """ Detects unused code. This functionality is limited to: - Unneeded pass statements. - Unneeded builtin imports. (Others might have side effects.) """ corrected = autoflake.fix_code(''.join(file)).splitlines(True) for diff in Diff.from_string_arrays(file, corrected).split_diff(): yield Result(self, "This file contains unused source code.", affected_code=(diff.range(filename),), diffs={filename: diff})
class PyDocStyleBear: """ Checks python docstrings. """ LANGUAGES = {'Python', 'Python 2', 'Python 3'} REQUIREMENTS = {PipRequirement('pydocstyle', '1.1')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Formatting', 'Documentation'} def create_arguments(self, filename, file, config_file, pydocstyle_select: typed_list(str) = (), pydocstyle_ignore: typed_list(str) = ()): """ :param pydocstyle_select: List of checked errors by specifying which errors to check for. Can't be used together with ``pydocstyle_ignore``. :param pydocstyle_ignore: List of checked errors by specifying which errors to ignore. Can't be used together with ``pydocstyle_select``. """ args = (filename, ) if pydocstyle_ignore and pydocstyle_select: self.err('The arguments pydocstyle_select and pydocstyle_ignore ' 'are both given but mutually exclusive.') return elif pydocstyle_ignore: ignore = ','.join(part.strip() for part in pydocstyle_ignore) args += ('--ignore=' + ignore, ) elif pydocstyle_select: select = ','.join(part.strip() for part in pydocstyle_select) args += ('--select=' + select, ) return args
class CMakeLintBear: """ Check CMake code for syntactical or formatting issues. For more information consult <https://github.com/richq/cmake-lint>. """ LANGUAGES = {'CMake'} REQUIREMENTS = {PipRequirement('cmakelint', '1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Syntax', 'Formatting'} @staticmethod def create_arguments(filename, file, config_file, cmakelint_config: path=""): """ :param cmakelint_config: The location of the cmakelintrc config file. """ args = () if cmakelint_config: args += ('--config=' + cmakelint_config,) return args + (filename,)
class VultureBear(GlobalBear): LANGUAGES = {'Python', 'Python 3'} REQUIREMENTS = {PipRequirement('vulture', '0.10.0')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/82256' CAN_DETECT = {'Unused Code'} EXECUTABLE = 'vulture' OUTPUT_REGEX = re.compile( r'(?P<filename>.*):(?P<line>.*):\s*(?P<message>.*)') @classmethod def check_prerequisites(cls): return ('Vulture is missing. Make sure to install it using ' '`pip3 install vulture`.' if which('vulture') is None else True) def run(self): """ Check Python code for unused variables and functions using `vulture`. See <https://bitbucket.org/jendrikseipp/vulture> for more information. """ stdout_output, _ = run_shell_command( (self.EXECUTABLE,) + tuple(filename for filename in self.file_dict.keys()), cwd=self.get_config_dir()) for match in re.finditer(self.OUTPUT_REGEX, stdout_output): groups = match.groupdict() yield Result.from_values(origin=self, message=groups['message'], file=groups['filename'], line=int(groups['line']))
class HTMLLintBear: """ Check HTML source code for invalid or misformatted code. See also <https://pypi.python.org/pypi/html-linter>. """ _html_lint = which('html_lint.py') LANGUAGES = {'HTML'} REQUIREMENTS = {PipRequirement('html-linter', '0.3')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Syntax', 'Formatting'} @staticmethod def create_arguments(filename, file, config_file, htmllint_ignore: typed_list(str)=()): """ :param htmllint_ignore: List of checkers to ignore. """ ignore = ','.join(part.strip() for part in htmllint_ignore) return HTMLLintBear._html_lint, '--disable=' + ignore, filename
class PEP8NotebookBear(LocalBear): LANGUAGES = {"Python", "Python 2", "Python 3"} REQUIREMENTS = { PipRequirement('autopep8', '1.*'), PipRequirement('nbformat', '4.*') } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/83333' CAN_FIX = {'Formatting'} def run(self, filename, file, max_line_length: int = 79, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, pep_ignore: typed_list(str) = (), pep_select: typed_list(str) = (), local_pep8_config: bool = False): """ Detects and fixes PEP8 incompliant code in Jupyter Notebooks. This bear will not change functionality of the code in any way. :param max_line_length: Maximum number of characters for a line. :param indent_size: Number of spaces per indent level. :param pep_ignore: A list of errors/warnings to ignore. :param pep_select: A list of errors/warnings to exclusively apply. :param local_pep8_config: Set to true if autopep8 should use a config file as if run normally from this directory. """ options = { "ignore": pep_ignore, "select": pep_select, "max_line_length": max_line_length, "indent_size": indent_size } notebook_node = notebook_node_from_string_list(file) cells = notebook_node['cells'] for cell in cells: if cell['cell_type'] != 'code': continue cell['source'] = autopep8_fix_code_cell(cell['source'], local_pep8_config, options) corrected = notebook_node_to_string_list(notebook_node) # If newline at eof in `file` but not in `corrected`, add # final newline character to `corrected` to make sure this difference # does not pop up in `diffs`. if file[-1].endswith('\n') and not corrected[-1].endswith('\n'): corrected[-1] += '\n' diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, "The code does not comply to PEP8.", affected_code=(diff.range(filename), ), diffs={filename: diff})
class LanguageToolBear(LocalBear): LANGUAGES = {"Natural Language"} REQUIREMENTS = { PipRequirement('guess-language-spirit', '0.5.*'), PipRequirement('language-check', '0.8.*') } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'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 return True except ImportError: # pragma: no cover return "Please install the `language-check` pip package." @deprecate_settings(language='locale') def run(self, filename, file, language: str = 'auto', languagetool_disable_rules: typed_list(str) = ()): ''' Checks the code with LanguageTool. :param language: A locale representing the language you want to have checked. If set to 'auto' the language is guessed. If the language cannot be guessed, 'en-US' is used. :param languagetool_disable_rules: List of rules to disable checks for. ''' # Defer import so the check_prerequisites can be run without # language_check being there. from language_check import LanguageTool, correct joined_text = "".join(file) language = (guess_language(joined_text) if language == 'auto' else language) language = 'en-US' if not language else language tool = LanguageTool(language, motherTongue="en_US") tool.disabled.update(languagetool_disable_rules) matches = tool.check(joined_text) for match in matches: if not match.replacements: diffs = None else: replaced = correct(joined_text, [match]).splitlines(True) diffs = {filename: Diff.from_string_arrays(file, replaced)} rule_id = match.ruleId if match.subId is not None: rule_id += '[{}]'.format(match.subId) message = match.msg + ' (' + rule_id + ')' source_range = SourceRange.from_values(filename, match.fromy + 1, match.fromx + 1, match.toy + 1, match.tox + 1) yield Result(self, message, diffs=diffs, affected_code=(source_range, ))
class PySafetyBear(LocalBear): """ Checks if any of your Python dependencies have known security issues. Data is taken from pyup.io's vulnerability database hosted at https://github.com/pyupio/safety. """ LANGUAGES = { 'Python Requirements', 'Python 2 Requirements', 'Python 3 Requirements', } AUTHORS = {'Bence Nagy'} REQUIREMENTS = {PipRequirement('safety', '0.5')} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL' CAN_DETECT = {'Security'} def run(self, filename, file): """ Checks for vulnerable package versions in requirements files. """ packages = list( Package(key=req.key, version=req.specs[0][1]) for req in self.try_parse_requirements(file) if len(req.specs) == 1 and req.specs[0][0] == '==' ) if not packages: return for vulnerability in safety.check(packages=packages): if vulnerability.is_cve: message_template = ( '{vuln.name}{vuln.spec} is vulnerable to {vuln.cve_id} ' 'and your project is using {vuln.version}.' ) else: message_template = ( '{vuln.name}{vuln.spec} is vulnerable and your project is ' 'using {vuln.version}.' ) # StopIteration should not ever happen so skipping its branch line_number, line = next( # pragma: no branch (index, line) for index, line in enumerate(file, start=1) if vulnerability.name in line ) version_spec_match = re.search(r'[=<>]+(\S+?)(?:$|\s|#)', line) source_range = SourceRange.from_values( filename, line_number, version_spec_match.start(1) + 1, line_number, version_spec_match.end(1) + 1, ) yield Result( self, message_template.format(vuln=vulnerability), additional_info=vulnerability.description, affected_code=(source_range, ), ) @staticmethod def try_parse_requirements(lines: typed_list(str)): """ Yields all package requirements parseable from the given lines. :param lines: An iterable of lines from a requirements file. """ for line in lines: try: yield from pkg_resources.parse_requirements(line) except pkg_resources.RequirementParseError: # unsupported requirement specification pass
class RuboCopBear: """ Check Ruby code for syntactic, formatting as well as semantic problems. See <https://github.com/bbatsov/rubocop#cops> for more information. """ LANGUAGES = {'Ruby'} REQUIREMENTS = { GemRequirement('rubocop'), PipRequirement('pyyaml', '3.12') } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/39241' CAN_DETECT = {'Simplification'} CAN_FIX = {'Syntax', 'Formatting'} severity_map = { 'error': RESULT_SEVERITY.MAJOR, 'warning': RESULT_SEVERITY.NORMAL, 'convention': RESULT_SEVERITY.INFO } @staticmethod def create_arguments(filename, file, config_file, rubocop_config: str = ''): # Need both stdin and filename. Explained in this comment: # https://github.com/bbatsov/rubocop/pull/2146#issuecomment-131403694 args = (filename, '--stdin', '--format=json') if rubocop_config: args += ('--config', rubocop_config) else: args += ('--config', config_file) return args @staticmethod @deprecate_settings(indent_size='tab_width', method_length_count_comments='method_count_comments', method_naming_convention='method_name_case', variable_naming_convention='variable_name_case') def generate_config(filename, file, access_modifier_indentation: str = 'indent', preferred_alias: str = 'prefer_alias', align_hash_rocket_by: str = 'key', align_colon_by: str = 'key', inspect_last_argument_hash: str = 'always_inspect', align_parameters: str = 'with_first_parameter', class_check: str = 'is_a?', comment_keywords: tuple = ('TODO', 'FIXME', 'OPTIMIZE', 'HACK', 'REVIEW'), min_if_unless_guard: int = 1, indent_size: int = 2, method_naming_convention: str = 'snake', string_literals: str = 'single_quotes', variable_naming_convention: str = 'snake', max_class_length: int = 100, class_length_count_comments: bool = False, max_module_length: int = 100, module_length_count_comments: bool = False, cyclomatic_complexity: int = 6, max_line_length: int = 79, line_length_allow_here_doc: bool = True, line_length_allow_uri: bool = True, max_method_length: int = 10, method_length_count_comments: bool = False, max_parameters: int = 5, count_keyword_args: bool = True, ignore_unused_block_args_if_empty: bool = True, allow_unused_block_keyword_arguments: bool = False, ignore_unused_method_args_if_empty: bool = True, allow_unused_method_keyword_args: bool = False): """ Not all settings added. Notable settings missing: Rails settings. :param access_modifier_indentation: Indent private/protected/public as deep as method definitions options: ``indent`` : Indent modifiers like class members. ``outdent`` : Indent modifiers one level less than class members. :param preferred_alias: Which method to use for aliasing in ruby. options : ``alias`` , ``alias_method``. :param align_hash_rocket_by: Alignment of entries using hash rocket as separator. :param align_colon_by: Alignment of entries using colon as separator. :param inspect_last_argument_hash: Select whether hashes that are the last argument in a method call should be inspected. options: ``always_inspect``, ``always_ignore``, ``ignore_implicit``, ``ignore_explicit``. :param align_parameters: Alignment of parameters in multi-line method calls. options: ``with_first_parameter``: Aligns the following lines along the same column as the first parameter. ``with_fixed_indentation``: Aligns the following lines with one level of indentation relative to the start of the line with the method call. :param class_check: How to check type of class. options: ``is_a?``, ``kind_of?``. :param comment_keywords: Checks formatting of special comments based on keywords like TODO, FIXME etc. :param min_if_unless_guard: The number of lines that are tolerable within an if/unless block, more than these lines call for the usage of a guard clause. :param indent_size: Number of spaces per indentation level. :param method_naming_convention: Case of a method's name. options: ``snake``, ``camel``. :param string_literals: Use ' or " as string literals. options: ``single_quotes``, ``double_quotes``. :param variable_naming_convention: Case of a variable's name. options: ``snake``, ``camel``. :param max_class_length: Max lines in a class. :param class_length_count_comments: Whether or not to count comments while calculating the class length. :param max_module_length: Max lines in a module. :param module_length_count_comments: Whether or not to count comments while calculating the module length. :param cyclomatic_complexity: Cyclomatic Complexity of the file. :param max_line_length: Max length of a line. :param line_length_allow_here_doc: Allow here-doc lines to be more than the max line length. :param line_length_allow_uri: To make it possible to copy or click on URIs in the code, we allow ignore long lines containing a URI to be longer than max line length. :param max_method_length: Max number of lines in a method. :param method_length_count_comments: Whether or not to count full line comments while calculating method length. :param max_parameters: Max number of parameters in parameter list. :param count_keyword_args: Count keyword args while counting all arguments? :param ignore_unused_block_args_if_empty: Ignore unused block arguments if block is empty. :param allow_unused_block_keyword_arguments: Allow unused block keyword arguments. :param ignore_unused_method_args_if_empty: Allows unused method argument if method is empty. :param allow_unused_method_keyword_args: Allows unused keyword arguments in a method. """ naming_convention = {'camel': 'camelCase', 'snake': 'snake_case'} options = { 'Style/AccessModifierIndentation': { 'EnforcedStyle': access_modifier_indentation }, 'Style/Alias': { 'EnforcedStyle': preferred_alias }, 'Style/AlignHash': { 'EnforcedHashRocketStyle': align_hash_rocket_by, 'EnforcedColonStyle': align_colon_by, 'EnforcedLastArgumentHashStyle': inspect_last_argument_hash }, 'Style/AlignParameters': { 'EnforcedStyle': align_parameters }, 'Style/ClassCheck': { 'EnforcedStyle': class_check }, 'Style/CommentAnnotation': { 'Keywords': comment_keywords }, 'Style/GuardClause': { 'MinBodyLength': min_if_unless_guard }, 'Style/IndentationWidth': { 'Width': indent_size }, 'Style/MethodName': { 'EnforcedStyle': naming_convention.get(method_naming_convention, method_naming_convention) }, 'Style/StringLiterals': { 'EnforcedStyle': string_literals }, 'Style/VariableName': { 'EnforcedStyle': naming_convention.get(variable_naming_convention, variable_naming_convention) }, 'Metrics/ClassLength': { 'Max': max_class_length, 'CountComments': class_length_count_comments }, 'Metrics/ModuleLength': { 'CountComments': module_length_count_comments, 'Max': max_module_length }, 'Metrics/CyclomaticComplexity': { 'Max': cyclomatic_complexity }, 'Metrics/LineLength': { 'Max': max_line_length, 'AllowHeredoc': line_length_allow_here_doc, 'AllowURI': line_length_allow_uri }, 'Metrics/MethodLength': { 'CountComments': method_length_count_comments, 'Max': max_method_length }, 'Metrics/ParameterLists': { 'Max': max_parameters, 'CountKeywordArgs': count_keyword_args }, 'Lint/UnusedBlockArgument': { 'IgnoreEmptyBlocks': ignore_unused_block_args_if_empty, 'AllowUnusedKeywordArguments': allow_unused_block_keyword_arguments }, 'Lint/UnusedMethodArgument': { 'AllowUnusedKeywordArguments': allow_unused_method_keyword_args, 'IgnoreEmptyMethods': ignore_unused_method_args_if_empty }, } return yaml.dump(options, default_flow_style=False) def process_output(self, output, filename, file): output = json.loads(output) assert len(output['files']) == 1 for result in output['files'][0]['offenses']: # TODO: Add condition for auto-correct, when rubocop is updated. # Relevant Issue: https://github.com/bbatsov/rubocop/issues/2932 yield Result.from_values( origin='{class_name} ({rule})'.format( class_name=self.__class__.__name__, rule=result['cop_name']), message=result['message'], file=filename, diffs=None, severity=self.severity_map[result['severity']], line=result['location']['line'], column=result['location']['column'], # Tested with linebreaks, it's secure. end_column=result['location']['column'] + result['location']['length'])
def test_installed_requirement(self): self.assertTrue(PipRequirement('pip').is_installed())
def test_install_command_with_version(self): self.assertEqual( [sys.executable, '-m', 'pip', 'install', 'setuptools==19.2'], PipRequirement('setuptools', '19.2').install_command())
def test_not_installed_requirement(self): self.assertFalse(PipRequirement('some_bad_package').is_installed())
class YapfBear(LocalBear): """ Check and correct formatting of Python code using ``yapf`` utility. See <https://github.com/google/yapf> for more information. """ LANGUAGES = {'Python', 'Python 2', 'Python 3'} AUTHORS = {'The coala developers'} REQUIREMENTS = {PipRequirement('yapf', '0.14')} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_FIX = {'Formatting'} ASCIINEMA_URL = 'https://asciinema.org/a/89021' @deprecate_settings(indent_size='tab_width') def run(self, filename, file, max_line_length: int=79, indent_size: int=SpacingHelper.DEFAULT_TAB_WIDTH, allow_multiline_lambdas: bool=False, blank_line_before_nested_class_or_def: bool=False, continuation_tab_width: int=SpacingHelper.DEFAULT_TAB_WIDTH, dedent_closing_brackets: bool=False, indent_dictionary_value: bool=False, coalesce_brackets: bool=False, join_multiple_lines: bool=True, spaces_around_power_operator: bool=True, spaces_before_comment: int=2, space_between_ending_comma_and_closing_bracket: bool=True, split_arguments_when_comma_terminated: bool=False, split_before_bitwise_operator: bool=False, split_before_first_argument: bool=False, split_before_logical_operator: bool=False, split_before_named_assigns: bool=True, use_spaces: bool=True, based_on_style: str='pep8', prefer_line_break_after_opening_bracket: bool=True): """ :param max_line_length: Maximum number of characters for a line. :param indent_size: Number of spaces per indentation level. :param allow_multiline_lambdas: Allows lambdas to be formatted on more than one line. :param blank_line_before_nested_class_or_def: Inserts a blank line before a ``def`` or ``class`` immediately nested within another ``def`` or ``class``. :param continuation_tab_width: Indent width used for line continuations. :param dedent_closing_brackets: Puts closing brackets on a separate line, dedented, if the bracketed expression can't fit in a single line. Applies to all kinds of brackets, including function definitions and calls. :param indent_dictionary_value: Indents the dictionary value if it cannot fit on the same line as the dictionary key. :param coalesce_brackets: Prevents splitting consecutive brackets. Only relevant when ``dedent_closing_brackets`` is set. Example: If ``True``, ``` call_func_that_takes_a_dict( { 'key1': 'value1', 'key2': 'value2', } ) ``` would reformat to: ``` call_func_that_takes_a_dict({ 'key1': 'value1', 'key2': 'value2', }) ``` :param join_multiple_lines: Joins short lines into one line. :param spaces_around_power_operator: Set to ``True`` to prefer using spaces around ``**``. :param spaces_before_comment: The number of spaces required before a trailing comment. :param space_between_ending_comma_and_closing_bracket: Inserts a space between the ending comma and closing bracket of a list, etc. :param split_arguments_when_comma_terminated: Splits before arguments if the argument list is terminated by a comma. :param split_before_bitwise_operator: Set to ``True`` to prefer splitting before ``&``, ``|`` or ``^`` rather than after. :param split_before_first_argument: If an argument / parameter list is going to be split, then split before the first argument. :param split_before_logical_operator: Set to ``True`` to prefer splitting before ``and`` or ``or`` rather than after. :param split_before_named_assigns: Splits named assignments into individual lines. :param use_spaces: 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 # newline into the file. To avoid this, we don't parse zero-byte # files as they cannot have anything to format either. return options = """ [style] indent_width = {indent_size} column_limit = {max_line_length} allow_multiline_lambdas = {allow_multiline_lambdas} continuation_indent_width = {continuation_tab_width} dedent_closing_brackets = {dedent_closing_brackets} indent_dictionary_value = {indent_dictionary_value} join_multiple_lines = {join_multiple_lines} spaces_around_power_operator = {spaces_around_power_operator} spaces_before_comment = {spaces_before_comment} coalesce_brackets = {coalesce_brackets} split_before_bitwise_operator = {split_before_bitwise_operator} split_before_first_argument = {split_before_first_argument} split_before_logical_operator = {split_before_logical_operator} split_before_named_assigns = {split_before_named_assigns} based_on_style = {based_on_style} blank_line_before_nested_class_or_def = {blank_line_before_nested_class_or_def} split_arguments_when_comma_terminated = {split_arguments_when_comma_terminated} space_between_ending_comma_and_closing_bracket= \ {space_between_ending_comma_and_closing_bracket} """ 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()) try: with prepare_file(options.splitlines(keepends=True), None) as (file_, fname): corrected = FormatCode( ''.join(file), style_config=fname)[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, 'The code does not comply with the settings ' 'provided.', affected_code=(diff.range(filename),), diffs={filename: diff})
class YapfBear(LocalBear): """ Check and correct formatting of Python code using ``yapf`` utility. See <https://github.com/google/yapf> for more information. """ LANGUAGES = {"Python", "Python 2", "Python 3"} AUTHORS = {'The coala developers'} REQUIREMENTS = {PipRequirement('yapf', '0.10')} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_FIX = {'Formatting'} @deprecate_settings(indent_size='tab_width') def run(self, filename, file, max_line_length: int = 79, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, allow_multiline_lambdas: bool = False, blank_line_before_nested_class_or_def: bool = False, continuation_tab_width: int = SpacingHelper.DEFAULT_TAB_WIDTH, dedent_closing_brackets: bool = False, indent_dictionary_value: bool = False, coalesce_brackets: bool = False, join_multiple_lines: bool = True, spaces_around_power_operator: bool = True, spaces_before_comment: int = 2, space_between_ending_comma_and_closing_bracket: bool = False, split_arguments_when_comma_terminated: bool = False, split_before_bitwise_operator: bool = False, split_before_first_argument: bool = False, split_before_logical_operator: bool = False, split_before_named_assigns: bool = True, use_spaces: bool = True, based_on_style: str = 'pep8'): """ :param max_line_length: Maximum number of characters for a line. :param indent_size: Number of spaces per indentation level. :param allow_multiline_lambdas: Allows lambdas to be formatted on more than one line. :param blank_line_before_nested_class_or_def: Inserts a blank line before a ``def`` or ``class`` immediately nested within another ``def`` or ``class``. :param continuation_tab_width: Indent width used for line continuations. :param dedent_closing_brackets: Puts closing brackets on a separate line, dedented, if the bracketed expression can't fit in a single line. Applies to all kinds of brackets, including function definitions and calls. :param indent_dictionary_value: Indents the dictionary value if it cannot fit on the same line as the dictionary key. :param coalesce_brackets: Prevents splitting consecutive brackets. Only relevant when ``dedent_closing_brackets`` is set. Example: If ``True``, ``` call_func_that_takes_a_dict( { 'key1': 'value1', 'key2': 'value2', } ) ``` would reformat to: ``` call_func_that_takes_a_dict({ 'key1': 'value1', 'key2': 'value2', }) ``` :param join_multiple_lines: Joins short lines into one line. :param spaces_around_power_operator: Set to ``True`` to prefer using spaces around ``**``. :param spaces_before_comment: The number of spaces required before a trailing comment. :param space_between_ending_comma_and_closing_bracket: Inserts a space between the ending comma and closing bracket of a list, etc. :param split_arguments_when_comma_terminated: Splits before arguments if the argument list is terminated by a comma. :param split_before_bitwise_operator: Set to ``True`` to prefer splitting before ``&``, ``|`` or ``^`` rather than after. :param split_before_first_argument: If an argument / parameter list is going to be split, then split before the first argument. :param split_before_logical_operator: Set to ``True`` to prefer splitting before ``and`` or ``or`` rather than after. :param split_before_named_assigns: Splits named assignments into individual lines. :param use_spaces: Uses spaces for indentation. :param based_on_style: The formatting style to be used as reference. """ options = """ [style] indent_width = {indent_size} column_limit = {max_line_length} allow_multiline_lambdas = {allow_multiline_lambdas} continuation_indent_width = {continuation_tab_width} dedent_closing_brackets = {dedent_closing_brackets} indent_dictionary_value = {indent_dictionary_value} join_multiple_lines = {join_multiple_lines} spaces_around_power_operator = {spaces_around_power_operator} spaces_before_comment = {spaces_before_comment} coalesce_brackets = {coalesce_brackets} split_before_bitwise_operator = {split_before_bitwise_operator} split_before_first_argument = {split_before_first_argument} split_before_logical_operator = {split_before_logical_operator} split_before_named_assigns = {split_before_named_assigns} based_on_style = {based_on_style} blank_line_before_nested_class_or_def = {blank_line_before_nested_class_or_def} split_arguments_when_comma_terminated = {split_arguments_when_comma_terminated} space_between_ending_comma_and_closing_bracket= \ {space_between_ending_comma_and_closing_bracket} """ options += 'use_tabs = ' + str(not use_spaces) options = options.format(**locals()) with prepare_file(options.splitlines(keepends=True), None) as (file_, fname): corrected = FormatFile(filename, style_config=fname)[0].splitlines(True) diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, "The code does not comply with the settings " "provided.", affected_code=(diff.range(filename), ), diffs={filename: diff}) @classmethod def check_prerequisites(cls): # pragma: no cover if not sys.version_info >= (3, 4): return 'Yapf only supports Python 2.7 and Python 3.4+' else: return True
class InvalidLinkBear(LocalBear): DEFAULT_TIMEOUT = 2 LANGUAGES = {'All'} REQUIREMENTS = {PipRequirement('requests', '2.12')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Documentation'} # IP Address of www.google.com check_connection_url = 'http://216.58.218.174' @classmethod def check_prerequisites(cls): code = cls.get_status_code(cls.check_connection_url, cls.DEFAULT_TIMEOUT) return ('You are not connected to the internet.' if code is None else True) @staticmethod def get_status_code(url, timeout): try: code = requests.head(url, allow_redirects=False, timeout=timeout).status_code return code except requests.exceptions.RequestException: pass @staticmethod def parse_pip_vcs_url(link): splitted_at = link.split('@')[0] splitted_schema = splitted_at[splitted_at.index('+') + 1:] return splitted_schema @staticmethod def find_links_in_file(file, timeout, link_ignore_regex): link_ignore_regex = re.compile(link_ignore_regex) regex = re.compile( r""" ((git\+|bzr\+|svn\+|hg\+|) # For VCS URLs https?:// # http:// or https:// as only these # are supported by the ``requests`` # library [^.:%\s_/?#[\]@\\]+ # Initial part of domain \. # A required dot `.` ( (?:[^\s()%\'"`<>|\\]+) # Path name # This part does not allow # any parenthesis: balanced or # unbalanced. | # OR \([^\s()%\'"`<>|\\]*\) # Path name contained within () # This part allows path names that # are explicitly enclosed within one # set of parenthesis. # An example can be: # http://wik.org/Hello_(Adele_song)/200 ) *) # Thus, the whole part above # prevents matching of # Unbalanced parenthesis (?<!\.)(?<!,) # Exclude trailing `.` or `,` from URL """, re.VERBOSE) for line_number, line in enumerate(file): match = regex.search(line) if match: link = match.group() if not link_ignore_regex.search(link): if link.startswith(('hg+', 'bzr+', 'git+', 'svn+')): link = InvalidLinkBear.parse_pip_vcs_url(link) code = InvalidLinkBear.get_status_code(link, timeout) yield line_number + 1, link, code @deprecate_settings(link_ignore_regex='ignore_regex') def run(self, filename, file, timeout: int = DEFAULT_TIMEOUT, link_ignore_regex: str = '([.\/]example\.com|\{|\$)', follow_redirects: bool = False): """ Find links in any text file and check if they are valid. A link is considered valid if the server responds with a 2xx code. This bear can automatically fix redirects, but ignores redirect URLs that have a huge difference with the original URL. Warning: This bear will make HEAD requests to all URLs mentioned in your codebase, which can potentially be destructive. As an example, this bear would naively just visit the URL from a line that goes like `do_not_ever_open = 'https://api.acme.inc/delete-all-data'` wiping out all your data. :param timeout: Request timeout period. :param link_ignore_regex: A regex for urls to ignore. :param follow_redirects: Set to true to autocorrect redirects. """ for line_number, link, code in InvalidLinkBear.find_links_in_file( file, timeout, link_ignore_regex): if code is None: yield Result.from_values( origin=self, message=('Broken link - unable to connect to ' '{url}').format(url=link), file=filename, line=line_number, severity=RESULT_SEVERITY.MAJOR) elif not 200 <= code < 300: # HTTP status 404, 410 or 50x if code in (404, 410) or 500 <= code < 600: yield Result.from_values( origin=self, message=('Broken link - unable to connect to {url} ' '(HTTP Error: {code})').format(url=link, code=code), file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL) if follow_redirects and 300 <= code < 400: # HTTP status 30x redirect_url = requests.head(link, allow_redirects=True).url matcher = SequenceMatcher(None, redirect_url, link) if (matcher.real_quick_ratio() > 0.7 and matcher.ratio()) > 0.7: diff = Diff(file) current_line = file[line_number - 1] start = current_line.find(link) end = start + len(link) replacement = current_line[:start] + \ redirect_url + current_line[end:] diff.change_line(line_number, current_line, replacement) yield Result.from_values( self, 'This link redirects to ' + redirect_url, diffs={filename: diff}, file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL)
class GitCommitBear(GlobalBear): LANGUAGES = {"Git"} REQUIREMENTS = {PipRequirement('nltk', '3.1.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/e146c9739ojhr8396wedsvf0d' CAN_DETECT = {'Formatting'} @classmethod def check_prerequisites(cls): if shutil.which("git") is None: return "git is not installed." else: return True @classmethod def get_shortlog_checks_metadata(cls): return FunctionMetadata.from_function(cls.check_shortlog, omit={"self", "shortlog"}) @classmethod def get_body_checks_metadata(cls): return FunctionMetadata.from_function(cls.check_body, omit={"self", "body"}) @classmethod def get_metadata(cls): return FunctionMetadata.merge( FunctionMetadata.from_function(cls.run, omit={"self", "dependency_results"}), cls.get_shortlog_checks_metadata(), cls.get_body_checks_metadata()) def run(self, allow_empty_commit_message: bool = False, **kwargs): """ Check the current git commit message at HEAD. This bear ensures automatically that the shortlog and body do not exceed a given line-length and that a newline lies between them. :param allow_empty_commit_message: Whether empty commit messages are allowed or not. """ with change_directory(self.get_config_dir() or os.getcwd()): stdout, stderr = run_shell_command("git log -1 --pretty=%B") if stderr: self.err("git:", repr(stderr)) return stdout = stdout.rstrip("\n").splitlines() if len(stdout) == 0: if not allow_empty_commit_message: yield Result(self, "HEAD commit has no message.") return yield from self.check_shortlog( stdout[0], **self.get_shortlog_checks_metadata().filter_parameters(kwargs)) yield from self.check_body( stdout[1:], **self.get_body_checks_metadata().filter_parameters(kwargs)) def check_shortlog(self, shortlog, shortlog_length: int = 50, shortlog_regex: str = "", shortlog_trailing_period: bool = None, shortlog_imperative_check: bool = True, shortlog_wip_check: bool = True): """ Checks the given shortlog. :param shortlog: The shortlog message string. :param shortlog_length: The maximum length of the shortlog. The newline character at end does not count to the length. :param regex: A regex to check the shortlog with. :param shortlog_trailing_period: Whether a dot shall be enforced at end end or not (or ``None`` for "don't care"). :param shortlog_wip_check: Whether a "WIP" in the shortlog text should yield a result or not. """ diff = len(shortlog) - shortlog_length if diff > 0: yield Result( self, "Shortlog of HEAD commit is {} character(s) longer " "than the limit ({} > {}).".format(diff, len(shortlog), shortlog_length)) if (shortlog[-1] != ".") == shortlog_trailing_period: yield Result( self, "Shortlog of HEAD commit contains no period at end." if shortlog_trailing_period else "Shortlog of HEAD commit contains a period at end.") if shortlog_regex: match = re.fullmatch(shortlog_regex, shortlog) if not match: yield Result( self, "Shortlog of HEAD commit does not match given regex.") if shortlog_imperative_check: colon_pos = shortlog.find(':') shortlog = (shortlog[colon_pos + 1:] if colon_pos != -1 else shortlog) has_flaws = self.check_imperative(shortlog) if has_flaws: bad_word = has_flaws[0] yield Result( self, "Shortlog of HEAD commit isn't imperative mood, " "bad words are '{}'".format(bad_word)) if shortlog_wip_check: if "wip" in shortlog.lower()[:4]: yield Result( self, "This commit seems to be marked as work in progress and " "should not be used in production. Treat carefully.") def check_imperative(self, paragraph): """ Check the given sentence/s for Imperatives. :param paragraph: The input paragraph to be tested. :returns: A list of tuples having 2 elements (invalid word, parts of speech) or an empty list if no invalid words are found. """ try: words = nltk.word_tokenize(nltk.sent_tokenize(paragraph)[0]) # VBZ : Verb, 3rd person singular present, like 'adds', 'writes' # etc # VBD : Verb, Past tense , like 'added', 'wrote' etc # VBG : Verb, Present participle, like 'adding', 'writing' word, tag = nltk.pos_tag(['I'] + words)[1:2][0] if (tag.startswith('VBZ') or tag.startswith('VBD') or tag.startswith('VBG') or word.endswith('ing')): # Handle special case for VBG return (word, tag) else: return None except LookupError as error: # pragma: no cover self.err("NLTK data missing, install by running following " "commands `python3 -m nltk.downloader punkt" " maxent_treebank_pos_tagger averaged_perceptron_tagger`") return def check_body(self, body, body_line_length: int = 72, force_body: bool = False): """ 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. """ if len(body) == 0: if force_body: yield Result(self, "No commit message body at HEAD.") return if body[0] != "": yield Result(self, "No newline between shortlog and body at HEAD.") return if any(len(line) > body_line_length for line in body[1:]): yield Result(self, "Body of HEAD commit contains too long lines.")
class InvalidLinkBear(LocalBear): DEFAULT_TIMEOUT = 2 LANGUAGES = {"All"} REQUIREMENTS = {PipRequirement('requests', '2.*')} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Documentation'} # IP Address of www.google.com check_connection_url = "http://216.58.218.174" @classmethod def check_prerequisites(cls): code = cls.get_status_code(cls.check_connection_url, cls.DEFAULT_TIMEOUT) return ("You are not connected to the internet." if code is None else True) @staticmethod def get_status_code(url, timeout): try: code = requests.head(url, allow_redirects=False, timeout=timeout).status_code return code except requests.exceptions.RequestException: pass @staticmethod def find_links_in_file(file, timeout, link_ignore_regex): link_ignore_regex = re.compile(link_ignore_regex) regex = re.compile( r'(https?://[^.:%\s_/?#[\]@\\]+\.(?:[^\s()%\'"`<>|\\]+|' r'\([^\s()%\'"`<>|\\]*\))*)(?<!\.)(?<!,)') for line_number, line in enumerate(file): match = regex.search(line) if match: link = match.group() if not link_ignore_regex.search(link): code = InvalidLinkBear.get_status_code(link, timeout) yield line_number + 1, link, code @deprecate_settings(link_ignore_regex='ignore_regex') def run(self, filename, file, timeout: int = DEFAULT_TIMEOUT, link_ignore_regex: str = "([.\/]example\.com|\{|\$)", follow_redirects: bool = False): """ Find links in any text file and check if they are valid. A link is considered valid if the server responds with a 2xx code. This bear can automatically fix redirects, but ignores redirect URLs that have a huge difference with the original URL. Warning: This bear will make HEAD requests to all URLs mentioned in your codebase, which can potentially be destructive. As an example, this bear would naively just visit the URL from a line that goes like `do_not_ever_open = 'https://api.acme.inc/delete-all-data'` wiping out all your data. :param timeout: Request timeout period. :param link_ignore_regex: A regex for urls to ignore. :param follow_redirects: Set to true to autocorrect redirects. """ for line_number, link, code in InvalidLinkBear.find_links_in_file( file, timeout, link_ignore_regex): if code is None: yield Result.from_values( origin=self, message=('Broken link - unable to connect to ' '{url}').format(url=link), file=filename, line=line_number, severity=RESULT_SEVERITY.MAJOR) elif not 200 <= code < 300: # HTTP status 404, 410 or 50x if code in (404, 410) or 500 <= code < 600: yield Result.from_values( origin=self, message=('Broken link - unable to connect to {url} ' '(HTTP Error: {code})').format(url=link, code=code), file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL) if follow_redirects and 300 <= code < 400: # HTTP status 30x redirect_url = requests.head(link, allow_redirects=True).url matcher = SequenceMatcher(None, redirect_url, link) if (matcher.real_quick_ratio() > 0.7 and matcher.ratio()) > 0.7: diff = Diff(file) current_line = file[line_number - 1] start = current_line.find(link) end = start + len(link) replacement = current_line[:start] + \ redirect_url + current_line[end:] diff.change_line(line_number, current_line, replacement) yield Result.from_values( self, 'This link redirects to ' + redirect_url, diffs={filename: diff}, file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL)
class SCSSLintBear: """ Check SCSS code to keep it clean and readable. More information is available at <https://github.com/brigade/scss-lint>. """ LANGUAGES = {'SCSS'} REQUIREMENTS = { GemRequirement('scss-lint', '', 'false'), PipRequirement('pyyaml', '3.12') } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Syntax', 'Formatting'} @staticmethod def create_arguments(filename, file, config_file): return filename, '--config', config_file @staticmethod def generate_config(filename, file, space_around_bang: list = [True, False], allow_chained_classes: bool = False, prefer_color_keywords: bool = False, use_color_variables: bool = True, allow_debug_statement: bool = False, check_declaration_order: bool = True, allow_duplicate_properties: bool = False, allow_consecutives_duplicate_property: bool = False, else_on_same_line: bool = True, force_empty_line_between_blocks: bool = True, allow_empty_rules: bool = False, use_short_hexadecimal_length_style: bool = True, use_lowercase_hexadecimal: bool = True, validate_hexadecimal: bool = True, allow_id_selector: bool = False, allow_important_rule_in_properties: bool = False, use_spaces: bool = True, indent_size: int = 2, exclude_leading_zero: bool = True, allow_mergeable_selectors: bool = False, allow_leading_underscore: bool = True, function_naming_convention: str = 'hyphen', mixin_naming_convention: str = 'hyphen', variable_naming_convention: str = 'hyphen', placeholder_naming_convention: str = 'hyphen', max_nesting_depth: int = 3, use_placeholder_selector_in_extend: bool = True, max_properties: int = 10, allow_unit_on_zero_values: bool = False, check_ulrs_format: bool = True, urls_in_quotes: bool = True, allow_unnecesseary_parent_reference: bool = False, allow_unnecessary_mantissa: bool = False, allow_trailing_whitespaces: bool = False, allow_trailing_semicolon: bool = True, check_imports_path: bool = True, allow_filename_leading_underscore: bool = False, allow_filename_extension: bool = False, use_length_variables: bool = True, check_properties_spelling: bool = True, extra_properties: list = (), disabled_properties: list = (), check_pseudo_elements: bool = True, spaces_between_parentheses: int = 0, spaces_around_operators: str = 1): """ :param space_around_bang: Enforces a space before and/or after ``!`` (the "bang"). :param allow_chained_classes: Allows defining a rule set using a selector with chained classes. :param prefer_color_keywords: Prefers color keywords over hexadecimal color codes. :param use_color_variables: Prefers color literals (keywords or hexadecimal codes) to be used only in variable declarations. :param allow_debug_statement: Allows ``@debug`` statements. :param check_declaration_order: Rule sets should be ordered as follows: ``@extend`` declarations, ``@include`` declarations without inner ``@content``, properties, ``@include`` declarations with inner ``@content``, then nested rule sets. :param allow_duplicate_properties: Allows defining the same property twice in a single rule set. :param allow_consecutives_duplicate_property: Allows defining the same property consecutively in a single rule set. :param else_on_same_line: Places ``@else`` statements on the same line as the preceding curly brace. :param force_empty_line_between_blocks: Separate rule, function, and mixin declarations with empty lines. :param allow_empty_rules: Allows empty rule set. :param use_short_hexadecimal_length_style: Prefer shorthand or long-form hexadecimal colors by setting the style option to short or long, respectively. :param use_lowercase_hexadecimal: Checks if hexadecimal colors are written in lowercase or uppercase. :param validate_hexadecimal: Ensure hexadecimal colors are valid (either three or six digits). :param allow_id_selector: Allows using ID selectors. :param allow_important_rule_in_property: Allows using ``!important`` in properties. :param use_spaces: Use spaces for indentation (tabs otherwise). :param indent_size: Number of spaces per indentation level. :param exclude_leading_zero: Determines whether leading zeros should be written or not in numeric values with a decimal point. :param allow_mergeable_selectors: Allows defining the same selector twice in a single sheet. :param allow_leading_underscore: Allows names to start with a single underscore. :param function_naming_convention: Name of convention (``hyphen``(use lowercase letters and hyphens) (default), ``camel``, ``snake``), or a ``regex`` the name must match (eg: ``^[a-zA-Z]+$``) to use for functions. :param mixin_naming_convention: Name of convention (``hyphen`` (default), ``camel``, ``snake``), or a regex the name must match (eg: ``^[a-zA-Z]+$``) to use for mixins. :param variable_naming_convention: Name of convention (``hyphen`` (default), ``camel``, ``snake``), or a regex the name must match (eg: ``^[a-zA-Z]+$``) to use for variables. :param placeholder_naming_convention: Name of convention (``hyphen`` (default), ``camel``, ``snake``), or a regex the name must match (eg: ``^[a-zA-Z]+$``) to use for placeholders. :param max_nesting_depth: Maximum nesting depth. :param use_placeholder_selector_in_extend: Enforces using placeholder selectors in ``@extend``. :param max_properties: Enforces a limit on the number of properties in a rule set. :param allow_unit_on_zero_values: Allow omitting length units on zero values. :param check_urls_format: URLs should be valid and not contain protocols or domain names. :param urls_in_quotes: URLs should always be enclosed within quotes. :param allow_unnecessary_parent_reference: Allows use of the parent selector references ``&`` even when they are not unnecessary. :param allow_unnecessary_mantissa: Numeric values can contain unnecessary fractional portions. :param allow_traling_whitespaces: Unables trailing whitespace. :param allow_trailing_semicolon: Property values; ``@extend``, ``@include``, and ``@import`` directives; and variable declarations should always end with a semicolon. :param check_imports_path: The basenames of ``@import``ed SCSS partials should not begin with an underscore and should not include the filename extension. These requirements can be modified by changing ``allow_filename_leading_underscore``, and ``allow_extensions``. :param allow_filename_leading_underscore: Requires basenames of ``@import``ed SCSS partials to begin with an underscore. This setting require ``check_import_paths`` to be enabled. :param allow_filename_extension: Requires basenames of ``@import``ed SCSS partials to include filename extension, this setting require ``check_import_paths`` to be enabled. :param use_length_variables: Prefer length literals (numbers with units) to be used only in variable declarations. :: div { width: 100px; } Is not valid, whereas :: $column-width: 100px; div { width: $column-width; } is valid. :param check_properties_spelling: Reports when an unknown or disabled CSS property is used (ignoring vendor-prefixed properties). :param extra_properties: List of extra properties to allow. :param disabled_properties: List of existing properties to deny. :param check_pseudo_elements: Pseudo-elements, like ``::before``, and ``::first-letter``, should be declared with two colons. Pseudo-classes, like ``:hover`` and ``:first-child``, should be declared with one colon. :: p::before { content: '>' } p:hover { color: red; } :param spaces_between_parentheses: Spaces to require between parentheses. :param spaces_around_operators: Operators should be formatted with a single space on both sides of an infix operator. The different value for this setting are ``1``, ``0`` or a number greater that ``1``. """ naming_convention_map = { 'camel': 'camel_case', 'snake': 'snake_case', 'hyphen': 'hyphenated_lowercase' } space_setting_map = {'one_space': 1, 'no_space': 0} options = { 'BangFormat': { 'enabled': True, 'space_before_bang': space_around_bang[0], 'space_after_bang': space_around_bang[1] }, 'ChainedClasses': { 'enabled': not allow_chained_classes }, 'ColorKeyword': { 'enabled': not prefer_color_keywords }, 'ColorVariable': { 'enabled': use_color_variables }, 'DebugStatement': { 'enabled': not allow_debug_statement }, 'DeclarationOrder': { 'enabled': check_declaration_order }, 'DuplicateProperty': { 'enabled': not allow_duplicate_properties, 'ignore_consecutive': allow_consecutives_duplicate_property }, 'ElsePlacement': { 'enabled': True, 'style': ('same_line' if else_on_same_line else '') }, 'EmptyLineBetweenBlocks': { 'enabled': force_empty_line_between_blocks, 'ignore_single_line_blocks': True }, 'EmptyRule': { 'enabled': not allow_empty_rules }, 'HexLength': { 'enabled': True, 'style': ('short' if use_short_hexadecimal_length_style else 'long') }, 'HexNotation': { 'enabled': True, 'style': ('lowercase' if use_lowercase_hexadecimal else 'uppercase') }, 'HexValidation': { 'enabled': validate_hexadecimal }, 'IdSelector': { 'enabled': not allow_id_selector }, 'ImportantRule': { 'enabled': not allow_important_rule_in_properties }, 'Indentation': { 'enabled': True, 'allow_non_nested_indentation': False, 'character': ('space' if use_spaces else 'tab'), 'width': indent_size }, 'LeadingZero': { 'enabled': True, 'style': ('exclude_zero' if exclude_leading_zero else 'include_zero') }, 'MergeableSelector': { 'enabled': allow_mergeable_selectors, 'force_nesting': True }, 'NestingDepth': { 'enabled': True, 'max_depth': max_nesting_depth, 'ignore_parent_selectors': False }, 'NameFormat': { 'enabled': True, 'allow_leading_underscore': allow_leading_underscore, 'function_convention': naming_convention_map.get(function_naming_convention, function_naming_convention), 'mixin_convention': naming_convention_map.get(mixin_naming_convention, mixin_naming_convention), 'variable_convention': naming_convention_map.get(variable_naming_convention, variable_naming_convention), 'placeholder_convention': naming_convention_map.get(placeholder_naming_convention, placeholder_naming_convention) }, 'PlaceholderInExtend': { 'enabled': use_placeholder_selector_in_extend }, 'PropertyCount': { 'enabled': False, 'include_nested': False, 'max_properties': max_properties }, 'ZeroUnit': { 'enabled': not allow_unit_on_zero_values }, 'UrlFormat': { 'enabled': check_ulrs_format }, 'UrlQuotes': { 'enabled': urls_in_quotes }, 'UnnecessaryMantissa': { 'enabled': not allow_unnecessary_mantissa }, 'UnnecessaryParentReference': { 'enabled': not allow_unnecesseary_parent_reference }, 'TrailingSemicolon': { 'enabled': allow_trailing_semicolon }, 'TrailingWhitespace': { 'enabled': not allow_trailing_whitespaces }, 'ImportPath': { 'enabled': check_imports_path, 'leading_underscore': allow_filename_leading_underscore, 'filename_extension': allow_filename_extension }, 'LengthVariable': { 'enabled': use_length_variables }, 'PropertySpelling': { 'enabled': check_properties_spelling, 'extra_properties': extra_properties, 'disabled_properties': disabled_properties }, 'SpaceBetweenParens': { 'enabled': True, 'spaces': spaces_between_parentheses }, 'SpaceAroundOperator': { 'enabled': True, 'style': space_setting_map.get(spaces_around_operators, 'at_least_one_space') } } configs = {'linters': options} return yaml.dump(configs, default_flow_style=False)