aboutsummaryrefslogtreecommitdiff
path: root/tests/builder
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-16 17:31:49 +0530
committernatsuoto <[email protected]>2026-07-16 17:31:49 +0530
commit1b43c8dc09adb9b54609cc8a1916c99eb6fe75c9 (patch)
tree7731af50ed9117f8ac3d200591c3be23818d7b19 /tests/builder
parentb7ae2f3885d5ff84237c9da8bc0c64fe1a509465 (diff)
downloadedify-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.py11
-rw-r--r--tests/builder/cache.test.py3
-rw-r--r--tests/builder/diagnose.test.py7
-rw-r--r--tests/builder/engine.test.py6
-rw-r--r--tests/builder/flags.test.py9
-rw-r--r--tests/builder/lookbehind.test.py4
-rw-r--r--tests/builder/properties.test.py65
-rw-r--r--tests/builder/reverse.test.py4
-rw-r--r--tests/builder/validation.test.py32
-rw-r--r--tests/builder/varargs.test.py6
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)