claude-dev-env 1.77.0 → 1.78.0

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.
@@ -34,32 +34,46 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
34
34
  ALL_DOCSTRING_RUNON_JOINER_MARKERS,
35
35
  ALL_GENERIC_CHECK_NAME_TOKENS,
36
36
  ALL_NAMING_CONVENTION_DESCRIPTOR_TOKENS,
37
+ ALL_PUNCTUATION_MARK_GLYPH_PROSE_NAMES,
37
38
  DOCSTRING_FALLBACK_BRANCH_MINIMUM_ROUTE_COUNT,
38
39
  DOCSTRING_REFERENCE_MARKER_WINDOW,
39
40
  DOCSTRING_RUNON_SENTENCE_BOUNDARY_PATTERN,
40
41
  DOCSTRING_RUNON_SENTENCE_WORD_LIMIT,
41
42
  DOCSTRING_TRIVIAL_FUNCTION_BODY_LINE_LIMIT,
42
43
  MAX_CLASS_DOCSTRING_PUBLIC_METHOD_ISSUES,
44
+ MAX_COMPANION_MODULE_RESOLUTION_DEPTH,
45
+ PYTHON_MODULE_FILE_SUFFIX,
46
+ WORD_BOUNDARY_REGEX,
43
47
  MAX_DOCSTRING_ARGS_SIGNATURE_ISSUES,
44
48
  MAX_DOCSTRING_CARDINAL_FAMILY_ISSUES,
45
49
  MAX_DOCSTRING_FALLBACK_BRANCH_ISSUES,
46
50
  MAX_DOCSTRING_FORMAT_ISSUES,
47
51
  MAX_DOCSTRING_INLINE_LITERAL_CLAIM_ISSUES,
52
+ MAX_DOCSTRING_MARK_GLYPH_ENUMERATION_ISSUES,
48
53
  MAX_DOCSTRING_NO_CONSUMER_CLAIM_ISSUES,
49
54
  MAX_DOCSTRING_RUNON_SENTENCE_ISSUES,
50
55
  ALL_DOCSTRING_SINGLE_LINE_SCOPE_PHRASES,
51
56
  ALL_DOCSTRING_SPAN_RANGE_BODY_CALLEE_NAMES,
52
57
  ALL_DOCSTRING_SPAN_SCOPE_OVERRIDE_PHRASES,
53
58
  MAX_DOCSTRING_ARGS_SPAN_SCOPE_ISSUES,
59
+ ALL_ZIPFILE_WRITE_MODE_VALUES,
60
+ DOCSTRING_LARGE_ZIP_FILE_EXCEPTION_NAME,
61
+ MAX_DOCSTRING_RAISES_LARGEZIPFILE_ISSUES,
54
62
  MAX_DOCSTRING_STEP_DISPATCH_ISSUES,
55
63
  MAX_DOCSTRING_RETURNS_PLURAL_CARDINALITY_ISSUES,
56
64
  MAX_DOCSTRING_TUPLE_ENUMERATION_ISSUES,
65
+ ZIPFILE_ALLOW_ZIP64_KEYWORD,
66
+ ZIPFILE_ALLOW_ZIP64_POSITIONAL_INDEX,
67
+ ZIPFILE_MODE_KEYWORD,
68
+ ZIPFILE_MODE_POSITIONAL_INDEX,
69
+ ZIPFILE_WRITER_CLASS_NAME,
57
70
  MAX_DOCSTRING_UNDEFINED_CONSTANT_ISSUES,
58
71
  MAX_DOCSTRING_UNGUARDED_PAYLOAD_CLAIM_ISSUES,
59
72
  MAX_MODULE_DOCSTRING_CHECK_ROSTER_ISSUES,
60
73
  MINIMUM_CONSTANT_FAMILY_MEMBERS_FOR_CARDINAL_CHECK,
61
74
  MINIMUM_DOCSTRING_FAMILY_OVERLAP_FOR_CARDINAL_CHECK,
62
75
  MINIMUM_NAMED_LINEAR_STEPS_FOR_DISPATCH_CHECK,
76
+ MINIMUM_NAMED_MARKS_FOR_PROSE_ENUMERATION,
63
77
  MINIMUM_PUBLIC_CHECKS_FOR_MODULE_DOCSTRING_ROSTER,
64
78
  MINIMUM_PUBLIC_METHODS_FOR_CLASS_DOCSTRING_BREADTH,
65
79
  MINIMUM_TOKENS_FOR_DISPATCH_CALLEE,
@@ -1024,6 +1038,203 @@ def check_docstring_tuple_enumeration_match(content: str, file_path: str) -> lis
1024
1038
  return issues[:MAX_DOCSTRING_TUPLE_ENUMERATION_ISSUES]
1025
1039
 
1026
1040
 
1041
+ def _known_glyph_marker_members(
1042
+ sequence_node: ast.Tuple | ast.List,
1043
+ ) -> frozenset[str] | None:
1044
+ normalized_glyphs: set[str] = set()
1045
+ for each_element in sequence_node.elts:
1046
+ if not (
1047
+ isinstance(each_element, ast.Constant)
1048
+ and isinstance(each_element.value, str)
1049
+ ):
1050
+ return None
1051
+ normalized_glyph = each_element.value.strip()
1052
+ if normalized_glyph not in ALL_PUNCTUATION_MARK_GLYPH_PROSE_NAMES:
1053
+ return None
1054
+ normalized_glyphs.add(normalized_glyph)
1055
+ return frozenset(normalized_glyphs)
1056
+
1057
+
1058
+ def _assignment_targets_and_sequence(
1059
+ statement: ast.stmt,
1060
+ ) -> tuple[list[str], ast.Tuple | ast.List | None]:
1061
+ if isinstance(statement, ast.Assign) and isinstance(
1062
+ statement.value, (ast.Tuple, ast.List)
1063
+ ):
1064
+ plain_target_names = [
1065
+ each_target.id
1066
+ for each_target in statement.targets
1067
+ if isinstance(each_target, ast.Name)
1068
+ ]
1069
+ return plain_target_names, statement.value
1070
+ if (
1071
+ isinstance(statement, ast.AnnAssign)
1072
+ and isinstance(statement.target, ast.Name)
1073
+ and isinstance(statement.value, (ast.Tuple, ast.List))
1074
+ ):
1075
+ return [statement.target.id], statement.value
1076
+ return [], None
1077
+
1078
+
1079
+ def _module_glyph_marker_tuples(parsed_tree: ast.Module) -> dict[str, frozenset[str]]:
1080
+ glyphs_by_constant: dict[str, frozenset[str]] = {}
1081
+ for each_statement in parsed_tree.body:
1082
+ target_names, sequence_node = _assignment_targets_and_sequence(each_statement)
1083
+ if sequence_node is None:
1084
+ continue
1085
+ marker_glyphs = _known_glyph_marker_members(sequence_node)
1086
+ if marker_glyphs is None:
1087
+ continue
1088
+ if len(marker_glyphs) < MINIMUM_NAMED_MARKS_FOR_PROSE_ENUMERATION:
1089
+ continue
1090
+ for each_target_name in target_names:
1091
+ glyphs_by_constant[each_target_name] = marker_glyphs
1092
+ return glyphs_by_constant
1093
+
1094
+
1095
+ def _companion_module_file(dotted_module: str, file_path: str) -> Path | None:
1096
+ relative_module_path = Path(*dotted_module.split(".")).with_suffix(
1097
+ PYTHON_MODULE_FILE_SUFFIX
1098
+ )
1099
+ file_directory = Path(file_path).parent
1100
+ candidate_roots = [file_directory, *file_directory.parents]
1101
+ for each_root in candidate_roots[:MAX_COMPANION_MODULE_RESOLUTION_DEPTH]:
1102
+ candidate_path = each_root / relative_module_path
1103
+ if candidate_path.is_file():
1104
+ return candidate_path
1105
+ return None
1106
+
1107
+
1108
+ def _companion_glyph_marker_tuples(companion_path: Path) -> dict[str, frozenset[str]]:
1109
+ try:
1110
+ companion_source = companion_path.read_text(encoding="utf-8")
1111
+ companion_tree = ast.parse(companion_source)
1112
+ except (OSError, ValueError, SyntaxError):
1113
+ return {}
1114
+ return _module_glyph_marker_tuples(companion_tree)
1115
+
1116
+
1117
+ def _import_brings_upper_snake_name(import_node: ast.ImportFrom) -> bool:
1118
+ return any(
1119
+ ALL_CAPS_WITH_UNDERSCORE_PATTERN.match(each_alias.name)
1120
+ for each_alias in import_node.names
1121
+ )
1122
+
1123
+
1124
+ def _imported_glyph_marker_tuples(
1125
+ parsed_tree: ast.Module, file_path: str
1126
+ ) -> dict[str, frozenset[str]]:
1127
+ glyphs_by_imported_name: dict[str, frozenset[str]] = {}
1128
+ for each_statement in parsed_tree.body:
1129
+ if not isinstance(each_statement, ast.ImportFrom):
1130
+ continue
1131
+ if not each_statement.module:
1132
+ continue
1133
+ if not _import_brings_upper_snake_name(each_statement):
1134
+ continue
1135
+ companion_path = _companion_module_file(each_statement.module, file_path)
1136
+ if companion_path is None:
1137
+ continue
1138
+ companion_tuples = _companion_glyph_marker_tuples(companion_path)
1139
+ for each_alias in each_statement.names:
1140
+ if each_alias.name not in companion_tuples:
1141
+ continue
1142
+ imported_name = each_alias.asname or each_alias.name
1143
+ glyphs_by_imported_name[imported_name] = companion_tuples[each_alias.name]
1144
+ return glyphs_by_imported_name
1145
+
1146
+
1147
+ def _docstring_names_mark_glyph(docstring_text: str, normalized_glyph: str) -> bool:
1148
+ lowercased_docstring = docstring_text.lower()
1149
+ for each_name in ALL_PUNCTUATION_MARK_GLYPH_PROSE_NAMES[normalized_glyph]:
1150
+ boundary_wrapped_name = (
1151
+ WORD_BOUNDARY_REGEX + re.escape(each_name) + WORD_BOUNDARY_REGEX
1152
+ )
1153
+ if re.search(boundary_wrapped_name, lowercased_docstring):
1154
+ return True
1155
+ return False
1156
+
1157
+
1158
+ def _marks_named_in_docstring(
1159
+ docstring_text: str, all_marker_glyphs: frozenset[str]
1160
+ ) -> set[str]:
1161
+ return {
1162
+ each_glyph
1163
+ for each_glyph in all_marker_glyphs
1164
+ if _docstring_names_mark_glyph(docstring_text, each_glyph)
1165
+ }
1166
+
1167
+
1168
+ def _text_names_multiple_marks(content_text: str) -> bool:
1169
+ all_known_glyphs = frozenset(ALL_PUNCTUATION_MARK_GLYPH_PROSE_NAMES)
1170
+ named_glyphs = _marks_named_in_docstring(content_text, all_known_glyphs)
1171
+ return len(named_glyphs) >= MINIMUM_NAMED_MARKS_FOR_PROSE_ENUMERATION
1172
+
1173
+
1174
+ def check_docstring_punctuation_mark_enumeration_coverage(
1175
+ content: str, file_path: str
1176
+ ) -> list[str]:
1177
+ """Flag a docstring that names some marks of a glyph tuple but omits one.
1178
+
1179
+ A module reads a tuple of punctuation-mark glyphs as its detection set. The
1180
+ tuple is defined in the module or imported from a companion module beside it.
1181
+ A docstring then enumerates those marks by their English names. When the
1182
+ prose names a closed set of marks but leaves one the tuple holds unnamed, a
1183
+ reader trusts the enumeration and believes an active mark never triggers the
1184
+ check. This is the shape that appears when a glyph joins the tuple while the
1185
+ prose enumeration stays as it was.
1186
+
1187
+ The check binds only when a docstring names two or more marks of one tuple. A
1188
+ docstring that mentions a single mark, names every mark, or describes
1189
+ unrelated punctuation is left alone. This is the deterministic glyph-prose
1190
+ slice of Category O6 docstring-prose-vs-implementation drift, the companion
1191
+ to check_docstring_tuple_enumeration_match for glyph members named in prose
1192
+ rather than identifier members named in inline code. It covers hook
1193
+ infrastructure, where the affected detection tuples live.
1194
+
1195
+ Args:
1196
+ content: The source text to inspect.
1197
+ file_path: The path the source will be written to, used for exemptions.
1198
+
1199
+ Returns:
1200
+ One issue per docstring whose mark enumeration omits a glyph the tuple it
1201
+ describes holds, capped at the module limit.
1202
+ """
1203
+ if is_strict_test_file(file_path):
1204
+ return []
1205
+ if not _text_names_multiple_marks(content):
1206
+ return []
1207
+ try:
1208
+ parsed_tree = ast.parse(content)
1209
+ except SyntaxError:
1210
+ return []
1211
+ glyphs_by_constant = {
1212
+ **_module_glyph_marker_tuples(parsed_tree),
1213
+ **_imported_glyph_marker_tuples(parsed_tree, file_path),
1214
+ }
1215
+ if not glyphs_by_constant:
1216
+ return []
1217
+ issues: list[str] = []
1218
+ for each_line, each_docstring in _documentable_docstrings_with_line(parsed_tree):
1219
+ for each_constant_name in sorted(glyphs_by_constant):
1220
+ marker_glyphs = glyphs_by_constant[each_constant_name]
1221
+ named_glyphs = _marks_named_in_docstring(each_docstring, marker_glyphs)
1222
+ if len(named_glyphs) < MINIMUM_NAMED_MARKS_FOR_PROSE_ENUMERATION:
1223
+ continue
1224
+ omitted_glyphs = marker_glyphs - named_glyphs
1225
+ if not omitted_glyphs:
1226
+ continue
1227
+ issues.append(
1228
+ f"Line {each_line}: docstring names {sorted(named_glyphs)} from "
1229
+ f"{each_constant_name} but omits {sorted(omitted_glyphs)} — name every "
1230
+ "mark the tuple holds so the enumeration matches the detection set "
1231
+ "(Category O6 docstring-vs-implementation drift)"
1232
+ )
1233
+ if len(issues) >= MAX_DOCSTRING_MARK_GLYPH_ENUMERATION_ISSUES:
1234
+ return issues[:MAX_DOCSTRING_MARK_GLYPH_ENUMERATION_ISSUES]
1235
+ return issues[:MAX_DOCSTRING_MARK_GLYPH_ENUMERATION_ISSUES]
1236
+
1237
+
1027
1238
  def _returns_section_text(docstring_text: str) -> str:
1028
1239
  docstring_lines = docstring_text.splitlines()
1029
1240
  returns_section_lines: list[str] = []
@@ -1836,3 +2047,144 @@ def check_docstring_runon_sentence(content: str, file_path: str) -> list[str]:
1836
2047
  if len(issues) >= MAX_DOCSTRING_RUNON_SENTENCE_ISSUES:
1837
2048
  break
1838
2049
  return issues[:MAX_DOCSTRING_RUNON_SENTENCE_ISSUES]
2050
+
2051
+
2052
+ def _raises_section_text(docstring_text: str) -> str:
2053
+ docstring_lines = docstring_text.splitlines()
2054
+ raises_section_lines: list[str] = []
2055
+ inside_raises_section = False
2056
+ for each_line in docstring_lines:
2057
+ stripped_line = each_line.strip()
2058
+ if stripped_line == "Raises:":
2059
+ inside_raises_section = True
2060
+ continue
2061
+ if not inside_raises_section:
2062
+ continue
2063
+ if _is_docstring_terminating_section_header(stripped_line):
2064
+ break
2065
+ raises_section_lines.append(stripped_line)
2066
+ return " ".join(raises_section_lines)
2067
+
2068
+
2069
+ def _call_argument_by_keyword_or_position(
2070
+ call_node: ast.Call, keyword_name: str, positional_index: int
2071
+ ) -> ast.expr | None:
2072
+ for each_keyword in call_node.keywords:
2073
+ if each_keyword.arg == keyword_name:
2074
+ return each_keyword.value
2075
+ if positional_index >= len(call_node.args):
2076
+ return None
2077
+ if any(
2078
+ isinstance(each_argument, ast.Starred)
2079
+ for each_argument in call_node.args[: positional_index + 1]
2080
+ ):
2081
+ return None
2082
+ return call_node.args[positional_index]
2083
+
2084
+
2085
+ def _call_opens_zipfile_write_mode_writer(call_node: ast.Call) -> bool:
2086
+ callee = call_node.func
2087
+ if isinstance(callee, ast.Attribute):
2088
+ callee_name = callee.attr
2089
+ elif isinstance(callee, ast.Name):
2090
+ callee_name = callee.id
2091
+ else:
2092
+ return False
2093
+ if callee_name != ZIPFILE_WRITER_CLASS_NAME:
2094
+ return False
2095
+ mode_argument = _call_argument_by_keyword_or_position(
2096
+ call_node, ZIPFILE_MODE_KEYWORD, ZIPFILE_MODE_POSITIONAL_INDEX
2097
+ )
2098
+ return (
2099
+ isinstance(mode_argument, ast.Constant)
2100
+ and mode_argument.value in ALL_ZIPFILE_WRITE_MODE_VALUES
2101
+ )
2102
+
2103
+
2104
+ def _zipfile_writer_forbids_zip64(call_node: ast.Call) -> bool:
2105
+ allow_zip64_argument = _call_argument_by_keyword_or_position(
2106
+ call_node, ZIPFILE_ALLOW_ZIP64_KEYWORD, ZIPFILE_ALLOW_ZIP64_POSITIONAL_INDEX
2107
+ )
2108
+ return (
2109
+ isinstance(allow_zip64_argument, ast.Constant)
2110
+ and allow_zip64_argument.value is False
2111
+ )
2112
+
2113
+
2114
+ def _function_documents_unraisable_largezipfile(
2115
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
2116
+ docstring_text: str,
2117
+ ) -> bool:
2118
+ if DOCSTRING_LARGE_ZIP_FILE_EXCEPTION_NAME not in _raises_section_text(docstring_text):
2119
+ return False
2120
+ write_mode_writers = [
2121
+ each_descendant
2122
+ for each_descendant in _walk_skipping_nested_functions(function_node)
2123
+ if isinstance(each_descendant, ast.Call)
2124
+ and _call_opens_zipfile_write_mode_writer(each_descendant)
2125
+ ]
2126
+ if not write_mode_writers:
2127
+ return False
2128
+ return not any(
2129
+ _zipfile_writer_forbids_zip64(each_writer) for each_writer in write_mode_writers
2130
+ )
2131
+
2132
+
2133
+ def check_docstring_raises_unraisable_largezipfile(
2134
+ content: str, file_path: str
2135
+ ) -> list[str]:
2136
+ """Flag a Raises clause naming LargeZipFile over a default-ZIP64 writer.
2137
+
2138
+ The drift this catches: a function whose docstring Raises clause lists
2139
+ ``zipfile.LargeZipFile`` while the function opens its ``zipfile.ZipFile``
2140
+ writer in a write mode (``w``/``a``/``x``) with ``allowZip64`` left at its
2141
+ default of True. The stdlib raises ``LargeZipFile`` only when an entry needs
2142
+ ZIP64 AND ``allowZip64`` is False; with ZIP64 permitted the writer
2143
+ transparently uses it and never raises. The Raises entry then documents an
2144
+ exception the body cannot produce, so a caller guarding ``LargeZipFile`` on
2145
+ the strength of the docstring guards an unreachable path. This is the
2146
+ deterministic slice of Category O6 docstring-prose-vs-implementation drift
2147
+ where a writer opened with default ZIP64 disagrees with a LargeZipFile
2148
+ Raises clause.
2149
+
2150
+ The check binds only when the function opens at least one write-mode
2151
+ ``ZipFile`` and every such writer permits ZIP64, so a function that forbids
2152
+ ZIP64 on any writer (``allowZip64=False``, by keyword or position), a
2153
+ read-only open, and a function that opens no writer — where the exception may
2154
+ propagate from a callee — are all left alone.
2155
+
2156
+ Args:
2157
+ content: The source text to inspect.
2158
+ file_path: The path the source will be written to, used for exemptions.
2159
+
2160
+ Returns:
2161
+ One issue per function whose LargeZipFile Raises clause names an
2162
+ unreachable exception, capped at the module limit.
2163
+ """
2164
+ if is_test_file(file_path) or is_hook_infrastructure(file_path):
2165
+ return []
2166
+ try:
2167
+ parsed_tree = ast.parse(content)
2168
+ except SyntaxError:
2169
+ return []
2170
+ issues: list[str] = []
2171
+ for each_node in _walk_skipping_type_checking_blocks(parsed_tree):
2172
+ if not isinstance(each_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2173
+ continue
2174
+ if _function_has_exempt_decorator(each_node):
2175
+ continue
2176
+ docstring_text = _function_docstring_text(each_node)
2177
+ if not docstring_text:
2178
+ continue
2179
+ if not _function_documents_unraisable_largezipfile(each_node, docstring_text):
2180
+ continue
2181
+ issues.append(
2182
+ f"Line {each_node.lineno}: {each_node.name}() docstring Raises lists "
2183
+ "zipfile.LargeZipFile, but the function opens its ZipFile writer with ZIP64 "
2184
+ "permitted (allowZip64 defaults to True) — LargeZipFile raises only when "
2185
+ "allowZip64 is False, so drop the entry or pass allowZip64=False "
2186
+ "(Category O6 docstring-vs-implementation drift)"
2187
+ )
2188
+ if len(issues) >= MAX_DOCSTRING_RAISES_LARGEZIPFILE_ISSUES:
2189
+ break
2190
+ return issues[:MAX_DOCSTRING_RAISES_LARGEZIPFILE_ISSUES]
@@ -64,6 +64,9 @@ from code_rules_dead_dataclass_field import ( # noqa: E402
64
64
  from code_rules_dead_module_constant import ( # noqa: E402
65
65
  check_dead_module_constants,
66
66
  )
67
+ from code_rules_dead_split_branch import ( # noqa: E402
68
+ check_dead_split_truthiness_branch,
69
+ )
67
70
  from code_rules_docstrings import ( # noqa: E402
68
71
  check_class_docstring_names_public_methods,
69
72
  check_docstring_args_match_signature,
@@ -74,6 +77,8 @@ from code_rules_docstrings import ( # noqa: E402
74
77
  check_docstring_names_undefined_constant,
75
78
  check_docstring_no_consumer_claim,
76
79
  check_docstring_no_inline_literal_claim,
80
+ check_docstring_punctuation_mark_enumeration_coverage,
81
+ check_docstring_raises_unraisable_largezipfile,
77
82
  check_docstring_returns_plural_cardinality,
78
83
  check_docstring_runon_sentence,
79
84
  check_docstring_step_enumeration_dispatch_coverage,
@@ -116,6 +121,9 @@ from code_rules_optional_params import ( # noqa: E402
116
121
  from code_rules_orphan_css_class import ( # noqa: E402
117
122
  check_orphan_css_classes,
118
123
  )
124
+ from code_rules_paired_test import ( # noqa: E402
125
+ check_public_function_missing_paired_test,
126
+ )
119
127
  from code_rules_paths_syspath import ( # noqa: E402
120
128
  check_hardcoded_user_paths,
121
129
  check_sys_path_insert_deduplication_guard,
@@ -131,6 +139,7 @@ from code_rules_string_magic import ( # noqa: E402
131
139
  check_inline_literal_collections,
132
140
  check_inline_tuple_string_magic,
133
141
  check_string_literal_magic,
142
+ check_whitespace_indentation_magic,
134
143
  )
135
144
  from code_rules_test_assertions import ( # noqa: E402
136
145
  check_constant_equality_tests,
@@ -305,6 +314,11 @@ def validate_content(
305
314
  all_issues.extend(
306
315
  check_docstring_tuple_enumeration_match(effective_content, file_path)
307
316
  )
317
+ all_issues.extend(
318
+ check_docstring_punctuation_mark_enumeration_coverage(
319
+ effective_content, file_path
320
+ )
321
+ )
308
322
  all_issues.extend(
309
323
  check_docstring_step_enumeration_dispatch_coverage(
310
324
  effective_content, file_path
@@ -313,6 +327,9 @@ def validate_content(
313
327
  all_issues.extend(
314
328
  check_docstring_returns_plural_cardinality(effective_content, file_path)
315
329
  )
330
+ all_issues.extend(
331
+ check_docstring_raises_unraisable_largezipfile(effective_content, file_path)
332
+ )
316
333
  all_issues.extend(
317
334
  check_docstring_cardinal_count_matches_constant_family(
318
335
  effective_content, file_path
@@ -373,6 +390,7 @@ def validate_content(
373
390
  all_issues.extend(
374
391
  check_dead_module_constants(content, file_path, full_file_content)
375
392
  )
393
+ all_issues.extend(check_dead_split_truthiness_branch(content, file_path))
376
394
  all_issues.extend(check_library_print(content, file_path))
377
395
  all_issues.extend(check_parameter_annotations(content, file_path))
378
396
  all_issues.extend(check_known_pytest_fixture_annotations(content, file_path))
@@ -388,10 +406,19 @@ def validate_content(
388
406
  defer_scope_to_caller,
389
407
  )
390
408
  )
409
+ all_issues.extend(
410
+ check_public_function_missing_paired_test(
411
+ effective_content,
412
+ file_path,
413
+ all_changed_lines,
414
+ defer_scope_to_caller,
415
+ )
416
+ )
391
417
  all_issues.extend(check_loop_variable_naming(content, file_path))
392
418
  all_issues.extend(check_inline_literal_collections(content, file_path))
393
419
  all_issues.extend(check_inline_tuple_string_magic(content, file_path))
394
420
  all_issues.extend(check_string_literal_magic(content, file_path))
421
+ all_issues.extend(check_whitespace_indentation_magic(content, file_path))
395
422
  all_issues.extend(check_orphan_css_classes(effective_content, file_path))
396
423
  check_incomplete_mocks(content, file_path)
397
424
  check_duplicated_format_patterns(content, file_path)