diff --git a/Cargo.lock b/Cargo.lock index 6392ce49..36c577bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,7 +249,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -275,7 +275,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -330,18 +330,42 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.100", "which", ] +[[package]] +name = "biome_aria" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_aria_metadata", +] + +[[package]] +name = "biome_aria_metadata" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_deserialize 0.6.0 (git+https://github.com/biomejs/biome)", + "biome_deserialize_macros 0.6.0 (git+https://github.com/biomejs/biome)", + "biome_string_case 0.5.7", + "prettyplease", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.100", +] + [[package]] name = "biome_console" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c672a9e31e47f8df74549a570ea3245a93ce3404115c724bb16762fcbbfe17e1" dependencies = [ - "biome_markup", - "biome_text_size", + "biome_markup 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_text_size 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", "termcolor", @@ -349,37 +373,75 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "biome_console" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_markup 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_text_size 0.5.7 (git+https://github.com/biomejs/biome)", + "serde", + "termcolor", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "biome_deserialize" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f619dc8ca0595ed8850d729ebc71722d4233aba68c5aec7d9993a53e59f3fe" dependencies = [ - "biome_console", - "biome_deserialize_macros", - "biome_diagnostics", - "biome_json_parser", - "biome_json_syntax", - "biome_rowan", + "biome_console 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_deserialize_macros 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_diagnostics 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_json_parser 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_json_syntax 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_rowan 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 2.6.0", - "indexmap 2.7.0", + "indexmap 2.8.0", "schemars", "serde", ] +[[package]] +name = "biome_deserialize" +version = "0.6.0" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_console 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_diagnostics 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_json_parser 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_json_syntax 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "enumflags2", +] + [[package]] name = "biome_deserialize_macros" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07c12826fff87ac09f63bbacf8bdf5225dfdf890da04d426f758cbcacf068e3e" dependencies = [ - "biome_string_case", + "biome_string_case 0.5.8", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "biome_deserialize_macros" +version = "0.6.0" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_string_case 0.5.7", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "biome_diagnostics" version = "0.5.7" @@ -387,12 +449,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe1317b6d610541c4e6a0e1f803a946f153ace3468bbc77a8f273dcb04ee526f" dependencies = [ "backtrace", - "biome_console", - "biome_diagnostics_categories", - "biome_diagnostics_macros", - "biome_rowan", - "biome_text_edit", - "biome_text_size", + "biome_console 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_diagnostics_categories 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_diagnostics_macros 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_rowan 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_text_edit 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_text_size 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 2.6.0", "bpaf", "oxc_resolver", @@ -401,6 +463,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "biome_diagnostics" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "backtrace", + "biome_console 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_diagnostics_categories 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_diagnostics_macros 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_text_edit 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_text_size 0.5.7 (git+https://github.com/biomejs/biome)", + "enumflags2", + "serde", + "serde_json", + "termcolor", + "terminal_size", + "unicode-width", +] + [[package]] name = "biome_diagnostics_categories" version = "0.5.7" @@ -411,6 +493,15 @@ dependencies = [ "serde", ] +[[package]] +name = "biome_diagnostics_categories" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "quote", + "serde", +] + [[package]] name = "biome_diagnostics_macros" version = "0.5.7" @@ -423,14 +514,99 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "biome_diagnostics_macros" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "biome_formatter" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_console 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_deserialize 0.6.0 (git+https://github.com/biomejs/biome)", + "biome_deserialize_macros 0.6.0 (git+https://github.com/biomejs/biome)", + "biome_diagnostics 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_string_case 0.5.7", + "camino", + "cfg-if", + "countme", + "drop_bomb", + "indexmap 2.8.0", + "rustc-hash 2.1.1", + "tracing", + "unicode-width", +] + +[[package]] +name = "biome_js_factory" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_js_syntax", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", +] + +[[package]] +name = "biome_js_formatter" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_deserialize 0.6.0 (git+https://github.com/biomejs/biome)", + "biome_deserialize_macros 0.6.0 (git+https://github.com/biomejs/biome)", + "biome_diagnostics_categories 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_formatter", + "biome_js_factory", + "biome_js_syntax", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_string_case 0.5.7", + "biome_suppression", + "biome_text_size 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_unicode_table 0.5.7 (git+https://github.com/biomejs/biome)", + "camino", + "smallvec", + "unicode-width", +] + +[[package]] +name = "biome_js_syntax" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_aria", + "biome_aria_metadata", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_string_case 0.5.7", + "camino", + "enumflags2", + "serde", +] + [[package]] name = "biome_json_factory" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e409eb289040f3660689dad178b00b6ac8cfa9a7fffd8225f35cb6b3d36437cf" dependencies = [ - "biome_json_syntax", - "biome_rowan", + "biome_json_syntax 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_rowan 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "biome_json_factory" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_json_syntax 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", ] [[package]] @@ -439,13 +615,29 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c6d23fb9b683e6356c094b4a0cb38f8aa0acee60ce9c3ef24628d21a204de4d" dependencies = [ - "biome_console", - "biome_diagnostics", - "biome_json_factory", - "biome_json_syntax", - "biome_parser", - "biome_rowan", - "biome_unicode_table", + "biome_console 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_diagnostics 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_json_factory 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_json_syntax 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_parser 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_rowan 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_unicode_table 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "unicode-bom", +] + +[[package]] +name = "biome_json_parser" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_console 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_diagnostics 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_json_factory 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_json_syntax 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_parser 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_unicode_table 0.5.7 (git+https://github.com/biomejs/biome)", "tracing", "unicode-bom", ] @@ -456,7 +648,18 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2645ca57f75680d3d390b2482c35db5850b1d849e1f96151a12f15f4abdb097" dependencies = [ - "biome_rowan", + "biome_rowan 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", +] + +[[package]] +name = "biome_json_syntax" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_string_case 0.5.7", + "camino", "serde", ] @@ -471,27 +674,51 @@ dependencies = [ "quote", ] +[[package]] +name = "biome_markup" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", +] + [[package]] name = "biome_parser" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955dd999f32c086371d5c0e64b4ea1a50f50c98f1f31a3b9fe17ef47198de19b" dependencies = [ - "biome_console", - "biome_diagnostics", - "biome_rowan", + "biome_console 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_diagnostics 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_rowan 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 2.6.0", "drop_bomb", ] +[[package]] +name = "biome_parser" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_console 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_diagnostics 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_unicode_table 0.5.7 (git+https://github.com/biomejs/biome)", + "drop_bomb", + "enumflags2", + "unicode-bom", +] + [[package]] name = "biome_rowan" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3c2dc25a7ba6ae89526340034abed6c89fac35b79060786771e32ed4aac77e7" dependencies = [ - "biome_text_edit", - "biome_text_size", + "biome_text_edit 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_text_size 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "countme", "hashbrown 0.12.3", "memoffset", @@ -499,19 +726,58 @@ dependencies = [ "tracing", ] +[[package]] +name = "biome_rowan" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_text_edit 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_text_size 0.5.7 (git+https://github.com/biomejs/biome)", + "countme", + "hashbrown 0.15.2", + "rustc-hash 2.1.1", + "serde", +] + +[[package]] +name = "biome_string_case" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" + [[package]] name = "biome_string_case" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5868798da491b19a5b27a0bad5d8727e1e65060fa2dac360b382df00ff520774" +[[package]] +name = "biome_suppression" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_console 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_diagnostics 0.5.7 (git+https://github.com/biomejs/biome)", + "biome_rowan 0.5.7 (git+https://github.com/biomejs/biome)", + "log", +] + [[package]] name = "biome_text_edit" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d486fdd96d5dad6428213ce64e6b9eb5bfb2fce6387fe901e844d386283de509" dependencies = [ - "biome_text_size", + "biome_text_size 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "similar", +] + +[[package]] +name = "biome_text_edit" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "biome_text_size 0.5.7 (git+https://github.com/biomejs/biome)", "serde", "similar", ] @@ -525,12 +791,25 @@ dependencies = [ "serde", ] +[[package]] +name = "biome_text_size" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" +dependencies = [ + "serde", +] + [[package]] name = "biome_unicode_table" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87e8604d34b02180a58af1dbdaac166f1805f27f5370934142a3246f83870952" +[[package]] +name = "biome_unicode_table" +version = "0.5.7" +source = "git+https://github.com/biomejs/biome#aaa9443c551dcea220adae1d26494b9850e1849e" + [[package]] name = "bitflags" version = "1.3.2" @@ -587,7 +866,7 @@ checksum = "cf95d9c7e6aba67f8fc07761091e93254677f4db9e27197adecebc7039a58722" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -619,6 +898,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" + [[package]] name = "cc" version = "1.2.3" @@ -694,7 +979,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -739,6 +1024,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -947,7 +1242,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -955,7 +1250,7 @@ name = "docs_codegen" version = "0.0.0" dependencies = [ "anyhow", - "biome_string_case", + "biome_string_case 0.5.8", "bpaf", "pglt_analyse", "pglt_analyser", @@ -1022,22 +1317,23 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", + "serde", ] [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1246,7 +1542,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1576,7 +1872,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1629,9 +1925,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1700,9 +1996,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -1798,7 +2094,7 @@ dependencies = [ name = "line_index" version = "0.0.0" dependencies = [ - "text-size", + "pglt_text_size", ] [[package]] @@ -1819,6 +2115,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" + [[package]] name = "litemap" version = "0.7.4" @@ -1837,9 +2139,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" dependencies = [ "value-bag", ] @@ -2098,10 +2400,10 @@ dependencies = [ "cfg-if", "dashmap 6.1.0", "dunce", - "indexmap 2.7.0", + "indexmap 2.8.0", "json-strip-comments", "once_cell", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "serde_json", "simdutf8", @@ -2190,7 +2492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.0", + "indexmap 2.8.0", ] [[package]] @@ -2215,17 +2517,17 @@ dependencies = [ name = "pglt_analyse" version = "0.0.0" dependencies = [ - "biome_deserialize", - "biome_deserialize_macros", + "biome_deserialize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_deserialize_macros 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "enumflags2", "pglt_console", "pglt_diagnostics", "pglt_query_ext", "pglt_schema_cache", - "rustc-hash 2.1.0", + "pglt_text_size", + "rustc-hash 2.1.1", "schemars", "serde", - "text-size", ] [[package]] @@ -2247,8 +2549,8 @@ name = "pglt_cli" version = "0.0.0" dependencies = [ "anyhow", - "biome_deserialize", - "biome_deserialize_macros", + "biome_deserialize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_deserialize_macros 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "bpaf", "crossbeam", "dashmap 5.5.3", @@ -2267,7 +2569,7 @@ dependencies = [ "pglt_workspace", "quick-junit", "rayon", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "serde_json", "tikv-jemallocator", @@ -2284,8 +2586,8 @@ version = "0.0.0" dependencies = [ "anyhow", "async-std", + "pglt_text_size", "sqlx", - "text-size", ] [[package]] @@ -2295,11 +2597,12 @@ dependencies = [ "async-std", "pglt_schema_cache", "pglt_test_utils", + "pglt_text_size", "pglt_treesitter_queries", + "schemars", "serde", "serde_json", "sqlx", - "text-size", "tokio", "tree-sitter", "tree_sitter_sql", @@ -2309,19 +2612,19 @@ dependencies = [ name = "pglt_configuration" version = "0.0.0" dependencies = [ - "biome_deserialize", - "biome_deserialize_macros", + "biome_deserialize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "biome_deserialize_macros 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "bpaf", - "indexmap 2.7.0", + "indexmap 2.8.0", "pglt_analyse", "pglt_analyser", "pglt_console", "pglt_diagnostics", - "rustc-hash 2.1.0", + "pglt_text_size", + "rustc-hash 2.1.1", "schemars", "serde", "serde_json", - "text-size", "toml", ] @@ -2330,10 +2633,10 @@ name = "pglt_console" version = "0.0.0" dependencies = [ "pglt_markup", + "pglt_text_size", "schemars", "serde", "termcolor", - "text-size", "trybuild", "unicode-segmentation", "unicode-width", @@ -2350,11 +2653,11 @@ dependencies = [ "pglt_diagnostics_categories", "pglt_diagnostics_macros", "pglt_text_edit", + "pglt_text_size", "schemars", "serde", "serde_json", "termcolor", - "text-size", "unicode-width", ] @@ -2394,7 +2697,7 @@ dependencies = [ "parking_lot", "pglt_diagnostics", "rayon", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "schemars", "serde", "smallvec", @@ -2409,8 +2712,8 @@ dependencies = [ "pg_query", "pglt_diagnostics", "pglt_lexer_codegen", + "pglt_text_size", "regex", - "text-size", ] [[package]] @@ -2427,7 +2730,7 @@ name = "pglt_lsp" version = "0.0.0" dependencies = [ "anyhow", - "biome_deserialize", + "biome_deserialize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures", "pglt_analyse", "pglt_completions", @@ -2438,12 +2741,12 @@ dependencies = [ "pglt_lsp_converters", "pglt_test_utils", "pglt_text_edit", + "pglt_text_size", "pglt_workspace", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "serde_json", "sqlx", - "text-size", "tokio", "toml", "tower", @@ -2456,8 +2759,8 @@ name = "pglt_lsp_converters" version = "0.0.0" dependencies = [ "anyhow", - "rustc-hash 2.1.0", - "text-size", + "pglt_text_size", + "rustc-hash 2.1.1", "tower-lsp", ] @@ -2479,7 +2782,7 @@ dependencies = [ "pglt_diagnostics", "pglt_lexer", "pglt_query_ext_codegen", - "text-size", + "pglt_text_size", ] [[package]] @@ -2524,8 +2827,8 @@ dependencies = [ "pglt_diagnostics", "pglt_lexer", "pglt_query_ext", + "pglt_text_size", "regex", - "text-size", ] [[package]] @@ -2556,10 +2859,20 @@ dependencies = [ name = "pglt_text_edit" version = "0.0.0" dependencies = [ + "pglt_text_size", "schemars", "serde", "similar", - "text-size", +] + +[[package]] +name = "pglt_text_size" +version = "0.0.0" +dependencies = [ + "schemars", + "serde", + "serde_test", + "static_assertions", ] [[package]] @@ -2589,18 +2902,37 @@ dependencies = [ "pglt_query_ext", "pglt_schema_cache", "pglt_test_utils", + "pglt_text_size", "sqlx", - "text-size", "tokio", "tree-sitter", "tree_sitter_sql", ] +[[package]] +name = "pglt_wasm" +version = "0.1.0" +dependencies = [ + "biome_js_factory", + "biome_js_formatter", + "console_error_panic_hook", + "js-sys", + "pglt_console", + "pglt_diagnostics", + "pglt_fs", + "pglt_workspace", + "quote", + "schemars", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", +] + [[package]] name = "pglt_workspace" version = "0.0.0" dependencies = [ - "biome_deserialize", + "biome_deserialize 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "dashmap 5.5.3", "futures", "ignore", @@ -2614,13 +2946,14 @@ dependencies = [ "pglt_query_ext", "pglt_schema_cache", "pglt_statement_splitter", + "pglt_text_size", "pglt_typecheck", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", + "schemars", "serde", "serde_json", "sqlx", "tempfile", - "text-size", "tokio", "toml", "tracing", @@ -2645,7 +2978,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -2746,12 +3079,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -2787,11 +3120,32 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -2869,7 +3223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322330e133eab455718444b4e033ebfac7c6528972c784fcde28d2cc783c6257" dependencies = [ "anyhow", - "indexmap 2.7.0", + "indexmap 2.8.0", "log", "protobuf", "protobuf-support", @@ -2913,7 +3267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" dependencies = [ "chrono", - "indexmap 2.7.0", + "indexmap 2.8.0", "newtype-uuid", "quick-xml", "strip-ansi-escapes", @@ -2932,9 +3286,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -3117,9 +3471,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" @@ -3148,6 +3502,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.9.2", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.23.19" @@ -3188,6 +3555,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.18" @@ -3211,7 +3584,7 @@ checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "indexmap 1.9.3", - "indexmap 2.7.0", + "indexmap 2.8.0", "schemars_derive", "serde", "serde_json", @@ -3227,7 +3600,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3238,22 +3611,33 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3264,16 +3648,16 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.8.0", "itoa", "memchr", "ryu", @@ -3288,7 +3672,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3300,6 +3684,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3376,9 +3769,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" dependencies = [ "bstr", "unicode-segmentation", @@ -3487,7 +3880,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.7.0", + "indexmap 2.8.0", "log", "memchr", "once_cell", @@ -3516,7 +3909,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3540,7 +3933,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.90", + "syn 2.0.100", "tempfile", "url", ] @@ -3654,6 +4047,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -3708,9 +4107,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -3725,7 +4124,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3758,12 +4157,13 @@ dependencies = [ ] [[package]] -name = "text-size" -version = "1.1.1" +name = "terminal_size" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "serde", + "rustix 1.0.2", + "windows-sys 0.59.0", ] [[package]] @@ -3792,7 +4192,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3803,7 +4203,7 @@ checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3918,7 +4318,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3961,7 +4361,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.8.0", "serde", "serde_spanned", "toml_datetime", @@ -4021,7 +4421,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4062,7 +4462,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4344,34 +4744,37 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", + "serde", + "serde_json", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -4382,9 +4785,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4392,28 +4795,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -4704,7 +5110,7 @@ name = "xtask_codegen" version = "0.0.0" dependencies = [ "anyhow", - "biome_string_case", + "biome_string_case 0.5.8", "bpaf", "pglt_analyse", "pglt_analyser", @@ -4734,7 +5140,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "synstructure", ] @@ -4756,7 +5162,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4776,7 +5182,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "synstructure", ] @@ -4805,7 +5211,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6b8be0c5..f3a211f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ biome_deserialize_macros = "0.6.0" biome_string_case = "0.5.8" bpaf = { version = "0.9.15", features = ["derive"] } crossbeam = "0.8.4" -enumflags2 = "0.7.10" +enumflags2 = "0.7.11" ignore = "0.4.23" indexmap = { version = "2.6.0", features = ["serde"] } insta = "1.31.0" @@ -39,7 +39,6 @@ smallvec = { version = "1.13.2", features = ["union", "const_new sqlx = { version = "0.8.2", features = ["runtime-async-std", "tls-rustls", "postgres", "json"] } syn = "1.0.109" termcolor = "1.4.1" -text-size = "1.1.1" tokio = { version = "1.40.0", features = ["full"] } toml = "0.8.19" tower-lsp = "0.20.0" @@ -75,6 +74,7 @@ pglt_query_proto_parser = { path = "./crates/pglt_query_proto_parser", versi pglt_schema_cache = { path = "./crates/pglt_schema_cache", version = "0.0.0" } pglt_statement_splitter = { path = "./crates/pglt_statement_splitter", version = "0.0.0" } pglt_text_edit = { path = "./crates/pglt_text_edit", version = "0.0.0" } +pglt_text_size = { path = "./crates/pglt_text_size", version = "0.0.0" } pglt_treesitter_queries = { path = "./crates/pglt_treesitter_queries", version = "0.0.0" } pglt_type_resolver = { path = "./crates/pglt_type_resolver", version = "0.0.0" } pglt_typecheck = { path = "./crates/pglt_typecheck", version = "0.0.0" } diff --git a/crates/pglt_analyse/Cargo.toml b/crates/pglt_analyse/Cargo.toml index e0b8f943..4574d90c 100644 --- a/crates/pglt_analyse/Cargo.toml +++ b/crates/pglt_analyse/Cargo.toml @@ -22,9 +22,9 @@ rustc-hash = { workspace = true } biome_deserialize = { workspace = true, optional = true } biome_deserialize_macros = { workspace = true, optional = true } enumflags2.workspace = true +pglt_text_size.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } -text-size.workspace = true [features] serde = ["dep:serde", "dep:schemars", "dep:biome_deserialize", "dep:biome_deserialize_macros"] diff --git a/crates/pglt_analyse/src/rule.rs b/crates/pglt_analyse/src/rule.rs index 59950205..1f70cf84 100644 --- a/crates/pglt_analyse/src/rule.rs +++ b/crates/pglt_analyse/src/rule.rs @@ -5,9 +5,9 @@ use pglt_diagnostics::{ Advices, Category, Diagnostic, DiagnosticTags, Location, LogCategory, MessageAndDescription, Visit, }; +use pglt_text_size::TextRange; use std::cmp::Ordering; use std::fmt::Debug; -use text_size::TextRange; use crate::{categories::RuleCategory, context::RuleContext, registry::RegistryVisitor}; diff --git a/crates/pglt_cli/src/execute/mod.rs b/crates/pglt_cli/src/execute/mod.rs index 6cae68bc..8e0be406 100644 --- a/crates/pglt_cli/src/execute/mod.rs +++ b/crates/pglt_cli/src/execute/mod.rs @@ -85,12 +85,6 @@ pub enum TraversalMode { Dummy, /// This mode is enabled when running the command `check` Check { - /// The type of fixes that should be applied when analyzing a file. - /// - /// It's [None] if the `check` command is called without `--apply` or `--apply-suggested` - /// arguments. - // fix_file_mode: Option, - /// An optional tuple. /// 1. The virtual path to the file /// 2. The content of the file diff --git a/crates/pglt_commands/Cargo.toml b/crates/pglt_commands/Cargo.toml index ffa9c214..2507b914 100644 --- a/crates/pglt_commands/Cargo.toml +++ b/crates/pglt_commands/Cargo.toml @@ -12,10 +12,10 @@ version = "0.0.0" [dependencies] -anyhow = "1.0.62" -async-std = "1.12.0" -sqlx.workspace = true -text-size.workspace = true +anyhow = "1.0.62" +async-std = "1.12.0" +pglt_text_size.workspace = true +sqlx.workspace = true [lib] doctest = false diff --git a/crates/pglt_completions/Cargo.toml b/crates/pglt_completions/Cargo.toml index 84a8abd2..f89aa56b 100644 --- a/crates/pglt_completions/Cargo.toml +++ b/crates/pglt_completions/Cargo.toml @@ -14,11 +14,12 @@ version = "0.0.0" [dependencies] async-std = "1.12.0" -text-size.workspace = true +pglt_text_size.workspace = true pglt_schema_cache.workspace = true pglt_treesitter_queries.workspace = true +schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tree-sitter.workspace = true @@ -35,3 +36,4 @@ pglt_test_utils.workspace = true doctest = false [features] +schema = ["dep:schemars"] diff --git a/crates/pglt_completions/src/complete.rs b/crates/pglt_completions/src/complete.rs index 6049a33a..48bd3c95 100644 --- a/crates/pglt_completions/src/complete.rs +++ b/crates/pglt_completions/src/complete.rs @@ -1,5 +1,5 @@ +use pglt_text_size::TextSize; use serde::{Deserialize, Serialize}; -use text_size::TextSize; use crate::{ builder::CompletionBuilder, @@ -18,7 +18,8 @@ pub struct CompletionParams<'a> { pub tree: Option<&'a tree_sitter::Tree>, } -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CompletionResult { pub(crate) items: Vec, } diff --git a/crates/pglt_completions/src/item.rs b/crates/pglt_completions/src/item.rs index d14485c2..ab1b442c 100644 --- a/crates/pglt_completions/src/item.rs +++ b/crates/pglt_completions/src/item.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub enum CompletionItemKind { Table, Function, @@ -8,6 +9,7 @@ pub enum CompletionItemKind { } #[derive(Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CompletionItem { pub label: String, pub(crate) score: i32, diff --git a/crates/pglt_configuration/Cargo.toml b/crates/pglt_configuration/Cargo.toml index 4c45bfbb..dde8592f 100644 --- a/crates/pglt_configuration/Cargo.toml +++ b/crates/pglt_configuration/Cargo.toml @@ -20,11 +20,11 @@ pglt_analyse = { workspace = true } pglt_analyser = { workspace = true } pglt_console = { workspace = true } pglt_diagnostics = { workspace = true } +pglt_text_size = { workspace = true } rustc-hash = { workspace = true } schemars = { workspace = true, features = ["indexmap1"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } -text-size = { workspace = true } toml = { workspace = true } [lib] diff --git a/crates/pglt_console/Cargo.toml b/crates/pglt_console/Cargo.toml index fde900ec..ab4ce461 100644 --- a/crates/pglt_console/Cargo.toml +++ b/crates/pglt_console/Cargo.toml @@ -12,8 +12,8 @@ version = "0.0.0" [dependencies] -pglt_markup = { workspace = true } -text-size = { workspace = true } +pglt_markup = { workspace = true } +pglt_text_size = { workspace = true } schemars = { workspace = true, optional = true } serde = { workspace = true, optional = true, features = ["derive"] } diff --git a/crates/pglt_console/src/markup.rs b/crates/pglt_console/src/markup.rs index a6781c23..4c046ba8 100644 --- a/crates/pglt_console/src/markup.rs +++ b/crates/pglt_console/src/markup.rs @@ -4,8 +4,8 @@ use std::{ io, }; +use pglt_text_size::TextSize; use termcolor::{Color, ColorSpec}; -use text_size::TextSize; use crate::fmt::{Display, Formatter, MarkupElements, Write}; diff --git a/crates/pglt_diagnostics/Cargo.toml b/crates/pglt_diagnostics/Cargo.toml index 0f4d80e2..a225afbb 100644 --- a/crates/pglt_diagnostics/Cargo.toml +++ b/crates/pglt_diagnostics/Cargo.toml @@ -14,16 +14,16 @@ version = "0.0.0" [dependencies] backtrace = "0.3.74" bpaf = { workspace = true } -enumflags2 = { workspace = true } +enumflags2 = { workspace = true, features = ["serde"] } pglt_console = { workspace = true, features = ["serde_markup"] } pglt_diagnostics_categories = { workspace = true, features = ["serde"] } pglt_diagnostics_macros = { workspace = true } pglt_text_edit = { workspace = true } +pglt_text_size.workspace = true schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } termcolor = { workspace = true } -text-size.workspace = true unicode-width = { workspace = true } [features] diff --git a/crates/pglt_diagnostics/src/context.rs b/crates/pglt_diagnostics/src/context.rs index 01fa3448..f972a97b 100644 --- a/crates/pglt_diagnostics/src/context.rs +++ b/crates/pglt_diagnostics/src/context.rs @@ -251,7 +251,7 @@ mod internal { use pglt_console::{fmt, markup}; use pglt_text_edit::TextEdit; - use text_size::TextRange; + use pglt_text_size::TextRange; use crate::{ Advices, Backtrace, Category, Diagnostic, DiagnosticTags, LineIndex, LineIndexBuf, diff --git a/crates/pglt_diagnostics/src/diagnostic.rs b/crates/pglt_diagnostics/src/diagnostic.rs index 5eb40da2..031a8942 100644 --- a/crates/pglt_diagnostics/src/diagnostic.rs +++ b/crates/pglt_diagnostics/src/diagnostic.rs @@ -117,7 +117,8 @@ pub trait Diagnostic: Debug { #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, )] -#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] /// The severity to associate to a diagnostic. pub enum Severity { /// Reports a hint. @@ -164,6 +165,7 @@ impl Display for Severity { /// Internal enum used to automatically generate bit offsets for [DiagnosticTags] /// and help with the implementation of `serde` and `schemars` for tags. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] #[bitflags] #[repr(u8)] diff --git a/crates/pglt_diagnostics/src/display.rs b/crates/pglt_diagnostics/src/display.rs index b01a6e87..735d9e48 100644 --- a/crates/pglt_diagnostics/src/display.rs +++ b/crates/pglt_diagnostics/src/display.rs @@ -669,8 +669,8 @@ mod tests { use pglt_diagnostics::{DiagnosticTags, Severity}; use pglt_diagnostics_categories::{Category, category}; use pglt_text_edit::TextEdit; + use pglt_text_size::{TextRange, TextSize}; use serde_json::{from_value, json}; - use text_size::{TextRange, TextSize}; use crate::{self as pglt_diagnostics}; use crate::{ diff --git a/crates/pglt_diagnostics/src/display/backtrace.rs b/crates/pglt_diagnostics/src/display/backtrace.rs index ff9b1d98..7b86e7be 100644 --- a/crates/pglt_diagnostics/src/display/backtrace.rs +++ b/crates/pglt_diagnostics/src/display/backtrace.rs @@ -91,6 +91,17 @@ impl<'de> serde::Deserialize<'de> for Backtrace { } } +#[cfg(feature = "schema")] +impl schemars::JsonSchema for Backtrace { + fn schema_name() -> String { + String::from("Backtrace") + } + + fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + >::json_schema(generator) + } +} + /// Internal representation of a [Backtrace], can be either a native backtrace /// instance or a vector of serialized frames. #[derive(Clone, Debug)] @@ -292,6 +303,11 @@ pub(super) fn print_backtrace( /// Serializable representation of a backtrace frame. #[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr( + feature = "schema", + derive(schemars::JsonSchema), + schemars(rename = "BacktraceFrame") +)] #[cfg_attr(test, derive(Eq, PartialEq))] struct SerializedFrame { ip: u64, @@ -309,6 +325,11 @@ impl From<&'_ backtrace::BacktraceFrame> for SerializedFrame { /// Serializable representation of a backtrace frame symbol. #[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr( + feature = "schema", + derive(schemars::JsonSchema), + schemars(rename = "BacktraceSymbol") +)] #[cfg_attr(test, derive(Eq, PartialEq))] struct SerializedSymbol { name: Option, diff --git a/crates/pglt_diagnostics/src/display/frame.rs b/crates/pglt_diagnostics/src/display/frame.rs index 86ca21b0..53b0c6a0 100644 --- a/crates/pglt_diagnostics/src/display/frame.rs +++ b/crates/pglt_diagnostics/src/display/frame.rs @@ -7,7 +7,7 @@ use std::{ }; use pglt_console::{fmt, markup}; -use text_size::{TextLen, TextRange, TextSize}; +use pglt_text_size::{TextLen, TextRange, TextSize}; use unicode_width::UnicodeWidthChar; use crate::{ diff --git a/crates/pglt_diagnostics/src/display_github.rs b/crates/pglt_diagnostics/src/display_github.rs index 0b4a8e68..2e9d4b77 100644 --- a/crates/pglt_diagnostics/src/display_github.rs +++ b/crates/pglt_diagnostics/src/display_github.rs @@ -1,8 +1,8 @@ use crate::display::frame::SourceFile; use crate::{Diagnostic, Resource, Severity, diagnostic::internal::AsDiagnostic}; use pglt_console::{MarkupBuf, fmt, markup}; +use pglt_text_size::{TextRange, TextSize}; use std::io; -use text_size::{TextRange, TextSize}; /// Helper struct for printing a diagnostic as markup into any formatter /// implementing [pglt_console::fmt::Write]. diff --git a/crates/pglt_diagnostics/src/location.rs b/crates/pglt_diagnostics/src/location.rs index 4b9c8fe8..73fe48e4 100644 --- a/crates/pglt_diagnostics/src/location.rs +++ b/crates/pglt_diagnostics/src/location.rs @@ -1,8 +1,8 @@ +use pglt_text_size::{TextRange, TextSize}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::ops::Range; use std::{borrow::Borrow, ops::Deref}; -use text_size::{TextRange, TextSize}; /// Represents the location of a diagnostic in a resource. #[derive(Debug, Default, Clone, Copy)] @@ -39,6 +39,7 @@ impl Eq for Location<'_> {} /// Represents the resource a diagnostic is associated with. #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub enum Resource

{ /// The diagnostic is related to the content of the command line arguments. @@ -343,7 +344,7 @@ impl AsSourceCode for String { #[cfg(test)] mod tests { - use text_size::TextSize; + use pglt_text_size::TextSize; use super::LineIndexBuf; diff --git a/crates/pglt_diagnostics/src/serde.rs b/crates/pglt_diagnostics/src/serde.rs index 44eea25c..61dceb2c 100644 --- a/crates/pglt_diagnostics/src/serde.rs +++ b/crates/pglt_diagnostics/src/serde.rs @@ -2,11 +2,11 @@ use std::io; use pglt_console::{MarkupBuf, fmt, markup}; use pglt_text_edit::TextEdit; +use pglt_text_size::{TextRange, TextSize}; use serde::{ Deserialize, Deserializer, Serialize, Serializer, de::{self, SeqAccess}, }; -use text_size::{TextRange, TextSize}; use crate::{ Advices as _, Backtrace, Category, DiagnosticTags, LogCategory, Resource, Severity, SourceCode, @@ -15,7 +15,7 @@ use crate::{ /// Serializable representation for a [Diagnostic](super::Diagnostic). #[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "snake_case"))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(test, derive(Eq, PartialEq))] pub struct Diagnostic { category: Option<&'static Category>, @@ -137,7 +137,7 @@ impl std::fmt::Display for PrintDescription<'_, D } #[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "snake_case"))] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(test, derive(Eq, PartialEq))] struct Location { path: Option>, @@ -159,7 +159,7 @@ impl From> for Location { /// Implementation of [Visitor] collecting serializable [Advice] into a vector. #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(test, derive(Eq, PartialEq))] struct Advices { advices: Vec, @@ -245,7 +245,7 @@ impl super::Advices for Advices { /// See the [Visitor] trait for additional documentation on all the supported /// advice types. #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(test, derive(Eq, PartialEq))] enum Advice { Log(LogCategory, MarkupBuf), @@ -354,12 +354,23 @@ impl<'de> Deserialize<'de> for DiagnosticTags { } } +#[cfg(feature = "schema")] +impl schemars::JsonSchema for DiagnosticTags { + fn schema_name() -> String { + String::from("DiagnosticTags") + } + + fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + >::json_schema(generator) + } +} + #[cfg(test)] mod tests { use std::io; + use pglt_text_size::{TextRange, TextSize}; use serde_json::{Value, json}; - use text_size::{TextRange, TextSize}; use crate::{ self as pglt_diagnostics, {Advices, LogCategory, Visit}, diff --git a/crates/pglt_diagnostics/src/suggestion.rs b/crates/pglt_diagnostics/src/suggestion.rs index 0809b2bf..324d6197 100644 --- a/crates/pglt_diagnostics/src/suggestion.rs +++ b/crates/pglt_diagnostics/src/suggestion.rs @@ -1,7 +1,7 @@ use ::serde::{Deserialize, Serialize}; use pglt_console::MarkupBuf; use pglt_text_edit::TextEdit; -use text_size::TextRange; +use pglt_text_size::TextRange; /// Indicates how a tool should manage this suggestion. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] diff --git a/crates/pglt_lexer/Cargo.toml b/crates/pglt_lexer/Cargo.toml index e77e9a07..daffbf24 100644 --- a/crates/pglt_lexer/Cargo.toml +++ b/crates/pglt_lexer/Cargo.toml @@ -18,7 +18,7 @@ pg_query.workspace = true pglt_diagnostics.workspace = true pglt_lexer_codegen.workspace = true -text-size.workspace = true +pglt_text_size.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/pglt_lexer/src/diagnostics.rs b/crates/pglt_lexer/src/diagnostics.rs index 29cd72cd..f1968437 100644 --- a/crates/pglt_lexer/src/diagnostics.rs +++ b/crates/pglt_lexer/src/diagnostics.rs @@ -1,5 +1,5 @@ use pglt_diagnostics::{Diagnostic, MessageAndDescription}; -use text_size::TextRange; +use pglt_text_size::TextRange; /// A specialized diagnostic for scan errors. /// diff --git a/crates/pglt_lexer/src/lib.rs b/crates/pglt_lexer/src/lib.rs index 3c4ed2af..696d22e2 100644 --- a/crates/pglt_lexer/src/lib.rs +++ b/crates/pglt_lexer/src/lib.rs @@ -3,9 +3,9 @@ pub mod diagnostics; use diagnostics::ScanError; use pg_query::protobuf::{KeywordKind, ScanToken}; +use pglt_text_size::{TextLen, TextRange, TextSize}; use regex::Regex; use std::{collections::VecDeque, sync::LazyLock}; -use text_size::{TextLen, TextRange, TextSize}; pub use crate::codegen::SyntaxKind; diff --git a/crates/pglt_lsp/Cargo.toml b/crates/pglt_lsp/Cargo.toml index 2a21e08c..ddef9f9a 100644 --- a/crates/pglt_lsp/Cargo.toml +++ b/crates/pglt_lsp/Cargo.toml @@ -12,25 +12,25 @@ version = "0.0.0" [dependencies] -anyhow = { workspace = true } -biome_deserialize = { workspace = true } -futures = "0.3.31" -pglt_analyse = { workspace = true } -pglt_completions = { workspace = true } -pglt_configuration = { workspace = true } -pglt_console = { workspace = true } -pglt_diagnostics = { workspace = true } -pglt_fs = { workspace = true } -pglt_lsp_converters = { workspace = true } -pglt_text_edit = { workspace = true } -pglt_workspace = { workspace = true } -rustc-hash = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -text-size.workspace = true -tokio = { workspace = true, features = ["rt", "io-std"] } -tower-lsp = { version = "0.20.0" } -tracing = { workspace = true, features = ["attributes"] } +anyhow = { workspace = true } +biome_deserialize = { workspace = true } +futures = "0.3.31" +pglt_analyse = { workspace = true } +pglt_completions = { workspace = true } +pglt_configuration = { workspace = true } +pglt_console = { workspace = true } +pglt_diagnostics = { workspace = true } +pglt_fs = { workspace = true } +pglt_lsp_converters = { workspace = true } +pglt_text_edit = { workspace = true } +pglt_text_size.workspace = true +pglt_workspace = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["rt", "io-std"] } +tower-lsp = { version = "0.20.0" } +tracing = { workspace = true, features = ["attributes"] } [dev-dependencies] pglt_test_utils = { workspace = true } diff --git a/crates/pglt_lsp/src/utils.rs b/crates/pglt_lsp/src/utils.rs index 1dc07f91..d1211b6c 100644 --- a/crates/pglt_lsp/src/utils.rs +++ b/crates/pglt_lsp/src/utils.rs @@ -7,12 +7,12 @@ use pglt_diagnostics::{Diagnostic, DiagnosticTags, Location, PrintDescription, S use pglt_lsp_converters::line_index::LineIndex; use pglt_lsp_converters::{PositionEncoding, from_proto, to_proto}; use pglt_text_edit::{CompressedOp, DiffOp, TextEdit}; +use pglt_text_size::{TextRange, TextSize}; use std::any::Any; use std::borrow::Cow; use std::fmt::{Debug, Display}; use std::io; use std::ops::{Add, Range}; -use text_size::{TextRange, TextSize}; use tower_lsp::jsonrpc::Error as LspError; use tower_lsp::lsp_types; use tower_lsp::lsp_types::{self as lsp, CodeDescription, Url}; diff --git a/crates/pglt_lsp_converters/Cargo.toml b/crates/pglt_lsp_converters/Cargo.toml index 48687ba3..c8bace04 100644 --- a/crates/pglt_lsp_converters/Cargo.toml +++ b/crates/pglt_lsp_converters/Cargo.toml @@ -12,10 +12,10 @@ version = "0.0.0" [dependencies] -anyhow = { workspace = true } -rustc-hash = { workspace = true } -text-size.workspace = true -tower-lsp = { version = "0.20.0" } +anyhow = { workspace = true } +pglt_text_size.workspace = true +rustc-hash = { workspace = true } +tower-lsp = { version = "0.20.0" } [dev-dependencies] diff --git a/crates/pglt_lsp_converters/src/from_proto.rs b/crates/pglt_lsp_converters/src/from_proto.rs index 1be89337..464a0934 100644 --- a/crates/pglt_lsp_converters/src/from_proto.rs +++ b/crates/pglt_lsp_converters/src/from_proto.rs @@ -1,7 +1,7 @@ use crate::line_index::LineIndex; use crate::{LineCol, PositionEncoding, WideLineCol}; use anyhow::{Context, Result}; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; use tower_lsp::lsp_types; /// The function is used to convert a LSP position to TextSize. diff --git a/crates/pglt_lsp_converters/src/lib.rs b/crates/pglt_lsp_converters/src/lib.rs index 64aa31cb..284114f7 100644 --- a/crates/pglt_lsp_converters/src/lib.rs +++ b/crates/pglt_lsp_converters/src/lib.rs @@ -1,6 +1,6 @@ //! The crate contains a set of converters to translate between `lsp-types` and `text_size` (and vice versa) types. -use text_size::TextSize; +use pglt_text_size::TextSize; use tower_lsp::lsp_types::{ClientCapabilities, PositionEncodingKind}; pub mod from_proto; @@ -91,7 +91,7 @@ mod tests { use crate::line_index::LineIndex; use crate::to_proto::position; use crate::{LineCol, PositionEncoding, WideEncoding}; - use text_size::TextSize; + use pglt_text_size::TextSize; use tower_lsp::lsp_types::Position; macro_rules! check_conversion { diff --git a/crates/pglt_lsp_converters/src/line_index.rs b/crates/pglt_lsp_converters/src/line_index.rs index 50376566..ffd77bdf 100644 --- a/crates/pglt_lsp_converters/src/line_index.rs +++ b/crates/pglt_lsp_converters/src/line_index.rs @@ -3,8 +3,8 @@ use std::mem; +use pglt_text_size::TextSize; use rustc_hash::FxHashMap; -use text_size::TextSize; use crate::{LineCol, WideChar, WideEncoding, WideLineCol}; diff --git a/crates/pglt_lsp_converters/src/to_proto.rs b/crates/pglt_lsp_converters/src/to_proto.rs index 387abbb5..f4446972 100644 --- a/crates/pglt_lsp_converters/src/to_proto.rs +++ b/crates/pglt_lsp_converters/src/to_proto.rs @@ -1,7 +1,7 @@ use crate::PositionEncoding; use crate::line_index::LineIndex; use anyhow::{Context, Result}; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; use tower_lsp::lsp_types; /// The function is used to convert TextSize to a LSP position. diff --git a/crates/pglt_query_ext/Cargo.toml b/crates/pglt_query_ext/Cargo.toml index 99e94ee6..2c97a9f9 100644 --- a/crates/pglt_query_ext/Cargo.toml +++ b/crates/pglt_query_ext/Cargo.toml @@ -18,7 +18,7 @@ pg_query.workspace = true pglt_diagnostics.workspace = true pglt_lexer.workspace = true pglt_query_ext_codegen.workspace = true -text-size.workspace = true +pglt_text_size.workspace = true [lib] doctest = false diff --git a/crates/pglt_query_ext/src/diagnostics.rs b/crates/pglt_query_ext/src/diagnostics.rs index a091dc97..68879064 100644 --- a/crates/pglt_query_ext/src/diagnostics.rs +++ b/crates/pglt_query_ext/src/diagnostics.rs @@ -1,5 +1,5 @@ use pglt_diagnostics::{Diagnostic, MessageAndDescription}; -use text_size::TextRange; +use pglt_text_size::TextRange; /// A specialized diagnostic for the libpg_query parser. /// diff --git a/crates/pglt_statement_splitter/Cargo.toml b/crates/pglt_statement_splitter/Cargo.toml index 3148d4f6..9fbc5fc9 100644 --- a/crates/pglt_statement_splitter/Cargo.toml +++ b/crates/pglt_statement_splitter/Cargo.toml @@ -15,8 +15,8 @@ version = "0.0.0" pglt_diagnostics = { workspace = true } pglt_lexer.workspace = true pglt_query_ext.workspace = true +pglt_text_size.workspace = true regex.workspace = true -text-size.workspace = true [dev-dependencies] ntest = "0.9.3" diff --git a/crates/pglt_statement_splitter/src/diagnostics.rs b/crates/pglt_statement_splitter/src/diagnostics.rs index 5288e08d..823b6ae7 100644 --- a/crates/pglt_statement_splitter/src/diagnostics.rs +++ b/crates/pglt_statement_splitter/src/diagnostics.rs @@ -1,5 +1,5 @@ use pglt_diagnostics::{Diagnostic, MessageAndDescription}; -use text_size::TextRange; +use pglt_text_size::TextRange; /// A specialized diagnostic for the statement splitter parser. /// diff --git a/crates/pglt_statement_splitter/src/lib.rs b/crates/pglt_statement_splitter/src/lib.rs index 7d74db03..0f5cc45c 100644 --- a/crates/pglt_statement_splitter/src/lib.rs +++ b/crates/pglt_statement_splitter/src/lib.rs @@ -22,7 +22,7 @@ mod tests { use diagnostics::SplitDiagnostic; use ntest::timeout; use pglt_lexer::SyntaxKind; - use text_size::TextRange; + use pglt_text_size::TextRange; use super::*; diff --git a/crates/pglt_statement_splitter/src/parser.rs b/crates/pglt_statement_splitter/src/parser.rs index f89f28dd..1252d2bd 100644 --- a/crates/pglt_statement_splitter/src/parser.rs +++ b/crates/pglt_statement_splitter/src/parser.rs @@ -6,7 +6,7 @@ mod dml; pub use common::source; use pglt_lexer::{SyntaxKind, Token, WHITESPACE_TOKENS}; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; use crate::diagnostics::SplitDiagnostic; diff --git a/crates/pglt_text_edit/Cargo.toml b/crates/pglt_text_edit/Cargo.toml index 1025093b..7e1f8441 100644 --- a/crates/pglt_text_edit/Cargo.toml +++ b/crates/pglt_text_edit/Cargo.toml @@ -12,10 +12,10 @@ version = "0.0.0" [dependencies] -schemars = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } -similar = { workspace = true, features = ["unicode"] } -text-size = { workspace = true, features = ["serde"] } +pglt_text_size = { workspace = true, features = ["serde", "schemars"] } +schemars = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } +similar = { workspace = true, features = ["unicode"] } [features] schemars = ["dep:schemars"] diff --git a/crates/pglt_text_edit/src/lib.rs b/crates/pglt_text_edit/src/lib.rs index 116d7066..3e73114c 100644 --- a/crates/pglt_text_edit/src/lib.rs +++ b/crates/pglt_text_edit/src/lib.rs @@ -10,12 +10,13 @@ use std::{cmp::Ordering, num::NonZeroU32}; +use pglt_text_size::{TextRange, TextSize}; use serde::{Deserialize, Serialize}; pub use similar::ChangeTag; use similar::{TextDiff, utils::TextDiffRemapper}; -use text_size::{TextRange, TextSize}; #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub struct TextEdit { dictionary: String, @@ -23,6 +24,7 @@ pub struct TextEdit { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub enum CompressedOp { DiffOp(DiffOp), @@ -30,6 +32,7 @@ pub enum CompressedOp { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub enum DiffOp { Equal { range: TextRange }, diff --git a/crates/pglt_text_size/Cargo.toml b/crates/pglt_text_size/Cargo.toml new file mode 100644 index 00000000..19290db3 --- /dev/null +++ b/crates/pglt_text_size/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "Utilities to treat text sizes/ranges in a more type-safe" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "pglt_text_size" +repository.workspace = true + +[dependencies] +schemars = { workspace = true, optional = true } +serde = { workspace = true, optional = true } + +[dev-dependencies] +serde_test = "1.0.177" +static_assertions = "1.1" + +[features] +schemars = ["dep:schemars"] +serde = ["dep:serde"] + +[[test]] +name = "serde" +path = "tests/serde.rs" +required-features = ["serde"] diff --git a/crates/pglt_text_size/LICENSE-APACHE b/crates/pglt_text_size/LICENSE-APACHE new file mode 100644 index 00000000..cd7ef7e8 --- /dev/null +++ b/crates/pglt_text_size/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2023 Biome Developers and Contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/crates/pglt_text_size/LICENSE-MIT b/crates/pglt_text_size/LICENSE-MIT new file mode 100644 index 00000000..723a1b43 --- /dev/null +++ b/crates/pglt_text_size/LICENSE-MIT @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023-present Biome Developers and Contributors. +Copyright (c) 2020-2023 Rome Tools is Rome Tools, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/pglt_text_size/src/lib.rs b/crates/pglt_text_size/src/lib.rs new file mode 100644 index 00000000..fbe4fcc6 --- /dev/null +++ b/crates/pglt_text_size/src/lib.rs @@ -0,0 +1,34 @@ +//! Newtypes for working with text sizes/ranges in a more type-safe manner. +//! +//! This library can help with two things: +//! * Reducing storage requirements for offsets and ranges, under the +//! assumption that 32 bits is enough. +//! * Providing standard vocabulary types for applications where text ranges +//! are pervasive. +//! +//! However, you should not use this library simply because you work with +//! strings. In the overwhelming majority of cases, using `usize` and +//! `std::ops::Range` is better. In particular, if you are publishing a +//! library, using only std types in the interface would make it more +//! interoperable. Similarly, if you are writing something like a lexer, which +//! produces, but does not *store* text ranges, then sticking to `usize` would +//! be better. +//! +//! Minimal Supported Rust Version: latest stable. + +#![forbid(unsafe_code)] +#![warn(missing_debug_implementations, missing_docs)] + +mod range; +mod size; +mod traits; + +#[cfg(feature = "schemars")] +mod schemars_impls; +#[cfg(feature = "serde")] +mod serde_impls; + +pub use crate::{range::TextRange, size::TextSize, traits::TextLen}; + +#[cfg(target_pointer_width = "16")] +compile_error!("pglt_text_size assumes usize >= u32 and does not work on 16-bit targets"); diff --git a/crates/pglt_text_size/src/range.rs b/crates/pglt_text_size/src/range.rs new file mode 100644 index 00000000..19c44588 --- /dev/null +++ b/crates/pglt_text_size/src/range.rs @@ -0,0 +1,565 @@ +use cmp::Ordering; + +use { + crate::TextSize, + std::{ + cmp, + convert::TryFrom, + fmt, + ops::{Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, Sub, SubAssign}, + }, +}; + +/// A range in text, represented as a pair of [`TextSize`][struct@TextSize]. +/// +/// It is a logic error for `start` to be greater than `end`. +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] +pub struct TextRange { + // Invariant: start <= end + start: TextSize, + end: TextSize, +} + +impl fmt::Debug for TextRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}..{}", self.start().raw, self.end().raw) + } +} + +impl PartialOrd for TextRange { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TextRange { + fn cmp(&self, other: &Self) -> Ordering { + match self.start.cmp(&other.start) { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => self.end.cmp(&other.end), + } + } +} + +impl TextRange { + /// Creates a new `TextRange` with the given `start` and `end` (`start..end`). + /// + /// # Panics + /// + /// Panics if `end < start`. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let start = TextSize::from(5); + /// let end = TextSize::from(10); + /// let range = TextRange::new(start, end); + /// + /// assert_eq!(range.start(), start); + /// assert_eq!(range.end(), end); + /// assert_eq!(range.len(), end - start); + /// ``` + #[inline] + pub const fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start.raw <= end.raw); + TextRange { start, end } + } + + /// Create a new `TextRange` with the given `offset` and `len` (`offset..offset + len`). + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let text = "0123456789"; + /// + /// let offset = TextSize::from(2); + /// let length = TextSize::from(5); + /// let range = TextRange::at(offset, length); + /// + /// assert_eq!(range, TextRange::new(offset, offset + length)); + /// assert_eq!(&text[range], "23456") + /// ``` + #[inline] + pub const fn at(offset: TextSize, len: TextSize) -> TextRange { + TextRange { + start: offset, + end: TextSize { + raw: offset.raw + len.raw, + }, + } + } + + /// Create a zero-length range at the specified offset (`offset..offset`). + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let point: TextSize; + /// # point = TextSize::from(3); + /// let range = TextRange::empty(point); + /// assert!(range.is_empty()); + /// assert_eq!(range, TextRange::new(point, point)); + /// ``` + #[inline] + pub const fn empty(offset: TextSize) -> TextRange { + TextRange { + start: offset, + end: offset, + } + } + + /// Create a range up to the given end (`..end`). + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let point: TextSize; + /// # point = TextSize::from(12); + /// let range = TextRange::up_to(point); + /// + /// assert_eq!(range.len(), point); + /// assert_eq!(range, TextRange::new(0.into(), point)); + /// assert_eq!(range, TextRange::at(0.into(), point)); + /// ``` + #[inline] + pub const fn up_to(end: TextSize) -> TextRange { + TextRange { + start: TextSize { raw: 0 }, + end, + } + } +} + +/// Identity methods. +impl TextRange { + /// The start point of this range. + #[inline] + pub const fn start(self) -> TextSize { + self.start + } + + /// The end point of this range. + #[inline] + pub const fn end(self) -> TextSize { + self.end + } + + /// The size of this range. + #[inline] + pub const fn len(self) -> TextSize { + // HACK for const fn: math on primitives only + TextSize { + raw: self.end().raw - self.start().raw, + } + } + + /// Check if this range is empty. + #[inline] + pub const fn is_empty(self) -> bool { + // HACK for const fn: math on primitives only + self.start().raw == self.end().raw + } +} + +/// Manipulation methods. +impl TextRange { + /// Check if this range contains an offset. + /// + /// The end index is considered excluded. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let (start, end): (TextSize, TextSize); + /// # start = 10.into(); end = 20.into(); + /// let range = TextRange::new(start, end); + /// assert!(range.contains(start)); + /// assert!(!range.contains(end)); + /// ``` + #[inline] + pub fn contains(self, offset: TextSize) -> bool { + self.start() <= offset && offset < self.end() + } + + /// Check if this range contains an offset. + /// + /// The end index is considered included. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let (start, end): (TextSize, TextSize); + /// # start = 10.into(); end = 20.into(); + /// let range = TextRange::new(start, end); + /// assert!(range.contains_inclusive(start)); + /// assert!(range.contains_inclusive(end)); + /// ``` + #[inline] + pub fn contains_inclusive(self, offset: TextSize) -> bool { + self.start() <= offset && offset <= self.end() + } + + /// Check if this range completely contains another range. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let larger = TextRange::new(0.into(), 20.into()); + /// let smaller = TextRange::new(5.into(), 15.into()); + /// assert!(larger.contains_range(smaller)); + /// assert!(!smaller.contains_range(larger)); + /// + /// // a range always contains itself + /// assert!(larger.contains_range(larger)); + /// assert!(smaller.contains_range(smaller)); + /// ``` + #[inline] + pub fn contains_range(self, other: TextRange) -> bool { + self.start() <= other.start() && other.end() <= self.end() + } + + /// The range covered by both ranges, if it exists. + /// If the ranges touch but do not overlap, the output range is empty. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// assert_eq!( + /// TextRange::intersect( + /// TextRange::new(0.into(), 10.into()), + /// TextRange::new(5.into(), 15.into()), + /// ), + /// Some(TextRange::new(5.into(), 10.into())), + /// ); + /// ``` + #[inline] + pub fn intersect(self, other: TextRange) -> Option { + let start = cmp::max(self.start(), other.start()); + let end = cmp::min(self.end(), other.end()); + if end < start { + return None; + } + Some(TextRange::new(start, end)) + } + + /// Extends the range to cover `other` as well. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// assert_eq!( + /// TextRange::cover( + /// TextRange::new(0.into(), 5.into()), + /// TextRange::new(15.into(), 20.into()), + /// ), + /// TextRange::new(0.into(), 20.into()), + /// ); + /// ``` + #[inline] + pub fn cover(self, other: TextRange) -> TextRange { + let start = cmp::min(self.start(), other.start()); + let end = cmp::max(self.end(), other.end()); + TextRange::new(start, end) + } + + /// Extends the range to cover `other` offsets as well. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// assert_eq!( + /// TextRange::empty(0.into()).cover_offset(20.into()), + /// TextRange::new(0.into(), 20.into()), + /// ) + /// ``` + #[inline] + pub fn cover_offset(self, offset: TextSize) -> TextRange { + self.cover(TextRange::empty(offset)) + } + + /// Add an offset to this range. + /// + /// Note that this is not appropriate for changing where a `TextRange` is + /// within some string; rather, it is for changing the reference anchor + /// that the `TextRange` is measured against. + /// + /// The unchecked version (`Add::add`) will _always_ panic on overflow, + /// in contrast to primitive integers, which check in debug mode only. + #[inline] + pub fn checked_add(self, offset: TextSize) -> Option { + Some(TextRange { + start: self.start.checked_add(offset)?, + end: self.end.checked_add(offset)?, + }) + } + + /// Subtract an offset from this range. + /// + /// Note that this is not appropriate for changing where a `TextRange` is + /// within some string; rather, it is for changing the reference anchor + /// that the `TextRange` is measured against. + /// + /// The unchecked version (`Sub::sub`) will _always_ panic on overflow, + /// in contrast to primitive integers, which check in debug mode only. + #[inline] + pub fn checked_sub(self, offset: TextSize) -> Option { + Some(TextRange { + start: self.start.checked_sub(offset)?, + end: self.end.checked_sub(offset)?, + }) + } + + /// Relative order of the two ranges (overlapping ranges are considered + /// equal). + /// + /// + /// This is useful when, for example, binary searching an array of disjoint + /// ranges. + /// + /// # Examples + /// + /// ``` + /// # use pglt_text_size::*; + /// # use std::cmp::Ordering; + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(4.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Less); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(3.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Less); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(2.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Equal); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(2.into(), 2.into()); + /// assert_eq!(a.ordering(b), Ordering::Equal); + /// + /// let a = TextRange::new(2.into(), 3.into()); + /// let b = TextRange::new(2.into(), 2.into()); + /// assert_eq!(a.ordering(b), Ordering::Greater); + /// ``` + #[inline] + pub fn ordering(self, other: TextRange) -> Ordering { + if self.end() <= other.start() { + Ordering::Less + } else if other.end() <= self.start() { + Ordering::Greater + } else { + Ordering::Equal + } + } + + /// Subtracts an offset from the start position. + /// + /// + /// ## Panics + /// If `start - amount` is less than zero. + /// + /// ## Examples + /// + /// ``` + /// use pglt_text_size::{TextRange, TextSize}; + /// + /// let range = TextRange::new(TextSize::from(5), TextSize::from(10)); + /// assert_eq!(range.sub_start(TextSize::from(2)), TextRange::new(TextSize::from(3), TextSize::from(10))); + /// ``` + #[inline] + pub fn sub_start(&self, amount: TextSize) -> TextRange { + TextRange::new(self.start() - amount, self.end()) + } + + /// Adds an offset to the start position. + /// + /// ## Panics + /// If `start + amount > end` + /// + /// ## Examples + /// + /// ``` + /// use pglt_text_size::{TextRange, TextSize}; + /// + /// let range = TextRange::new(TextSize::from(5), TextSize::from(10)); + /// assert_eq!(range.add_start(TextSize::from(3)), TextRange::new(TextSize::from(8), TextSize::from(10))); + /// ``` + #[inline] + pub fn add_start(&self, amount: TextSize) -> TextRange { + TextRange::new(self.start() + amount, self.end()) + } + + /// Subtracts an offset from the end position. + /// + /// + /// ## Panics + /// If `end - amount < 0` or `end - amount < start` + /// + /// ## Examples + /// + /// ``` + /// use pglt_text_size::{TextRange, TextSize}; + /// + /// let range = TextRange::new(TextSize::from(5), TextSize::from(10)); + /// assert_eq!(range.sub_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(8))); + /// ``` + #[inline] + pub fn sub_end(&self, amount: TextSize) -> TextRange { + TextRange::new(self.start(), self.end() - amount) + } + + /// Adds an offset to the end position. + /// + /// + /// ## Panics + /// If `end + amount > u32::MAX` + /// + /// ## Examples + /// + /// ``` + /// use pglt_text_size::{TextRange, TextSize}; + /// + /// let range = TextRange::new(TextSize::from(5), TextSize::from(10)); + /// assert_eq!(range.add_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(12))); + /// ``` + #[inline] + pub fn add_end(&self, amount: TextSize) -> TextRange { + TextRange::new(self.start(), self.end() + amount) + } +} + +impl Index for str { + type Output = str; + #[inline] + fn index(&self, index: TextRange) -> &str { + &self[Range::::from(index)] + } +} + +impl Index for String { + type Output = str; + #[inline] + fn index(&self, index: TextRange) -> &str { + &self[Range::::from(index)] + } +} + +impl IndexMut for str { + #[inline] + fn index_mut(&mut self, index: TextRange) -> &mut str { + &mut self[Range::::from(index)] + } +} + +impl IndexMut for String { + #[inline] + fn index_mut(&mut self, index: TextRange) -> &mut str { + &mut self[Range::::from(index)] + } +} + +impl RangeBounds for TextRange { + fn start_bound(&self) -> Bound<&TextSize> { + Bound::Included(&self.start) + } + + fn end_bound(&self) -> Bound<&TextSize> { + Bound::Excluded(&self.end) + } +} + +impl From for Range +where + T: From, +{ + #[inline] + fn from(r: TextRange) -> Self { + r.start().into()..r.end().into() + } +} + +macro_rules! ops { + (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => { + impl $Op<&TextSize> for TextRange { + type Output = TextRange; + #[inline] + fn $f(self, other: &TextSize) -> TextRange { + self $op *other + } + } + impl $Op for &TextRange + where + TextRange: $Op, + { + type Output = TextRange; + #[inline] + fn $f(self, other: T) -> TextRange { + *self $op other + } + } + }; +} + +impl Add for TextRange { + type Output = TextRange; + #[inline] + fn add(self, offset: TextSize) -> TextRange { + self.checked_add(offset) + .expect("TextRange +offset overflowed") + } +} + +impl Sub for TextRange { + type Output = TextRange; + #[inline] + fn sub(self, offset: TextSize) -> TextRange { + self.checked_sub(offset) + .expect("TextRange -offset overflowed") + } +} + +ops!(impl Add for TextRange by fn add = +); +ops!(impl Sub for TextRange by fn sub = -); + +impl AddAssign for TextRange +where + TextRange: Add, +{ + #[inline] + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextRange +where + TextRange: Sub, +{ + #[inline] + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} + +impl TryFrom<(usize, usize)> for TextRange { + type Error = std::num::TryFromIntError; + #[inline] + fn try_from((start, end): (usize, usize)) -> Result { + Ok(TextRange::new( + TextSize::try_from(start)?, + TextSize::try_from(end)?, + )) + } +} diff --git a/crates/pglt_text_size/src/schemars_impls.rs b/crates/pglt_text_size/src/schemars_impls.rs new file mode 100644 index 00000000..ec528c15 --- /dev/null +++ b/crates/pglt_text_size/src/schemars_impls.rs @@ -0,0 +1,33 @@ +//! This module implements the [JsonSchema] trait from the [schemars] crate for +//! [TextSize] and [TextRange] if the `schemars` feature is enabled. This trait +//! exposes meta-information on how a given type is serialized and deserialized +//! using `serde`, and is currently used to generate autocomplete information +//! for the `biome.json` configuration file and TypeScript types for the node.js +//! bindings to the Workspace API + +use crate::{TextRange, TextSize}; +use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::Schema}; + +impl JsonSchema for TextSize { + fn schema_name() -> String { + String::from("TextSize") + } + + fn json_schema(r#gen: &mut SchemaGenerator) -> Schema { + // TextSize is represented as a raw u32, see serde_impls.rs for the + // actual implementation + ::json_schema(r#gen) + } +} + +impl JsonSchema for TextRange { + fn schema_name() -> String { + String::from("TextRange") + } + + fn json_schema(r#gen: &mut SchemaGenerator) -> Schema { + // TextSize is represented as (TextSize, TextSize), see serde_impls.rs + // for the actual implementation + <(TextSize, TextSize)>::json_schema(r#gen) + } +} diff --git a/crates/pglt_text_size/src/serde_impls.rs b/crates/pglt_text_size/src/serde_impls.rs new file mode 100644 index 00000000..9e4e42b7 --- /dev/null +++ b/crates/pglt_text_size/src/serde_impls.rs @@ -0,0 +1,47 @@ +use { + crate::{TextRange, TextSize}, + serde::{Deserialize, Deserializer, Serialize, Serializer, de}, +}; + +impl Serialize for TextSize { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.raw.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TextSize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + u32::deserialize(deserializer).map(TextSize::from) + } +} + +impl Serialize for TextRange { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (self.start(), self.end()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TextRange { + #[allow(clippy::nonminimal_bool)] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let (start, end) = Deserialize::deserialize(deserializer)?; + if !(start <= end) { + return Err(de::Error::custom(format!( + "invalid range: {start:?}..{end:?}" + ))); + } + Ok(TextRange::new(start, end)) + } +} diff --git a/crates/pglt_text_size/src/size.rs b/crates/pglt_text_size/src/size.rs new file mode 100644 index 00000000..7d4c100a --- /dev/null +++ b/crates/pglt_text_size/src/size.rs @@ -0,0 +1,160 @@ +use { + crate::TextLen, + std::{ + convert::TryFrom, + fmt, iter, + num::TryFromIntError, + ops::{Add, AddAssign, Sub, SubAssign}, + }, +}; + +/// A measure of text length. Also, equivalently, an index into text. +/// +/// This is a UTF-8 bytes offset stored as `u32`, but +/// most clients should treat it as an opaque measure. +/// +/// For cases that need to escape `TextSize` and return to working directly +/// with primitive integers, `TextSize` can be converted losslessly to/from +/// `u32` via [`From`] conversions as well as losslessly be converted [`Into`] +/// `usize`. The `usize -> TextSize` direction can be done via [`TryFrom`]. +/// +/// These escape hatches are primarily required for unit testing and when +/// converting from UTF-8 size to another coordinate space, such as UTF-16. +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TextSize { + pub(crate) raw: u32, +} + +impl fmt::Debug for TextSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.raw) + } +} + +impl TextSize { + /// The text size of some primitive text-like object. + /// + /// Accepts `char`, `&str`, and `&String`. + /// + /// # Examples + /// + /// ```rust + /// # use pglt_text_size::*; + /// let char_size = TextSize::of('🦀'); + /// assert_eq!(char_size, TextSize::from(4)); + /// + /// let str_size = TextSize::of("rust-analyzer"); + /// assert_eq!(str_size, TextSize::from(13)); + /// ``` + #[inline] + pub fn of(text: T) -> TextSize { + text.text_len() + } +} + +/// Methods to act like a primitive integer type, where reasonably applicable. +// Last updated for parity with Rust 1.42.0. +impl TextSize { + /// Checked addition. Returns `None` if overflow occurred. + #[inline] + pub fn checked_add(self, rhs: TextSize) -> Option { + self.raw.checked_add(rhs.raw).map(|raw| TextSize { raw }) + } + + /// Checked subtraction. Returns `None` if overflow occurred. + #[inline] + pub fn checked_sub(self, rhs: TextSize) -> Option { + self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw }) + } +} + +impl From for TextSize { + #[inline] + fn from(raw: u32) -> Self { + TextSize { raw } + } +} + +impl From for u32 { + #[inline] + fn from(value: TextSize) -> Self { + value.raw + } +} + +impl TryFrom for TextSize { + type Error = TryFromIntError; + #[inline] + fn try_from(value: usize) -> Result { + Ok(u32::try_from(value)?.into()) + } +} + +impl From for usize { + #[inline] + fn from(value: TextSize) -> Self { + value.raw as usize + } +} + +macro_rules! ops { + (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { + impl $Op for TextSize { + type Output = TextSize; + #[inline] + fn $f(self, other: TextSize) -> TextSize { + TextSize { raw: self.raw $op other.raw } + } + } + impl $Op<&TextSize> for TextSize { + type Output = TextSize; + #[inline] + fn $f(self, other: &TextSize) -> TextSize { + self $op *other + } + } + impl $Op for &TextSize + where + TextSize: $Op, + { + type Output = TextSize; + #[inline] + fn $f(self, other: T) -> TextSize { + *self $op other + } + } + }; +} + +ops!(impl Add for TextSize by fn add = +); +ops!(impl Sub for TextSize by fn sub = -); + +impl AddAssign for TextSize +where + TextSize: Add, +{ + #[inline] + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextSize +where + TextSize: Sub, +{ + #[inline] + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} + +impl iter::Sum for TextSize +where + TextSize: Add, +{ + #[inline] + fn sum>(iter: I) -> TextSize { + iter.fold(0.into(), Add::add) + } +} diff --git a/crates/pglt_text_size/src/traits.rs b/crates/pglt_text_size/src/traits.rs new file mode 100644 index 00000000..d0bb6c1f --- /dev/null +++ b/crates/pglt_text_size/src/traits.rs @@ -0,0 +1,36 @@ +use {crate::TextSize, std::convert::TryInto}; + +use priv_in_pub::Sealed; +mod priv_in_pub { + pub trait Sealed {} +} + +/// Primitives with a textual length that can be passed to [`TextSize::of`]. +pub trait TextLen: Copy + Sealed { + /// The textual length of this primitive. + fn text_len(self) -> TextSize; +} + +impl Sealed for &'_ str {} +impl TextLen for &'_ str { + #[inline] + fn text_len(self) -> TextSize { + self.len().try_into().unwrap() + } +} + +impl Sealed for &'_ String {} +impl TextLen for &'_ String { + #[inline] + fn text_len(self) -> TextSize { + self.as_str().text_len() + } +} + +impl Sealed for char {} +impl TextLen for char { + #[inline] + fn text_len(self) -> TextSize { + (self.len_utf8() as u32).into() + } +} diff --git a/crates/pglt_text_size/tests/auto_traits.rs b/crates/pglt_text_size/tests/auto_traits.rs new file mode 100644 index 00000000..0a64ba15 --- /dev/null +++ b/crates/pglt_text_size/tests/auto_traits.rs @@ -0,0 +1,18 @@ +use { + pglt_text_size::*, + static_assertions::*, + std::{ + fmt::Debug, + hash::Hash, + marker::{Send, Sync}, + panic::{RefUnwindSafe, UnwindSafe}, + }, +}; + +// auto traits +assert_impl_all!(TextSize: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); +assert_impl_all!(TextRange: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); + +// common traits +assert_impl_all!(TextSize: Copy, Debug, Default, Hash, Ord); +assert_impl_all!(TextRange: Copy, Debug, Default, Hash, Eq); diff --git a/crates/pglt_text_size/tests/contructors.rs b/crates/pglt_text_size/tests/contructors.rs new file mode 100644 index 00000000..ea4a4064 --- /dev/null +++ b/crates/pglt_text_size/tests/contructors.rs @@ -0,0 +1,24 @@ +use pglt_text_size::TextSize; + +#[derive(Copy, Clone)] +struct BadRope<'a>(&'a [&'a str]); + +impl BadRope<'_> { + fn text_len(self) -> TextSize { + self.0.iter().copied().map(TextSize::of).sum() + } +} + +#[test] +fn main() { + let x: char = 'c'; + let _ = TextSize::of(x); + + let x: &str = "hello"; + let _ = TextSize::of(x); + + let x: &String = &"hello".into(); + let _ = TextSize::of(x); + + let _ = BadRope(&[""]).text_len(); +} diff --git a/crates/pglt_text_size/tests/indexing.rs b/crates/pglt_text_size/tests/indexing.rs new file mode 100644 index 00000000..4e66c923 --- /dev/null +++ b/crates/pglt_text_size/tests/indexing.rs @@ -0,0 +1,8 @@ +use pglt_text_size::*; + +#[test] +fn main() { + let range = TextRange::default(); + let _ = &""[range]; + let _ = &String::new()[range]; +} diff --git a/crates/pglt_text_size/tests/main.rs b/crates/pglt_text_size/tests/main.rs new file mode 100644 index 00000000..6b9fe923 --- /dev/null +++ b/crates/pglt_text_size/tests/main.rs @@ -0,0 +1,76 @@ +use {pglt_text_size::*, std::ops}; + +fn size(x: u32) -> TextSize { + TextSize::from(x) +} + +fn range(x: ops::Range) -> TextRange { + TextRange::new(x.start.into(), x.end.into()) +} + +#[test] +fn sum() { + let xs: Vec = vec![size(0), size(1), size(2)]; + assert_eq!(xs.iter().sum::(), size(3)); + assert_eq!(xs.into_iter().sum::(), size(3)); +} + +#[test] +fn math() { + assert_eq!(size(10) + size(5), size(15)); + assert_eq!(size(10) - size(5), size(5)); +} + +#[test] +fn checked_math() { + assert_eq!(size(1).checked_add(size(1)), Some(size(2))); + assert_eq!(size(1).checked_sub(size(1)), Some(size(0))); + assert_eq!(size(1).checked_sub(size(2)), None); + assert_eq!(size(!0).checked_add(size(1)), None); +} + +#[test] +#[rustfmt::skip] +fn contains() { + assert!( range(2..4).contains_range(range(2..3))); + assert!( ! range(2..4).contains_range(range(1..3))); +} + +#[test] +fn intersect() { + assert_eq!(range(1..2).intersect(range(2..3)), Some(range(2..2))); + assert_eq!(range(1..5).intersect(range(2..3)), Some(range(2..3))); + assert_eq!(range(1..2).intersect(range(3..4)), None); +} + +#[test] +fn cover() { + assert_eq!(range(1..2).cover(range(2..3)), range(1..3)); + assert_eq!(range(1..5).cover(range(2..3)), range(1..5)); + assert_eq!(range(1..2).cover(range(4..5)), range(1..5)); +} + +#[test] +fn cover_offset() { + assert_eq!(range(1..3).cover_offset(size(0)), range(0..3)); + assert_eq!(range(1..3).cover_offset(size(1)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(2)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(3)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(4)), range(1..4)); +} + +#[test] +#[rustfmt::skip] +fn contains_point() { + assert!( ! range(1..3).contains(size(0))); + assert!( range(1..3).contains(size(1))); + assert!( range(1..3).contains(size(2))); + assert!( ! range(1..3).contains(size(3))); + assert!( ! range(1..3).contains(size(4))); + + assert!( ! range(1..3).contains_inclusive(size(0))); + assert!( range(1..3).contains_inclusive(size(1))); + assert!( range(1..3).contains_inclusive(size(2))); + assert!( range(1..3).contains_inclusive(size(3))); + assert!( ! range(1..3).contains_inclusive(size(4))); +} diff --git a/crates/pglt_text_size/tests/serde.rs b/crates/pglt_text_size/tests/serde.rs new file mode 100644 index 00000000..e350b566 --- /dev/null +++ b/crates/pglt_text_size/tests/serde.rs @@ -0,0 +1,79 @@ +use {pglt_text_size::*, serde_test::*, std::ops}; + +fn size(x: u32) -> TextSize { + TextSize::from(x) +} + +fn range(x: ops::Range) -> TextRange { + TextRange::new(x.start.into(), x.end.into()) +} + +#[test] +fn size_serialization() { + assert_tokens(&size(00), &[Token::U32(00)]); + assert_tokens(&size(10), &[Token::U32(10)]); + assert_tokens(&size(20), &[Token::U32(20)]); + assert_tokens(&size(30), &[Token::U32(30)]); +} + +#[test] +fn range_serialization() { + assert_tokens( + &range(00..10), + &[ + Token::Tuple { len: 2 }, + Token::U32(00), + Token::U32(10), + Token::TupleEnd, + ], + ); + assert_tokens( + &range(10..20), + &[ + Token::Tuple { len: 2 }, + Token::U32(10), + Token::U32(20), + Token::TupleEnd, + ], + ); + assert_tokens( + &range(20..30), + &[ + Token::Tuple { len: 2 }, + Token::U32(20), + Token::U32(30), + Token::TupleEnd, + ], + ); + assert_tokens( + &range(30..40), + &[ + Token::Tuple { len: 2 }, + Token::U32(30), + Token::U32(40), + Token::TupleEnd, + ], + ); +} + +#[test] +fn invalid_range_deserialization() { + assert_tokens::( + &range(62..92), + &[ + Token::Tuple { len: 2 }, + Token::U32(62), + Token::U32(92), + Token::TupleEnd, + ], + ); + assert_de_tokens_error::( + &[ + Token::Tuple { len: 2 }, + Token::U32(92), + Token::U32(62), + Token::TupleEnd, + ], + "invalid range: 92..62", + ); +} diff --git a/crates/pglt_typecheck/Cargo.toml b/crates/pglt_typecheck/Cargo.toml index c078a922..19fc7b65 100644 --- a/crates/pglt_typecheck/Cargo.toml +++ b/crates/pglt_typecheck/Cargo.toml @@ -16,8 +16,8 @@ pglt_console.workspace = true pglt_diagnostics.workspace = true pglt_query_ext.workspace = true pglt_schema_cache.workspace = true +pglt_text_size.workspace = true sqlx.workspace = true -text-size.workspace = true tokio.workspace = true tree-sitter.workspace = true tree_sitter_sql.workspace = true diff --git a/crates/pglt_typecheck/src/diagnostics.rs b/crates/pglt_typecheck/src/diagnostics.rs index 495af5a6..741865cf 100644 --- a/crates/pglt_typecheck/src/diagnostics.rs +++ b/crates/pglt_typecheck/src/diagnostics.rs @@ -2,8 +2,8 @@ use std::io; use pglt_console::markup; use pglt_diagnostics::{Advices, Diagnostic, LogCategory, MessageAndDescription, Severity, Visit}; +use pglt_text_size::TextRange; use sqlx::postgres::{PgDatabaseError, PgSeverity}; -use text_size::TextRange; /// A specialized diagnostic for the typechecker. /// diff --git a/crates/pglt_typecheck/src/lib.rs b/crates/pglt_typecheck/src/lib.rs index 90b78777..341f21b1 100644 --- a/crates/pglt_typecheck/src/lib.rs +++ b/crates/pglt_typecheck/src/lib.rs @@ -2,11 +2,11 @@ mod diagnostics; pub use diagnostics::TypecheckDiagnostic; use diagnostics::create_type_error; +use pglt_text_size::TextRange; use sqlx::Executor; use sqlx::PgPool; use sqlx::postgres::PgDatabaseError; pub use sqlx::postgres::PgSeverity; -use text_size::TextRange; #[derive(Debug)] pub struct TypecheckParams<'a> { diff --git a/crates/pglt_wasm/Cargo.toml b/crates/pglt_wasm/Cargo.toml new file mode 100644 index 00000000..1c920f94 --- /dev/null +++ b/crates/pglt_wasm/Cargo.toml @@ -0,0 +1,34 @@ +[package] +authors.workspace = true +categories.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "pglt_wasm" +repository.workspace = true +rust-version.workspace = true +version = "0.1.0" + +[dependencies] +js-sys = "0.3.77" +pglt_console = { workspace = true } +pglt_diagnostics = { workspace = true } +pglt_fs = { workspace = true } +pglt_workspace = { workspace = true, features = ["schema"] } +serde = { workspace = true } +serde-wasm-bindgen = "0.6.5" +wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } + +[build-dependencies] +biome_js_factory = { git = "https://github.com/biomejs/biome" } +biome_js_formatter = { git = "https://github.com/biomejs/biome" } +pglt_workspace = { workspace = true, features = ["schema"] } +quote = "1.0.14" +schemars = { workspace = true } diff --git a/crates/pglt_wasm/build.rs b/crates/pglt_wasm/build.rs new file mode 100644 index 00000000..9c027a78 --- /dev/null +++ b/crates/pglt_wasm/build.rs @@ -0,0 +1,108 @@ +use biome_js_factory::syntax::JsFileSource; +use biome_js_factory::{ + make, + syntax::{AnyJsDeclaration, AnyJsModuleItem, AnyJsStatement}, +}; +use biome_js_formatter::{context::JsFormatOptions, format_node}; +use pglt_workspace::workspace_types::{ModuleQueue, generate_type, methods}; +use quote::{format_ident, quote}; +use schemars::r#gen::{SchemaGenerator, SchemaSettings}; +use std::{env, fs, io, path::PathBuf}; + +fn main() -> io::Result<()> { + let methods = methods(); + + let mut items = Vec::new(); + let mut queue = ModuleQueue::default(); + + // FIXME: a lot of this code is duplicated in xtask/codegen/src/generate_bindings.rs + for method in &methods { + generate_type(&mut items, &mut queue, &method.params); + generate_type(&mut items, &mut queue, &method.result); + } + + let module = make::js_module( + make::js_directive_list(None), + make::js_module_item_list(items.into_iter().map(|(decl, _)| { + AnyJsModuleItem::AnyJsStatement(match decl { + AnyJsDeclaration::JsClassDeclaration(decl) => { + AnyJsStatement::JsClassDeclaration(decl) + } + AnyJsDeclaration::JsFunctionDeclaration(decl) => { + AnyJsStatement::JsFunctionDeclaration(decl) + } + AnyJsDeclaration::JsVariableDeclaration(decl) => { + AnyJsStatement::JsVariableStatement(make::js_variable_statement(decl).build()) + } + AnyJsDeclaration::TsDeclareFunctionDeclaration(decl) => { + AnyJsStatement::TsDeclareFunctionDeclaration(decl) + } + AnyJsDeclaration::TsEnumDeclaration(decl) => { + AnyJsStatement::TsEnumDeclaration(decl) + } + AnyJsDeclaration::TsExternalModuleDeclaration(decl) => { + AnyJsStatement::TsExternalModuleDeclaration(decl) + } + AnyJsDeclaration::TsGlobalDeclaration(decl) => { + AnyJsStatement::TsGlobalDeclaration(decl) + } + AnyJsDeclaration::TsImportEqualsDeclaration(decl) => { + AnyJsStatement::TsImportEqualsDeclaration(decl) + } + AnyJsDeclaration::TsInterfaceDeclaration(decl) => { + AnyJsStatement::TsInterfaceDeclaration(decl) + } + AnyJsDeclaration::TsModuleDeclaration(decl) => { + AnyJsStatement::TsModuleDeclaration(decl) + } + AnyJsDeclaration::TsTypeAliasDeclaration(decl) => { + AnyJsStatement::TsTypeAliasDeclaration(decl) + } + }) + })), + make::eof(), + ) + .build(); + + // Wasm-bindgen will paste the generated TS code as-is into the final .d.ts file, + // ensure it looks good by running it through the biome formatter + let formatted = format_node(JsFormatOptions::new(JsFileSource::ts()), module.syntax()).unwrap(); + let printed = formatted.print().unwrap(); + let definitions = printed.into_code(); + + // Generate wasm-bindgen extern type imports for all the types defined in the TS code + let types = queue.visited().iter().map(|name| { + // we prefix types with "I" to avoid conflicts with Rust keywords + let ident = format_ident!("I{name}"); + if name.contains('_') { + quote! { + #[wasm_bindgen(typescript_type = #name)] + #[expect(non_camel_case_types)] + pub type #ident; + } + } else { + quote! { + #[wasm_bindgen(typescript_type = #name)] + pub type #ident; + } + } + }); + + let tokens = quote! { + #[wasm_bindgen(typescript_custom_section)] + const TS_TYPEDEFS: &'static str = #definitions; + + #[wasm_bindgen] + extern "C" { + #( #types )* + } + }; + + let out_dir = env::var("OUT_DIR").unwrap(); + fs::write( + PathBuf::from(out_dir).join("ts_types.rs"), + tokens.to_string(), + )?; + + Ok(()) +} diff --git a/crates/pglt_wasm/src/lib.rs b/crates/pglt_wasm/src/lib.rs new file mode 100644 index 00000000..21eeadeb --- /dev/null +++ b/crates/pglt_wasm/src/lib.rs @@ -0,0 +1,105 @@ +use js_sys::Error; +use pglt_fs::MemoryFileSystem; +use pglt_workspace::workspace::{ + self, ChangeFileParams, CloseFileParams, CompletionParams, GetFileContentParams, + PullDiagnosticsParams, UpdateSettingsParams, +}; +use wasm_bindgen::prelude::*; + +mod utils; + +pub use crate::utils::DiagnosticPrinter; +use crate::utils::{into_error, set_panic_hook}; + +#[wasm_bindgen(start)] +pub fn main() { + set_panic_hook(); +} + +#[wasm_bindgen] +pub struct Workspace { + inner: Box, +} + +#[wasm_bindgen] +impl Workspace { + #[wasm_bindgen(constructor)] + pub fn new() -> Workspace { + Workspace { + inner: workspace::server(), + } + } + + #[wasm_bindgen(js_name = updateSettings)] + pub fn update_settings( + &self, + params: IUpdateSettingsParams, + ) -> Result { + let params: UpdateSettingsParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + let result = self.inner.update_settings(params).map_err(into_error)?; + to_value(&result) + .map(IUpdateSettingsResult::from) + .map_err(into_error) + } + + #[wasm_bindgen(js_name = getCompletions)] + pub fn get_completions(&self, params: ICompletionsParams) -> Result<(), Error> { + let params: CompletionParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + self.inner.open_file(params).map_err(into_error) + } + + #[wasm_bindgen(js_name = openFile)] + pub fn open_file(&self, params: IOpenFileParams) -> Result<(), Error> { + let params: OpenFileParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + self.inner.open_file(params).map_err(into_error) + } + + #[wasm_bindgen(js_name = getFileContent)] + pub fn get_file_content(&self, params: IGetFileContentParams) -> Result { + let params: GetFileContentParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + self.inner.get_file_content(params).map_err(into_error) + } + + #[wasm_bindgen(js_name = changeFile)] + pub fn change_file(&self, params: IChangeFileParams) -> Result<(), Error> { + let params: ChangeFileParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + self.inner.change_file(params).map_err(into_error) + } + + #[wasm_bindgen(js_name = closeFile)] + pub fn close_file(&self, params: ICloseFileParams) -> Result<(), Error> { + let params: CloseFileParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + self.inner.close_file(params).map_err(into_error) + } + + #[wasm_bindgen(js_name = pullDiagnostics)] + pub fn pull_diagnostics( + &self, + params: IPullDiagnosticsParams, + ) -> Result { + let params: PullDiagnosticsParams = + serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?; + let result = self.inner.pull_diagnostics(params).map_err(into_error)?; + to_value(&result) + .map(IPullDiagnosticsResult::from) + .map_err(into_error) + } +} + +impl Default for Workspace { + fn default() -> Self { + Self::new() + } +} + +fn to_value( + value: &T, +) -> Result { + value.serialize(&serde_wasm_bindgen::Serializer::new().serialize_missing_as_null(true)) +} diff --git a/crates/pglt_wasm/src/utils.rs b/crates/pglt_wasm/src/utils.rs new file mode 100644 index 00000000..dab0cce3 --- /dev/null +++ b/crates/pglt_wasm/src/utils.rs @@ -0,0 +1,80 @@ +use std::fmt::Display; + +use js_sys::Error; +use wasm_bindgen::prelude::*; + +use pglt_console::fmt::HTML; +use pglt_console::{fmt::Formatter, markup}; +use pglt_diagnostics::serde::Diagnostic; +use pglt_diagnostics::{DiagnosticExt, LineIndexBuf, PrintDiagnostic, SourceCode}; + +use super::IDiagnostic; + +pub(crate) fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} + +#[wasm_bindgen] +pub struct DiagnosticPrinter { + file_name: String, + file_source: SourceCode, + buffer: Vec, +} + +#[wasm_bindgen] +impl DiagnosticPrinter { + #[wasm_bindgen(constructor)] + pub fn new(file_name: String, file_source: String) -> Self { + let line_starts = LineIndexBuf::from_source_text(&file_source); + Self { + file_name, + file_source: SourceCode { + text: file_source, + line_starts: Some(line_starts), + }, + buffer: Vec::new(), + } + } + + pub fn print_simple(&mut self, diagnostic: IDiagnostic) -> Result<(), Error> { + self.print(diagnostic, |err| PrintDiagnostic::simple(err)) + } + + pub fn print_verbose(&mut self, diagnostic: IDiagnostic) -> Result<(), Error> { + self.print(diagnostic, |err| PrintDiagnostic::verbose(err)) + } + + fn print( + &mut self, + diagnostic: IDiagnostic, + printer: fn(&pglt_diagnostics::Error) -> PrintDiagnostic, + ) -> Result<(), Error> { + let diag: Diagnostic = + serde_wasm_bindgen::from_value(diagnostic.into()).map_err(into_error)?; + let err = diag + .with_file_path(&self.file_name) + .with_file_source_code(&self.file_source); + + let mut html = HTML::new(&mut self.buffer); + Formatter::new(&mut html) + .write_markup(markup!({ printer(&err) })) + .map_err(into_error)?; + + Ok(()) + } + + pub fn finish(self) -> Result { + String::from_utf8(self.buffer).map_err(into_error) + } +} + +pub(crate) fn into_error(err: E) -> Error { + Error::new(&err.to_string()) +} diff --git a/crates/pglt_workspace/Cargo.toml b/crates/pglt_workspace/Cargo.toml index 2f36d0d0..645339e1 100644 --- a/crates/pglt_workspace/Cargo.toml +++ b/crates/pglt_workspace/Cargo.toml @@ -26,12 +26,13 @@ pglt_fs = { workspace = true, features = ["serde"] } pglt_query_ext = { workspace = true } pglt_schema_cache = { workspace = true } pglt_statement_splitter = { workspace = true } +pglt_text_size.workspace = true pglt_typecheck = { workspace = true } rustc-hash = { workspace = true } +schemars = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["raw_value"] } sqlx.workspace = true -text-size.workspace = true tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } toml = { workspace = true } tracing = { workspace = true, features = ["attributes", "log"] } @@ -45,3 +46,4 @@ tempfile = "3.15.0" doctest = false [features] +schema = ["dep:schemars", "pglt_configuration/schema", "pglt_fs/serde", "pglt_completions/schema"] diff --git a/crates/pglt_workspace/src/lib.rs b/crates/pglt_workspace/src/lib.rs index 6985d09f..7f4c221e 100644 --- a/crates/pglt_workspace/src/lib.rs +++ b/crates/pglt_workspace/src/lib.rs @@ -9,6 +9,8 @@ pub mod dome; pub mod matcher; pub mod settings; pub mod workspace; +#[cfg(feature = "schema")] +pub mod workspace_types; pub use crate::diagnostics::{TransportError, WorkspaceError}; pub use crate::workspace::Workspace; diff --git a/crates/pglt_workspace/src/workspace.rs b/crates/pglt_workspace/src/workspace.rs index 41139aac..55b2dcec 100644 --- a/crates/pglt_workspace/src/workspace.rs +++ b/crates/pglt_workspace/src/workspace.rs @@ -4,8 +4,8 @@ pub use self::client::{TransportRequest, WorkspaceClient, WorkspaceTransport}; use pglt_analyse::RuleCategories; use pglt_configuration::{PartialConfiguration, RuleSelector}; use pglt_fs::PgLTPath; +use pglt_text_size::{TextRange, TextSize}; use serde::{Deserialize, Serialize}; -use text_size::{TextRange, TextSize}; use crate::WorkspaceError; @@ -13,6 +13,7 @@ mod client; mod server; #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct OpenFileParams { pub path: PgLTPath, pub content: String, @@ -20,11 +21,13 @@ pub struct OpenFileParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CloseFileParams { pub path: PgLTPath, } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ChangeFileParams { pub path: PgLTPath, pub version: i32, @@ -32,6 +35,7 @@ pub struct ChangeFileParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct PullDiagnosticsParams { pub path: PgLTPath, pub categories: RuleCategories, @@ -41,6 +45,7 @@ pub struct PullDiagnosticsParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct CompletionParams { /// The File for which a completion is requested. pub path: PgLTPath, @@ -49,24 +54,15 @@ pub struct CompletionParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct PullDiagnosticsResult { pub diagnostics: Vec, pub errors: usize, pub skipped_diagnostics: u64, } -#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] -/// Which fixes should be applied during the analyzing phase -pub enum FixFileMode { - /// Applies [safe](pglt_diagnostics::Applicability::Always) fixes - SafeFixes, - /// Applies [safe](pglt_diagnostics::Applicability::Always) and [unsafe](pglt_diagnostics::Applicability::MaybeIncorrect) fixes - SafeAndUnsafeFixes, - /// Applies suppression comments to existing diagnostics when using `--suppress` - ApplySuppressions, -} - #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ChangeParams { /// The range of the file that changed. If `None`, the whole file changed. pub range: Option, @@ -80,11 +76,13 @@ impl ChangeParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct IsPathIgnoredParams { pub pglt_path: PgLTPath, } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct UpdateSettingsParams { pub configuration: PartialConfiguration, pub vcs_base_path: Option, @@ -94,11 +92,13 @@ pub struct UpdateSettingsParams { } #[derive(Debug, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct GetFileContentParams { pub path: PgLTPath, } #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ServerInfo { /// The name of the server as defined by the server. pub name: String, @@ -213,75 +213,6 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { skip, }) } - // - // pub fn pull_actions( - // &self, - // range: Option, - // only: Vec, - // skip: Vec, - // suppression_reason: Option, - // ) -> Result { - // self.workspace.pull_actions(PullActionsParams { - // path: self.path.clone(), - // range, - // only, - // skip, - // suppression_reason, - // }) - // } - // - // pub fn format_file(&self) -> Result { - // self.workspace.format_file(FormatFileParams { - // path: self.path.clone(), - // }) - // } - // - // pub fn format_range(&self, range: TextRange) -> Result { - // self.workspace.format_range(FormatRangeParams { - // path: self.path.clone(), - // range, - // }) - // } - // - // pub fn format_on_type(&self, offset: TextSize) -> Result { - // self.workspace.format_on_type(FormatOnTypeParams { - // path: self.path.clone(), - // offset, - // }) - // } - // - // pub fn fix_file( - // &self, - // fix_file_mode: FixFileMode, - // should_format: bool, - // rule_categories: RuleCategories, - // only: Vec, - // skip: Vec, - // suppression_reason: Option, - // ) -> Result { - // self.workspace.fix_file(FixFileParams { - // path: self.path.clone(), - // fix_file_mode, - // should_format, - // only, - // skip, - // rule_categories, - // suppression_reason, - // }) - // } - // - // pub fn organize_imports(&self) -> Result { - // self.workspace.organize_imports(OrganizeImportsParams { - // path: self.path.clone(), - // }) - // } - // - // pub fn search_pattern(&self, pattern: &PatternId) -> Result { - // self.workspace.search_pattern(SearchPatternParams { - // path: self.path.clone(), - // pattern: pattern.clone(), - // }) - // } } impl Drop for FileGuard<'_, W> { diff --git a/crates/pglt_workspace/src/workspace/server/change.rs b/crates/pglt_workspace/src/workspace/server/change.rs index 174c75b9..5e3df205 100644 --- a/crates/pglt_workspace/src/workspace/server/change.rs +++ b/crates/pglt_workspace/src/workspace/server/change.rs @@ -1,5 +1,5 @@ +use pglt_text_size::{TextLen, TextRange, TextSize}; use std::ops::{Add, Sub}; -use text_size::{TextLen, TextRange, TextSize}; use crate::workspace::{ChangeFileParams, ChangeParams}; @@ -416,7 +416,7 @@ impl ChangeParams { mod tests { use super::*; use pglt_diagnostics::Diagnostic; - use text_size::TextRange; + use pglt_text_size::TextRange; use crate::workspace::{ChangeFileParams, ChangeParams}; diff --git a/crates/pglt_workspace/src/workspace/server/document.rs b/crates/pglt_workspace/src/workspace/server/document.rs index ec0c184d..cbb97a17 100644 --- a/crates/pglt_workspace/src/workspace/server/document.rs +++ b/crates/pglt_workspace/src/workspace/server/document.rs @@ -1,6 +1,6 @@ use pglt_diagnostics::{Diagnostic, DiagnosticExt, Severity, serde::Diagnostic as SDiagnostic}; use pglt_fs::PgLTPath; -use text_size::{TextRange, TextSize}; +use pglt_text_size::{TextRange, TextSize}; /// Global unique identifier for a statement #[derive(Debug, Hash, Eq, PartialEq, Clone)] diff --git a/crates/pglt_workspace/src/workspace_types.rs b/crates/pglt_workspace/src/workspace_types.rs new file mode 100644 index 00000000..5a97ecfd --- /dev/null +++ b/crates/pglt_workspace/src/workspace_types.rs @@ -0,0 +1,600 @@ +//! Utility functions to help with generating bindings for the [Workspace] API + +use std::collections::VecDeque; + +use biome_js_syntax::{AnyJsDeclaration, AnyTsTupleTypeElement}; +use rustc_hash::FxHashSet; +use schemars::{ + JsonSchema, + r#gen::{SchemaGenerator, SchemaSettings}, + schema::{InstanceType, RootSchema, Schema, SchemaObject, SingleOrVec}, +}; +use serde_json::Value; + +use crate::{WorkspaceError, workspace::*}; +use biome_js_factory::{ + make, + syntax::{AnyJsObjectMemberName, AnyTsName, AnyTsType, AnyTsTypeMember, T}, +}; +use biome_rowan::{AstSeparatedList, TriviaPieceKind}; + +/// Manages a queue of type definitions that need to be generated +#[derive(Default)] +pub struct ModuleQueue<'a> { + /// Set of type names that have already been emitted + visited: FxHashSet<&'a str>, + /// Queue of type names and definitions that need to be generated + queue: VecDeque<(&'a str, &'a SchemaObject)>, +} + +impl<'a> ModuleQueue<'a> { + /// Add a type definition to the queue if it hasn't been emitted already + fn push_back(&mut self, item: (&'a str, &'a SchemaObject)) { + if self.visited.insert(item.0) { + self.queue.push_back(item); + } + } + + /// Pull a type name and definition from the queue + fn pop_front(&mut self) -> Option<(&'a str, &'a SchemaObject)> { + self.queue.pop_front() + } + + pub fn visited(&self) -> &FxHashSet<&'a str> { + &self.visited + } +} + +/// Generate a [TsType] node from the `instance_type` of a [SchemaObject] +fn instance_type<'a>( + queue: &mut ModuleQueue<'a>, + root_schema: &'a RootSchema, + schema: &'a SchemaObject, + ty: InstanceType, +) -> AnyTsType { + match ty { + // If the instance type is an object, generate a TS object type with the corresponding properties + InstanceType::Object => { + let object = schema.object.as_deref().unwrap(); + let properties = object + .properties + .iter() + .map(|(property, schema)| { + let (ts_type, optional, description) = schema_type(queue, root_schema, schema); + assert!(!optional, "optional nested types are not supported"); + + let mut property = make::ident(property); + if let Some(description) = description { + let comment = format!("/**\n\t* {description} \n\t */"); + let trivia = vec![ + (TriviaPieceKind::Newline, "\n"), + (TriviaPieceKind::MultiLineComment, comment.as_str()), + (TriviaPieceKind::Newline, "\n"), + ]; + property = property.with_leading_trivia(trivia); + } + + AnyTsTypeMember::from( + make::ts_property_signature_type_member(AnyJsObjectMemberName::from( + make::js_literal_member_name(property), + )) + .with_type_annotation(make::ts_type_annotation(make::token(T![:]), ts_type)) + .build(), + ) + }) + .collect::>(); + + let properties_type = (!properties.is_empty()).then(|| { + make::ts_object_type( + make::token(T!['{']), + make::ts_type_member_list(properties), + make::token(T!['}']), + ) + .into() + }); + + // Don't use `additionalProperties: false` here. + let additional_properties = + object + .additional_properties + .as_deref() + .and_then(|schema| match schema { + Schema::Bool(false) => None, + _ => Some(schema), + }); + + // If `additionalProperties` is not empty, add a mapped or record type. + let additional_properties_type = additional_properties.map(|schema| { + // If `propertyNames` is not empty, use it as the key type. + let key_type = object.property_names.as_deref().map(|schema| { + let (ts_type, optional, _) = schema_type(queue, root_schema, schema); + assert!(!optional, "optional nested types are not supported"); + ts_type + }); + + let value_type = { + let (ts_type, optional, _) = schema_type(queue, root_schema, schema); + assert!(!optional, "optional nested types are not supported"); + ts_type + }; + + if let Some(key_type) = key_type { + // Use a mapped type for the key type and the value type. All keys are optional. + // e.g. `{ [K in Key]?: Value }`. + // TODO: Support `required` keys here when needed. + make::ts_mapped_type( + make::token(T!['{']), + make::token(T!['[']), + make::ts_type_parameter_name(make::ident("K")), + make::token(T![in]), + key_type, + make::token(T![']']), + make::token(T!['}']), + ) + .with_optional_modifier( + make::ts_mapped_type_optional_modifier_clause(make::token(T![?])).build(), + ) + .with_mapped_type(make::ts_type_annotation(make::token(T![:]), value_type)) + .build() + .into() + } else { + // Use `Record` otherwise. + make::ts_reference_type( + make::js_reference_identifier(make::ident("Record")).into(), + ) + .with_type_arguments(make::ts_type_arguments( + make::token(T![<]), + make::ts_type_argument_list( + [ + make::ts_reference_type( + make::js_reference_identifier(make::ident("string")).into(), + ) + .build() + .into(), + value_type, + ], + [make::token(T![,])], + ), + make::token(T![>]), + )) + .build() + .into() + } + }); + + // If both `properties` and `additionalProperties` are provided, turn into an + // intersection type. Pick one for the final type otherwise. + let result = [properties_type, additional_properties_type] + .into_iter() + .flatten() + .collect::>(); + + let separators = (0..result.len().saturating_sub(1)).map(|_| make::token(T![&])); + + make::ts_intersection_type(make::ts_intersection_type_element_list(result, separators)) + .build() + .into() + } + // If the instance type is an array, generate a TS array type with the corresponding item type + InstanceType::Array => { + let array = schema.array.as_deref().unwrap(); + let items = array.items.as_ref().unwrap(); + match items { + SingleOrVec::Single(schema) => { + let (ts_type, optional, _) = schema_type(queue, root_schema, schema); + assert!(!optional, "optional nested types are not supported"); + + AnyTsType::from(make::ts_array_type( + ts_type, + make::token(T!['[']), + make::token(T![']']), + )) + } + SingleOrVec::Vec(items) => AnyTsType::from(make::ts_tuple_type( + make::token(T!['[']), + make::ts_tuple_type_element_list( + items.iter().map(|schema| { + let (ts_type, optional, _) = schema_type(queue, root_schema, schema); + assert!(!optional, "optional nested types are not supported"); + AnyTsTupleTypeElement::AnyTsType(ts_type) + }), + items.iter().map(|_| make::token(T![,])), + ), + make::token(T![']']), + )), + } + } + + // Map native types to the corresponding TS type + InstanceType::Null => AnyTsType::from(make::ts_null_literal_type(make::token(T![null]))), + InstanceType::Boolean => AnyTsType::from(make::ts_boolean_type(make::token(T![boolean]))), + InstanceType::String => AnyTsType::from(make::ts_string_type(make::token(T![string]))), + InstanceType::Number | InstanceType::Integer => { + AnyTsType::from(make::ts_number_type(make::token(T![number]))) + } + } +} + +/// Generate a literal [TsType] from a `serde_json` [Value] +fn value_type(value: &Value) -> AnyTsType { + match value { + Value::Null => AnyTsType::from(make::ts_null_literal_type(make::token(T![null]))), + Value::Bool(true) => AnyTsType::from(make::ts_boolean_literal_type(make::token(T![true]))), + Value::Bool(false) => { + AnyTsType::from(make::ts_boolean_literal_type(make::token(T![false]))) + } + Value::Number(value) => AnyTsType::from( + make::ts_number_literal_type(make::js_number_literal(value.as_f64().unwrap())).build(), + ), + Value::String(value) => { + AnyTsType::from(make::ts_string_literal_type(make::js_string_literal(value))) + } + Value::Array(_) => unimplemented!(), + Value::Object(_) => unimplemented!(), + } +} + +/// Generate a union [TsType] node from a list of [TsType]s, +/// flattening any nested union type the iterator may emit +fn make_union_type(items: impl IntoIterator) -> AnyTsType { + let mut result = Vec::new(); + + for item in items { + if let AnyTsType::TsUnionType(union_type) = item { + for item in union_type.types().iter() { + result.push(item.unwrap()); + } + } else { + result.push(item); + } + } + + let separators = (0..result.len().saturating_sub(1)).map(|_| make::token(T![|])); + AnyTsType::from( + make::ts_union_type(make::ts_union_type_variant_list(result, separators)).build(), + ) +} + +/// Generate a [TsType] node from a [SchemaObject], returning the generated +/// TypeScript type along with a boolean flag indicating whether the type is +/// considered "optional" in the schema +fn schema_object_type<'a>( + queue: &mut ModuleQueue<'a>, + root_schema: &'a RootSchema, + schema: &'a SchemaObject, +) -> (AnyTsType, bool, Option<&'a String>) { + // Start by detecting enum types by inspecting the `enum_values` field, i + // the field is set return a union type generated from the literal enum values + let description = schema + .metadata + .as_ref() + .and_then(|s| s.description.as_ref()); + let ts_type = schema + .enum_values + .as_deref() + .map(|enum_values| make_union_type(enum_values.iter().map(value_type))) + // If the type isn't an enum, inspect its `instance_type` field, if the + // field is set return a type annotation for the corresponding type + .or_else(|| { + Some(match schema.instance_type.as_ref()? { + SingleOrVec::Single(ty) => instance_type(queue, root_schema, schema, **ty), + SingleOrVec::Vec(types) => make_union_type( + types + .iter() + .map(|ty| instance_type(queue, root_schema, schema, *ty)), + ), + }) + }) + // Otherwise inspect the `reference` field of the schema, if its set return + // a TS reference type and add the corresponding type to the queue + .or_else(|| { + let reference = schema.reference.as_deref()?; + let key = reference.trim_start_matches("#/components/schemas/"); + match root_schema.definitions.get(key) { + Some(Schema::Bool(_)) => unimplemented!(), + Some(Schema::Object(schema)) => queue.push_back((key, schema)), + None => panic!("definition for type {key:?} not found"), + } + + Some(AnyTsType::from( + make::ts_reference_type(AnyTsName::from(make::js_reference_identifier( + make::ident(key), + ))) + .build(), + )) + }) + // Finally try to inspect the subschemas for this type + .or_else(|| { + let subschemas = schema.subschemas.as_deref()?; + // First try to inspect the `all_of` list of subschemas, if it's + // set generate an intersection type from it + subschemas + .all_of + .as_deref() + .map(|all_of| { + AnyTsType::from( + make::ts_intersection_type(make::ts_intersection_type_element_list( + all_of.iter().map(|ty| { + let (ts_type, optional, _) = schema_type(queue, root_schema, ty); + assert!(!optional, "optional nested types are not supported"); + ts_type + }), + (0..all_of.len().saturating_sub(1)).map(|_| make::token(T![&])), + )) + .build(), + ) + }) + // Otherwise try to inspect the `any_of` list of subschemas, and + // generate the corresponding union type for it + .or_else(|| { + let any_of = subschemas + .any_of + .as_deref() + .or(subschemas.one_of.as_deref())?; + + Some(make_union_type(any_of.iter().map(|ty| { + let (ts_type, optional, _) = schema_type(queue, root_schema, ty); + assert!(!optional, "optional nested types are not supported"); + ts_type + }))) + }) + }) + .unwrap_or_else(|| { + // this is temporary workaround to fix the `options` field, which is not used at the moment + AnyTsType::from(make::ts_any_type(make::token(T![any]))) + }); + + // Types are considered "optional" in the serialization protocol if they + // have the `nullable` OpenAPI extension property, or if they have a default value + let is_nullable = matches!(schema.extensions.get("nullable"), Some(Value::Bool(true))); + let has_defaults = schema + .metadata + .as_ref() + .is_some_and(|metadata| metadata.default.is_some()); + + (ts_type, is_nullable || has_defaults, description) +} + +/// Generate a [TsType] node from a [Schema], returning the generated type +/// along with a boolean flag indicating whether the type is considered +/// "optional" in the schema +fn schema_type<'a>( + queue: &mut ModuleQueue<'a>, + root_schema: &'a RootSchema, + schema: &'a Schema, +) -> (AnyTsType, bool, Option<&'a String>) { + match schema { + // Types defined as `true` in the schema always pass validation, + // map them to the `any` type + Schema::Bool(true) => ( + AnyTsType::from(make::ts_any_type(make::token(T![any]))), + true, + None, + ), + // Types defined as `false` in the schema never pass validation, + // map them to the `never` type + Schema::Bool(false) => ( + AnyTsType::from(make::ts_never_type(make::token(T![never]))), + false, + None, + ), + Schema::Object(schema_object) => schema_object_type(queue, root_schema, schema_object), + } +} + +/// Generate and emit all the types defined in `root_schema` into the `module` +pub fn generate_type<'a>( + module: &mut Vec<(AnyJsDeclaration, Option<&'a String>)>, + queue: &mut ModuleQueue<'a>, + root_schema: &'a RootSchema, +) -> AnyTsType { + // Read the root type of the schema and push it to the queue + let root_name = root_schema + .schema + .metadata + .as_deref() + .and_then(|metadata| metadata.title.as_deref()) + .unwrap(); + + match root_name { + "Null" => return AnyTsType::TsVoidType(make::ts_void_type(make::token(T![void]))), + "Boolean" => { + return AnyTsType::TsBooleanType(make::ts_boolean_type(make::token(T![boolean]))); + } + "String" => return AnyTsType::TsStringType(make::ts_string_type(make::token(T![string]))), + _ => {} + } + + queue.push_back((root_name, &root_schema.schema)); + + while let Some((name, schema)) = queue.pop_front() { + // Detect if the type being emitted is an object, emit it as an + // interface definition if that's the case + let is_interface = schema.object.as_deref().is_some_and(|object| { + object + .additional_properties + .as_deref() + .is_none_or(|additional_properties| { + matches!(additional_properties, Schema::Bool(false)) + }) + }) && schema.instance_type.as_ref().is_none_or(|instance_type| { + if let SingleOrVec::Single(instance_type) = instance_type { + matches!(**instance_type, InstanceType::Object) + } else { + false + } + }); + + if is_interface { + let mut members = Vec::new(); + + // Create a property signature member in the interface for each + // property of the corresponding schema object + let object = schema.object.as_deref().unwrap(); + for (property_str, schema) in &object.properties { + let (ts_type, optional, description) = schema_type(queue, root_schema, schema); + + let mut property = make::ident(property_str); + if let Some(description) = description { + let comment = format!("/**\n\t* {description} \n\t */"); + let trivia = vec![ + (TriviaPieceKind::Newline, "\n"), + (TriviaPieceKind::MultiLineComment, comment.as_str()), + (TriviaPieceKind::Newline, "\n"), + ]; + property = property.with_leading_trivia(trivia); + } + + let type_annotation = if let Some((container_type, key_type, value_type)) = + match property_str.as_str() { + "featuresSupported" => Some(("Map", "FeatureKind", "SupportKind")), + _ => None, + } { + // HACK: force the `featuresSupported` property to be a Map + // This is a temporary workaround to fix the type annotation for this property. The + // better fix would be to use the `transform` feature that is available in `schemars` 1.0 to + // add a metadata field that we can pick up here to generate the correct type annotation. + // Alternatively, we could generate these types based on the actual rust types instead of the + // json schema. + // + // We also manually fix the types for some other properties as well. + let full_type = make::ts_reference_type( + make::js_reference_identifier(make::ident(container_type)).into(), + ) + .with_type_arguments(make::ts_type_arguments( + make::token(T![<]), + make::ts_type_argument_list( + [ + make::ts_reference_type( + make::js_reference_identifier(make::ident(key_type)).into(), + ) + .build() + .into(), + make::ts_reference_type( + make::js_reference_identifier(make::ident(value_type)).into(), + ) + .build() + .into(), + ], + [make::token(T![,])], + ), + make::token(T![>]), + )) + .build(); + make::ts_type_annotation(make::token(T![:]), full_type.into()) + } else { + make::ts_type_annotation(make::token(T![:]), ts_type) + }; + + let mut builder = make::ts_property_signature_type_member( + AnyJsObjectMemberName::from(make::js_literal_member_name(property)), + ) + .with_type_annotation(type_annotation); + + if optional { + builder = builder.with_optional_token(make::token(T![?])); + } + + members.push(AnyTsTypeMember::from(builder.build())); + } + + let description = schema + .metadata + .as_ref() + .and_then(|s| s.description.as_ref()); + let current_module = AnyJsDeclaration::from( + make::ts_interface_declaration( + make::token(T![interface]), + make::ts_identifier_binding(make::ident(name)).into(), + make::token(T!['{']), + make::ts_type_member_list(members), + make::token(T!['}']), + ) + .build(), + ); + module.push((current_module, description)); + } else { + // If the schema for this type is not an object, emit it as a type alias + let (ts_type, optional, description) = schema_object_type(queue, root_schema, schema); + + assert!(!optional, "optional nested types are not supported"); + + let current_module = AnyJsDeclaration::from( + make::ts_type_alias_declaration( + make::token(T![type]), + make::ts_identifier_binding(make::ident(name)).into(), + make::token(T![=]), + ts_type, + ) + .build(), + ); + module.push((current_module, description)); + } + } + + AnyTsType::TsReferenceType( + make::ts_reference_type(AnyTsName::JsReferenceIdentifier( + make::js_reference_identifier(make::ident(root_name)), + )) + .build(), + ) +} + +/// Signature metadata for a [Workspace] method +pub struct WorkspaceMethod { + /// Name of the method + pub name: &'static str, + /// Schema for the parameters object of the method + pub params: RootSchema, + /// Schema for the result object of the method + pub result: RootSchema, +} + +impl WorkspaceMethod { + /// Construct a [WorkspaceMethod] from a name, a parameter type and a result type + fn of(name: &'static str) -> Self + where + P: JsonSchema, + R: JsonSchema, + { + let params = SchemaGenerator::from(SchemaSettings::openapi3()).root_schema_for::

(); + let result = SchemaGenerator::from(SchemaSettings::openapi3()).root_schema_for::(); + Self { + name, + params, + result, + } + } + + /// Construct a [WorkspaceMethod] from a name and a function pointer + fn from_method( + name: &'static str, + _func: fn(T, P) -> Result, + ) -> Self + where + P: JsonSchema, + R: JsonSchema, + { + Self::of::(name) + } +} + +/// Helper macro for generated an OpenAPI schema for a type implementing JsonSchema +macro_rules! workspace_method { + ($name:ident) => { + WorkspaceMethod::from_method(stringify!($name), ::$name) + }; +} + +/// Returns a list of signature for all the methods in the [Workspace] trait +pub fn methods() -> [WorkspaceMethod; 7] { + [ + workspace_method!(update_settings), + workspace_method!(open_file), + workspace_method!(change_file), + workspace_method!(close_file), + workspace_method!(get_file_content), + workspace_method!(pull_diagnostics), + workspace_method!(get_completions), + ] +} diff --git a/lib/line_index/Cargo.toml b/lib/line_index/Cargo.toml index 55a438cb..59be7b96 100644 --- a/lib/line_index/Cargo.toml +++ b/lib/line_index/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] -text-size.workspace = true +pglt_text_size.workspace = true [lib] doctest = false diff --git a/lib/line_index/src/lib.rs b/lib/line_index/src/lib.rs index 6b61bf11..ce83e690 100644 --- a/lib/line_index/src/lib.rs +++ b/lib/line_index/src/lib.rs @@ -4,8 +4,8 @@ //! representation. use std::{collections::HashMap, iter}; -use text_size::TextRange; -use text_size::TextSize; +use pglt_text_size::TextRange; +use pglt_text_size::TextSize; #[derive(Clone, Debug, PartialEq, Eq)] pub struct LineIndex {