From 59371d598567352c3f99bedf2b32c8bcf8ab7fe6 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Tue, 15 Jun 2021 12:40:04 +0300 Subject: [PATCH 1/3] Calculate inspectors statistics and small code refactoring --- .../inspectors/inspectors_stat/README.md | 45 ++ .../inspectors/inspectors_stat/__init__.py | 0 .../inspectors_stat/issues/__init__.py | 0 .../issues/flake8_all_issues.py | 601 ++++++++++++++++++ .../inspectors_stat/issues/other_issues.py | 8 + .../issues/pylint_all_issues.py | 496 +++++++++++++++ .../inspectors_stat/statistics_gathering.py | 131 ++++ src/python/review/common/language.py | 4 + src/python/review/inspectors/issue.py | 58 +- .../review/inspectors/pmd/issue_types.py | 2 +- src/python/review/inspectors/pmd/pmd.py | 4 +- .../review/inspectors/pyast/python_ast.py | 16 +- src/python/review/inspectors/radon/radon.py | 23 +- whitelist.txt | 4 +- 14 files changed, 1376 insertions(+), 16 deletions(-) create mode 100644 src/python/evaluation/inspectors/inspectors_stat/README.md create mode 100644 src/python/evaluation/inspectors/inspectors_stat/__init__.py create mode 100644 src/python/evaluation/inspectors/inspectors_stat/issues/__init__.py create mode 100644 src/python/evaluation/inspectors/inspectors_stat/issues/flake8_all_issues.py create mode 100644 src/python/evaluation/inspectors/inspectors_stat/issues/other_issues.py create mode 100644 src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py create mode 100644 src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py diff --git a/src/python/evaluation/inspectors/inspectors_stat/README.md b/src/python/evaluation/inspectors/inspectors_stat/README.md new file mode 100644 index 00000000..c33c16f3 --- /dev/null +++ b/src/python/evaluation/inspectors/inspectors_stat/README.md @@ -0,0 +1,45 @@ +# Hyperstyle evaluation: inspectors statistics gathering + +This module allows gathering statistics about inspections that are used +during analysis for a specific language. We collect all available issues' keys, +removed ignored ones and gather statistics for fours main categories: + +- code style issues; +- best practice issues; +- error-prone issues; +- code complexity issues. + +More information about these categories can be found on [this](https://support.hyperskill.org/hc/en-us/articles/360049582712-Code-style-Code-quality) page. + +## Current statistics + +The current statistics is: + + | Error prone | Code style | Code complexity | Best practice +---------------| -----------|-------------|-----------------|-------------- +| Python | 162 | 146 | 35 | 254 | +| Java | 105 | 133 | 15 | 203 | +| JavaScript | 15 | 17 | 1 | 34 | +| Kotlin | 21 | 70 | 12 | 75 | + + +## Usage + +Run the [statistics_gathering.py](statistics_gathering.py) with the arguments from command line. + +Required arguments: + +`language` — the language for which statistics will be gathering. +Available values are: `python`, `java`, `kotlin`, `javascript`. + +An example of the output is: + +```text +Collected statistics for python language: +best practices: 254 times; +code style: 146 times; +complexity: 35 times; +error prone: 162 times; +undefined: 3 times; +Note: undefined means a category that is not categorized among the four main categories. Most likely it is info category +``` \ No newline at end of file diff --git a/src/python/evaluation/inspectors/inspectors_stat/__init__.py b/src/python/evaluation/inspectors/inspectors_stat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/python/evaluation/inspectors/inspectors_stat/issues/__init__.py b/src/python/evaluation/inspectors/inspectors_stat/issues/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/python/evaluation/inspectors/inspectors_stat/issues/flake8_all_issues.py b/src/python/evaluation/inspectors/inspectors_stat/issues/flake8_all_issues.py new file mode 100644 index 00000000..eecc92dc --- /dev/null +++ b/src/python/evaluation/inspectors/inspectors_stat/issues/flake8_all_issues.py @@ -0,0 +1,601 @@ +# According to https://gist.github.com/sharkykh/c76c80feadc8f33b129d846999210ba3 +ALL_STANDARD_ISSUES = { + # Indentation + 'E101': 'indentation contains mixed spaces and tabs', + 'E111': 'indentation is not a multiple of four', + 'E112': 'expected an indented block', + 'E113': 'unexpected indentation', + 'E114': 'indentation is not a multiple of four (comment)', + 'E115': 'expected an indented block (comment)', + 'E116': 'unexpected indentation (comment)', + 'E121': 'continuation line under-indented for hanging indent', + 'E122': 'continuation line missing indentation or outdented', + 'E123': 'closing bracket does not match indentation of opening bracket\'s line', + 'E124': 'closing bracket does not match visual indentation', + 'E125': 'continuation line with same indent as next logical line', + 'E126': 'continuation line over-indented for hanging indent', + 'E127': 'continuation line over-indented for visual indent', + 'E128': 'continuation line under-indented for visual indent', + 'E129': 'visually indented line with same indent as next logical line', + 'E131': 'continuation line unaligned for hanging indent', + 'E133': 'closing bracket is missing indentation', + + # Whitespace + 'E201': 'whitespace after \'(\'', + 'E202': 'whitespace before \')\'', + 'E203': 'whitespace before \':\'', + 'E211': 'whitespace before \'(\'', + 'E221': 'multiple spaces before operator', + 'E222': 'multiple spaces after operator', + 'E223': 'tab before operator', + 'E224': 'tab after operator', + 'E225': 'missing whitespace around operator', + 'E226': 'missing whitespace around arithmetic operator', + 'E227': 'missing whitespace around bitwise or shift operator', + 'E228': 'missing whitespace around modulo operator', + 'E231': 'missing whitespace after \',\', \';\', or \':\'', + 'E241': 'multiple spaces after \',\'', + 'E242': 'tab after \',\'', + 'E251': 'unexpected spaces around keyword / parameter equals', + 'E261': 'at least two spaces before inline comment', + 'E262': 'inline comment should start with \'# \'', + 'E265': 'block comment should start with \'# \'', + 'E266': 'too many leading \'#\' for block comment', + 'E271': 'multiple spaces after keyword', + 'E272': 'multiple spaces before keyword', + 'E273': 'tab after keyword', + 'E274': 'tab before keyword', + 'E275': 'missing whitespace after keyword', + + # Blank line + 'E301': 'expected 1 blank line, found 0', + 'E302': 'expected 2 blank lines, found 0', + 'E303': 'too many blank lines (3)', + 'E304': 'blank lines found after function decorator', + 'E305': 'expected 2 blank lines after end of function or class', + 'E306': 'expected 1 blank line before a nested definition', + + # Import + 'E401': 'multiple imports on one line', + 'E402': 'module level import not at top of file', + + # Line length + 'E501': 'line too long (82 > 79 characters)', + 'E502': 'the backslash is redundant between brackets', + + # Statement + 'E701': 'multiple statements on one line (colon)', + 'E702': 'multiple statements on one line (semicolon)', + 'E703': 'statement ends with a semicolon', + 'E704': 'multiple statements on one line (def)', + 'E711': 'comparison to None should be \'if cond is None:\'', + 'E712': 'comparison to True should be \'if cond is True:\' or \'if cond:\'', + 'E713': 'test for membership should be \'not in\'', + 'E714': 'test for object identity should be \'is not\'', + 'E721': 'do not compare types, use \'isinstance()\'', + 'E722': 'do not use bare except, specify exception instead', + 'E731': 'do not use variables named \'l\', \'O\', or \'I\'', + 'E741': 'do not use variables named \'l\', \'O\', or \'I\'', + 'E742': 'do not define classes named \'l\', \'O\', or \'I\'', + 'E743': 'do not define functions named \'l\', \'O\', or \'I\'', + + # Runtime + 'E901': 'SyntaxError or IndentationError', + 'E902': 'IOError', + + # Indentation warning + 'W191': 'indentation contains tabs', + + # Whitespace warning + 'W291': 'trailing whitespace', + 'W292': 'no newline at end of file', + 'W293': 'blank line contains whitespace', + + # Blank line warning + 'W391': 'blank line at end of file', + + # Line break warning + 'W503': 'line break before binary operator', + 'W504': 'line break after binary operator', + 'W505': 'doc line too long (82 > 79 characters)', + + # Deprecation warning + 'W601': '.has_key() is deprecated, use \'in\'', + 'W602': 'deprecated form of raising exception', + 'W603': '\'<>\' is deprecated, use \'!=\'', + 'W604': 'backticks are deprecated, use \'repr()\'', + 'W605': 'invalid escape sequence \'x\'', + 'W606': '\'async\' and \'await\' are reserved keywords starting with Python 3.7', + + 'F401': 'module imported but unused', + 'F402': 'import module from line N shadowed by loop variable', + 'F403': '\'from module import *\' used; unable to detect undefined names', + 'F404': 'future import(s) name after other statements', + 'F405': 'name may be undefined, or defined from star imports: module', + 'F406': '\'from module import *\' only allowed at module level', + 'F407': 'an undefined __future__ feature name was imported', + + 'F601': 'dictionary key name repeated with different values', + 'F602': 'dictionary key variable name repeated with different values', + 'F621': 'too many expressions in an assignment with star-unpacking', + 'F622': 'two or more starred expressions in an assignment (a, *b, *c = d)', + 'F631': 'assertion test is a tuple, which are always True', + + 'F701': 'a break statement outside of a while or for loop', + 'F702': 'a continue statement outside of a while or for loop', + 'F703': 'a continue statement in a finally block in a loop', + 'F704': 'a yield or yield from statement outside of a function', + 'F705': 'a return statement with arguments inside a generator', + 'F706': 'a return statement outside of a function/method', + 'F707': 'an except: block as not the last exception handler', + 'F721': 'doctest syntax error', + 'F722': 'syntax error in forward type annotation', + + 'F811': 'redefinition of unused name from line N', + 'F812': 'list comprehension redefines name from line N', + 'F821': 'undefined name name', + 'F822': 'undefined name name in __all__', + 'F823': 'local variable name ... referenced before assignment', + 'F831': 'duplicate argument name in function definition', + 'F841': 'local variable name is assigned to but never used', + + 'F901': 'raise NotImplemented should be raise NotImplementedError', + + 'N801': 'class names should use CapWords convention', + 'N802': 'function name should be lowercase', + 'N803': 'argument name should be lowercase', + 'N804': 'first argument of a classmethod should be named \'cls\'', + 'N805': 'first argument of a method should be named \'self\'', + 'N806': 'variable in function should be lowercase', + 'N807': 'function name should not start or end with \'__\'', + 'N811': 'constant imported as non constant', + 'N812': 'lowercase imported as non lowercase', + 'N813': 'camelcase imported as lowercase', + 'N814': 'camelcase imported as constant', + 'N815': 'mixedCase variable in class scope', + 'N816': 'mixedCase variable in global scope', +} + +# According to https://pypi.org/project/flake8-bugbear/ +ALL_BUGBEAR_ISSUES = { + 'B001': 'Do not use bare except:, it also catches unexpected events like memory errors, interrupts, system exit, ' + 'and so on. Prefer except Exception:. If you’re sure what you’re doing, be explicit and write except ' + 'BaseException:. Disable E722 to avoid duplicate warnings.', + 'B002': 'Python does not support the unary prefix increment. Writing ++n is equivalent to +(+(n)), which equals ' + 'n. You meant n += 1.', + 'B003': 'Assigning to os.environ doesn’t clear the environment. Subprocesses are going to see outdated variables, ' + 'in disagreement with the current process. Use os.environ.clear() or the env= argument to Popen.', + 'B004': 'Using hasattr(x, \'__call__\') to test if x is callable is unreliable. If x implements custom ' + '__getattr__ or its __call__ is itself not callable, you might get misleading results. Use callable(x) ' + 'for consistent results.', + 'B005': 'Using .strip() with multi-character strings is misleading the reader. It looks like stripping a ' + 'substring. Move your character set to a constant if this is deliberate. Use .replace() or regular ' + 'expressions to remove string fragments.', + 'B006': 'Do not use mutable data structures for argument defaults. They are created during function definition ' + 'time. All calls to the function reuse this one instance of that data structure, persisting changes ' + 'between them.', + 'B007': 'Loop control variable not used within the loop body. If this is intended, start the name with an ' + 'underscore.', + 'B008': 'Do not perform function calls in argument defaults. The call is performed only once at function ' + 'definition time. All calls to your function will reuse the result of that definition-time function call. ' + 'If this is intended, assign the function call to a module-level variable and use that variable as a ' + 'default value.', + 'B009': 'Do not call getattr(x, \'attr\'), instead use normal property access: x.attr. Missing a default to ' + 'getattr will cause an AttributeError to be raised for non-existent properties. There is no additional ' + 'safety in using getattr if you know the attribute name ahead of time.', + 'B010': 'Do not call setattr(x, \'attr\', val), instead use normal property access: x.attr = val. There is no ' + 'additional safety in using setattr if you know the attribute name ahead of time.', + 'B011': 'Do not call assert False since python -O removes these calls. Instead callers should raise ' + 'AssertionError().', + 'B012': 'Use of break, continue or return inside finally blocks will silence exceptions or override return values ' + 'from the try or except blocks. To silence an exception, do it explicitly in the except block. To ' + 'properly use a break, continue or return refactor your code so these statements are not in the finally ' + 'block.', + 'B013': 'A length-one tuple literal is redundant. Write except SomeError: instead of except (SomeError,):.', + 'B014': 'Redundant exception types in except (Exception, TypeError):. Write except Exception:, which catches ' + 'exactly the same exceptions.', + 'B015': 'Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend assert or ' + 'remove it.', + 'B016': 'Cannot raise a literal. Did you intend to return it or raise an Exception?', + 'B017': 'self.assertRaises(Exception): should be considered evil. It can lead to your test passing even if the ' + 'code being tested is never executed due to a typo. Either assert for a more specific exception (builtin ' + 'or custom), use assertRaisesRegex, or use the context manager form of assertRaises (with ' + 'self.assertRaises(Exception) as ex:) with an assertion against the data available in ex.', + + # Python 3 compatibility warnings + 'B301': 'Python 3 does not include .iter* methods on dictionaries. The default behavior is to return iterables. ' + 'Simply remove the iter prefix from the method. For Python 2 compatibility, also prefer the Python 3 ' + 'equivalent if you expect that the size of the dict to be small and bounded. The performance regression ' + 'on Python 2 will be negligible and the code is going to be the clearest. Alternatively, use six.iter* or ' + 'future.utils.iter*.', + 'B302': 'Python 3 does not include .view* methods on dictionaries. The default behavior is to return viewables. ' + 'Simply remove the view prefix from the method. For Python 2 compatibility, also prefer the Python 3 ' + 'equivalent if you expect that the size of the dict to be small and bounded. The performance regression ' + 'on Python 2 will be negligible and the code is going to be the clearest. Alternatively, use six.view* or ' + 'future.utils.view*.', + 'B303': 'The __metaclass__ attribute on a class definition does nothing on Python 3. Use class MyClass(BaseClass, ' + 'metaclass=...). For Python 2 compatibility, use six.add_metaclass.', + 'B304': 'sys.maxint is not a thing on Python 3. Use sys.maxsize.', + 'B305': '.next() is not a thing on Python 3. Use the next() builtin. For Python 2 compatibility, use six.next().', + 'B306': 'BaseException.message has been deprecated as of Python 2.6 and is removed in Python 3. Use str(e) to ' + 'access the user-readable message. Use e.args to access arguments passed to the exception.', +} + +# According to https://github.com/gforcada/flake8-builtins/blob/master/flake8_builtins.py#L49 +ALL_BUILTINS_ISSUES = { + 'A001': 'variable is shadowing a python builtin', + 'A002': 'argument is shadowing a python builtin', + 'A003': 'class attribute is shadowing a python builtin', +} + +# According to https://github.com/afonasev/flake8-return +ALL_RETURN_ISSUES = { + 'R501': 'do not explicitly return None in function if it is the only possible return value.', + 'R502': 'do not implicitly return None in function able to return non-None value.', + 'R503': 'missing explicit return at the end of function able to return non-None value.', + 'R504': 'unecessary variable assignement before return statement.', +} + +# According to https://pypi.org/project/flake8-string-format/ +ALL_FORMAT_STRING_ISSUES = { + # Presence of implicit parameters + 'P101': 'format string does contain unindexed parameters', + 'P102': 'docstring does contain unindexed parameters', + 'P103': 'other string does contain unindexed parameters', + + # Missing values in the parameters + 'P201': 'format call uses too large index (INDEX)', + 'P202': 'format call uses missing keyword (KEYWORD)', + 'P203': 'format call uses keyword arguments but no named entries', + 'P204': 'format call uses variable arguments but no numbered entries', + 'P205': 'format call uses implicit and explicit indexes together', + + # Unused values in the parameters + 'P301': 'format call provides unused index (INDEX)', + 'P302': 'format call provides unused keyword (KEYWORD)', +} + +# According to https://pypi.org/project/flake8-import-order/ +ALL_IMPORT_ORDER_ISSUES = { + 'I100': 'Your import statements are in the wrong order.', + 'I101': 'The names in your from import are in the wrong order.', + 'I201': 'Missing newline between import groups.', + 'I202': 'Additional newline in a group of imports.', +} + +# According to https://pypi.org/project/flake8-comprehensions/ +ALL_COMPREHENSIONS_ISSUES = { + 'C400': 'Unnecessary generator - rewrite as a comprehension.', + 'C401': 'Unnecessary generator - rewrite as a comprehension.', + 'C402': 'Unnecessary generator - rewrite as a comprehension.', + + 'C403': 'Unnecessary list comprehension - rewrite as a comprehension.', + 'C404': 'Unnecessary list comprehension - rewrite as a comprehension.', + + 'C405': 'Unnecessary literal - rewrite as a literal.', + 'C406': 'Unnecessary literal - rewrite as a literal.', + + 'C408': 'Unnecessary call - rewrite as a literal.', + + 'C409': ' Unnecessary passed to () - (remove the outer call to ``' + '()/rewrite as a `` literal).', + 'C410': ' Unnecessary passed to () - (remove the outer call to ``' + '()/rewrite as a `` literal).', + + 'C411': 'Unnecessary list call - remove the outer call to list().', + + 'C413': 'Unnecessary call around sorted().', + + 'C414': 'Unnecessary call within ().', + + 'C415': 'Unnecessary subscript reversal of iterable within ().', + + 'C416': 'Unnecessary comprehension - rewrite using ().', +} + +# According to https://pypi.org/project/flake8-spellcheck/ +ALL_SPELLCHECK_ISSUES = { + 'SC100': 'Spelling error in comments', + 'SC200': 'Spelling error in name (e.g. variable, function, class)', +} + +# According to https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html +ALL_WPS_ISSUES = { + # Naming + 'WPS100': 'Found wrong module name', + 'WPS101': 'Found wrong module magic name', + 'WPS102': 'Found incorrect module name pattern', + 'WPS110': 'Found wrong variable name', + 'WPS111': 'Found too short name', + 'WPS112': 'Found private name pattern', + 'WPS113': 'Found same alias import', + 'WPS114': 'Found underscored number name pattern', + 'WPS115': 'Found upper-case constant in a class', + 'WPS116': 'Found consecutive underscores name', + 'WPS117': 'Found name reserved for first argument', + 'WPS118': 'Found too long name', + 'WPS119': 'Found unicode name', + 'WPS120': 'Found regular name with trailing underscore', + 'WPS121': 'Found usage of a variable marked as unused', + 'WPS122': 'Found all unused variables definition', + 'WPS123': 'Found wrong unused variable name', + 'WPS124': 'Found unreadable characters combination', + 'WPS125': 'Found builtin shadowing', + + # Complexity + 'WPS200': 'Found module with high Jones Complexity score', + 'WPS201': 'Found module with too many imports', + 'WPS202': 'Found too many module members', + 'WPS203': 'Found module with too many imported names', + 'WPS204': 'Found overused expression', + 'WPS210': 'Found too many local variables', + 'WPS211': 'Found too many arguments', + 'WPS212': 'Found too many return statements', + 'WPS213': 'Found too many expressions', + 'WPS214': 'Found too many methods', + 'WPS215': 'Too many base classes', + 'WPS216': 'Too many decorators', + 'WPS217': 'Found too many await expressions', + 'WPS218': 'Found too many `assert` statements', + 'WPS219': 'Found too deep access level', + 'WPS220': 'Found too deep nesting', + 'WPS221': 'Found line with high Jones Complexity', + 'WPS222': 'Found a condition with too much logic', + 'WPS223': 'Found too many `elif` branches', + 'WPS224': 'Found a comprehension with too many `for` statements', + 'WPS225': 'Found too many `except` cases', + 'WPS226': 'Found string constant over-use', + 'WPS227': 'Found too long yield tuple', + 'WPS228': 'Found too long compare', + 'WPS229': 'Found too long ``try`` body length', + 'WPS230': 'Found too many public instance attributes', + 'WPS231': 'Found function with too much cognitive complexity', + 'WPS232': 'Found module cognitive complexity that is too high', + 'WPS233': 'Found call chain that is too long', + 'WPS234': 'Found overly complex annotation', + 'WPS235': 'Found too many imported names from a module', + 'WPS236': 'Found too many variables used to unpack a tuple', + 'WPS237': 'Found a too complex `f` string', + 'WPS238': 'Found too many raises in a function', + + # Consistency + 'WPS300': 'Found local folder import', + 'WPS301': 'Found dotted raw import', + 'WPS302': 'Found unicode string prefix', + 'WPS303': 'Found underscored number', + 'WPS304': 'Found partial float', + 'WPS305': 'Found `f` string', + 'WPS306': 'Found class without a base class', + 'WPS307': 'Found list comprehension with multiple `if`s', + 'WPS308': 'Found constant comparison', + 'WPS309': 'Found reversed compare order', + 'WPS310': 'Found bad number suffix', + 'WPS311': 'Found multiple `in` compares', + 'WPS312': 'Found comparison of a variable to itself', + 'WPS313': 'Found parenthesis immediately after a keyword', + 'WPS314': 'Found conditional that always evaluates the same', + 'WPS315': 'Found extra `object` in parent classes list', + 'WPS316': 'Found context manager with too many assignments', + 'WPS317': 'Found incorrect multi-line parameters', + 'WPS318': 'Found extra indentation', + 'WPS319': 'Found bracket in wrong position', + 'WPS320': 'Found multi-line function type annotation', + 'WPS321': 'Found uppercase string modifier', + 'WPS322': 'Found incorrect multi-line string', + 'WPS323': 'Found `%` string formatting', + 'WPS324': 'Found inconsistent `return` statement', + 'WPS325': 'Found inconsistent `yield` statement', + 'WPS326': 'Found implicit string concatenation', + 'WPS327': 'Found useless `continue` at the end of the loop', + 'WPS328': 'Found useless node', + 'WPS329': 'Found useless `except` case', + 'WPS330': 'Found unnecessary operator', + 'WPS332': 'Found walrus operator', + 'WPS333': 'Found implicit complex compare', + 'WPS334': 'Found reversed complex comparison', + 'WPS335': 'Found incorrect `for` loop iter type', + 'WPS336': 'Found explicit string concatenation', + 'WPS337': 'Found multiline conditions', + 'WPS338': 'Found incorrect order of methods in a class', + 'WPS339': 'Found number with meaningless zeros', + 'WPS340': 'Found exponent number with positive exponent', + 'WPS341': 'Found wrong hex number case', + 'WPS342': 'Found implicit raw string', + 'WPS343': 'Found wrong complex number suffix', + 'WPS344': 'Found explicit zero division', + 'WPS345': 'Found meaningless number operation', + 'WPS346': 'Found wrong operation sign', + 'WPS347': 'Found vague import that may cause confusion', + 'WPS348': 'Found a line that starts with a dot', + 'WPS349': 'Found redundant subscript slice', + 'WPS350': 'Found usable augmented assign pattern', + 'WPS351': 'Found unnecessary literals', + 'WPS352': 'Found multiline loop', + 'WPS353': 'Found incorrect `yield from` target', + 'WPS354': 'Found consecutive `yield` expressions', + 'WPS355': 'Found an unnecessary blank line before a bracket', + 'WPS356': 'Found an unnecessary iterable unpacking', + 'WPS357': 'Found a ``\\r`` (carriage return) line break', + 'WPS358': 'Found a float zero (0.0)', + 'WPS359': 'Found an iterable unpacking to list', + 'WPS360': 'Found an unnecessary use of a raw string', + 'WPS361': 'Found an inconsistently structured comprehension', + 'WPS362': 'Found assignment to a subscript slice', + + # Best practices + 'WPS400': 'Found wrong magic comment', + 'WPS401': 'Found wrong doc comment', + 'WPS402': 'Found `noqa` comments overuse', + 'WPS403': 'Found `noqa` comments overuse', + 'WPS404': 'Found complex default value', + 'WPS405': 'Found wrong `for` loop variable definition', + 'WPS406': 'Found wrong context manager variable definition', + 'WPS407': 'Found mutable module constant', + 'WPS408': 'Found duplicate logical condition', + 'WPS409': 'Found heterogeneous compare', + 'WPS410': 'Found wrong metadata variable', + 'WPS411': 'Found empty module', + 'WPS412': 'Found `__init__.py` module with logic', + 'WPS413': 'Found bad magic module function', + 'WPS414': 'Found incorrect unpacking target', + 'WPS415': 'Found duplicate exception', + 'WPS416': 'Found `yield` inside comprehension', + 'WPS417': 'Found non-unique item in hash', + 'WPS418': 'Found exception inherited from `BaseException`', + 'WPS419': 'Found `try`/`else`/`finally` with multiple return paths', + 'WPS420': 'Found wrong keyword', + 'WPS421': 'Found wrong function call', + 'WPS422': 'Found future import', + 'WPS423': 'Found raise NotImplemented', + 'WPS424': 'Found except `BaseException`', + 'WPS425': 'Found boolean non-keyword argument', + 'WPS426': 'Found `lambda` in loop\'s body', + 'WPS427': 'Found unreachable code', + 'WPS428': 'Found statement that has no effect', + 'WPS429': 'Found multiple assign targets', + 'WPS430': 'Found nested function', + 'WPS431': 'Found nested class', + 'WPS432': 'Found magic number', + 'WPS433': 'Found nested import', + 'WPS434': 'Found reassigning variable to itself', + 'WPS435': 'Found list multiply', + 'WPS436': 'Found protected module import', + 'WPS437': 'Found protected attribute usage', + 'WPS438': 'Found `StopIteration` raising inside generator', + 'WPS439': 'Found unicode escape in a binary string', + 'WPS440': 'Found block variables overlap', + 'WPS441': 'Found control variable used after block', + 'WPS442': 'Found outer scope names shadowing', + 'WPS443': 'Found unhashable item', + 'WPS444': 'Found incorrect keyword condition', + 'WPS445': 'Found incorrectly named keyword in the starred dict', + 'WPS446': 'Found approximate constant', + 'WPS447': 'Found alphabet as strings', + 'WPS448': 'Found incorrect exception order', + 'WPS449': 'Found float used as a key', + 'WPS450': 'Found protected object import', + 'WPS451': 'Found positional-only argument', + 'WPS452': 'Found `break` or `continue` in `finally` block', + 'WPS453': 'Found executable mismatch', + 'WPS454': 'Found wrong `raise` exception type', + 'WPS455': 'Found non-trivial expression as an argument for "except"', + 'WPS456': 'Found "NaN" as argument to float()', + 'WPS457': 'Found an infinite while loop', + 'WPS458': 'Found imports collision', + 'WPS459': 'Found comparison with float or complex number', + 'WPS460': 'Found single element destructuring', + 'WPS461': 'Forbidden inline ignore', + 'WPS462': 'Wrong multiline string usage', + 'WPS463': 'Found a getter without a return value', + 'WPS464': 'Found empty comment', + 'WPS465': 'Found likely bitwise and boolean operation mixup', + 'WPS466': 'Found new-styled decorator', + + # Refactoring + 'WPS500': 'Found `else` in a loop without `break`', + 'WPS501': 'Found `finally` in `try` block without `except`', + 'WPS502': 'Found simplifiable `if` condition', + 'WPS503': 'Found useless returning `else` statement', + 'WPS504': 'Found negated condition', + 'WPS505': 'Found nested `try` block', + 'WPS506': 'Found useless lambda declaration', + 'WPS507': 'Found useless `len()` compare', + 'WPS508': 'Found incorrect `not` with compare usage', + 'WPS509': 'Found incorrectly nested ternary', + 'WPS510': 'Found `in` used with a non-set container', + 'WPS511': 'Found separate `isinstance` calls that can be merged for', + 'WPS512': 'Found `isinstance` call with a single element tuple', + 'WPS513': 'Found implicit `elif` condition', + 'WPS514': 'Found implicit `in` condition', + 'WPS515': 'Found `open()` used without a context manager', + 'WPS516': 'Found `type()` used to compare types', + 'WPS517': 'Found pointless starred expression', + 'WPS518': 'Found implicit `enumerate()` call', + 'WPS519': 'Found implicit `sum()` call', + 'WPS520': 'Found compare with falsy constant', + 'WPS521': 'Found wrong `is` compare', + 'WPS522': 'Found implicit primitive in a form of `lambda`', + 'WPS523': 'Found incorrectly swapped variables', + 'WPS524': 'Found self assignment with refactored assignment', + 'WPS525': 'Found wrong `in` compare with single item container', + 'WPS526': 'Found implicit `yield from` usage', + 'WPS527': 'Found not a tuple used as an argument', + 'WPS528': 'Found implicit `.items()` usage', + 'WPS529': 'Found implicit `.get()` dict usage', + 'WPS530': 'Found implicit negative index', + 'WPS531': 'Found simplifiable returning `if` condition in a function', + + # OOP + 'WPS600': 'Found subclassing a builtin', + 'WPS601': 'Found shadowed class attribute', + 'WPS602': 'Found using `@staticmethod`', + 'WPS603': 'Found using restricted magic method', + 'WPS604': 'Found incorrect node inside `class` body', + 'WPS605': 'Found method without arguments', + 'WPS606': 'Found incorrect base class', + 'WPS607': 'Found incorrect `__slots__` syntax', + 'WPS608': 'Found incorrect `super()` call', + 'WPS609': 'Found direct magic attribute usage', + 'WPS610': 'Found forbidden `async` magic method usage', + 'WPS611': 'Found forbidden `yield` magic method usage', + 'WPS612': 'Found useless overwritten method', + 'WPS613': 'Found incorrect `super()` call context: incorrect name access', + 'WPS614': 'Found descriptor applied on a function', + 'WPS615': 'Found unpythonic getter or setter', +} + +# According to the flake8 inspector config +FLAKE8_DISABLED_ISSUES = { + 'W291', + 'W292', # no newline at end of file + 'W293', + 'W503', # line break before binary operator + 'C408', # unnecessary (dict/list/tuple) call - rewrite as a literal + 'E501', # line too long + 'E800', # commented out code + 'I101', # order of imports within a line + 'I202', # additional new line + 'Q000', + 'E301', 'E302', 'E303', 'E304', 'E305', + 'E402', # module level import not at top of file + 'I100', # Import statements are in the wrong order + # WPS: Naming + 'WPS110', # Forbid blacklisted variable names. + 'WPS111', # Forbid short variable or module names. + 'WPS112', # Forbid private name pattern. + 'WPS114', # Forbid names with underscored numbers pattern. + 'WPS125', # Forbid variable or module names which shadow builtin names. + # WPS: Consistency + 'WPS303', # Forbid underscores in numbers. + 'WPS305', # Forbid f strings. + 'WPS306', # Forbid writing classes without base classes. + 'WPS318', # Forbid extra indentation. + 'WPS323', # Forbid % formatting on strings. + 'WPS324', # Enforce consistent return statements. + 'WPS335', # Forbid wrong for loop iter targets. + 'WPS358', # Forbid using float zeros: 0.0. + 'WPS362', # Forbid assignment to a subscript slice. + # WPS: Best practices + 'WPS404', # Forbid complex defaults. + 'WPS420', # Forbid some python keywords. + 'WPS421', # Forbid calling some built-in functions.(e.g., print) + 'WPS429', # Forbid multiple assignments on the same line. + 'WPS430', # Forbid nested functions. + 'WPS431', # Forbid nested classes. + 'WPS435', # Forbid multiplying lists. + # WPS: Refactoring + 'WPS518', # Forbid implicit enumerate() calls. + 'WPS527', # Require tuples as arguments for frozenset. + # WPS: OOP + 'WPS602', # Forbid @staticmethod decorator. + # flake8-string-format + 'P101', + 'P102', + 'P103', + 'F522', # unused named arguments. + 'F523', # unused positional arguments. + 'F524', # missing argument. + 'F525', # mixing automatic and manual numbering. + # flake8-commas + 'C814', # missing trailing comma in Python 2 +} diff --git a/src/python/evaluation/inspectors/inspectors_stat/issues/other_issues.py b/src/python/evaluation/inspectors/inspectors_stat/issues/other_issues.py new file mode 100644 index 00000000..365e6d74 --- /dev/null +++ b/src/python/evaluation/inspectors/inspectors_stat/issues/other_issues.py @@ -0,0 +1,8 @@ +PYTHON_RADON_ISSUES = { + 'RAD100': 'MAINTAINABILITY index', +} + +PYTHON_AST_ISSUES = { + 'C001': 'Boolean expressions length', + 'C002': 'Functions length', +} diff --git a/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py b/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py new file mode 100644 index 00000000..3e5ec04d --- /dev/null +++ b/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py @@ -0,0 +1,496 @@ +# According to https://seanwasere.com/pylint--list-msgs/ +ALL_ISSUES = { + 'C0102': 'Used when the name is listed in the black list (unauthorized names).', + 'C0103': 'Used when the name doesn\'t conform to naming rules associated to its type (constant, variable, ' + 'class...).', + 'C0111': 'Used when a module, function, class or method has no docstring.Some special methods like __init__ ' + 'doesn\'t necessary require a docstring.', + 'C0112': 'Used when a module, function, class or method has an empty docstring (it would be too easy ;).', + 'C0113': 'Used when a boolean expression contains an unneeded negation.', + 'C0121': 'Used when an expression is compared to singleton values like True, False or None.', + 'C0122': 'Used when the constant is placed on the left side of a comparison. It is usually clearer in intent to ' + 'place it in the right hand side of the comparison.', + 'C0123': 'The idiomatic way to perform an explicit typecheck in Python is to use isinstance(x, Y) rather than ' + 'type(x) == Y, type(x) is Y. Though there are unusual situations where these give different results.', + 'C0200': 'Emitted when code that iterates with range and len is encountered. Such code can be simplified by using ' + 'the enumerate builtin.', + 'C0201': 'Emitted when the keys of a dictionary are iterated through the .keys() method. It is enough to just ' + 'iterate through the dictionary itself, as in "for key in dictionary".', + 'C0202': 'Used when a class method has a first argument named differently than the value specified in ' + 'valid-classmethod-first-arg option (default to "cls"), recommended to easily differentiate them from ' + 'regular instance methods.', + 'C0203': 'Used when a metaclass method has a first argument named differently than the value specified in ' + 'valid-classmethod-first-arg option (default to "cls"), recommended to easily differentiate them from ' + 'regular instance methods.', + 'C0204': 'Used when a metaclass class method has a first argument named differently than the value specified in ' + 'valid-metaclass-classmethod-first-arg option (default to "mcs"), recommended to easily differentiate ' + 'them from regular instance methods.', + 'C0205': 'Used when a class __slots__ is a simple string, rather than an iterable.', + 'C0301': 'Used when a line is longer than a given number of characters.', + 'C0302': 'Used when a module has too many lines, reducing its readability.', + 'C0303': 'Used when there is whitespace between the end of a line and the newline.', + 'C0304': 'Used when the last line in a file is missing a newline.', + 'C0305': 'Used when there are trailing blank lines in a file.', + 'C0321': 'Used when more than on statement are found on the same line.', + 'C0325': 'Used when a single item in parentheses follows an if, for, or other keyword.', + 'C0326': 'Used when a wrong number of spaces is used around an operator, bracket or block opener.', + 'C0327': 'Used when there are mixed (LF and CRLF) newline signs in a file.', + 'C0328': 'Used when there is different newline than expected.', + 'C0330': 'bad-continuation', + 'C0401': 'Used when a word in comment is not spelled correctly.', + 'C0402': 'Used when a word in docstring is not spelled correctly.', + 'C0403': 'Used when a word in docstring cannot be checked by enchant.', + 'C0410': 'Used when import statement importing multiple modules is detected.', + 'C0411': 'Used when PEP8 import order is not respected (standard imports first, then third-party libraries, ' + 'then local imports)', + 'C0412': 'Used when imports are not grouped by packages', + 'C0413': 'Used when code and imports are mixed', + 'C0414': 'Used when an import alias is same as original package.e.g using import numpy as numpy instead of import ' + 'numpy as np', + 'C1801': 'Used when Pylint detects that len(sequence) is being used inside a condition to determine if a sequence ' + 'is empty. Instead of comparing the length to 0, rely on the fact that empty sequences are false.', + + 'E0001': 'Used when a syntax error is raised for a module.', + 'E0011': 'Used when an unknown inline option is encountered.', + 'E0012': 'Used when a bad value for an inline option is encountered.', + 'E0100': 'Used when the special class method __init__ is turned into a generator by a yield in its body.', + 'E0101': 'Used when the special class method __init__ has an explicit return value.', + 'E0102': 'Used when a function / class / method is redefined.', + 'E0103': 'Used when break or continue keywords are used outside a loop.', + 'E0104': 'Used when a "return" statement is found outside a function or method.', + 'E0105': 'Used when a "yield" statement is found outside a function or method.', + 'E0107': 'Used when you attempt to use the C-style pre-increment or pre-decrement operator -- and ++, ' + 'which doesn\'t exist in Python.', + 'E0108': 'Duplicate argument names in function definitions are syntax errors.', + 'E0110': 'Used when an abstract class with `abc.ABCMeta` as metaclass has abstract methods and is instantiated.', + 'E0111': 'Used when the first argument to reversed() builtin isn\'t a sequence (does not implement __reversed__, ' + 'nor __getitem__ and __len__', + 'E0112': 'Emitted when there are more than one starred expressions (`*x`) in an assignment. This is a SyntaxError.', + 'E0113': 'Emitted when a star expression is used as a starred assignment target.', + 'E0114': 'Emitted when a star expression is not used in an assignment target.', + 'E0115': 'Emitted when a name is both nonlocal and global.', + 'E0116': 'Emitted when the `continue` keyword is found inside a finally clause, which is a SyntaxError.', + 'E0117': 'Emitted when a nonlocal variable does not have an attached name somewhere in the parent scopes', + 'E0118': 'Emitted when a name is used prior a global declaration, which results in an error since Python 3.6. ' + 'This message can\'t be emitted when using Python < 3.6.', + 'E0119': 'Emitted when format function is not called on str object. e.g doing print("value: {}").format(123) ' + 'instead of print("value: {}".format(123)). This might not be what the user intended to do.', + 'E0202': 'Used when a class defines a method which is hidden by an instance attribute from an ancestor class or ' + 'set by some client code.', + 'E0203': 'Used when an instance member is accessed before it\'s actually assigned.', + 'E0211': 'Used when a method which should have the bound instance as first argument has no argument defined.', + 'E0213': 'Used when a method has an attribute different the "self" as first argument. This is considered as an ' + 'error since this is a so common convention that you shouldn\'t break it!', + 'E0236': 'Used when an invalid (non-string) object occurs in __slots__.', + 'E0237': 'Used when assigning to an attribute not defined in the class slots.', + 'E0238': 'Used when an invalid __slots__ is found in class. Only a string, an iterable or a sequence is permitted.', + 'E0239': 'Used when a class inherits from something which is not a class.', + 'E0240': 'Used when a class has an inconsistent method resolution order.', + 'E0241': 'Used when a class has duplicate bases.', + 'E0301': 'Used when an __iter__ method returns something which is not an iterable (i.e. has no `__next__` method)', + 'E0302': 'Emitted when a special method was defined with an invalid number of parameters. If it has too few or ' + 'too many, it might not work at all.', + 'E0303': 'Used when a __len__ method returns something which is not a non-negative integer', + 'E0401': 'Used when pylint has been unable to import a module.', + 'E0402': 'Used when a relative import tries to access too many levels in the current package.', + 'E0601': 'Used when a local variable is accessed before its assignment.', + 'E0602': 'Used when an undefined variable is accessed.', + 'E0603': 'Used when an undefined variable name is referenced in __all__.', + 'E0604': 'Used when an invalid (non-string) object occurs in __all__.', + 'E0611': 'Used when a name cannot be found in a module.', + 'E0633': 'Used when something which is not a sequence is used in an unpack assignment', + 'E0701': 'Used when except clauses are not in the correct order (from the more specific to the more generic). If ' + 'you don\'t fix the order, some exceptions may not be caught by the most specific handler.', + 'E0702': 'Used when something which is neither a class, an instance or a string is raised (i.e. a `TypeError` ' + 'will be raised).', + 'E0703': 'Used when using the syntax "raise ... from ...", where the exception context is not an exception, ' + 'nor None.', + 'E0704': 'Used when a bare raise is not used inside an except clause. This generates an error, since there are no ' + 'active exceptions to be reraised. An exception to this rule is represented by a bare raise inside a ' + 'finally clause, which might work, as long as an exception is raised inside the try block, ' + 'but it is nevertheless a code smell that must not be relied upon.', + 'E0710': 'Used when a new style class which doesn\'t inherit from BaseException is raised.', + 'E0711': 'Used when NotImplemented is raised instead of NotImplementedError', + 'E0712': 'Used when a class which doesn\'t inherit from Exception is used as an exception in an except clause.', + 'E1003': 'Used when another argument than the current class is given as first argument of the super builtin.', + 'E1101': 'Used when a variable is accessed for an unexistent member.', + 'E1102': 'Used when an object being called has been inferred to a non callable object.', + 'E1111': 'Used when an assignment is done on a function call but the inferred function doesn\'t return anything.', + 'E1120': 'Used when a function call passes too few arguments.', + 'E1121': 'Used when a function call passes too many positional arguments.', + 'E1123': 'Used when a function call passes a keyword argument that doesn\'t correspond to one of the function\'s ' + 'parameter names.', + 'E1124': 'Used when a function call would result in assigning multiple values to a function parameter, one value ' + 'from a positional argument and one from a keyword argument.', + 'E1125': 'Used when a function call does not pass a mandatory keyword-only argument.', + 'E1126': 'Used when a sequence type is indexed with an invalid type. Valid types are ints, slices, and objects ' + 'with an __index__ method.', + 'E1127': 'Used when a slice index is not an integer, None, or an object with an __index__ method.', + 'E1128': 'Used when an assignment is done on a function call but the inferred function returns nothing but None.', + 'E1129': 'Used when an instance in a with statement doesn\'t implement the context manager protocol(' + '__enter__/__exit__).', + 'E1130': 'Emitted when a unary operand is used on an object which does not support this type of operation.', + 'E1131': 'Emitted when a binary arithmetic operation between two operands is not supported.', + 'E1132': 'Emitted when a function call got multiple values for a keyword.', + 'E1133': 'Used when a non-iterable value is used in place where iterable is expected', + 'E1134': 'Used when a non-mapping value is used in place where mapping is expected', + 'E1135': 'Emitted when an instance in membership test expression doesn\'t implement membership protocol (' + '__contains__/__iter__/__getitem__).', + 'E1136': 'Emitted when a subscripted value doesn\'t support subscription (i.e. doesn\'t define __getitem__ method ' + 'or __class_getitem__ for a class).', + 'E1137': 'Emitted when an object does not support item assignment (i.e. doesn\'t define __setitem__ method).', + 'E1138': 'Emitted when an object does not support item deletion (i.e. doesn\'t define __delitem__ method).', + 'E1139': 'Emitted whenever we can detect that a class is using, as a metaclass, something which might be invalid ' + 'for using as a metaclass.', + 'E1140': 'Emitted when a dict key is not hashable (i.e. doesn\'t define __hash__ method).', + 'E1200': 'Used when an unsupported format character is used in a logging statement format string.', + 'E1201': 'Used when a logging statement format string terminates before the end of a conversion specifier.', + 'E1205': 'Used when a logging format string is given too many arguments.', + 'E1206': 'Used when a logging format string is given too few arguments.', + 'E1300': 'Used when an unsupported format character is used in a format string.', + 'E1301': 'Used when a format string terminates before the end of a conversion specifier.', + 'E1302': 'Used when a format string contains both named (e.g. \'%(foo)d\') and unnamed (e.g. \'%d\') conversion ' + 'specifiers. This is also used when a named conversion specifier contains * for the minimum field width ' + 'and/or precision.', + 'E1303': 'Used when a format string that uses named conversion specifiers is used with an argument that is not a ' + 'mapping.', + 'E1304': 'Used when a format string that uses named conversion specifiers is used with a dictionary that doesn\'t ' + 'contain all the keys required by the format string.', + 'E1305': 'Used when a format string that uses unnamed conversion specifiers is given too many arguments.', + 'E1306': 'Used when a format string that uses unnamed conversion specifiers is given too few arguments', + 'E1307': 'Used when a type required by format string is not suitable for actual argument type', + 'E1310': 'The argument to a str.{l,r,}strip call contains a duplicate character,', + 'E1507': 'Env manipulation functions support only string type arguments. See ' + 'https://docs.python.org/3/library/os.html#os.getenv.', + 'E1601': 'Used when a print statement is used (`print` is a function in Python 3)', + 'E1602': 'Used when parameter unpacking is specified for a function(Python 3 doesn\'t allow it)', + 'E1603': 'Python3 will not allow implicit unpacking of exceptions in except clauses. See ' + 'http://www.python.org/dev/peps/pep-3110/', + 'E1604': 'Used when the alternate raise syntax \'raise foo, bar\' is used instead of \'raise foo(bar)\'.', + 'E1605': 'Used when the deprecated "``" (backtick) operator is used instead of the str() function.', + 'E1700': 'Used when an `yield` or `yield from` statement is found inside an async function. This message can\'t ' + 'be emitted when using Python < 3.5.', + 'E1701': 'Used when an async context manager is used with an object that does not implement the async context ' + 'management protocol. This message can\'t be emitted when using Python < 3.5.', + + # refactoring related checks + 'R0123': 'Used when comparing an object to a literal, which is usually what you do not want to do, since you can ' + 'compare to a different literal than what was expected altogether.', + 'R0124': 'Used when something is compared against itself.', + 'R0201': 'Used when a method doesn\'t use its bound instance, and so could be written as a function.', + 'R0202': 'Used when a class method is defined without using the decorator syntax.', + 'R0203': 'Used when a static method is defined without using the decorator syntax.', + 'R0205': 'Used when a class inherit from object, which under python3 is implicit, hence can be safely removed ' + 'from bases.', + 'R0401': 'Used when a cyclic import between two or more modules is detected.', + 'R0801': 'Indicates that a set of similar lines has been detected among multiple file. This usually means that ' + 'the code should be refactored to avoid this duplication.', + 'R0901': 'Used when class has too many parent classes, try to reduce this to get a simpler (and so easier to use) ' + 'class.', + 'R0902': 'Used when class has too many instance attributes, try to reduce this to get a simpler (and so easier to ' + 'use) class.', + 'R0903': 'Used when class has too few public methods, so be sure it\'s really worth it.', + 'R0904': 'Used when class has too many public methods, try to reduce this to get a simpler (and so easier to use) ' + 'class.', + 'R0911': 'Used when a function or method has too many return statement, making it hard to follow.', + 'R0912': 'Used when a function or method has too many branches, making it hard to follow.', + 'R0913': 'Used when a function or method takes too many arguments.', + 'R0914': 'Used when a function or method has too many local variables.', + 'R0915': 'Used when a function or method has too many statements. You should then split it in smaller functions / ' + 'methods.', + 'R0916': 'Used when an if statement contains too many boolean expressions.', + 'R1701': 'Used when multiple consecutive isinstance calls can be merged into one.', + 'R1702': 'Used when a function or a method has too many nested blocks. This makes the code less understandable ' + 'and maintainable.', + 'R1703': 'Used when an if statement can be replaced with \'bool(test)\'.', + 'R1704': 'Used when a local name is redefining an argument, which might suggest a potential error. This is taken ' + 'in account only for a handful of name binding operations, such as for iteration, with statement ' + 'assignment and exception handler assignment.', + 'R1705': 'Used in order to highlight an unnecessary block of code following an if containing a return statement. ' + 'As such, it will warn when it encounters an else following a chain of ifs, all of them containing a ' + 'return statement.', + 'R1706': 'Used when one of known pre-python 2.5 ternary syntax is used.', + 'R1707': 'In Python, a tuple is actually created by the comma symbol, not by the parentheses. Unfortunately, ' + 'one can actually create a tuple by misplacing a trailing comma, which can lead to potential weird bugs ' + 'in your code. You should always use parentheses explicitly for creating a tuple.', + 'R1708': 'According to PEP479, the raise of StopIteration to end the loop of a generator may lead to hard to find ' + 'bugs. This PEP specify that raise StopIteration has to be replaced by a simple return statement', + 'R1709': 'Emitted when redundant pre-python 2.5 ternary syntax is used.', + 'R1710': 'According to PEP8, if any return statement returns an expression, any return statements where no value ' + 'is returned should explicitly state this as return None, and an explicit return statement should be ' + 'present at the end of the function (if reachable)', + 'R1711': 'Emitted when a single "return" or "return None" statement is found at the end of function or method ' + 'definition. This statement can safely be removed because Python will implicitly return None', + 'R1712': 'You do not have to use a temporary variable in order to swap variables. Using "tuple unpacking" to ' + 'directly swap variables makes the intention more clear.', + 'R1713': 'Using str.join(sequence) is faster, uses less memory and increases readability compared to for-loop ' + 'iteration.', + 'R1714': 'To check if a variable is equal to one of many values,combine the values into a tuple and check if the ' + 'variable is contained "in" it instead of checking for equality against each of the values.This is ' + 'faster and less verbose.', + 'R1715': 'Using the builtin dict.get for getting a value from a dictionary if a key is present or a default if ' + 'not, is simpler and considered more idiomatic, although sometimes a bit slower', + 'R1716': 'This message is emitted when pylint encounters boolean operation like"a < b and b < c", suggesting ' + 'instead to refactor it to "a < b < c"', + 'R1717': 'Although there is nothing syntactically wrong with this code, it is hard to read and can be simplified ' + 'to a dict comprehension.Also it is faster since you don\'t need to create another transient list', + 'R1718': 'Although there is nothing syntactically wrong with this code, it is hard to read and can be simplified ' + 'to a set comprehension.Also it is faster since you don\'t need to create another transient list', + 'R1719': 'Used when an if expression can be replaced with \'bool(test)\'.', + 'R1720': 'Used in order to highlight an unnecessary block of code following an if containing a raise statement. ' + 'As such, it will warn when it encounters an else following a chain of ifs, all of them containing a ' + 'raise statement.', + + # warnings for stylistic issues, or minor programming issues + 'W0101': 'Used when there is some code behind a "return" or "raise" statement, which will never be accessed.', + 'W0102': 'Used when a mutable value as list or dictionary is detected in a default value for an argument.', + 'W0104': 'Used when a statement doesn\'t have (or at least seems to) any effect.', + 'W0105': 'Used when a string is used as a statement (which of course has no effect). This is a particular case of ' + 'W0104 with its own message so you can easily disable it if you\'re using those strings as ' + 'documentation, instead of comments.', + 'W0106': 'Used when an expression that is not a function call is assigned to nothing. Probably something else was ' + 'intended.', + 'W0107': 'Used when a "pass" statement that can be avoided is encountered.', + 'W0108': 'Used when the body of a lambda expression is a function call on the same argument list as the lambda ' + 'itself; such lambda expressions are in all but a few cases replaceable with the function being called ' + 'in the body of the lambda.', + 'W0109': 'Used when a dictionary expression binds the same key multiple times.', + 'W0111': 'Used when assignment will become invalid in future Python release due to introducing new keyword.', + 'W0120': 'Loops should only have an else clause if they can exit early with a break statement, otherwise the ' + 'statements under else should be on the same scope as the loop itself.', + 'W0122': 'Used when you use the "exec" statement (function for Python 3), to discourage its usage. That doesn\'t ' + 'mean you cannot use it !', + 'W0123': 'Used when you use the "eval" function, to discourage its usage. Consider using `ast.literal_eval` for ' + 'safely evaluating strings containing Python expressions from untrusted sources.', + 'W0124': 'Emitted when a `with` statement component returns multiple values and uses name binding with `as` only ' + 'for a part of those values, as in with ctx() as a, b. This can be misleading, since it\'s not clear if ' + 'the context manager returns a tuple or if the node without a name binding is another context manager.', + 'W0125': 'Emitted when a conditional statement (If or ternary if) uses a constant value for its test. This might ' + 'not be what the user intended to do.', + 'W0143': 'This message is emitted when pylint detects that a comparison with a callable was made, which might ' + 'suggest that some parenthesis were omitted, resulting in potential unwanted behaviour.', + 'W0150': 'Used when a break or a return statement is found inside the finally clause of a try...finally block: ' + 'the exceptions raised in the try clause will be silently swallowed instead of being re-raised.', + 'W0199': 'A call of assert on a tuple will always evaluate to true if the tuple is not empty, and will always ' + 'evaluate to false if it is.', + 'W0201': 'Used when an instance attribute is defined outside the __init__ method.', + 'W0211': 'Used when a static method has "self" or a value specified in valid- classmethod-first-arg option or ' + 'valid-metaclass-classmethod-first-arg option as first argument.', + 'W0212': 'Used when a protected member (i.e. class member with a name beginning with an underscore) is access ' + 'outside the class or a descendant of the class where it\'s defined.', + 'W0221': 'Used when a method has a different number of arguments than in the implemented interface or in an ' + 'overridden method.', + 'W0222': 'Used when a method signature is different than in the implemented interface or in an overridden method.', + 'W0223': 'Used when an abstract method (i.e. raise NotImplementedError) is not overridden in concrete class.', + 'W0231': 'Used when an ancestor class method has an __init__ method which is not called by a derived class.', + 'W0232': 'Used when a class has no __init__ method, neither its parent classes.', + 'W0233': 'Used when an __init__ method is called on a class which is not in the direct ancestors for the analysed ' + 'class.', + 'W0235': 'Used whenever we can detect that an overridden method is useless, relying on super() delegation to do ' + 'the same thing as another method from the MRO.', + 'W0301': 'Used when a statement is ended by a semi-colon (";"), which isn\'t necessary (that\'s python, not C ;).', + 'W0311': 'Used when an unexpected number of indentation\'s tabulations or spaces has been found.', + 'W0312': 'Used when there are some mixed tabs and spaces in a module.', + 'W0401': 'Used when `from module import *` is detected.', + 'W0402': 'Used a module marked as deprecated is imported.', + 'W0404': 'Used when a module is reimported multiple times.', + 'W0406': 'Used when a module is importing itself.', + 'W0410': 'Python 2.5 and greater require __future__ import to be the first non docstring statement in the module.', + 'W0511': 'Used when a warning note as FIXME or XXX is detected.', + 'W0601': 'Used when a variable is defined through the "global" statement but the variable is not defined in the ' + 'module scope.', + 'W0602': 'Used when a variable is defined through the "global" statement but no assignment to this variable is ' + 'done.', + 'W0603': 'Used when you use the "global" statement to update a global variable. Pylint just try to discourage ' + 'this usage. That doesn\'t mean you cannot use it !', + 'W0604': 'Used when you use the "global" statement at the module level since it has no effect', + 'W0611': 'Used when an imported module or variable is not used.', + 'W0612': 'Used when a variable is defined but not used.', + 'W0613': 'Used when a function or method argument is not used.', + 'W0614': 'Used when an imported module or variable is not used from a `\'from X import *\'` style import.', + 'W0621': 'Used when a variable\'s name hides a name defined in the outer scope.', + 'W0622': 'Used when a variable or function override a built-in.', + 'W0623': 'Used when an exception handler assigns the exception to an existing name', + 'W0631': 'Used when a loop variable (i.e. defined by a for loop or a list comprehension or a generator ' + 'expression) is used outside the loop.', + 'W0632': 'Used when there is an unbalanced tuple unpacking in assignment', + 'W0640': 'A variable used in a closure is defined in a loop. This will result in all closures using the same ' + 'value for the closed-over variable.', + 'W0641': 'Used when a variable is defined but might not be used. The possibility comes from the fact that locals(' + ') might be used, which could consume or not the said variable', + 'W0642': 'Invalid assignment to self or cls in instance or class method respectively.', + 'W0702': 'Used when an except clause doesn\'t specify exceptions type to catch.', + 'W0703': 'Used when an except catches a too general exception, possibly burying unrelated errors.', + 'W0705': 'Used when an except catches a type that was already caught by a previous handler.', + 'W0706': 'Used when an except handler uses raise as its first or only operator. This is useless because it raises ' + 'back the exception immediately. Remove the raise operator or the entire try-except-raise block!', + 'W0711': 'Used when the exception to catch is of the form "except A or B:". If intending to catch multiple, ' + 'rewrite as "except (A, B):"', + 'W0715': 'Used when passing multiple arguments to an exception constructor, the first of them a string literal ' + 'containing what appears to be placeholders intended for formatting', + 'W0716': 'Used when an operation is done against an exception, but the operation is not valid for the exception ' + 'in question. Usually emitted when having binary operations between exceptions in except handlers.', + 'W1113': 'When defining a keyword argument before variable positional arguments, one can end up in having ' + 'multiple values passed for the aforementioned parameter in case the method is called with keyword ' + 'arguments.', + 'W1201': 'Used when a logging statement has a call form of "logging.(format_string % (' + 'format_args...))". Such calls should leave string interpolation to the logging method itself and be ' + 'written "logging.(format_string, format_args...)" so that the program may avoid ' + 'incurring the cost of the interpolation in those cases in which no message will be logged. For more, ' + 'see http://www.python.org/dev/peps/pep-0282/.', + 'W1202': 'Used when a logging statement has a call form of "logging.(format_string.format(' + 'format_args...))". Such calls should use % formatting instead, but leave interpolation to the logging ' + 'function by passing the parameters as arguments.', + 'W1203': 'Used when a logging statement has a call form of "logging.method(f"..."))". Such calls should use % ' + 'formatting instead, but leave interpolation to the logging function by passing the parameters as ' + 'arguments.', + 'W1300': 'Used when a format string that uses named conversion specifiers is used with a dictionary whose keys ' + 'are not all strings.', + 'W1301': 'Used when a format string that uses named conversion specifiers is used with a dictionary that contains ' + 'keys not required by the format string.', + 'W1302': 'Used when a PEP 3101 format string is invalid.', + 'W1303': 'Used when a PEP 3101 format string that uses named fields doesn\'t ' + 'receive one or more required keywords.', + 'W1304': 'Used when a PEP 3101 format string that uses named fields is used with an argument that is not required ' + 'by the format string.', + 'W1305': 'Used when a PEP 3101 format string contains both automatic field numbering (e.g. \'{}\') and manual ' + 'field specification (e.g. \'{0}\').', + 'W1306': 'Used when a PEP 3101 format string uses an attribute specifier ({0.length}), but the argument passed ' + 'for formatting doesn\'t have that attribute.', + 'W1307': 'Used when a PEP 3101 format string uses a lookup specifier ({a[1]}), but the argument passed for ' + 'formatting doesn\'t contain or doesn\'t have that key as an attribute.', + 'W1308': 'Used when we detect that a string formatting is repeating an argument instead of using named string ' + 'arguments', + 'W1401': 'Used when a backslash is in a literal string but not as an escape.', + 'W1402': 'Used when an escape like \\u is encountered in a byte string where it has no effect.', + 'W1403': 'String literals are implicitly concatenated in a ' + 'literal iterable definition : maybe a comma is missing ?', + 'W1501': 'Python supports: r, w, a[, x] modes with b, +, and U (only with r) options. ' + 'See http://docs.python.org/2/library/functions.html#open', + 'W1503': 'The first argument of assertTrue and assertFalse is a condition. If a constant is passed as parameter, ' + 'that condition will be always true. In this case a warning should be emitted.', + 'W1505': 'The method is marked as deprecated and will be removed in a future version of Python. Consider looking ' + 'for an alternative in the documentation.', + 'W1506': 'The warning is emitted when a threading.Thread class is instantiated without the target function being ' + 'passed. By default, the first parameter is the group param, not the target param.', + 'W1507': 'os.environ is not a dict object but proxy object, so shallow copy has still effects on original object. ' + 'See https://bugs.python.org/issue15373 for reference.', + 'W1508': 'Env manipulation functions return None or str values. Supplying anything different as a default may ' + 'cause bugs. See https://docs.python.org/3/library/os.html#os.getenv.', + 'W1509': 'The preexec_fn parameter is not safe to use in the presence of threads in your application. The child ' + 'process could deadlock before exec is called. If you must use it, keep it trivial! Minimize the number ' + 'of libraries you call into.https://docs.python.org/3/library/subprocess.html#popen-constructor', + # miss some inspections that were missed from Python 3 + + 'I0001': 'Used to inform that a built-in module has not been checked using the raw checkers.', + 'I0010': 'Used when an inline option is either badly formatted or can\'t be used inside modules.', + 'I0011': 'Used when an inline option disables a message or a messages category.', + 'I0013': 'Used to inform that the file will not be checked', + 'I0020': 'A message was triggered on a line, but suppressed explicitly by a disable= comment in the file. ' + 'This message is not generated for messages that are ignored due to configuration settings.', + 'I0021': 'Reported when a message is explicitly disabled for a line or a block of code, but never triggered.', + 'I0022': 'Some inline pylint options have been renamed or reworked, only the most recent form should be used. ' + 'NOTE:skip-all is only available with pylint >= 0.26', + 'I0023': 'Used when a message is enabled or disabled by id.', + 'I1101': 'Used when a variable is accessed for non-existent member of C extension. Due to unavailability of source ' + 'static analysis is impossible, but it may be performed by introspecting living objects in run-time.', +} + +# According to the pylint inspector config +PYLINT_DISABLED_ISSUES = { + 'C0103', # invalid-name + 'C0111', # missing-docstring + 'C0301', # line-too-long + 'C0304', # missing-final-newline + 'E1601', # print-statement + 'E1602', # parameter-unpacking + 'E1603', # unpacking-in-except + 'E1604', # old-raise-syntax + 'E1605', + 'I0001', # raw-checker-failed + 'I0010', # bad-inline-option + 'I0011', # locally-disabled + 'I0013', # file-ignored + 'I0020', # suppressed-message + 'I0021', # useless-suppression + 'I0022', # deprecated-pragma + 'I0023', # use-symbolic-message-instead + 'R0901', # too-many-ancestors + 'R0902', # too-many-instance-attributes + 'R0903', # too-few-public-methods + 'R0904', # too-many-public-methods, + 'R0911', # too-many-return-statements + 'R0912', # too-many-branches + 'R0913', # too-many-arguments + 'R0914', # too-many-locals + 'R0916', # too-many-boolean-expressions + 'W1601', # apply-builtin + 'W1602', # basestring-builtin + 'W1603', # buffer-builtin + 'W1604', # cmp-builtin + 'W1605', # coerce-builtin + 'W1606', + 'W1607', # file-builtin + 'W1608', # long-builtin + 'W1609', # raw_input-builtin + 'W1610', # reduce-builtin + 'W1611', + 'W1612', # unicode-builtin + 'W1613', + 'W1614', # coerce-method + 'W1615', + 'W1616', + 'W1617', + 'W1618', # no-absolute-import + 'W1619', # old-division + 'W1620', # dict-iter-method + 'W1621', # dict-view-method + 'W1622', # next-method-called + 'W1623', + 'W1624', # indexing-exception + 'W1625', # raising-string + 'W1626', # reload-builtin + 'W1627', # oct-method + 'W1628', # hex-method + 'W1629', # nonzero-method + 'W1630', # cmp-method + 'W1632', # input-builtin + 'W1633', # round-builtin + 'W1634', # intern-builtin + 'W1635', + 'W1636', # map-builtin-not-iterating + 'W1637', # zip-builtin-not-iterating + 'W1638', # range-builtin-not-iterating + 'W1639', # filter-builtin-not-iterating + 'W1640', # using-cmp-argument + 'W1641', # eq-without-hash + 'W1642', # div-method + 'W1643', + 'W1644', + 'W1645', # exception-message-attribute + 'W1646', + 'W1647', # sys-max-int + 'W1648', # bad-python3-import + 'W1649', # deprecated-string-function + 'W1650', # deprecated-str-translate-call + 'W1651', # deprecated-itertools-function + 'W1652', # deprecated-types-field + 'W1653', # next-method-defined + 'W1654', # dict-items-not-iterating + 'W1655', # dict-keys-not-iterating + 'W1656', # dict-values-not-iterating + 'W1657', # deprecated-operator-function + 'W1658', # deprecated-urllib-function + 'W1659', + 'W1660', # deprecated-sys-function + 'W1661', # exception-escape + 'W1662', # comprehension-escape, + 'W0603', # global-statement + 'C0413', # wrong-import-position + 'R0915', # too-many-statements + 'C0327', # mixed-line-endings + 'E0401', # import-error + 'C0303', # trailing-whitespace + 'R1705', # no-else-return + 'R1720', # no-else-raise +} diff --git a/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py b/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py new file mode 100644 index 00000000..f411bbf2 --- /dev/null +++ b/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py @@ -0,0 +1,131 @@ +import argparse +from typing import Callable, Dict, List, Set, Tuple + +from src.python.evaluation.inspectors.inspectors_stat.issues.flake8_all_issues import ( + ALL_BUGBEAR_ISSUES, ALL_BUILTINS_ISSUES, ALL_COMPREHENSIONS_ISSUES, ALL_FORMAT_STRING_ISSUES, + ALL_IMPORT_ORDER_ISSUES, ALL_RETURN_ISSUES, ALL_SPELLCHECK_ISSUES, ALL_STANDARD_ISSUES, ALL_WPS_ISSUES, + FLAKE8_DISABLED_ISSUES +) +from src.python.evaluation.inspectors.inspectors_stat.issues.other_issues import PYTHON_AST_ISSUES, PYTHON_RADON_ISSUES +from src.python.evaluation.inspectors.inspectors_stat.issues.pylint_all_issues import ALL_ISSUES, PYLINT_DISABLED_ISSUES +from src.python.review.common.language import Language +from src.python.review.inspectors.checkstyle.checkstyle import CheckstyleInspector +from src.python.review.inspectors.checkstyle.issue_types import CHECK_CLASS_NAME_TO_ISSUE_TYPE +from src.python.review.inspectors.detekt.detekt import DetektInspector +from src.python.review.inspectors.detekt.issue_types import DETECT_CLASS_NAME_TO_ISSUE_TYPE +from src.python.review.inspectors.eslint.eslint import ESLintInspector +from src.python.review.inspectors.eslint.issue_types import ESLINT_CLASS_NAME_TO_ISSUE_TYPE +from src.python.review.inspectors.flake8.flake8 import Flake8Inspector +from src.python.review.inspectors.issue import ( + get_default_issue_stat, get_main_category_by_issue_type, IssuesStat, IssueType +) +from src.python.review.inspectors.pmd.issue_types import PMD_RULE_TO_ISSUE_TYPE +from src.python.review.inspectors.pmd.pmd import PMDInspector +from src.python.review.inspectors.pyast.python_ast import PythonAstInspector +from src.python.review.inspectors.pylint.pylint import PylintInspector +from src.python.review.inspectors.radon.radon import RadonInspector + + +def __get_flake8_issue_keys() -> Set[str]: + issues_dicts = [ALL_STANDARD_ISSUES, ALL_BUGBEAR_ISSUES, ALL_BUILTINS_ISSUES, ALL_RETURN_ISSUES, + ALL_FORMAT_STRING_ISSUES, ALL_IMPORT_ORDER_ISSUES, ALL_COMPREHENSIONS_ISSUES, + ALL_SPELLCHECK_ISSUES, ALL_WPS_ISSUES] + all_issues = set().union(*map(lambda d: d.keys(), issues_dicts)) + return set(all_issues - set(FLAKE8_DISABLED_ISSUES)) + + +def __match_issue_keys_to_issue_type(issue_keys: Set[str], matcher: Callable) -> Dict[str, IssueType]: + matched_issues = {} + for key in issue_keys: + matched_issues[key] = matcher(key) + return matched_issues + + +# Count for each main category the frequency of issues for this category +def __gather_issues_stat(issue_types: List[IssueType]) -> IssuesStat: + main_category_to_issue_type = get_default_issue_stat() + for issue_type in issue_types: + main_category_to_issue_type[get_main_category_by_issue_type(issue_type)] += 1 + return main_category_to_issue_type + + +def __merge_issues_stats(*args: IssuesStat) -> IssuesStat: + assert len(args) >= 1, 'Please, use at least one argument' + final_stat = {} + for key in args[0].keys(): + final_stat[key] = sum(d[key] for d in args) + return final_stat + + +def __collect_language_stat(*args: Set[Tuple[Set[str], Callable]]) -> IssuesStat: + all_issue_types = [] + for issues, matcher in args: + all_issue_types.append(__match_issue_keys_to_issue_type(issues, matcher).values()) + return __merge_issues_stats(*map(lambda stat: __gather_issues_stat(stat), all_issue_types)) + + +def collect_stat_by_language(language: Language) -> IssuesStat: + if language == Language.PYTHON: + python_inspection_to_matcher = [ + (set(set(ALL_ISSUES.keys()) - set(PYLINT_DISABLED_ISSUES)), PylintInspector.choose_issue_type), + (__get_flake8_issue_keys(), Flake8Inspector.choose_issue_type), + (set(PYTHON_RADON_ISSUES.keys()), RadonInspector.choose_issue_type), + (set(PYTHON_AST_ISSUES.keys()), PythonAstInspector.choose_issue_type), + ] + return __collect_language_stat(*python_inspection_to_matcher) + elif language == Language.JAVA: + java_inspection_to_matcher = [ + (set(PMD_RULE_TO_ISSUE_TYPE.keys()), PMDInspector.choose_issue_type), + (set(CHECK_CLASS_NAME_TO_ISSUE_TYPE.keys() - set(CheckstyleInspector.skipped_issues)), + CheckstyleInspector.choose_issue_type), + ] + return __collect_language_stat(*java_inspection_to_matcher) + elif language == Language.KOTLIN: + kotlin_inspection_to_matcher = [ + (set(DETECT_CLASS_NAME_TO_ISSUE_TYPE.keys()), DetektInspector.choose_issue_type), + ] + return __collect_language_stat(*kotlin_inspection_to_matcher) + elif language == Language.JS: + js_inspection_to_matcher = [ + (set(ESLINT_CLASS_NAME_TO_ISSUE_TYPE.keys()), ESLintInspector.choose_issue_type), + ] + return __collect_language_stat(*js_inspection_to_matcher) + + raise NotImplementedError(f'Language {language} is not supported yet!') + + +def print_stat(language: Language, stat: IssuesStat) -> None: + print(f'Collected statistics for {language.value.lower()} language:') + for issue_type, freq in stat.items(): + print(f'{issue_type}: {freq} times;') + print(f'Note: {IssueType.UNDEFINED} means a category that is not categorized among the four main categories. ' + f'Most likely it is {IssueType.INFO} category') + + +def __parse_language(language: str) -> Language: + try: + return Language(language.upper()) + except KeyError: + raise KeyError(f'Incorrect language key: {language}. Please, try again!') + + +def configure_arguments(parser: argparse.ArgumentParser) -> None: + languages = ', '.join(map(lambda l: l.lower(), Language.values())) + + parser.add_argument('language', + type=__parse_language, + help=f'The language for which statistics will be printed. Available values are: {languages}') + + +def main() -> None: + parser = argparse.ArgumentParser() + configure_arguments(parser) + args = parser.parse_args() + + language = args.language + stat = collect_stat_by_language(language) + print_stat(language, stat) + + +if __name__ == '__main__': + main() diff --git a/src/python/review/common/language.py b/src/python/review/common/language.py index 48780467..8490bd45 100644 --- a/src/python/review/common/language.py +++ b/src/python/review/common/language.py @@ -13,6 +13,10 @@ class Language(Enum): JS = 'JAVASCRIPT' UNKNOWN = 'UNKNOWN' + @classmethod + def values(cls) -> List[str]: + return [member.value for member in cls.__members__.values()] + EXTENSION_TO_LANGUAGE = { Extension.JAVA: Language.JAVA, diff --git a/src/python/review/inspectors/issue.py b/src/python/review/inspectors/issue.py index 2db397e9..3122378d 100644 --- a/src/python/review/inspectors/issue.py +++ b/src/python/review/inspectors/issue.py @@ -1,8 +1,9 @@ import abc +from collections import defaultdict from dataclasses import dataclass from enum import Enum, unique from pathlib import Path -from typing import Any, Dict, Union +from typing import Any, Dict, List, Union from src.python.review.inspectors.inspector_type import InspectorType @@ -28,6 +29,61 @@ class IssueType(Enum): MAINTAINABILITY = 'MAINTAINABILITY' INFO = 'INFO' + UNDEFINED = 'UNDEFINED' + + def __str__(self) -> str: + return ' '.join(self.value.lower().split('_')) + + +ISSUE_TYPE_TO_MAIN_CATEGORY = { + # CODE_STYLE + IssueType.CODE_STYLE: IssueType.CODE_STYLE, + IssueType.LINE_LEN: IssueType.CODE_STYLE, + + # BEST_PRACTICES + IssueType.BEST_PRACTICES: IssueType.BEST_PRACTICES, + IssueType.FUNC_LEN: IssueType.BEST_PRACTICES, + IssueType.BOOL_EXPR_LEN: IssueType.BEST_PRACTICES, + IssueType.METHOD_NUMBER: IssueType.BEST_PRACTICES, + IssueType.CLASS_RESPONSE: IssueType.BEST_PRACTICES, + + # ERROR_PRONE + IssueType.ERROR_PRONE: IssueType.ERROR_PRONE, + + # COMPLEXITY + IssueType.COMPLEXITY: IssueType.COMPLEXITY, + IssueType.CYCLOMATIC_COMPLEXITY: IssueType.COMPLEXITY, + IssueType.WEIGHTED_METHOD: IssueType.COMPLEXITY, + IssueType.COUPLING: IssueType.COMPLEXITY, + IssueType.COHESION: IssueType.COMPLEXITY, + IssueType.MAINTAINABILITY: IssueType.COMPLEXITY, + IssueType.CHILDREN_NUMBER: IssueType.COMPLEXITY, + IssueType.INHERITANCE_DEPTH: IssueType.COMPLEXITY, + IssueType.ARCHITECTURE: IssueType.COMPLEXITY, +} + + +def get_main_category_by_issue_type(issue_type: IssueType) -> IssueType: + return ISSUE_TYPE_TO_MAIN_CATEGORY.get(issue_type, IssueType.UNDEFINED) + + +def main_category_to_issue_type_list_dict() -> Dict[IssueType, List[IssueType]]: + main_category_to_issue_type = defaultdict(list) + for key, value in ISSUE_TYPE_TO_MAIN_CATEGORY.items(): + main_category_to_issue_type[value].append(key) + return main_category_to_issue_type + + +MAIN_CATEGORY_TO_ISSUE_TYPE_LIST = main_category_to_issue_type_list_dict() + +IssuesStat = Dict[IssueType, int] + + +def get_default_issue_stat() -> IssuesStat: + stat = {issue: 0 for issue in set(ISSUE_TYPE_TO_MAIN_CATEGORY.values())} + stat[IssueType.UNDEFINED] = 0 + return stat + # Keys in results dictionary @unique diff --git a/src/python/review/inspectors/pmd/issue_types.py b/src/python/review/inspectors/pmd/issue_types.py index e4609146..2188702b 100644 --- a/src/python/review/inspectors/pmd/issue_types.py +++ b/src/python/review/inspectors/pmd/issue_types.py @@ -2,7 +2,7 @@ from src.python.review.inspectors.issue import IssueType -RULE_TO_ISSUE_TYPE: Dict[str, IssueType] = { +PMD_RULE_TO_ISSUE_TYPE: Dict[str, IssueType] = { # Best Practices 'AbstractClassWithoutAbstractMethod': IssueType.BEST_PRACTICES, 'AccessorClassGeneration': IssueType.BEST_PRACTICES, diff --git a/src/python/review/inspectors/pmd/pmd.py b/src/python/review/inspectors/pmd/pmd.py index 4b8c11f0..c1f98194 100644 --- a/src/python/review/inspectors/pmd/pmd.py +++ b/src/python/review/inspectors/pmd/pmd.py @@ -10,7 +10,7 @@ from src.python.review.inspectors.base_inspector import BaseInspector from src.python.review.inspectors.inspector_type import InspectorType from src.python.review.inspectors.issue import BaseIssue, CodeIssue, IssueType -from src.python.review.inspectors.pmd.issue_types import RULE_TO_ISSUE_TYPE +from src.python.review.inspectors.pmd.issue_types import PMD_RULE_TO_ISSUE_TYPE logger = logging.getLogger(__name__) @@ -72,7 +72,7 @@ def parse_output(self, output_path: Path) -> List[BaseIssue]: @classmethod def choose_issue_type(cls, rule: str) -> IssueType: - issue_type = RULE_TO_ISSUE_TYPE.get(rule) + issue_type = PMD_RULE_TO_ISSUE_TYPE.get(rule) if not issue_type: logger.warning('%s: %s - unknown rule' % (cls.inspector_type.value, rule)) diff --git a/src/python/review/inspectors/pyast/python_ast.py b/src/python/review/inspectors/pyast/python_ast.py index 6426d115..58fc1bdd 100644 --- a/src/python/review/inspectors/pyast/python_ast.py +++ b/src/python/review/inspectors/pyast/python_ast.py @@ -40,7 +40,7 @@ def visit(self, node: ast.AST): origin_class=BOOL_EXPR_LEN_ORIGIN_CLASS, inspector_type=self._inspector_type, bool_expr_len=length, - type=IssueType.BOOL_EXPR_LEN, + type=PythonAstInspector.choose_issue_type(BOOL_EXPR_LEN_ORIGIN_CLASS), )) @@ -69,7 +69,7 @@ def visit(self, node): origin_class=FUNC_LEN_ORIGIN_CLASS, inspector_type=self._inspector_type, func_len=func_length, - type=IssueType.FUNC_LEN, + type=PythonAstInspector.choose_issue_type(FUNC_LEN_ORIGIN_CLASS), )) self._previous_node = node @@ -91,7 +91,7 @@ def function_lens(self) -> List[FuncLenIssue]: origin_class=FUNC_LEN_ORIGIN_CLASS, inspector_type=self._inspector_type, func_len=func_length, - type=IssueType.FUNC_LEN, + type=PythonAstInspector.choose_issue_type(FUNC_LEN_ORIGIN_CLASS), )) self._previous_node = None @@ -136,6 +136,16 @@ def inspect(cls, path: Path, config: dict) -> List[BaseIssue]: return metrics + @staticmethod + def choose_issue_type(code: str) -> IssueType: + if code == BOOL_EXPR_LEN_ORIGIN_CLASS: + return IssueType.BOOL_EXPR_LEN + + if code == FUNC_LEN_ORIGIN_CLASS: + return IssueType.FUNC_LEN + + return IssueType.BEST_PRACTICES + def create_line_no_to_sym_no_map(content) -> Dict[int, int]: mapping = defaultdict(lambda: len(content), {1: 0}) diff --git a/src/python/review/inspectors/radon/radon.py b/src/python/review/inspectors/radon/radon.py index 79223164..a5153fc1 100644 --- a/src/python/review/inspectors/radon/radon.py +++ b/src/python/review/inspectors/radon/radon.py @@ -10,7 +10,7 @@ from src.python.review.inspectors.tips import get_maintainability_index_tip -MAINTAINABILITY_ORIGIN_CLASS = "RAD100" +MAINTAINABILITY_ORIGIN_CLASS = 'RAD100' class RadonInspector(BaseInspector): @@ -19,9 +19,9 @@ class RadonInspector(BaseInspector): @classmethod def inspect(cls, path: Path, config: dict) -> List[BaseIssue]: mi_command = [ - "radon", "mi", # compute the Maintainability Index score - "--max", "F", # set the maximum MI rank to display - "--show", # actual MI value is shown in results, alongside the rank + 'radon', 'mi', # compute the Maintainability Index score + '--max', 'F', # set the maximum MI rank to display + '--show', # actual MI value is shown in results, alongside the rank path, ] @@ -31,13 +31,13 @@ def inspect(cls, path: Path, config: dict) -> List[BaseIssue]: @classmethod def mi_parse(cls, mi_output: str) -> List[BaseIssue]: """ - Parses the results of the "mi" command. + Parses the results of the 'mi' command. Description: https://radon.readthedocs.io/en/latest/commandline.html#the-mi-command - :param mi_output: "mi" command output. + :param mi_output: 'mi' command output. :return: list of issues. """ - row_re = re.compile(r"^(.*) - \w \((.*)\)$", re.M) + row_re = re.compile(r'^(.*) - \w \((.*)\)$', re.M) issues: List[BaseIssue] = [] for groups in row_re.findall(mi_output): @@ -49,8 +49,15 @@ def mi_parse(cls, mi_output: str) -> List[BaseIssue]: ) issue_data[IssueData.DESCRIPTION.value] = get_maintainability_index_tip() issue_data[IssueData.MAINTAINABILITY_LACK.value] = maintainability_lack - issue_data[IssueData.ISSUE_TYPE.value] = IssueType.MAINTAINABILITY + issue_data[IssueData.ISSUE_TYPE.value] = cls.choose_issue_type(MAINTAINABILITY_ORIGIN_CLASS) issues.append(MaintainabilityLackIssue(**issue_data)) return issues + + @staticmethod + def choose_issue_type(code: str) -> IssueType: + if code == MAINTAINABILITY_ORIGIN_CLASS: + return IssueType.MAINTAINABILITY + + return IssueType.BEST_PRACTICES diff --git a/whitelist.txt b/whitelist.txt index e253cce3..11a57364 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -154,4 +154,6 @@ WANDB PNG consts Measurer -ndarray \ No newline at end of file +ndarray +Runtime +matcher \ No newline at end of file From 60f12eab85f3b39afc92d459b48ba9ab0a605d69 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Tue, 15 Jun 2021 12:52:26 +0300 Subject: [PATCH 2/3] Fix flake8 issues --- .../inspectors_stat/issues/pylint_all_issues.py | 8 ++++---- .../inspectors/inspectors_stat/statistics_gathering.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py b/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py index 3e5ec04d..b73e9efd 100644 --- a/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py +++ b/src/python/evaluation/inspectors/inspectors_stat/issues/pylint_all_issues.py @@ -73,8 +73,8 @@ 'E0117': 'Emitted when a nonlocal variable does not have an attached name somewhere in the parent scopes', 'E0118': 'Emitted when a name is used prior a global declaration, which results in an error since Python 3.6. ' 'This message can\'t be emitted when using Python < 3.6.', - 'E0119': 'Emitted when format function is not called on str object. e.g doing print("value: {}").format(123) ' - 'instead of print("value: {}".format(123)). This might not be what the user intended to do.', + 'E0119': 'Emitted when format function is not called on str object. ' + 'This might not be what the user intended to do.', 'E0202': 'Used when a class defines a method which is hidden by an instance attribute from an ancestor class or ' 'set by some client code.', 'E0203': 'Used when an instance member is accessed before it\'s actually assigned.', @@ -353,8 +353,8 @@ 'receive one or more required keywords.', 'W1304': 'Used when a PEP 3101 format string that uses named fields is used with an argument that is not required ' 'by the format string.', - 'W1305': 'Used when a PEP 3101 format string contains both automatic field numbering (e.g. \'{}\') and manual ' - 'field specification (e.g. \'{0}\').', + 'W1305': 'Used when a PEP 3101 format string contains both automatic field numbering and manual ' + 'field specification.', 'W1306': 'Used when a PEP 3101 format string uses an attribute specifier ({0.length}), but the argument passed ' 'for formatting doesn\'t have that attribute.', 'W1307': 'Used when a PEP 3101 format string uses a lookup specifier ({a[1]}), but the argument passed for ' diff --git a/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py b/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py index f411bbf2..234c7ccc 100644 --- a/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py +++ b/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py @@ -4,7 +4,7 @@ from src.python.evaluation.inspectors.inspectors_stat.issues.flake8_all_issues import ( ALL_BUGBEAR_ISSUES, ALL_BUILTINS_ISSUES, ALL_COMPREHENSIONS_ISSUES, ALL_FORMAT_STRING_ISSUES, ALL_IMPORT_ORDER_ISSUES, ALL_RETURN_ISSUES, ALL_SPELLCHECK_ISSUES, ALL_STANDARD_ISSUES, ALL_WPS_ISSUES, - FLAKE8_DISABLED_ISSUES + FLAKE8_DISABLED_ISSUES, ) from src.python.evaluation.inspectors.inspectors_stat.issues.other_issues import PYTHON_AST_ISSUES, PYTHON_RADON_ISSUES from src.python.evaluation.inspectors.inspectors_stat.issues.pylint_all_issues import ALL_ISSUES, PYLINT_DISABLED_ISSUES @@ -17,7 +17,7 @@ from src.python.review.inspectors.eslint.issue_types import ESLINT_CLASS_NAME_TO_ISSUE_TYPE from src.python.review.inspectors.flake8.flake8 import Flake8Inspector from src.python.review.inspectors.issue import ( - get_default_issue_stat, get_main_category_by_issue_type, IssuesStat, IssueType + get_default_issue_stat, get_main_category_by_issue_type, IssuesStat, IssueType, ) from src.python.review.inspectors.pmd.issue_types import PMD_RULE_TO_ISSUE_TYPE from src.python.review.inspectors.pmd.pmd import PMDInspector From 9ffb8c655e7ada115e1c98fe2d8456efdf8c29a6 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Wed, 16 Jun 2021 14:28:32 +0300 Subject: [PATCH 3/3] Fix PR#48 comments --- .../inspectors/inspectors_stat/statistics_gathering.py | 2 +- src/python/review/common/language.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py b/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py index 234c7ccc..c2c159ad 100644 --- a/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py +++ b/src/python/evaluation/inspectors/inspectors_stat/statistics_gathering.py @@ -67,7 +67,7 @@ def __collect_language_stat(*args: Set[Tuple[Set[str], Callable]]) -> IssuesStat def collect_stat_by_language(language: Language) -> IssuesStat: if language == Language.PYTHON: python_inspection_to_matcher = [ - (set(set(ALL_ISSUES.keys()) - set(PYLINT_DISABLED_ISSUES)), PylintInspector.choose_issue_type), + (set(ALL_ISSUES.keys()) - set(PYLINT_DISABLED_ISSUES), PylintInspector.choose_issue_type), (__get_flake8_issue_keys(), Flake8Inspector.choose_issue_type), (set(PYTHON_RADON_ISSUES.keys()), RadonInspector.choose_issue_type), (set(PYTHON_AST_ISSUES.keys()), PythonAstInspector.choose_issue_type), diff --git a/src/python/review/common/language.py b/src/python/review/common/language.py index 8490bd45..6dc5728d 100644 --- a/src/python/review/common/language.py +++ b/src/python/review/common/language.py @@ -15,7 +15,7 @@ class Language(Enum): @classmethod def values(cls) -> List[str]: - return [member.value for member in cls.__members__.values()] + return [member.value for member in Language] EXTENSION_TO_LANGUAGE = {