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.
68 lines
2.2 KiB
Rust
68 lines
2.2 KiB
Rust
//! `mv` warns (never edits) about non-import references: markdown links,
|
|
//! `package.json` fields and `jest.mock` strings survive silent unless the
|
|
//! user sees them. JSON payload carries the same list for agents.
|
|
|
|
mod common;
|
|
|
|
use common::{copy_fixture, in_root, jmove, read};
|
|
use predicates::prelude::*;
|
|
|
|
fn dry_run_refs() -> String {
|
|
let tmp = copy_fixture("typescript", "refs");
|
|
let out = jmove(
|
|
&tmp,
|
|
&["mv", "lib/sum.ts", "lib/total.ts", "--dry-run", "--json"],
|
|
)
|
|
.success()
|
|
.stdout(predicate::str::contains("\"non_import_refs\""))
|
|
.get_output()
|
|
.stdout
|
|
.clone();
|
|
String::from_utf8(out).expect("json is utf-8")
|
|
}
|
|
|
|
#[test]
|
|
fn json_dry_run_lists_hidden_references_and_skips_the_noise() {
|
|
let out = dry_run_refs();
|
|
for expected in [
|
|
"\"file\": \"README.md\"",
|
|
"\"file\": \"package.json\"",
|
|
"\"file\": \"__tests__/sum.test.ts\"",
|
|
] {
|
|
assert!(out.contains(expected), "missing {expected} in {out}");
|
|
}
|
|
for absent in [
|
|
"\"file\": \"app.ts\"",
|
|
"\"file\": \"package-lock.json\"",
|
|
"\"file\": \".notes/refs.md\"",
|
|
"\"file\": \"lib/summary.ts\"",
|
|
] {
|
|
assert!(!out.contains(absent), "leaked {absent} in {out}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn human_dry_run_warns_on_stderr_and_exits_zero() {
|
|
let tmp = copy_fixture("typescript", "refs");
|
|
jmove(&tmp, &["mv", "lib/sum.ts", "lib/total.ts", "--dry-run"])
|
|
.success()
|
|
.stderr(
|
|
predicate::str::contains("non-import reference")
|
|
.and(predicate::str::contains("README.md:1"))
|
|
.and(predicate::str::contains("__tests__/sum.test.ts:1")),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn real_move_still_succeeds_and_still_warns() {
|
|
let tmp = copy_fixture("typescript", "refs");
|
|
jmove(&tmp, &["mv", "lib/sum.ts", "lib/total.ts"])
|
|
.success()
|
|
.stderr(predicate::str::contains("may need manual fixing"));
|
|
assert!(in_root(tmp.path(), "lib/total.ts").exists());
|
|
let app = read(&in_root(tmp.path(), "app.ts"));
|
|
assert!(app.contains("from './lib/total'"), "{app}");
|
|
// The scanner warns; only the import graph is rewritten.
|
|
let readme = read(&in_root(tmp.path(), "README.md"));
|
|
assert!(readme.contains("./lib/sum.ts"), "{readme}");
|
|
}
|