Skip to content

Commit

Permalink
Radon support (#19)
Browse files Browse the repository at this point in the history
Added new inspector: Radon. 
Added maintainability index.
  • Loading branch information
GirZ0n authored Apr 12, 2021
1 parent 7862043 commit 809a8f6
Show file tree
Hide file tree
Showing 64 changed files with 395 additions and 178 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,venv,test/resources,.eggs,review.egg-info,.pytest_cache,node_modules
# TODO: change max-complexity into 10 after refactoring
flake8 . --count --max-complexity=11 --max-line-length=120 --max-doc-length=120 --ignore=I201,I202,I101,I100,R504,A003,E800,SC200,SC100,E402,W503,WPS,C812,H601 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,venv,test/resources,.eggs,review.egg-info,.pytest_cache,node_modules
flake8 . --count --max-complexity=11 --max-line-length=120 --max-doc-length=120 --ignore=I201,I202,I101,I100,R504,A003,E800,SC200,SC100,E402,W503,WPS,H601 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,venv,test/resources,.eggs,review.egg-info,.pytest_cache,node_modules
- name: Set up Eslint
run: |
npm install eslint --save-dev
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ Python language:
- [x] Pylint [GNU LGPL v2]
* [Site and docs](https://www.pylint.org/)
* [Repository](https://github.com/PyCQA/pylint)


- [x] Radon [MIT]
* [Site and docs](https://radon.readthedocs.io/en/latest/)
* [Repository](https://github.com/rubik/radon)

Java language:

Expand Down Expand Up @@ -92,7 +95,7 @@ Argument | Description
--- | ---
**‑h**, **‑‑help** | show the help message and exit.
**‑v**, **‑‑verbosity** | choose logging level according [this](https://docs.python.org/3/library/logging.html#levels) list: `1` - **ERROR**; `2` - **INFO**; `3` - **DEBUG**; `0` - disable logging (**CRITICAL** value); default value is `0` (**CRITICAL**).
**‑d**, **‑‑disable** | disable inspectors. Available values: for **Python** language: `pylint` for [Pylint](https://github.com/PyCQA/pylint), `flake8` for [flake8](https://flake8.pycqa.org/en/latest/), `python_ast` to check different measures providing by AST; for **Java** language: `checkstyle` for the [Checkstyle](https://checkstyle.sourceforge.io/), `pmd` for [PMD](https://pmd.github.io/); for `Kotlin` language: detekt for [Detekt](https://detekt.github.io/detekt/); for **JavaScript** language: `eslint` for [ESlint](https://eslint.org/). Example: `-d pylint,flake8`.
**‑d**, **‑‑disable** | disable inspectors. Available values: for **Python** language: `pylint` for [Pylint](https://github.com/PyCQA/pylint), `flake8` for [flake8](https://flake8.pycqa.org/en/latest/), `radon` for [Radon](https://radon.readthedocs.io/en/latest/), `python_ast` to check different measures providing by AST; for **Java** language: `checkstyle` for the [Checkstyle](https://checkstyle.sourceforge.io/), `pmd` for [PMD](https://pmd.github.io/); for `Kotlin` language: detekt for [Detekt](https://detekt.github.io/detekt/); for **JavaScript** language: `eslint` for [ESlint](https://eslint.org/). Example: `-d pylint,flake8`.
**‑‑allow-duplicates** | allow duplicate issues found by different linters. By default, duplicates are skipped.
**‑‑language-version**, **‑‑language_version** | specify the language version for JAVA inspectors. Available values: `java7`, `java8`, `java9`, `java11`. **Note**: **‑‑language_version** is deprecated. Will be deleted in the future.
**‑‑n-cpu**, **‑‑n_cpu** | specify number of _cpu_ that can be used to run inspectors. **Note**: **‑‑n_cpu** is deprecated. Will be deleted in the future.
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ flake8-broken-line==0.3.0
flake8-string-format==0.3.0
flake8-commas==2.0.0
cohesion==1.0.0
radon==4.5.0

# extra libraries and frameworks
django==3.0.8
Expand Down
12 changes: 6 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,22 @@ def get_inspectors_additional_files() -> List[str]:
'Topic :: Software Development :: Build Tools',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Operating System :: OS Independent'
'Operating System :: OS Independent',
],
keywords='code review',
python_requires='>=3.8, <4',
install_requires=['upsourceapi'],
packages=find_packages(exclude=[
'*.unit_tests', '*.unit_tests.*', 'unit_tests.*', 'unit_tests',
'*.functional_tests', '*.functional_tests.*', 'functional_tests.*', 'functional_tests'
'*.functional_tests', '*.functional_tests.*', 'functional_tests.*', 'functional_tests',
]),
zip_safe=False,
package_data={
'': get_inspectors_additional_files()
'': get_inspectors_additional_files(),
},
entry_points={
'console_scripts': [
'review=review.run_tool:main'
]
}
'review=review.run_tool:main',
],
},
)
2 changes: 1 addition & 1 deletion src/python/review/common/file_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def create_directory(directory: str) -> None:
def get_file_line(path: Path, line_number: int):
return linecache.getline(
str(path),
line_number
line_number,
).strip()


Expand Down
2 changes: 1 addition & 1 deletion src/python/review/common/java_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def javac(javac_args: Union[str, Path]) -> bool:
output_bytes: bytes = subprocess.check_output(
f'javac {javac_args}',
shell=True,
stderr=subprocess.STDOUT
stderr=subprocess.STDOUT,
)
output_str = str(output_bytes, Encoding.UTF_ENCODING.value)

Expand Down
2 changes: 1 addition & 1 deletion src/python/review/common/parallel_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def inspect_in_parallel(path: Path,
with multiprocessing.Pool(config.n_cpu) as pool:
issues = pool.map(
functools.partial(run_inspector, path, config),
inspectors
inspectors,
)

return list(itertools.chain(*issues))
2 changes: 1 addition & 1 deletion src/python/review/common/subprocess_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def run_in_subprocess(command: List[str]) -> str:
process = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
stderr=subprocess.PIPE,
)

stdout = process.stdout.decode()
Expand Down
4 changes: 2 additions & 2 deletions src/python/review/inspectors/checkstyle/checkstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ class CheckstyleInspector(BaseInspector):
r'Boolean expression complexity is (\d+)',

'LineLengthCheck':
r'Line is longer than \d+ characters \(found (\d+)\)'
r'Line is longer than \d+ characters \(found (\d+)\)',
}

@classmethod
def _create_command(cls, path: Path, output_path: Path) -> List[str]:
return [
'java', '-jar', PATH_TOOLS_CHECKSTYLE_JAR,
'-c', PATH_TOOLS_CHECKSTYLE_CONFIG,
'-f', 'xml', '-o', output_path, str(path)
'-f', 'xml', '-o', output_path, str(path),
]

def inspect(self, path: Path, config: dict) -> List[BaseIssue]:
Expand Down
12 changes: 12 additions & 0 deletions src/python/review/inspectors/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from math import floor


def convert_percentage_of_value_to_lack_of_value(percentage_of_value: float) -> int:
"""
Converts percentage of value to lack of value.
Calculated by the formula: floor(100 - percentage_of_value).
:param percentage_of_value: value in the range from 0 to 100.
:return: lack of value.
"""
return floor(100 - percentage_of_value)
4 changes: 2 additions & 2 deletions src/python/review/inspectors/detekt/detekt.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class DetektInspector(BaseInspector):
'ComplexCondition':
r'This condition is too complex \((\d+)\)',
'ComplexMethod':
r'The function .* appears to be too complex \((\d+)\)'
r'The function .* appears to be too complex \((\d+)\)',
}

@classmethod
Expand All @@ -38,7 +38,7 @@ def _create_command(cls, path: Path, output_path: Path):
'--config', PATH_DETEKT_CONFIG,
'--plugins', PATH_DETEKT_PLUGIN,
'--report', f'xml:{output_path}',
'--input', str(path)
'--input', str(path),
]

def inspect(self, path: Path, config) -> List[BaseIssue]:
Expand Down
2 changes: 1 addition & 1 deletion src/python/review/inspectors/eslint/eslint.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ESLintInspector(BaseInspector):

origin_class_to_pattern = {
'complexity':
r'complexity of (\d+)'
r'complexity of (\d+)',
}

@classmethod
Expand Down
3 changes: 1 addition & 2 deletions src/python/review/inspectors/flake8/.flake8
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,4 @@ ignore=W291, # trailing whitespaces
F524, # missing argument. TODO: Collision with "P201" and "P202"
F525, # mixing automatic and manual numbering. TODO: Collision with "P205"
# flake8-commas
C814, # missing trailing comma in Python

C814, # missing trailing comma in Python 2
17 changes: 4 additions & 13 deletions src/python/review/inspectors/flake8/flake8.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import math
import re
from pathlib import Path
from typing import List
Expand All @@ -17,6 +16,7 @@
CohesionIssue,
)
from src.python.review.inspectors.tips import get_cyclomatic_complexity_tip
from src.python.review.inspectors.common import convert_percentage_of_value_to_lack_of_value

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -69,7 +69,9 @@ def parse(cls, output: str) -> List[BaseIssue]:
issues.append(CyclomaticComplexityIssue(**issue_data))
elif cohesion_match is not None: # flake8-cohesion
issue_data[IssueData.DESCRIPTION.value] = description # TODO: Add tip
issue_data[IssueData.COHESION_LACK.value] = cls.__get_cohesion_lack(float(cohesion_match.group(1)))
issue_data[IssueData.COHESION_LACK.value] = convert_percentage_of_value_to_lack_of_value(
float(cohesion_match.group(1)),
)
issue_data[IssueData.ISSUE_TYPE.value] = IssueType.COHESION
issues.append(CohesionIssue(**issue_data))
else:
Expand Down Expand Up @@ -98,14 +100,3 @@ def choose_issue_type(code: str) -> IssueType:
return IssueType.BEST_PRACTICES

return issue_type

@staticmethod
def __get_cohesion_lack(cohesion_percentage: float) -> int:
"""
Converts cohesion percentage to lack of cohesion.
Calculated by the formula: floor(100 - cohesion_percentage).
:param cohesion_percentage: cohesion set as a percentage.
:return: lack of cohesion
"""
return math.floor(100 - cohesion_percentage)
2 changes: 2 additions & 0 deletions src/python/review/inspectors/inspector_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class InspectorType(Enum):
PYLINT = 'PYLINT'
PYTHON_AST = 'PYTHON_AST'
FLAKE8 = 'FLAKE8'
RADON = 'RADON'

# Java language
PMD = 'PMD'
Expand All @@ -29,6 +30,7 @@ def available_values(cls) -> List[str]:
cls.PYLINT.value,
cls.FLAKE8.value,
cls.PYTHON_AST.value,
cls.RADON.value,

# Java language
cls.PMD.value,
Expand Down
8 changes: 4 additions & 4 deletions src/python/review/inspectors/intellij/intellij.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self):
def create_command(output_dir_path) -> List[Union[str, Path]]:
return [
INTELLIJ_INSPECTOR_EXECUTABLE, INTELLIJ_INSPECTOR_PROJECT,
INTELLIJ_INSPECTOR_SETTINGS, output_dir_path, '-v2'
INTELLIJ_INSPECTOR_SETTINGS, output_dir_path, '-v2',
]

def inspect(self, path: Path, config: dict) -> List[BaseIssue]:
Expand Down Expand Up @@ -134,8 +134,8 @@ def parse(cls, out_dir_path: Path,
file_path = Path(
text.replace(
'file://$PROJECT_DIR$',
str(INTELLIJ_INSPECTOR_PROJECT)
)
str(INTELLIJ_INSPECTOR_PROJECT),
),
)
elif tag == 'line':
line_no = int(text)
Expand All @@ -160,7 +160,7 @@ def parse(cls, out_dir_path: Path,
description=description,
origin_class=issue_class,
inspector_type=cls.inspector_type,
type=issue_type
type=issue_type,
))

return issues
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,5 @@
'\'when\' that can be simplified by introducing an argument':
IssueType.CODE_STYLE,

'Annotator': IssueType.ERROR_PRONE
'Annotator': IssueType.ERROR_PRONE,
}
13 changes: 12 additions & 1 deletion src/python/review/inspectors/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class IssueType(Enum):
COHESION = 'COHESION'
CLASS_RESPONSE = 'CLASS_RESPONSE'
METHOD_NUMBER = 'METHOD_NUMBER'
MAINTAINABILITY = 'MAINTAINABILITY'


# Keys in results dictionary
Expand All @@ -46,6 +47,7 @@ class IssueData(Enum):
BOOL_EXPR_LEN = 'bool_expr_len'
CYCLOMATIC_COMPLEXITY = 'cc_value'
COHESION_LACK = 'cohesion_lack'
MAINTAINABILITY_LACK = 'maintainability_lack'

@classmethod
def get_base_issue_data_dict(cls,
Expand All @@ -59,7 +61,7 @@ def get_base_issue_data_dict(cls,
cls.LINE_NUMBER.value: line_number,
cls.COLUMN_NUMBER.value: column_number,
cls.ORIGIN_ClASS.value: origin_class,
cls.INSPECTOR_TYPE.value: inspector_type
cls.INSPECTOR_TYPE.value: inspector_type,
}


Expand Down Expand Up @@ -184,3 +186,12 @@ class MethodNumberIssue(BaseIssue, Measurable):

def measure(self) -> int:
return self.method_number


@dataclass(frozen=True)
class MaintainabilityLackIssue(BaseIssue, Measurable):
maintainability_lack: int
type = IssueType.MAINTAINABILITY

def measure(self) -> int:
return self.maintainability_lack
2 changes: 1 addition & 1 deletion src/python/review/inspectors/pmd/pmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _create_command(cls, path: Path,
'-language', 'java',
'-version', java_version.value,
'-f', 'csv', '-r', str(output_path),
'-t', str(n_cpu)
'-t', str(n_cpu),
]

def inspect(self, path: Path, config: dict) -> List[BaseIssue]:
Expand Down
14 changes: 7 additions & 7 deletions src/python/review/inspectors/pyast/python_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=IssueType.BOOL_EXPR_LEN,
))


Expand All @@ -58,7 +58,7 @@ def __init__(self, content: str, file_path: Path, inspector_type: InspectorType)
def visit(self, node):
if isinstance(self._previous_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
func_length = self._find_func_len(
self._previous_node.lineno, node.lineno
self._previous_node.lineno, node.lineno,
)

self._function_lens.append(FuncLenIssue(
Expand All @@ -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=IssueType.FUNC_LEN,
))

self._previous_node = node
Expand All @@ -80,7 +80,7 @@ def visit(self, node):
def function_lens(self) -> List[FuncLenIssue]:
if isinstance(self._previous_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
func_length = self._find_func_len(
self._previous_node.lineno, self._n_lines + 1
self._previous_node.lineno, self._n_lines + 1,
)

self._function_lens.append(FuncLenIssue(
Expand All @@ -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=IssueType.FUNC_LEN,
))

self._previous_node = None
Expand Down Expand Up @@ -125,13 +125,13 @@ def inspect(cls, path: Path, config: dict) -> List[BaseIssue]:
bool_gatherer = BoolExpressionLensGatherer(path_to_file, cls.inspector_type)
bool_gatherer.visit(tree)
metrics.extend(
bool_gatherer.bool_expression_lens
bool_gatherer.bool_expression_lens,
)

func_gatherer = FunctionLensGatherer(file_content, path_to_file, cls.inspector_type)
func_gatherer.visit(tree)
metrics.extend(
func_gatherer.function_lens
func_gatherer.function_lens,
)

return metrics
Expand Down
6 changes: 3 additions & 3 deletions src/python/review/inspectors/pylint/pylint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class PylintInspector(BaseInspector):
supported_issue_types = (
IssueType.CODE_STYLE,
IssueType.BEST_PRACTICES,
IssueType.ERROR_PRONE
IssueType.ERROR_PRONE,
)

@classmethod
Expand All @@ -31,7 +31,7 @@ def inspect(cls, path: Path, config: dict) -> List[CodeIssue]:
'--load-plugins', 'pylint_django',
f'--rcfile={PATH_PYLINT_CONFIG}',
f'--msg-template={MSG_TEMPLATE}',
str(path)
str(path),
]

output = run_in_subprocess(command)
Expand Down Expand Up @@ -70,7 +70,7 @@ def parse(cls, output: str) -> List[CodeIssue]:
origin_class=origin_class,
description=description,
inspector_type=cls.inspector_type,
type=issue_type
type=issue_type,
))

return issues
Expand Down
Empty file.
Loading

0 comments on commit 809a8f6

Please sign in to comment.