chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
"""Tools 17, 18: refactor_func, apply_refactor_func."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..hints import generate_hints, get_session
|
||||
from ..incremental import find_project_root
|
||||
from ..refactor import (
|
||||
apply_refactor,
|
||||
find_dead_code,
|
||||
rename_preview,
|
||||
suggest_refactorings,
|
||||
)
|
||||
from ._common import _get_store, _validate_repo_root
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool 17: refactor_tool [REFACTOR]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def refactor_func(
|
||||
mode: str = "rename",
|
||||
old_name: str | None = None,
|
||||
new_name: str | None = None,
|
||||
kind: str | None = None,
|
||||
file_pattern: str | None = None,
|
||||
repo_root: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Unified refactoring entry point.
|
||||
|
||||
[REFACTOR] Supports three modes:
|
||||
- ``rename``: Preview renaming a symbol (requires *old_name* and
|
||||
*new_name*).
|
||||
- ``dead_code``: Find unreferenced functions/classes.
|
||||
- ``suggest``: Get community-driven refactoring suggestions.
|
||||
|
||||
Args:
|
||||
mode: One of ``"rename"``, ``"dead_code"``, or ``"suggest"``.
|
||||
old_name: (rename mode) Current symbol name.
|
||||
new_name: (rename mode) Desired new name.
|
||||
kind: (dead_code mode) Optional node kind filter.
|
||||
file_pattern: (dead_code mode) Optional file path substring filter.
|
||||
repo_root: Repository root path. Auto-detected if omitted.
|
||||
|
||||
Returns:
|
||||
Mode-specific results dict.
|
||||
"""
|
||||
valid_modes = {"rename", "dead_code", "suggest"}
|
||||
if mode not in valid_modes:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": (
|
||||
f"Invalid mode '{mode}'. "
|
||||
f"Must be one of: {', '.join(sorted(valid_modes))}"
|
||||
),
|
||||
}
|
||||
|
||||
store, root = _get_store(repo_root)
|
||||
try:
|
||||
if mode == "rename":
|
||||
if not old_name or not new_name:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": (
|
||||
"rename mode requires both old_name and new_name."
|
||||
),
|
||||
}
|
||||
preview = rename_preview(store, old_name, new_name)
|
||||
if preview is None:
|
||||
return {
|
||||
"status": "not_found",
|
||||
"summary": f"No node found matching '{old_name}'.",
|
||||
}
|
||||
result = {
|
||||
"status": "ok",
|
||||
"summary": (
|
||||
f"Rename preview: {old_name} -> {new_name}, "
|
||||
f"{len(preview['edits'])} edit(s). "
|
||||
f"Use apply_refactor_tool(refactor_id="
|
||||
f"'{preview['refactor_id']}') to apply."
|
||||
),
|
||||
**preview,
|
||||
}
|
||||
result["_hints"] = generate_hints(
|
||||
"refactor", result, get_session()
|
||||
)
|
||||
return result
|
||||
|
||||
elif mode == "dead_code":
|
||||
dead = find_dead_code(
|
||||
store, kind=kind, file_pattern=file_pattern, root=root
|
||||
)
|
||||
result = {
|
||||
"status": "ok",
|
||||
"summary": f"Found {len(dead)} dead code symbol(s).",
|
||||
"dead_code": dead,
|
||||
"total": len(dead),
|
||||
}
|
||||
result["_hints"] = generate_hints(
|
||||
"refactor", result, get_session()
|
||||
)
|
||||
return result
|
||||
|
||||
else: # suggest
|
||||
suggestions = suggest_refactorings(store)
|
||||
result = {
|
||||
"status": "ok",
|
||||
"summary": (
|
||||
f"Generated {len(suggestions)} "
|
||||
"refactoring suggestion(s)."
|
||||
),
|
||||
"suggestions": suggestions,
|
||||
"total": len(suggestions),
|
||||
}
|
||||
result["_hints"] = generate_hints(
|
||||
"refactor", result, get_session()
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as exc:
|
||||
return {"status": "error", "error": str(exc)}
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool 18: apply_refactor_tool [REFACTOR]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def apply_refactor_func(
|
||||
refactor_id: str,
|
||||
repo_root: str | None = None,
|
||||
dry_run: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Apply a previously previewed refactoring to source files.
|
||||
|
||||
[REFACTOR] Validates the refactor_id, checks expiry, ensures all edit
|
||||
paths are within the repo root, then performs exact string replacements.
|
||||
|
||||
Args:
|
||||
refactor_id: ID returned by a prior ``refactor_tool(mode="rename")``
|
||||
call.
|
||||
repo_root: Repository root path. Auto-detected if omitted.
|
||||
dry_run: If True, return a unified diff of what would change
|
||||
without touching disk. The refactor_id remains valid so the
|
||||
user can review the diff, then call again with ``dry_run=False``
|
||||
to actually write the changes. See: #176
|
||||
|
||||
Returns:
|
||||
Status with count of applied edits and modified files. When
|
||||
``dry_run=True`` the response additionally contains ``would_modify``
|
||||
(list of file paths) and ``diffs`` (map of file -> unified-diff
|
||||
string).
|
||||
"""
|
||||
try:
|
||||
root = (
|
||||
_validate_repo_root(Path(repo_root))
|
||||
if repo_root
|
||||
else find_project_root()
|
||||
)
|
||||
except (RuntimeError, ValueError) as exc:
|
||||
return {"status": "error", "error": str(exc)}
|
||||
|
||||
result = apply_refactor(refactor_id, root, dry_run=dry_run)
|
||||
return result
|
||||
Reference in New Issue
Block a user