class ShellCheckBear: # pragma nt: no cover """ Check bash/shell scripts for syntactical problems (with understandable messages), semantical problems as well as subtle caveats and pitfalls. A gallery of bad code that can be detected is available at <https://github.com/koalaman/shellcheck/blob/master/README.md>. """ LANGUAGES = {'sh', 'bash', 'ksh', 'dash'} REQUIREMENTS = {AnyOneOfRequirements( [CabalRequirement('ShellCheck', '0.4.1'), DistributionRequirement('shellcheck') ] ), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Syntax', 'Security', 'Undefined Element', 'Unused Code'} @staticmethod def create_arguments(filename, file, config_file, shell: str = 'sh', shellcheck_ignore: list = None, ): """ :param shell: Target shell being used. :param shellcheck_ignore: List of linting rules that should be ignored. """ args = ('--f', 'gcc', '-s', shell, filename) if shellcheck_ignore: args += ('-e', ','.join(shellcheck_ignore)) return args
class GhcModBear: # pragma nt: no cover """ Syntax checking with ``ghc`` for Haskell files. See <https://hackage.haskell.org/package/ghc-mod> for more information! """ LANGUAGES = {'Haskell'} REQUIREMENTS = { AnyOneOfRequirements( [CabalRequirement('ghc-mod', '5.6.0.0'), DistributionRequirement('ghc-mod') ] ), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/98873' CAN_DETECT = {'Syntax'} @staticmethod def create_arguments(filename, file, config_file): # -b '. ' is the argument given for ghc-mod for seperation of messages return '-b', '. ', 'check', filename
class HaskellLintBear: """ Check Haskell code for possible problems. This bear can propose patches for using alternative functions, simplifying code and removing redundancies. See <http://community.haskell.org/~ndm/darcs/hlint/hlint.htm> for more information. """ LANGUAGES = {'Haskell'} REQUIREMENTS = { AnyOneOfRequirements([ CabalRequirement(package='hlint', version='1.9.27'), DistributionRequirement(apt_get='hlint'), ], ), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Duplication'} CAN_FIX = {'Unused Code', 'Code Simplification'} severity_map = { 'Error': RESULT_SEVERITY.MAJOR, 'Warning': RESULT_SEVERITY.NORMAL, 'Suggestion': RESULT_SEVERITY.INFO } @staticmethod def create_arguments(filename, file, config_file): return '--json', filename def process_output(self, output, filename, file): output = json.loads(output) for issue in output: diff = Diff(file) from_lines = issue['from'].splitlines() to_lines = issue['to'].splitlines() assert len(from_lines) == len(to_lines) for other_lines in range(1, len(from_lines)): assert from_lines[other_lines] == to_lines[other_lines] line_nr = issue['startLine'] line_to_change = file[line_nr - 1] newline = line_to_change.replace(from_lines[0], to_lines[0]) diff.change_line(line_nr, line_to_change, newline) yield Result.from_values( origin=self, message=issue['hint'], file=filename, severity=self.severity_map[issue['severity']], line=issue['startLine'], column=issue['startColumn'], end_line=issue['endLine'], end_column=issue['endColumn'], diffs={filename: diff})
class RubySyntaxBear: """ Checks the code with ``ruby -wc`` on each file separately. """ LANGUAGES = {'Ruby'} REQUIREMENTS = { AnyOneOfRequirements( [DistributionRequirement('ruby'), CondaRequirement('ruby', '2.2.3', 'bioconda'), ], ), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Syntax'} @staticmethod def create_arguments(filename, file, config_file): return '-wc', filename
class PHPMessDetectorBear: """ The bear takes a given PHP source code base and looks for several potential problems within that source. These problems can be things like: - Possible bugs - Suboptimal code - Overcomplicated expressions - Unused parameters, methods, properties """ LANGUAGES = {'PHP'} REQUIREMENTS = { AnyOneOfRequirements([ DistributionRequirement( apt_get='phpmd', dnf='php-phpmd-PHP-PMD', ), ComposerRequirement('phpmd/phpmd'), ], ), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = { 'Formatting', 'Complexity', 'Unused Code', 'Redundancy', 'Variable Misuse' } SEE_MORE = 'https://phpmd.org/about.html' @staticmethod def create_arguments(filename, file, config_file, phpmd_rulesets: typed_list(str)): """ :param phpmd_rulesets: A list of rulesets to use for analysis. Available rulesets: cleancode, codesize, controversial, design, naming, unusedcode. """ return filename, 'text', ','.join(phpmd_rulesets)
class PHPCodeSnifferBear: """ Ensures that your PHP, JavaScript or CSS code remains clean and consistent. See <https://github.com/squizlabs/PHP_CodeSniffer> for more information. """ LANGUAGES = {'PHP', 'JavaScript', 'CSS'} REQUIREMENTS = { AnyOneOfRequirements([ DistributionRequirement( apt_get='php-codesniffer', zypper='php-pear-php_codesniffer', ), ComposerRequirement('squizlabs/php_codesniffer'), ], ), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = { 'Formatting', 'Syntax', 'Documentation', 'Code Simplification' } ASCIINEMA_URL = 'https://asciinema.org/a/efawv96vdalck73tc3hwcabov' @staticmethod def create_arguments(filename, file, config_file): return '--report=emacs', '--standard=' + config_file, filename @staticmethod def generate_config(filename, file, max_line_length: int = 79, line_ending_character: str = '\\n', indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, use_spaces: bool = True, allow_multiple_statements_per_line: bool = False, force_lower_case_keywords: bool = True, force_lower_case_constants: bool = True, blank_line_after_namespace_declaration: bool = True, check_use_blocks: bool = True, check_class_declaration: bool = True, check_property_declaration: bool = True, force_scope_modifier_on_method: bool = True, function_declaration_argument_spacing: int = 1, allow_multiline_function_declaration: bool = True): """ :param max_line_length: Maximum number of characters for a line. :param line_ending_character: Checks that end of line characters correspond to the one provided. :param indent_size: Number of spaces per indentation level. :param use_spaces: True if spaces are to be used instead of tabs. :param allow_multiple_statements_per_line: Allows having multiple statements on one line. :param force_lower_case_keyword: Checks that ``PHP`` keywords are lowercase. :param force_lower_case_constant: Checks that all uses of ``true``, ``false`` and ``null`` are lowercase. :param blank_line_after_namespace_declaration: Ensures that there is a blank line after a namespace declaration. :param check_use_blocks: Ensures that there is one blank line after a ``use`` block, that there is only one use block per line, and that all ``use`` declaration are done after namespaces declaration. :param check_class_declaration: Ensures that ``extends`` and ``implements`` keywords are declared on the same line as the class name, that the opening brace for a class is on the next line, and that the closing brace for a class is on the next line after the body. Allows splitting implements list accross multiple lines. :param check_property_declaration: Ensures that visibility is declared on all properties, that the ``var`` keyword is not used to declare a property, that there is not more that one property declared on a line, that properties are not prefixed with an underscore. :param force_scope_modifier_on_method: Verifies that class methods have scope modifiers. :param function_declaration_argument_spacing: Number of spaces between arguments in function declaration. :param allow_multiline_function_declaration: Allows argument lists to be split accross multiple lines correctly indented. """ rules_map = { 'Generic.WhiteSpace.DisallowTabIndent': use_spaces, 'Generic.Formatting.DisallowMultipleStatements': not allow_multiple_statements_per_line, 'Generic.PHP.LowerCaseKeyword': force_lower_case_keywords, 'Generic.PHP.LowerCaseConstant': force_lower_case_constants, 'PSR2.Namespaces.UseDeclaration': check_use_blocks, 'PSR2.Namespaces.NamespaceDeclaration': blank_line_after_namespace_declaration, 'PSR2.Classes.ClassDeclaration': check_class_declaration, 'PSR2.Classes.PropertyDeclaration': check_property_declaration, 'Squiz.Scope.MethodScope': force_scope_modifier_on_method, 'Squiz.Functions.MultiLineFunctionDeclaration': allow_multiline_function_declaration } rules = '' for k, v in rules_map.items(): rules += '<rule ref="{}"/>\n'.format(k) if v else '' configs = '''<?xml version="1.0"?> <ruleset name="Custom Standard"> <description>A custom coding standard</description> <!-- Include the whole PSR-1 standard --> <rule ref="PSR1"/> <rule ref="Generic.Files.LineLength"> <properties> <property name="lineLimit" value="{max_line_length}"/> <property name="absoluteLineLimit" value="{absolute_line_length}"/> </properties> </rule> <rule ref="Generic.Files.LineEndings"> <properties> <property name="eolChar" value="{line_ending_character}"/> </properties> </rule> <rule ref="Generic.WhiteSpace.ScopeIndent"> <properties> <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT_OPEN_TAG"/> </properties> <properties> <property name="indent" value="{indent_size}"/> </properties> <properties> <property name="exact" value="true"/> </properties> </rule> {some_rules} <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> <properties> <property name="equalsSpacing" value="{function_declaration_argument_spacing}"/> </properties> </rule> <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> <severity>0</severity> </rule> </ruleset> '''.format(max_line_length=max_line_length, absolute_line_length=0, line_ending_character=line_ending_character, indent_size=indent_size, some_rules=rules, function_declaration_argument_spacing=( function_declaration_argument_spacing)) return configs
class FormatRBear: """ Check and correct formatting of R Code using known formatR utility. """ LANGUAGES = {'R'} AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} REQUIREMENTS = { AnyOneOfRequirements( [DistributionRequirement( apt_get='r-cran-formatr', zypper='R-formatR', ), RscriptRequirement('formatR'), ] ) } LICENSE = 'AGPL-3.0' ASCIINEMA_URL = 'https://asciinema.org/a/0y0oxtak18v492jdyfqwpw1n4' CAN_FIX = {'Formatting'} @staticmethod @deprecate_settings(indent_size='tab_width') def create_arguments(filename, file, config_file, r_keep_comments: bool = True, r_keep_blank_lines: bool = True, r_braces_on_next_line: bool = None, r_use_arrows: bool = None, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, r_max_expression_length: int = 0, ): """ :param r_keep_comments: Determines whether comments are kept or not. :param r_keep_blank_lines: Determines whether blank lines are kept or not. :param r_braces_on_next_line: Determines whether a brace should be placed on the next line. Example: If ``True``:: if (...) { changes to:: if (...) { If ``False`` the brace is placed on the same line. :param r_use_arrows: Determines whether the assignment operator ``=`` should be replaced by an arrow ``<-`` or not. Example: If ``True``, ``x = 1`` changes to ``x <- 1``. :param indent_size: Number of spaces per indentation level. :param r_max_expression_length: Maximum number of characters for an expression. Example: If ``20`` then:: 1 + 1 + 1 + 1 + 1 + 1 + 1 changes to:: 1 + 1 + 1 + 1 + 1 + 1 + 1 """ options = {'source="' + escape(filename, '"\\') + '"', 'blank=' + _map_to_r_bool(r_keep_blank_lines), 'comment=' + _map_to_r_bool(r_keep_comments), 'indent=' + str(indent_size)} if r_max_expression_length: options.add('width.cutoff=' + str(r_max_expression_length)) if r_braces_on_next_line is not None: options.add('brace.newline=' + _map_to_r_bool(r_braces_on_next_line)) if r_use_arrows is not None: options.add('arrow=' + _map_to_r_bool(r_use_arrows)) rcode = 'library(formatR);formatR::tidy_source({})'.format( ','.join(options)) return '-e', rcode
class CPDBear(GlobalBear): language_dict = { 'C#': 'cs', 'CPP': 'cpp', 'JavaScript': 'ecmascript', 'Fortran': 'fortran', 'Go': 'go', 'Java': 'java', 'JSP': 'jsp', 'Matlab': 'matlab', 'Octave': 'matlab', 'Objective-C': 'objectivec', 'PHP': 'php', 'PLSQL': 'plsql', 'Python': 'python', 'Python 2': 'python', 'Python 3': 'python', 'Ruby': 'ruby', 'Scala': 'scala', 'Swift': 'swift' } LANGUAGES = set(language_dict.keys()) REQUIREMENTS = { AnyOneOfRequirements([ ExecutableRequirement('cpd'), ExecutableRequirement('run.sh'), ]), } AUTHORS = {'The coala developers'} AUTHORS_EMAILS = {'*****@*****.**'} LICENSE = 'AGPL-3.0' CAN_DETECT = {'Duplication'} def run( self, language: language, minimum_tokens: int = 20, ignore_annotations: bool = False, ignore_identifiers: bool = True, ignore_literals: bool = False, ignore_usings: bool = False, skip_duplicate_files: bool = True, ): """ Checks for similar code that looks as it could be replaced to reduce redundancy. For more details see: <https://pmd.github.io/pmd-6.4.0/pmd_userdocs_cpd.html> :param language: One of the supported languages of this bear. :param minimum_tokens: The minimum token length which should be reported as a duplicate. :param ignore_annotations: Ignore language annotations when comparing text. :param ignore_identifiers: Ignore constant and variable names when comparing text. :param ignore_literals: Ignore number values and string contents when comparing text. :param ignore_usings: Ignore ``using`` directives in C#. :param skip_duplicate_files: Ignore multiple copies of files of the same name and length in comparison. """ for supported_lang in self.language_dict: if supported_lang in language: cpd_language = self.language_dict[supported_lang] break else: self.err('This bear does not support files with the extension ' "'{}'.".format(language)) return options = { '--ignore-annotations': ignore_annotations, '--ignore-identifiers': ignore_identifiers, '--ignore-literals': ignore_literals, '--ignore-usings': ignore_usings, '--skip-duplicate-files': skip_duplicate_files } files = ','.join(self.file_dict.keys()) executable = which('cpd') or which('run.sh') executable = tuple([executable] if not executable.endswith('run.sh') else [executable, 'cpd']) arguments = ('--skip-lexical-errors', '--minimum-tokens', str(minimum_tokens), '--language', cpd_language, '--files', files, '--format', 'xml') arguments += tuple(option for option, enable in options.items() if enable is True) arguments = executable + arguments stdout_output, _ = run_shell_command(arguments) if stdout_output: root = ElementTree.fromstring(stdout_output) for duplication in root.findall('duplication'): length = int(duplication.attrib['lines']) affected_code = list() for xml_file in duplication.findall('file'): filename = xml_file.attrib['path'] start_line = int(xml_file.attrib['line']) end_line = min(start_line + length - 1, len(self.file_dict[filename])) affected_code.append( SourceRange.from_values(filename, start_line=start_line, end_line=end_line)) yield Result( self, 'Duplicate code found.', affected_code, additional_info=( 'Duplicate code is an indicator ' 'that you have more code than you need. Consider' ' refactor your code to remove one of the' ' occurrences. For more information go here:' 'http://tinyurl.com/coala-clone'))
class DistributionRequirement(PackageRequirement): """ This class is a subclass of ``PackageRequirement``. It specifies the proper type automatically. """ """ List of supported package manager with their command respectively """ SUPPORTED_PACKAGE_MANAGERS = { 'apt_get': 'apt-get', 'brew': 'brew', 'dnf': 'dnf', 'pacman': 'pacman', 'portage': 'emerge', 'xbps': 'xbps-install', 'yum': 'yum', 'zypper': 'zypper', } _available_managers = None REQUIREMENTS = { AnyOneOfRequirements( list( ExecutableRequirement(name) for name in SUPPORTED_PACKAGE_MANAGERS.values())), ExecutableRequirement('grep'), } """ List of commands that can be used to verify if the package is installed. """ CHECKER_COMMANDS = { 'apt_get': 'dpkg-query -l {}', 'brew': 'brew list {}', 'dnf': 'rpm -qa | grep "^{}"', 'pacman': 'pacman -Qs {}', 'portage': 'equery list {}', 'xbps': 'xbps-query {}', 'yum': 'rpm -qa | grep "^{}"', 'zypper': 'rpm -qa | grep "^{}"', } """ List of commands that can be used to install a package. """ _INSTALL_COMMANDS = { 'apt_get': ('apt-get', 'install', '--yes'), 'brew': ('brew', 'install'), 'dnf': ('dnf', 'install', '--assumeyes'), 'pacman': ('pacman', ), 'portage': ('emerge', ), 'xbps': ('xbps-install', '--yes'), 'yum': ('yum', 'install', '--assumeyes'), 'zypper': ('zypper', '--non-interactive', 'install'), } def __init__(self, package: str = None, version='', repo='', **package_overrides): """ Constructs a new ``DistributionRequirement``, using the ``PackageRequirement`` constructor. When a ``package`` name is provided, it is used as the package attribute, even when override package names are provided for specific package managers. >>> dr = DistributionRequirement(package='clang', ... apt_get='libclang', ... dnf='libclangg') >>> dr.package 'clang' >>> dr.packages['apt_get'] 'libclang' >>> dr.packages['dnf'] 'libclangg' When no ``package`` name is provided, the override package name for the local host's package manager is used if possible, otherwise the most common override is used. >>> dr = DistributionRequirement(unknown1='libclangg', ... unknown2='libclang', ... unknown3='libclang') >>> dr.package 'libclang' >>> dr.packages['unknown1'] 'libclangg' >>> dr.packages['unknown2'] 'libclang' >>> dr.packages['unknown3'] 'libclang' >>> from pprint import pprint >>> len(dr.REQUIREMENTS) 2 >>> not_grep_req = [dep for dep in dr.REQUIREMENTS ... if str(dep) != 'grep'][0] >>> pprint(str(not_grep_req)) ('ExecutableRequirement(apt-get) ExecutableRequirement(brew) ' 'ExecutableRequirement(dnf) ExecutableRequirement(emerge) ' 'ExecutableRequirement(pacman) ExecutableRequirement(xbps-install) ' 'ExecutableRequirement(yum) ExecutableRequirement(zypper)') :param package: A string with the name of the package to be installed. :param version: A version string. Unused. :param repo: The repository from which the package is to be installed. Unused. :param kwargs: Override package names for supported package managers. """ self._managers = None self._manager = None self.packages = package_overrides if not package and not package_overrides: raise NoArgsNotImplementedError('No package managers specified') if package: defaults = {(pm, package) for pm in self.SUPPORTED_PACKAGE_MANAGERS.keys() if pm not in package_overrides} self.packages.update(defaults) else: package = self._get_best_package_name() PackageRequirement.__init__(self, 'distribution', package, version) def _get_best_package_name(self): package_names = Counter(self.packages.values()) if len(package_names) == 1: return package_names.most_common()[0][0] try: return self.packages[self.get_available_package_manager()] except NotImplementedError: return package_names.most_common()[0][0] def get_available_package_manager(self): """ Returns the available package manager that can be used to satisfy the requirement. Raises NotImplementedError if there's no supported package manager. :return: Supported package manager key """ if not self._manager: self._manager = next(self.get_package_managers()) return self._manager def get_package_managers(self): """ Yield package managers that can satisfy requirement. :raises NotImplementedError: There are no package managers for requirement. """ found = False available_managers = self.available_package_managers supported_managers = self.SUPPORTED_PACKAGE_MANAGERS.keys() for manager in self.packages.keys(): if manager in available_managers: found = True yield manager elif manager not in supported_managers: raise NotImplementedError( "{} is not supported".format(manager)) if found: return raise NotImplementedError("This platform doesn't have any of the " 'specified package manager(s): ' '{}'.format(','.join(self.packages.keys()))) @property def package_managers(self): """ Return package managers that can satisfy requirement. :raises NotImplementedError: There are no package managers for requirement. """ if self._managers is None: self._managers = list(self.get_package_managers()) return self._managers @property def available_package_managers(self): """ Return all available package managers. :raises NotImplementedError: There are no package managers for requirement. """ if self._available_managers is None: self._available_managers = list( self.get_all_available_package_managers()) return self._available_managers @classmethod def get_all_available_package_managers(self): """ Yield the available package managers. :raises NotImplementedError: There are no supported package managers. """ found = False for manager, exe in self.SUPPORTED_PACKAGE_MANAGERS.items(): if is_executable_exists(exe): found = True yield manager if found: return raise NotImplementedError( "This platform doesn't have any of the supported package " 'manager(s): {}'.format(', '.join( self.SUPPORTED_PACKAGE_MANAGERS))) def is_installed(self): """ Check if the requirement is satisfied by calling various package managers that are mentioned. Raises NotImplementedError if executable is not defined when there's no supported package manager. :return: True if the dependency is installed, false otherwise """ package_manager = self.get_available_package_manager() command = self.CHECKER_COMMANDS[package_manager] package = self.packages[package_manager] return not run(command.format(package), stdout=Capture(), stderr=Capture()).returncode def install_command(self): """ Creates the installation command for the instance of the class. >>> DistributionRequirement('indent').install_command()[-1] 'indent' :param return: A list with the installation command. """ package_manager = self.get_available_package_manager() package = self.packages[package_manager] command = list(self._INSTALL_COMMANDS[package_manager]) command.append(package) return command