diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b5f788b..ea731ce5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,22 @@ jobs: cd /review /hyperstyle/bin/pytest + type-checking: + name: Type checking + runs-on: [ self-hosted, small ] + needs: + - build_image + container: + image: hyperskill.azurecr.io/hyperstyle:${{ github.sha }} + credentials: + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + steps: + - name: Lint + run: | + cd /review + /hyperstyle/bin/mypy . + build: runs-on: [ self-hosted, small ] needs: diff --git a/hyperstyle/src/python/common/tool_arguments.py b/hyperstyle/src/python/common/tool_arguments.py index d4376cc0..785671d9 100644 --- a/hyperstyle/src/python/common/tool_arguments.py +++ b/hyperstyle/src/python/common/tool_arguments.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from enum import Enum, unique -from typing import ClassVar from hyperstyle.src.python.review.common.language import Language from hyperstyle.src.python.review.common.language_version import LanguageVersion @@ -30,6 +29,10 @@ class ArgumentsInfo: description: str +INSPECTORS = [inspector.lower() for inspector in InspectorType.available_values()] +DISABLED_INSPECTORS_EXAMPLE = f"-d {INSPECTORS[0].lower()},{INSPECTORS[1].lower()}" + + @unique class RunToolArgument(Enum): VERBOSITY = ArgumentsInfo( @@ -43,15 +46,12 @@ class RunToolArgument(Enum): "default is 0", ) - inspectors: ClassVar = [inspector.lower() for inspector in InspectorType.available_values()] - disabled_inspectors_example = f"-d {inspectors[0].lower()},{inspectors[1].lower()}" - DISABLE = ArgumentsInfo( "-d", "--disable", 'Disable inspectors. ' - f'Available values: {", ".join(inspectors)}. ' - f'Example: {disabled_inspectors_example}', + f'Available values: {", ".join(INSPECTORS)}. ' + f'Example: {DISABLED_INSPECTORS_EXAMPLE}', ) DUPLICATES = ArgumentsInfo( diff --git a/hyperstyle/src/python/review/application_config.py b/hyperstyle/src/python/review/application_config.py index dc177612..56ee6daa 100644 --- a/hyperstyle/src/python/review/application_config.py +++ b/hyperstyle/src/python/review/application_config.py @@ -13,7 +13,7 @@ class ApplicationConfig: disabled_inspectors: set[InspectorType] allow_duplicates: bool n_cpu: int - inspectors_config: dict + inspectors_config: dict[str, object] with_all_categories: bool start_line: int = 1 language: Language | None = None diff --git a/hyperstyle/src/python/review/common/file_system.py b/hyperstyle/src/python/review/common/file_system.py index aa39cc5c..7d785769 100644 --- a/hyperstyle/src/python/review/common/file_system.py +++ b/hyperstyle/src/python/review/common/file_system.py @@ -88,10 +88,6 @@ def new_temp_dir() -> Iterator[Path]: yield Path(temp_dir) -def new_temp_file(suffix: Extension = Extension.EMPTY) -> Iterator[tuple[str, str]]: - yield tempfile.mkstemp(suffix=suffix.value) - - def get_file_line(path: Path, line_number: int) -> str: return linecache.getline( str(path), diff --git a/hyperstyle/src/python/review/common/parallel_runner.py b/hyperstyle/src/python/review/common/parallel_runner.py index c2013fc0..b6380b37 100644 --- a/hyperstyle/src/python/review/common/parallel_runner.py +++ b/hyperstyle/src/python/review/common/parallel_runner.py @@ -56,9 +56,11 @@ def inspect_in_parallel( return issues with multiprocessing.Pool(config.n_cpu) as pool: - issues = pool.map( - functools.partial(inspector_runner, data, config), - inspectors_to_run, + return list( + itertools.chain( + *pool.map( + functools.partial(inspector_runner, data, config), + inspectors_to_run, + ) + ) ) - - return list(itertools.chain(*issues)) diff --git a/hyperstyle/src/python/review/inspectors/common/inspector/base_inspector.py b/hyperstyle/src/python/review/inspectors/common/inspector/base_inspector.py index 8ad519cc..12bc8122 100644 --- a/hyperstyle/src/python/review/inspectors/common/inspector/base_inspector.py +++ b/hyperstyle/src/python/review/inspectors/common/inspector/base_inspector.py @@ -138,10 +138,6 @@ def convert_to_base_issues( return base_issues def _get_inspection_result(self, code_text: str, file_path: Path) -> list[BaseIssue]: - if self.host is None or self.port is None: - msg = "Connection parameters is not set up." - raise Exception(msg) - client = IJClient(self.host, self.port) code = model_pb2.Code() diff --git a/hyperstyle/src/python/review/inspectors/common/inspector/ij_client.py b/hyperstyle/src/python/review/inspectors/common/inspector/ij_client.py index f06e42c8..4c651609 100644 --- a/hyperstyle/src/python/review/inspectors/common/inspector/ij_client.py +++ b/hyperstyle/src/python/review/inspectors/common/inspector/ij_client.py @@ -8,6 +8,8 @@ class IJClient: + stub: model_pb2_grpc.CodeInspectionServiceStub + def __init__(self, host: str = "localhost", port: int = 8080) -> None: self.host = host self.port = port @@ -22,10 +24,10 @@ def __init__(self, host: str = "localhost", port: int = 8080) -> None: msg = "Failed to connect to ij code server" raise Exception(msg) from e else: - self.stub = model_pb2_grpc.CodeInspectionServiceStub(self.channel) + self.stub = model_pb2_grpc.CodeInspectionServiceStub(self.channel) # type: ignore[no-untyped-call] def inspect(self, code: model_pb2.Code) -> model_pb2.InspectionResult: return self.stub.inspect(code, timeout=TIMEOUT) def init(self, service: model_pb2.Service) -> model_pb2.InitResult: - return self.stub.init(service, timeout=TIMEOUT) + return self.stub.init(service, timeout=TIMEOUT) # type: ignore[attr-defined] diff --git a/hyperstyle/src/python/review/inspectors/common/issue/base_issue_converter.py b/hyperstyle/src/python/review/inspectors/common/issue/base_issue_converter.py index ca3ce617..5739a91c 100644 --- a/hyperstyle/src/python/review/inspectors/common/issue/base_issue_converter.py +++ b/hyperstyle/src/python/review/inspectors/common/issue/base_issue_converter.py @@ -8,7 +8,7 @@ get_issue_class_by_issue_type, get_measure_name_by_measurable_issue_type, IssueData, - Measurable, + MeasurableIssue, ) if TYPE_CHECKING: @@ -36,7 +36,7 @@ def convert_base_issue(base_issue: BaseIssue, issue_configs_handler: IssueConfig issue_data[IssueData.DESCRIPTION.value] = issue_configs_handler.get_description(origin_class, description) issue_class = get_issue_class_by_issue_type(issue_type) - if issubclass(issue_class, Measurable): + if issubclass(issue_class, MeasurableIssue): measure = issue_configs_handler.parse_measure(origin_class, description) if measure is None: logger.error(f"{inspector_type.value}: Unable to parse measure.") diff --git a/hyperstyle/src/python/review/inspectors/common/issue/issue.py b/hyperstyle/src/python/review/inspectors/common/issue/issue.py index 8de9d492..7bfe76f1 100644 --- a/hyperstyle/src/python/review/inspectors/common/issue/issue.py +++ b/hyperstyle/src/python/review/inspectors/common/issue/issue.py @@ -1,11 +1,10 @@ from __future__ import annotations -import abc import logging from collections import defaultdict from dataclasses import dataclass from enum import Enum, unique -from typing import Any, TYPE_CHECKING +from typing import Any, Protocol, runtime_checkable, TYPE_CHECKING if TYPE_CHECKING: from pathlib import Path @@ -206,10 +205,9 @@ class BaseIssue(ShortIssue): difficulty: IssueDifficulty -class Measurable(abc.ABC): - @abc.abstractmethod - def measure(self) -> int: - pass +@runtime_checkable +class MeasurableIssue(Protocol): + def measure(self) -> int: ... @dataclass(frozen=True) @@ -218,7 +216,7 @@ class CodeIssue(BaseIssue): @dataclass(frozen=True) -class BoolExprLenIssue(BaseIssue, Measurable): +class BoolExprLenIssue(BaseIssue, MeasurableIssue): bool_expr_len: int type = IssueType.BOOL_EXPR_LEN @@ -227,7 +225,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class FuncLenIssue(BaseIssue, Measurable): +class FuncLenIssue(BaseIssue, MeasurableIssue): func_len: int type = IssueType.FUNC_LEN @@ -236,7 +234,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class LineLenIssue(BaseIssue, Measurable): +class LineLenIssue(BaseIssue, MeasurableIssue): line_len: int type = IssueType.LINE_LEN @@ -245,7 +243,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class CyclomaticComplexityIssue(BaseIssue, Measurable): +class CyclomaticComplexityIssue(BaseIssue, MeasurableIssue): cc_value: int type = IssueType.CYCLOMATIC_COMPLEXITY @@ -254,7 +252,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class InheritanceIssue(BaseIssue, Measurable): +class InheritanceIssue(BaseIssue, MeasurableIssue): inheritance_tree_depth: int type = IssueType.INHERITANCE_DEPTH @@ -263,7 +261,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class ChildrenNumberIssue(BaseIssue, Measurable): +class ChildrenNumberIssue(BaseIssue, MeasurableIssue): children_number: int type = IssueType.CHILDREN_NUMBER @@ -272,7 +270,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class WeightedMethodIssue(BaseIssue, Measurable): +class WeightedMethodIssue(BaseIssue, MeasurableIssue): weighted_method: int type = IssueType.WEIGHTED_METHOD @@ -281,7 +279,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class CouplingIssue(BaseIssue, Measurable): +class CouplingIssue(BaseIssue, MeasurableIssue): class_objects_coupling: int type = IssueType.COUPLING @@ -290,7 +288,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class CohesionIssue(BaseIssue, Measurable): +class CohesionIssue(BaseIssue, MeasurableIssue): cohesion_lack: int type = IssueType.COHESION @@ -299,7 +297,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class ClassResponseIssue(BaseIssue, Measurable): +class ClassResponseIssue(BaseIssue, MeasurableIssue): class_response: int type = IssueType.CLASS_RESPONSE @@ -308,7 +306,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class MethodNumberIssue(BaseIssue, Measurable): +class MethodNumberIssue(BaseIssue, MeasurableIssue): method_number: int type = IssueType.METHOD_NUMBER @@ -317,7 +315,7 @@ def measure(self) -> int: @dataclass(frozen=True) -class MaintainabilityLackIssue(BaseIssue, Measurable): +class MaintainabilityLackIssue(BaseIssue, MeasurableIssue): maintainability_lack: int type = IssueType.MAINTAINABILITY diff --git a/hyperstyle/src/python/review/inspectors/common/issue/issue_configs.py b/hyperstyle/src/python/review/inspectors/common/issue/issue_configs.py index 431909cd..d5c7866e 100644 --- a/hyperstyle/src/python/review/inspectors/common/issue/issue_configs.py +++ b/hyperstyle/src/python/review/inspectors/common/issue/issue_configs.py @@ -2,7 +2,7 @@ import logging from dataclasses import dataclass, field -from typing import Optional, TYPE_CHECKING +from typing import TYPE_CHECKING from hyperstyle.src.python.review.inspectors.common.utils import ( contains_format_fields, @@ -136,7 +136,7 @@ def __init__(self, *issue_configs: IssueConfig) -> None: issue_config.origin_class: issue_config for issue_config in issue_configs } - def _parse_description(self, origin_class: str, description: str) -> tuple | None: + def _parse_description(self, origin_class: str, description: str) -> tuple[object, ...] | None: """Parse a description. :param origin_class: An origin class of issue. @@ -158,7 +158,7 @@ def _parse_description(self, origin_class: str, description: str) -> tuple | Non return args - def parse_measure(self, origin_class: str, description: str) -> Optional: + def parse_measure(self, origin_class: str, description: str) -> object | None: """Parse a measure from a description. :param origin_class: An origin class of issue. diff --git a/hyperstyle/src/python/review/inspectors/common/xml_parser.py b/hyperstyle/src/python/review/inspectors/common/xml_parser.py index 0ab99557..bf4c68d4 100644 --- a/hyperstyle/src/python/review/inspectors/common/xml_parser.py +++ b/hyperstyle/src/python/review/inspectors/common/xml_parser.py @@ -20,12 +20,12 @@ logger = logging.getLogger(__name__) -def __should_handle_element(element: ET) -> bool: +def __should_handle_element(element: Element) -> bool: """Checks if a tree element is a file.""" return element.tag == "file" -def __is_error(element: ET) -> bool: +def __is_error(element: Element) -> bool: """Checks if a tree element is an error.""" return element.tag == "error" diff --git a/hyperstyle/src/python/review/inspectors/flake8/flake8.py b/hyperstyle/src/python/review/inspectors/flake8/flake8.py index ca71bb20..d06a7d92 100644 --- a/hyperstyle/src/python/review/inspectors/flake8/flake8.py +++ b/hyperstyle/src/python/review/inspectors/flake8/flake8.py @@ -91,6 +91,7 @@ def choose_issue_type(code: str) -> IssueType: return CODE_TO_ISSUE_TYPE[code] regex_match = re.match(r"^([A-Z]+)(\d)\d*$", code, re.IGNORECASE) + assert regex_match is not None, f"flake8: {code} - unexpected error code format" code_prefix = regex_match.group(1) first_code_number = regex_match.group(2) diff --git a/hyperstyle/src/python/review/inspectors/ij_java/ij_java.py b/hyperstyle/src/python/review/inspectors/ij_java/ij_java.py index 5b3c43aa..d72ebe28 100644 --- a/hyperstyle/src/python/review/inspectors/ij_java/ij_java.py +++ b/hyperstyle/src/python/review/inspectors/ij_java/ij_java.py @@ -12,7 +12,7 @@ class JavaIJInspector(BaseIJInspector): inspector_type = InspectorType.IJ_JAVA - language_id = model_pb2.LanguageId.Java + language_id = model_pb2.LanguageId.Java # type: ignore[attr-defined] issue_configs = ISSUE_CONFIGS ij_inspection_to_issue_type = IJ_INSPECTION_TO_ISSUE_TYPE ij_message_to_issue_type = IJ_MESSAGE_TO_ISSUE_TYPE diff --git a/hyperstyle/src/python/review/inspectors/ij_java/issue_types.py b/hyperstyle/src/python/review/inspectors/ij_java/issue_types.py index 5c0fe456..17f7a93b 100644 --- a/hyperstyle/src/python/review/inspectors/ij_java/issue_types.py +++ b/hyperstyle/src/python/review/inspectors/ij_java/issue_types.py @@ -1,5 +1,10 @@ from __future__ import annotations -IJ_INSPECTION_TO_ISSUE_TYPE = {} +from typing import TYPE_CHECKING -IJ_MESSAGE_TO_ISSUE_TYPE = {} +if TYPE_CHECKING: + from hyperstyle.src.python.review.inspectors.common.issue.issue import IssueType + +IJ_INSPECTION_TO_ISSUE_TYPE: dict[str, IssueType] = {} + +IJ_MESSAGE_TO_ISSUE_TYPE: dict[str, dict[str, IssueType]] = {} diff --git a/hyperstyle/src/python/review/inspectors/ij_kotlin/ij_kotlin.py b/hyperstyle/src/python/review/inspectors/ij_kotlin/ij_kotlin.py index bc78323d..bbf661b7 100644 --- a/hyperstyle/src/python/review/inspectors/ij_kotlin/ij_kotlin.py +++ b/hyperstyle/src/python/review/inspectors/ij_kotlin/ij_kotlin.py @@ -12,7 +12,7 @@ class KotlinIJInspector(BaseIJInspector): inspector_type = InspectorType.IJ_KOTLIN - language_id = model_pb2.LanguageId.kotlin + language_id = model_pb2.LanguageId.kotlin # type: ignore[attr-defined] issue_configs = ISSUE_CONFIGS ij_inspection_to_issue_type = IJ_INSPECTION_TO_ISSUE_TYPE ij_message_to_issue_type = IJ_MESSAGE_TO_ISSUE_TYPE diff --git a/hyperstyle/src/python/review/inspectors/ij_kotlin/issue_types.py b/hyperstyle/src/python/review/inspectors/ij_kotlin/issue_types.py index 5c0fe456..17f7a93b 100644 --- a/hyperstyle/src/python/review/inspectors/ij_kotlin/issue_types.py +++ b/hyperstyle/src/python/review/inspectors/ij_kotlin/issue_types.py @@ -1,5 +1,10 @@ from __future__ import annotations -IJ_INSPECTION_TO_ISSUE_TYPE = {} +from typing import TYPE_CHECKING -IJ_MESSAGE_TO_ISSUE_TYPE = {} +if TYPE_CHECKING: + from hyperstyle.src.python.review.inspectors.common.issue.issue import IssueType + +IJ_INSPECTION_TO_ISSUE_TYPE: dict[str, IssueType] = {} + +IJ_MESSAGE_TO_ISSUE_TYPE: dict[str, dict[str, IssueType]] = {} diff --git a/hyperstyle/src/python/review/inspectors/ij_python/ij_python.py b/hyperstyle/src/python/review/inspectors/ij_python/ij_python.py index 10016dab..3e864fa0 100644 --- a/hyperstyle/src/python/review/inspectors/ij_python/ij_python.py +++ b/hyperstyle/src/python/review/inspectors/ij_python/ij_python.py @@ -12,7 +12,7 @@ class PythonIJInspector(BaseIJInspector): inspector_type = InspectorType.IJ_PYTHON - language_id = model_pb2.LanguageId.Python + language_id = model_pb2.LanguageId.Python # type: ignore[attr-defined] issue_configs = ISSUE_CONFIGS ij_inspection_to_issue_type = IJ_INSPECTION_TO_ISSUE_TYPE ij_message_to_issue_type = IJ_MESSAGE_TO_ISSUE_TYPE diff --git a/hyperstyle/src/python/review/inspectors/ij_python/issue_types.py b/hyperstyle/src/python/review/inspectors/ij_python/issue_types.py index aa715272..6f14d8ae 100644 --- a/hyperstyle/src/python/review/inspectors/ij_python/issue_types.py +++ b/hyperstyle/src/python/review/inspectors/ij_python/issue_types.py @@ -3,7 +3,7 @@ from hyperstyle.src.python.review.inspectors.common.issue.issue import IssueType # Synchronized with https://github.com/JetBrains-Research/code-quality-ij-server/tree/master/docs/inspections/python -IJ_INSPECTION_TO_ISSUE_TYPE = { +IJ_INSPECTION_TO_ISSUE_TYPE: dict[str, IssueType] = { # BEST_PRACTICES "PyUnusedLocalInspection": IssueType.BEST_PRACTICES, "PySimplifyBooleanCheckInspection": IssueType.BEST_PRACTICES, @@ -63,7 +63,7 @@ "PyUnresolvedReferencesInspection": IssueType.ERROR_PRONE, } -IJ_MESSAGE_TO_ISSUE_TYPE = { +IJ_MESSAGE_TO_ISSUE_TYPE: dict[str, dict[str, IssueType]] = { "PyDataclassInspection": { "is useless until " "__post_init__" " is declared": IssueType.BEST_PRACTICES, "should take all init-only variables (incl. inherited) in the same order as they are defined": IssueType.BEST_PRACTICES, diff --git a/hyperstyle/src/python/review/inspectors/pyast/python_ast.py b/hyperstyle/src/python/review/inspectors/pyast/python_ast.py index a7ebd6ab..aa61d5e0 100644 --- a/hyperstyle/src/python/review/inspectors/pyast/python_ast.py +++ b/hyperstyle/src/python/review/inspectors/pyast/python_ast.py @@ -145,7 +145,7 @@ def inspect(cls, path: Path, config: dict[str, Any]) -> list[BaseIssue]: path_to_files = language.filter_paths_by_language(path_to_files, Language.PYTHON) - metrics = [] + metrics: list[BaseIssue] = [] for path_to_file in path_to_files: file_content = path_to_file.read_text() tree = ast.parse(file_content, path_to_file.name) diff --git a/hyperstyle/src/python/review/inspectors/pylint/pylint.py b/hyperstyle/src/python/review/inspectors/pylint/pylint.py index f94c18dc..149981b9 100644 --- a/hyperstyle/src/python/review/inspectors/pylint/pylint.py +++ b/hyperstyle/src/python/review/inspectors/pylint/pylint.py @@ -64,7 +64,7 @@ def parse(cls, output: str) -> list[BaseIssue]: row_re = re.compile(r"^(.*):(\d+):(\d+):([IRCWEF]\d+):(.*)$", re.MULTILINE) issue_configs_handler = IssueConfigsHandler(*ISSUE_CONFIGS) - issues = [] + issues: list[BaseIssue] = [] for groups in row_re.findall(output): if groups[1] == INFO_CATEGORY: continue @@ -76,7 +76,7 @@ def parse(cls, output: str) -> list[BaseIssue]: origin_class = groups[3] issue_type = cls.choose_issue_type(origin_class) if issue_type not in cls.supported_issue_types: - logger.error("pylint: unsupported issue type %s", issue_type.__name__) + logger.error("pylint: unsupported issue type %s", issue_type.__name__) # type: ignore[attr-defined] continue base_issue = BaseIssue( diff --git a/hyperstyle/src/python/review/inspectors/radon/radon.py b/hyperstyle/src/python/review/inspectors/radon/radon.py index dd6ea992..1bff57bb 100644 --- a/hyperstyle/src/python/review/inspectors/radon/radon.py +++ b/hyperstyle/src/python/review/inspectors/radon/radon.py @@ -31,7 +31,7 @@ def inspect_in_memory(cls, code: str, config: dict[str, Any]) -> list[BaseIssue] @classmethod def inspect(cls, path: Path, config: dict[str, Any]) -> list[BaseIssue]: - mi_command = [ + mi_command: list[str] = [ sys.executable, "-m", "radon", @@ -39,7 +39,7 @@ def inspect(cls, path: Path, config: dict[str, Any]) -> list[BaseIssue]: "--max", "F", # set the maximum MI rank to display "--show", # actual MI value is shown in results, alongside the rank - path, + str(path), ] mi_output = run_in_subprocess(mi_command) diff --git a/hyperstyle/src/python/review/logging_config.py b/hyperstyle/src/python/review/logging_config.py index ab7ef702..ff50d915 100644 --- a/hyperstyle/src/python/review/logging_config.py +++ b/hyperstyle/src/python/review/logging_config.py @@ -1,8 +1,12 @@ from __future__ import annotations import sys +from typing import TYPE_CHECKING -logging_config: dict[str, object] = { +if TYPE_CHECKING: + from logging.config import _DictConfigArgs + +logging_config: _DictConfigArgs = { "version": 1, "formatters": { "common": { diff --git a/hyperstyle/src/python/review/quality/model.py b/hyperstyle/src/python/review/quality/model.py index cb72606d..f178146e 100644 --- a/hyperstyle/src/python/review/quality/model.py +++ b/hyperstyle/src/python/review/quality/model.py @@ -34,7 +34,7 @@ def __le__(self, other: QualityType) -> bool: class Rule(abc.ABC): rule_type: IssueType - quality_type: QualityType + quality_type: QualityType | None next_level_type: QualityType next_level_delta: float value: int @@ -43,6 +43,10 @@ class Rule(abc.ABC): def apply(self, *args, **kwargs) -> None: pass + @abc.abstractmethod + def merge(self, other: Rule) -> Rule: + raise NotImplementedError + class Quality: def __init__(self, rules: list[Rule]) -> None: @@ -50,7 +54,10 @@ def __init__(self, rules: list[Rule]) -> None: @property def quality_type(self) -> QualityType: - return min((rule.quality_type for rule in self.rules), default=QualityType.EXCELLENT) + return min( + (rule.quality_type for rule in self.rules if rule.quality_type is not None), + default=QualityType.EXCELLENT, + ) @property def next_quality_type(self) -> QualityType: diff --git a/hyperstyle/src/python/review/quality/penalty.py b/hyperstyle/src/python/review/quality/penalty.py index 300a1e28..6ed1b675 100644 --- a/hyperstyle/src/python/review/quality/penalty.py +++ b/hyperstyle/src/python/review/quality/penalty.py @@ -59,7 +59,7 @@ class PenaltyConfig: class PreviousIssue: origin_class: str number: int - category: IssueType = None + category: IssueType | None = None def get_previous_issues_by_language(lang_to_history: str | None, language: Language) -> list[PreviousIssue]: @@ -78,12 +78,10 @@ def get_previous_issues_by_language(lang_to_history: str | None, language: Langu def categorize(previous_issues: list[PreviousIssue], current_issues: list[BaseIssue]) -> None: """For each previously made issue determines its category, with the help of current issues.""" - origin_class_to_category = {} - for issue in current_issues: - origin_class_to_category[issue.origin_class] = issue.type + origin_class_to_category = {issue.origin_class: issue.type for issue in current_issues} for issue in previous_issues: - issue.category = origin_class_to_category.get(issue.origin_class, None) + issue.category = origin_class_to_category.get(issue.origin_class) class Punisher: @@ -154,9 +152,13 @@ def _get_penalty_coefficient( filter(lambda issue: issue.origin_class in penalizing_classes, previous_issues) ) - coefficient = 0 + coefficient: float = 0 for issue in penalizing_issues: - coefficient += ISSUE_TYPE_TO_PENALTY_COEFFICIENT.get(issue.category, 1) * issue.number + coefficient += ( + ISSUE_TYPE_TO_PENALTY_COEFFICIENT.get(issue.category, 1) * issue.number + if issue.category + else issue.number + ) return coefficient @@ -164,7 +166,7 @@ def _get_normalized_penalty_coefficient(self, current_issues: list[BaseIssue]) - """The penalty coefficient is normalized by the formula: k / (k + n), where k is the penalty coefficient, n is the number of current issues. """ - coefficient = 0 + coefficient: float = 0 if current_issues: coefficient = self._penalty_coefficient / (self._penalty_coefficient + len(current_issues)) @@ -185,7 +187,11 @@ def _get_issue_class_to_influence( result = {} for issue in penalizing_issues: - issue_coefficient = ISSUE_TYPE_TO_PENALTY_COEFFICIENT.get(issue.category, 1) * issue.number + issue_coefficient = ( + ISSUE_TYPE_TO_PENALTY_COEFFICIENT.get(issue.category, 1) * issue.number + if issue.category + else issue.number + ) normalized_issue_coefficient = issue_coefficient / ( self._penalty_coefficient + len(current_issues) ) diff --git a/hyperstyle/src/python/review/quality/rules/best_practices_scoring.py b/hyperstyle/src/python/review/quality/rules/best_practices_scoring.py index 9d5e4ab5..5ba01148 100644 --- a/hyperstyle/src/python/review/quality/rules/best_practices_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/best_practices_scoring.py @@ -57,13 +57,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: BestPracticesRule) -> BestPracticesRule: + def merge(self, other: Rule) -> BestPracticesRule: + assert isinstance(other, BestPracticesRule) config = BestPracticesRuleConfig( min(self.config.n_best_practices_moderate, other.config.n_best_practices_moderate), min(self.config.n_best_practices_good, other.config.n_best_practices_good), n_files=self.config.n_files + other.config.n_files, ) result_rule = BestPracticesRule(config) + assert self.n_best_practices is not None + assert other.n_best_practices is not None result_rule.apply(self.n_best_practices + other.n_best_practices) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/boolean_length_scoring.py b/hyperstyle/src/python/review/quality/rules/boolean_length_scoring.py index 6c2bc8b4..74b56c2e 100644 --- a/hyperstyle/src/python/review/quality/rules/boolean_length_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/boolean_length_scoring.py @@ -64,13 +64,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: BooleanExpressionRule) -> BooleanExpressionRule: + def merge(self, other: Rule) -> BooleanExpressionRule: + assert isinstance(other, BooleanExpressionRule) config = BooleanExpressionRuleConfig( min(self.config.bool_expr_len_bad, other.config.bool_expr_len_bad), min(self.config.bool_expr_len_moderate, other.config.bool_expr_len_moderate), min(self.config.bool_expr_len_good, other.config.bool_expr_len_good), ) result_rule = BooleanExpressionRule(config) + assert self.bool_expr_len is not None + assert other.bool_expr_len is not None result_rule.apply(max(self.bool_expr_len, other.bool_expr_len)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/class_response_scoring.py b/hyperstyle/src/python/review/quality/rules/class_response_scoring.py index 6eeefcc4..a57dcf93 100644 --- a/hyperstyle/src/python/review/quality/rules/class_response_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/class_response_scoring.py @@ -51,12 +51,15 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: ResponseRule) -> ResponseRule: + def merge(self, other: Rule) -> ResponseRule: + assert isinstance(other, ResponseRule) config = ResponseRuleConfig( min(self.config.response_moderate, other.config.response_moderate), min(self.config.response_good, other.config.response_good), ) result_rule = ResponseRule(config) + assert self.response is not None + assert other.response is not None result_rule.apply(max(self.response, other.response)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/code_style_scoring.py b/hyperstyle/src/python/review/quality/rules/code_style_scoring.py index 94837c35..038b27f1 100644 --- a/hyperstyle/src/python/review/quality/rules/code_style_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/code_style_scoring.py @@ -72,8 +72,8 @@ def __init__(self, config: CodeStyleRuleConfig) -> None: self.rule_type = IssueType.CODE_STYLE self.total_lines = 0 self.n_code_style_lines = 0 - self.ratio = 0 - self.quality_type = None + self.ratio: float = 0 + self.quality_type: QualityType | None = None self.next_level_delta = 0 # TODO: refactor @@ -119,7 +119,10 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: CodeStyleRule) -> CodeStyleRule: + def merge(self, other: Rule) -> CodeStyleRule: + assert isinstance(other, CodeStyleRule) + assert self.quality_type is not None + assert other.quality_type is not None if self.quality_type > other.quality_type: return other return self diff --git a/hyperstyle/src/python/review/quality/rules/cohesion_scoring.py b/hyperstyle/src/python/review/quality/rules/cohesion_scoring.py index 79c20423..064d2420 100644 --- a/hyperstyle/src/python/review/quality/rules/cohesion_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/cohesion_scoring.py @@ -61,13 +61,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: CohesionRule) -> CohesionRule: + def merge(self, other: Rule) -> CohesionRule: + assert isinstance(other, CohesionRule) config = CohesionRuleConfig( min(self.config.cohesion_lack_bad, other.config.cohesion_lack_bad), min(self.config.cohesion_lack_moderate, other.config.cohesion_lack_moderate), min(self.config.cohesion_lack_good, other.config.cohesion_lack_good), ) result_rule = CohesionRule(config) + assert self.cohesion_lack is not None + assert other.cohesion_lack is not None result_rule.apply(max(self.cohesion_lack, other.cohesion_lack)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/complexity_scoring.py b/hyperstyle/src/python/review/quality/rules/complexity_scoring.py index f9685fb7..20da2e87 100644 --- a/hyperstyle/src/python/review/quality/rules/complexity_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/complexity_scoring.py @@ -58,13 +58,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: ComplexityRule) -> ComplexityRule: + def merge(self, other: Rule) -> ComplexityRule: + assert isinstance(other, ComplexityRule) config = ComplexityRuleConfig( min(self.config.complexity_bad, other.config.complexity_bad), min(self.config.complexity_moderate, other.config.complexity_moderate), min(self.config.complexity_good, other.config.complexity_good), ) result_rule = ComplexityRule(config) + assert self.complexity is not None + assert other.complexity is not None result_rule.apply(max(self.complexity, other.complexity)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/coupling_scoring.py b/hyperstyle/src/python/review/quality/rules/coupling_scoring.py index 85831061..f778929b 100644 --- a/hyperstyle/src/python/review/quality/rules/coupling_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/coupling_scoring.py @@ -51,12 +51,15 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.MODERATE return QualityType.EXCELLENT - def merge(self, other: CouplingRule) -> CouplingRule: + def merge(self, other: Rule) -> CouplingRule: + assert isinstance(other, CouplingRule) config = CouplingRuleConfig( min(self.config.coupling_bad, other.config.coupling_bad), min(self.config.coupling_moderate, other.config.coupling_moderate), ) result_rule = CouplingRule(config) + assert self.coupling is not None + assert other.coupling is not None result_rule.apply(max(self.coupling, other.coupling)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/cyclomatic_complexity_scoring.py b/hyperstyle/src/python/review/quality/rules/cyclomatic_complexity_scoring.py index 73859e1e..c90c95e2 100644 --- a/hyperstyle/src/python/review/quality/rules/cyclomatic_complexity_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/cyclomatic_complexity_scoring.py @@ -62,12 +62,15 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.MODERATE return QualityType.EXCELLENT - def merge(self, other: CyclomaticComplexityRule) -> CyclomaticComplexityRule: + def merge(self, other: Rule) -> CyclomaticComplexityRule: + assert isinstance(other, CyclomaticComplexityRule) config = CyclomaticComplexityRuleConfig( min(self.config.cc_value_bad, other.config.cc_value_bad), min(self.config.cc_value_moderate, other.config.cc_value_moderate), ) result_rule = CyclomaticComplexityRule(config) + assert self.cc_value is not None + assert other.cc_value is not None result_rule.apply(max(self.cc_value, other.cc_value)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/error_prone_scoring.py b/hyperstyle/src/python/review/quality/rules/error_prone_scoring.py index 8879906e..a5227292 100644 --- a/hyperstyle/src/python/review/quality/rules/error_prone_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/error_prone_scoring.py @@ -48,9 +48,12 @@ def apply(self, n_error_prone: int) -> None: def __get_next_quality_type(self) -> QualityType: return QualityType.EXCELLENT - def merge(self, other: ErrorProneRule) -> ErrorProneRule: + def merge(self, other: Rule) -> ErrorProneRule: + assert isinstance(other, ErrorProneRule) config = ErrorProneRuleConfig(min(self.config.n_error_prone_bad, other.config.n_error_prone_bad)) result_rule = ErrorProneRule(config) + assert self.n_error_prone is not None + assert other.n_error_prone is not None result_rule.apply(self.n_error_prone + other.n_error_prone) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/function_length_scoring.py b/hyperstyle/src/python/review/quality/rules/function_length_scoring.py index 14be729f..c1449986 100644 --- a/hyperstyle/src/python/review/quality/rules/function_length_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/function_length_scoring.py @@ -51,11 +51,14 @@ def apply(self, func_len: int) -> None: def __get_next_quality_type(self) -> QualityType: return QualityType.EXCELLENT - def merge(self, other: FunctionLengthRule) -> FunctionLengthRule: + def merge(self, other: Rule) -> FunctionLengthRule: + assert isinstance(other, FunctionLengthRule) config = FunctionLengthRuleConfig( min(self.config.func_len_bad, other.config.func_len_bad), ) result_rule = FunctionLengthRule(config) + assert self.func_len is not None + assert other.func_len is not None result_rule.apply(max(self.func_len, other.func_len)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/inheritance_depth_scoring.py b/hyperstyle/src/python/review/quality/rules/inheritance_depth_scoring.py index 79a87a74..6e0191a9 100644 --- a/hyperstyle/src/python/review/quality/rules/inheritance_depth_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/inheritance_depth_scoring.py @@ -44,9 +44,12 @@ def apply(self, depth: int) -> None: def __get_next_quality_type(self) -> QualityType: return QualityType.EXCELLENT - def merge(self, other: InheritanceDepthRule) -> InheritanceDepthRule: + def merge(self, other: Rule) -> InheritanceDepthRule: + assert isinstance(other, InheritanceDepthRule) config = InheritanceDepthRuleConfig(min(self.config.depth_bad, other.config.depth_bad)) result_rule = InheritanceDepthRule(config) + assert self.depth is not None + assert other.depth is not None result_rule.apply(max(self.depth, other.depth)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/line_len_scoring.py b/hyperstyle/src/python/review/quality/rules/line_len_scoring.py index 0c0d290b..4db860e0 100644 --- a/hyperstyle/src/python/review/quality/rules/line_len_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/line_len_scoring.py @@ -59,7 +59,8 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: LineLengthRule) -> LineLengthRule: + def merge(self, other: Rule) -> LineLengthRule: + assert isinstance(other, LineLengthRule) config = LineLengthRuleConfig( min(self.config.n_line_len_bad, other.config.n_line_len_bad), min(self.config.n_line_len_moderate, other.config.n_line_len_moderate), diff --git a/hyperstyle/src/python/review/quality/rules/maintainability_scoring.py b/hyperstyle/src/python/review/quality/rules/maintainability_scoring.py index b08860c0..bec14770 100644 --- a/hyperstyle/src/python/review/quality/rules/maintainability_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/maintainability_scoring.py @@ -75,13 +75,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: MaintainabilityRule) -> MaintainabilityRule: + def merge(self, other: Rule) -> MaintainabilityRule: + assert isinstance(other, MaintainabilityRule) config = MaintainabilityRuleConfig( min(self.config.maintainability_lack_bad, other.config.maintainability_lack_bad), min(self.config.maintainability_lack_moderate, other.config.maintainability_lack_moderate), min(self.config.maintainability_lack_good, other.config.maintainability_lack_good), ) result_rule = MaintainabilityRule(config) + assert self.maintainability_lack is not None + assert other.maintainability_lack is not None result_rule.apply(max(self.maintainability_lack, other.maintainability_lack)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/method_number_scoring.py b/hyperstyle/src/python/review/quality/rules/method_number_scoring.py index 0b5c35f5..2e6f79d3 100644 --- a/hyperstyle/src/python/review/quality/rules/method_number_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/method_number_scoring.py @@ -58,13 +58,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: MethodNumberRule) -> MethodNumberRule: + def merge(self, other: Rule) -> MethodNumberRule: + assert isinstance(other, MethodNumberRule) config = MethodNumberRuleConfig( min(self.config.method_number_bad, other.config.method_number_bad), min(self.config.method_number_moderate, other.config.method_number_moderate), min(self.config.method_number_good, other.config.method_number_good), ) result_rule = MethodNumberRule(config) + assert self.method_number is not None + assert other.method_number is not None result_rule.apply(max(self.method_number, other.method_number)) return result_rule diff --git a/hyperstyle/src/python/review/quality/rules/weighted_methods_scoring.py b/hyperstyle/src/python/review/quality/rules/weighted_methods_scoring.py index 635f6164..2766e57c 100644 --- a/hyperstyle/src/python/review/quality/rules/weighted_methods_scoring.py +++ b/hyperstyle/src/python/review/quality/rules/weighted_methods_scoring.py @@ -56,13 +56,16 @@ def __get_next_quality_type(self) -> QualityType: return QualityType.GOOD return QualityType.EXCELLENT - def merge(self, other: WeightedMethodsRule) -> WeightedMethodsRule: + def merge(self, other: Rule) -> WeightedMethodsRule: + assert isinstance(other, WeightedMethodsRule) config = WeightedMethodsRuleConfig( min(self.config.weighted_methods_bad, other.config.weighted_methods_bad), min(self.config.weighted_methods_moderate, other.config.weighted_methods_moderate), min(self.config.weighted_methods_good, other.config.weighted_methods_good), ) result_rule = WeightedMethodsRule(config) + assert self.weighted_methods is not None + assert other.weighted_methods is not None result_rule.apply(max(self.weighted_methods, other.weighted_methods)) return result_rule diff --git a/hyperstyle/src/python/review/reviewers/common.py b/hyperstyle/src/python/review/reviewers/common.py index 2e85a328..fa952e95 100644 --- a/hyperstyle/src/python/review/reviewers/common.py +++ b/hyperstyle/src/python/review/reviewers/common.py @@ -73,26 +73,28 @@ } -def _inspect_code( - metadata: Metadata, config: ApplicationConfig, language: Language -) -> list[BaseIssue] | None: +def _inspect_code(metadata: Metadata, config: ApplicationConfig, language: Language) -> list[BaseIssue]: inspectors = LANGUAGE_TO_INSPECTORS[language] - ij_inspectors = list( - filter( - lambda inspector: isinstance(inspector, BaseIJInspector) - and inspector.inspector_type not in config.disabled_inspectors, - inspectors, - ), - ) + ij_inspectors: list[BaseIJInspector] = [ + inspector + for inspector in inspectors + if ( + isinstance(inspector, BaseIJInspector) + and inspector.inspector_type not in config.disabled_inspectors + ) + ] connection_parameters = ( None if config.ij_config is None else json.loads(config.ij_config).get(language.value.lower()) ) - if ij_inspectors and connection_parameters is None: - msg = f"Connection parameters for {language.value} inspectors are not provided" - raise ValueError(msg) - for inspector in ij_inspectors: - inspector.setup_connection_parameters(connection_parameters["host"], connection_parameters["port"]) + if ij_inspectors: + if connection_parameters is None: + msg = f"Connection parameters for {language.value} inspectors are not provided" + raise ValueError(msg) + for inspector in ij_inspectors: + inspector.setup_connection_parameters( + connection_parameters["host"], connection_parameters["port"] + ) if isinstance(metadata, InMemoryMetadata): return inspect_in_parallel(run_inspector_in_memory, metadata.code, config, inspectors) @@ -110,19 +112,20 @@ def perform_language_review( if not config.allow_duplicates: issues = filter_duplicate_issues(issues) - current_files = None if isinstance(metadata, FileMetadata): current_files = [metadata.path] issues = filter_out_of_range_issues(issues, config.start_line, config.end_line) elif isinstance(metadata, ProjectMetadata): current_files = metadata.language_to_files[language] + else: + current_files = None file_path_to_issues = defaultdict(list) for issue in issues: file_path_to_issues[issue.file_path].append(issue) if current_files is None: - files = file_path_to_issues.keys() + files = list(file_path_to_issues.keys()) current_files = [] if len(files) == 0 else files previous_issues = get_previous_issues_by_language(config.history, language) diff --git a/hyperstyle/src/python/review/reviewers/utils/code_statistics.py b/hyperstyle/src/python/review/reviewers/utils/code_statistics.py index b9b92509..06d97cb4 100644 --- a/hyperstyle/src/python/review/reviewers/utils/code_statistics.py +++ b/hyperstyle/src/python/review/reviewers/utils/code_statistics.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from hyperstyle.src.python.review.common.file_system import get_total_code_lines_from_file -from hyperstyle.src.python.review.inspectors.common.issue.issue import BaseIssue, IssueType +from hyperstyle.src.python.review.inspectors.common.issue.issue import BaseIssue, IssueType, MeasurableIssue if TYPE_CHECKING: from pathlib import Path @@ -64,7 +64,11 @@ def get_code_style_lines(issues: list[BaseIssue]) -> int: def __get_max_measure_by_issue_type(issue_type: IssueType, issues: list[BaseIssue]) -> int: return max( - (issue.measure() for issue in filter(lambda issue: issue.type == issue_type, issues)), + ( + issue.measure() or 0 + for issue in issues + if isinstance(issue, MeasurableIssue) and issue.type == issue_type + ), default=0, ) diff --git a/hyperstyle/src/python/review/reviewers/utils/issues_filter.py b/hyperstyle/src/python/review/reviewers/utils/issues_filter.py index e2848680..bf72dc79 100644 --- a/hyperstyle/src/python/review/reviewers/utils/issues_filter.py +++ b/hyperstyle/src/python/review/reviewers/utils/issues_filter.py @@ -7,7 +7,7 @@ BaseIssue, IssueDifficulty, IssueType, - Measurable, + MeasurableIssue, ) from hyperstyle.src.python.review.quality.rules.boolean_length_scoring import ( LANGUAGE_TO_BOOLEAN_EXPRESSION_RULE_CONFIG, @@ -60,7 +60,7 @@ def __get_issue_type_to_low_measure_dict(language: Language) -> dict[IssueType, def __more_than_low_measure(issue: BaseIssue, issue_type_to_low_measure_dict: dict[IssueType, int]) -> bool: issue_type = issue.type return not ( - isinstance(issue, Measurable) + isinstance(issue, MeasurableIssue) and issue.measure() <= issue_type_to_low_measure_dict.get(issue_type, -1) ) diff --git a/hyperstyle/src/python/review/reviewers/utils/metadata_exploration.py b/hyperstyle/src/python/review/reviewers/utils/metadata_exploration.py index 7b8dd334..a37abcb3 100644 --- a/hyperstyle/src/python/review/reviewers/utils/metadata_exploration.py +++ b/hyperstyle/src/python/review/reviewers/utils/metadata_exploration.py @@ -29,9 +29,7 @@ class ProjectMetadata: @property def languages(self) -> set[Language]: - languages = set() - languages.update(file_metadata.language for file_metadata in self.inner_files) - return languages + return {file_metadata.language for file_metadata in self.inner_files} @property def size_bytes(self) -> int: diff --git a/hyperstyle/src/python/review/reviewers/utils/print_review.py b/hyperstyle/src/python/review/reviewers/utils/print_review.py index 007a2fb9..f134daf0 100644 --- a/hyperstyle/src/python/review/reviewers/utils/print_review.py +++ b/hyperstyle/src/python/review/reviewers/utils/print_review.py @@ -3,13 +3,10 @@ import json import linecache from enum import Enum, unique -from pathlib import Path -from typing import Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING, TypedDict from hyperstyle.src.python.review.common.file_system import get_file_line -from hyperstyle.src.python.review.inspectors.common.inspector.inspector_type import InspectorType -from hyperstyle.src.python.review.inspectors.common.issue.issue import BaseIssue, IssueDifficulty, IssueType -from hyperstyle.src.python.review.quality.penalty import PenaltyIssue +from hyperstyle.src.python.review.inspectors.common.issue.issue import BaseIssue, IssueDifficulty from hyperstyle.src.python.review.reviewers.review_result import ( FileReviewResult, GeneralReviewResult, @@ -17,6 +14,8 @@ ) if TYPE_CHECKING: + from pathlib import Path + from hyperstyle.src.python.review.application_config import ApplicationConfig from hyperstyle.src.python.review.quality.model import QualityType @@ -83,9 +82,9 @@ def _get_quality_with_penalty(review_result: ReviewResult) -> dict[IssueDifficul def get_quality_json_dict( quality: dict[IssueDifficulty, QualityType], config: ApplicationConfig -) -> dict[str, object]: - quality_json_dict = { - difficulty.value: { +) -> dict[IssueDifficulty, object] | object: + quality_json_dict: dict[IssueDifficulty, object] = { + difficulty: { OutputJsonFields.CODE.value: quality.value, OutputJsonFields.TEXT.value: f"Code quality (beta): {quality.value}", } @@ -95,7 +94,7 @@ def get_quality_json_dict( if config.group_by_difficulty: return quality_json_dict - return quality_json_dict[IssueDifficulty.HARD.value] + return quality_json_dict[IssueDifficulty.HARD] def get_influence_on_penalty_json_dict( @@ -106,8 +105,8 @@ def get_influence_on_penalty_json_dict( quality_without_penalty = _get_quality_without_penalty(review_result) quality_with_penalty = _get_quality_with_penalty(review_result) - influence_on_penalty_json_dict = { - difficulty.value: punisher.get_issue_influence_on_penalty(origin_class) + influence_on_penalty_json_dict: dict[IssueDifficulty, int] = { + difficulty: punisher.get_issue_influence_on_penalty(origin_class) if quality_with_penalty[difficulty] != quality_without_penalty[difficulty] else 0 for difficulty, punisher in review_result.punisher_by_difficulty.items() @@ -116,25 +115,30 @@ def get_influence_on_penalty_json_dict( if config.group_by_difficulty: return influence_on_penalty_json_dict - return influence_on_penalty_json_dict[IssueDifficulty.HARD.value] + return influence_on_penalty_json_dict[IssueDifficulty.HARD] -def convert_review_result_to_json_dict( - review_result: ReviewResult, config: ApplicationConfig -) -> dict[str, object]: +class OutputJson(TypedDict): + quality: dict[IssueDifficulty, object] | object + issues: list[dict[str, object]] + file_name: str + + +def convert_review_result_to_json_dict(review_result: ReviewResult, config: ApplicationConfig) -> OutputJson: issues = review_result.issues issues.sort(key=lambda issue: issue.line_no) quality_with_penalty = _get_quality_with_penalty(review_result) - output_json = {} + output_json: OutputJson = { + OutputJsonFields.QUALITY.value: get_quality_json_dict(quality_with_penalty, config), + OutputJsonFields.ISSUES.value: [], + OutputJsonFields.FILE_NAME.value: "", + } if isinstance(review_result, FileReviewResult): output_json[OutputJsonFields.FILE_NAME.value] = str(review_result.file_path) - output_json[OutputJsonFields.QUALITY.value] = get_quality_json_dict(quality_with_penalty, config) - output_json[OutputJsonFields.ISSUES.value] = [] - for issue in issues: json_issue = convert_issue_to_json(issue, config) @@ -162,7 +166,7 @@ def print_review_result_as_multi_file_json( def get_review_result_as_multi_file_json( review_result: GeneralReviewResult, config: ApplicationConfig -) -> dict: +) -> dict[str, object]: review_result.file_review_results.sort(key=lambda result: result.file_path) file_review_result_jsons = [ @@ -211,23 +215,3 @@ def convert_issue_to_json(issue: BaseIssue, config: ApplicationConfig) -> dict[s OutputJsonFields.CATEGORY.value: issue_type.value, OutputJsonFields.DIFFICULTY.value: issue.difficulty.value, } - - -# It works only for old json format -def convert_json_to_issues(issues_json: list[dict]) -> list[PenaltyIssue]: - return [ - PenaltyIssue( - origin_class=issue[OutputJsonFields.CODE.value], - description=issue[OutputJsonFields.TEXT.value], - line_no=int(issue[OutputJsonFields.LINE_NUMBER.value]), - column_no=int(issue[OutputJsonFields.COLUMN_NUMBER.value]), - type=IssueType(issue[OutputJsonFields.CATEGORY.value]), - file_path=Path(), - inspector_type=InspectorType.UNDEFINED, - influence_on_penalty=issue.get(OutputJsonFields.INFLUENCE_ON_PENALTY.value, 0), - difficulty=IssueDifficulty( - issue.get(OutputJsonFields.DIFFICULTY.value, IssueDifficulty.HARD.value) - ), - ) - for issue in issues_json - ] diff --git a/hyperstyle/src/python/review/run_tool.py b/hyperstyle/src/python/review/run_tool.py index 7fd1bc0e..adfcba06 100644 --- a/hyperstyle/src/python/review/run_tool.py +++ b/hyperstyle/src/python/review/run_tool.py @@ -35,7 +35,7 @@ def parse_disabled_inspectors(value: str) -> set[InspectorType]: allowed_names = {inspector.value for inspector in InspectorType} if not all(name in allowed_names for name in passed_names): msg = "disable" - raise argparse.ArgumentError(msg, "Incorrect inspectors' names") + raise argparse.ArgumentError(msg, "Incorrect inspectors' names") # type: ignore[arg-type] return {InspectorType(name) for name in passed_names} @@ -50,7 +50,7 @@ def positive_int(value: str) -> int: def configure_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument( - RunToolArgument.VERBOSITY.value.short_name, + RunToolArgument.VERBOSITY.value.short_name, # type: ignore[arg-type] RunToolArgument.VERBOSITY.value.long_name, help=RunToolArgument.VERBOSITY.value.description, default=VerbosityLevel.DISABLE.value, @@ -60,7 +60,7 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: # Usage example: -d Flake8,IntelliJ parser.add_argument( - RunToolArgument.DISABLE.value.short_name, + RunToolArgument.DISABLE.value.short_name, # type: ignore[arg-type] RunToolArgument.DISABLE.value.long_name, help=RunToolArgument.DISABLE.value.description, type=parse_disabled_inspectors, @@ -107,7 +107,7 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( - RunToolArgument.FORMAT.value.short_name, + RunToolArgument.FORMAT.value.short_name, # type: ignore[arg-type] RunToolArgument.FORMAT.value.long_name, default=OutputFormat.JSON.value, choices=OutputFormat.values(), @@ -116,7 +116,7 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( - RunToolArgument.START_LINE.value.short_name, + RunToolArgument.START_LINE.value.short_name, # type: ignore[arg-type] RunToolArgument.START_LINE.value.long_name, default=1, type=positive_int, @@ -124,7 +124,7 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( - RunToolArgument.END_LINE.value.short_name, + RunToolArgument.END_LINE.value.short_name, # type: ignore[arg-type] RunToolArgument.END_LINE.value.long_name, default=None, type=positive_int, diff --git a/poetry.lock b/poetry.lock index 2a6d46a5..78a9a004 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1228,29 +1228,29 @@ files = [ [[package]] name = "ruff" -version = "0.6.5" +version = "0.6.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748"}, - {file = "ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69"}, - {file = "ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178"}, - {file = "ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae"}, - {file = "ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc"}, - {file = "ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5"}, - {file = "ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9"}, - {file = "ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0"}, - {file = "ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb"}, + {file = "ruff-0.6.7-py3-none-linux_armv6l.whl", hash = "sha256:08277b217534bfdcc2e1377f7f933e1c7957453e8a79764d004e44c40db923f2"}, + {file = "ruff-0.6.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c6707a32e03b791f4448dc0dce24b636cbcdee4dd5607adc24e5ee73fd86c00a"}, + {file = "ruff-0.6.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:533d66b7774ef224e7cf91506a7dafcc9e8ec7c059263ec46629e54e7b1f90ab"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a86aac6f915932d259f7bec79173e356165518859f94649d8c50b81ff087e9"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3f8822defd260ae2460ea3832b24d37d203c3577f48b055590a426a722d50ef"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba4efe5c6dbbb58be58dd83feedb83b5e95c00091bf09987b4baf510fee5c99"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:525201b77f94d2b54868f0cbe5edc018e64c22563da6c5c2e5c107a4e85c1c0d"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8854450839f339e1049fdbe15d875384242b8e85d5c6947bb2faad33c651020b"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f0b62056246234d59cbf2ea66e84812dc9ec4540518e37553513392c171cb18"}, + {file = "ruff-0.6.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1462fa56c832dc0cea5b4041cfc9c97813505d11cce74ebc6d1aae068de36b"}, + {file = "ruff-0.6.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:02b083770e4cdb1495ed313f5694c62808e71764ec6ee5db84eedd82fd32d8f5"}, + {file = "ruff-0.6.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c05fd37013de36dfa883a3854fae57b3113aaa8abf5dea79202675991d48624"}, + {file = "ruff-0.6.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f49c9caa28d9bbfac4a637ae10327b3db00f47d038f3fbb2195c4d682e925b14"}, + {file = "ruff-0.6.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0e1655868164e114ba43a908fd2d64a271a23660195017c17691fb6355d59bb"}, + {file = "ruff-0.6.7-py3-none-win32.whl", hash = "sha256:a939ca435b49f6966a7dd64b765c9df16f1faed0ca3b6f16acdf7731969deb35"}, + {file = "ruff-0.6.7-py3-none-win_amd64.whl", hash = "sha256:590445eec5653f36248584579c06252ad2e110a5d1f32db5420de35fb0e1c977"}, + {file = "ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8"}, + {file = "ruff-0.6.7.tar.gz", hash = "sha256:44e52129d82266fa59b587e2cd74def5637b730a69c4542525dfdecfaae38bd5"}, ] [[package]] @@ -1307,6 +1307,17 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "types-protobuf" +version = "5.28.0.20240924" +description = "Typing stubs for protobuf" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-protobuf-5.28.0.20240924.tar.gz", hash = "sha256:d181af8a256e5a91ce8d5adb53496e880efd9144c7d54483e3653332b60296f0"}, + {file = "types_protobuf-5.28.0.20240924-py3-none-any.whl", hash = "sha256:5cecf612ccdefb7dc95f7a51fb502902f20fc2e6681cd500184aaa1b3931d6a7"}, +] + [[package]] name = "typing-extensions" version = "3.10.0.2" @@ -1336,13 +1347,13 @@ typing-extensions = ">=3.7.4" [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -1463,4 +1474,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.10" -content-hash = "9db75465afe75cbcef197a6aed301dd091eb6d71d5fe460103ff74e8fa35a8e4" +content-hash = "617a81c3d550e69a35f583bb892200431fd63925ca4728c886ec63f07be8625f" diff --git a/pyproject.toml b/pyproject.toml index 858b15f1..7af8e03f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,8 @@ mypy = "1.3.0" pytest = "8.3.3" pytest-runner = "6.0.1" pytest-subtests = "0.13.1" -ruff = "0.6.5" +ruff = "0.6.7" +types-protobuf = "5.28.0.20240924" [build-system] requires = ["poetry-core"] diff --git a/test/python/inspectors/test_filter_duplicate_issues.py b/test/python/inspectors/test_filter_duplicate_issues.py index e815dfca..0b7d351c 100644 --- a/test/python/inspectors/test_filter_duplicate_issues.py +++ b/test/python/inspectors/test_filter_duplicate_issues.py @@ -3,12 +3,17 @@ from pathlib import Path from hyperstyle.src.python.review.inspectors.common.inspector.inspector_type import InspectorType -from hyperstyle.src.python.review.inspectors.common.issue.issue import CodeIssue, IssueDifficulty, IssueType +from hyperstyle.src.python.review.inspectors.common.issue.issue import ( + BaseIssue, + CodeIssue, + IssueDifficulty, + IssueType, +) from hyperstyle.src.python.review.reviewers.utils.issues_filter import filter_duplicate_issues def test_filter_duplicate_issues_when_single_inspector() -> None: - issues = [ + issues: list[BaseIssue] = [ CodeIssue( file_path=Path("code.py"), line_no=10, @@ -57,7 +62,7 @@ def test_filter_duplicate_issues_when_single_inspector() -> None: def test_filter_duplicate_issues_when_several_inspectors() -> None: - issues = [ + issues: list[BaseIssue] = [ CodeIssue( file_path=Path("code.py"), line_no=10, @@ -106,7 +111,7 @@ def test_filter_duplicate_issues_when_several_inspectors() -> None: def test_filter_duplicate_issues_when_several_issues_in_line_no() -> None: - issues = [ + issues: list[BaseIssue] = [ CodeIssue( file_path=Path("code.py"), line_no=10, diff --git a/test/python/inspectors/test_issue_configs.py b/test/python/inspectors/test_issue_configs.py index 7e6137d9..1069fb61 100644 --- a/test/python/inspectors/test_issue_configs.py +++ b/test/python/inspectors/test_issue_configs.py @@ -131,7 +131,7 @@ def test_init_raises_exception( expected_error_message: str, ) -> None: with pytest.raises(expected_exception) as excinfo: - cls(*args) + cls(*args) # type: ignore[arg-type] assert str(excinfo.value) == expected_error_message