diff --git a/README.md b/README.md index 4833b3a6..c2bfc4a2 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Argument | Description **‑e**, **‑‑end-line** | the end line to be analyzed. The default value is `None`, which meant to handle file by the end. **‑‑new-format** | the argument determines whether the tool should use the _new format_. _New format_ means separating the result by the files to allow getting quality and observed issues for each file separately. The default value is `False`. **‑‑history** | JSON string with a list of issues for each language. For each issue its class and quantity are specified. Example: `--history "{\"python\": [{\"origin_class\": \"SC200\", \"number\": 20}, {\"origin_class\": \"WPS314\", \"number\": 3}]}"` +**‑‑with‑all‑categories** | Without this flag, all issues will be categorized into 5 main categories: `CODE_STYLE`, `BEST_PRACTICES`, `ERROR_PRONE`, `COMPLEXITY`, `INFO`. The output examples: diff --git a/src/python/common/tool_arguments.py b/src/python/common/tool_arguments.py index 84439f6a..5911933a 100644 --- a/src/python/common/tool_arguments.py +++ b/src/python/common/tool_arguments.py @@ -78,6 +78,10 @@ class RunToolArgument(Enum): 'Json string, which contains lists of issues in the previous submissions ' 'for other tasks for one user.') + WITH_ALL_CATEGORIES = ArgumentsInfo(None, '--with-all-categories', + 'Without this flag, all issues will be categorized into 5 main categories: ' + 'CODE_STYLE, BEST_PRACTICES, ERROR_PRONE, COMPLEXITY, INFO.') + SOLUTIONS_FILE_PATH = ArgumentsInfo(None, 'solutions_file_path', 'Local XLSX-file or CSV-file path. ' 'Your file must include column-names: ' diff --git a/src/python/review/application_config.py b/src/python/review/application_config.py index 995b0bc0..eb6109a1 100644 --- a/src/python/review/application_config.py +++ b/src/python/review/application_config.py @@ -12,6 +12,7 @@ class ApplicationConfig: allow_duplicates: bool n_cpu: int inspectors_config: dict + with_all_categories: bool start_line: int = 1 end_line: Optional[int] = None new_format: bool = False diff --git a/src/python/review/inspectors/issue.py b/src/python/review/inspectors/issue.py index 3122378d..3ff25255 100644 --- a/src/python/review/inspectors/issue.py +++ b/src/python/review/inspectors/issue.py @@ -10,30 +10,47 @@ @unique class IssueType(Enum): + # Code style issues CODE_STYLE = 'CODE_STYLE' + LINE_LEN = 'LINE_LEN' + + # Best practice issues BEST_PRACTICES = 'BEST_PRACTICES' - ERROR_PRONE = 'ERROR_PRONE' FUNC_LEN = 'FUNC_LEN' - LINE_LEN = 'LINE_LEN' - CYCLOMATIC_COMPLEXITY = 'CYCLOMATIC_COMPLEXITY' BOOL_EXPR_LEN = 'BOOL_EXPR_LEN' + CLASS_RESPONSE = 'CLASS_RESPONSE' + METHOD_NUMBER = 'METHOD_NUMBER' + + # Error-prone issues + ERROR_PRONE = 'ERROR_PRONE' + + # Code complexity issues COMPLEXITY = 'COMPLEXITY' - ARCHITECTURE = 'ARCHITECTURE' + CYCLOMATIC_COMPLEXITY = 'CYCLOMATIC_COMPLEXITY' INHERITANCE_DEPTH = 'INHERITANCE_DEPTH' CHILDREN_NUMBER = 'CHILDREN_NUMBER' WEIGHTED_METHOD = 'WEIGHTED_METHOD' COUPLING = 'COUPLING' COHESION = 'COHESION' - CLASS_RESPONSE = 'CLASS_RESPONSE' - METHOD_NUMBER = 'METHOD_NUMBER' MAINTAINABILITY = 'MAINTAINABILITY' + + # Info issues INFO = 'INFO' + # Others UNDEFINED = 'UNDEFINED' + ARCHITECTURE = 'ARCHITECTURE' # TODO: Distribute into one of the main types def __str__(self) -> str: return ' '.join(self.value.lower().split('_')) + def to_main_type(self) -> 'IssueType': + """ + Converts the issue type to main issue type. + Main issue types: CODE_STYLE, BEST_PRACTICES, ERROR_PRONE, COMPLEXITY, INFO. + """ + return get_main_category_by_issue_type(self) + ISSUE_TYPE_TO_MAIN_CATEGORY = { # CODE_STYLE @@ -60,6 +77,9 @@ def __str__(self) -> str: IssueType.CHILDREN_NUMBER: IssueType.COMPLEXITY, IssueType.INHERITANCE_DEPTH: IssueType.COMPLEXITY, IssueType.ARCHITECTURE: IssueType.COMPLEXITY, + + # INFO + IssueType.INFO: IssueType.INFO, } diff --git a/src/python/review/quality/penalty.py b/src/python/review/quality/penalty.py index b1334682..ff4a6e13 100644 --- a/src/python/review/quality/penalty.py +++ b/src/python/review/quality/penalty.py @@ -25,6 +25,8 @@ IssueType.MAINTAINABILITY: 0.3, IssueType.METHOD_NUMBER: 0.2, IssueType.WEIGHTED_METHOD: 0.2, + IssueType.UNDEFINED: 0, + IssueType.INFO: 0, } diff --git a/src/python/review/reviewers/perform_review.py b/src/python/review/reviewers/perform_review.py index 9f495751..fb44c9a0 100644 --- a/src/python/review/reviewers/perform_review.py +++ b/src/python/review/reviewers/perform_review.py @@ -61,11 +61,11 @@ def perform_and_print_review(path: Path, if OutputFormat.JSON == output_format: if config.new_format: - print_review_result_as_multi_file_json(review_result) + print_review_result_as_multi_file_json(review_result, config) else: - print_review_result_as_json(review_result) + print_review_result_as_json(review_result, config) else: - print_review_result_as_text(review_result, path) + print_review_result_as_text(review_result, path, config) # Don't count INFO issues too return len(list(filter(lambda issue: issue.type != IssueType.INFO, review_result.all_issues))) diff --git a/src/python/review/reviewers/utils/print_review.py b/src/python/review/reviewers/utils/print_review.py index 617eb548..66a5f2a3 100644 --- a/src/python/review/reviewers/utils/print_review.py +++ b/src/python/review/reviewers/utils/print_review.py @@ -5,6 +5,7 @@ from typing import Any, Dict, List from src.python.evaluation.inspectors.common.statistics import PenaltyIssue +from src.python.review.application_config import ApplicationConfig from src.python.review.common.file_system import get_file_line from src.python.review.inspectors.inspector_type import InspectorType from src.python.review.inspectors.issue import BaseIssue, IssueType @@ -12,7 +13,8 @@ def print_review_result_as_text(review_result: ReviewResult, - path: Path) -> None: + path: Path, + config: ApplicationConfig) -> None: heading = f'\nReview of {str(path)} ({len(review_result.all_issues)} violations)' print(heading) @@ -32,9 +34,13 @@ def print_review_result_as_text(review_result: ReviewResult, issue.line_no, ).strip() + issue_type = issue.type + if not config.with_all_categories: + issue_type = issue_type.to_main_type() + print(f'{issue.line_no} : ' f'{issue.column_no} : ' - f'{issue.type.value} : ' + f'{issue_type.value} : ' f'{issue.inspector_type.value} : ' f'{issue.origin_class} : ' f'{issue.description} : ' @@ -48,7 +54,7 @@ def print_review_result_as_text(review_result: ReviewResult, print(review_result.general_quality, end='') -def print_review_result_as_json(review_result: ReviewResult) -> None: +def print_review_result_as_json(review_result: ReviewResult, config: ApplicationConfig) -> None: issues = review_result.all_issues issues.sort(key=lambda issue: issue.line_no) @@ -65,12 +71,12 @@ def print_review_result_as_json(review_result: ReviewResult) -> None: if quality_with_penalty != quality_without_penalty: influence_on_penalty = review_result.general_punisher.get_issue_influence_on_penalty(issue.origin_class) - output_json['issues'].append(convert_issue_to_json(issue, influence_on_penalty)) + output_json['issues'].append(convert_issue_to_json(issue, config, influence_on_penalty)) print(json.dumps(output_json)) -def print_review_result_as_multi_file_json(review_result: ReviewResult) -> None: +def print_review_result_as_multi_file_json(review_result: ReviewResult, config: ApplicationConfig) -> None: file_review_result_jsons = [] review_result.file_review_results.sort(key=lambda result: result.file_path) @@ -94,7 +100,7 @@ def print_review_result_as_multi_file_json(review_result: ReviewResult) -> None: if quality_with_penalty != quality_without_penalty: influence_on_penalty = file_review_result.punisher.get_issue_influence_on_penalty(issue.origin_class) - file_review_result_json['issues'].append(convert_issue_to_json(issue, influence_on_penalty)) + file_review_result_json['issues'].append(convert_issue_to_json(issue, config, influence_on_penalty)) quality_without_penalty = review_result.general_quality.quality_type quality_with_penalty = review_result.general_punisher.get_quality_with_penalty(quality_without_penalty) @@ -121,16 +127,20 @@ class IssueJsonFields(Enum): INFLUENCE_ON_PENALTY = 'influence_on_penalty' -def convert_issue_to_json(issue: BaseIssue, influence_on_penalty: int = 0) -> Dict[str, Any]: +def convert_issue_to_json(issue: BaseIssue, config: ApplicationConfig, influence_on_penalty: int = 0) -> Dict[str, Any]: line_text = get_file_line(issue.file_path, issue.line_no) + issue_type = issue.type + if not config.with_all_categories: + issue_type = issue_type.to_main_type() + return { IssueJsonFields.CODE.value: issue.origin_class, IssueJsonFields.TEXT.value: issue.description, IssueJsonFields.LINE.value: line_text, IssueJsonFields.LINE_NUMBER.value: issue.line_no, IssueJsonFields.COLUMN_NUMBER.value: issue.column_no, - IssueJsonFields.CATEGORY.value: issue.type.value, + IssueJsonFields.CATEGORY.value: issue_type.value, IssueJsonFields.INFLUENCE_ON_PENALTY.value: influence_on_penalty, } diff --git a/src/python/review/run_tool.py b/src/python/review/run_tool.py index caddaed7..0230b05d 100644 --- a/src/python/review/run_tool.py +++ b/src/python/review/run_tool.py @@ -106,6 +106,10 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: help=RunToolArgument.HISTORY.value.description, type=str) + parser.add_argument(RunToolArgument.WITH_ALL_CATEGORIES.value.long_name, + help=RunToolArgument.WITH_ALL_CATEGORIES.value.description, + action='store_true') + def configure_logging(verbosity: VerbosityLevel) -> None: if verbosity is VerbosityLevel.ERROR: @@ -150,6 +154,7 @@ def main() -> int: end_line=args.end_line, new_format=args.new_format, history=args.history, + with_all_categories=args.with_all_categories, ) n_issues = perform_and_print_review(args.path, OutputFormat(args.format), config) diff --git a/test/python/inspectors/test_local_review.py b/test/python/inspectors/test_local_review.py index 5b182540..e7bf3b8b 100644 --- a/test/python/inspectors/test_local_review.py +++ b/test/python/inspectors/test_local_review.py @@ -24,6 +24,7 @@ def config() -> ApplicationConfig: allow_duplicates=False, n_cpu=1, inspectors_config={"n_cpu": 1}, + with_all_categories=False, )