From 91126869e1d09228793e1a33e4023ed8c58f68b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Hirsz?= Date: Tue, 29 Oct 2024 12:34:30 +0100 Subject: [PATCH] Fix typing issues from ruff (#1136) * ruff RUF010: remove str() from fstring * ruff UP037: remove unnecessary quotes in typing * ruff ISC001: concat string literals * ruff D403: capitalize first word of documentation * ruff: replace typing with literal types (List -> list) * ruff: Optional -> None, Union -> | --- pyproject.toml | 14 +++++----- robocop/checkers/__init__.py | 28 ++++++++++--------- robocop/checkers/community_rules/keywords.py | 4 +-- robocop/checkers/community_rules/usage.py | 22 ++++++++------- robocop/checkers/lengths.py | 3 +- robocop/checkers/misc.py | 14 +++++----- robocop/checkers/naming.py | 9 ++++-- robocop/config.py | 11 ++++---- robocop/exceptions.py | 6 ++-- robocop/reports/__init__.py | 5 ++-- robocop/reports/file_stats_report.py | 6 ++-- robocop/reports/rules_by_id_report.py | 7 ++--- robocop/rules.py | 27 ++++++++++-------- robocop/run.py | 5 ++-- robocop/utils/disablers.py | 5 ++-- robocop/utils/file_types.py | 2 +- robocop/utils/misc.py | 12 ++++---- robocop/utils/run_keywords.py | 6 ++-- robocop/utils/variable_matcher.py | 2 +- robocop/utils/version_matching.py | 12 ++++---- .../external_project_checker/__init__.py | 4 +-- tests/atest/utils/__init__.py | 17 +++++------ 22 files changed, 116 insertions(+), 105 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3b798ec18..f3a7cad67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,12 +58,9 @@ lint.ignore = [ "B007", # TODO: loop variable not used "TRY003", # TODO: exception with long message outside exception class "INP001", # TODO: file part of implicit namespace package - "FA100", # TODO: Use from future import to replace Optional with | None "A001", # TODO: variable shadowing python builtin "A002", # TODO: argument shadowing python builtin "FBT001", # TODO: boolean positional argument in fn def. Check if fn can be splitted on two depending on flag - "UP006", # TODO: List -> list - "UP035", # TODO: List -> list "E501", # TODO: line too long "FIX001", # TODO: code with fixme "TD003", # TODO: code with fixme and without issue link @@ -72,10 +69,8 @@ lint.ignore = [ "N999", # TODO: invalid module name unused-keyword "PLR2004", # TODO: magic value used in comparison (50 issues) "RET503", # TODO: missing explicit return - "D403", # TODO: capitalize first word "PYI024", # TODO: namedtuple -> NamedTuple "TRY201", # TODO: use raise without exception name - "ISC001", # TODO: implicitly concatenated string literals on one line "PTH100", # TODO: use Path "PTH109", # TODO: use Path "PTH110", # TODO: use Path @@ -88,10 +83,8 @@ lint.ignore = [ "B028", # TODO: no explicit stacklevel in warn "B904", # TODO: raise from "PLR0912", # TODO: too many branches - "RUF010", # TODO: use explicit conversion flag "ERA001", # TODO: commented out code "SIM105", # TODO: use supress - "UP037", # TODO: remove quotes from type annotation "ARG001", # TODO: unused function argument "DTZ005", # TODO: `datetime.datetime.now()` called without a `tz` argument ] @@ -100,6 +93,13 @@ lint.unfixable = [ # In addition, it can't do anything with invalid typing annotations, protected by mypy. "PT022", ] +[tool.ruff.lint.extend-per-file-ignores] +# following breaks for now with Robot Framework dynamic importing +"robocop/checkers/misc.py" = ["FA100"] +"robocop/checkers/community_rules/usage.py" = ["FA100"] +# return type for tests does not make sense +"tests/*" = ["ANN201"] + [tool.coverage.run] omit = ["*tests*"] diff --git a/robocop/checkers/__init__.py b/robocop/checkers/__init__.py index 4c0a93b93..cf7df46c4 100644 --- a/robocop/checkers/__init__.py +++ b/robocop/checkers/__init__.py @@ -33,13 +33,15 @@ configurations etc.). You can optionally configure rule severity or other parameters. """ +from __future__ import annotations + import ast import importlib.util import inspect from collections import defaultdict from importlib import import_module from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING try: from robot.api.parsing import ModelVisitor @@ -68,7 +70,7 @@ def __init__(self): self.source = None self.lines = None self.issues = [] - self.rules: Dict[str, "Rule"] = {} + self.rules: dict[str, Rule] = {} self.templated_suite = False def param(self, rule, param_name): @@ -92,7 +94,7 @@ def report( extended_disablers=None, sev_threshold_value=None, severity=None, - source: Optional[str] = None, + source: str | None = None, **kwargs, ): rule_def = self.rules.get(rule, None) @@ -121,8 +123,8 @@ def report( class VisitorChecker(BaseChecker, ModelVisitor): - def scan_file(self, ast_model, filename, in_memory_content, templated=False) -> List["Message"]: - self.issues: List["Message"] = [] + def scan_file(self, ast_model, filename, in_memory_content, templated=False) -> list[Message]: + self.issues: list[Message] = [] self.source = filename self.templated_suite = templated if in_memory_content is not None: @@ -138,7 +140,7 @@ def visit_File(self, node): # noqa: N802 class ProjectChecker(VisitorChecker): - def scan_project(self) -> List["Message"]: + def scan_project(self) -> list[Message]: """ Perform checks on the whole project. @@ -149,8 +151,8 @@ def scan_project(self) -> List["Message"]: class RawFileChecker(BaseChecker): - def scan_file(self, ast_model, filename, in_memory_content, templated=False) -> List["Message"]: - self.issues: List["Message"] = [] + def scan_file(self, ast_model, filename, in_memory_content, templated=False) -> list[Message]: + self.issues: list[Message] = [] self.source = filename self.templated_suite = templated if in_memory_content is not None: @@ -174,7 +176,7 @@ def check_line(self, line, lineno): raise NotImplementedError -def is_checker(checker_class_def: Tuple) -> bool: +def is_checker(checker_class_def: tuple) -> bool: return issubclass(checker_class_def[1], BaseChecker) and getattr(checker_class_def[1], "reports", False) @@ -294,7 +296,7 @@ def get_imported_rules(rule_modules): yield module_name, rule @staticmethod - def get_rules_from_module(module) -> Dict: + def get_rules_from_module(module) -> dict: module_rules = getattr(module, "rules", {}) if not isinstance(module_rules, dict): return {} @@ -307,13 +309,13 @@ def get_rules_from_module(module) -> Dict: rules[rule.name] = rule return rules - def register_deprecated_rules(self, module_rules: Dict[str, "Rule"]): + def register_deprecated_rules(self, module_rules: dict[str, Rule]): for rule_name, rule_def in module_rules.items(): if rule_def.deprecated: self.deprecated_rules[rule_name] = rule_def self.deprecated_rules[rule_def.rule_id] = rule_def - def get_checkers_from_module(self, module, is_community: bool) -> List: + def get_checkers_from_module(self, module, is_community: bool) -> list: classes = inspect.getmembers(module, inspect.isclass) checkers = [checker for checker in classes if is_checker(checker)] category_id = getattr(module, "RULE_CATEGORY_ID", None) @@ -339,7 +341,7 @@ def get_checkers_from_module(self, module, is_community: bool) -> List: return checker_instances -def init(linter: "Robocop"): +def init(linter: Robocop): robocop_importer = RobocopImporter(linter.config.ext_rules) for checker in robocop_importer.get_initialized_checkers(): linter.register_checker(checker) diff --git a/robocop/checkers/community_rules/keywords.py b/robocop/checkers/community_rules/keywords.py index 2f66ac63f..ee0c69bb2 100644 --- a/robocop/checkers/community_rules/keywords.py +++ b/robocop/checkers/community_rules/keywords.py @@ -1,5 +1,3 @@ -from typing import Set - from robot.api import Token from robot.model import Keyword from robot.utils.robottime import timestr_to_secs @@ -12,7 +10,7 @@ RULE_CATEGORY_ID = "00" -def comma_separated_list(value: str) -> Set[str]: +def comma_separated_list(value: str) -> set[str]: if value is None: return set() return {normalize_robot_name(kw) for kw in value.split(",")} diff --git a/robocop/checkers/community_rules/usage.py b/robocop/checkers/community_rules/usage.py index b6548cd7b..370dfea88 100644 --- a/robocop/checkers/community_rules/usage.py +++ b/robocop/checkers/community_rules/usage.py @@ -1,6 +1,8 @@ +from collections.abc import Iterable from dataclasses import dataclass, field from itertools import chain -from typing import Dict, Iterable, List, Optional, Pattern, Set, Union +from re import Pattern +from typing import Optional, Union from robot.api import Token from robot.errors import DataError @@ -54,7 +56,7 @@ class KeywordUsage: found_def: bool = False used: int = 0 - names: Set[str] = field(default_factory=set) + names: set[str] = field(default_factory=set) def update(self, name: str): self.used += 1 @@ -66,7 +68,7 @@ class KeywordDefinition: name: Union[str, Pattern] keyword_node: Keyword used: int = 0 - used_names: Set[str] = field(default_factory=set) + used_names: set[str] = field(default_factory=set) is_private: bool = False def update(self, used_as: KeywordUsage): @@ -79,9 +81,9 @@ def update(self, used_as: KeywordUsage): class RobotFile: path: str is_suite: bool = False - normal_keywords: Dict[str, KeywordDefinition] = field(default_factory=dict) - embedded_keywords: Dict[str, KeywordDefinition] = field(default_factory=dict) - used_keywords: Dict[str, KeywordUsage] = field(default_factory=dict) + normal_keywords: dict[str, KeywordDefinition] = field(default_factory=dict) + embedded_keywords: dict[str, KeywordDefinition] = field(default_factory=dict) + used_keywords: dict[str, KeywordUsage] = field(default_factory=dict) @property def keywords(self) -> Iterable[KeywordDefinition]: @@ -92,11 +94,11 @@ def any_private(self) -> bool: return any(keyword.is_private for keyword in self.keywords) @property - def private_keywords(self) -> List[KeywordDefinition]: + def private_keywords(self) -> list[KeywordDefinition]: return [keyword for keyword in self.keywords if keyword.is_private] @property - def not_used_keywords(self) -> List[KeywordDefinition]: + def not_used_keywords(self) -> list[KeywordDefinition]: not_used = [] for keyword in self.keywords: if keyword.used or not (self.is_suite or keyword.is_private): @@ -126,11 +128,11 @@ class UnusedKeywords(ProjectChecker): # TODO: handle BDD def __init__(self): - self.files: Dict[str, RobotFile] = {} + self.files: dict[str, RobotFile] = {} self.current_file: Optional[RobotFile] = None super().__init__() - def scan_project(self) -> List["Message"]: + def scan_project(self) -> list["Message"]: self.issues = [] for robot_file in self.files.values(): if not (robot_file.is_suite or robot_file.any_private): diff --git a/robocop/checkers/lengths.py b/robocop/checkers/lengths.py index 7a87135f8..ffcf6ba60 100644 --- a/robocop/checkers/lengths.py +++ b/robocop/checkers/lengths.py @@ -1,7 +1,6 @@ """Lengths checkers""" import re -from typing import List from robot.api import Token from robot.parsing.model.blocks import CommentSection, TestCase @@ -852,7 +851,7 @@ def visit_Arguments(self, node): # noqa: N802 ) @staticmethod - def first_non_sep(line: List[Token]) -> Token: + def first_non_sep(line: list[Token]) -> Token: for token in line: if token.type != Token.SEPARATOR: return token diff --git a/robocop/checkers/misc.py b/robocop/checkers/misc.py index bfa76c6ba..e5d8be09b 100644 --- a/robocop/checkers/misc.py +++ b/robocop/checkers/misc.py @@ -3,7 +3,7 @@ import ast from dataclasses import dataclass from pathlib import Path -from typing import Dict, List, Optional +from typing import Optional from robot.api import Token from robot.errors import VariableError @@ -46,7 +46,7 @@ RULE_CATEGORY_ID = "09" -def comma_separated_list(value: str) -> List[str]: +def comma_separated_list(value: str) -> list[str]: return value.split(",") @@ -1214,7 +1214,7 @@ class SectionVariablesCollector(ast.NodeVisitor): """Visitor for collecting all variables in the suite""" def __init__(self): - self.section_variables: Dict[str, CachedVariable] = {} + self.section_variables: dict[str, CachedVariable] = {} def visit_Variable(self, node): # noqa: N802 if get_errors(node): @@ -1234,11 +1234,11 @@ class UnusedVariablesChecker(VisitorChecker): ) def __init__(self): - self.arguments: Dict[str, CachedVariable] = {} - self.variables: List[Dict[str, CachedVariable]] = [ + self.arguments: dict[str, CachedVariable] = {} + self.variables: list[dict[str, CachedVariable]] = [ {} ] # variables are list of scope-dictionaries, to support IF branches - self.section_variables: Dict[str, CachedVariable] = {} + self.section_variables: dict[str, CachedVariable] = {} self.used_in_scope = set() # variables that were used in current FOR/WHILE loop self.ignore_overwriting = False # temporarily ignore overwriting, e.g. in FOR loops self.in_loop = False # if we're in the loop we need to check whole scope for unused-variable @@ -1573,7 +1573,7 @@ def variable_namespaces(self): yield self.section_variables yield from self.variables[::-1] - def _set_variable_as_used(self, normalized_name: str, variable_scope: Dict[str, CachedVariable]) -> None: + def _set_variable_as_used(self, normalized_name: str, variable_scope: dict[str, CachedVariable]) -> None: """If variable is found in variable_scope, set it as used.""" if normalized_name in variable_scope: variable_scope[normalized_name].is_used = True diff --git a/robocop/checkers/naming.py b/robocop/checkers/naming.py index bfa65e731..600d714c6 100644 --- a/robocop/checkers/naming.py +++ b/robocop/checkers/naming.py @@ -1,10 +1,12 @@ """Naming checkers""" +from __future__ import annotations + import re import string from collections import defaultdict from pathlib import Path -from typing import Iterable, Optional +from typing import TYPE_CHECKING from robot.api import Token from robot.errors import VariableError @@ -28,6 +30,9 @@ from robocop.utils.run_keywords import iterate_keyword_names from robocop.utils.variable_matcher import VariableMatches +if TYPE_CHECKING: + from collections.abc import Iterable + RULE_CATEGORY_ID = "03" rules = { @@ -841,7 +846,7 @@ class SettingsNamingChecker(VisitorChecker): def __init__(self): self.section_name_pattern = re.compile(r"\*\*\*\s.+\s\*\*\*") - self.task_section: Optional[bool] = None + self.task_section: bool | None = None super().__init__() def visit_InvalidSection(self, node): # noqa: N802 diff --git a/robocop/config.py b/robocop/config.py index 45bafdd41..5f276d56a 100644 --- a/robocop/config.py +++ b/robocop/config.py @@ -5,7 +5,8 @@ import sys from itertools import chain from pathlib import Path -from typing import TYPE_CHECKING, Dict, Pattern, Set +from re import Pattern +from typing import TYPE_CHECKING import tomli from robot.utils import FileReader @@ -486,7 +487,7 @@ def load_pyproject_file(self, pyproject_path): with Path(pyproject_path).open("rb") as fp: config = tomli.load(fp) except tomli.TOMLDecodeError as err: - raise exceptions.InvalidArgumentError(f"Failed to decode {str(pyproject_path)}: {err}") from None + raise exceptions.InvalidArgumentError(f"Failed to decode {pyproject_path!s}: {err}") from None config = config.get("tool", {}).get("robocop", {}) if self.parse_toml_to_config(config, config_dir): self.config_from = pyproject_path @@ -511,13 +512,13 @@ def parse_args(self, args): self.parse_args_to_config(args) @staticmethod - def replace_in_set(container: Set, old_key: str, new_key: str): + def replace_in_set(container: set, old_key: str, new_key: str): if old_key not in container: return container.remove(old_key) container.add(new_key) - def validate_rules_exists_and_not_deprecated(self, rules: Dict[str, "Rule"]): + def validate_rules_exists_and_not_deprecated(self, rules: dict[str, "Rule"]): for rule in chain(self.include, self.exclude): if rule not in rules: raise exceptions.RuleDoesNotExist(rule, rules) from None @@ -560,7 +561,7 @@ def replace_severity_values(rule_name: str): rule_name = rule_name.replace(char, "") return rule_name - def parse_toml_to_config(self, toml_data: Dict, config_dir: Path): + def parse_toml_to_config(self, toml_data: dict, config_dir: Path): if not toml_data: return False resolve_relative = {"paths", "ext_rules", "output"} diff --git a/robocop/exceptions.py b/robocop/exceptions.py index 335d47b3b..d8fa5c13c 100644 --- a/robocop/exceptions.py +++ b/robocop/exceptions.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING if TYPE_CHECKING: from robocop.rules import Rule @@ -90,7 +90,7 @@ def __init__(self, report, reports): class RuleDoesNotExist(ConfigGeneralError): - def __init__(self, rule: str, rules: Dict[str, "Rule"]): + def __init__(self, rule: str, rules: dict[str, "Rule"]): from robocop.utils import RecommendationFinder similar = RecommendationFinder().find_similar(rule, rules) @@ -99,7 +99,7 @@ def __init__(self, rule: str, rules: Dict[str, "Rule"]): class RuleOrReportDoesNotExist(ConfigGeneralError): - def __init__(self, rule: str, rules: Dict[str, "Rule"]): + def __init__(self, rule: str, rules: dict[str, "Rule"]): from robocop.utils import RecommendationFinder similar = RecommendationFinder().find_similar(rule, rules) diff --git a/robocop/reports/__init__.py b/robocop/reports/__init__.py index 75c4baa1d..9e051eba2 100644 --- a/robocop/reports/__init__.py +++ b/robocop/reports/__init__.py @@ -2,7 +2,6 @@ import json from collections import OrderedDict from pathlib import Path -from typing import Dict, List import robocop.exceptions from robocop.checkers import RobocopImporter @@ -84,7 +83,7 @@ def is_report_internal(report): return getattr(report, "INTERNAL", False) -def disable_external_reports_if_none(configured_reports: List[str]) -> List[str]: +def disable_external_reports_if_none(configured_reports: list[str]) -> list[str]: """If any reports is 'None', disable other reports other than internal reports.""" if "None" in configured_reports: if "internal_json_report" in configured_reports: @@ -153,7 +152,7 @@ def load_reports_result_from_cache(): return None -def save_reports_result_to_cache(working_dir: str, report_results: Dict): +def save_reports_result_to_cache(working_dir: str, report_results: dict): """ Save results from Robocop reports to json file. diff --git a/robocop/reports/file_stats_report.py b/robocop/reports/file_stats_report.py index bb601aea1..6765a2183 100644 --- a/robocop/reports/file_stats_report.py +++ b/robocop/reports/file_stats_report.py @@ -1,5 +1,3 @@ -from typing import Dict - import robocop.reports from robocop.rules import Message from robocop.utils.misc import get_plural_form, get_string_diff @@ -29,12 +27,12 @@ def add_message(self, message: Message): def persist_result(self): return {"files_count": self.files_count, "files_with_issues": len(self.files_with_issues)} - def get_report(self, prev_results: Dict) -> str: + def get_report(self, prev_results: dict) -> str: if self.compare_runs and prev_results: return self.get_report_with_compare(prev_results) return self.get_report_without_compare() - def get_report_with_compare(self, prev_results: Dict) -> str: + def get_report_with_compare(self, prev_results: dict) -> str: plural_files = get_plural_form(self.files_count) prev_files_count = prev_results["files_count"] prev_files_with_issues = prev_results["files_with_issues"] diff --git a/robocop/reports/rules_by_id_report.py b/robocop/reports/rules_by_id_report.py index ebfe72d0a..7b6dd7f42 100644 --- a/robocop/reports/rules_by_id_report.py +++ b/robocop/reports/rules_by_id_report.py @@ -1,6 +1,5 @@ from collections import defaultdict from operator import itemgetter -from typing import Dict import robocop.reports from robocop.rules import Message @@ -30,10 +29,10 @@ def __init__(self, compare_runs): def add_message(self, message: Message): self.message_counter[message.get_fullname()] += 1 - def persist_result(self) -> Dict: + def persist_result(self) -> dict: return dict(self.message_counter.items()) - def get_diff_counter(self, prev_results: Dict) -> Dict: + def get_diff_counter(self, prev_results: dict) -> dict: result = {} for issue_code, count in self.message_counter.items(): old_count = prev_results.pop(issue_code, 0) @@ -47,7 +46,7 @@ def get_report(self, prev_results) -> str: return self.get_report_with_compare(prev_results) return self.get_report_without_compare() - def get_report_with_compare(self, prev_results: Dict) -> str: + def get_report_with_compare(self, prev_results: dict) -> str: diff_counter = self.get_diff_counter(prev_results) message_counter_ordered = sorted(self.message_counter.items(), key=itemgetter(1), reverse=True) report = "\nIssues by ID:" diff --git a/robocop/rules.py b/robocop/rules.py index 7f382ab1d..2fcacc053 100644 --- a/robocop/rules.py +++ b/robocop/rules.py @@ -26,10 +26,12 @@ """ +from __future__ import annotations + from enum import Enum from functools import total_ordering from textwrap import dedent -from typing import Any, Callable, Dict, Optional, Pattern, Union +from typing import TYPE_CHECKING, Any, Callable, Optional from jinja2 import Template @@ -38,6 +40,9 @@ from robocop.utils.misc import str2bool from robocop.utils.version_matching import VersionSpecifier +if TYPE_CHECKING: + from re import Pattern + @total_ordering class RuleSeverity(Enum): @@ -65,7 +70,7 @@ class RuleSeverity(Enum): ERROR = "E" @classmethod - def parser(cls, value: Union[str, "RuleSeverity"], rule_severity=True) -> "RuleSeverity": + def parser(cls, value: str | RuleSeverity, rule_severity=True) -> RuleSeverity: # parser can be invoked from Rule() with severity=RuleSeverity.WARNING (enum directly) or # from configuration with severity:W (string representation) severity = { @@ -135,7 +140,7 @@ class RuleParam: Each rule can have number of parameters (default one is severity). """ - def __init__(self, name: str, default: Any, converter: Callable, desc: str, show_type: Optional[str] = None): + def __init__(self, name: str, default: Any, converter: Callable, desc: str, show_type: str | None = None): """ :param name: Name of the parameter used when configuring rule (also displayed in the docs) :param default: Default value of the parameter @@ -153,9 +158,9 @@ def __init__(self, name: str, default: Any, converter: Callable, desc: str, show self.value = default def __str__(self): - s = f"{self.name} = {self.raw_value}\n" f" type: {self.converter.__name__}" + s = f"{self.name} = {self.raw_value}\n type: {self.converter.__name__}" if self.desc: - s += "\n" f" info: {self.desc}" + s += f"\n info: {self.desc}" return s @property @@ -283,14 +288,14 @@ class Rule: def __init__( self, - *params: Union[RuleParam, SeverityThreshold], + *params: RuleParam | SeverityThreshold, rule_id: str, name: str, msg: str, severity: RuleSeverity, version: str = None, docs: str = "", - added_in_version: Optional[str] = None, + added_in_version: str | None = None, enabled: bool = True, deprecated: bool = False, ): @@ -385,7 +390,7 @@ def supported_in_rf_version(version: str) -> bool: return all(ROBOT_VERSION in VersionSpecifier(condition) for condition in version.split(";")) @staticmethod - def get_template(msg: str) -> Optional[Template]: + def get_template(msg: str) -> Template | None: if "{" in msg: return Template(msg) return None @@ -457,8 +462,8 @@ def prepare_message( overwrite_severity=severity, ) - def matches_pattern(self, pattern: Union[str, Pattern]): - """check if this rule matches given pattern""" + def matches_pattern(self, pattern: str | Pattern): + """Check if this rule matches given pattern""" if isinstance(pattern, str): return pattern in (self.name, self.rule_id) return pattern.match(self.name) or pattern.match(self.rule_id) @@ -511,7 +516,7 @@ def get_severity(overwrite_severity, rule, sev_threshold_value): def get_fullname(self) -> str: return f"{self.severity.value}{self.rule_id} ({self.name})" - def to_json(self) -> Dict: + def to_json(self) -> dict: return { "source": self.source, "line": self.line, diff --git a/robocop/run.py b/robocop/run.py index 4814a88f7..83442418f 100644 --- a/robocop/run.py +++ b/robocop/run.py @@ -3,7 +3,6 @@ import os import sys from collections import Counter -from typing import List from robot.api import get_resource_model from robot.errors import DataError @@ -145,7 +144,7 @@ def run_check(self, ast_model, filename, source=None): ] return found_issues - def run_project_checks(self) -> List: + def run_project_checks(self) -> list: found_issues = [] for checker in self.checkers: if not checker.disabled and isinstance(checker, checkers.ProjectChecker): @@ -200,7 +199,7 @@ def list_checkers(self): else: _, params = rule.available_configurables(include_severity=False) if params: - print(f"{rule}\n" f" {params}") + print(f"{rule}\n {params}") severity_counter[rule.severity.value] += 1 configurable_rules_sum = sum(severity_counter.values()) plural = get_plural_form(configurable_rules_sum) diff --git a/robocop/utils/disablers.py b/robocop/utils/disablers.py index 9902255f4..36436b295 100644 --- a/robocop/utils/disablers.py +++ b/robocop/utils/disablers.py @@ -1,9 +1,10 @@ """Collection of classes for detecting checker disablers (like # robocop: disable) in robot files""" +from __future__ import annotations + import re from collections import defaultdict from copy import deepcopy -from typing import List, Optional from robot.api import Token from robot.parsing.model.blocks import CommentSection @@ -17,7 +18,7 @@ class DisablersInFile: # pylint: disable=too-few-public-methods """Container for file disablers""" - def __init__(self, blocks: Optional[List] = None): + def __init__(self, blocks: list | None = None): self.lastblock = -1 self.lines = set() self.blocks = blocks if blocks else [] diff --git a/robocop/utils/file_types.py b/robocop/utils/file_types.py index e5d553404..073394495 100644 --- a/robocop/utils/file_types.py +++ b/robocop/utils/file_types.py @@ -33,7 +33,7 @@ class FileType(Enum): INIT = "init" def get_parser(self): - """return parser (method) for given model type""" + """Return parser (method) for given model type""" return { "resource": get_resource_model, "general": get_model, diff --git a/robocop/utils/misc.py b/robocop/utils/misc.py index 3ddfba072..268e18b73 100644 --- a/robocop/utils/misc.py +++ b/robocop/utils/misc.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ast import difflib import re @@ -6,8 +8,8 @@ from collections import Counter, defaultdict, namedtuple from io import StringIO from pathlib import Path +from re import Pattern from tokenize import generate_tokens -from typing import Dict, List, Optional, Pattern, Tuple import platformdirs from robot.api import Token @@ -62,7 +64,7 @@ def rf_supports_lang(): return ROBOT_VERSION >= ROBOT_WITH_LANG -def normalize_robot_name(name: str, remove_prefix: Optional[str] = None) -> str: +def normalize_robot_name(name: str, remove_prefix: str | None = None) -> str: name = name.replace(" ", "").replace("_", "").lower() if name else "" if remove_prefix: return name[name.startswith(remove_prefix) and len(remove_prefix) :] @@ -101,7 +103,7 @@ def token_col(node, *token_type) -> int: return token.col_offset + 1 -def issues_to_lsp_diagnostic(issues) -> List[Dict]: +def issues_to_lsp_diagnostic(issues) -> list[dict]: return [ { "range": { @@ -288,8 +290,8 @@ def remove_robot_vars(name: str) -> str: return replaced -def find_robot_vars(name: str) -> List[Tuple[int, int]]: - """return list of tuples with (start, end) pos of vars in name""" +def find_robot_vars(name: str) -> list[tuple[int, int]]: + """Return list of tuples with (start, end) pos of vars in name""" var_start = set("$@%&") brackets = 0 index = 0 diff --git a/robocop/utils/run_keywords.py b/robocop/utils/run_keywords.py index b4e8851f8..dbccb5bb8 100644 --- a/robocop/utils/run_keywords.py +++ b/robocop/utils/run_keywords.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from __future__ import annotations from robocop.utils.misc import normalize_robot_name @@ -8,7 +8,7 @@ def __init__( self, name: str, resolve: int = 1, - branches: Optional[List] = None, + branches: list | None = None, split_on_and: bool = False, prefix: str = "builtin", ): @@ -20,7 +20,7 @@ def __init__( class RunKeywords(dict): - def __init__(self, keywords: List[RunKeywordVariant]): + def __init__(self, keywords: list[RunKeywordVariant]): normalized_keywords = {} for keyword_variant in keywords: normalized_name = normalize_robot_name(keyword_variant.name) diff --git a/robocop/utils/variable_matcher.py b/robocop/utils/variable_matcher.py index 1e821e086..a18ded906 100644 --- a/robocop/utils/variable_matcher.py +++ b/robocop/utils/variable_matcher.py @@ -3,7 +3,7 @@ try: from robot.variables import VariableMatches except ImportError: - from typing import Iterator, Sequence + from collections.abc import Iterator, Sequence class VariableMatches: def __init__(self, string: str, identifiers: Sequence[str] = "$@&%", ignore_errors: bool = False): diff --git a/robocop/utils/version_matching.py b/robocop/utils/version_matching.py index 9bc86ce25..cc7d019cf 100644 --- a/robocop/utils/version_matching.py +++ b/robocop/utils/version_matching.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import itertools import re from functools import total_ordering -from typing import List, Optional, SupportsInt, Tuple, Union +from typing import SupportsInt VERSION_PATTERN = r""" v? @@ -24,7 +26,7 @@ """ -def _get_comparison_key(release: Tuple[int, ...]): +def _get_comparison_key(release: tuple[int, ...]): # When we compare a release version, we want to compare it with all the # trailing zeros removed. So we'll use a reverse the list, drop all the now # leading zeros until we come to something non zero, then take the rest @@ -33,7 +35,7 @@ def _get_comparison_key(release: Tuple[int, ...]): return tuple(reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))) -def _parse_letter_version(letter: str, number: Union[str, bytes, SupportsInt]) -> Optional[Tuple[str, int]]: +def _parse_letter_version(letter: str, number: str | bytes | SupportsInt) -> tuple[str, int] | None: if letter: # We consider there to be an implicit 0 in a pre-release if there is # not a numeral associated with it. @@ -118,7 +120,7 @@ def _pad_version(left, right): def _version_split(version: str): - result: List[str] = [] + result: list[str] = [] for item in version.split("."): match = _prefix_regex.search(item) if match: @@ -195,7 +197,7 @@ def __init__(self, spec: str = "") -> None: if not match: raise ValueError(f"Invalid specifier: '{spec}'") - self._spec: Tuple[str, str] = ( + self._spec: tuple[str, str] = ( match.group("operator").strip(), match.group("version").strip(), ) diff --git a/tests/atest/rules/custom_tests/project_checker/external_project_checker/__init__.py b/tests/atest/rules/custom_tests/project_checker/external_project_checker/__init__.py index 94d1e8805..22da3ad45 100644 --- a/tests/atest/rules/custom_tests/project_checker/external_project_checker/__init__.py +++ b/tests/atest/rules/custom_tests/project_checker/external_project_checker/__init__.py @@ -1,5 +1,3 @@ -from typing import List - from robocop.checkers import ProjectChecker from robocop.rules import Message, Rule, RuleSeverity @@ -36,7 +34,7 @@ def visit_File(self, node): # noqa: N802 def visit_TestCase(self, node): # noqa: N802 self.test_count += 1 - def scan_project(self) -> List[Message]: + def scan_project(self) -> list[Message]: self.issues = [] for source in self.sources: self.report("project-checker", source=source) diff --git a/tests/atest/utils/__init__.py b/tests/atest/utils/__init__.py index 5f433989b..e3cd05968 100644 --- a/tests/atest/utils/__init__.py +++ b/tests/atest/utils/__init__.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import contextlib import io import os import re import sys from pathlib import Path -from typing import List, Optional, Union import pytest @@ -55,7 +56,7 @@ def load_expected_file(test_data, expected_file): ) -def configure_robocop_with_rule(args, runner, rule, path, src_files: Optional[List], format): +def configure_robocop_with_rule(args, runner, rule, path, src_files: list | None, format): runner.from_cli = True config = Config() if src_files is None: @@ -90,11 +91,11 @@ class RuleAcceptance: def check_rule( self, - expected_file: Optional[str] = None, - config: Optional[str] = None, - rule: Optional[str] = None, - src_files: Optional[List] = None, - target_version: Optional[Union[str, List[str]]] = None, + expected_file: str | None = None, + config: str | None = None, + rule: str | None = None, + src_files: list | None = None, + target_version: str | list[str] | None = None, issue_format: str = "default", deprecated: bool = False, ): @@ -148,7 +149,7 @@ def rule_is_enabled(self, robocop_rules): return robocop_rules[self.rule_name].enabled_in_version @staticmethod - def enabled_in_version(target_version: Optional[Union["list", str]]): + def enabled_in_version(target_version: list | str | None): """ Check if rule is enabled for given target version condition.