feat: fix engine (Phase 1.6) + git-aware mv + windows-safe path output

- Edit engine: generic span edits (replace/insert/delete) with stale-index
  guard; mv and fix share one atomic apply/rollback/diff/--json pipeline
- jmove mv|check|fix commands; java rules: unused-import, missing-import
  (unique-FQN insert, ambiguous -> candidates for agents), import-order
  (google style; cross-rule byte conflicts prune by severity and converge)
- mv of tracked files goes through git mv (staged rename, --no-git opt-out,
  moved_via/would_move_via in --json, GIT_ERROR code)
- fix java extract_package losing single-segment packages (bare identifier,
  not scoped_identifier) — broke the FQN index for 'package p;' projects
- core::rel_str: project-relative paths rendered with '/' on every platform
  (human/JSON/diff headers/git pathspecs), Path::display() no longer used
  for output; e2e fixtures: java/fix, java/fix_missing, java/order
This commit is contained in:
loki5512344 2026-09-15 16:10:04 +02:00
parent 0216fd6523
commit 9dda5f4562
Signed by: boba
GPG key ID: 253067914055423B
37 changed files with 2521 additions and 234 deletions

View file

@ -7,9 +7,9 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use crate::core::JmoveResult;
use crate::core::index::Index;
use crate::core::plan::{MovePlan, Rewrite};
use crate::core::{JmoveResult, rel_str};
use super::json::{BrokenImport, Change, ChangedFile};
@ -57,7 +57,7 @@ pub fn broken_imports(root: &Path, index: &Index) -> JmoveResult<Vec<BrokenImpor
}
let text = read_file(root, file)?;
broken.push(BrokenImport {
file: file.display().to_string(),
file: rel_str(file),
line: line_of(&text, import.record.span.start),
import: import.record.specifier.clone(),
reason: "file_not_found",
@ -89,21 +89,23 @@ pub fn changed_files(root: &Path, plan: &MovePlan) -> JmoveResult<Vec<ChangedFil
Ok(per_file
.into_iter()
.map(|(path, changes)| ChangedFile {
path: path.display().to_string(),
path: rel_str(path),
changes,
})
.collect())
}
/// `moved src -> tgt, updated N imports in M files` success summary.
/// `moved src -> tgt, updated N imports in M files` success summary,
/// noting when the rename went through `git mv`.
#[must_use]
pub fn mv_summary(plan: &MovePlan) -> String {
pub fn mv_summary(plan: &MovePlan, via_git: bool) -> String {
let imports = plan.rewrites.len();
let files = group_by_file(&plan.rewrites).len();
let git = if via_git { " (via git mv)" } else { "" };
format!(
"moved {} -> {}, updated {} {} in {} {}",
plan.source.display(),
plan.target.display(),
"moved {} -> {}{git}, updated {} {} in {} {}",
rel_str(&plan.source),
rel_str(&plan.target),
imports,
plural(imports, "import"),
files,
@ -136,7 +138,7 @@ pub fn print_error(message: &str, hint: Option<&str>) {
}
/// `N noun` with a naive English plural.
fn plural(count: usize, noun: &str) -> String {
pub(crate) fn plural(count: usize, noun: &str) -> String {
if count == 1 {
noun.to_owned()
} else {