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,10 +14,18 @@
|
|||
//! - Rewrites must touch **only the specifier string**, never the rest of
|
||||
//! the statement (KISS + no formatter dependency): that is why
|
||||
//! [`ImportRecord::span`] is a byte range into the original source.
|
||||
//! - Fix rules (Phase 1.6) implement [`Fix`] and propose [`FixCandidate`]s:
|
||||
//! byte [`Edit`](crate::core::Edit)s for one file, run through the same
|
||||
//! dry-run/atomic engine as `mv`. Deterministic by contract — ambiguity
|
||||
//! is reported as `auto_fixable: false` for an agent to resolve, never
|
||||
//! guessed at.
|
||||
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::core::Edit;
|
||||
use crate::core::index::Index;
|
||||
|
||||
pub mod java;
|
||||
pub mod resolve;
|
||||
pub mod ts;
|
||||
|
|
@ -111,3 +119,94 @@ pub fn frontend_for(lang: SourceLanguage) -> Box<dyn Language> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Urgency of a [`FixCandidate`] for the reader.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Severity {
|
||||
/// The code does not compile (or resolves) without the fix.
|
||||
Error,
|
||||
/// The code works but is dirty: unused or misordered imports.
|
||||
Warning,
|
||||
/// Style note only.
|
||||
Info,
|
||||
}
|
||||
|
||||
impl Severity {
|
||||
/// Stable lowercase name used in `--json` output.
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Error => "error",
|
||||
Self::Warning => "warning",
|
||||
Self::Info => "info",
|
||||
}
|
||||
}
|
||||
|
||||
/// Overlap-resolution priority: on a byte conflict the more urgent
|
||||
/// rule wins and the other candidate is deferred to the next run.
|
||||
#[must_use]
|
||||
pub const fn rank(self) -> u8 {
|
||||
match self {
|
||||
Self::Error => 0,
|
||||
Self::Warning => 1,
|
||||
Self::Info => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One proposed fix for a single file: what is wrong and — when
|
||||
/// `auto_fixable` — the exact edits that repair it.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FixCandidate {
|
||||
/// Stable rule id; also the value accepted by `jmove fix --rule`.
|
||||
pub rule: &'static str,
|
||||
/// One-line explanation for humans and agents.
|
||||
pub message: String,
|
||||
/// How urgent the fix is.
|
||||
pub severity: Severity,
|
||||
/// `false` marks an ambiguous finding an agent or human must resolve;
|
||||
/// the engine never applies such candidates automatically.
|
||||
pub auto_fixable: bool,
|
||||
/// Byte range of the offending construct (issue location for reports,
|
||||
/// even when `edits` is empty).
|
||||
pub span: Range<usize>,
|
||||
/// Edits on this file, ascending by span.
|
||||
pub edits: Vec<Edit>,
|
||||
/// Fully-qualified options for an ambiguous finding (`auto_fixable:
|
||||
/// false`); empty for every auto-fixable or non-lookup candidate.
|
||||
pub candidates: Vec<String>,
|
||||
}
|
||||
|
||||
/// A deterministic single-file fix rule (Phase 1.6).
|
||||
pub trait Fix: Send + Sync {
|
||||
/// Stable rule id reported in candidates and accepted by `--rule`.
|
||||
fn rule(&self) -> &'static str;
|
||||
|
||||
/// Candidates for `path` (project-relative) with the given contents.
|
||||
/// A rule may only return `auto_fixable: true` edits it can prove
|
||||
/// safe; anything ambiguous goes out as a non-auto candidate.
|
||||
fn fixes(&self, path: &Path, source: &str, index: &Index) -> Vec<FixCandidate>;
|
||||
}
|
||||
|
||||
/// Default rule set for `lang` (empty for languages without rules yet).
|
||||
#[must_use]
|
||||
pub fn fixers_for(lang: SourceLanguage) -> Vec<Box<dyn Fix>> {
|
||||
match lang {
|
||||
SourceLanguage::Java => vec![
|
||||
Box::new(java::rules::unused_imports::JavaUnusedImports::new()),
|
||||
Box::new(java::rules::missing_imports::JavaMissingImports::new()),
|
||||
Box::new(java::rules::import_order::JavaImportOrder::new()),
|
||||
],
|
||||
SourceLanguage::TypeScript | SourceLanguage::Tsx | SourceLanguage::JavaScript => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Every rule id `jmove fix` currently knows about.
|
||||
#[must_use]
|
||||
pub fn rule_ids() -> &'static [&'static str] {
|
||||
&[
|
||||
java::rules::unused_imports::RULE,
|
||||
java::rules::missing_imports::RULE,
|
||||
java::rules::import_order::RULE,
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue