Skip to content

Commit 80cdb33

Browse files
committed
debt: Extend Python linting rules
1 parent b7c3ec8 commit 80cdb33

24 files changed

+378
-198
lines changed

python/.pre-commit-config.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: v0.13.0
3+
rev: v0.14.0
44
hooks:
55
- id: ruff-check
66
args: [--fix]
77
- id: ruff-format
8+
- repo: https://github.com/astral-sh/uv-pre-commit
9+
rev: 0.9.1
10+
hooks:
11+
# Ensure the uv lock file is up to date
12+
- id: uv-lock
13+
files: pyproject.toml
14+
args: [--project, python]

python/pyproject.toml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ classifiers = [
2222
"Intended Audience :: Developers",
2323
"Operating System :: OS Independent",
2424
"Programming Language :: Python :: 3",
25-
"Programming Language :: Python :: 3.8",
2625
"Programming Language :: Python :: 3.9",
2726
"Programming Language :: Python :: 3.10",
2827
"Programming Language :: Python :: 3.11",
2928
"Programming Language :: Python :: 3.12",
29+
"Programming Language :: Python :: 3.13",
30+
"Programming Language :: Python :: 3.14",
3031
"Programming Language :: Python :: Implementation :: CPython",
3132
"Programming Language :: Python :: Implementation :: PyPy",
3233
"Topic :: Software Development :: Testing",
@@ -43,7 +44,24 @@ Changelog = "https://github.com/cucumber/cucumber-expressions/releases"
4344

4445
[tool.ruff.lint]
4546
extend-select = [
47+
"B",
48+
"C4",
49+
"COM",
50+
"FURB",
4651
"I",
52+
"ISC",
53+
"N",
54+
"PERF",
55+
"PIE",
56+
"RET",
57+
"TID",
58+
"UP",
59+
]
60+
ignore = [
61+
"B023",
62+
"COM812",
63+
"N818",
64+
"PERF401",
4765
]
4866

4967
[dependency-groups]

python/src/cucumber_expressions/argument.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import annotations
22

3-
from typing import List, Optional
4-
53
from cucumber_expressions.errors import CucumberExpressionError
64
from cucumber_expressions.group import Group
75
from cucumber_expressions.parameter_type import ParameterType
@@ -15,8 +13,10 @@ def __init__(self, group, parameter_type):
1513

1614
@staticmethod
1715
def build(
18-
tree_regexp: TreeRegexp, text: str, parameter_types: List
19-
) -> Optional[List[Argument]]:
16+
tree_regexp: TreeRegexp,
17+
text: str,
18+
parameter_types: list,
19+
) -> list[Argument] | None:
2020
match_group = tree_regexp.match(text)
2121
if not match_group:
2222
return None
@@ -25,7 +25,7 @@ def build(
2525

2626
if len(arg_groups) != len(parameter_types):
2727
raise CucumberExpressionError(
28-
f"Group has {len(arg_groups)} capture groups, but there were {len(parameter_types)} parameter types"
28+
f"Group has {len(arg_groups)} capture groups, but there were {len(parameter_types)} parameter types",
2929
)
3030

3131
return [

python/src/cucumber_expressions/ast.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from enum import Enum
4-
from typing import List, Optional
54

65

76
class NodeType(Enum):
@@ -41,8 +40,8 @@ class Node:
4140
def __init__(
4241
self,
4342
ast_type: NodeType,
44-
nodes: Optional[List[Node]],
45-
token: Optional[str],
43+
nodes: [list[Node]] | None,
44+
token: str | None,
4645
start: int,
4746
end: int,
4847
):
@@ -59,7 +58,7 @@ def ast_type(self) -> NodeType:
5958
return self._ast_type
6059

6160
@property
62-
def nodes(self) -> List[Node]:
61+
def nodes(self) -> list[Node]:
6362
return self._nodes
6463

6564
@property
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import List
2-
31
from cucumber_expressions.generated_expression import GeneratedExpression
42
from cucumber_expressions.parameter_type import ParameterType
53

@@ -12,22 +10,22 @@ def __init__(self, expression_template, parameter_type_combinations):
1210
self.expression_template = expression_template
1311
self.parameter_type_combinations = parameter_type_combinations
1412

15-
def generate_expressions(self) -> List[GeneratedExpression]:
13+
def generate_expressions(self) -> list[GeneratedExpression]:
1614
generated_expressions = []
1715
self.generate_permutations(generated_expressions, 0, [])
1816
return generated_expressions
1917

2018
def generate_permutations(
2119
self,
22-
generated_expressions: List[GeneratedExpression],
20+
generated_expressions: list[GeneratedExpression],
2321
depth: int,
24-
current_parameter_types: List[ParameterType],
22+
current_parameter_types: list[ParameterType],
2523
):
2624
if len(generated_expressions) >= MAX_EXPRESSIONS:
2725
return
2826
if depth == len(self.parameter_type_combinations):
2927
generated_expressions.append(
30-
GeneratedExpression(self.expression_template, current_parameter_types)
28+
GeneratedExpression(self.expression_template, current_parameter_types),
3129
)
3230
return
3331
for parameter_type_combination in self.parameter_type_combinations[depth]:
@@ -36,5 +34,7 @@ def generate_permutations(
3634
new_current_parameter_types = current_parameter_types.copy()
3735
new_current_parameter_types.append(parameter_type_combination)
3836
self.generate_permutations(
39-
generated_expressions, depth + 1, new_current_parameter_types
37+
generated_expressions,
38+
depth + 1,
39+
new_current_parameter_types,
4040
)

python/src/cucumber_expressions/errors.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33

44
def generate_message(
5-
index: int, expression: str, pointer: str, problem: str, solution: str
5+
index: int,
6+
expression: str,
7+
pointer: str,
8+
problem: str,
9+
solution: str,
610
):
711
return "\n".join(
812
[
@@ -12,7 +16,7 @@ def generate_message(
1216
pointer,
1317
problem + ".",
1418
solution,
15-
]
19+
],
1620
)
1721

1822

@@ -43,7 +47,7 @@ def __init__(self, node, expression: str):
4347
pointer=point_at_located(node),
4448
problem="An alternative may not exclusively contain optionals",
4549
solution="If you did not mean to use an optional you can use '\\(' to escape the '('",
46-
)
50+
),
4751
)
4852

4953

@@ -56,7 +60,7 @@ def __init__(self, node, expression: str):
5660
pointer=point_at_located(node),
5761
problem="Alternative may not be empty",
5862
solution="If you did not mean to use an alternative you can use '\\/' to escape the '/'",
59-
)
63+
),
6064
)
6165

6266

@@ -69,7 +73,7 @@ def __init__(self, expression: str, index: int):
6973
pointer=point_at(index),
7074
problem="Only the characters '{', '}', '(', ')', '\\', '/' and whitespace can be escaped",
7175
solution="If you did mean to use an '\\' you can use '\\\\' to escape it",
72-
)
76+
),
7377
)
7478

7579

@@ -82,7 +86,7 @@ def __init__(self, node, expression: str):
8286
pointer=point_at_located(node),
8387
problem="An optional must contain some text",
8488
solution="If you did not mean to use an optional you can use '\\(' to escape the '('",
85-
)
89+
),
8690
)
8791

8892

@@ -95,7 +99,7 @@ def __init__(self, node, expression: str):
9599
pointer=point_at_located(node),
96100
problem="An optional may not contain a parameter type",
97101
solution="If you did not mean to use an parameter type you can use '\\{' to escape the '{'",
98-
)
102+
),
99103
)
100104

101105

@@ -111,7 +115,7 @@ def __init__(self, node, expression: str):
111115
"If you did not mean to use an optional type you can use '\\(' to escape the '('. "
112116
"For more complicated expressions consider using a regular expression instead."
113117
),
114-
)
118+
),
115119
)
116120

117121

@@ -128,7 +132,7 @@ def __init__(self, expression: str, begin_token, end_token, current):
128132
pointer=point_at_located(current),
129133
problem=f"The '{begin_symbol}' does not have a matching '{end_symbol}'",
130134
solution=f"If you did not intend to use {purpose} you can use '\\{begin_symbol}' to escape the {purpose}",
131-
)
135+
),
132136
)
133137

134138

@@ -144,7 +148,7 @@ def __init__(self, expression: str, current):
144148
"If you did not mean to use an alternation you can use '\\/' to escape the '/'. "
145149
"Otherwise rephrase your expression or consider using a regular expression instead."
146150
),
147-
)
151+
),
148152
)
149153

150154

@@ -154,7 +158,7 @@ def __init__(self, type_name: str):
154158
"Illegal character in parameter name {"
155159
+ type_name
156160
+ "}. "
157-
+ "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'"
161+
+ "Parameter names may not contain '{', '}', '(', ')', '\\' or '/'",
158162
)
159163

160164

@@ -167,7 +171,7 @@ def __init__(self, expression: str, token):
167171
pointer=point_at_located(token),
168172
problem="Parameter names may not contain '{', '}', '(', ')', '\\' or '/'",
169173
solution="Did you mean to use a regular expression?",
170-
)
174+
),
171175
)
172176

173177

@@ -180,7 +184,7 @@ def __init__(self, node, expression: str, undefined_parameter_type_name: str):
180184
pointer=point_at_located(node),
181185
problem=f"Undefined parameter type '{undefined_parameter_type_name}'",
182186
solution=f"Please register a ParameterType for '{undefined_parameter_type_name}'",
183-
)
187+
),
184188
)
185189

186190

@@ -224,5 +228,5 @@ def __init__(self, expression: str):
224228
pointer=point_at(index),
225229
problem="The end of line can not be escaped",
226230
solution="You can use '\\\\' to escape the '\\'",
227-
)
231+
),
228232
)

python/src/cucumber_expressions/expression.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import List, Optional
2-
31
from cucumber_expressions.argument import Argument
42
from cucumber_expressions.ast import Node, NodeType
53
from cucumber_expressions.errors import (
@@ -21,12 +19,12 @@ class CucumberExpression:
2119
def __init__(self, expression, parameter_type_registry):
2220
self.expression = expression
2321
self.parameter_type_registry = parameter_type_registry
24-
self.parameter_types: List[ParameterType] = []
22+
self.parameter_types: list[ParameterType] = []
2523
self.tree_regexp = TreeRegexp(
26-
self.rewrite_to_regex(CucumberExpressionParser().parse(self.expression))
24+
self.rewrite_to_regex(CucumberExpressionParser().parse(self.expression)),
2725
)
2826

29-
def match(self, text: str) -> Optional[List[Argument]]:
27+
def match(self, text: str) -> list[Argument] | None:
3028
return Argument.build(self.tree_regexp, text, self.parameter_types)
3129

3230
@property
@@ -61,12 +59,14 @@ def rewrite_optional(self, node: Node):
6159
_possible_node_with_params = self.get_possible_node_with_parameters(node)
6260
if _possible_node_with_params:
6361
raise ParameterIsNotAllowedInOptional(
64-
_possible_node_with_params, self.expression
62+
_possible_node_with_params,
63+
self.expression,
6564
)
6665
_possible_node_with_optionals = self.get_possible_node_with_optionals(node)
6766
if _possible_node_with_optionals:
6867
raise OptionalIsNotAllowedInOptional(
69-
_possible_node_with_optionals, self.expression
68+
_possible_node_with_optionals,
69+
self.expression,
7070
)
7171
if self.are_nodes_empty(node):
7272
raise OptionalMayNotBeEmpty(node, self.expression)
@@ -79,7 +79,8 @@ def rewrite_alternation(self, node: Node):
7979
raise AlternativeMayNotBeEmpty(alternative, self.expression)
8080
if self.are_nodes_empty(alternative):
8181
raise AlternativeMayNotExclusivelyContainOptionals(
82-
alternative, self.expression
82+
alternative,
83+
self.expression,
8384
)
8485
regex = "|".join([self.rewrite_to_regex(_node) for _node in node.nodes])
8586
return f"(?:{regex})"
@@ -108,14 +109,14 @@ def rewrite_expression(self, node: Node):
108109
def are_nodes_empty(self, node: Node) -> bool:
109110
return not any(self.get_nodes_with_ast_type(node, NodeType.TEXT))
110111

111-
def get_possible_node_with_parameters(self, node: Node) -> Optional[Node]:
112+
def get_possible_node_with_parameters(self, node: Node) -> Node | None:
112113
results = self.get_nodes_with_ast_type(node, NodeType.PARAMETER)
113114
return results[0] if results else None
114115

115-
def get_possible_node_with_optionals(self, node: Node) -> Optional[Node]:
116+
def get_possible_node_with_optionals(self, node: Node) -> Node | None:
116117
results = self.get_nodes_with_ast_type(node, NodeType.OPTIONAL)
117118
return results[0] if results else None
118119

119120
@staticmethod
120-
def get_nodes_with_ast_type(node: Node, ast_type: NodeType) -> List[Node]:
121+
def get_nodes_with_ast_type(node: Node, ast_type: NodeType) -> list[Node]:
121122
return [ast_node for ast_node in node.nodes if ast_node.ast_type == ast_type]

0 commit comments

Comments
 (0)