claude-dev-env 1.42.0 → 1.43.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.
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +1 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +293 -8
- package/_shared/pr-loop/scripts/fix_hookspath.py +96 -5
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +3 -16
- package/_shared/pr-loop/scripts/post_audit_thread.py +4 -4
- package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_permissions_constants.py +1 -1
- package/_shared/pr-loop/scripts/preflight.py +13 -31
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -16
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +3 -16
- package/_shared/pr-loop/scripts/tests/conftest.py +1 -51
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +4 -4
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_claude_settings_keys_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath_constants.py +6 -2
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +2 -2
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1 -2
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +4 -2
- package/_shared/pr-loop/scripts/tests/test_preflight.py +17 -52
- package/_shared/pr-loop/scripts/tests/test_preflight_constants.py +6 -2
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +2 -2
- package/agents/pr-description-writer.md +50 -140
- package/docs/PR_DESCRIPTION_GUIDE.md +101 -102
- package/hooks/_gh_pr_author_swap_utils.py +1 -1
- package/hooks/blocking/bot_mention_comment_blocker.py +4 -10
- package/hooks/blocking/code_rules_enforcer.py +217 -99
- package/hooks/blocking/code_rules_path_utils.py +8 -1
- package/hooks/blocking/destructive_command_blocker.py +1 -1
- package/hooks/blocking/es_exe_path_rewriter.py +7 -13
- package/hooks/blocking/gh_body_arg_blocker.py +6 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +5 -5
- package/hooks/blocking/gh_pr_author_restore.py +5 -5
- package/hooks/blocking/hedging_language_blocker.py +4 -10
- package/hooks/blocking/md_path_exemptions.py +205 -0
- package/hooks/blocking/md_to_html_blocker.py +48 -20
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -11
- package/hooks/blocking/pr_description_enforcer.py +626 -41
- package/hooks/blocking/question_to_user_enforcer.py +4 -10
- package/hooks/blocking/state_description_blocker.py +6 -12
- package/hooks/blocking/tdd_enforcer.py +1 -1
- package/hooks/blocking/test_bot_mention_comment_blocker.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer.py +3 -3
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -2
- package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +184 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +29 -29
- package/hooks/blocking/test_gh_body_arg_blocker.py +7 -8
- package/hooks/blocking/test_gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/test_gh_pr_author_restore.py +1 -1
- package/hooks/blocking/test_hedging_language_blocker.py +2 -2
- package/hooks/blocking/test_md_to_html_blocker.py +463 -8
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +1 -1
- package/hooks/blocking/test_pr_description_enforcer.py +1210 -13
- package/hooks/blocking/test_question_to_user_enforcer.py +1 -1
- package/hooks/blocking/windows_rmtree_blocker.py +5 -11
- package/hooks/diagnostic/hook_log_extractor.py +1 -1
- package/hooks/diagnostic/hook_log_init.py +1 -1
- package/hooks/diagnostic/hook_log_stop_wrapper.py +1 -1
- package/hooks/diagnostic/test_hook_log_extractor.py +1 -1
- package/hooks/diagnostic/test_hook_log_init.py +2 -2
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +1 -1
- package/hooks/git-hooks/gate_utils.py +1 -1
- package/hooks/git-hooks/pre_commit.py +1 -1
- package/hooks/git-hooks/pre_push.py +1 -1
- package/hooks/git-hooks/test_config.py +5 -5
- package/hooks/git-hooks/test_pre_push.py +6 -6
- package/hooks/{config → hooks_constants}/code_rules_enforcer_constants.py +37 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +28 -0
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +82 -0
- package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_state.py +1 -1
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +154 -0
- package/hooks/{config → hooks_constants}/pre_tool_use_stdin.py +1 -1
- package/hooks/{config → hooks_constants}/project_paths_reader.py +2 -2
- package/hooks/{config → hooks_constants}/test_banned_identifiers_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_dynamic_stderr_handler.py +1 -1
- package/hooks/{config → hooks_constants}/test_hardcoded_user_path_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_hook_log_extractor_constants.py +2 -2
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +110 -0
- package/hooks/{config → hooks_constants}/test_messages.py +2 -6
- package/hooks/{config → hooks_constants}/test_path_rewriter_constants.py +1 -1
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +292 -0
- package/hooks/{config → hooks_constants}/test_pre_tool_use_stdin.py +2 -2
- package/hooks/{config → hooks_constants}/test_project_paths_reader.py +3 -3
- package/hooks/{config → hooks_constants}/test_session_env_cleanup_constants.py +1 -1
- package/hooks/{config → hooks_constants}/test_setup_project_paths_constants.py +2 -2
- package/hooks/{config → hooks_constants}/test_unused_module_import_constants.py +1 -1
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +5 -11
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +1 -1
- package/hooks/session/gh_pr_author_session_cleanup.py +5 -6
- package/hooks/session/session_env_cleanup.py +4 -10
- package/hooks/session/test_gh_pr_author_session_cleanup.py +1 -1
- package/hooks/session/test_untracked_repo_detector.py +2 -2
- package/hooks/session/untracked_repo_detector.py +6 -12
- package/hooks/test__gh_pr_author_swap_utils.py +1 -1
- package/hooks/validators/run_all_validators.py +16 -5
- package/hooks/validators/test_output_formatter.py +46 -0
- package/hooks/workflow/doc_gist_auto_publish.py +1 -1
- package/hooks/workflow/md_to_html_companion.py +8 -15
- package/hooks/workflow/test_md_to_html_companion.py +184 -23
- package/package.json +1 -1
- package/rules/ask-user-question-required.md +1 -1
- package/rules/vault-context.md +1 -1
- package/scripts/{config → dev_env_scripts_constants}/timing.py +1 -1
- package/scripts/setup_project_paths.py +49 -11
- package/scripts/sweep_empty_dirs.py +10 -1
- package/scripts/test_setup_project_paths.py +2 -2
- package/scripts/test_sweep_empty_dirs.py +2 -6
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +1 -1
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +1 -1
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +2 -2
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +2 -2
- package/skills/bugteam/PROMPTS.md +1 -1
- package/skills/bugteam/SKILL.md +1 -1
- package/skills/bugteam/reference/github-pr-reviews.md +1 -1
- package/skills/bugteam/scripts/{_claude_permissions_common.py → _bugteam_permissions_common.py} +1 -13
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +1 -13
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +1 -16
- package/skills/bugteam/scripts/bugteam_preflight.py +1 -13
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +2 -8
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +1 -1
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +2 -8
- package/skills/bugteam/scripts/{test__claude_permissions_common.py → test__bugteam_permissions_common.py} +4 -4
- package/skills/bugteam/scripts/test_agent_config_carveout.py +2 -2
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -26
- package/skills/bugteam/scripts/{test_claude_permissions_common.py → test_bugteam_permissions_common.py} +3 -66
- package/skills/bugteam/scripts/test_bugteam_preflight.py +2 -27
- package/skills/bugteam/scripts/windows_safe_rmtree.py +1 -1
- package/skills/doc-gist/SKILL.md +1 -1
- package/skills/doc-gist/scripts/gist_upload.py +1 -1
- package/skills/implement/SKILL.md +2 -2
- package/skills/implement/scripts/append_note.py +1 -1
- package/skills/pr-converge/pr_converge_skill_constants/__init__.py +0 -0
- package/skills/pr-converge/{config → pr_converge_skill_constants}/constants.py +1 -1
- package/skills/pr-converge/scripts/check_bugbot_ci.py +1 -1
- package/skills/pr-converge/scripts/check_convergence.py +11 -4
- package/skills/pr-converge/scripts/check_pending_reviews.py +1 -1
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +1 -1
- package/skills/pr-converge/scripts/post_fix_reply.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/__init__.py +0 -0
- package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/pr_converge_constants.py +1 -1
- package/skills/pr-converge/scripts/reflow_skill_md.py +90 -16
- package/skills/pr-converge/scripts/test_check_convergence.py +18 -0
- package/skills/pr-converge/scripts/test_reflow_skill_md.py +0 -31
- package/skills/session-log/SKILL.md +98 -233
- package/hooks/config/pr_description_enforcer_constants.py +0 -19
- package/hooks/config/test_pr_description_enforcer_constants.py +0 -82
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +0 -55
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +0 -55
- package/skills/pr-converge/scripts/conftest.py +0 -60
- package/skills/pr-converge/scripts/evict_cached_config_modules.py +0 -20
- package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +0 -22
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/__init__.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/claude_settings_keys_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/code_rules_gate_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/fix_hookspath_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/post_audit_thread_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/preflight_constants.py +0 -0
- /package/_shared/pr-loop/scripts/{config → pr_loop_shared_constants}/reviews_disabled_constants.py +0 -0
- /package/hooks/git-hooks/{config.py → git_hooks_constants/__init__.py} +0 -0
- /package/hooks/{config → hooks_constants}/__init__.py +0 -0
- /package/hooks/{config → hooks_constants}/any_type_config.py +0 -0
- /package/hooks/{config → hooks_constants}/banned_identifiers_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/blocking_check_limits.py +0 -0
- /package/hooks/{config → hooks_constants}/bot_mention_comment_blocker_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/convergence_branch_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/doc_gist_auto_publish_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/dynamic_stderr_handler.py +0 -0
- /package/hooks/{config → hooks_constants}/gh_pr_author_swap_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/hardcoded_user_path_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/hook_log_extractor_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/html_companion_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/inline_tuple_string_magic_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/messages.py +0 -0
- /package/hooks/{config → hooks_constants}/path_rewriter_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/pr_converge_bugteam_enforcer_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/session_env_cleanup_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/setup_project_paths_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/state_description_blocker_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/stuttering_check_config.py +0 -0
- /package/hooks/{config → hooks_constants}/stuttering_import_binding_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/sys_path_insert_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/unused_module_import_constants.py +0 -0
- /package/hooks/{config → hooks_constants}/windows_rmtree_blocker_constants.py +0 -0
- /package/{skills/_shared/pr-loop/scripts/config → hooks/lifecycle}/__init__.py +0 -0
- /package/{skills/bugteam/scripts/config → hooks/session}/__init__.py +0 -0
- /package/scripts/{config → dev_env_scripts_constants}/__init__.py +0 -0
- /package/skills/{doc-gist/scripts/config → _shared/pr-loop/scripts/skills_pr_loop_constants}/__init__.py +0 -0
- /package/skills/_shared/pr-loop/scripts/{config → skills_pr_loop_constants}/path_resolver_constants.py +0 -0
- /package/skills/{implement/scripts/config → bugteam/scripts/bugteam_scripts_constants}/__init__.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_code_rules_gate_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_fix_hookspath_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/bugteam_preflight_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/claude_permissions_common_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/probe_code_rules_enforcer_check_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/reflow_skill_md_constants.py +0 -0
- /package/skills/bugteam/scripts/{config → bugteam_scripts_constants}/windows_safe_rmtree_constants.py +0 -0
- /package/skills/{pr-converge/config → doc-gist/scripts/doc_gist_scripts_constants}/__init__.py +0 -0
- /package/skills/doc-gist/scripts/{config → doc_gist_scripts_constants}/gist_upload_constants.py +0 -0
- /package/skills/{pr-converge/scripts/config → implement/scripts/implement_scripts_constants}/__init__.py +0 -0
- /package/skills/implement/scripts/{config → implement_scripts_constants}/notes_constants.py +0 -0
- /package/skills/pr-converge/scripts/{config → pr_converge_scripts_constants}/reflow_skill_md_constants.py +0 -0
|
@@ -15,17 +15,11 @@ import re
|
|
|
15
15
|
import sys
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
19
|
+
if _hooks_dir not in sys.path:
|
|
20
|
+
sys.path.insert(0, _hooks_dir)
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
hooks_tree = Path(__file__).resolve().parent.parent
|
|
21
|
-
hooks_tree_string = str(hooks_tree)
|
|
22
|
-
if hooks_tree_string not in sys.path:
|
|
23
|
-
sys.path.insert(0, hooks_tree_string)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_insert_hooks_tree_for_imports()
|
|
27
|
-
|
|
28
|
-
from config.messages import USER_FACING_ASKUSERQUESTION_NOTICE
|
|
22
|
+
from hooks_constants.messages import USER_FACING_ASKUSERQUESTION_NOTICE # noqa: E402
|
|
29
23
|
|
|
30
24
|
|
|
31
25
|
def strip_code_and_quotes(text: str) -> str:
|
|
@@ -6,23 +6,17 @@ Enforces the "describe current state only" rule — no "instead of", "previously
|
|
|
6
6
|
describe what IS, not what WAS or what CHANGED.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import io
|
|
10
9
|
import json
|
|
11
10
|
import os
|
|
12
11
|
import sys
|
|
13
12
|
from pathlib import Path
|
|
13
|
+
from typing import TextIO
|
|
14
14
|
|
|
15
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent)
|
|
16
|
+
if _hooks_dir not in sys.path:
|
|
17
|
+
sys.path.insert(0, _hooks_dir)
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
hooks_tree = Path(__file__).absolute().parent.parent
|
|
18
|
-
hooks_tree_string = str(hooks_tree)
|
|
19
|
-
if hooks_tree_string not in sys.path:
|
|
20
|
-
sys.path.insert(0, hooks_tree_string)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
_insert_hooks_tree_for_imports()
|
|
24
|
-
|
|
25
|
-
from config.state_description_blocker_constants import (
|
|
19
|
+
from hooks_constants.state_description_blocker_constants import ( # noqa: E402
|
|
26
20
|
ALL_BLOCK_COMMENT_EXTENSIONS,
|
|
27
21
|
ALL_BLOCK_COMMENT_ONLY_EXTENSIONS,
|
|
28
22
|
ALL_COMMENT_BEARING_EXTENSIONS,
|
|
@@ -233,7 +227,7 @@ def main() -> None:
|
|
|
233
227
|
sys.exit(0)
|
|
234
228
|
|
|
235
229
|
|
|
236
|
-
def _emit_hook_result(all_hook_data: dict, output_stream:
|
|
230
|
+
def _emit_hook_result(all_hook_data: dict, output_stream: TextIO) -> None:
|
|
237
231
|
"""Write the hook result JSON to the given output stream."""
|
|
238
232
|
output_stream.write(json.dumps(all_hook_data) + "\n")
|
|
239
233
|
output_stream.flush()
|
|
@@ -18,7 +18,7 @@ _hooks_root_path_string = str(Path(__file__).resolve().parent.parent)
|
|
|
18
18
|
if _hooks_root_path_string not in sys.path:
|
|
19
19
|
sys.path.insert(0, _hooks_root_path_string)
|
|
20
20
|
|
|
21
|
-
from
|
|
21
|
+
from hooks_constants.messages import USER_FACING_TDD_NOTICE
|
|
22
22
|
|
|
23
23
|
PRODUCTION_EXTENSIONS = {'.py', '.ts', '.tsx', '.js', '.jsx'}
|
|
24
24
|
SKIP_PATTERNS = {
|
|
@@ -22,7 +22,7 @@ hook_spec.loader.exec_module(hook_module)
|
|
|
22
22
|
_detect_bot_mention = hook_module._detect_bot_mention
|
|
23
23
|
_body_contains_token = hook_module._body_contains_token
|
|
24
24
|
|
|
25
|
-
from
|
|
25
|
+
from hooks_constants.bot_mention_comment_blocker_constants import (
|
|
26
26
|
CORRECTIVE_MESSAGE_COPILOT,
|
|
27
27
|
CORRECTIVE_MESSAGE_CURSOR,
|
|
28
28
|
CURSOR_MENTION_TOKEN,
|
|
@@ -40,18 +40,18 @@ if str(_HOOKS_TREE_DIR) not in sys.path:
|
|
|
40
40
|
sys.path.insert(0, str(_HOOKS_TREE_DIR))
|
|
41
41
|
|
|
42
42
|
from code_rules_path_utils import is_config_file as path_utils_is_config_file # noqa: E402
|
|
43
|
-
from
|
|
43
|
+
from hooks_constants.banned_identifiers_constants import ( # noqa: E402
|
|
44
44
|
ALL_BANNED_IDENTIFIERS as config_all_banned_identifiers,
|
|
45
45
|
BANNED_IDENTIFIER_MESSAGE_SUFFIX as config_banned_identifier_message_suffix,
|
|
46
46
|
BANNED_IDENTIFIER_SKIP_ADVISORY as config_banned_identifier_skip_advisory,
|
|
47
47
|
MAX_BANNED_IDENTIFIER_ISSUES as config_max_banned_identifier_issues,
|
|
48
48
|
)
|
|
49
|
-
from
|
|
49
|
+
from hooks_constants.hardcoded_user_path_constants import ( # noqa: E402
|
|
50
50
|
HARDCODED_USER_PATH_GUIDANCE as config_hardcoded_user_path_guidance,
|
|
51
51
|
HARDCODED_USER_PATH_PATTERN as config_hardcoded_user_path_pattern,
|
|
52
52
|
MAX_HARDCODED_USER_PATH_ISSUES as config_max_hardcoded_user_path_issues,
|
|
53
53
|
)
|
|
54
|
-
from
|
|
54
|
+
from hooks_constants.stuttering_check_config import ( # noqa: E402
|
|
55
55
|
MAX_STUTTERING_PREFIX_ISSUES as config_max_stuttering_prefix_issues,
|
|
56
56
|
STUTTERING_ALL_PREFIX_PATTERN as config_stuttering_all_prefix_pattern,
|
|
57
57
|
)
|
|
@@ -56,7 +56,7 @@ def test_should_still_flag_in_regular_module() -> None:
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def test_any_type_config_module_exists_and_exposes_constant() -> None:
|
|
59
|
-
config_module_path = Path(__file__).parent.parent / "
|
|
59
|
+
config_module_path = Path(__file__).parent.parent / "hooks_constants" / "any_type_config.py"
|
|
60
60
|
assert config_module_path.is_file(), f"Missing: {config_module_path}"
|
|
61
61
|
spec = importlib.util.spec_from_file_location("any_type_config_under_test", config_module_path)
|
|
62
62
|
assert spec is not None
|
|
@@ -48,8 +48,6 @@ KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW: frozenset[str] = frozenset(
|
|
|
48
48
|
"check_boolean_naming",
|
|
49
49
|
"check_collection_prefix",
|
|
50
50
|
"check_comment_changes",
|
|
51
|
-
"check_comments_javascript",
|
|
52
|
-
"check_comments_python",
|
|
53
51
|
"check_constant_equality_tests",
|
|
54
52
|
"check_constants_outside_config",
|
|
55
53
|
"check_constants_outside_config_advisory",
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Tests pinning string-aware ``#`` detection in the Python comment checks.
|
|
2
|
+
|
|
3
|
+
``#`` characters appear inside string literals in three common shapes:
|
|
4
|
+
hex color codes (``"#FFFFFF"``), URL fragments
|
|
5
|
+
(``"https://x#section"``), and f-string interpolation patterns. None of
|
|
6
|
+
those ``#`` characters belong to a comment token. ``check_comments_python``
|
|
7
|
+
and the Python branch of ``extract_comment_texts`` route their ``#``
|
|
8
|
+
detection through ``tokenize.generate_tokens`` so only true ``COMMENT``
|
|
9
|
+
tokens are considered. These tests pin both halves of that contract:
|
|
10
|
+
``#``-in-strings is exempt; real inline comments that land AFTER such
|
|
11
|
+
a string still flag.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib.util
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from types import ModuleType
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _load_enforcer_module() -> ModuleType:
|
|
22
|
+
module_path = Path(__file__).parent / "code_rules_enforcer.py"
|
|
23
|
+
spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
|
|
24
|
+
assert spec is not None
|
|
25
|
+
assert spec.loader is not None
|
|
26
|
+
module = importlib.util.module_from_spec(spec)
|
|
27
|
+
spec.loader.exec_module(module)
|
|
28
|
+
return module
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
code_rules_enforcer = _load_enforcer_module()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_python_check_should_not_flag_hex_color_literal() -> None:
|
|
35
|
+
content = 'palette_primary = "#FFFFFF"\n'
|
|
36
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
37
|
+
assert issues == []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_python_check_should_not_flag_url_fragment_in_string() -> None:
|
|
41
|
+
content = 'docs_link = "https://example.com/guide#installation"\n'
|
|
42
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
43
|
+
assert issues == []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_python_check_should_not_flag_hash_inside_fstring_interpolation() -> None:
|
|
47
|
+
content = 'rendered = f"prefix #{count} suffix"\n'
|
|
48
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
49
|
+
assert issues == []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_python_check_should_not_flag_hash_inside_triple_quoted_string() -> None:
|
|
53
|
+
content = 'message = """use # for inline comments"""\n'
|
|
54
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
55
|
+
assert issues == []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_python_check_should_flag_real_inline_comment_after_string_with_hash() -> None:
|
|
59
|
+
content = 'name = "user" # this comment must still flag\n'
|
|
60
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
61
|
+
assert len(issues) == 1
|
|
62
|
+
assert "Comment found" in issues[0]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_python_check_should_flag_real_comment_after_hex_color_literal() -> None:
|
|
66
|
+
content = 'palette_primary = "#FFFFFF" # accidentally added comment\n'
|
|
67
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
68
|
+
assert len(issues) == 1
|
|
69
|
+
assert "Comment found" in issues[0]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_extract_should_not_classify_hex_color_as_inline_comment() -> None:
|
|
73
|
+
content = 'palette_primary = "#FFFFFF"\n'
|
|
74
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
75
|
+
assert inline == set()
|
|
76
|
+
assert standalone == set()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_extract_should_classify_real_inline_comment_after_string_with_hash() -> None:
|
|
80
|
+
content = 'name = "user" # real comment\n'
|
|
81
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
82
|
+
assert len(inline) == 1
|
|
83
|
+
assert "# real comment" in next(iter(inline))
|
|
84
|
+
assert standalone == set()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_extract_should_classify_standalone_comment_correctly() -> None:
|
|
88
|
+
content = "# standalone comment\nx = 1\n"
|
|
89
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
90
|
+
assert "# standalone comment" in standalone
|
|
91
|
+
assert inline == set()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_extract_should_distinguish_inline_from_standalone_in_same_file() -> None:
|
|
95
|
+
content = '# standalone first\nx = "#FFFFFF" # inline real comment\n# standalone second\n'
|
|
96
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
97
|
+
assert "# inline real comment" in next(iter(inline))
|
|
98
|
+
assert "# standalone first" in standalone
|
|
99
|
+
assert "# standalone second" in standalone
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_check_comment_changes_skips_removal_when_new_python_un_parseable() -> None:
|
|
103
|
+
old_content = 'x = 1 # existing comment\n'
|
|
104
|
+
new_content = '"""unterminated multi-line string\n'
|
|
105
|
+
issues = code_rules_enforcer.check_comment_changes(old_content, new_content, "foo.py")
|
|
106
|
+
assert issues == []
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_check_comment_changes_skips_removal_when_old_python_un_parseable() -> None:
|
|
110
|
+
old_content = '"""unterminated multi-line string\n'
|
|
111
|
+
new_content = 'x = 1 # newly added comment\n'
|
|
112
|
+
issues = code_rules_enforcer.check_comment_changes(old_content, new_content, "foo.py")
|
|
113
|
+
assert issues == []
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_check_comment_changes_still_detects_removal_on_parseable_python() -> None:
|
|
117
|
+
old_content = 'x = 1 # existing comment\n'
|
|
118
|
+
new_content = 'x = 1\n'
|
|
119
|
+
issues = code_rules_enforcer.check_comment_changes(old_content, new_content, "foo.py")
|
|
120
|
+
assert any("Existing comment removed" in each_issue for each_issue in issues)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_python_check_should_exempt_directive_without_space_after_hash() -> None:
|
|
124
|
+
for each_directive in ("#noqa", "#type: ignore", "#pylint: disable", "#pragma: no cover"):
|
|
125
|
+
content = f"{each_directive}\n"
|
|
126
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
127
|
+
assert issues == [], f"expected exempt for {each_directive!r}"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_python_check_should_exempt_directive_with_tab_after_hash() -> None:
|
|
131
|
+
content = "#\tnoqa: F401\n"
|
|
132
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
133
|
+
assert issues == []
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_python_check_should_still_flag_unrelated_no_space_comment() -> None:
|
|
137
|
+
content = "#realcomment text\n"
|
|
138
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
139
|
+
assert len(issues) == 1
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_extract_should_exempt_directive_without_space_after_hash() -> None:
|
|
143
|
+
content = "#noqa\nx = 1\n"
|
|
144
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
145
|
+
assert standalone == set()
|
|
146
|
+
assert inline == set()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_python_check_should_exempt_bare_hash_comment() -> None:
|
|
150
|
+
for each_content in ("#\n", "# \n", "x = 1 #\n"):
|
|
151
|
+
issues = code_rules_enforcer.check_comments_python(each_content)
|
|
152
|
+
assert issues == [], f"expected exempt for bare hash in {each_content!r}"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_extract_should_not_classify_bare_hash_as_comment() -> None:
|
|
156
|
+
content = "#\nx = 1 #\n"
|
|
157
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
158
|
+
assert standalone == set()
|
|
159
|
+
assert inline == set()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_python_check_should_exempt_true_shebang_on_line_one() -> None:
|
|
163
|
+
content = "#!/usr/bin/env python3\nx = 1\n"
|
|
164
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
165
|
+
assert issues == []
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_python_check_should_flag_inline_shebang_lookalike() -> None:
|
|
169
|
+
content = "x = 1 #! hidden comment\n"
|
|
170
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
171
|
+
assert len(issues) == 1
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_python_check_should_flag_shebang_on_later_standalone_line() -> None:
|
|
175
|
+
content = "x = 1\n#! not a real shebang\n"
|
|
176
|
+
issues = code_rules_enforcer.check_comments_python(content)
|
|
177
|
+
assert len(issues) == 1
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_extract_should_classify_inline_shebang_lookalike_as_inline_comment() -> None:
|
|
181
|
+
content = "x = 1 #! hidden\n"
|
|
182
|
+
inline, standalone = code_rules_enforcer.extract_comment_texts(content, "foo.py")
|
|
183
|
+
assert inline != set()
|
|
184
|
+
assert standalone == set()
|
|
@@ -110,3 +110,85 @@ def test_should_track_only_innermost_type_checking_block() -> None:
|
|
|
110
110
|
assert len(issues) == 1
|
|
111
111
|
assert issues[0].startswith("Line 7:")
|
|
112
112
|
assert "Import inside function" in issues[0]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_should_skip_docstring_lines_starting_with_import_keyword() -> None:
|
|
116
|
+
"""Docstring sentences that incidentally start with ``from `` or ``import `` after
|
|
117
|
+
stripping must not trigger the import-inside-function check.
|
|
118
|
+
"""
|
|
119
|
+
content = (
|
|
120
|
+
"def helper():\n"
|
|
121
|
+
' """Apply the priority queue atomically.\n'
|
|
122
|
+
"\n"
|
|
123
|
+
" from a rename within the trailing-revenue window the duplicate\n"
|
|
124
|
+
" import the loaders for the cycle so the writer can advance.\n"
|
|
125
|
+
' """\n'
|
|
126
|
+
" return 42\n"
|
|
127
|
+
)
|
|
128
|
+
issues = check_imports_at_top(content)
|
|
129
|
+
assert issues == []
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_should_still_flag_real_import_after_docstring_closes() -> None:
|
|
133
|
+
"""An actual import statement after a one-line docstring closes must still flag."""
|
|
134
|
+
content = (
|
|
135
|
+
"def helper():\n"
|
|
136
|
+
' """One-line docstring."""\n'
|
|
137
|
+
" import os\n"
|
|
138
|
+
" return os\n"
|
|
139
|
+
)
|
|
140
|
+
issues = check_imports_at_top(content)
|
|
141
|
+
assert len(issues) == 1
|
|
142
|
+
assert "Import inside function" in issues[0]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_should_skip_triple_single_quoted_docstring_lines() -> None:
|
|
146
|
+
"""Triple-single-quote (''') docstrings exempt their interior lines too."""
|
|
147
|
+
content = (
|
|
148
|
+
"def helper():\n"
|
|
149
|
+
" '''Apply the cycle reset.\n"
|
|
150
|
+
"\n"
|
|
151
|
+
" from a rename within the cycle window the writer would advance.\n"
|
|
152
|
+
" '''\n"
|
|
153
|
+
" return 1\n"
|
|
154
|
+
)
|
|
155
|
+
issues = check_imports_at_top(content)
|
|
156
|
+
assert issues == []
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_should_flag_real_import_after_multi_line_docstring_closes() -> None:
|
|
160
|
+
"""Real imports landing AFTER a multi-line docstring closes must still flag,
|
|
161
|
+
confirming the triple-quote state correctly transitions back to ``None``.
|
|
162
|
+
"""
|
|
163
|
+
content = (
|
|
164
|
+
"def helper():\n"
|
|
165
|
+
' """Apply the priority queue atomically.\n'
|
|
166
|
+
"\n"
|
|
167
|
+
" from a rename within the cycle window.\n"
|
|
168
|
+
' """\n'
|
|
169
|
+
" import os\n"
|
|
170
|
+
" return os\n"
|
|
171
|
+
)
|
|
172
|
+
issues = check_imports_at_top(content)
|
|
173
|
+
assert len(issues) == 1
|
|
174
|
+
assert "Import inside function" in issues[0]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_should_skip_module_level_docstring_text() -> None:
|
|
178
|
+
"""A module-level docstring containing ``from ``/``import `` text must not flag.
|
|
179
|
+
|
|
180
|
+
The check ignores top-level lines regardless of triple-quote state because
|
|
181
|
+
function-tracking is the only path that produces issues, but this exercises
|
|
182
|
+
the entry-condition path where the docstring opens on line 1.
|
|
183
|
+
"""
|
|
184
|
+
content = (
|
|
185
|
+
'"""Module docstring opener.\n'
|
|
186
|
+
"\n"
|
|
187
|
+
"from a rename within the trailing-revenue window.\n"
|
|
188
|
+
'"""\n'
|
|
189
|
+
"\n"
|
|
190
|
+
"def helper():\n"
|
|
191
|
+
" return 1\n"
|
|
192
|
+
)
|
|
193
|
+
issues = check_imports_at_top(content)
|
|
194
|
+
assert issues == []
|
|
@@ -39,7 +39,7 @@ TEST_FILE_PATH = "packages/app/tests/test_loader.py"
|
|
|
39
39
|
|
|
40
40
|
def test_should_flag_unused_from_import() -> None:
|
|
41
41
|
source = (
|
|
42
|
-
"from
|
|
42
|
+
"from hooks_constants.preflight_constants import VENV_DIRECTORY_NAME\n"
|
|
43
43
|
"\n"
|
|
44
44
|
"def run() -> None:\n"
|
|
45
45
|
" return None\n"
|
|
@@ -52,7 +52,7 @@ def test_should_flag_unused_from_import() -> None:
|
|
|
52
52
|
|
|
53
53
|
def test_should_not_flag_used_from_import() -> None:
|
|
54
54
|
source = (
|
|
55
|
-
"from
|
|
55
|
+
"from hooks_constants.preflight_constants import VENV_DIRECTORY_NAME\n"
|
|
56
56
|
"\n"
|
|
57
57
|
"def run() -> str:\n"
|
|
58
58
|
" return VENV_DIRECTORY_NAME\n"
|
|
@@ -79,7 +79,7 @@ def test_should_not_flag_when_alias_is_used() -> None:
|
|
|
79
79
|
|
|
80
80
|
def test_should_skip_file_with_dunder_all() -> None:
|
|
81
81
|
source = (
|
|
82
|
-
"from
|
|
82
|
+
"from hooks_constants.preflight_constants import VENV_DIRECTORY_NAME\n"
|
|
83
83
|
"\n"
|
|
84
84
|
"__all__ = ['something']\n"
|
|
85
85
|
)
|
|
@@ -91,7 +91,7 @@ def test_should_skip_file_with_dunder_all() -> None:
|
|
|
91
91
|
|
|
92
92
|
def test_should_skip_file_with_dunder_all_annotated_assignment() -> None:
|
|
93
93
|
source = (
|
|
94
|
-
"from
|
|
94
|
+
"from hooks_constants.preflight_constants import VENV_DIRECTORY_NAME\n"
|
|
95
95
|
"\n"
|
|
96
96
|
'__all__: list[str] = ["VENV_DIRECTORY_NAME"]\n'
|
|
97
97
|
)
|
|
@@ -105,7 +105,7 @@ def test_should_skip_file_with_dunder_all_annotated_assignment() -> None:
|
|
|
105
105
|
def test_should_skip_file_using_type_checking_block() -> None:
|
|
106
106
|
source = (
|
|
107
107
|
"from typing import TYPE_CHECKING\n"
|
|
108
|
-
"from
|
|
108
|
+
"from hooks_constants.constants import UNUSED_NAME\n"
|
|
109
109
|
"\n"
|
|
110
110
|
"if TYPE_CHECKING:\n"
|
|
111
111
|
" from somewhere import OtherName\n"
|
|
@@ -121,7 +121,7 @@ def test_should_skip_file_using_type_checking_block() -> None:
|
|
|
121
121
|
|
|
122
122
|
def test_should_skip_test_files() -> None:
|
|
123
123
|
source = (
|
|
124
|
-
"from
|
|
124
|
+
"from hooks_constants.constants import UNUSED_NAME\n"
|
|
125
125
|
"\n"
|
|
126
126
|
"def test_thing() -> None:\n"
|
|
127
127
|
" assert True\n"
|
|
@@ -139,7 +139,7 @@ def test_should_handle_syntax_error_gracefully() -> None:
|
|
|
139
139
|
def test_should_include_line_number_in_issue() -> None:
|
|
140
140
|
source = (
|
|
141
141
|
"\n"
|
|
142
|
-
"from
|
|
142
|
+
"from hooks_constants.preflight_constants import VENV_DIRECTORY_NAME\n"
|
|
143
143
|
"\n"
|
|
144
144
|
"def run() -> None:\n"
|
|
145
145
|
" return None\n"
|
|
@@ -152,7 +152,7 @@ def test_should_include_line_number_in_issue() -> None:
|
|
|
152
152
|
|
|
153
153
|
def test_should_flag_each_unused_in_multi_import() -> None:
|
|
154
154
|
source = (
|
|
155
|
-
"from
|
|
155
|
+
"from hooks_constants.constants import USED_ONE, UNUSED_TWO\n"
|
|
156
156
|
"\n"
|
|
157
157
|
"def run() -> str:\n"
|
|
158
158
|
" return USED_ONE\n"
|
|
@@ -178,7 +178,7 @@ def test_should_not_flag_when_referenced_in_annotation() -> None:
|
|
|
178
178
|
|
|
179
179
|
def test_should_skip_noqa_marked_imports() -> None:
|
|
180
180
|
source = (
|
|
181
|
-
"from
|
|
181
|
+
"from hooks_constants.constants import UNUSED_BUT_DELIBERATE # noqa: F401\n"
|
|
182
182
|
"\n"
|
|
183
183
|
"def run() -> None:\n"
|
|
184
184
|
" return None\n"
|
|
@@ -191,7 +191,7 @@ def test_should_skip_noqa_marked_imports() -> None:
|
|
|
191
191
|
|
|
192
192
|
def test_should_skip_noqa_on_from_keyword_line_for_multiline_import() -> None:
|
|
193
193
|
source = (
|
|
194
|
-
"from
|
|
194
|
+
"from hooks_constants.constants import ( # noqa: F401\n"
|
|
195
195
|
" SOME_CONSTANT,\n"
|
|
196
196
|
" ANOTHER_CONSTANT,\n"
|
|
197
197
|
")\n"
|
|
@@ -261,7 +261,7 @@ def test_should_flag_when_name_only_appears_in_comment() -> None:
|
|
|
261
261
|
|
|
262
262
|
def test_should_not_skip_when_type_checking_only_in_string_constant() -> None:
|
|
263
263
|
source = (
|
|
264
|
-
'from
|
|
264
|
+
'from hooks_constants.constants import UNUSED_NAME\n'
|
|
265
265
|
'\n'
|
|
266
266
|
'HELP_TEXT = "See TYPE_CHECKING docs"\n'
|
|
267
267
|
'\n'
|
|
@@ -276,7 +276,7 @@ def test_should_not_skip_when_type_checking_only_in_string_constant() -> None:
|
|
|
276
276
|
|
|
277
277
|
def test_should_flag_when_noqa_lists_only_non_f401_codes() -> None:
|
|
278
278
|
source = (
|
|
279
|
-
"from
|
|
279
|
+
"from hooks_constants.constants import UNUSED # noqa: E402\n"
|
|
280
280
|
"\n"
|
|
281
281
|
"def run() -> None:\n"
|
|
282
282
|
" return None\n"
|
|
@@ -289,7 +289,7 @@ def test_should_flag_when_noqa_lists_only_non_f401_codes() -> None:
|
|
|
289
289
|
|
|
290
290
|
def test_should_skip_when_noqa_is_bare() -> None:
|
|
291
291
|
source = (
|
|
292
|
-
"from
|
|
292
|
+
"from hooks_constants.constants import UNUSED # noqa\n"
|
|
293
293
|
"\n"
|
|
294
294
|
"def run() -> None:\n"
|
|
295
295
|
" return None\n"
|
|
@@ -299,9 +299,9 @@ def test_should_skip_when_noqa_is_bare() -> None:
|
|
|
299
299
|
|
|
300
300
|
|
|
301
301
|
def test_should_not_flag_imports_referenced_only_in_full_file_content() -> None:
|
|
302
|
-
fragment = "from
|
|
302
|
+
fragment = "from hooks_constants.constants import NEW_NAME\n"
|
|
303
303
|
full_file = (
|
|
304
|
-
"from
|
|
304
|
+
"from hooks_constants.constants import NEW_NAME\n"
|
|
305
305
|
"\n"
|
|
306
306
|
"def existing_function() -> str:\n"
|
|
307
307
|
" return NEW_NAME\n"
|
|
@@ -315,9 +315,9 @@ def test_should_not_flag_imports_referenced_only_in_full_file_content() -> None:
|
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def test_should_flag_imports_unused_in_full_file_content() -> None:
|
|
318
|
-
fragment = "from
|
|
318
|
+
fragment = "from hooks_constants.constants import TRULY_UNUSED\n"
|
|
319
319
|
full_file = (
|
|
320
|
-
"from
|
|
320
|
+
"from hooks_constants.constants import TRULY_UNUSED\n"
|
|
321
321
|
"\n"
|
|
322
322
|
"def existing_function() -> None:\n"
|
|
323
323
|
" return None\n"
|
|
@@ -331,10 +331,10 @@ def test_should_flag_imports_unused_in_full_file_content() -> None:
|
|
|
331
331
|
|
|
332
332
|
|
|
333
333
|
def test_should_only_flag_imports_in_fragment_not_full_file() -> None:
|
|
334
|
-
fragment = "from
|
|
334
|
+
fragment = "from hooks_constants.constants import FRAGMENT_IMPORT\n"
|
|
335
335
|
full_file = (
|
|
336
|
-
"from
|
|
337
|
-
"from
|
|
336
|
+
"from hooks_constants.other import PRE_EXISTING_UNUSED\n"
|
|
337
|
+
"from hooks_constants.constants import FRAGMENT_IMPORT\n"
|
|
338
338
|
"\n"
|
|
339
339
|
"def existing_function() -> str:\n"
|
|
340
340
|
" return FRAGMENT_IMPORT\n"
|
|
@@ -349,9 +349,9 @@ def test_should_only_flag_imports_in_fragment_not_full_file() -> None:
|
|
|
349
349
|
|
|
350
350
|
|
|
351
351
|
def test_should_skip_when_full_file_declares_dunder_all() -> None:
|
|
352
|
-
fragment = "from
|
|
352
|
+
fragment = "from hooks_constants.constants import NEW_NAME\n"
|
|
353
353
|
full_file = (
|
|
354
|
-
"from
|
|
354
|
+
"from hooks_constants.constants import NEW_NAME\n"
|
|
355
355
|
"\n"
|
|
356
356
|
"__all__ = ['NEW_NAME']\n"
|
|
357
357
|
)
|
|
@@ -365,10 +365,10 @@ def test_should_skip_when_full_file_declares_dunder_all() -> None:
|
|
|
365
365
|
|
|
366
366
|
|
|
367
367
|
def test_should_skip_when_full_file_uses_type_checking_gate() -> None:
|
|
368
|
-
fragment = "from
|
|
368
|
+
fragment = "from hooks_constants.constants import NEW_NAME\n"
|
|
369
369
|
full_file = (
|
|
370
370
|
"from typing import TYPE_CHECKING\n"
|
|
371
|
-
"from
|
|
371
|
+
"from hooks_constants.constants import NEW_NAME\n"
|
|
372
372
|
"\n"
|
|
373
373
|
"if TYPE_CHECKING:\n"
|
|
374
374
|
" from somewhere import OtherName\n"
|
|
@@ -387,7 +387,7 @@ def test_should_skip_when_full_file_uses_type_checking_gate() -> None:
|
|
|
387
387
|
|
|
388
388
|
def test_should_fall_back_to_content_when_full_file_content_is_none() -> None:
|
|
389
389
|
source = (
|
|
390
|
-
"from
|
|
390
|
+
"from hooks_constants.constants import VENV_DIRECTORY_NAME\n"
|
|
391
391
|
"\n"
|
|
392
392
|
"def run() -> None:\n"
|
|
393
393
|
" return None\n"
|
|
@@ -400,7 +400,7 @@ def test_should_fall_back_to_content_when_full_file_content_is_none() -> None:
|
|
|
400
400
|
|
|
401
401
|
|
|
402
402
|
def test_should_fall_back_when_full_file_content_has_syntax_error() -> None:
|
|
403
|
-
fragment = "from
|
|
403
|
+
fragment = "from hooks_constants.constants import NEW_NAME\n"
|
|
404
404
|
full_file_with_syntax_error = "from config import (\n not python\n"
|
|
405
405
|
issues = check_unused_module_level_imports(
|
|
406
406
|
fragment, PRODUCTION_FILE_PATH, full_file_content=full_file_with_syntax_error,
|
|
@@ -471,7 +471,7 @@ def test_should_flag_import_when_only_shadowed_local_name_is_loaded() -> None:
|
|
|
471
471
|
def test_should_skip_when_type_checking_uses_imported_alias() -> None:
|
|
472
472
|
source = (
|
|
473
473
|
"from typing import TYPE_CHECKING as IS_TYPE_CHECKING\n"
|
|
474
|
-
"from
|
|
474
|
+
"from hooks_constants.constants import UNUSED_NAME\n"
|
|
475
475
|
"\n"
|
|
476
476
|
"if IS_TYPE_CHECKING:\n"
|
|
477
477
|
" from somewhere import OtherName\n"
|
|
@@ -488,7 +488,7 @@ def test_should_skip_when_type_checking_uses_imported_alias() -> None:
|
|
|
488
488
|
def test_should_skip_when_type_checking_uses_module_alias() -> None:
|
|
489
489
|
source = (
|
|
490
490
|
"import typing as t\n"
|
|
491
|
-
"from
|
|
491
|
+
"from hooks_constants.constants import UNUSED_NAME\n"
|
|
492
492
|
"\n"
|
|
493
493
|
"if t.TYPE_CHECKING:\n"
|
|
494
494
|
" from somewhere import OtherName\n"
|
|
@@ -517,7 +517,7 @@ def test_should_not_flag_when_referenced_in_quoted_annotation() -> None:
|
|
|
517
517
|
|
|
518
518
|
def test_should_flag_when_noqa_only_appears_inside_string_literal() -> None:
|
|
519
519
|
source = (
|
|
520
|
-
"from
|
|
520
|
+
"from hooks_constants.constants import UNUSED; MARKER = '# noqa: F401'\n"
|
|
521
521
|
"\n"
|
|
522
522
|
"def run() -> None:\n"
|
|
523
523
|
" return None\n"
|
|
@@ -19,7 +19,13 @@ hook_spec.loader.exec_module(hook_module)
|
|
|
19
19
|
_uses_body_string_arg = hook_module._uses_body_string_arg
|
|
20
20
|
_has_backtick = hook_module._has_backtick
|
|
21
21
|
|
|
22
|
-
from _gh_body_arg_utils import
|
|
22
|
+
from blocking._gh_body_arg_utils import (
|
|
23
|
+
_all_equals_prefixes_for_skip,
|
|
24
|
+
_quoted_value_starts_split,
|
|
25
|
+
all_body_flag_prefixes,
|
|
26
|
+
count_extra_tokens_to_skip_for_split_quoted_value,
|
|
27
|
+
iter_significant_tokens,
|
|
28
|
+
)
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
def test_blocks_issue_create_with_body_string() -> None:
|
|
@@ -337,40 +343,33 @@ def test_space_form_value_flag_remaining_excludes_consumed_value() -> None:
|
|
|
337
343
|
|
|
338
344
|
|
|
339
345
|
def test_quoted_value_starts_split_unclosed_single_quote() -> None:
|
|
340
|
-
from _gh_body_arg_utils import _quoted_value_starts_split
|
|
341
346
|
assert _quoted_value_starts_split("'it") is True
|
|
342
347
|
|
|
343
348
|
|
|
344
349
|
def test_quoted_value_starts_split_fully_closed() -> None:
|
|
345
|
-
from _gh_body_arg_utils import _quoted_value_starts_split
|
|
346
350
|
assert _quoted_value_starts_split("'hello'") is False
|
|
347
351
|
|
|
348
352
|
|
|
349
353
|
def test_quoted_value_starts_split_double_quote_unclosed() -> None:
|
|
350
|
-
from _gh_body_arg_utils import _quoted_value_starts_split
|
|
351
354
|
assert _quoted_value_starts_split('"hello') is True
|
|
352
355
|
|
|
353
356
|
|
|
354
357
|
def test_count_extra_tokens_returns_none_when_exhausted() -> None:
|
|
355
|
-
from _gh_body_arg_utils import count_extra_tokens_to_skip_for_split_quoted_value
|
|
356
358
|
result = count_extra_tokens_to_skip_for_split_quoted_value([], "'unclosed")
|
|
357
359
|
assert result is None
|
|
358
360
|
|
|
359
361
|
|
|
360
362
|
def test_count_extra_tokens_returns_none_no_closing_in_remaining() -> None:
|
|
361
|
-
from _gh_body_arg_utils import count_extra_tokens_to_skip_for_split_quoted_value
|
|
362
363
|
result = count_extra_tokens_to_skip_for_split_quoted_value(["word", "another"], "'unclosed")
|
|
363
364
|
assert result is None
|
|
364
365
|
|
|
365
366
|
|
|
366
367
|
def test_count_extra_tokens_returns_zero_for_self_contained() -> None:
|
|
367
|
-
from _gh_body_arg_utils import count_extra_tokens_to_skip_for_split_quoted_value
|
|
368
368
|
result = count_extra_tokens_to_skip_for_split_quoted_value(["next"], "'complete'")
|
|
369
369
|
assert result == 0
|
|
370
370
|
|
|
371
371
|
|
|
372
372
|
def test_all_body_flag_prefixes_used_for_equals_skip() -> None:
|
|
373
|
-
from _gh_body_arg_utils import _all_equals_prefixes_for_skip, all_body_flag_prefixes
|
|
374
373
|
for each_prefix in all_body_flag_prefixes:
|
|
375
374
|
assert each_prefix in _all_equals_prefixes_for_skip
|
|
376
375
|
|