feat: warn about non-import references to moved files
The import graph only rewrites import/export-from/require. Files are also named from markdown links, package.json fields, jest.mock strings and tsconfig file lists, and a move that dangles those must not pass silently. mv now scans text formats plus source strings for the moved paths, module names and old specifier forms (word-boundary matched, lockfiles/hidden dirs skipped) and reports each occurrence once per line on stderr and as non_import_refs[] in --json (dry-run included). References are never edited; exit codes are unchanged. Also moves normalize_scope into Index (its only consumer) and the Change/ChangedFile JSON structs into output where they are built, to stay inside the 250-line-per-file budget.
This commit is contained in:
parent
b1af375763
commit
1ddf79b508
21 changed files with 557 additions and 81 deletions
|
|
@ -8,9 +8,11 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::core::plan::MovePlan;
|
||||
use crate::core::refs::NonImportRef;
|
||||
use crate::core::{JmoveError, rel_str};
|
||||
|
||||
use super::output;
|
||||
use super::output::ChangedFile;
|
||||
use super::output::{self};
|
||||
|
||||
/// Top-level envelope for every `--json` response.
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -107,26 +109,6 @@ impl ErrorData {
|
|||
}
|
||||
}
|
||||
|
||||
/// One rewritten import inside a changed file.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Change {
|
||||
/// 1-based line of the rewritten specifier.
|
||||
pub line: usize,
|
||||
/// Specifier text before the move.
|
||||
pub old: String,
|
||||
/// Specifier text after the move.
|
||||
pub new: String,
|
||||
}
|
||||
|
||||
/// A file whose imports were rewritten, with line-level change details.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ChangedFile {
|
||||
/// Project-relative path of the importer.
|
||||
pub path: String,
|
||||
/// Rewritten specifiers, in source order.
|
||||
pub changes: Vec<Change>,
|
||||
}
|
||||
|
||||
/// Success payload of `mv --json` (flattened under `status: "ok"`).
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MvData {
|
||||
|
|
@ -142,6 +124,9 @@ pub struct MvData {
|
|||
pub updated_imports: usize,
|
||||
/// Rename backend: `"git"` (every rename staged in the index) or `"fs"`.
|
||||
pub moved_via: &'static str,
|
||||
/// Non-import textual references left unfixed (omitted when none).
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub non_import_refs: Vec<NonImportRef>,
|
||||
/// Directory moves only: each `(from, to)` relocation (omitted for file moves).
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub moved_files: Vec<FileMoveData>,
|
||||
|
|
@ -180,7 +165,12 @@ fn dir_moves(plan: &MovePlan) -> Vec<FileMoveData> {
|
|||
impl MvData {
|
||||
/// Assemble the payload from an applied plan and its change details.
|
||||
#[must_use]
|
||||
pub fn new(plan: &MovePlan, changed_files: Vec<ChangedFile>, via_git: bool) -> Self {
|
||||
pub fn new(
|
||||
plan: &MovePlan,
|
||||
changed_files: Vec<ChangedFile>,
|
||||
via_git: bool,
|
||||
non_import_refs: Vec<NonImportRef>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source: rel_str(&plan.source),
|
||||
target: rel_str(&plan.target),
|
||||
|
|
@ -188,6 +178,7 @@ impl MvData {
|
|||
moved: plan.moves.len(),
|
||||
updated_imports: plan.rewrites.len(),
|
||||
moved_via: if via_git { "git" } else { "fs" },
|
||||
non_import_refs,
|
||||
// A single-file move keeps the old contract: no extra fields.
|
||||
moved_files: dir_moves(plan),
|
||||
left_behind: plan.left_behind.iter().map(|p| rel_str(p)).collect(),
|
||||
|
|
@ -211,6 +202,9 @@ pub struct MvDryRunData {
|
|||
pub diff: String,
|
||||
/// Rename backend a real run would use: `"git"` or `"fs"`.
|
||||
pub would_move_via: &'static str,
|
||||
/// Non-import references a real run would leave unfixed.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub non_import_refs: Vec<NonImportRef>,
|
||||
/// Directory moves only: every relocation that would happen.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub would_move_files: Vec<FileMoveData>,
|
||||
|
|
@ -219,7 +213,12 @@ pub struct MvDryRunData {
|
|||
impl MvDryRunData {
|
||||
/// Assemble the preview payload from a plan and its rendered diff.
|
||||
#[must_use]
|
||||
pub fn new(plan: &MovePlan, diff: String, via_git: bool) -> Self {
|
||||
pub fn new(
|
||||
plan: &MovePlan,
|
||||
diff: String,
|
||||
via_git: bool,
|
||||
non_import_refs: Vec<NonImportRef>,
|
||||
) -> Self {
|
||||
Self {
|
||||
would_move: rel_str(&plan.source),
|
||||
target: rel_str(&plan.target),
|
||||
|
|
@ -231,6 +230,7 @@ impl MvDryRunData {
|
|||
diff,
|
||||
would_move_via: if via_git { "git" } else { "fs" },
|
||||
would_move_files: dir_moves(plan),
|
||||
non_import_refs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue