claude-dev-env 1.50.0 → 1.50.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/hooks/blocking/_gh_body_arg_utils.py +67 -11
  2. package/hooks/blocking/_md_to_html_blocker_test_support.py +65 -0
  3. package/hooks/blocking/code_rules_annotations_length.py +167 -0
  4. package/hooks/blocking/code_rules_banned_identifiers.py +385 -0
  5. package/hooks/blocking/code_rules_boolean_mustcheck.py +350 -0
  6. package/hooks/blocking/code_rules_comments.py +337 -0
  7. package/hooks/blocking/code_rules_constants_config.py +252 -0
  8. package/hooks/blocking/code_rules_docstrings.py +308 -0
  9. package/hooks/blocking/code_rules_enforcer.py +98 -5765
  10. package/hooks/blocking/code_rules_imports_logging.py +276 -0
  11. package/hooks/blocking/code_rules_magic_values.py +180 -0
  12. package/hooks/blocking/code_rules_mock_completeness.py +295 -0
  13. package/hooks/blocking/code_rules_naming_collection.py +264 -0
  14. package/hooks/blocking/code_rules_optional_params.py +288 -0
  15. package/hooks/blocking/code_rules_paths_syspath.py +186 -0
  16. package/hooks/blocking/code_rules_probe_chains.py +305 -0
  17. package/hooks/blocking/code_rules_probe_detection.py +257 -0
  18. package/hooks/blocking/code_rules_probe_recording.py +225 -0
  19. package/hooks/blocking/code_rules_scope_binding.py +151 -0
  20. package/hooks/blocking/code_rules_shared.py +301 -0
  21. package/hooks/blocking/code_rules_string_magic.py +207 -0
  22. package/hooks/blocking/code_rules_test_assertions.py +226 -0
  23. package/hooks/blocking/code_rules_test_branching_except.py +181 -0
  24. package/hooks/blocking/code_rules_test_isolation.py +341 -0
  25. package/hooks/blocking/code_rules_type_escape.py +341 -0
  26. package/hooks/blocking/code_rules_typeddict_stub.py +305 -0
  27. package/hooks/blocking/code_rules_unused_imports.py +256 -0
  28. package/hooks/blocking/conftest.py +30 -0
  29. package/hooks/blocking/pr_description_body_audit.py +148 -0
  30. package/hooks/blocking/pr_description_command_parser.py +233 -0
  31. package/hooks/blocking/pr_description_enforcer.py +36 -825
  32. package/hooks/blocking/pr_description_pr_number.py +153 -0
  33. package/hooks/blocking/pr_description_readability.py +366 -0
  34. package/hooks/blocking/tdd_enforcer.py +31 -0
  35. package/hooks/blocking/test_code_rules_constants_config.py +26 -0
  36. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +5 -2
  37. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -5
  38. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +21 -15
  39. package/hooks/blocking/test_code_rules_enforcer_config_path.py +20 -16
  40. package/hooks/blocking/test_code_rules_enforcer_exempt_marker_chained.py +4 -2
  41. package/hooks/blocking/test_code_rules_enforcer_function_length.py +154 -18
  42. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +1 -2
  43. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +22 -12
  44. package/hooks/blocking/test_code_rules_enforcer_split_annotations_length.py +55 -0
  45. package/hooks/blocking/test_code_rules_enforcer_split_banned.py +170 -0
  46. package/hooks/blocking/test_code_rules_enforcer_split_comments.py +60 -0
  47. package/hooks/blocking/test_code_rules_enforcer_split_config_path.py +52 -0
  48. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +236 -0
  49. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +296 -0
  50. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +238 -0
  51. package/hooks/blocking/test_code_rules_enforcer_split_isolation_1.py +271 -0
  52. package/hooks/blocking/test_code_rules_enforcer_split_isolation_2.py +283 -0
  53. package/hooks/blocking/test_code_rules_enforcer_split_isolation_3.py +268 -0
  54. package/hooks/blocking/test_code_rules_enforcer_split_isolation_4.py +85 -0
  55. package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +303 -0
  56. package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +111 -0
  57. package/hooks/blocking/test_code_rules_enforcer_split_mustcheck.py +87 -0
  58. package/hooks/blocking/test_code_rules_enforcer_split_naming.py +107 -0
  59. package/hooks/blocking/test_code_rules_enforcer_split_optional_params.py +325 -0
  60. package/hooks/blocking/test_code_rules_enforcer_split_paths_syspath.py +110 -0
  61. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +44 -0
  62. package/hooks/blocking/test_code_rules_enforcer_split_string_magic.py +55 -0
  63. package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +56 -0
  64. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +21 -15
  65. package/hooks/blocking/test_code_rules_paths_syspath.py +26 -0
  66. package/hooks/blocking/test_md_to_html_blocker_exemptions.py +368 -0
  67. package/hooks/blocking/test_md_to_html_blocker_extensions.py +157 -0
  68. package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +336 -0
  69. package/hooks/blocking/test_pr_description_enforcer.py +13 -1499
  70. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +247 -0
  71. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +493 -0
  72. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +366 -0
  73. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +159 -0
  74. package/hooks/blocking/test_pr_description_enforcer_readability.py +443 -0
  75. package/hooks/blocking/test_tdd_enforcer.py +116 -0
  76. package/hooks/hooks_constants/blocking_check_limits.py +3 -0
  77. package/hooks/hooks_constants/code_rules_enforcer_constants.py +8 -0
  78. package/hooks/hooks_constants/pr_description_enforcer_constants.py +7 -0
  79. package/hooks/hooks_constants/sys_path_insert_constants.py +1 -0
  80. package/package.json +1 -1
  81. package/hooks/blocking/test_code_rules_enforcer.py +0 -2669
  82. package/hooks/blocking/test_md_to_html_blocker.py +0 -810
@@ -0,0 +1,325 @@
1
+ """Behavior tests for the code_rules_optional_params code-rules check module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import sys
7
+ from pathlib import Path
8
+ from types import SimpleNamespace
9
+
10
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
11
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
12
+ if _BLOCKING_DIRECTORY not in sys.path:
13
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
14
+ if _HOOKS_DIRECTORY not in sys.path:
15
+ sys.path.insert(0, _HOOKS_DIRECTORY)
16
+
17
+ from code_rules_optional_params import ( # noqa: E402
18
+ _build_fstring_skeleton,
19
+ check_duplicated_format_patterns,
20
+ check_unused_optional_parameters,
21
+ )
22
+
23
+ code_rules_enforcer = SimpleNamespace(
24
+ _build_fstring_skeleton=_build_fstring_skeleton,
25
+ check_duplicated_format_patterns=check_duplicated_format_patterns,
26
+ check_unused_optional_parameters=check_unused_optional_parameters,
27
+ )
28
+
29
+
30
+ DUPLICATED_FORMAT_PRODUCTION_FILE_PATH = "packages/app/services/api_client.py"
31
+
32
+ DUPLICATED_FORMAT_TEST_FILE_PATH = "packages/app/tests/test_api_client.py"
33
+
34
+ KWARGS_EXPANSION_PRODUCTION_FILE_PATH = "packages/app/services/fetcher.py"
35
+
36
+ NESTED_FUNCTION_PRODUCTION_FILE_PATH = "packages/app/services/nested.py"
37
+
38
+ UNUSED_OPTIONAL_CONFIG_FILE_PATH = "packages/app/config/constants.py"
39
+
40
+ UNUSED_OPTIONAL_PRODUCTION_FILE_PATH = "packages/app/services/feature.py"
41
+
42
+ UNUSED_OPTIONAL_TEST_FILE_PATH = "packages/app/tests/test_feature.py"
43
+
44
+
45
+ def test_should_flag_optional_param_never_varied_in_file() -> None:
46
+ source = (
47
+ "def build_url(path: str, prefix: str = '/api') -> str:\n"
48
+ " return f'{prefix}{path}'\n"
49
+ "\n"
50
+ "def call_first() -> str:\n"
51
+ " return build_url('/users')\n"
52
+ "\n"
53
+ "def call_second() -> str:\n"
54
+ " return build_url('/items')\n"
55
+ )
56
+ issues = code_rules_enforcer.check_unused_optional_parameters(
57
+ source, UNUSED_OPTIONAL_PRODUCTION_FILE_PATH
58
+ )
59
+ assert any("prefix" in issue for issue in issues), (
60
+ f"Expected 'prefix' flagged as never-varied, got: {issues}"
61
+ )
62
+
63
+
64
+ def test_should_not_flag_when_param_is_varied_at_call_site() -> None:
65
+ source = (
66
+ "def build_url(path: str, prefix: str = '/api') -> str:\n"
67
+ " return f'{prefix}{path}'\n"
68
+ "\n"
69
+ "def call_with_default() -> str:\n"
70
+ " return build_url('/users')\n"
71
+ "\n"
72
+ "def call_with_override() -> str:\n"
73
+ " return build_url('/items', prefix='/v2')\n"
74
+ )
75
+ issues = code_rules_enforcer.check_unused_optional_parameters(
76
+ source, UNUSED_OPTIONAL_PRODUCTION_FILE_PATH
77
+ )
78
+ assert not any("prefix" in issue for issue in issues), (
79
+ f"Expected 'prefix' not flagged when varied, got: {issues}"
80
+ )
81
+
82
+
83
+ def test_should_not_flag_unused_optional_in_test_files() -> None:
84
+ source = (
85
+ "def build_url(path: str, prefix: str = '/api') -> str:\n"
86
+ " return f'{prefix}{path}'\n"
87
+ "\n"
88
+ "def call_first() -> str:\n"
89
+ " return build_url('/users')\n"
90
+ )
91
+ issues = code_rules_enforcer.check_unused_optional_parameters(
92
+ source, UNUSED_OPTIONAL_TEST_FILE_PATH
93
+ )
94
+ assert issues == [], f"Expected no issues in test file, got: {issues}"
95
+
96
+
97
+ def test_should_not_flag_unused_optional_in_config_files() -> None:
98
+ source = (
99
+ "def build_url(path: str, prefix: str = '/api') -> str:\n"
100
+ " return f'{prefix}{path}'\n"
101
+ "\n"
102
+ "def call_first() -> str:\n"
103
+ " return build_url('/users')\n"
104
+ )
105
+ issues = code_rules_enforcer.check_unused_optional_parameters(
106
+ source, UNUSED_OPTIONAL_CONFIG_FILE_PATH
107
+ )
108
+ assert issues == [], f"Expected no issues in config file, got: {issues}"
109
+
110
+
111
+ def test_should_not_flag_when_no_same_file_call_sites_exist() -> None:
112
+ source = (
113
+ "def build_url(path: str, prefix: str = '/api') -> str:\n"
114
+ " return f'{prefix}{path}'\n"
115
+ )
116
+ issues = code_rules_enforcer.check_unused_optional_parameters(
117
+ source, UNUSED_OPTIONAL_PRODUCTION_FILE_PATH
118
+ )
119
+ assert issues == [], (
120
+ f"Expected no issues when no same-file call sites, got: {issues}"
121
+ )
122
+
123
+
124
+ def test_should_include_line_number_and_param_name_in_issue() -> None:
125
+ source = (
126
+ "def fetch(url: str, timeout: int = 30) -> str:\n"
127
+ " return get(url, timeout=timeout)\n"
128
+ "\n"
129
+ "def run_fetch() -> str:\n"
130
+ " return fetch('http://example.com')\n"
131
+ )
132
+ issues = code_rules_enforcer.check_unused_optional_parameters(
133
+ source, UNUSED_OPTIONAL_PRODUCTION_FILE_PATH
134
+ )
135
+ assert any("Line 1" in issue and "timeout" in issue for issue in issues), (
136
+ f"Expected issue with line number and param name, got: {issues}"
137
+ )
138
+
139
+
140
+ def test_should_flag_when_every_call_passes_the_exact_default() -> None:
141
+ source = (
142
+ "def fetch(url: str, timeout: int = 30) -> str:\n"
143
+ " return get(url, timeout=timeout)\n"
144
+ "\n"
145
+ "def run_fetch() -> str:\n"
146
+ " return fetch('http://example.com', timeout=30)\n"
147
+ )
148
+ issues = code_rules_enforcer.check_unused_optional_parameters(
149
+ source, UNUSED_OPTIONAL_PRODUCTION_FILE_PATH
150
+ )
151
+ assert any("timeout" in issue for issue in issues), (
152
+ f"Expected 'timeout' flagged when every call passes the exact default, got: {issues}"
153
+ )
154
+
155
+
156
+ def test_should_advise_when_fstring_skeleton_appears_three_or_more_times(capsys: object) -> None:
157
+ source = (
158
+ "def get_user(user_id: str) -> str:\n"
159
+ " return f'/api/{user_id}'\n"
160
+ "\n"
161
+ "def get_order(order_id: str) -> str:\n"
162
+ " return f'/api/{order_id}'\n"
163
+ "\n"
164
+ "def get_product(product_id: str) -> str:\n"
165
+ " return f'/api/{product_id}'\n"
166
+ )
167
+ code_rules_enforcer.check_duplicated_format_patterns(
168
+ source, DUPLICATED_FORMAT_PRODUCTION_FILE_PATH
169
+ )
170
+ captured = getattr(capsys, "readouterr")()
171
+ assert "/api/" in captured.err and "3" in captured.err, (
172
+ f"Expected advisory for repeated /api/<x> pattern, got: {captured.err!r}"
173
+ )
174
+
175
+
176
+ def test_should_not_advise_when_fstring_skeleton_appears_fewer_than_three_times(capsys: object) -> None:
177
+ source = (
178
+ "def get_user(user_id: str) -> str:\n"
179
+ " return f'/api/{user_id}'\n"
180
+ "\n"
181
+ "def get_order(order_id: str) -> str:\n"
182
+ " return f'/api/{order_id}'\n"
183
+ )
184
+ code_rules_enforcer.check_duplicated_format_patterns(
185
+ source, DUPLICATED_FORMAT_PRODUCTION_FILE_PATH
186
+ )
187
+ captured = getattr(capsys, "readouterr")()
188
+ assert "/api/" not in captured.err, (
189
+ f"Expected no advisory for pattern appearing only twice, got: {captured.err!r}"
190
+ )
191
+
192
+
193
+ def test_should_not_advise_for_duplicated_format_patterns_in_test_files(capsys: object) -> None:
194
+ source = (
195
+ "def test_user() -> None:\n"
196
+ " url_a = f'/api/{1}'\n"
197
+ " url_b = f'/api/{2}'\n"
198
+ " url_c = f'/api/{3}'\n"
199
+ )
200
+ code_rules_enforcer.check_duplicated_format_patterns(
201
+ source, DUPLICATED_FORMAT_TEST_FILE_PATH
202
+ )
203
+ captured = getattr(capsys, "readouterr")()
204
+ assert "/api/" not in captured.err, (
205
+ f"Expected no advisory in test file, got: {captured.err!r}"
206
+ )
207
+
208
+
209
+ def test_should_advise_with_distinct_skeletons(capsys: object) -> None:
210
+ source = (
211
+ "def first(team: str, user: str) -> str:\n"
212
+ " return f'/teams/{team}/users/{user}'\n"
213
+ "\n"
214
+ "def second(team: str, role: str) -> str:\n"
215
+ " return f'/teams/{team}/users/{role}'\n"
216
+ "\n"
217
+ "def third(team: str, admin: str) -> str:\n"
218
+ " return f'/teams/{team}/users/{admin}'\n"
219
+ )
220
+ code_rules_enforcer.check_duplicated_format_patterns(
221
+ source, DUPLICATED_FORMAT_PRODUCTION_FILE_PATH
222
+ )
223
+ captured = getattr(capsys, "readouterr")()
224
+ assert "/teams/" in captured.err, (
225
+ f"Expected advisory for repeated /teams/<x>/users/<x> pattern, got: {captured.err!r}"
226
+ )
227
+
228
+
229
+ def test_build_fstring_skeleton_preserves_literal_interp_substring() -> None:
230
+ joined_str_expression = ast.parse("f'PREFIX INTERP {value} SUFFIX'", mode="eval").body
231
+ assert isinstance(joined_str_expression, ast.JoinedStr)
232
+ skeleton = code_rules_enforcer._build_fstring_skeleton(joined_str_expression)
233
+ assert skeleton == "PREFIX INTERP <x> SUFFIX", (
234
+ "Literal 'INTERP' text inside an f-string must survive skeleton building — "
235
+ f"only interpolation slots should become '<x>'. Got: {skeleton!r}"
236
+ )
237
+
238
+
239
+ def test_should_not_flag_nested_function_optional_param() -> None:
240
+ source = (
241
+ "def outer() -> None:\n"
242
+ " def inner(timeout: int = 30) -> None:\n"
243
+ " pass\n"
244
+ " inner()\n"
245
+ " inner()\n"
246
+ )
247
+ issues = code_rules_enforcer.check_unused_optional_parameters(
248
+ source, NESTED_FUNCTION_PRODUCTION_FILE_PATH
249
+ )
250
+ assert not any("timeout" in issue for issue in issues), (
251
+ f"Expected nested function 'timeout' not flagged, got: {issues}"
252
+ )
253
+
254
+
255
+ def test_should_not_flag_optional_param_when_only_call_site_uses_kwargs_expansion() -> None:
256
+ """A call using **defaults passes unknown values — the param must NOT be flagged."""
257
+ source = (
258
+ "def fetch(url: str, timeout: int = 30) -> str:\n"
259
+ " return url\n"
260
+ "\n"
261
+ "def run() -> str:\n"
262
+ " defaults = {'timeout': 30}\n"
263
+ " return fetch('http://example.com', **defaults)\n"
264
+ )
265
+ issues = code_rules_enforcer.check_unused_optional_parameters(
266
+ source, KWARGS_EXPANSION_PRODUCTION_FILE_PATH
267
+ )
268
+ assert not any("timeout" in issue for issue in issues), (
269
+ f"Expected 'timeout' NOT flagged when call uses **kwargs expansion, got: {issues}"
270
+ )
271
+
272
+
273
+ def test_should_not_advise_when_duplicated_fstring_literal_is_short(capsys: object) -> None:
274
+ """Short logger-prefix style f-strings must not emit a duplication advisory.
275
+
276
+ A three-times-repeated ``f"Got {x}"`` has only four characters of literal
277
+ text (``"Got "``). Flagging such short fragments creates noise for common
278
+ logging prefixes. The heuristic requires a minimum amount of structural
279
+ literal text before an advisory fires.
280
+ """
281
+ source = (
282
+ "def first(value: str) -> str:\n"
283
+ " return f'Got {value}'\n"
284
+ "\n"
285
+ "def second(value: str) -> str:\n"
286
+ " return f'Got {value}'\n"
287
+ "\n"
288
+ "def third(value: str) -> str:\n"
289
+ " return f'Got {value}'\n"
290
+ )
291
+ code_rules_enforcer.check_duplicated_format_patterns(
292
+ source, DUPLICATED_FORMAT_PRODUCTION_FILE_PATH
293
+ )
294
+ captured = getattr(capsys, "readouterr")()
295
+ assert "Got" not in captured.err, (
296
+ "Expected no advisory for a short repeated f-string literal fragment, "
297
+ f"got: {captured.err!r}"
298
+ )
299
+
300
+
301
+ def test_should_still_advise_when_duplicated_fstring_literal_is_long(capsys: object) -> None:
302
+ """Longer duplicated f-string skeletons must continue to fire.
303
+
304
+ The short-literal heuristic must not regress the existing
305
+ ``/api/<x>`` and ``/teams/<x>/users/<x>`` advisories — those path
306
+ skeletons carry enough structural literal text to warrant a helper.
307
+ """
308
+ source = (
309
+ "def get_user(user_id: str) -> str:\n"
310
+ " return f'/api/{user_id}'\n"
311
+ "\n"
312
+ "def get_order(order_id: str) -> str:\n"
313
+ " return f'/api/{order_id}'\n"
314
+ "\n"
315
+ "def get_product(product_id: str) -> str:\n"
316
+ " return f'/api/{product_id}'\n"
317
+ )
318
+ code_rules_enforcer.check_duplicated_format_patterns(
319
+ source, DUPLICATED_FORMAT_PRODUCTION_FILE_PATH
320
+ )
321
+ captured = getattr(capsys, "readouterr")()
322
+ assert "/api/" in captured.err, (
323
+ "Expected the existing /api/<x> path-shape advisory to still fire, "
324
+ f"got: {captured.err!r}"
325
+ )
@@ -0,0 +1,110 @@
1
+ """Behavior tests for the code_rules_paths_syspath code-rules check module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from types import SimpleNamespace
8
+
9
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
10
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
11
+ if _BLOCKING_DIRECTORY not in sys.path:
12
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
13
+ if _HOOKS_DIRECTORY not in sys.path:
14
+ sys.path.insert(0, _HOOKS_DIRECTORY)
15
+
16
+ from code_rules_paths_syspath import ( # noqa: E402
17
+ HARDCODED_USER_PATH_GUIDANCE,
18
+ HARDCODED_USER_PATH_PATTERN,
19
+ MAX_HARDCODED_USER_PATH_ISSUES,
20
+ check_sys_path_insert_deduplication_guard,
21
+ )
22
+
23
+ from hooks_constants.hardcoded_user_path_constants import ( # noqa: E402
24
+ HARDCODED_USER_PATH_GUIDANCE as config_hardcoded_user_path_guidance,
25
+ )
26
+ from hooks_constants.hardcoded_user_path_constants import ( # noqa: E402
27
+ HARDCODED_USER_PATH_PATTERN as config_hardcoded_user_path_pattern,
28
+ )
29
+ from hooks_constants.hardcoded_user_path_constants import ( # noqa: E402
30
+ MAX_HARDCODED_USER_PATH_ISSUES as config_max_hardcoded_user_path_issues,
31
+ )
32
+
33
+ code_rules_enforcer = SimpleNamespace(
34
+ HARDCODED_USER_PATH_GUIDANCE=HARDCODED_USER_PATH_GUIDANCE,
35
+ HARDCODED_USER_PATH_PATTERN=HARDCODED_USER_PATH_PATTERN,
36
+ MAX_HARDCODED_USER_PATH_ISSUES=MAX_HARDCODED_USER_PATH_ISSUES,
37
+ check_sys_path_insert_deduplication_guard=check_sys_path_insert_deduplication_guard,
38
+ )
39
+
40
+
41
+ SYS_PATH_INSERT_HOOK_INFRASTRUCTURE_FILE_PATH = "/repo/.claude/hooks/blocking/some_hook.py"
42
+
43
+ SYS_PATH_INSERT_PRODUCTION_FILE_PATH = "packages/app/services/loader.py"
44
+
45
+
46
+ def test_should_reexport_hardcoded_user_path_pattern_from_config() -> None:
47
+ assert code_rules_enforcer.HARDCODED_USER_PATH_PATTERN is config_hardcoded_user_path_pattern
48
+
49
+
50
+ def test_should_reexport_max_hardcoded_user_path_issues_from_config() -> None:
51
+ assert code_rules_enforcer.MAX_HARDCODED_USER_PATH_ISSUES == config_max_hardcoded_user_path_issues
52
+
53
+
54
+ def test_should_reexport_hardcoded_user_path_guidance_from_config() -> None:
55
+ assert code_rules_enforcer.HARDCODED_USER_PATH_GUIDANCE == config_hardcoded_user_path_guidance
56
+
57
+
58
+ def test_sys_path_insert_should_flag_mismatched_guard_path() -> None:
59
+ source = (
60
+ "import sys\n"
61
+ 'if "wrong_path" not in sys.path:\n'
62
+ ' sys.path.insert(0, "actual_path")\n'
63
+ )
64
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
65
+ source, SYS_PATH_INSERT_PRODUCTION_FILE_PATH
66
+ )
67
+ assert any("sys.path.insert" in each_issue for each_issue in issues), (
68
+ "Guard testing a different value than what is inserted must be flagged, "
69
+ f"got: {issues}"
70
+ )
71
+
72
+
73
+ def test_sys_path_insert_should_not_flag_matching_guard_path() -> None:
74
+ source = (
75
+ "import sys\n"
76
+ 'if "correct_path" not in sys.path:\n'
77
+ ' sys.path.insert(0, "correct_path")\n'
78
+ )
79
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
80
+ source, SYS_PATH_INSERT_PRODUCTION_FILE_PATH
81
+ )
82
+ assert issues == [], (
83
+ f"Guard testing the same value that is inserted must not be flagged, got: {issues}"
84
+ )
85
+
86
+
87
+ def test_sys_path_insert_should_not_flag_guarded_insert_in_class_body() -> None:
88
+ source = (
89
+ "import sys\n"
90
+ "class Configurator:\n"
91
+ " target = '/some/path'\n"
92
+ " if target not in sys.path:\n"
93
+ " sys.path.insert(0, target)\n"
94
+ )
95
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
96
+ source, SYS_PATH_INSERT_PRODUCTION_FILE_PATH
97
+ )
98
+ assert issues == [], (
99
+ f"Guarded sys.path.insert directly in a class body must not be flagged, got: {issues}"
100
+ )
101
+
102
+
103
+ def test_sys_path_insert_should_skip_hook_infrastructure_files() -> None:
104
+ source = "import sys\nsys.path.insert(0, '/some/path')\n"
105
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
106
+ source, SYS_PATH_INSERT_HOOK_INFRASTRUCTURE_FILE_PATH
107
+ )
108
+ assert issues == [], (
109
+ f"Hook infrastructure files are exempt from this rule, got: {issues}"
110
+ )
@@ -0,0 +1,44 @@
1
+ """Behavior tests for the code_rules_shared code-rules check module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from types import SimpleNamespace
8
+
9
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
10
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
11
+ if _BLOCKING_DIRECTORY not in sys.path:
12
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
13
+ if _HOOKS_DIRECTORY not in sys.path:
14
+ sys.path.insert(0, _HOOKS_DIRECTORY)
15
+
16
+ from code_rules_enforcer import ( # noqa: E402
17
+ prior_and_post_edit_content,
18
+ )
19
+ from code_rules_shared import ( # noqa: E402
20
+ changed_line_numbers,
21
+ )
22
+
23
+ code_rules_enforcer = SimpleNamespace(
24
+ changed_line_numbers=changed_line_numbers,
25
+ prior_and_post_edit_content=prior_and_post_edit_content,
26
+ )
27
+
28
+
29
+ def test_readable_prior_yields_consistent_prior_and_reconstruction(tmp_path) -> None:
30
+ """When the prior reads cleanly, the helper returns the same prior content it
31
+ reconstructed the post-edit view from, so the two never diverge across two
32
+ independent reads."""
33
+ source_file = tmp_path / "module.py"
34
+ original = "alpha = 1\nbeta = 2\n"
35
+ source_file.write_text(original, encoding="utf-8")
36
+ prior_content, post_edit_content = code_rules_enforcer.prior_and_post_edit_content(
37
+ str(source_file),
38
+ old_string="beta = 2\n",
39
+ new_string="beta = 3\n",
40
+ )
41
+ assert prior_content == original
42
+ assert post_edit_content == "alpha = 1\nbeta = 3\n"
43
+ changed = code_rules_enforcer.changed_line_numbers(prior_content, post_edit_content)
44
+ assert changed == {2}
@@ -0,0 +1,55 @@
1
+ """Behavior tests for the code_rules_string_magic code-rules check module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from types import SimpleNamespace
8
+
9
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
10
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
11
+ if _BLOCKING_DIRECTORY not in sys.path:
12
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
13
+ if _HOOKS_DIRECTORY not in sys.path:
14
+ sys.path.insert(0, _HOOKS_DIRECTORY)
15
+
16
+ from code_rules_string_magic import ( # noqa: E402
17
+ check_inline_literal_collections,
18
+ check_string_literal_magic,
19
+ )
20
+
21
+ code_rules_enforcer = SimpleNamespace(
22
+ check_inline_literal_collections=check_inline_literal_collections,
23
+ check_string_literal_magic=check_string_literal_magic,
24
+ )
25
+
26
+
27
+ INLINE_LITERAL_PRODUCTION_FILE_PATH = "packages/app/services/inline_literal.py"
28
+
29
+ STRING_MAGIC_PRODUCTION_FILE_PATH = "packages/app/services/string_magic.py"
30
+
31
+
32
+ def test_check_inline_literal_collections_flags_three_string_set_in_function() -> None:
33
+ source = (
34
+ "def is_known(value: str) -> bool:\n"
35
+ " return value in {'true', 'false', 'none'}\n"
36
+ )
37
+ issues = code_rules_enforcer.check_inline_literal_collections(
38
+ source, INLINE_LITERAL_PRODUCTION_FILE_PATH
39
+ )
40
+ assert len(issues) == 1, f"Expected 3-element string set flagged, got: {issues}"
41
+
42
+
43
+ def test_check_string_literal_magic_flags_env_var_name() -> None:
44
+ source = (
45
+ "import os\n"
46
+ "\n"
47
+ "def fetch_secret() -> str:\n"
48
+ " return os.environ['STRIPE_SECRET']\n"
49
+ )
50
+ issues = code_rules_enforcer.check_string_literal_magic(
51
+ source, STRING_MAGIC_PRODUCTION_FILE_PATH
52
+ )
53
+ assert any("STRIPE_SECRET" in each_issue for each_issue in issues), (
54
+ f"Expected env-var name flagged, got: {issues}"
55
+ )
@@ -0,0 +1,56 @@
1
+ """Behavior tests for the code_rules_test_assertions code-rules check module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+ from types import SimpleNamespace
8
+
9
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
10
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
11
+ if _BLOCKING_DIRECTORY not in sys.path:
12
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
13
+ if _HOOKS_DIRECTORY not in sys.path:
14
+ sys.path.insert(0, _HOOKS_DIRECTORY)
15
+
16
+ from code_rules_test_assertions import ( # noqa: E402
17
+ check_constant_equality_tests,
18
+ )
19
+
20
+ code_rules_enforcer = SimpleNamespace(
21
+ check_constant_equality_tests=check_constant_equality_tests,
22
+ )
23
+
24
+
25
+ CONSTANT_EQUALITY_TEST_FILE_PATH = "packages/app/tests/test_constants.py"
26
+
27
+
28
+ def test_should_not_flag_two_named_constants_compared_to_each_other() -> None:
29
+ source = (
30
+ "FOO = 'a'\n"
31
+ "BAR = 'b'\n"
32
+ "\n"
33
+ "def test_constants_differ() -> None:\n"
34
+ " assert FOO == BAR\n"
35
+ )
36
+ issues = code_rules_enforcer.check_constant_equality_tests(
37
+ source, CONSTANT_EQUALITY_TEST_FILE_PATH
38
+ )
39
+ assert issues == [], (
40
+ f"Expected no flag when both sides are named constants, got: {issues}"
41
+ )
42
+
43
+
44
+ def test_should_flag_named_constant_compared_to_literal() -> None:
45
+ source = (
46
+ "FOO = 'a'\n"
47
+ "\n"
48
+ "def test_foo_value() -> None:\n"
49
+ " assert FOO == 'literal'\n"
50
+ )
51
+ issues = code_rules_enforcer.check_constant_equality_tests(
52
+ source, CONSTANT_EQUALITY_TEST_FILE_PATH
53
+ )
54
+ assert any("constant-value test" in issue for issue in issues), (
55
+ f"Expected flag when UPPER_SNAKE compared to literal, got: {issues}"
56
+ )
@@ -7,22 +7,28 @@ rule blocks every authored TODO. These tests pin the exemption.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import importlib.util
10
+ import sys
11
11
  from pathlib import Path
12
- from types import ModuleType
13
-
14
-
15
- def _load_enforcer_module() -> ModuleType:
16
- module_path = Path(__file__).parent / "code_rules_enforcer.py"
17
- spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
18
- assert spec is not None
19
- assert spec.loader is not None
20
- module = importlib.util.module_from_spec(spec)
21
- spec.loader.exec_module(module)
22
- return module
23
-
24
-
25
- code_rules_enforcer = _load_enforcer_module()
12
+ from types import SimpleNamespace
13
+
14
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
15
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
16
+ if _BLOCKING_DIRECTORY not in sys.path:
17
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
18
+ if _HOOKS_DIRECTORY not in sys.path:
19
+ sys.path.insert(0, _HOOKS_DIRECTORY)
20
+
21
+ from code_rules_comments import ( # noqa: E402
22
+ check_comments_javascript,
23
+ check_comments_python,
24
+ extract_comment_texts,
25
+ )
26
+
27
+ code_rules_enforcer = SimpleNamespace(
28
+ check_comments_javascript=check_comments_javascript,
29
+ check_comments_python=check_comments_python,
30
+ extract_comment_texts=extract_comment_texts,
31
+ )
26
32
 
27
33
 
28
34
  def test_python_check_should_exempt_standalone_todo_comment() -> None:
@@ -0,0 +1,26 @@
1
+ """Regression guard pinning docstring prose in code_rules_paths_syspath."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
9
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
10
+ if _BLOCKING_DIRECTORY not in sys.path:
11
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
12
+ if _HOOKS_DIRECTORY not in sys.path:
13
+ sys.path.insert(0, _HOOKS_DIRECTORY)
14
+
15
+ from code_rules_magic_values import check_magic_values # noqa: E402
16
+
17
+
18
+ def test_module_source_carries_no_docstring_magic_value() -> None:
19
+ module_path = Path(__file__).resolve().parent / "code_rules_paths_syspath.py"
20
+ module_source = module_path.read_text(encoding="utf-8")
21
+ magic_value_issues = check_magic_values(module_source, str(module_path))
22
+ assert magic_value_issues == [], (
23
+ "Docstring prose in code_rules_paths_syspath.py must not carry a "
24
+ "bare-number token that the magic-value check flags as a literal, "
25
+ f"got: {magic_value_issues}"
26
+ )