1010from ethereum_spec_tools .forks import Hardfork
1111from ethereum_spec_tools .new_fork .cli import main as new_fork
1212
13+ import libcst as cst
14+ from libcst .codemod import CodemodContext
15+
16+ from ethereum_spec_tools .new_fork .builder import ClearDocstring , ForkBuilder
17+ from ethereum_spec_tools .new_fork .codemod .remove_docstring import (
18+ RemoveDocstringCommand ,
19+ )
20+
1321
1422@pytest .mark .parametrize (
1523 "template_fork" ,
@@ -56,10 +64,14 @@ def test_end_to_end(template_fork: str) -> None:
5664 with (fork_dir / "__init__.py" ).open ("r" ) as f :
5765 source = f .read ()
5866
67+ assert '"""' not in source [:20 ]
5968 assert "FORK_CRITERIA = ByTimestamp(7)" in source
60- assert "E2E Fork" in source
6169 assert template_fork .capitalize () not in source
6270
71+ with (fork_dir / "utils" / "hexadecimal.py" ).open ("r" ) as f :
72+ source = f .read ()
73+ assert "E2E Fork" in source
74+
6375 with (fork_dir / "vm" / "gas.py" ).open ("r" ) as f :
6476 source = f .read ()
6577
@@ -82,3 +94,76 @@ def test_end_to_end(template_fork: str) -> None:
8294 "from ethereum.forks.paris import trie as previous_trie"
8395 in f .read ()
8496 )
97+
98+
99+ def has_module_docstring (file_path : Path ) -> bool :
100+ """Return True if the file starts with a module-level doc-string."""
101+ tree = cst .parse_module (file_path .read_text ())
102+ if not tree .body :
103+ return False
104+ first = tree .body [0 ]
105+ if not isinstance (first , cst .SimpleStatementLine ):
106+ return False
107+ if len (first .body ) != 1 :
108+ return False
109+ expr = first .body [0 ]
110+ return isinstance (expr , cst .Expr ) and isinstance (
111+ expr .value , cst .SimpleString
112+ )
113+
114+
115+ def test_remove_docstring_command () -> None :
116+ """Test that RemoveDocstringCommand removes module docstrings."""
117+ source = '"""Module docstring."""\n \n some_var = 123\n '
118+ module = cst .parse_module (source )
119+ context = CodemodContext ()
120+ command = RemoveDocstringCommand (context )
121+
122+ new_module = command .transform_module (module )
123+ result = new_module .code
124+
125+ assert '"""Module docstring."""' not in result
126+ assert "some_var = 123" in result
127+
128+
129+ def test_remove_docstring_preserves_other_docstrings () -> None :
130+ """Test that function/class docstrings are preserved."""
131+ source = '''"""Module docstring."""
132+
133+ def foo():
134+ """Function docstring."""
135+ pass
136+ '''
137+ module = cst .parse_module (source )
138+ context = CodemodContext ()
139+ command = RemoveDocstringCommand (context )
140+
141+ new_module = command .transform_module (module )
142+ result = new_module .code
143+
144+ assert not result .startswith ('"""Module docstring."""' )
145+ assert '"""Function docstring."""' in result
146+
147+
148+ def test_remove_docstring_handles_files_without_docstrings () -> None :
149+ """Test that files without docstrings remain unchanged."""
150+ source_without_docstring = "some_var = 123\n \n def foo():\n pass\n "
151+ module = cst .parse_module (source_without_docstring )
152+ context = CodemodContext ()
153+ command = RemoveDocstringCommand (context )
154+
155+ new_module = command .transform_module (module )
156+
157+ assert new_module .code == source_without_docstring
158+
159+
160+ def test_remove_docstring_handles_empty_files () -> None :
161+ """Test that empty files remain empty."""
162+ source_empty = ""
163+ module = cst .parse_module (source_empty )
164+ context = CodemodContext ()
165+ command = RemoveDocstringCommand (context )
166+
167+ new_module = command .transform_module (module )
168+
169+ assert new_module .code == source_empty
0 commit comments