diff --git a/README.md b/README.md index 23ad7bc5..68676135 100644 --- a/README.md +++ b/README.md @@ -24,36 +24,49 @@ The source code of **hyperstyle** is distributed under the Apache 2.0 License. The 3rd party software we use in this project has its own licenses. -* Checkstyle [GNU LGPL v2.1] - * [Site and docs](https://checkstyle.sourceforge.io/) - * [Repository](https://github.com/checkstyle/checkstyle) - -* Detekt [Apache 2.0] - * [Site and docs](https://detekt.github.io/detekt/) - * [Repository](https://github.com/detekt/detekt) +Python language: -* ESlint [MIT] - * [Site and docs](https://eslint.org/) - * [Repository](https://github.com/eslint/eslint) - -* flake8 [MIT] +- [x] flake8 [MIT] * [Site and docs](https://flake8.pycqa.org/en/latest/) * [Repository](https://github.com/PyCQA/flake8) - -* PMD [BSD] - * [Site and docs](https://pmd.github.io/) - * [Repository](https://github.com/pmd/pmd) - -* Pylint [GNU LGPL v2] + +- [x] Pylint [GNU LGPL v2] * [Site and docs](https://www.pylint.org/) * [Repository](https://github.com/PyCQA/pylint) + + +Java language: -* SpotBugs [GNU LGPL v2.1] +- [x] PMD [BSD] + * [Site and docs](https://pmd.github.io/) + * [Repository](https://github.com/pmd/pmd) + +- [x] Checkstyle [GNU LGPL v2.1] + * [Site and docs](https://checkstyle.sourceforge.io/) + * [Repository](https://github.com/checkstyle/checkstyle) + +- [ ] SpotBugs [GNU LGPL v2.1] * [Site and docs](https://spotbugs.github.io/) * [Repository](https://github.com/spotbugs/spotbugs) - -* SpringLint + +- [ ] SpringLint * [Repository](https://github.com/mauricioaniche/springlint) + + + +Kotlin language: + +- [x] Detekt [Apache 2.0] + * [Site and docs](https://detekt.github.io/detekt/) + * [Repository](https://github.com/detekt/detekt) + + + +JavaScript language: + +- [x] ESlint [MIT] + * [Site and docs](https://eslint.org/) + * [Repository](https://github.com/eslint/eslint) --- @@ -64,6 +77,111 @@ Simply clone the repository and run the following commands: 1. `pip install -r requirements.txt` 2. `pip install -r requirements-test.txt` for tests +## Usage + +Run the [run_tool.py](./src/python/review/run_tool.py) with the arguments. + +A simple configuration: `python run_tool.py `. + +**Required arguments:** +1. **path** — path to file or directory to inspect. + +Optional arguments: + +Argument | Description +--- | --- +**‑h**, **‑‑help** | show the help message and exit. +**‑v**, **‑‑verbosity** | choose logging level according [this](https://docs.python.org/3/library/logging.html#levels) list: `1` - **ERROR**; `2` - **INFO**; `3` - **DEBUG**; `0` - disable logging (**CRITICAL** value); default value is `0` (**CRITICAL**). +**‑d**, **‑‑disable** | disable inspectors. Available values: for **Python** language: `pylint` for [Pylint](https://github.com/PyCQA/pylint), `flake8` for [flake8](https://flake8.pycqa.org/en/latest/), `python_ast` to check different measures providing by AST; for **Java** language: `checkstyle` for the [Checkstyle](https://checkstyle.sourceforge.io/), `pmd` for [PMD](https://pmd.github.io/); for `Kotlin` language: detekt for [Detekt](https://detekt.github.io/detekt/); for **JavaScript** language: `eslint` for [ESlint](https://eslint.org/). Example: `-d pylint,flake8`. +**‑‑allow_duplicates** | allow duplicate issues found by different linters. By default, duplicates are skipped. +**‑‑language_version** | specify the language version for JAVA inspectors. Available values: `java7`, `java8`, `java9`, `java11`. +**‑‑n_cpu** | specify number of _cpu_ that can be used to run inspectors +**‑f**, **‑‑format** | the output format. Available values: `json`, `text`. Default value is `json`. +**‑s**, **‑‑start_line**| the first line to be analyzed. By default it starts from `1`. +**‑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`. + +The output examples: + +(_New format_ means separating the result by the files to allow getting quality and observed issues for each file separately) + +1. Json `old format` (without **‑‑new_format** argument): + +```json +{ + "quality": { + "code": "BAD", + "text": "Code quality (beta): BAD" + }, + "issues": [ + { + "code": "C002", + "text": "Too long function. Try to split it into smaller functions / methods.It will make your code easy to understand and less error prone.", + "line": "", + "line_number": 54, + "column_number": 0, + "category": "FUNC_LEN" + }, + ... + ] +} +``` + +2. Json `new format` (with **‑‑new_format** argument): + +```json +{ + "quality": { + "code": "BAD", + "text": "Code quality (beta): BAD" + }, + "file_review_results": [ + { + "file_name": "", + "quality": { + "code": "BAD", + "text": "Code quality (beta): BAD" + }, + "issues": [ + { + "code": "W0703", + "text": "Catching too general exception Exception", + "line": "", + "line_number": 174, + "column_number": 12, + "category": "BEST_PRACTICES" + }, + ... + ] + } + ] +} +``` + +3. Text format: + +```text +Review of (N violations) +*********************************************************************************************************** +File +----------------------------------------------------------------------------------------------------------- +Line № : Column № : Type : Inspector : Origin : Description : Line : Path +54 : 0 : FUNC_LEN : PYTHON_AST : C002 : : : +... +----------------------------------------------------------------------------------------------------------- +Code quality (beta): BAD +Next level: EXCELLENT +Next level requirements: +FUNC_LEN: 12 + +*********************************************************************************************************** +General quality: +Code quality (beta): BAD +Next level: EXCELLENT +Next level requirements: +FUNC_LEN: 12 +``` + --- ## Tests running diff --git a/src/python/review/application_config.py b/src/python/review/application_config.py index 23dd5665..0f1dd7d8 100644 --- a/src/python/review/application_config.py +++ b/src/python/review/application_config.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum, unique -from typing import Optional, Set +from typing import Optional, Set, List from src.python.review.inspectors.inspector_type import InspectorType @@ -22,3 +22,7 @@ class LanguageVersion(Enum): JAVA_8 = 'java8' JAVA_9 = 'java9' JAVA_11 = 'java11' + + @classmethod + def values(cls) -> List[str]: + return [member.value for _, member in LanguageVersion.__members__.items()] diff --git a/src/python/review/inspectors/inspector_type.py b/src/python/review/inspectors/inspector_type.py index f03899f3..bac74e01 100644 --- a/src/python/review/inspectors/inspector_type.py +++ b/src/python/review/inspectors/inspector_type.py @@ -1,15 +1,42 @@ from enum import Enum, unique +from typing import List @unique class InspectorType(Enum): + # Python language PYLINT = 'PYLINT' - FLAKE8 = 'FLAKE8' PYTHON_AST = 'PYTHON_AST' - CHECKSTYLE = 'CHECKSTYLE' + FLAKE8 = 'FLAKE8' + + # Java language PMD = 'PMD' + CHECKSTYLE = 'CHECKSTYLE' SPOTBUGS = 'SPOTBUGS' + SPRINGLINT = 'SPRINGLINT' + + # Kotlin language DETEKT = 'DETEKT' INTELLIJ = 'INTELLIJ' - SPRINGLINT = 'SPRINGLINT' + + # JavaScript language ESLINT = 'ESLINT' + + @classmethod + def available_values(cls) -> List[str]: + return [ + # Python language + InspectorType.PYLINT.value, + InspectorType.FLAKE8.value, + InspectorType.PYTHON_AST.value, + + # Java language + InspectorType.PMD.value, + InspectorType.CHECKSTYLE.value, + + # Kotlin language + InspectorType.DETEKT.value, + + # JavaScript language + InspectorType.ESLINT.value, + ] diff --git a/src/python/review/reviewers/perform_review.py b/src/python/review/reviewers/perform_review.py index b64ef09a..678e503a 100644 --- a/src/python/review/reviewers/perform_review.py +++ b/src/python/review/reviewers/perform_review.py @@ -2,7 +2,7 @@ import logging from functools import partial from pathlib import Path -from typing import Final +from typing import Final, List from src.python.review.application_config import ApplicationConfig from src.python.review.common.language import Language @@ -45,6 +45,10 @@ def __str__(self): def __repr__(self): return str(self) + @classmethod + def values(cls) -> List[str]: + return [member.value for _, member in OutputFormat.__members__.items()] + def perform_and_print_review(path: Path, output_format: OutputFormat, diff --git a/src/python/review/run_tool.py b/src/python/review/run_tool.py index 70d10805..1c356f7b 100644 --- a/src/python/review/run_tool.py +++ b/src/python/review/run_tool.py @@ -5,7 +5,7 @@ import traceback from enum import Enum, unique from pathlib import Path -from typing import Set +from typing import Set, List sys.path.append('') sys.path.append('../../..') @@ -29,6 +29,10 @@ class VerbosityLevel(Enum): ERROR = '1' DISABLE = '0' + @classmethod + def values(cls) -> List[str]: + return [member.value for _, member in VerbosityLevel.__members__.items()] + def parse_disabled_inspectors(value: str) -> Set[InspectorType]: passed_names = value.upper().split(',') @@ -50,17 +54,17 @@ def positive_int(value: str) -> int: def configure_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument('-v', '--verbosity', help='Choose logging level: ' - f'{VerbosityLevel.ERROR.value} - >= ERROR; ' - f'{VerbosityLevel.INFO.value} - >= INFO; ' - f'{VerbosityLevel.DEBUG.value} - >= DEBUG; ' - f'{VerbosityLevel.DISABLE.value} - disable logging' + f'{VerbosityLevel.ERROR.value} - ERROR; ' + f'{VerbosityLevel.INFO.value} - INFO; ' + f'{VerbosityLevel.DEBUG.value} - DEBUG; ' + f'{VerbosityLevel.DISABLE.value} - disable logging; ' 'default is 0', default=VerbosityLevel.DISABLE.value, - choices=list(VerbosityLevel), - type=VerbosityLevel) + choices=VerbosityLevel.values(), + type=str) # Usage example: -d Flake8,Intelli - inspectors = [inspector.value.lower() for inspector in InspectorType] + inspectors = [inspector.lower() for inspector in InspectorType.available_values()] example = f'-d {inspectors[0].lower()},{inspectors[1].lower()}' parser.add_argument('-d', '--disable', @@ -70,15 +74,15 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: type=parse_disabled_inspectors, default=set()) - parser.add_argument('--allow-duplicates', action='store_true', + parser.add_argument('--allow_duplicates', action='store_true', help='Allow duplicate issues found by different linters. ' 'By default, duplicates are skipped.') parser.add_argument('--language_version', - help='Specify the language version for inspectors.', + help='Specify the language version for JAVA inspectors.', default=None, - choices=list(LanguageVersion), - type=LanguageVersion) + choices=LanguageVersion.values(), + type=str) parser.add_argument('--n_cpu', help='Specify number of cpu that can be used to run inspectors', @@ -91,21 +95,21 @@ def configure_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument('-f', '--format', default=OutputFormat.JSON, - choices=list(OutputFormat), - type=OutputFormat, + choices=OutputFormat.values(), + type=str, help='The output format. Default is JSON.') - parser.add_argument('-s', '--start-line', + parser.add_argument('-s', '--start_line', default=1, type=positive_int, help='The first line to be analyzed. It starts from 1.') - parser.add_argument('-e', '--end-line', + parser.add_argument('-e', '--end_line', default=None, type=positive_int, help='The end line to be analyzed or None.') - parser.add_argument('--new-format', + parser.add_argument('--new_format', action='store_true', help='The argument determines whether the tool ' 'should use the new format') @@ -130,7 +134,7 @@ def main() -> int: try: args = parser.parse_args() - configure_logging(args.verbosity) + configure_logging(VerbosityLevel(args.verbosity)) n_cpu = args.n_cpu max_n_cpu = os.cpu_count() @@ -144,7 +148,7 @@ def main() -> int: start_line = 1 inspectors_config = { - 'language_version': args.language_version, + 'language_version': LanguageVersion(args.language_version) if args.language_version is not None else None, 'n_cpu': n_cpu } @@ -156,7 +160,7 @@ def main() -> int: new_format=args.new_format, ) - n_issues = perform_and_print_review(args.path, args.format, config) + n_issues = perform_and_print_review(args.path, OutputFormat(args.format), config) if not n_issues: return 0 diff --git a/test/python/functional_tests/conftest.py b/test/python/functional_tests/conftest.py index cec7b0a0..6c47ed24 100644 --- a/test/python/functional_tests/conftest.py +++ b/test/python/functional_tests/conftest.py @@ -32,13 +32,13 @@ def build(self) -> List[str]: command.extend(['-d', ','.join(self.disable)]) if self.allow_duplicates: - command.append('--allow-duplicates') + command.append('--allow_duplicates') if self.language_version is not None: - command.extend(['--language-version', self.language_version]) + command.extend(['--language_version', self.language_version]) if self.new_format: - command.append('--new-format') + command.append('--new_format') command.extend([ '--n_cpu', str(self.n_cpu),