chore: project skeleton — module contracts, docs plan, GPLv3
This commit is contained in:
commit
a33ed2eb30
20 changed files with 2220 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/jmove
|
||||
.jmove/
|
||||
695
Cargo.lock
generated
Normal file
695
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
"libc",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jmove"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"clap",
|
||||
"ignore",
|
||||
"predicates",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tree-sitter",
|
||||
"tree-sitter-typescript",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"float-cmp",
|
||||
"normalize-line-endings",
|
||||
"predicates-core",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f66ca1f7aca2474dc10c942eb22feffc897735f54cd1db90138c2fddb490987"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2038684e0058edba0d17302619f62eabce4a8e11c6ac59506996a8d79848851d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"streaming-iterator",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-language"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081"
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-typescript"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "jmove"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.104"
|
||||
clap = { version = "4.6.6", features = ["derive"] }
|
||||
ignore = "0.4.33"
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_json = "1.0.151"
|
||||
similar = "3.2.0"
|
||||
thiserror = "2.0.20"
|
||||
tree-sitter = "0.27.0"
|
||||
tree-sitter-typescript = "0.23.2"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.2.2"
|
||||
predicates = "3.1.4"
|
||||
tempfile = "3.27.0"
|
||||
674
LICENSE
Normal file
674
LICENSE
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
66
README.md
Normal file
66
README.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# jmove
|
||||
|
||||
**Move source files. Keep every import intact.**
|
||||
|
||||
`jmove` is a fast, single-binary CLI that moves/renames source files in a
|
||||
project and rewrites all import statements that reference them — no IDE,
|
||||
no language server, no runtime dependencies. Built for humans *and* AI
|
||||
agents.
|
||||
|
||||
## Why
|
||||
|
||||
`git mv` moves the file but breaks every relative import pointing at it.
|
||||
IDE renames fix imports but need an IDE. Existing CLI alternatives either
|
||||
shell out to heavy language servers (bun/ts-morph, Rope, gopls,
|
||||
rust-analyzer) or refuse to preview what they will change.
|
||||
|
||||
jmove's answer:
|
||||
|
||||
- **Dry-run + unified diff** — see every change before it hits disk.
|
||||
- **Atomic apply with rollback** — imports are rewritten first, the file
|
||||
moves last; any failure restores everything.
|
||||
- **AI-agent mode** — `--json` everywhere, stable error codes, exit codes,
|
||||
and a machine-readable skill doc (`docs/SKILL.md`).
|
||||
- **Pure Rust, one binary** — no external toolchains to install.
|
||||
- **Java support** — the package/directory coupling nobody else automates
|
||||
outside IntelliJ.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
cargo install jmove
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
# Preview what a move would change
|
||||
jmove mv src/utils/parser.ts src/core/parser.ts --dry-run
|
||||
|
||||
# Apply it
|
||||
jmove mv src/utils/parser.ts src/core/parser.ts
|
||||
|
||||
# Java: jmove updates `package`, all `import`s and moves the file
|
||||
jmove mv src/com/example/utils/Parser.java src/com/example/core/Parser.java
|
||||
|
||||
# Find broken imports (exit code 2 if any)
|
||||
jmove check
|
||||
|
||||
# AI-agent workflow
|
||||
jmove mv src/foo.ts src/bar/foo.ts --dry-run --json
|
||||
jmove mv src/foo.ts src/bar/foo.ts --json
|
||||
jmove check --json
|
||||
```
|
||||
|
||||
See [docs/EXAMPLES.md](docs/EXAMPLES.md) for more, and
|
||||
[docs/SKILL.md](docs/SKILL.md) if you are an AI agent.
|
||||
|
||||
## Roadmap
|
||||
|
||||
TypeScript/JavaScript first, then Java (open niche), Python, Go.
|
||||
`split` (automatic file decomposition) is planned — no tool does it.
|
||||
Full plan: [docs/PLAN.md](docs/PLAN.md).
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0-only. See [LICENSE](LICENSE).
|
||||
90
docs/PLAN.md
Normal file
90
docs/PLAN.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# jmove — Product Plan
|
||||
|
||||
## Vision
|
||||
|
||||
The `mv` for source code that never breaks imports: fast, safe (dry-run +
|
||||
atomic rollback), single static Rust binary — usable by humans in a
|
||||
terminal and by AI agents over `--json`, with zero external toolchains.
|
||||
|
||||
## Competitive landscape
|
||||
|
||||
The closest analog is **refac / ai_refac** (github: jav-ed/ai_refac):
|
||||
moves sources and updates imports/paths across the project, aimed at
|
||||
AI agents without an IDE.
|
||||
|
||||
| | refac/ai_refac | jmove |
|
||||
|---|---|---|
|
||||
| Languages | TS/JS (ts-morph+Bun), Python (Rope), Rust (rust-analyzer LSP), Go (gopls), Dart, Markdown | TS/JS → Java → Python/Go |
|
||||
| Under the hood | spawns external toolchains/LSPs per run | pure Rust, tree-sitter grammars in-process |
|
||||
| Dry-run | **none** — writes straight to disk | core feature, unified diff |
|
||||
| Atomicity/rollback | not described | rewrite-then-move, rollback on failure |
|
||||
| TS limit | 30 files per call | none (index-based) |
|
||||
| Go behaviour | moves whole package for any file | per-file moves |
|
||||
| Java | not supported, not planned | **open niche — headline feature** |
|
||||
| UX | agent-oriented CLI | human UX + first-class `--json` agent mode |
|
||||
|
||||
**Conclusion:** the idea is validated, the product is not. jmove wins on
|
||||
dry-run, speed (no LSP startup), human UX, single-binary distribution,
|
||||
the `split` command (nobody has it) and Java.
|
||||
|
||||
## Why Java is the wedge
|
||||
|
||||
Java imports are fully-qualified package paths hard-coupled to directory
|
||||
layout:
|
||||
|
||||
```
|
||||
src/com/example/utils/Parser.java
|
||||
→ package com.example.utils; // must change
|
||||
→ import com.example.utils.Parser; // must change in every importer
|
||||
→ + physical file move
|
||||
```
|
||||
|
||||
Three coordinated edits per move; outside IntelliJ nobody automates it.
|
||||
In headless/CI/agent contexts there is no IntelliJ. Implementation:
|
||||
tree-sitter Java grammar (mature) + directory⇄package convention +
|
||||
source-root detection (`src/main/java`, `src/`, ...). This is the feature
|
||||
that makes jmove *the* answer for "move a Java file in CI".
|
||||
|
||||
## AI-agent design
|
||||
|
||||
Existing tools ship a raw `.agents/skills/` folder; we do it properly:
|
||||
|
||||
- `--json` on every command. Envelope:
|
||||
`{ "status": "ok" | "dry_run" | "error", "operation": "...", ... }`
|
||||
- Stable error codes + actionable hints:
|
||||
`{ "status":"error", "code":"TARGET_EXISTS", "message":"...", "hint":"Use --force or choose another target" }`
|
||||
- `mv --dry-run --json` → `{ would_move, would_update, affected_files }`
|
||||
so agents preview → confirm → apply → `check --json`.
|
||||
- Exit codes: `0` ok · `1` error · `2` broken imports (from `check`).
|
||||
- `docs/SKILL.md` — machine-readable instructions an agent can drop into
|
||||
context: short, no fluff, workflow-first.
|
||||
|
||||
## Correctness principles
|
||||
|
||||
1. Rewrite **only the specifier string** (byte span), never reformat the
|
||||
statement — no formatter dependency, minimal diffs.
|
||||
2. Resolution must match the language's own lookup: TS extension guessing
|
||||
+ `index.*`; Java package⇄path; (Phase 2) tsconfig `paths` aliases.
|
||||
3. Imports are rewritten **before** the move; any error rolls back all
|
||||
writes. Prefer `git mv` when inside a repo to preserve history.
|
||||
4. Beyond imports, warn (don't silently rewrite) about other references:
|
||||
`package.json` exports, jest mocks, `tsconfig` includes, markdown links.
|
||||
|
||||
## Phases
|
||||
|
||||
- **Phase 1 (MVP)** — as in `todo.md`: TS/JS scan+index, graph, mv with
|
||||
dry-run/atomic apply. Add to MVP: relative-path resolution incl.
|
||||
`index.*` (without it the tool is toy-grade); `check` as the self-test.
|
||||
- **Phase 1.5 — Java.** The differentiator, as soon as the TS pipeline is
|
||||
proven. Also: `--json` + SKILL.md ship *with* Java, not after.
|
||||
- **Phase 2** — disk index cache (`.jmove/`), incremental reindex,
|
||||
tsconfig paths, rayon parallelism, `--git` integration.
|
||||
- **Phase 3** — Python, Go (per-file, unlike refac), `split` command.
|
||||
- **Later** — LSP server mode (become the thing refac spawns), watch mode,
|
||||
VS Code wrapper.
|
||||
|
||||
## Rules (binding, see todo.md)
|
||||
|
||||
KISS · DRY · SOLID · ≤250 lines/file · ≤4 files/folder · fmt+clippy
|
||||
`-D warnings` per commit · no dead code, no `#[allow(dead_code)]` ·
|
||||
English Javadoc-style comments on all public API.
|
||||
59
docs/SKILL.md
Normal file
59
docs/SKILL.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# jmove skill (for AI agents)
|
||||
|
||||
## What this tool does
|
||||
|
||||
Moves or renames source files inside a project and updates every import
|
||||
statement referencing them. Never breaks imports. Supported: TypeScript,
|
||||
JavaScript; Java/Python/Go on the roadmap. Single binary, no LSP needed.
|
||||
|
||||
## When to use
|
||||
|
||||
- The user asks to move/rename/reorganize source files or packages.
|
||||
- You refactored a directory layout and imports now point at nothing.
|
||||
- You need to verify the project has no broken imports (`check`).
|
||||
|
||||
## Commands
|
||||
|
||||
### mv — move a file and rewrite its importers
|
||||
|
||||
```
|
||||
jmove mv <source> <target> [--root DIR] [--dry-run] [--json] [--force]
|
||||
```
|
||||
|
||||
Always run `--dry-run` first and confirm the change set looks right.
|
||||
|
||||
### check — find broken imports
|
||||
|
||||
```
|
||||
jmove check [--root DIR] [--json]
|
||||
```
|
||||
|
||||
Run after any move (or any edit) to validate project consistency.
|
||||
|
||||
## Recommended agent workflow
|
||||
|
||||
1. `jmove mv <src> <target> --dry-run --json` — preview.
|
||||
2. Inspect `affected_files`; if unexpected, abort and ask the user.
|
||||
3. `jmove mv <src> <target> --json` — apply (atomic, rolls back on error).
|
||||
4. `jmove check --json` — verify nothing is broken.
|
||||
|
||||
## Output format (--json)
|
||||
|
||||
Every response: `{ "status": "ok" | "dry_run" | "error", "operation": "...", ... }`.
|
||||
Errors carry a stable `code` (`TARGET_EXISTS`, `SOURCE_NOT_FOUND`, ...)
|
||||
and a `hint` describing the next action. On success, `mv` reports
|
||||
`changed_files` with line-level `old`/`new` import diffs and counters
|
||||
(`moved`, `updated_imports`).
|
||||
|
||||
## Exit codes
|
||||
|
||||
- `0` — success
|
||||
- `1` — operation failed (read `--json` error or stderr)
|
||||
- `2` — `check` found broken imports
|
||||
|
||||
## Rules of use
|
||||
|
||||
- Never run `mv` without a prior `--dry-run` in the same session.
|
||||
- Never pass `--force` unless the user explicitly authorized overwriting.
|
||||
- After every successful `mv`, run `check`; treat exit code `2` as
|
||||
a failed refactor.
|
||||
5
src/cache/mod.rs
vendored
Normal file
5
src/cache/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
//! On-disk index cache.
|
||||
//!
|
||||
//! Intentionally empty in Phase 1: the project is rescanned on every run.
|
||||
//! Phase 2 will persist the import graph under `.jmove/` and support
|
||||
//! incremental reindexing (see `todo.md`).
|
||||
36
src/cli/json.rs
Normal file
36
src/cli/json.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//! JSON output shapes shared by all `--json` commands.
|
||||
//!
|
||||
//! Every response is an [`Envelope`] whose `status` is one of
|
||||
//! `"ok" | "dry_run" | "error"`, plus a machine-readable error `code`
|
||||
//! and a human `hint` on failure (see `docs/SKILL.md`).
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Top-level envelope for every `--json` response.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Envelope<T: Serialize> {
|
||||
/// One of `"ok"`, `"dry_run"`, `"error"`.
|
||||
pub status: &'static str,
|
||||
/// The operation that produced this response, e.g. `"mv"`, `"check"`.
|
||||
pub operation: &'static str,
|
||||
/// Command-specific payload.
|
||||
#[serde(flatten)]
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
/// Error payload: stable `code`, human `message`, actionable `hint`.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ErrorData {
|
||||
/// Machine-readable code, e.g. `TARGET_EXISTS`, `SOURCE_NOT_FOUND`.
|
||||
pub code: String,
|
||||
/// Human-readable explanation.
|
||||
pub message: String,
|
||||
/// What the caller should do next (never null in output; omit if none).
|
||||
pub hint: Option<String>,
|
||||
}
|
||||
|
||||
/// Serialize `value` as pretty JSON to stdout.
|
||||
pub fn print<T: Serialize>(value: &T) {
|
||||
let _ = value;
|
||||
todo!("cli agent: println!(serde_json::to_string_pretty)")
|
||||
}
|
||||
96
src/cli/mod.rs
Normal file
96
src/cli/mod.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//! Command line interface: argument parsing, dispatch and exit codes.
|
||||
//!
|
||||
//! Exit codes (mirrored in `docs/SKILL.md`):
|
||||
//! `0` success · `1` operation error · `2` broken imports found.
|
||||
|
||||
pub mod json;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
/// jmove — move source files, keep every import intact.
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "jmove", version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Subcommand to execute.
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
/// Project root (defaults to the current directory).
|
||||
#[arg(long, global = true, default_value = ".")]
|
||||
pub root: PathBuf,
|
||||
}
|
||||
|
||||
/// Available subcommands (MVP: `mv`, `check`).
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Move a file and rewrite all imports referencing it.
|
||||
Mv {
|
||||
/// File being moved (project-relative or inside the root).
|
||||
source: PathBuf,
|
||||
/// Destination path.
|
||||
target: PathBuf,
|
||||
/// Preview changes without touching the disk.
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
/// Machine-readable JSON output (for AI agents).
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
/// Allow overwriting an existing target file.
|
||||
#[arg(long)]
|
||||
force: bool,
|
||||
},
|
||||
/// Report broken imports in the project.
|
||||
Check {
|
||||
/// Machine-readable JSON output.
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Process exit codes documented for humans and agents alike.
|
||||
pub mod exit {
|
||||
/// Operation completed successfully.
|
||||
pub const OK: i32 = 0;
|
||||
/// Invalid usage, rejected plan or IO failure.
|
||||
pub const ERROR: i32 = 1;
|
||||
/// `check` found at least one broken import.
|
||||
pub const BROKEN: i32 = 2;
|
||||
}
|
||||
|
||||
/// Parse arguments and run the selected command.
|
||||
/// Returns the process exit code; `Err` is reserved for unexpected failures.
|
||||
pub fn run() -> anyhow::Result<i32> {
|
||||
let args = Args::parse();
|
||||
match args.command {
|
||||
Command::Mv {
|
||||
source,
|
||||
target,
|
||||
dry_run,
|
||||
json,
|
||||
force,
|
||||
} => mv(&args.root, &source, &target, dry_run, json, force),
|
||||
Command::Check { json } => check(&args.root, json),
|
||||
}
|
||||
}
|
||||
|
||||
/// `mv` handler: build index, plan, then dry-run-print or apply.
|
||||
fn mv(
|
||||
root: &Path,
|
||||
source: &Path,
|
||||
target: &Path,
|
||||
dry_run: bool,
|
||||
json: bool,
|
||||
force: bool,
|
||||
) -> anyhow::Result<i32> {
|
||||
let _ = (root, source, target, dry_run, json, force);
|
||||
todo!("cli agent: wire mv to core::index/plan/apply")
|
||||
}
|
||||
|
||||
/// `check` handler: report imports that resolve to nothing.
|
||||
fn check(root: &Path, json: bool) -> anyhow::Result<i32> {
|
||||
let _ = (root, json);
|
||||
todo!("cli agent: wire check to core::index")
|
||||
}
|
||||
|
||||
use std::path::Path;
|
||||
35
src/core/apply.rs
Normal file
35
src/core/apply.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//! Atomic apply with rollback, plus unified-diff rendering for dry-run.
|
||||
//!
|
||||
//! Order matters: specifier rewrites are applied to importer files first
|
||||
//! (each written atomically via temp-file + rename), and the actual
|
||||
//! `source -> target` rename happens last. Any failure mid-way triggers
|
||||
//! rollback of everything already written.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::core::JmoveResult;
|
||||
use crate::core::plan::MovePlan;
|
||||
|
||||
/// Summary of a successfully applied plan.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Applied {
|
||||
/// Number of files whose imports were rewritten.
|
||||
pub files_rewritten: usize,
|
||||
/// The moved file's new project-relative path.
|
||||
pub new_path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
/// Apply `plan` under `root` atomically (see module docs). Rollback is
|
||||
/// best-effort: on restore failure the error message states which files
|
||||
/// need manual recovery.
|
||||
pub fn apply(root: &Path, plan: &MovePlan) -> JmoveResult<Applied> {
|
||||
let _ = (root, plan);
|
||||
todo!("index agent: atomic apply + rollback")
|
||||
}
|
||||
|
||||
/// Render the plan as a unified diff (rewrites + file rename) for dry-run.
|
||||
#[must_use]
|
||||
pub fn render_diff(root: &Path, plan: &MovePlan) -> JmoveResult<String> {
|
||||
let _ = (root, plan);
|
||||
todo!("index agent: diff rendering via `similar`")
|
||||
}
|
||||
79
src/core/index.rs
Normal file
79
src/core/index.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
//! Project indexing: gitignore-aware filesystem scan plus the import graph.
|
||||
//!
|
||||
//! Built fresh on every command (disk cache is Phase 2). `ignore::WalkBuilder`
|
||||
//! handles `.gitignore`/hidden-file rules; every indexed TS/JS source file is
|
||||
//! parsed through [`crate::parser`] and its specifiers resolved through
|
||||
//! [`crate::parser::resolve`].
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::core::JmoveResult;
|
||||
use crate::parser::ImportRecord;
|
||||
|
||||
/// Indexed source files with O(1) membership lookups.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FileSet {
|
||||
paths: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl FileSet {
|
||||
/// Add a normalized project-relative path; `false` if already present.
|
||||
pub fn add(&mut self, path: PathBuf) -> bool {
|
||||
self.paths.insert(path)
|
||||
}
|
||||
|
||||
/// Whether `path` is a known indexed source file.
|
||||
#[must_use]
|
||||
pub fn contains(&self, path: &Path) -> bool {
|
||||
self.paths.contains(path)
|
||||
}
|
||||
|
||||
/// All files in deterministic sorted order (stable for tests and diffs).
|
||||
#[must_use]
|
||||
pub fn sorted(&self) -> Vec<PathBuf> {
|
||||
let mut v: Vec<PathBuf> = self.paths.iter().cloned().collect();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
/// One import occurrence plus the project file it resolves to.
|
||||
/// `target: None` means "external" — a bare package specifier or a path
|
||||
/// that does not exist in the index.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolvedImport {
|
||||
/// Raw record from the parser (specifier text + byte span).
|
||||
pub record: ImportRecord,
|
||||
/// Project-relative resolved file, if any.
|
||||
pub target: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Full in-memory project index: file set and forward import edges.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Index {
|
||||
/// Absolute project root the index was built for.
|
||||
pub root: PathBuf,
|
||||
/// All indexed source files.
|
||||
pub files: FileSet,
|
||||
/// For each file, the imports it declares (in source order).
|
||||
pub imports: HashMap<PathBuf, Vec<ResolvedImport>>,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
/// Scan `root`, parse every supported source file and build the graph.
|
||||
/// Unparseable files are skipped, not fatal.
|
||||
pub fn build(root: &Path) -> JmoveResult<Self> {
|
||||
let _ = root;
|
||||
todo!(
|
||||
"index agent: scan with `ignore`, parse via crate::parser, resolve via parser::resolve"
|
||||
)
|
||||
}
|
||||
|
||||
/// Reverse edge lookup: every indexed file that imports `target`.
|
||||
#[must_use]
|
||||
pub fn importers_of(&self, target: &Path) -> Vec<PathBuf> {
|
||||
let _ = target;
|
||||
todo!("index agent: reverse-edge lookup")
|
||||
}
|
||||
}
|
||||
103
src/core/mod.rs
Normal file
103
src/core/mod.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
//! Core engine: project indexing, dependency graph, move planning and
|
||||
//! atomic apply with rollback.
|
||||
//!
|
||||
//! Path convention used across the crate: every `PathBuf` produced by
|
||||
//! `jmove` is **project-root-relative, normalized** (no `.`/`..` segments).
|
||||
//! Use [`normalize_rel_path`] to canonicalize paths coming from users or
|
||||
//! from OS walking.
|
||||
|
||||
pub mod apply;
|
||||
pub mod index;
|
||||
pub mod plan;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Crate-wide error type surfaced to the CLI layer.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JmoveError {
|
||||
/// Filesystem or IO failure.
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
/// The user supplied a path that is invalid for the requested operation.
|
||||
#[error("invalid argument: {0}")]
|
||||
InvalidArgument(String),
|
||||
/// Project index is stale (a file vanished or was moved externally).
|
||||
#[error("index is stale: {0}")]
|
||||
StaleIndex(String),
|
||||
/// The planned move cannot be applied safely.
|
||||
#[error("plan rejected: {0}")]
|
||||
PlanRejected(String),
|
||||
}
|
||||
|
||||
/// Result alias used throughout the crate.
|
||||
pub type JmoveResult<T> = Result<T, JmoveError>;
|
||||
|
||||
/// Normalize a project-relative path: strip `.` segments, collapse `..`
|
||||
/// where possible and reject paths that escape the project root.
|
||||
/// Returns `None` if the result would be empty, absolute or above the root.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::path::Path;
|
||||
/// use jmove::core::normalize_rel_path;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// normalize_rel_path(Path::new("./src/../utils/foo.ts")),
|
||||
/// Some(PathBuf::from("utils/foo.ts"))
|
||||
/// );
|
||||
/// assert_eq!(normalize_rel_path(Path::new("../outside")), None);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn normalize_rel_path(path: &Path) -> Option<PathBuf> {
|
||||
let mut stack: Vec<&OsStr> = Vec::new();
|
||||
for comp in path.components() {
|
||||
match comp {
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
if stack.pop().is_none() {
|
||||
return None; // would escape the project root
|
||||
}
|
||||
}
|
||||
Component::Normal(piece) => stack.push(piece),
|
||||
// Absolute paths and Windows prefixes are not project-relative.
|
||||
Component::RootDir | Component::Prefix(_) => return None,
|
||||
}
|
||||
}
|
||||
(!stack.is_empty()).then(|| stack.iter().collect::<PathBuf>())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::normalize_rel_path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[test]
|
||||
fn normalizes_dots_and_parent_dirs() {
|
||||
assert_eq!(
|
||||
normalize_rel_path(Path::new("./src/../utils/foo.ts")),
|
||||
Some(PathBuf::from("utils/foo.ts"))
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_rel_path(Path::new("a/b/c/../../d.ts")),
|
||||
Some(PathBuf::from("a/d.ts"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_escaping_and_empty_paths() {
|
||||
assert_eq!(normalize_rel_path(Path::new("../outside")), None);
|
||||
assert_eq!(normalize_rel_path(Path::new("a/../../outside")), None);
|
||||
assert_eq!(normalize_rel_path(Path::new("")), None);
|
||||
assert_eq!(normalize_rel_path(Path::new("./")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_absolute_paths() {
|
||||
assert_eq!(normalize_rel_path(Path::new("/etc/passwd")), None);
|
||||
}
|
||||
}
|
||||
46
src/core/plan.rs
Normal file
46
src/core/plan.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! Move planning: decide which import specifiers must be rewritten.
|
||||
//!
|
||||
//! A plan is pure data (no disk writes), so dry-run and `--json` can render
|
||||
//! it without touching the filesystem.
|
||||
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::core::JmoveResult;
|
||||
use crate::core::index::Index;
|
||||
|
||||
/// One in-file replacement of an import specifier. Only the specifier text
|
||||
/// between the quotes is touched — the statement layout is never reformatted.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Rewrite {
|
||||
/// Project-relative file to modify.
|
||||
pub file: PathBuf,
|
||||
/// Byte range of the old specifier text (without quotes).
|
||||
pub span: Range<usize>,
|
||||
/// Specifier as currently written.
|
||||
pub old_text: String,
|
||||
/// Specifier after the move.
|
||||
pub new_text: String,
|
||||
}
|
||||
|
||||
/// Complete plan for moving `source` to `target`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MovePlan {
|
||||
/// Project-relative path being moved.
|
||||
pub source: PathBuf,
|
||||
/// Project-relative destination path.
|
||||
pub target: PathBuf,
|
||||
/// Specifier rewrites, grouped per importer file.
|
||||
pub rewrites: Vec<Rewrite>,
|
||||
}
|
||||
|
||||
/// Compute the rewrite plan for `source -> target`.
|
||||
///
|
||||
/// Every indexed import whose resolved target is `source` gets a new
|
||||
/// relative specifier computed from the *importer's* directory to `target`.
|
||||
/// Rewrites whose result equals the old specifier are dropped.
|
||||
pub fn plan_move(index: &Index, source: &Path, target: &Path) -> JmoveResult<MovePlan> {
|
||||
let _ = (index, source, target);
|
||||
todo!("index agent: implement planner incl. relative-specifier math")
|
||||
}
|
||||
17
src/lib.rs
Normal file
17
src/lib.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//! `jmove` — a project-aware file mover for TypeScript/JavaScript.
|
||||
//!
|
||||
//! Moving a file inside a project invalidates every relative import that
|
||||
//! points at it. `jmove` indexes the project's import graph, computes the
|
||||
//! minimal set of specifier rewrites, and applies everything atomically
|
||||
//! (with rollback), optionally in dry-run mode.
|
||||
//!
|
||||
//! Module map:
|
||||
//! - [`cli`] — argument parsing, command dispatch, user-facing output.
|
||||
//! - [`core`] — indexing, dependency graph, move planning, atomic apply.
|
||||
//! - [`parser`] — language frontends (import extraction, path resolution).
|
||||
//! - [`cache`] — on-disk index cache (Phase 2, intentionally empty for now).
|
||||
|
||||
pub mod cache;
|
||||
pub mod cli;
|
||||
pub mod core;
|
||||
pub mod parser;
|
||||
19
src/main.rs
Normal file
19
src/main.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//! Binary entry point for `jmove`.
|
||||
//!
|
||||
//! All logic lives in the library crate; this main only maps the outcome of
|
||||
//! [`jmove::cli::run`] onto process exit codes:
|
||||
//! - `0` — success,
|
||||
//! - `1` — user-facing plan/validation error (already printed),
|
||||
//! - `2` — unexpected failure.
|
||||
|
||||
/// Parses arguments, runs the selected command and converts failures into a
|
||||
/// non-zero exit code, printing a one-line message for the user.
|
||||
fn main() {
|
||||
match jmove::cli::run() {
|
||||
Ok(code) => std::process::exit(code),
|
||||
Err(err) => {
|
||||
eprintln!("jmove: {err}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/parser/mod.rs
Normal file
83
src/parser/mod.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
//! Language frontends: import extraction and module specifier resolution.
|
||||
//!
|
||||
//! ## Contract (stable across submodules — implementers must not change it)
|
||||
//!
|
||||
//! - A [`Language`] parses one source file into [`ImportRecord`]s: every
|
||||
//! *static-ish* module reference (TS `import`/`export from`/`require`/
|
||||
//! dynamic `import()`).
|
||||
//! - [`crate::core::parser_support::resolve`] turns a specifier into a
|
||||
//! project-relative file path using a resolver aware of the indexed file
|
||||
//! set. Non-project (package/bare) specifiers resolve to `None`.
|
||||
//! - 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.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
pub mod resolve;
|
||||
pub mod ts;
|
||||
|
||||
/// Source languages `jmove` understands (Phase 1: TypeScript/JavaScript).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum SourceLanguage {
|
||||
/// `.ts` (non-TSX) sources.
|
||||
TypeScript,
|
||||
/// `.tsx` / `.jsx` sources.
|
||||
Tsx,
|
||||
/// Plain `.js` / `.mjs` / `.cjs` sources.
|
||||
JavaScript,
|
||||
}
|
||||
|
||||
impl SourceLanguage {
|
||||
/// Map a file extension (lowercase, no dot) to a language, if supported.
|
||||
#[must_use]
|
||||
pub fn from_extension(ext: &str) -> Option<Self> {
|
||||
match ext {
|
||||
"ts" | "mts" | "cts" => Some(Self::TypeScript),
|
||||
"tsx" | "jsx" => Some(Self::Tsx),
|
||||
"js" | "mjs" | "cjs" => Some(Self::JavaScript),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the language from a file name. `None` means "not a source file
|
||||
/// we index" (skip it).
|
||||
#[must_use]
|
||||
pub fn for_path(path: &Path) -> Option<Self> {
|
||||
Self::from_extension(
|
||||
path.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(str::to_ascii_lowercase)
|
||||
.as_deref()?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// One module reference found in a source file.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ImportRecord {
|
||||
/// Raw specifier text as written, e.g. `"../utils/format"`.
|
||||
pub specifier: String,
|
||||
/// Byte range of the *specifier string contents* (inside the quotes,
|
||||
/// without the quote characters) in the parsed file. The rewriter
|
||||
/// replaces exactly this span and nothing else.
|
||||
pub span: std::ops::Range<usize>,
|
||||
/// `true` for dynamic `import("...")` / `require("...")` occurrences.
|
||||
pub is_dynamic: bool,
|
||||
}
|
||||
|
||||
/// A language frontend that extracts imports from source text.
|
||||
pub trait Language: Send + Sync {
|
||||
/// The language this frontend handles.
|
||||
fn language(&self) -> SourceLanguage;
|
||||
|
||||
/// Extract all import records from `source` in byte-offset order.
|
||||
/// Parse errors must not be fatal: return what was understood.
|
||||
fn extract_imports(&self, source: &str) -> Vec<ImportRecord>;
|
||||
}
|
||||
|
||||
/// Build the default frontend for `lang`.
|
||||
#[must_use]
|
||||
pub fn frontend_for(lang: SourceLanguage) -> Box<dyn Language> {
|
||||
Box::new(ts::TreeSitterTs::new(lang))
|
||||
}
|
||||
26
src/parser/resolve.rs
Normal file
26
src/parser/resolve.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//! Module specifier resolution for TS/JS projects.
|
||||
//!
|
||||
//! CONTRACT: see [`crate::parser`]. Given a *relative* specifier and the
|
||||
//! importing file, find which indexed project file it refers to. Bare /
|
||||
//! package specifiers (not starting with `.`) are out of project scope and
|
||||
//! resolve to `None`.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::core::index::FileSet;
|
||||
|
||||
/// Resolve `specifier` (e.g. `"../utils/fmt"`) written in the file at
|
||||
/// `importer` (project-relative), against the indexed `files`.
|
||||
///
|
||||
/// Resolution order for extensionless specifiers (Node/TS classic):
|
||||
/// 1. exact path if indexed (e.g. `"./a.ts"`),
|
||||
/// 2. `<base>` + each supported extension (`.ts`, `.tsx`, `.js`, `.jsx`,
|
||||
/// `.mjs`, `.cjs` — declaration files only when nothing else matches),
|
||||
/// 3. `<base>/index.<ext>`.
|
||||
///
|
||||
/// Returns `None` for bare specifiers or unresolvable paths.
|
||||
#[must_use]
|
||||
pub fn resolve_module(importer: &Path, specifier: &str, files: &FileSet) -> Option<PathBuf> {
|
||||
let _ = (importer, specifier, files);
|
||||
todo!("parser agent: implement resolver")
|
||||
}
|
||||
29
src/parser/ts.rs
Normal file
29
src/parser/ts.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! Tree-sitter based frontend for TypeScript/JavaScript.
|
||||
//!
|
||||
//! CONTRACT: see [`crate::parser`]. Implement `extract_imports` using
|
||||
//! `tree-sitter-typescript` grammars.
|
||||
|
||||
use super::{ImportRecord, Language, SourceLanguage};
|
||||
|
||||
/// Frontend backed by the tree-sitter TypeScript/TSX/JS grammar.
|
||||
pub struct TreeSitterTs {
|
||||
lang: SourceLanguage,
|
||||
}
|
||||
|
||||
impl TreeSitterTs {
|
||||
/// Create a frontend for the given language variant.
|
||||
#[must_use]
|
||||
pub fn new(lang: SourceLanguage) -> Self {
|
||||
Self { lang }
|
||||
}
|
||||
}
|
||||
|
||||
impl Language for TreeSitterTs {
|
||||
fn language(&self) -> SourceLanguage {
|
||||
self.lang
|
||||
}
|
||||
|
||||
fn extract_imports(&self, _source: &str) -> Vec<ImportRecord> {
|
||||
todo!("parser agent: implement tree-sitter extraction")
|
||||
}
|
||||
}
|
||||
39
todo.md
Normal file
39
todo.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# jmove — TODO
|
||||
|
||||
## Правила
|
||||
- KISS — simplest solution that works; no speculative abstractions.
|
||||
- DRY — no duplicated logic; extract into a function/module instead.
|
||||
- SOLID — single responsibility per module (`cli`/`core`/`parser`/`cache`), open for extension (new languages) via the parser trait, no deps on implementation details.
|
||||
- Max 250 lines per file — split when exceeded.
|
||||
- Max 4 files per folder (module) — split the module when exceeded.
|
||||
- Каждый коммит проходит `cargo fmt --check` и `cargo clippy -- -D warnings`.
|
||||
- Запрещён mёртвый код: unused code удаляется или реализуется; `#[allow(dead_code)]` не использовать.
|
||||
|
||||
## MVP (Phase 1)
|
||||
- [ ] CLI skeleton (clap) — команды: mv, index, check, --dry-run
|
||||
- [ ] Сканер файлов проекта (уважать .gitignore через `ignore` крейт)
|
||||
- [ ] Парсер импортов для TypeScript/JS через tree-sitter
|
||||
- [ ] Построение графа зависимостей (файл → что импортирует)
|
||||
- [ ] Инвертированный граф (файл → кто его импортирует)
|
||||
- [ ] Вычисление нового относительного пути после mv
|
||||
- [ ] Rewrite импортов в файлах
|
||||
- [ ] Атомарный apply (сначала rewrite, потом mv) + rollback при ошибке
|
||||
- [ ] Dry-run режим с diff выводом
|
||||
|
||||
## Phase 2
|
||||
- [ ] Кэш индекса на диске (bincode/rkyv) → .jmove/index
|
||||
- [ ] Инкрементальная переиндексация (только изменённые файлы)
|
||||
- [ ] Поддержка tsconfig paths / алиасов (@/...)
|
||||
- [ ] Параллельная индексация через rayon
|
||||
|
||||
## Phase 3
|
||||
- [ ] Поддержка Python
|
||||
- [ ] Поддержка Java
|
||||
- [ ] Поддержка Go
|
||||
- [ ] Команда split (авто-разбивка файла на несколько)
|
||||
- [ ] Команда check (найти все битые импорты)
|
||||
|
||||
## Идеи на потом
|
||||
- [ ] LSP интеграция
|
||||
- [ ] Watch mode
|
||||
- [ ] VS Code расширение как обёртка над CLI
|
||||
Loading…
Add table
Add a link
Reference in a new issue