diff options
| author | natsuoto <[email protected]> | 2026-07-16 17:31:49 +0530 |
|---|---|---|
| committer | natsuoto <[email protected]> | 2026-07-16 17:31:49 +0530 |
| commit | 1b43c8dc09adb9b54609cc8a1916c99eb6fe75c9 (patch) | |
| tree | 7731af50ed9117f8ac3d200591c3be23818d7b19 /tests/builder | |
| parent | b7ae2f3885d5ff84237c9da8bc0c64fe1a509465 (diff) | |
| download | edify-1b43c8dc09adb9b54609cc8a1916c99eb6fe75c9.tar.xz edify-1b43c8dc09adb9b54609cc8a1916c99eb6fe75c9.zip | |
chore: enforce fully strict static typing across library, tests, and tooling with zero suppressions
Diffstat (limited to 'tests/builder')
| -rw-r--r-- | tests/builder/builder.test.py | 11 | ||||
| -rw-r--r-- | tests/builder/cache.test.py | 3 | ||||
| -rw-r--r-- | tests/builder/diagnose.test.py | 7 | ||||
| -rw-r--r-- | tests/builder/engine.test.py | 6 | ||||
| -rw-r--r-- | tests/builder/flags.test.py | 9 | ||||
| -rw-r--r-- | tests/builder/lookbehind.test.py | 4 | ||||
| -rw-r--r-- | tests/builder/properties.test.py | 65 | ||||
| -rw-r--r-- | tests/builder/reverse.test.py | 4 | ||||
| -rw-r--r-- | tests/builder/validation.test.py | 32 | ||||
| -rw-r--r-- | tests/builder/varargs.test.py | 6 |
10 files changed, 60 insertions, 87 deletions
diff --git a/tests/builder/builder.test.py b/tests/builder/builder.test.py index 5f96b5c..7f58d47 100644 --- a/tests/builder/builder.test.py +++ b/tests/builder/builder.test.py @@ -5,7 +5,7 @@ import pytest from edify import RegexBuilder from edify.errors.anchors import StartInputAlreadyDefinedError from edify.errors.captures import InvalidTotalCaptureGroupsIndexError -from edify.errors.input import MustBeInstanceError, MustBeSingleCharacterError +from edify.errors.input import MustBeSingleCharacterError from edify.errors.naming import ( CannotCreateDuplicateNamedGroupError, NamedGroupDoesNotExistError, @@ -39,13 +39,13 @@ first_layer_se = ( ) -def regex_equality(regex, rb_expression): +def regex_equality(regex: str, rb_expression: RegexBuilder) -> None: regex_str = str(regex) rb_expression_str = rb_expression.to_regex_string() assert regex_str == str(rb_expression_str) -def regex_compilation(regex, rb_expression, f=0): +def regex_compilation(regex: str, rb_expression: RegexBuilder, f: int = 0) -> None: rb_expression_c = rb_expression.to_regex() assert re.compile(regex, flags=f) == rb_expression_c.compiled @@ -458,11 +458,6 @@ def test_range(): regex_compilation("[a-z]", expr) -def test_must_be_instance_error(): - with pytest.raises(MustBeInstanceError): - RegexBuilder().subexpression("nope") - - def test_simple_se(): expr = ( RegexBuilder() diff --git a/tests/builder/cache.test.py b/tests/builder/cache.test.py index 045e8e9..4211698 100644 --- a/tests/builder/cache.test.py +++ b/tests/builder/cache.test.py @@ -1,6 +1,7 @@ """Tests for the per-instance lazy compile cache — semantics and cold/warm ratio.""" import time +from collections.abc import Callable from edify import Pattern, RegexBuilder @@ -21,7 +22,7 @@ def _hex_number_builder(): ) -def _measure_call_wall_clock(action, iterations): +def _measure_call_wall_clock(action: Callable[[], object], iterations: int) -> float: start = time.perf_counter() for _ in range(iterations): action() diff --git a/tests/builder/diagnose.test.py b/tests/builder/diagnose.test.py index 88c4f03..8b9e4dc 100644 --- a/tests/builder/diagnose.test.py +++ b/tests/builder/diagnose.test.py @@ -6,13 +6,6 @@ from edify import Pattern, RegexBuilder from edify.errors.comparison import CannotCompareUnfinishedBuilderError -def _first_pointer_hint(text: str) -> str | None: - for line in text.splitlines(): - if "->" in line and ".py:" in line: - return line - return None - - def test_two_finished_builders_compare_equal_without_diagnostic(): left = RegexBuilder().digit() right = RegexBuilder().digit() diff --git a/tests/builder/engine.test.py b/tests/builder/engine.test.py index c6a32d1..9fddeb4 100644 --- a/tests/builder/engine.test.py +++ b/tests/builder/engine.test.py @@ -32,7 +32,7 @@ def test_engine_regex_and_engine_re_are_not_equal_for_the_same_source(): assert left != right -def test_engine_regex_raises_clean_import_error_without_the_extra(monkeypatch): +def test_engine_regex_raises_clean_import_error_without_the_extra(monkeypatch: pytest.MonkeyPatch): monkeypatch.setitem(sys.modules, "regex", None) with pytest.raises(MissingRegexBackendError, match="engine='regex'") as excinfo: RegexBuilder().digit().to_regex(engine="regex") @@ -41,7 +41,9 @@ def test_engine_regex_raises_clean_import_error_without_the_extra(monkeypatch): assert "= note:" in text -def test_missing_regex_backend_error_chains_from_underlying_import_error(monkeypatch): +def test_missing_regex_backend_error_chains_from_underlying_import_error( + monkeypatch: pytest.MonkeyPatch, +): monkeypatch.setitem(sys.modules, "regex", None) with pytest.raises(MissingRegexBackendError) as excinfo: RegexBuilder().digit().to_regex(engine="regex") diff --git a/tests/builder/flags.test.py b/tests/builder/flags.test.py index 704065f..03b79f4 100644 --- a/tests/builder/flags.test.py +++ b/tests/builder/flags.test.py @@ -2,6 +2,7 @@ import re import sys +from typing import cast import pytest import regex as regex_module @@ -112,13 +113,15 @@ def test_regex_engine_debug_kwarg_compiles_without_error(): assert compiled.source == "hi" -def test_regex_engine_debug_flag_is_forwarded_to_regex_module_bitmask(monkeypatch): +def test_regex_engine_debug_flag_is_forwarded_to_regex_module_bitmask( + monkeypatch: pytest.MonkeyPatch, +): captured_flags: list[int] = [] original_compile = regex_module.compile - def capturing_compile(pattern, flags=0): + def capturing_compile(pattern: str, flags: int = 0) -> re.Pattern[str]: captured_flags.append(flags) - return original_compile(pattern) + return cast("re.Pattern[str]", original_compile(pattern)) monkeypatch.setattr(regex_module, "compile", capturing_compile) RegexBuilder().string("hi").to_regex(engine="regex", debug=True) diff --git a/tests/builder/lookbehind.test.py b/tests/builder/lookbehind.test.py index 5e101f3..c9a2235 100644 --- a/tests/builder/lookbehind.test.py +++ b/tests/builder/lookbehind.test.py @@ -47,8 +47,8 @@ def test_variable_width_lookbehind_error_chains_the_underlying_pattern_error(): assert isinstance(excinfo.value.__cause__, re.error) -def test_re_engine_still_surfaces_other_pattern_errors_unchanged(monkeypatch): - def raise_other_error(_pattern, flags=0): +def test_re_engine_still_surfaces_other_pattern_errors_unchanged(monkeypatch: pytest.MonkeyPatch): + def raise_other_error(_pattern: str, flags: int = 0) -> re.Pattern[str]: raise re.error("some other syntax error") monkeypatch.setattr(re, "compile", raise_other_error) diff --git a/tests/builder/properties.test.py b/tests/builder/properties.test.py index 87770fa..5bd9c0c 100644 --- a/tests/builder/properties.test.py +++ b/tests/builder/properties.test.py @@ -12,7 +12,7 @@ from dataclasses import dataclass from hypothesis import given from hypothesis import strategies as st -from edify import Pattern, RegexBuilder +from edify import RegexBuilder @dataclass(frozen=True) @@ -29,28 +29,31 @@ class LeafNode: class GroupNode: """A non-capturing group wrapping ``children``.""" - children: tuple[object, ...] + children: tuple[_Node, ...] @dataclass(frozen=True) class CaptureNode: """An unnamed capture group wrapping ``children``.""" - children: tuple[object, ...] + children: tuple[_Node, ...] @dataclass(frozen=True) class NamedCaptureNode: """A named-capture group wrapping ``children``; the name is assigned deterministically.""" - children: tuple[object, ...] + children: tuple[_Node, ...] @dataclass(frozen=True) class SubexpressionNode: """A subexpression built as a separate ``Pattern`` and merged into the parent.""" - children: tuple[object, ...] + children: tuple[_Node, ...] + + +_Node = LeafNode | GroupNode | CaptureNode | NamedCaptureNode | SubexpressionNode _QUANTIFIER_STRATEGIES: list[st.SearchStrategy[tuple[str, tuple[int, ...], str]]] = [ @@ -121,10 +124,10 @@ _node_strategy = st.recursive(_leaf_node_strategy, _extend, max_leaves=6) def _apply_sequence( - builder, - nodes: list[object], + builder: RegexBuilder, + nodes: list[_Node], name_counter: int, -) -> tuple[object, str, int]: +) -> tuple[RegexBuilder, str, int]: """Apply ``nodes`` to ``builder`` in order; return the updated triple.""" fragments: list[str] = [] for node in nodes: @@ -134,7 +137,9 @@ def _apply_sequence( return builder, combined_regex, name_counter -def _apply_node(builder, node, name_counter: int) -> tuple[object, str, int]: +def _apply_node( + builder: RegexBuilder, node: _Node, name_counter: int +) -> tuple[RegexBuilder, str, int]: """Dispatch on ``node`` type, applying it to ``builder`` and returning the fragment.""" if isinstance(node, LeafNode): return _apply_leaf(builder, node, name_counter) @@ -147,7 +152,9 @@ def _apply_node(builder, node, name_counter: int) -> tuple[object, str, int]: return _apply_subexpression(builder, node, name_counter) -def _apply_leaf(builder, node: LeafNode, name_counter: int) -> tuple[object, str, int]: +def _apply_leaf( + builder: RegexBuilder, node: LeafNode, name_counter: int +) -> tuple[RegexBuilder, str, int]: """Apply a single leaf element, optionally preceded by a quantifier.""" if node.quantifier is None: builder_after_element = getattr(builder, node.element_name)(*node.element_args) @@ -159,7 +166,9 @@ def _apply_leaf(builder, node: LeafNode, name_counter: int) -> tuple[object, str return builder_after_element, fragment, name_counter -def _apply_group(builder, node: GroupNode, name_counter: int) -> tuple[object, str, int]: +def _apply_group( + builder: RegexBuilder, node: GroupNode, name_counter: int +) -> tuple[RegexBuilder, str, int]: """Apply a non-capturing group around ``node.children``.""" builder_opened = builder.group() builder_inner, inner_regex, name_counter = _apply_sequence( @@ -169,7 +178,9 @@ def _apply_group(builder, node: GroupNode, name_counter: int) -> tuple[object, s return builder_closed, f"(?:{inner_regex})", name_counter -def _apply_capture(builder, node: CaptureNode, name_counter: int) -> tuple[object, str, int]: +def _apply_capture( + builder: RegexBuilder, node: CaptureNode, name_counter: int +) -> tuple[RegexBuilder, str, int]: """Apply an unnamed capture group around ``node.children``.""" builder_opened = builder.capture() builder_inner, inner_regex, name_counter = _apply_sequence( @@ -180,10 +191,10 @@ def _apply_capture(builder, node: CaptureNode, name_counter: int) -> tuple[objec def _apply_named_capture( - builder, + builder: RegexBuilder, node: NamedCaptureNode, name_counter: int, -) -> tuple[object, str, int]: +) -> tuple[RegexBuilder, str, int]: """Apply a named-capture group with a deterministically-assigned name.""" assigned_name = f"n{name_counter}" next_counter = name_counter + 1 @@ -196,35 +207,41 @@ def _apply_named_capture( def _apply_subexpression( - builder, + builder: RegexBuilder, node: SubexpressionNode, name_counter: int, -) -> tuple[object, str, int]: - """Apply a subexpression built as an independent ``Pattern`` and merged in.""" - sub_pattern = Pattern() - sub_pattern_finished, sub_regex, next_counter = _apply_sequence( - sub_pattern, list(node.children), name_counter +) -> tuple[RegexBuilder, str, int]: + """Apply a subexpression built as an independent builder and merged in.""" + sub_builder = RegexBuilder() + sub_finished, sub_regex, next_counter = _apply_sequence( + sub_builder, list(node.children), name_counter ) - builder_after_merge = builder.subexpression(sub_pattern_finished) + builder_after_merge = builder.subexpression(sub_finished) return builder_after_merge, sub_regex, next_counter @given(st.lists(_leaf_node_strategy, min_size=1, max_size=8)) -def test_bare_element_chain_emits_the_concatenation_of_element_fragments(nodes): +def test_bare_element_chain_emits_the_concatenation_of_element_fragments( + nodes: list[LeafNode], +) -> None: builder = RegexBuilder() builder_after, expected_regex, _ = _apply_sequence(builder, list(nodes), 0) assert builder_after.to_regex_string() == expected_regex @given(st.lists(_leaf_node_strategy, min_size=1, max_size=8)) -def test_every_quantifier_chain_call_produces_exactly_one_output_quantifier(nodes): +def test_every_quantifier_chain_call_produces_exactly_one_output_quantifier( + nodes: list[LeafNode], +) -> None: builder = RegexBuilder() builder_after, expected_regex, _ = _apply_sequence(builder, list(nodes), 0) assert builder_after.to_regex_string() == expected_regex @given(st.lists(_node_strategy, min_size=1, max_size=6)) -def test_composition_over_groups_captures_and_subexpressions_is_faithful(nodes): +def test_composition_over_groups_captures_and_subexpressions_is_faithful( + nodes: list[_Node], +) -> None: builder = RegexBuilder() builder_after, expected_regex, _ = _apply_sequence(builder, list(nodes), 0) assert builder_after.to_regex_string() == expected_regex diff --git a/tests/builder/reverse.test.py b/tests/builder/reverse.test.py index 1ed00ec..bbc7037 100644 --- a/tests/builder/reverse.test.py +++ b/tests/builder/reverse.test.py @@ -41,7 +41,7 @@ from edify.builder.reverse import UnsupportedReverseParseError ("(?<!x)y", "(?<!x)y"), ], ) -def test_from_regex_translates_common_constructs_faithfully(source, expected): +def test_from_regex_translates_common_constructs_faithfully(source: str, expected: str): builder = RegexBuilder.from_regex(source) assert builder.to_regex_string() == expected @@ -58,7 +58,7 @@ def test_from_regex_translates_common_constructs_faithfully(source, expected): r"(?=x)\w+", ], ) -def test_round_trip_compiled_regex_matches_the_same_inputs(source): +def test_round_trip_compiled_regex_matches_the_same_inputs(source: str): original = re.compile(source) reversed_builder = RegexBuilder.from_regex(source) reversed_compiled = reversed_builder.to_regex() diff --git a/tests/builder/validation.test.py b/tests/builder/validation.test.py index a1b57d7..a528aa2 100644 --- a/tests/builder/validation.test.py +++ b/tests/builder/validation.test.py @@ -13,8 +13,6 @@ from edify.errors.anchors import ( StartInputAlreadyDefinedError, ) from edify.errors.input import ( - MustBeAStringError, - MustBeInstanceError, MustBeIntegerGreaterThanZeroError, MustBeLessThanError, MustBeOneCharacterError, @@ -41,51 +39,26 @@ def test_end_of_input_twice_raises(): RegexBuilder().end_of_input().end_of_input() -def test_named_capture_non_string_raises(): - with pytest.raises(MustBeAStringError): - RegexBuilder().named_capture(42) - - def test_named_capture_empty_string_raises(): with pytest.raises(MustBeOneCharacterError): RegexBuilder().named_capture("") -def test_string_non_string_raises(): - with pytest.raises(MustBeAStringError): - RegexBuilder().string(42) - - def test_string_empty_raises(): with pytest.raises(MustBeOneCharacterError): RegexBuilder().string("") -def test_char_non_string_raises(): - with pytest.raises(MustBeAStringError): - RegexBuilder().char(42) - - def test_range_first_codepoint_not_less_than_second_raises(): with pytest.raises(MustHaveASmallerValueError): RegexBuilder().range("z", "a") -def test_anything_but_string_non_string_raises(): - with pytest.raises(MustBeAStringError): - RegexBuilder().anything_but_string(42) - - def test_anything_but_string_empty_raises(): with pytest.raises(MustBeOneCharacterError): RegexBuilder().anything_but_string("") -def test_anything_but_chars_non_string_raises(): - with pytest.raises(MustBeAStringError): - RegexBuilder().anything_but_chars(42) - - def test_anything_but_chars_empty_raises(): with pytest.raises(MustBeOneCharacterError): RegexBuilder().anything_but_chars("") @@ -151,8 +124,3 @@ def test_to_regex_with_open_frame_raises(): unfinished = RegexBuilder().capture().digit() with pytest.raises(CannotCallSubexpressionError): unfinished.to_regex() - - -def test_subexpression_non_builder_raises(): - with pytest.raises(MustBeInstanceError): - RegexBuilder().subexpression("not a builder") diff --git a/tests/builder/varargs.test.py b/tests/builder/varargs.test.py index 6a30636..3fb2254 100644 --- a/tests/builder/varargs.test.py +++ b/tests/builder/varargs.test.py @@ -4,7 +4,6 @@ import pytest from edify import Pattern, RegexBuilder from edify.errors.input import ( - MustBeAStringError, MustBeAtLeastOneLiteralError, MustBeOneCharacterError, ) @@ -65,8 +64,3 @@ def test_one_of_zero_args_raises_must_be_at_least_one_literal(): def test_any_of_varargs_rejects_empty_string_literal(): with pytest.raises(MustBeOneCharacterError): RegexBuilder().any_of("cat", "") - - -def test_one_of_rejects_non_string_literal(): - with pytest.raises(MustBeAStringError): - RegexBuilder().one_of("cat", 42) |
