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:
parent
0216fd6523
commit
9dda5f4562
37 changed files with 2521 additions and 234 deletions
|
|
@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use crate::core::index::Index;
|
||||
use crate::core::plan::Rewrite;
|
||||
use crate::core::{JmoveError, JmoveResult};
|
||||
use crate::core::{JmoveError, JmoveResult, rel_str};
|
||||
|
||||
/// Rewrite set for moving `source.java` to `target.java` (both
|
||||
/// project-relative, validated by the caller).
|
||||
|
|
@ -28,7 +28,7 @@ pub(super) fn java_rewrites(
|
|||
if target.extension().is_none_or(|e| e != "java") {
|
||||
return Err(rejected(format!(
|
||||
"'{}' is a .java file, the target must keep the .java extension",
|
||||
source.display()
|
||||
rel_str(source)
|
||||
)));
|
||||
}
|
||||
let sdir = source.parent().unwrap_or(Path::new(""));
|
||||
|
|
@ -40,7 +40,7 @@ pub(super) fn java_rewrites(
|
|||
} else {
|
||||
Err(rejected(format!(
|
||||
"'{}' has no `package` declaration (default package); it can only be renamed inside its directory",
|
||||
source.display()
|
||||
rel_str(source)
|
||||
)))
|
||||
};
|
||||
};
|
||||
|
|
@ -50,15 +50,15 @@ pub(super) fn java_rewrites(
|
|||
if !sdir.ends_with(Path::new(&pkg_path)) {
|
||||
return Err(rejected(format!(
|
||||
"package '{pkg}' does not match directory '{}'",
|
||||
sdir.display()
|
||||
rel_str(sdir)
|
||||
)));
|
||||
}
|
||||
let src_root = strip_package_dir(sdir, pkg);
|
||||
let rest = tdir.strip_prefix(&src_root).map_err(|_| {
|
||||
rejected(format!(
|
||||
"target directory '{}' is outside the Java source root '{}'",
|
||||
tdir.display(),
|
||||
src_root.display()
|
||||
rel_str(tdir),
|
||||
rel_str(&src_root)
|
||||
))
|
||||
})?;
|
||||
let new_pkg = package_of(rest);
|
||||
|
|
@ -124,7 +124,7 @@ fn file_stem(path: &Path) -> JmoveResult<String> {
|
|||
.and_then(|s| s.to_str())
|
||||
.map(str::to_owned)
|
||||
.ok_or_else(|| {
|
||||
JmoveError::InvalidArgument(format!("invalid file name '{}'", path.display()))
|
||||
JmoveError::InvalidArgument(format!("invalid file name '{}'", rel_str(path)))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ fn move_between_packages_rewrites_package_and_all_importers() {
|
|||
rewrites
|
||||
.iter()
|
||||
.map(|r| (
|
||||
r.file.display().to_string(),
|
||||
crate::core::rel_str(&r.file),
|
||||
r.old_text.clone(),
|
||||
r.new_text.clone()
|
||||
))
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use std::ops::Range;
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::core::index::Index;
|
||||
use crate::core::{JmoveError, JmoveResult, normalize_rel_path};
|
||||
use crate::core::{Edit, JmoveError, JmoveResult, normalize_rel_path, rel_str};
|
||||
use crate::parser::SourceLanguage;
|
||||
|
||||
/// One in-file replacement of an import specifier. Only the specifier text
|
||||
|
|
@ -32,6 +32,16 @@ pub struct Rewrite {
|
|||
pub new_text: String,
|
||||
}
|
||||
|
||||
impl From<&Rewrite> for Edit {
|
||||
fn from(rewrite: &Rewrite) -> Self {
|
||||
Edit {
|
||||
span: rewrite.span.clone(),
|
||||
old_text: rewrite.old_text.clone(),
|
||||
new_text: rewrite.new_text.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete plan for moving `source` to `target`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MovePlan {
|
||||
|
|
@ -60,7 +70,7 @@ pub fn plan_move(index: &Index, source: &Path, target: &Path) -> JmoveResult<Mov
|
|||
};
|
||||
let (source, target) = (rel("source path", source)?, rel("target path", target)?);
|
||||
if !index.files.contains(&source) {
|
||||
let s = source.display();
|
||||
let s = rel_str(&source);
|
||||
return Err(JmoveError::InvalidArgument(format!(
|
||||
"source '{s}' is not an indexed file"
|
||||
)));
|
||||
|
|
@ -71,7 +81,7 @@ pub fn plan_move(index: &Index, source: &Path, target: &Path) -> JmoveResult<Mov
|
|||
));
|
||||
}
|
||||
if index.files.contains(&target) {
|
||||
let t = target.display();
|
||||
let t = rel_str(&target);
|
||||
return Err(JmoveError::PlanRejected(format!(
|
||||
"target '{t}' already exists"
|
||||
)));
|
||||
|
|
@ -141,7 +151,7 @@ mod tests {
|
|||
let keys: Vec<(String, usize)> = plan
|
||||
.rewrites
|
||||
.iter()
|
||||
.map(|r| (r.file.display().to_string(), r.span.start))
|
||||
.map(|r| (crate::core::rel_str(&r.file), r.span.start))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
keys,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue