Skip to content

Commit

Permalink
Fix typing issues from ruff (#1136)
Browse files Browse the repository at this point in the history
* 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 -> |
  • Loading branch information
bhirsz authored Oct 29, 2024
1 parent 0cca6d0 commit 9112686
Show file tree
Hide file tree
Showing 22 changed files with 116 additions and 105 deletions.
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
]
Expand All @@ -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*"]
Expand Down
28 changes: 15 additions & 13 deletions robocop/checkers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand All @@ -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:
Expand All @@ -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)


Expand Down Expand Up @@ -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 {}
Expand All @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 1 addition & 3 deletions robocop/checkers/community_rules/keywords.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(",")}
Expand Down
22 changes: 12 additions & 10 deletions robocop/checkers/community_rules/usage.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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]:
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
3 changes: 1 addition & 2 deletions robocop/checkers/lengths.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
14 changes: 7 additions & 7 deletions robocop/checkers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(",")


Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions robocop/checkers/naming.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 = {
Expand Down Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions robocop/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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"}
Expand Down
Loading

0 comments on commit 9112686

Please sign in to comment.