From 5f6406e1cc21a57adb32b41923ea38ac45f9927c Mon Sep 17 00:00:00 2001 From: Nastya Birillo Date: Fri, 23 Jul 2021 01:47:39 +0300 Subject: [PATCH 01/10] Add whitelist for flake8-spellcheck (#68) * Add whitelist for flake8-spellcheck * Add a test for spellcheck and ignore unnecessary rule * Fix flake8 config --- src/python/review/inspectors/flake8/flake8.py | 5 + .../review/inspectors/flake8/whitelist.txt | 127 ++++++++++++++++++ .../inspectors/test_flake8_inspector.py | 7 +- .../inspectors/python/case31_spellcheck.py | 3 + 4 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 src/python/review/inspectors/flake8/whitelist.txt create mode 100644 test/resources/inspectors/python/case31_spellcheck.py diff --git a/src/python/review/inspectors/flake8/flake8.py b/src/python/review/inspectors/flake8/flake8.py index 6f071338..6ea27f09 100644 --- a/src/python/review/inspectors/flake8/flake8.py +++ b/src/python/review/inspectors/flake8/flake8.py @@ -13,6 +13,10 @@ logger = logging.getLogger(__name__) PATH_FLAKE8_CONFIG = Path(__file__).parent / '.flake8' +# To make the whitelist, a list of words was examined based on students' solutions +# that were flagged by flake8-spellcheck as erroneous. In general, the whitelist included those words +# that belonged to library methods and which were common abbreviations. +PATH_FLAKE8_SPELLCHECK_WHITELIST = Path(__file__).parent / 'whitelist.txt' FORMAT = '%(path)s:%(row)d:%(col)d:%(code)s:%(text)s' INSPECTOR_NAME = 'flake8' @@ -26,6 +30,7 @@ def inspect(cls, path: Path, config: dict) -> List[BaseIssue]: 'flake8', f'--format={FORMAT}', f'--config={PATH_FLAKE8_CONFIG}', + f'--whitelist={PATH_FLAKE8_SPELLCHECK_WHITELIST}', '--max-complexity', '0', path ] diff --git a/src/python/review/inspectors/flake8/whitelist.txt b/src/python/review/inspectors/flake8/whitelist.txt new file mode 100644 index 00000000..8ab08f11 --- /dev/null +++ b/src/python/review/inspectors/flake8/whitelist.txt @@ -0,0 +1,127 @@ +aggfunc +appendleft +argmax +asctime +astype +betavariate +birthdate +blackbox +bs4 +byteorder +calc +capwords +casefold +caseless +concat +consts +coord +copysign +csgraph +ctime +dataframe +dataframes +dataset +datasets +decrypted +dedent +deque +desc +devs +df +dicts +dirs +divmod +dtype +edu +eig +elems +etree +expm1 +falsy +fillna +floordiv +fromstring +fullmatch +gensim +gmtime +groupby +halfs +hashable +href +hyp +hyperskill +iadd +iloc +inplace +ints +isalnum +isalpha +isin +islice +islower +isnumeric +isprintable +istitle +isub +iterrows +kcal +kcals +lastname +lemmatize +lemmatizer +lifes +lim +linalg +linspace +lowercased +lvl +lxml +matmul +multiline +ndarray +ndigits +ndim +nltk +nrows +numpy +nums +ost +param +params +parsers +pathlib +popleft +pos +punct +readline +rfind +rindex +rmdir +schur +scipy +sigmoid +sqrt +src +stemmer +stepik +subdicts +subdir +subdirs +substr +substring +textwrap +todos +tokenize +tokenized +tokenizer +tolist +tracklist +truediv +truthy +unpickled +upd +util +utils +webpage +whitespaces +writeback \ No newline at end of file diff --git a/test/python/inspectors/test_flake8_inspector.py b/test/python/inspectors/test_flake8_inspector.py index 56e02aec..3c655331 100644 --- a/test/python/inspectors/test_flake8_inspector.py +++ b/test/python/inspectors/test_flake8_inspector.py @@ -14,7 +14,7 @@ ('case3_redefining_builtin.py', 1), ('case4_naming.py', 10), ('case5_returns.py', 1), - # ('case6_unused_variables.py', 3), + ('case6_unused_variables.py', 3), ('case8_good_class.py', 0), ('case7_empty_lines.py', 0), ('case10_unused_variable_in_loop.py', 1), @@ -28,6 +28,7 @@ ('case19_bad_indentation.py', 3), ('case21_imports.py', 2), ('case25_django.py', 0), + ('case31_spellcheck.py', 0), ] @@ -50,8 +51,8 @@ def test_file_with_issues(file_name: str, n_issues: int): n_cc=8)), ('case3_redefining_builtin.py', IssuesTestInfo(n_error_prone=1)), ('case4_naming.py', IssuesTestInfo(n_code_style=7, n_best_practices=0, n_cc=5)), - # ('case6_unused_variables.py', IssuesTestInfo(n_best_practices=3, - # n_cc=1)), + ('case6_unused_variables.py', IssuesTestInfo(n_best_practices=3, + n_cc=1)), ('case8_good_class.py', IssuesTestInfo(n_cc=1)), ('case7_empty_lines.py', IssuesTestInfo(n_cc=4)), ('case10_unused_variable_in_loop.py', IssuesTestInfo(n_best_practices=1, diff --git a/test/resources/inspectors/python/case31_spellcheck.py b/test/resources/inspectors/python/case31_spellcheck.py new file mode 100644 index 00000000..b777eb79 --- /dev/null +++ b/test/resources/inspectors/python/case31_spellcheck.py @@ -0,0 +1,3 @@ +import math + +number = math.sqrt(float(input())) From dfcb8df015fd8daf73fbb5a99719ba7302bbbddd Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 15:23:57 +0300 Subject: [PATCH 02/10] Delete evaluation part, fix PMD bug, fix flake8 and PMD --- src/python/evaluation/README.md | 31 ---- src/python/evaluation/__init__.py | 0 src/python/evaluation/common/__init__.py | 0 src/python/evaluation/common/util.py | 34 ---- src/python/evaluation/common/xlsx_util.py | 42 ----- src/python/evaluation/evaluation_config.py | 43 ----- src/python/evaluation/xlsx_run_tool.py | 169 ------------------ src/python/review/inspectors/common.py | 11 ++ src/python/review/inspectors/flake8/.flake8 | 3 +- src/python/review/inspectors/flake8/flake8.py | 15 +- .../review/inspectors/flake8/issue_types.py | 2 + .../review/inspectors/flake8/whitelist.txt | 127 +++++++++++++ .../review/inspectors/pmd/issue_types.py | 2 +- src/python/review/inspectors/pmd/pmd.py | 42 ++++- test/python/evaluation/__init__.py | 11 -- test/python/evaluation/test_data_path.py | 14 -- test/python/evaluation/test_output_results.py | 32 ---- test/python/evaluation/test_tool_path.py | 26 --- .../evaluation/test_xlsx_file_structure.py | 23 --- test/python/evaluation/testing_config.py | 18 -- .../xlsx_files/test_empty_lang_cell.xlsx | Bin 5162 -> 0 bytes .../xlsx_files/test_empty_table.xlsx | Bin 4589 -> 0 bytes .../xlsx_files/test_java_no_version.xlsx | Bin 5156 -> 0 bytes .../xlsx_files/test_sorted_order.xlsx | Bin 7317 -> 0 bytes .../xlsx_files/test_unsorted_order.xlsx | Bin 6995 -> 0 bytes .../xlsx_files/test_wrong_column_name.xlsx | Bin 5175 -> 0 bytes .../target_sorted_order.xlsx | Bin 38024 -> 0 bytes .../target_unsorted_order.xlsx | Bin 30970 -> 0 bytes 28 files changed, 193 insertions(+), 452 deletions(-) delete mode 100644 src/python/evaluation/README.md delete mode 100644 src/python/evaluation/__init__.py delete mode 100644 src/python/evaluation/common/__init__.py delete mode 100644 src/python/evaluation/common/util.py delete mode 100644 src/python/evaluation/common/xlsx_util.py delete mode 100644 src/python/evaluation/evaluation_config.py delete mode 100644 src/python/evaluation/xlsx_run_tool.py create mode 100644 src/python/review/inspectors/flake8/whitelist.txt delete mode 100644 test/python/evaluation/__init__.py delete mode 100644 test/python/evaluation/test_data_path.py delete mode 100644 test/python/evaluation/test_output_results.py delete mode 100644 test/python/evaluation/test_tool_path.py delete mode 100644 test/python/evaluation/test_xlsx_file_structure.py delete mode 100644 test/python/evaluation/testing_config.py delete mode 100644 test/resources/evaluation/xlsx_files/test_empty_lang_cell.xlsx delete mode 100644 test/resources/evaluation/xlsx_files/test_empty_table.xlsx delete mode 100644 test/resources/evaluation/xlsx_files/test_java_no_version.xlsx delete mode 100644 test/resources/evaluation/xlsx_files/test_sorted_order.xlsx delete mode 100644 test/resources/evaluation/xlsx_files/test_unsorted_order.xlsx delete mode 100644 test/resources/evaluation/xlsx_files/test_wrong_column_name.xlsx delete mode 100644 test/resources/evaluation/xlsx_target_files/target_sorted_order.xlsx delete mode 100644 test/resources/evaluation/xlsx_target_files/target_unsorted_order.xlsx diff --git a/src/python/evaluation/README.md b/src/python/evaluation/README.md deleted file mode 100644 index 67e1d45e..00000000 --- a/src/python/evaluation/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Hyperstyle evaluation - -This tool allows running the `Hyperstyle` tool on an xlsx table to get code quality for all code fragments. Please, note that your input file should consist of at least 2 obligatory columns to run xlsx-tool on its code fragments: - -- `code` -- `lang` - -Possible values for column `lang` are: `python3`, `kotlin`, `java8`, `java11`. - -Output file is a new `xlsx` file with 3 columns: -- `code` -- `lang` -- `grade` -Grade assessment is conducted by [`run_tool.py`](https://github.com/hyperskill/hyperstyle/blob/main/README.md) with default arguments. Avaliable values for column `grade` are: BAD, MODERATE, GOOD, EXCELLENT. It is also possible add fourth column: `traceback` to get full inspectors feedback on each code fragment. More details on enabling traceback column in **Optional Arguments** table. - -## Usage - -Run the [xlsx_run_tool.py](xlsx_run_tool.py) with the arguments from command line. - -Required arguments: - -`xlsx_file_path` — path to xlsx-file with code samples to inspect. - -Optional arguments: -Argument | Description ---- | --- -|**‑f**, **‑‑format**| The output format. Available values: `json`, `text`. The default value is `json` . Use this argument when `traceback` is enabled, otherwise it will not be used.| -|**‑tp**, **‑‑tool_path**| Path to run-tool. Default is `src/python/review/run_tool.py` .| -|**‑tr**, **‑‑traceback**| To include a column with errors traceback into an output file. Default is `False`.| -|**‑ofp**, **‑‑output_folder_path**| An explicit folder path to store file with results. Default is a parent directory of a folder with xlsx-file sent for inspection. | -|**‑ofn**, **‑‑output_file_name**| A name of an output file where evaluation results will be stored. Default is `results.xlsx`.| diff --git a/src/python/evaluation/__init__.py b/src/python/evaluation/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/python/evaluation/common/__init__.py b/src/python/evaluation/common/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/python/evaluation/common/util.py b/src/python/evaluation/common/util.py deleted file mode 100644 index c20b16d8..00000000 --- a/src/python/evaluation/common/util.py +++ /dev/null @@ -1,34 +0,0 @@ -from enum import Enum, unique - -from src.python.review.application_config import LanguageVersion -from src.python.review.common.file_system import Extension - - -@unique -class ColumnName(Enum): - CODE = 'code' - LANG = 'lang' - LANGUAGE = 'language' - GRADE = 'grade' - - -@unique -class EvaluationArgument(Enum): - TRACEBACK = 'traceback' - RESULT_FILE_NAME = 'results' - RESULT_FILE_NAME_EXT = f'{RESULT_FILE_NAME}{Extension.XLSX.value}' - - -script_structure_rule = ('Please, make sure your XLSX-file matches following script standards: \n' - '1. Your XLSX-file should have 2 obligatory columns named:' - f'"{ColumnName.CODE.value}" & "{ColumnName.LANG.value}". \n' - f'"{ColumnName.CODE.value}" column -- relates to the code-sample. \n' - f'"{ColumnName.LANG.value}" column -- relates to the language of a ' - 'particular code-sample. \n' - '2. Your code samples should belong to the one of the supported languages. \n' - 'Supported languages are: Java, Kotlin, Python. \n' - f'3. Check that "{ColumnName.LANG.value}" column cells are filled with ' - 'acceptable language-names: \n' - f'Acceptable language-names are: {LanguageVersion.PYTHON_3.value}, ' - f'{LanguageVersion.JAVA_8.value} ,' - f'{LanguageVersion.JAVA_11.value} and {LanguageVersion.KOTLIN.value}.') diff --git a/src/python/evaluation/common/xlsx_util.py b/src/python/evaluation/common/xlsx_util.py deleted file mode 100644 index 032a5ce6..00000000 --- a/src/python/evaluation/common/xlsx_util.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging.config -from pathlib import Path -from typing import Union - -import pandas as pd -from openpyxl import load_workbook, Workbook -from src.python.evaluation.evaluation_config import EvaluationConfig - -logger = logging.getLogger(__name__) - - -def remove_sheet(workbook_path: Union[str, Path], sheet_name: str, to_raise_error: bool = False) -> None: - try: - workbook = load_workbook(workbook_path) - workbook.remove(workbook[sheet_name]) - workbook.save(workbook_path) - - except KeyError as e: - message = f'Sheet with specified name: {sheet_name} does not exist.' - if to_raise_error: - logger.exception(message) - raise e - else: - logger.info(message) - - -def create_and_get_workbook_path(config: EvaluationConfig) -> Path: - workbook = Workbook() - workbook_path = config.get_output_file_path() - workbook.save(workbook_path) - return workbook_path - - -def write_dataframe_to_xlsx_sheet(xlsx_file_path: Union[str, Path], df: pd.DataFrame, sheet_name: str, - mode: str = 'a', to_write_row_names: bool = False) -> None: - """ - mode: str Available values are {'w', 'a'}. File mode to use (write or append). - to_write_row_names: bool Write row names. - """ - - with pd.ExcelWriter(xlsx_file_path, mode=mode) as writer: - df.to_excel(writer, sheet_name=sheet_name, index=to_write_row_names) diff --git a/src/python/evaluation/evaluation_config.py b/src/python/evaluation/evaluation_config.py deleted file mode 100644 index 3a856b78..00000000 --- a/src/python/evaluation/evaluation_config.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging.config -import os -from argparse import Namespace -from pathlib import Path -from typing import List, Union - -from src.python.common.tool_arguments import RunToolArgument -from src.python.evaluation.common.util import EvaluationArgument -from src.python.review.application_config import LanguageVersion - -logger = logging.getLogger(__name__) - - -class EvaluationConfig: - def __init__(self, args: Namespace): - self.tool_path: Union[str, Path] = args.tool_path - self.format: str = args.format - self.xlsx_file_path: Union[str, Path] = args.xlsx_file_path - self.traceback: bool = args.traceback - self.output_folder_path: Union[str, Path] = args.output_folder_path - self.output_file_name: str = args.output_file_name - - def build_command(self, inspected_file_path: Union[str, Path], lang: str) -> List[str]: - command = [LanguageVersion.PYTHON_3.value, - self.tool_path, - inspected_file_path, - RunToolArgument.FORMAT.value.short_name, self.format] - - if lang == LanguageVersion.JAVA_8.value or lang == LanguageVersion.JAVA_11.value: - command.extend([RunToolArgument.LANG_VERSION.value.long_name, lang]) - return command - - def get_output_file_path(self) -> Path: - if self.output_folder_path is None: - try: - self.output_folder_path = ( - Path(self.xlsx_file_path).parent.parent / EvaluationArgument.RESULT_FILE_NAME.value - ) - os.makedirs(self.output_folder_path, exist_ok=True) - except FileNotFoundError as e: - logger.error('XLSX-file with the specified name does not exists.') - raise e - return Path(self.output_folder_path) / self.output_file_name diff --git a/src/python/evaluation/xlsx_run_tool.py b/src/python/evaluation/xlsx_run_tool.py deleted file mode 100644 index 7235b041..00000000 --- a/src/python/evaluation/xlsx_run_tool.py +++ /dev/null @@ -1,169 +0,0 @@ -import argparse -import logging.config -import os -import re -import sys -import traceback -from pathlib import Path -from typing import Type - -sys.path.append('') -sys.path.append('../../..') - -import pandas as pd -from src.python.common.tool_arguments import RunToolArgument -from src.python.evaluation.common.util import ColumnName, EvaluationArgument, script_structure_rule -from src.python.evaluation.common.xlsx_util import ( - create_and_get_workbook_path, - remove_sheet, - write_dataframe_to_xlsx_sheet, -) -from src.python.evaluation.evaluation_config import EvaluationConfig -from src.python.review.application_config import LanguageVersion -from src.python.review.common.file_system import create_file, new_temp_dir -from src.python.review.common.subprocess_runner import run_in_subprocess -from src.python.review.reviewers.perform_review import OutputFormat - -logger = logging.getLogger(__name__) - - -def configure_arguments(parser: argparse.ArgumentParser, run_tool_arguments: Type[RunToolArgument]) -> None: - parser.add_argument('xlsx_file_path', - type=lambda value: Path(value).absolute(), - help='Local XLSX-file path. ' - 'Your XLSX-file must include column-names: ' - f'"{ColumnName.CODE.value}" and ' - f'"{ColumnName.LANG.value}". Acceptable values for ' - f'"{ColumnName.LANG.value}" column are: ' - f'{LanguageVersion.PYTHON_3.value}, {LanguageVersion.JAVA_8.value}, ' - f'{LanguageVersion.JAVA_11.value}, {LanguageVersion.KOTLIN.value}.') - - parser.add_argument('-tp', '--tool-path', - default=Path('src/python/review/run_tool.py').absolute(), - type=lambda value: Path(value).absolute(), - help='Path to script to run on files.') - - parser.add_argument('-tr', '--traceback', - help='If True, column with the full inspector feedback will be added ' - 'to the output file with results.', - action='store_true') - - parser.add_argument('-ofp', '--output-folder-path', - help='An absolute path to the folder where file with evaluation results' - 'will be stored.' - 'Default is the path to a directory, where is the folder with xlsx_file.', - # if None default path will be specified based on xlsx_file_path. - default=None, - type=str) - - parser.add_argument('-ofn', '--output-file-name', - help='Filename for that will be created to store inspection results.' - f'Default is "{EvaluationArgument.RESULT_FILE_NAME_EXT.value}"', - default=f'{EvaluationArgument.RESULT_FILE_NAME_EXT.value}', - type=str) - - parser.add_argument(run_tool_arguments.FORMAT.value.short_name, - run_tool_arguments.FORMAT.value.long_name, - default=OutputFormat.JSON.value, - choices=OutputFormat.values(), - type=str, - help=f'{run_tool_arguments.FORMAT.value.description}' - f'Use this argument when {EvaluationArgument.TRACEBACK.value} argument' - 'is enabled argument will not be used otherwise.') - - -def create_dataframe(config: EvaluationConfig) -> pd.DataFrame: - report = pd.DataFrame( - { - ColumnName.LANGUAGE.value: [], - ColumnName.CODE.value: [], - ColumnName.GRADE.value: [], - }, - ) - - if config.traceback: - report[EvaluationArgument.TRACEBACK.value] = [] - - try: - lang_code_dataframe = pd.read_excel(config.xlsx_file_path) - - except FileNotFoundError as e: - logger.error('XLSX-file with the specified name does not exists.') - raise e - - try: - for lang, code in zip(lang_code_dataframe[ColumnName.LANG.value], - lang_code_dataframe[ColumnName.CODE.value]): - - with new_temp_dir() as create_temp_dir: - temp_dir_path = create_temp_dir - lang_extension = LanguageVersion.language_by_extension(lang) - temp_file_path = os.path.join(temp_dir_path, ('file' + lang_extension)) - temp_file_path = next(create_file(temp_file_path, code)) - - try: - assert os.path.exists(temp_file_path) - except AssertionError as e: - logger.exception('Path does not exist.') - raise e - - command = config.build_command(temp_file_path, lang) - results = run_in_subprocess(command) - os.remove(temp_file_path) - temp_dir_path.rmdir() - # this regular expression matches final tool grade: EXCELLENT, GOOD, MODERATE or BAD - grades = re.match(r'^.*{"code":\s"([A-Z]+)"', results).group(1) - output_row_values = [lang, code, grades] - column_indices = [ColumnName.LANGUAGE.value, - ColumnName.CODE.value, - ColumnName.GRADE.value] - - if config.traceback: - output_row_values.append(results) - column_indices.append(EvaluationArgument.TRACEBACK.value) - - new_file_report_row = pd.Series(data=output_row_values, index=column_indices) - report = report.append(new_file_report_row, ignore_index=True) - - return report - - except KeyError as e: - logger.error(script_structure_rule) - raise e - - except Exception as e: - traceback.print_exc() - logger.exception('An unexpected error.') - raise e - - -def main() -> int: - parser = argparse.ArgumentParser() - configure_arguments(parser, RunToolArgument) - - try: - args = parser.parse_args() - config = EvaluationConfig(args) - workbook_path = create_and_get_workbook_path(config) - results = create_dataframe(config) - write_dataframe_to_xlsx_sheet(workbook_path, results, 'inspection_results') - # remove empty sheet that was initially created with the workbook - remove_sheet(workbook_path, 'Sheet') - return 0 - - except FileNotFoundError: - logger.error('XLSX-file with the specified name does not exists.') - return 2 - - except KeyError: - logger.error(script_structure_rule) - return 2 - - except Exception: - traceback.print_exc() - logger.exception('An unexpected error.') - return 2 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/python/review/inspectors/common.py b/src/python/review/inspectors/common.py index dfedbb1e..a307bd96 100644 --- a/src/python/review/inspectors/common.py +++ b/src/python/review/inspectors/common.py @@ -10,3 +10,14 @@ def convert_percentage_of_value_to_lack_of_value(percentage_of_value: float) -> :return: lack of value. """ return floor(100 - percentage_of_value) + + +# TODO: When upgrading to python 3.9+, replace it with removeprefix. +# See: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +def remove_prefix(text: str, prefix: str) -> str: + """ + Removes the prefix if it is present, otherwise returns the original string. + """ + if text.startswith(prefix): + return text[len(prefix):] + return text diff --git a/src/python/review/inspectors/flake8/.flake8 b/src/python/review/inspectors/flake8/.flake8 index 19edebba..815863c0 100644 --- a/src/python/review/inspectors/flake8/.flake8 +++ b/src/python/review/inspectors/flake8/.flake8 @@ -1,6 +1,8 @@ [flake8] disable_noqa=True +max-line-length=120 + dictionaries=en_US,python,technical,django ignore=W291, # trailing whitespaces @@ -8,7 +10,6 @@ ignore=W291, # trailing whitespaces W293, # blank line contains whitespaces 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 diff --git a/src/python/review/inspectors/flake8/flake8.py b/src/python/review/inspectors/flake8/flake8.py index 5745a75f..7eb75a9d 100644 --- a/src/python/review/inspectors/flake8/flake8.py +++ b/src/python/review/inspectors/flake8/flake8.py @@ -15,12 +15,17 @@ CyclomaticComplexityIssue, IssueData, IssueType, + LineLenIssue, ) -from src.python.review.inspectors.tips import get_cyclomatic_complexity_tip +from src.python.review.inspectors.tips import get_cyclomatic_complexity_tip, get_line_len_tip logger = logging.getLogger(__name__) PATH_FLAKE8_CONFIG = Path(__file__).parent / '.flake8' +# To make the whitelist, a list of words was examined based on students' solutions +# that were flagged by flake8-spellcheck as erroneous. In general, the whitelist included those words +# that belonged to library methods and which were common abbreviations. +PATH_FLAKE8_SPELLCHECK_WHITELIST = Path(__file__).parent / 'whitelist.txt' FORMAT = '%(path)s:%(row)d:%(col)d:%(code)s:%(text)s' INSPECTOR_NAME = 'flake8' @@ -34,6 +39,7 @@ def inspect(cls, path: Path, config: dict) -> List[BaseIssue]: 'flake8', f'--format={FORMAT}', f'--config={PATH_FLAKE8_CONFIG}', + f'--whitelist={PATH_FLAKE8_SPELLCHECK_WHITELIST}', '--max-complexity', '0', '--cohesion-below', '100', path, @@ -46,6 +52,7 @@ def parse(cls, output: str) -> List[BaseIssue]: row_re = re.compile(r'^(.*):(\d+):(\d+):([A-Z]+\d{3}):(.*)$', re.M) cc_description_re = re.compile(r"'(.+)' is too complex \((\d+)\)") cohesion_description_re = re.compile(r"class has low \((\d*\.?\d*)%\) cohesion") + line_len_description_re = re.compile(r"line too long \((\d+) > \d+ characters\)") issues: List[BaseIssue] = [] for groups in row_re.findall(output): @@ -53,6 +60,7 @@ def parse(cls, output: str) -> List[BaseIssue]: origin_class = groups[3] cc_match = cc_description_re.match(description) cohesion_match = cohesion_description_re.match(description) + line_len_match = line_len_description_re.match(description) file_path = Path(groups[0]) line_no = int(groups[1]) @@ -74,6 +82,11 @@ def parse(cls, output: str) -> List[BaseIssue]: ) issue_data[IssueData.ISSUE_TYPE.value] = IssueType.COHESION issues.append(CohesionIssue(**issue_data)) + elif line_len_match is not None: + issue_data[IssueData.DESCRIPTION.value] = get_line_len_tip() + issue_data[IssueData.LINE_LEN.value] = int(line_len_match.groups()[0]) + issue_data[IssueData.ISSUE_TYPE.value] = IssueType.LINE_LEN + issues.append(LineLenIssue(**issue_data)) else: issue_type = cls.choose_issue_type(origin_class) issue_data[IssueData.ISSUE_TYPE.value] = issue_type diff --git a/src/python/review/inspectors/flake8/issue_types.py b/src/python/review/inspectors/flake8/issue_types.py index 440f5f82..b1074b52 100644 --- a/src/python/review/inspectors/flake8/issue_types.py +++ b/src/python/review/inspectors/flake8/issue_types.py @@ -27,6 +27,8 @@ 'C818': IssueType.CODE_STYLE, 'C819': IssueType.CODE_STYLE, + # The categorization for WPS was created using the following document: https://bit.ly/3yms06n + # WPS: Naming 'WPS117': IssueType.CODE_STYLE, # Forbid naming variables self, cls, or mcs. 'WPS125': IssueType.ERROR_PRONE, # Forbid variable or module names which shadow builtin names. diff --git a/src/python/review/inspectors/flake8/whitelist.txt b/src/python/review/inspectors/flake8/whitelist.txt new file mode 100644 index 00000000..47340d90 --- /dev/null +++ b/src/python/review/inspectors/flake8/whitelist.txt @@ -0,0 +1,127 @@ +aggfunc +appendleft +argmax +asctime +astype +betavariate +birthdate +blackbox +bs4 +byteorder +calc +capwords +casefold +caseless +concat +consts +coord +copysign +csgraph +ctime +dataframe +dataframes +dataset +datasets +decrypted +dedent +deque +desc +devs +df +dicts +dirs +divmod +dtype +edu +eig +elems +etree +expm1 +falsy +fillna +floordiv +fromstring +fullmatch +gensim +gmtime +groupby +halfs +hashable +href +hyp +hyperskill +iadd +iloc +inplace +ints +isalnum +isalpha +isin +islice +islower +isnumeric +isprintable +istitle +isub +iterrows +kcal +kcals +lastname +lemmatize +lemmatizer +lifes +lim +linalg +linspace +lowercased +lvl +lxml +matmul +multiline +ndarray +ndigits +ndim +nltk +nrows +numpy +nums +ost +param +params +parsers +pathlib +popleft +pos +punct +readline +rfind +rindex +rmdir +schur +scipy +sigmoid +sqrt +src +stemmer +stepik +subdicts +subdir +subdirs +substr +substring +textwrap +todos +tokenize +tokenized +tokenizer +tolist +tracklist +truediv +truthy +unpickled +upd +util +utils +webpage +whitespaces +writeback 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..eb4878dd 100644 --- a/src/python/review/inspectors/pmd/pmd.py +++ b/src/python/review/inspectors/pmd/pmd.py @@ -8,15 +8,17 @@ from src.python.review.common.file_system import new_temp_dir from src.python.review.common.subprocess_runner import run_in_subprocess from src.python.review.inspectors.base_inspector import BaseInspector +from src.python.review.inspectors.common import remove_prefix 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__) PATH_TOOLS_PMD_FILES = Path(__file__).parent / 'files' PATH_TOOLS_PMD_SHELL_SCRIPT = PATH_TOOLS_PMD_FILES / 'bin' / 'run.sh' PATH_TOOLS_PMD_RULES_SET = PATH_TOOLS_PMD_FILES / 'bin' / 'basic.xml' +DEFAULT_JAVA_VERSION = LanguageVersion.JAVA_11 class PMDInspector(BaseInspector): @@ -28,14 +30,14 @@ def __init__(self): @classmethod def _create_command(cls, path: Path, output_path: Path, - java_version: LanguageVersion, + language_version: LanguageVersion, n_cpu: int) -> List[str]: return [ PATH_TOOLS_PMD_SHELL_SCRIPT, 'pmd', '-d', str(path), '-no-cache', '-R', PATH_TOOLS_PMD_RULES_SET, '-language', 'java', - '-version', java_version.value, + '-version', cls._get_java_version(language_version), '-f', 'csv', '-r', str(output_path), '-t', str(n_cpu), ] @@ -46,13 +48,21 @@ def inspect(self, path: Path, config: dict) -> List[BaseIssue]: language_version = config.get('language_version') if language_version is None: - language_version = LanguageVersion.JAVA_11 + logger.info( + f"The version of Java is not passed. The version to be used is: {DEFAULT_JAVA_VERSION.value}.", + ) + language_version = DEFAULT_JAVA_VERSION command = self._create_command(path, output_path, language_version, config['n_cpu']) run_in_subprocess(command) return self.parse_output(output_path) def parse_output(self, output_path: Path) -> List[BaseIssue]: + """ + Parses the PMD output, which is a csv file, and returns a list of the issues found there. + + If the passed path is not a file, an empty list is returned. + """ if not output_path.is_file(): logger.error('%s: error - no output file' % self.inspector_type.value) return [] @@ -65,17 +75,37 @@ def parse_output(self, output_path: Path) -> List[BaseIssue]: line_no=int(row['Line']), column_no=1, type=self.choose_issue_type(row['Rule']), - origin_class=row['Rule set'], + origin_class=row['Rule'], description=row['Description'], inspector_type=self.inspector_type, ) for row in reader] @classmethod def choose_issue_type(cls, rule: str) -> IssueType: - issue_type = RULE_TO_ISSUE_TYPE.get(rule) + """ + Defines IssueType by PMD rule name using config. + """ + issue_type = PMD_RULE_TO_ISSUE_TYPE.get(rule) if not issue_type: logger.warning('%s: %s - unknown rule' % (cls.inspector_type.value, rule)) return IssueType.BEST_PRACTICES return issue_type + + @staticmethod + def _get_java_version(language_version: LanguageVersion) -> str: + """ + Converts language_version to the version of Java that PMD can work with. + + For example, java11 will be converted to 11. + """ + java_version = language_version.value + + if not language_version.is_java(): + logger.warning( + f"The version passed is not the Java version. The version to be used is: {DEFAULT_JAVA_VERSION.value}.", + ) + java_version = DEFAULT_JAVA_VERSION.value + + return remove_prefix(java_version, "java") diff --git a/test/python/evaluation/__init__.py b/test/python/evaluation/__init__.py deleted file mode 100644 index 31b1b86f..00000000 --- a/test/python/evaluation/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from test.python import TEST_DATA_FOLDER - -from src.python import MAIN_FOLDER - -CURRENT_TEST_DATA_FOLDER = TEST_DATA_FOLDER / 'evaluation' - -XLSX_DATA_FOLDER = CURRENT_TEST_DATA_FOLDER / 'xlsx_files' - -TARGET_XLSX_DATA_FOLDER = CURRENT_TEST_DATA_FOLDER / 'xlsx_target_files' - -RESULTS_DIR_PATH = MAIN_FOLDER.parent / 'evaluation/results' diff --git a/test/python/evaluation/test_data_path.py b/test/python/evaluation/test_data_path.py deleted file mode 100644 index 0d8e3502..00000000 --- a/test/python/evaluation/test_data_path.py +++ /dev/null @@ -1,14 +0,0 @@ -from test.python.evaluation import XLSX_DATA_FOLDER -from test.python.evaluation.testing_config import get_testing_arguments - -import pytest -from src.python.evaluation.evaluation_config import EvaluationConfig -from src.python.evaluation.xlsx_run_tool import create_dataframe - - -def test_incorrect_data_path(): - with pytest.raises(FileNotFoundError): - testing_arguments_dict = get_testing_arguments(to_add_traceback=True, to_add_tool_path=True) - testing_arguments_dict.xlsx_file_path = XLSX_DATA_FOLDER / 'do_not_exist.xlsx' - config = EvaluationConfig(testing_arguments_dict) - assert create_dataframe(config) diff --git a/test/python/evaluation/test_output_results.py b/test/python/evaluation/test_output_results.py deleted file mode 100644 index 519652e5..00000000 --- a/test/python/evaluation/test_output_results.py +++ /dev/null @@ -1,32 +0,0 @@ -from test.python.evaluation import TARGET_XLSX_DATA_FOLDER, XLSX_DATA_FOLDER -from test.python.evaluation.testing_config import get_testing_arguments - -import pandas as pd -import pytest -from src.python.evaluation.evaluation_config import EvaluationConfig -from src.python.evaluation.xlsx_run_tool import create_dataframe - -FILE_NAMES = [ - ('test_sorted_order.xlsx', 'target_sorted_order.xlsx', False), - ('test_sorted_order.xlsx', 'target_sorted_order.xlsx', True), - ('test_unsorted_order.xlsx', 'target_unsorted_order.xlsx', False), - ('test_unsorted_order.xlsx', 'target_unsorted_order.xlsx', True), -] - - -@pytest.mark.parametrize(('test_file', 'target_file', 'output_type'), FILE_NAMES) -def test_correct_output(test_file: str, target_file: str, output_type: bool): - - testing_arguments_dict = get_testing_arguments(to_add_tool_path=True) - testing_arguments_dict.xlsx_file_path = XLSX_DATA_FOLDER / test_file - testing_arguments_dict.traceback = output_type - - config = EvaluationConfig(testing_arguments_dict) - test_dataframe = create_dataframe(config) - - sheet_name = 'grades' - if output_type: - sheet_name = 'traceback' - target_dataframe = pd.read_excel(TARGET_XLSX_DATA_FOLDER / target_file, sheet_name=sheet_name) - - assert test_dataframe.reset_index(drop=True).equals(target_dataframe.reset_index(drop=True)) diff --git a/test/python/evaluation/test_tool_path.py b/test/python/evaluation/test_tool_path.py deleted file mode 100644 index 0581caad..00000000 --- a/test/python/evaluation/test_tool_path.py +++ /dev/null @@ -1,26 +0,0 @@ -from test.python.evaluation import XLSX_DATA_FOLDER -from test.python.evaluation.testing_config import get_testing_arguments - -import pytest -from src.python import MAIN_FOLDER -from src.python.evaluation.evaluation_config import EvaluationConfig -from src.python.evaluation.xlsx_run_tool import create_dataframe - - -def test_correct_tool_path(): - try: - testing_arguments_dict = get_testing_arguments(to_add_traceback=True, to_add_tool_path=True) - testing_arguments_dict.xlsx_file_path = XLSX_DATA_FOLDER / 'test_unsorted_order.xlsx' - config = EvaluationConfig(testing_arguments_dict) - create_dataframe(config) - except Exception: - pytest.fail("Unexpected error") - - -def test_incorrect_tool_path(): - with pytest.raises(Exception): - testing_arguments_dict = get_testing_arguments(to_add_traceback=True) - testing_arguments_dict.xlsx_file_path = XLSX_DATA_FOLDER / 'test_unsorted_order.xlsx' - testing_arguments_dict.tool_path = MAIN_FOLDER.parent / 'review/incorrect_path.py' - config = EvaluationConfig(testing_arguments_dict) - assert create_dataframe(config) diff --git a/test/python/evaluation/test_xlsx_file_structure.py b/test/python/evaluation/test_xlsx_file_structure.py deleted file mode 100644 index 9965992e..00000000 --- a/test/python/evaluation/test_xlsx_file_structure.py +++ /dev/null @@ -1,23 +0,0 @@ -from test.python.evaluation import XLSX_DATA_FOLDER -from test.python.evaluation.testing_config import get_testing_arguments - -import pytest -from src.python.evaluation.evaluation_config import EvaluationConfig -from src.python.evaluation.xlsx_run_tool import create_dataframe - - -FILE_NAMES = [ - 'test_wrong_column_name.xlsx', - 'test_java_no_version.xlsx', - 'test_empty_lang_cell.xlsx', - 'test_empty_table.xlsx', -] - - -@pytest.mark.parametrize('file_name', FILE_NAMES) -def test_wrong_column(file_name: str): - with pytest.raises(KeyError): - testing_arguments_dict = get_testing_arguments(to_add_traceback=True, to_add_tool_path=True) - testing_arguments_dict.xlsx_file_path = XLSX_DATA_FOLDER / file_name - config = EvaluationConfig(testing_arguments_dict) - assert create_dataframe(config) diff --git a/test/python/evaluation/testing_config.py b/test/python/evaluation/testing_config.py deleted file mode 100644 index 70341635..00000000 --- a/test/python/evaluation/testing_config.py +++ /dev/null @@ -1,18 +0,0 @@ -from argparse import Namespace - -from src.python import MAIN_FOLDER -from src.python.evaluation.common.util import EvaluationArgument -from src.python.review.reviewers.perform_review import OutputFormat - - -def get_testing_arguments(to_add_traceback=None, to_add_tool_path=None) -> Namespace: - testing_arguments = Namespace(format=OutputFormat.JSON.value, - output_file_name=EvaluationArgument.RESULT_FILE_NAME_EXT.value, - output_folder_path=None) - if to_add_traceback: - testing_arguments.traceback = True - - if to_add_tool_path: - testing_arguments.tool_path = MAIN_FOLDER.parent / 'review/run_tool.py' - - return testing_arguments diff --git a/test/resources/evaluation/xlsx_files/test_empty_lang_cell.xlsx b/test/resources/evaluation/xlsx_files/test_empty_lang_cell.xlsx deleted file mode 100644 index 91cdada068a9aa8ebe487d14c970a6f7415b1b8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5162 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 diff --git a/test/resources/evaluation/xlsx_files/test_empty_table.xlsx b/test/resources/evaluation/xlsx_files/test_empty_table.xlsx deleted file mode 100644 index 486b83a16a6b512ee860cc18323274db8588882b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4589 zcmai12RzjO|L5#=cbvUtlblWV2$4-jM)ui4cJ^NBj8GCXGvY+{%FakdC}f2r*^%+T z)9?37{=aX(*R9Vz9`||P`~7^qU(eU`rLB&IO^$(&kB?DInxKnuE~ru0zRvtM9+o~1 zuJ)e)z7V+L>*Aa}V&>WbCC2*>SpynOJD5;I6v@p;G@-y*c2~Lw_xpkQpMu`1i8=&e zGP(@N9&8M{de#0=vVsJ%u2z%b_*2wuny~^m+z@G8;$h>r!+VrXle=7h*3gKQ4>uKE zS`RQUswTac&}pJ}Q~K(xAo23M8j=O(Lip7Zf}!i)H;10DUfYtx&kA_`Du-ikwH+%9 zYkvDgV?3f(gm7OzoBmLoC4$oG5NF?RwyXrNLPuKr2(vplOehWzEt<~r3dud8s&Fht zAbvu-`?*)QrT>FP?`QHJOa`sa%k_%>z0p-1<}E3<%0Ayh>tI+6p##0I#DC0xuHxwv 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 V<6}n&000U0m5FVIK9r|-{|8~OR4o7i diff --git a/test/resources/evaluation/xlsx_files/test_sorted_order.xlsx b/test/resources/evaluation/xlsx_files/test_sorted_order.xlsx deleted file mode 100644 index bfdca3b2a22591703e61d6ba572aac5e027d4ce3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7317 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%0A0WM$% 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 diff --git a/test/resources/evaluation/xlsx_target_files/target_unsorted_order.xlsx b/test/resources/evaluation/xlsx_target_files/target_unsorted_order.xlsx deleted file mode 100644 index 25ed61462f273535632efd1ca804ad1b33e3dd7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30970 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 From 8153ec2f32e625f396854634f0e9d009f829b36c Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 15:27:56 +0300 Subject: [PATCH 03/10] Fix flake8 --- src/python/review/inspectors/flake8/flake8.py | 10 +--------- whitelist.txt | 1 + 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/python/review/inspectors/flake8/flake8.py b/src/python/review/inspectors/flake8/flake8.py index 7eb75a9d..c5dfd274 100644 --- a/src/python/review/inspectors/flake8/flake8.py +++ b/src/python/review/inspectors/flake8/flake8.py @@ -15,9 +15,8 @@ CyclomaticComplexityIssue, IssueData, IssueType, - LineLenIssue, ) -from src.python.review.inspectors.tips import get_cyclomatic_complexity_tip, get_line_len_tip +from src.python.review.inspectors.tips import get_cyclomatic_complexity_tip logger = logging.getLogger(__name__) @@ -52,7 +51,6 @@ def parse(cls, output: str) -> List[BaseIssue]: row_re = re.compile(r'^(.*):(\d+):(\d+):([A-Z]+\d{3}):(.*)$', re.M) cc_description_re = re.compile(r"'(.+)' is too complex \((\d+)\)") cohesion_description_re = re.compile(r"class has low \((\d*\.?\d*)%\) cohesion") - line_len_description_re = re.compile(r"line too long \((\d+) > \d+ characters\)") issues: List[BaseIssue] = [] for groups in row_re.findall(output): @@ -60,7 +58,6 @@ def parse(cls, output: str) -> List[BaseIssue]: origin_class = groups[3] cc_match = cc_description_re.match(description) cohesion_match = cohesion_description_re.match(description) - line_len_match = line_len_description_re.match(description) file_path = Path(groups[0]) line_no = int(groups[1]) @@ -82,11 +79,6 @@ def parse(cls, output: str) -> List[BaseIssue]: ) issue_data[IssueData.ISSUE_TYPE.value] = IssueType.COHESION issues.append(CohesionIssue(**issue_data)) - elif line_len_match is not None: - issue_data[IssueData.DESCRIPTION.value] = get_line_len_tip() - issue_data[IssueData.LINE_LEN.value] = int(line_len_match.groups()[0]) - issue_data[IssueData.ISSUE_TYPE.value] = IssueType.LINE_LEN - issues.append(LineLenIssue(**issue_data)) else: issue_type = cls.choose_issue_type(origin_class) issue_data[IssueData.ISSUE_TYPE.value] = issue_type diff --git a/whitelist.txt b/whitelist.txt index 77ff8f33..0b861514 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -94,3 +94,4 @@ util Namespace case18 case34 +removeprefix From 78852e76f002282f394daeb01c5d7f9fc27a68da Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 15:38:51 +0300 Subject: [PATCH 04/10] Fix a bug with PMD --- src/python/review/application_config.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/python/review/application_config.py b/src/python/review/application_config.py index 8d9a56f5..49f8f69f 100644 --- a/src/python/review/application_config.py +++ b/src/python/review/application_config.py @@ -42,3 +42,12 @@ def language_to_extension_dict(cls) -> dict: @classmethod def language_by_extension(cls, lang: str) -> str: return cls.language_to_extension_dict()[lang] + + def is_java(self) -> bool: + return ( + self == LanguageVersion.JAVA_7 + or self == LanguageVersion.JAVA_8 + or self == LanguageVersion.JAVA_9 + or self == LanguageVersion.JAVA_11 + or self == LanguageVersion.JAVA_15 + ) \ No newline at end of file From e2b5e8b9e43534fa718d28741445779dceb22767 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 15:40:59 +0300 Subject: [PATCH 05/10] Fix flake8 --- src/python/review/application_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/review/application_config.py b/src/python/review/application_config.py index 49f8f69f..ea1dca2c 100644 --- a/src/python/review/application_config.py +++ b/src/python/review/application_config.py @@ -50,4 +50,4 @@ def is_java(self) -> bool: or self == LanguageVersion.JAVA_9 or self == LanguageVersion.JAVA_11 or self == LanguageVersion.JAVA_15 - ) \ No newline at end of file + ) From 1d79efbb8b5cc7369077a47ea02a82d68aa228c0 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 15:54:16 +0300 Subject: [PATCH 06/10] Merge master --- test/python/inspectors/test_flake8_inspector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/python/inspectors/test_flake8_inspector.py b/test/python/inspectors/test_flake8_inspector.py index 8c5e3f7e..82db3abf 100644 --- a/test/python/inspectors/test_flake8_inspector.py +++ b/test/python/inspectors/test_flake8_inspector.py @@ -29,11 +29,10 @@ ('case19_bad_indentation.py', 3), ('case21_imports.py', 2), ('case25_django.py', 0), - ('case31_line_break.py', 11), + ('case31_spellcheck.py', 0), ('case32_string_format.py', 34), ('case33_commas.py', 14), ('case34_cohesion.py', 1), - ('case35_spellcheck.py', 0), ] From 1c03c49383bc34273f54206c5fd8e5861aee369f Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 16:11:02 +0300 Subject: [PATCH 07/10] Delete JAVA 15 --- src/python/review/application_config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python/review/application_config.py b/src/python/review/application_config.py index ea1dca2c..6032aef3 100644 --- a/src/python/review/application_config.py +++ b/src/python/review/application_config.py @@ -49,5 +49,4 @@ def is_java(self) -> bool: or self == LanguageVersion.JAVA_8 or self == LanguageVersion.JAVA_9 or self == LanguageVersion.JAVA_11 - or self == LanguageVersion.JAVA_15 ) From 8d31ecfb6ca95df6fabe695c80fb9dcf3b9cb49e Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 16:22:28 +0300 Subject: [PATCH 08/10] Fix complexity bug --- src/python/review/inspectors/detekt/issue_types.py | 2 +- test/python/inspectors/test_detekt_inspector.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/review/inspectors/detekt/issue_types.py b/src/python/review/inspectors/detekt/issue_types.py index e05d1a30..2379bba8 100644 --- a/src/python/review/inspectors/detekt/issue_types.py +++ b/src/python/review/inspectors/detekt/issue_types.py @@ -15,7 +15,7 @@ # complexity: 'ComplexCondition': IssueType.BOOL_EXPR_LEN, 'ComplexInterface': IssueType.COMPLEXITY, - 'ComplexMethod': IssueType.COMPLEXITY, + 'ComplexMethod': IssueType.CYCLOMATIC_COMPLEXITY, 'LabeledExpression': IssueType.COMPLEXITY, 'LargeClass': IssueType.COMPLEXITY, 'LongMethod': IssueType.FUNC_LEN, diff --git a/test/python/inspectors/test_detekt_inspector.py b/test/python/inspectors/test_detekt_inspector.py index 61d05200..99f84f27 100644 --- a/test/python/inspectors/test_detekt_inspector.py +++ b/test/python/inspectors/test_detekt_inspector.py @@ -23,6 +23,7 @@ ('case16_redundant_unit.kt', 1), ('case18_redundant_braces.kt', 3), ('case20_cyclomatic_complexity.kt', 0), + ('case21_cyclomatic_complexity_bad.kt', 2), ('case22_too_many_arguments.kt', 1), ('case23_bad_range_performance.kt', 3), ('case24_duplicate_when_bug.kt', 1), From 75afbef6dd3e2c7ce8e5b2341364091f9223b790 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 16:26:55 +0300 Subject: [PATCH 09/10] Fix flake8 --- src/python/review/inspectors/flake8/.flake8 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/python/review/inspectors/flake8/.flake8 b/src/python/review/inspectors/flake8/.flake8 index 815863c0..19edebba 100644 --- a/src/python/review/inspectors/flake8/.flake8 +++ b/src/python/review/inspectors/flake8/.flake8 @@ -1,8 +1,6 @@ [flake8] disable_noqa=True -max-line-length=120 - dictionaries=en_US,python,technical,django ignore=W291, # trailing whitespaces @@ -10,6 +8,7 @@ ignore=W291, # trailing whitespaces W293, # blank line contains whitespaces 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 From a90786ea4040475d6b97d365d0fcc1f1a6ff5888 Mon Sep 17 00:00:00 2001 From: "Anastasiia.Birillo" Date: Mon, 26 Jul 2021 16:30:35 +0300 Subject: [PATCH 10/10] Add flake8 test --- test/python/inspectors/test_flake8_inspector.py | 3 ++- .../python/{case31_line_break.py => case35_line_break.py} | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename test/resources/inspectors/python/{case31_line_break.py => case35_line_break.py} (100%) diff --git a/test/python/inspectors/test_flake8_inspector.py b/test/python/inspectors/test_flake8_inspector.py index 82db3abf..f1ed0958 100644 --- a/test/python/inspectors/test_flake8_inspector.py +++ b/test/python/inspectors/test_flake8_inspector.py @@ -33,6 +33,7 @@ ('case32_string_format.py', 34), ('case33_commas.py', 14), ('case34_cohesion.py', 1), + ('case35_line_break.py', 11), ] @@ -69,7 +70,7 @@ def test_file_with_issues(file_name: str, n_issues: int): ('case14_returns_errors.py', IssuesTestInfo(n_best_practices=1, n_error_prone=3, n_cc=4)), - ('case31_line_break.py', IssuesTestInfo(n_best_practices=1, + ('case35_line_break.py', IssuesTestInfo(n_best_practices=1, n_code_style=10, n_cc=1)), ('case32_string_format.py', IssuesTestInfo(n_error_prone=28, n_other_complexity=6)), diff --git a/test/resources/inspectors/python/case31_line_break.py b/test/resources/inspectors/python/case35_line_break.py similarity index 100% rename from test/resources/inspectors/python/case31_line_break.py rename to test/resources/inspectors/python/case35_line_break.py