difftreelog
refactor split pretty-printer into a library crate
in: master
14 files changed
Cargo.lockdiffbeforeafterboth184 packageslockfile v4
Might be heavy and slow!
ahash
0.8.12crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75used byaho-corasick
1.1.4crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301depends onaliasable
0.1.3crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fdused byallocator-api2
0.2.21crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923annotate-snippets
0.12.11crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum16e4850548ff4a25a77ce3bda7241874e17fb702ea551f0cc62a2dbe052f1272depends onannotated-string
0.3.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum298ed730801db3c02f2edba003c9420a0f57ea48d37fdc5601c536113668c059used byanstream
0.6.21crates.io↘ 7↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4adepends onused byanstyle
1.0.13crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78anstyle-parse
0.2.7crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2depends onused byanstyle-query
1.1.5crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadcdepends onused byanstyle-wincon
3.0.11crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747dused byanyhow
1.0.101crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75eaar_archive_writer
0.5.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340bdepends onused byarraydeque
0.5.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236used byautocfg
1.5.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8used bybase64
0.22.1crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6bitflags
2.10.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3used byblock-buffer
0.10.4crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71depends onused bybumpalo
3.19.1crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510depends oncc
1.2.55crates.io↘ 2↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29depends oncfg-if
1.0.4crates.io↘ 0↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801clap
4.5.57crates.io↘ 2↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31adepends onclap_builder
4.5.57crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238used byclap_complete
4.5.65crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5ddepends onused byclap_derive
4.5.55crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5used byclap_lex
0.7.7crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32used bycolorchoice
1.0.4crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75used byconsole
0.15.11crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8used bycountme
3.0.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636used bycpufeatures
0.2.17crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280depends oncrypto-common
0.1.7crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60adepends onused bydifflib
0.4.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8used bydigest
0.10.7crates.io↘ 2↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292depends ondprint-core
0.67.4crates.io↘ 7↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2c1d827947704a9495f705d6aeed270fa21a67f825f22902c28f38dc3af7a9aedepends ondrop_bomb
0.1.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1educe
0.6.0crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417either
1.15.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719used byencode_unicode
1.0.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0used byencoding_rs
0.8.35crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3depends onused byencoding_rs_io
0.1.7crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83depends onused byenum-ordinalize
4.3.2crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0depends onused byenum-ordinalize-derive
4.3.2crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631depends onused byequivalent
1.0.2crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0ferrno
0.3.14crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efebdepends onused byextension-trait
1.0.2crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdd65f1b59dd22d680c7a626cc4a000c1e03d241c51c3e034d2bc9f1e90734f9bdepends onused byfastrand
2.3.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411beused byfind-msvc-tools
0.1.9crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582used byfnv
1.0.7crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1used byfoldhash
0.1.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2used byfoldhash
0.2.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdbused bygeneric-array
0.14.7crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9adepends ongetrandom
0.3.4crates.io↘ 6↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fdhashbrown
0.14.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1used byhashbrown
0.15.5crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1used byhashbrown
0.16.1crates.io↘ 3↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100heck
0.4.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8used byheck
0.5.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55eaused byhi-doc
0.3.0crates.io↘ 10↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf70fb920ba34768415fb239d7d607486083bfc38ad35e3f1d558697f9f646f11depends onhi-doc-jumprope
1.2.1crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum236c25809a9c0a0249b3488feb57744e12aa64e4f3db851980eab303719c7bdddepends onused byindexmap
2.13.0crates.io↘ 4↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017indoc
2.0.7crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706depends oninsta
1.46.3crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4is_terminal_polyfill
1.70.2crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695used byitertools
0.14.0crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285depends onitoa
1.0.17crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2used byjrsonnet-cli
0.5.0-pre97workspace↘ 5↖ 1jrsonnet-evaluator
0.5.0-pre97workspace↘ 18↖ 5depends on- annotate-snippets
0.12.11 - anyhow
1.0.101 - educe
0.6.0 - hi-doc
0.3.0 - jrsonnet-gcmodule
0.4.1 - jrsonnet-interner
0.5.0-pre97 - jrsonnet-macros
0.5.0-pre97 - jrsonnet-parser
0.5.0-pre97 - jrsonnet-types
0.5.0-pre97 - num-bigint
0.4.6 - pathdiff
0.2.3 - rustc-hash
2.1.1 - rustversion
1.0.22 - serde
1.0.228 - stacker
0.1.23 - static_assertions
1.1.0 - strsim
0.11.1 - thiserror
2.0.18
- annotate-snippets
jrsonnet-fmt
0.5.0-pre97workspace↘ 6↖ 0jrsonnet-formatter
0.5.0-pre97workspace↘ 3↖ 1jrsonnet-gcmodule
0.4.1crates.io↘ 1↖ 9sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc33f4f6cdc60f5ae94ebae3dfe7f484ae79b364225d9b19601b24c804cfd8751depends onjrsonnet-gcmodule-derive
0.4.1crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2b30c95b285f9bb6709f1b3e6fc69b3a25e39b32ff987587fd108f0f22be5fa3depends onused byjrsonnet-interner
0.5.0-pre97workspace↘ 3↖ 3jrsonnet-macros
0.5.0-pre97workspace↘ 4↖ 2jrsonnet-parser
0.5.0-pre97workspace↘ 4↖ 5jrsonnet-rowan-parser
0.5.0-pre97workspace↘ 7↖ 1jrsonnet-stdlib
0.5.0-pre97workspace↘ 16↖ 3depends onjrsonnet-types
0.5.0-pre97workspace↘ 2↖ 1depends onjs-sys
0.3.85crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3depends onused byjson-structural-diff
0.2.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume878e36a8a44c158505c2c818abdc1350413ad83dcb774a0459f6a7ef2b65cbfdepends onused bykeccak
0.1.5crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654depends onused bylibc
0.2.180crates.io↘ 0↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fclibjsonnet
0.5.0-pre97workspace↘ 5↖ 0linux-raw-sys
0.11.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdf1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039used bylogos
0.16.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumeb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5fdepends onlogos-codegen
0.16.1crates.io↘ 6↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970used bylogos-derive
0.16.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31depends onused bylru
0.16.3crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593depends onused bymd5
0.8.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0used bymemchr
2.8.0crates.io↘ 0↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79mimalloc-sys
0.1.6crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4aa3cefb626f6ae3d0b2f71c5378c89d2b1d4d7bc246b0ca9a7ee61a4daad291depends onused bymimallocator
0.1.3crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2d44fe4ebf6b538fcf39d9975c2b90bb3232d1ba8e8bffeacd004f27b20c577adepends onused bynohash-hasher
0.2.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451used bynum-bigint
0.4.6crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9num-integer
0.1.46crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858fdepends onused bynum-traits
0.2.19crates.io↘ 1↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841depends onobject
0.37.3crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fedepends onused byonce_cell
1.21.3crates.io↘ 0↖ 6sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2donce_cell_polyfill
1.70.2crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4feused byouroboros
0.18.5crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59used byouroboros_macro
0.18.5crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0used bypathdiff
0.2.3crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdf94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3peg
0.8.5crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477depends onpeg-macros
0.8.5crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71used bypeg-runtime
0.8.5crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fcaused byppv-lite86
0.2.21crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9depends onused byproc-macro2
1.0.106crates.io↘ 1↖ 18sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934depends onused by- clap_derive
4.5.55 - educe
0.6.0 - enum-ordinalize-derive
4.3.2 - extension-trait
1.0.2 - jrsonnet-gcmodule-derive
0.4.1 - jrsonnet-macros
0.5.0-pre97 - logos-codegen
0.16.1 - ouroboros_macro
0.18.5 - peg-macros
0.8.5 - proc-macro2-diagnostics
0.10.1 - quote
1.0.44 - serde_derive
1.0.228 - syn
2.0.114 - syn-dissect-closure
0.1.0 - thiserror-impl
2.0.18 - wasm-bindgen-macro-support
0.2.108 - xtask
0.1.0 - zerocopy-derive
0.8.39
- clap_derive
proc-macro2-diagnostics
0.10.1crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumaf066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8used bypsm
0.1.30crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8depends onused byquote
1.0.44crates.io↘ 1↖ 18sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4depends onused by- clap_derive
4.5.55 - educe
0.6.0 - enum-ordinalize-derive
4.3.2 - extension-trait
1.0.2 - jrsonnet-gcmodule-derive
0.4.1 - jrsonnet-macros
0.5.0-pre97 - logos-codegen
0.16.1 - ouroboros_macro
0.18.5 - peg-macros
0.8.5 - proc-macro2-diagnostics
0.10.1 - serde_derive
1.0.228 - syn
2.0.114 - syn-dissect-closure
0.1.0 - thiserror-impl
2.0.18 - wasm-bindgen-macro
0.2.108 - wasm-bindgen-macro-support
0.2.108 - xtask
0.1.0 - zerocopy-derive
0.8.39
- clap_derive
r-efi
5.3.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0fused byrand
0.9.2crates.io↘ 2↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1depends onrand_chacha
0.9.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cbdepends onused byrand_core
0.9.5crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83cdepends onused byrandom_color
1.1.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd635c5e80ae160390ac62ca027d2d06c94c1dc69e5c0a12f1e3a53664dc84966depends onused byrange-map
0.2.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95fdepends onused byregex
1.12.3crates.io↘ 4↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276regex-automata
0.4.14crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8fregex-syntax
0.8.9crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4crowan
0.16.1crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21rustc-hash
1.1.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2used byrustc-hash
2.1.1crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0drustix
1.1.3crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34used byrustversion
1.0.22crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46dsaphyr-parser-bw
0.0.607crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2f9bae8d059bf1ca32753cf3cdafbf5d391502de2fc2ca54510811fe9c100d90used byserde
1.0.228crates.io↘ 2↖ 9sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9edepends onserde_core
1.0.228crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67addepends onserde_derive
1.0.228crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79depends onserde_json
1.0.149crates.io↘ 6↖ 6sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86serde-saphyr
0.0.17crates.io↘ 13↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbc14a55107113a16346915d7e3d78acc539a923458385db89670e22cac106d7adepends onused bysha1
0.10.6crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6badepends onused bysha2
0.10.9crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283depends onused bysha3
0.10.8crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60depends onused bysimilar
2.7.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daaused bysmallvec
1.15.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03used bysmallvec
2.0.0-alpha.12crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumef784004ca8777809dcdad6ac37629f0a97caee4c685fcea805278d81dd8b857stacker
0.1.23crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013static_assertions
1.1.0crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543fstr_indices
0.4.4crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6used bystreaming-iterator
0.1.9crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520strsim
0.11.1crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4fsyn
2.0.114crates.io↘ 3↖ 14sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2aused by- clap_derive
4.5.55 - educe
0.6.0 - enum-ordinalize-derive
4.3.2 - extension-trait
1.0.2 - jrsonnet-gcmodule-derive
0.4.1 - jrsonnet-macros
0.5.0-pre97 - logos-codegen
0.16.1 - ouroboros_macro
0.18.5 - proc-macro2-diagnostics
0.10.1 - serde_derive
1.0.228 - syn-dissect-closure
0.1.0 - thiserror-impl
2.0.18 - wasm-bindgen-macro-support
0.2.108 - zerocopy-derive
0.8.39
- clap_derive
syn-dissect-closure
0.1.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum343bae741672e4b94421cbe93f9794ba9a061434272f7e3a29ff43be26be3ac9depends onused bytempfile
3.24.0crates.io↘ 5↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9ctests
0.1.0workspace↘ 6↖ 0text-size
1.1.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233used bythiserror
2.0.18crates.io↘ 1↖ 6sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4depends onthiserror-impl
2.0.18crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5depends onused bytree-sitter
0.26.5crates.io↘ 6↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum12987371f54efc9b9306a20dc87ed5aaee9f320c8a8b115e28515c412b2efe39depends onused bytree-sitter-highlight
0.26.5crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2b688407049ea1b55a7e872f138947d22389118b9c4d09b459cb34ca205e41c0used bytree-sitter-language
0.1.7crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782used bytypenum
1.19.0crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bbungrammar
1.16.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68fused byunicode-box-drawing
0.3.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2a1f97719cf40224391201fc11e7f5b0cc0ba21416367cfc914e2d45af4e42efused byunicode-ident
1.0.22crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5unicode-width
0.2.2crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254utf8parse
0.2.2crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821version_check
0.9.5crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105awasip2
1.0.2+wasi-0.2.9crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5depends onused bywasm-bindgen
0.2.108crates.io↘ 5↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566depends onused bywasm-bindgen-macro
0.2.108crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608used bywasm-bindgen-macro-support
0.2.108crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55used bywindows_aarch64_gnullvm
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3used bywindows_aarch64_msvc
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469used bywindows_i686_gnu
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0bused bywindows_i686_gnullvm
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66used bywindows_i686_msvc
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66used bywindows_x86_64_gnu
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78used bywindows_x86_64_gnullvm
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0dused bywindows_x86_64_msvc
0.52.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ecused bywindows-link
0.2.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5used bywindows-sys
0.59.0crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853bdepends onused bywindows-sys
0.61.2crates.io↘ 1↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fcdepends onwindows-targets
0.52.6crates.io↘ 8↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973depends onused bywit-bindgen
0.51.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5used byxshell
0.2.7crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373ddepends onused byxshell-macros
0.2.7crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547used byxtask
0.1.0workspace↘ 8↖ 0yansi
1.0.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumcfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049zerocopy
0.8.39crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdb6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4adepends onused byzerocopy-derive
0.8.39crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517depends onused byzmij
1.0.19crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@
jrsonnet-stdlib = { path = "./crates/jrsonnet-stdlib", version = "0.5.0-pre97" }
jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre97" }
jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre97" }
+jrsonnet-formatter = { path = "./crates/jrsonnet-formatter", version = "0.5.0-pre97" }
jrsonnet-gcmodule = { version = "0.4.1" }
# Diagnostics.
# hi-doc is my library, which handles text formatting very well, but isn't polished enough yet
cmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet-fmt/Cargo.toml
+++ b/cmds/jrsonnet-fmt/Cargo.toml
@@ -10,11 +10,9 @@
workspace = true
[dependencies]
-dprint-core.workspace = true
-jrsonnet-rowan-parser.workspace = true
-insta.workspace = true
indoc.workspace = true
hi-doc.workspace = true
clap = { workspace = true, features = ["derive"] }
tempfile.workspace = true
thiserror.workspace = true
+jrsonnet-formatter.workspace = true
cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/children.rs
+++ /dev/null
@@ -1,251 +0,0 @@
-// TODO: Return errors as trivia
-
-use std::{fmt::Debug, mem};
-
-use jrsonnet_rowan_parser::{
- nodes::{CustomError, Trivia, TriviaKind},
- AstNode, AstToken, SyntaxElement, SyntaxNode, TS,
-};
-
-pub type ChildTrivia = Vec<Result<Trivia, String>>;
-
-/// Node should have no non-trivia tokens before element
-pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {
- let mut out = Vec::new();
- for item in node.children_with_tokens() {
- if Some(&item) == end {
- break;
- }
-
- if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
- out.push(Ok(trivia));
- } else if CustomError::can_cast(item.kind()) {
- out.push(Err(item.to_string()));
- } else if end.is_none() {
- break;
- } else {
- assert!(
- TS![, ;].contains(item.kind()),
- "silently eaten token: {:?}",
- item.kind()
- );
- }
- }
- out
-}
-/// Node should have no non-trivia tokens after element
-pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {
- if start.is_none() {
- return Vec::new();
- }
- let mut iter = node.children_with_tokens().peekable();
- while iter.peek() != start {
- iter.next();
- }
- iter.next();
- let mut out = Vec::new();
- for item in iter {
- if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
- out.push(Ok(trivia));
- } else if CustomError::can_cast(item.kind()) {
- out.push(Err(item.to_string()));
- } else {
- assert!(
- TS![, ;].contains(item.kind()),
- "silently eaten token: {:?}",
- item.kind()
- );
- }
- }
- out
-}
-
-pub fn children_between<T: AstNode + Debug>(
- node: SyntaxNode,
- start: Option<&SyntaxElement>,
- end: Option<&SyntaxElement>,
- trailing: Option<ChildTrivia>,
-) -> (Vec<Child<T>>, EndingComments) {
- let mut iter = node.children_with_tokens().peekable();
- if start.is_some() {
- while iter.peek() != start {
- iter.next();
- }
- iter.next();
- }
- children(
- iter.take_while(|i| Some(i) != end),
- start.is_none() && end.is_none(),
- trailing,
- )
-}
-
-pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {
- count_newlines_before(tt)
- + prev_inline
- .map(count_newlines_after)
- .unwrap_or_default()
-
- // First for previous item end, second for current item
- >= 2
-}
-
-fn count_newlines_before(tt: &ChildTrivia) -> usize {
- let mut nl_count = 0;
- for t in tt {
- match t {
- Ok(t) => match t.kind() {
- TriviaKind::Whitespace => {
- nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
- }
- _ => break,
- },
- Err(_) => {
- nl_count += 1;
- }
- }
- }
- nl_count
-}
-fn count_newlines_after(tt: &ChildTrivia) -> usize {
- let mut nl_count = 0;
- for t in tt.iter().rev() {
- match t {
- Ok(t) => match t.kind() {
- TriviaKind::Whitespace => {
- nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
- }
- TriviaKind::SingleLineHashComment | TriviaKind::SingleLineSlashComment => {
- nl_count += 1;
- break;
- }
- _ => {}
- },
- Err(_) => nl_count += 1,
- }
- }
- nl_count
-}
-
-pub fn children<T: AstNode + Debug>(
- items: impl Iterator<Item = SyntaxElement>,
- loose: bool,
- mut trailing: Option<ChildTrivia>,
-) -> (Vec<Child<T>>, EndingComments) {
- let mut out = Vec::new();
- let mut current_child = None::<Child<T>>;
- let mut next = ChildTrivia::new();
- // Previous element ended, do not add more inline comments
- let mut started_next = false;
- let mut had_some = false;
-
- for item in items {
- if let Some(value) = item.as_node().cloned().and_then(T::cast) {
- let before_trivia = if let Some(trailing) = trailing.take() {
- assert!(next.is_empty());
- trailing
- } else {
- mem::take(&mut next)
- };
- let last_child = current_child.replace(Child {
- // First item should not start with newline
- should_start_with_newline: had_some
- && should_start_with_newline(
- current_child.as_ref().map(|c| &c.inline_trivia),
- &before_trivia,
- ),
- before_trivia,
- value,
- inline_trivia: Vec::new(),
- });
- if let Some(last_child) = last_child {
- out.push(last_child);
- }
- had_some = true;
- started_next = false;
- } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
- let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment
- || trivia.kind() == TriviaKind::SingleLineSlashComment;
- if trailing.is_some() {
- // Someone have already parsed trivia for us
- continue;
- } else if started_next
- || current_child.is_none()
- || trivia.text().contains('\n') && !is_single_line_comment
- {
- next.push(Ok(trivia.clone()));
- started_next = true;
- } else {
- let cur = current_child.as_mut().expect("checked not none");
- cur.inline_trivia.push(Ok(trivia));
- if is_single_line_comment {
- started_next = true;
- }
- }
- had_some = true;
- } else if CustomError::can_cast(item.kind()) {
- next.push(Err(item.to_string()));
- } else if loose {
- if had_some {
- break;
- }
- started_next = true;
- } else {
- assert!(
- TS![, ;].contains(item.kind()),
- "silently eaten token: {:?}",
- item.kind()
- );
- }
- }
-
- let ending_comments = EndingComments {
- should_start_with_newline: should_start_with_newline(
- current_child.as_ref().map(|c| &c.inline_trivia),
- &next,
- ),
- trivia: next,
- };
-
- if let Some(current_child) = current_child {
- out.push(current_child);
- }
-
- (out, ending_comments)
-}
-
-#[derive(Debug)]
-pub struct Child<T> {
- /// If this child has two newlines above in source code, so it needs to have it in the output
- pub should_start_with_newline: bool,
- /// Comment before item, i.e
- ///
- /// ```ignore
- /// // Comment
- /// item
- /// ```
- pub before_trivia: ChildTrivia,
- pub value: T,
- /// Comment after line, but located at same line
- ///
- /// ```ignore
- /// item1, // Inline comment
- /// // Not inline comment
- /// item2,
- /// ```
- pub inline_trivia: ChildTrivia,
-}
-
-pub struct EndingComments {
- /// If this child has two newlines above in source code, so it needs to have it in the output
- pub should_start_with_newline: bool,
- pub trivia: ChildTrivia,
-}
-impl EndingComments {
- pub fn is_empty(&self) -> bool {
- !self.should_start_with_newline && self.trivia.is_empty()
- }
- pub fn extract_trailing(&mut self) -> ChildTrivia {
- mem::take(&mut self.trivia)
- }
-}
cmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/comments.rs
+++ /dev/null
@@ -1,181 +0,0 @@
-use std::string::String;
-
-use dprint_core::formatting::PrintItems;
-use jrsonnet_rowan_parser::{nodes::TriviaKind, AstToken};
-
-use crate::{children::ChildTrivia, p, pi};
-
-pub enum CommentLocation {
- /// Above local, field, other things
- AboveItem,
- /// After item
- ItemInline,
- /// After all items in object
- EndOfItems,
-}
-
-#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
-pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation, out: &mut PrintItems) {
- for c in comments {
- let Ok(c) = c else {
- let mut text = c.as_ref().unwrap_err() as &str;
- while !text.is_empty() {
- let pos = text.find(['\n', '\t']).unwrap_or(text.len());
- let sliced = &text[..pos];
- p!(out, string(sliced.to_string()));
- text = &text[pos..];
- if !text.is_empty() {
- match text.as_bytes()[0] {
- b'\n' => p!(out, nl),
- b'\t' => p!(out, tab),
- _ => unreachable!(),
- }
- text = &text[1..];
- }
- }
- continue;
- };
- match c.kind() {
- TriviaKind::Whitespace => {}
- TriviaKind::MultiLineComment => {
- let mut text = c
- .text()
- .strip_prefix("/*")
- .expect("ml comment starts with /*")
- .strip_suffix("*/")
- .expect("ml comment ends with */");
- // doc-style comment, /**
- let doc = if text.starts_with('*') {
- text = &text[1..];
- true
- } else {
- false
- };
- // Is comment starts with text immediatly, i.e /*text
- let mut immediate_start = true;
- let mut lines = text
- .split('\n')
- .map(|l| l.trim_end().to_string())
- .skip_while(|l| {
- if l.is_empty() {
- immediate_start = false;
- true
- } else {
- false
- }
- })
- .collect::<Vec<_>>();
- while lines.last().is_some_and(String::is_empty) {
- lines.pop();
- }
- if lines.len() == 1 && !doc {
- if matches!(loc, CommentLocation::ItemInline) {
- p!(out, str(" "));
- }
- p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl);
- } else if !lines.is_empty() {
- fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
- let offset = a
- .bytes()
- .zip(b.bytes())
- .take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))
- .count();
- &a[..offset]
- }
- // First line is not empty, extract ws prefix of it
- let mut common_ws_padding = (if immediate_start && lines.len() > 1 {
- common_ws_prefix(&lines[1], &lines[1])
- } else {
- common_ws_prefix(&lines[0], &lines[0])
- })
- .to_string();
- for line in lines
- .iter()
- .skip(if immediate_start { 2 } else { 1 })
- .filter(|l| !l.is_empty())
- {
- common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();
- }
- for line in lines
- .iter_mut()
- .skip(usize::from(immediate_start))
- .filter(|l| !l.is_empty())
- {
- *line = line
- .strip_prefix(&common_ws_padding)
- .expect("all non-empty lines start with this padding")
- .to_string();
- }
-
- p!(out, str("/*"));
- if doc {
- p!(out, str("*"));
- }
- p!(out, nl);
- for mut line in lines {
- if doc {
- p!(out, str(" *"));
- }
- if line.is_empty() {
- p!(out, nl);
- } else {
- if doc {
- p!(out, str(" "));
- }
- while let Some(new_line) = line.strip_prefix('\t') {
- if doc {
- p!(out, str(" "));
- } else {
- p!(out, tab);
- }
- line = new_line.to_string();
- }
- p!(out, string(line.to_string()) nl);
- }
- }
- if doc {
- p!(out, str(" "));
- }
- p!(out, str("*/") nl);
- }
- }
- // TODO: Keep common padding for multiple continous lines of single-line comments
- // I.e
- // ```
- // # Line1
- // # Line2
- // ```
- // Should be reformatted as
- // ```
- // # Line1
- // # Line2
- // ```
- // But currently comment formatter is not aware of continous comment lines, and reformats it as
- // ```
- // # Line1
- // # Line2
- // ```
- TriviaKind::SingleLineHashComment => {
- if matches!(loc, CommentLocation::ItemInline) {
- p!(out, str(" "));
- }
- p!(out, str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));
- if !matches!(loc, CommentLocation::ItemInline) {
- p!(out, nl);
- }
- }
- TriviaKind::SingleLineSlashComment => {
- if matches!(loc, CommentLocation::ItemInline) {
- p!(out, str(" "));
- }
- p!(out, str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));
- if !matches!(loc, CommentLocation::ItemInline) {
- p!(out, nl);
- }
- }
- // Garbage in - garbage out
- TriviaKind::ErrorCommentTooShort => p!(out, str("/*/")),
- TriviaKind::ErrorCommentUnterminated => p!(out, string(c.text().to_string())),
- }
- }
-}
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -1,755 +1,7 @@
-use std::{
- any::type_name,
- fs,
- io::{self, Write},
- path::PathBuf,
- process,
- rc::Rc,
-};
+use std::{fs, io};
-use children::{children_between, trivia_before};
use clap::Parser;
-use dprint_core::formatting::{
- condition_helpers::is_multiple_lines, condition_resolvers::true_resolver,
- ConditionResolverContext, LineNumber, PrintItems, PrintOptions,
-};
-use hi_doc::Formatting;
-use jrsonnet_rowan_parser::{
- nodes::{
- Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
- DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
- Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,
- UnaryOperator, Visibility,
- },
- AstNode, AstToken as _, SyntaxToken,
-};
-
-use crate::{
- children::trivia_after,
- comments::{format_comments, CommentLocation},
-};
-
-mod children;
-mod comments;
-#[cfg(test)]
-mod tests;
-
-pub trait Printable {
- fn print(&self, out: &mut PrintItems);
-}
-
-macro_rules! pi {
- (@i; $($t:tt)*) => {{
- #[allow(unused_mut)]
- let mut o = dprint_core::formatting::PrintItems::new();
- pi!(@s; o: $($t)*);
- o
- }};
- (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{
- $o.push_string($e.to_owned());
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{
- $o.push_string($e);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: nl $($t:tt)*) => {{
- $o.push_signal(dprint_core::formatting::Signal::NewLine);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: tab $($t:tt)*) => {{
- $o.push_signal(dprint_core::formatting::Signal::Tab);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: >i $($t:tt)*) => {{
- $o.push_signal(dprint_core::formatting::Signal::StartIndent);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: <i $($t:tt)*) => {{
- $o.push_signal(dprint_core::formatting::Signal::FinishIndent);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: info($v:expr) $($t:tt)*) => {{
- $o.push_info($v);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{
- $o.push_condition(dprint_core::formatting::conditions::if_true(
- $s,
- $cond.clone(),
- {
- let mut o = PrintItems::new();
- p!(o, $($i)*);
- o
- },
- ));
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{
- $o.push_condition(dprint_core::formatting::conditions::if_true_or(
- $s,
- $cond.clone(),
- {
- let mut o = PrintItems::new();
- p!(o, $($i)*);
- o
- },
- {
- let mut o = PrintItems::new();
- p!(o, $($e)*);
- o
- },
- ));
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{
- $o.push_condition(dprint_core::formatting::conditions::if_true_or(
- $s,
- $cond.clone(),
- {
- let o = PrintItems::new();
- o
- },
- {
- let mut o = PrintItems::new();
- p!(o, $($e)*);
- o
- },
- ));
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{
- $expr.print($o);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{
- $o.extend($expr);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{
- if $e {
- pi!(@s; $o: $($then)*);
- }
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{
- if $e {
- pi!(@s; $o: $($then)*);
- } else {
- pi!(@s; $o: $($else)*);
- }
- pi!(@s; $o: $($t)*);
- }};
- (@s; $i:ident:) => {}
-}
-macro_rules! p {
- ($o:ident, $($t:tt)*) => {
- pi!(@s; $o: $($t)*)
- };
-}
-pub(crate) use p;
-pub(crate) use pi;
-
-impl<P> Printable for Option<P>
-where
- P: Printable,
-{
- fn print(&self, out: &mut PrintItems) {
- if let Some(v) = self {
- v.print(out);
- } else {
- p!(
- out,
- string(format!(
- "/*missing {}*/",
- type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")
- ),)
- );
- }
- }
-}
-
-impl Printable for SyntaxToken {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(self.to_string()));
- }
-}
-
-impl Printable for Text {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(format!("{}", self)));
- }
-}
-impl Printable for Number {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(format!("{}", self)));
- }
-}
-
-impl Printable for Name {
- fn print(&self, out: &mut PrintItems) {
- p!(out, { self.ident_lit() });
- }
-}
-
-impl Printable for DestructRest {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("..."));
- if let Some(name) = self.into() {
- p!(out, { name });
- }
- }
-}
-
-impl Printable for Destruct {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::DestructFull(f) => {
- p!(out, { f.name() });
- }
- Self::DestructSkip(_) => p!(out, str("?")),
- Self::DestructArray(a) => {
- p!(out, str("[") >i nl);
- for el in a.destruct_array_parts() {
- match el {
- DestructArrayPart::DestructArrayElement(e) => {
- p!(out, {e.destruct()} str(",") nl);
- }
- DestructArrayPart::DestructRest(d) => {
- p!(out, {d} str(",") nl);
- }
- }
- }
- p!(out, <i str("]"));
- }
- Self::DestructObject(o) => {
- p!(out, str("{") >i nl);
- for item in o.destruct_object_fields() {
- p!(out, { item.field() });
- if let Some(des) = item.destruct() {
- p!(out, str(": ") {des});
- }
- if let Some(def) = item.expr() {
- p!(out, str(" = ") {def});
- }
- p!(out, str(",") nl);
- }
- if let Some(rest) = o.destruct_rest() {
- p!(out, {rest} nl);
- }
- p!(out, <i str("}"));
- }
- }
- }
-}
-
-impl Printable for FieldName {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::FieldNameFixed(f) => {
- if let Some(id) = f.id() {
- p!(out, { id });
- } else if let Some(str) = f.text() {
- p!(out, { str });
- } else {
- p!(out, str("/*missing FieldName*/"));
- }
- }
- Self::FieldNameDynamic(d) => {
- p!(out, str("[") {d.expr()} str("]"));
- }
- }
- }
-}
-
-impl Printable for Visibility {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(self.to_string()));
- }
-}
-
-impl Printable for ObjLocal {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("local ") {self.bind()});
- }
-}
-
-impl Printable for Assertion {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("assert ") {self.condition()});
- if self.colon_token().is_some() || self.message().is_some() {
- p!(out, str(": ") {self.message()});
- }
- }
-}
-
-impl Printable for ParamsDesc {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("(") >i nl);
- for param in self.params() {
- p!(out, { param.destruct() });
- if param.assign_token().is_some() || param.expr().is_some() {
- p!(out, str(" = ") {param.expr()});
- }
- p!(out, str(",") nl);
- }
- p!(out, <i str(")"));
- }
-}
-impl Printable for ArgsDesc {
- fn print(&self, out: &mut PrintItems) {
- let start = LineNumber::new("start");
- let end = LineNumber::new("end");
- let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {
- is_multiple_lines(condition_context, start, end).map(|v| !v)
- });
- p!(out, str("(") info(start) if("start args", multi_line, >i nl));
- let (children, end_comments) = children_between::<Arg>(
- self.syntax().clone(),
- self.l_paren_token().map(Into::into).as_ref(),
- self.r_paren_token().map(Into::into).as_ref(),
- None,
- );
- let mut args = children.into_iter().peekable();
- while let Some(ele) = args.next() {
- if ele.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);
- let arg = ele.value;
- if arg.name().is_some() || arg.assign_token().is_some() {
- p!(out, {arg.name()} str(" = "));
- }
- let comma_between = if args.peek().is_some() {
- true_resolver()
- } else {
- multi_line.clone()
- };
- p!(out, {arg.expr()} if("arg comma", comma_between, str(",") if_not("between args", multi_line, str(" "))));
- format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);
- p!(out, if("between args", multi_line, nl));
- }
- if end_comments.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
- p!(out, if("end args", multi_line, <i info(end)) str(")"));
- }
-}
-impl Printable for SliceDesc {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("["));
- if self.from().is_some() {
- p!(out, { self.from() });
- }
- p!(out, str(":"));
- if self.end().is_some() {
- p!(out, { self.end().map(|e| e.expr()) });
- }
- // Keep only one : in case if we don't need step
- if self.step().is_some() {
- p!(out, str(":") {self.step().map(|e|e.expr())});
- }
- p!(out, str("]"));
- }
-}
-
-impl Printable for Member {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::MemberBindStmt(b) => {
- p!(out, { b.obj_local() });
- }
- Self::MemberAssertStmt(ass) => {
- p!(out, { ass.assertion() });
- }
- Self::MemberFieldNormal(n) => {
- p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});
- }
- Self::MemberFieldMethod(m) => {
- p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});
- }
- }
- }
-}
-
-impl Printable for ObjBody {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::ObjBodyComp(l) => {
- let (children, mut end_comments) = children_between::<Member>(
- l.syntax().clone(),
- l.l_brace_token().map(Into::into).as_ref(),
- Some(
- &(l.comp_specs()
- .next()
- .expect("at least one spec is defined")
- .syntax()
- .clone())
- .into(),
- ),
- None,
- );
- let trailing_for_comp = end_comments.extract_trailing();
- p!(out, str("{") >i nl);
- for mem in children {
- if mem.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
- p!(out, {mem.value} str(","));
- format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
- p!(out, nl);
- }
-
- if end_comments.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
-
- let (compspecs, end_comments) = children_between::<CompSpec>(
- l.syntax().clone(),
- l.member_comps()
- .last()
- .map(|m| m.syntax().clone())
- .map(Into::into)
- .or_else(|| l.l_brace_token().map(Into::into))
- .as_ref(),
- l.r_brace_token().map(Into::into).as_ref(),
- Some(trailing_for_comp),
- );
- for mem in compspecs {
- if mem.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
- p!(out, { mem.value });
- format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
- }
- if end_comments.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
-
- p!(out, nl <i str("}"));
- }
- Self::ObjBodyMemberList(l) => {
- let (children, end_comments) = children_between::<Member>(
- l.syntax().clone(),
- l.l_brace_token().map(Into::into).as_ref(),
- l.r_brace_token().map(Into::into).as_ref(),
- None,
- );
- if children.is_empty() && end_comments.is_empty() {
- p!(out, str("{ }"));
- return;
- }
- p!(out, str("{") >i nl);
- for (i, mem) in children.into_iter().enumerate() {
- if mem.should_start_with_newline && i != 0 {
- p!(out, nl);
- }
- format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
- p!(out, {mem.value} str(","));
- format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
- p!(out, nl);
- }
-
- if end_comments.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
- p!(out, <i str("}"));
- }
- }
- }
-}
-impl Printable for UnaryOperator {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(self.text().to_string()));
- }
-}
-impl Printable for BinaryOperator {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(self.text().to_string()));
- }
-}
-impl Printable for Bind {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::BindDestruct(d) => {
- p!(out, {d.into()} str(" = ") {d.value()});
- }
- Self::BindFunction(f) => {
- p!(out, {f.name()} {f.params()} str(" = ") {f.value()});
- }
- }
- }
-}
-impl Printable for Literal {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(self.syntax().to_string()));
- }
-}
-impl Printable for ImportKind {
- fn print(&self, out: &mut PrintItems) {
- p!(out, string(self.syntax().to_string()));
- }
-}
-impl Printable for ForSpec {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});
- }
-}
-impl Printable for IfSpec {
- fn print(&self, out: &mut PrintItems) {
- p!(out, str("if ") {self.expr()});
- }
-}
-impl Printable for CompSpec {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::ForSpec(f) => f.print(out),
- Self::IfSpec(i) => i.print(out),
- }
- }
-}
-impl Printable for Expr {
- fn print(&self, out: &mut PrintItems) {
- let (stmts, _ending) = children_between::<Stmt>(
- self.syntax().clone(),
- None,
- self.expr_base()
- .as_ref()
- .map(ExprBase::syntax)
- .cloned()
- .map(Into::into)
- .as_ref(),
- None,
- );
- for stmt in stmts {
- p!(out, { stmt.value });
- }
- p!(out, { self.expr_base() });
- let (suffixes, _ending) = children_between::<Suffix>(
- self.syntax().clone(),
- self.expr_base()
- .as_ref()
- .map(ExprBase::syntax)
- .cloned()
- .map(Into::into)
- .as_ref(),
- None,
- None,
- );
- for suffix in suffixes {
- p!(out, { suffix.value });
- }
- }
-}
-impl Printable for Suffix {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::SuffixIndex(i) => {
- if i.question_mark_token().is_some() {
- p!(out, str("?"));
- }
- p!(out, str(".") {i.index()});
- }
- Self::SuffixIndexExpr(e) => {
- if e.question_mark_token().is_some() {
- p!(out, str(".?"));
- }
- p!(out, str("[") {e.index()} str("]"));
- }
- Self::SuffixSlice(d) => {
- p!(out, { d.slice_desc() });
- }
- Self::SuffixApply(a) => {
- p!(out, { a.args_desc() });
- }
- }
- }
-}
-impl Printable for Stmt {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::StmtLocal(l) => {
- let (binds, end_comments) = children_between::<Bind>(
- l.syntax().clone(),
- l.local_kw_token().map(Into::into).as_ref(),
- l.semi_token().map(Into::into).as_ref(),
- None,
- );
- if binds.len() == 1 {
- let bind = &binds[0];
- format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);
- p!(out, str("local ") {bind.value});
- // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
- } else {
- p!(out,str("local") >i nl);
- for bind in binds {
- if bind.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);
- p!(out, {bind.value} str(","));
- format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);
- p!(out, nl);
- }
- if end_comments.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
- p!(out,<i);
- }
- p!(out,str(";") nl);
- }
- Self::StmtAssert(a) => {
- p!(out, {a.assertion()} str(";") nl);
- }
- }
- }
-}
-impl Printable for ExprBase {
- fn print(&self, out: &mut PrintItems) {
- match self {
- Self::ExprBinary(b) => {
- p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});
- }
- Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),
- // Self::ExprSlice(s) => {
- // p!(new: {s.expr()} {s.slice_desc()})
- // }
- // Self::ExprIndex(i) => {
- // p!(new: {i.expr()} str(".") {i.index()})
- // }
- // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),
- // Self::ExprApply(a) => {
- // let mut pi = p!(new: {a.expr()} {a.args_desc()});
- // if a.tailstrict_kw_token().is_some() {
- // p!(out,str(" tailstrict"));
- // }
- // pi
- // }
- Self::ExprObjExtend(ex) => {
- p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});
- }
- Self::ExprParened(p) => {
- p!(out, str("(") {p.expr()} str(")"));
- }
- Self::ExprString(s) => p!(out, { s.text() }),
- Self::ExprNumber(n) => p!(out, { n.number() }),
- Self::ExprArray(a) => {
- p!(out, str("[") >i nl);
- for el in a.exprs() {
- p!(out, {el} str(",") nl);
- }
- p!(out, <i str("]"));
- }
- Self::ExprObject(obj) => {
- p!(out, { obj.obj_body() });
- }
- Self::ExprArrayComp(arr) => {
- p!(out, str("[") {arr.expr()});
- for spec in arr.comp_specs() {
- p!(out, str(" ") {spec});
- }
- p!(out, str("]"));
- }
- Self::ExprImport(v) => {
- p!(out, {v.import_kind()} str(" ") {v.text()});
- }
- Self::ExprVar(n) => p!(out, { n.name() }),
- // Self::ExprLocal(l) => {
- // }
- Self::ExprIfThenElse(ite) => {
- p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});
- if ite.else_kw_token().is_some() || ite.else_().is_some() {
- p!(out, str(" else ") {ite.else_().map(|t| t.expr())});
- }
- }
- Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),
- // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),
- Self::ExprError(e) => p!(out, str("error ") {e.expr()}),
- Self::ExprLiteral(l) => {
- p!(out, { l.literal() });
- }
- }
- }
-}
-
-impl Printable for SourceFile {
- fn print(&self, out: &mut PrintItems) {
- let before = trivia_before(
- self.syntax().clone(),
- self.expr()
- .map(|e| e.syntax().clone())
- .map(Into::into)
- .as_ref(),
- );
- let after = trivia_after(
- self.syntax().clone(),
- self.expr()
- .map(|e| e.syntax().clone())
- .map(Into::into)
- .as_ref(),
- );
- format_comments(&before, CommentLocation::AboveItem, out);
- p!(out, {self.expr()} nl);
- format_comments(&after, CommentLocation::EndOfItems, out);
- }
-}
-
-struct FormatOptions {
- // 0 for hard tabs
- indent: u8,
-}
-fn format(input: &str, opts: &FormatOptions) -> Option<String> {
- let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
- if !errors.is_empty() {
- let mut builder = hi_doc::SnippetBuilder::new(input);
- for error in errors {
- builder
- .error(hi_doc::Text::fragment(
- format!("{:?}", error.error),
- Formatting::default(),
- ))
- .range(
- error.range.start().into()
- ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),
- )
- .build();
- }
- let snippet = builder.build();
- let ansi = hi_doc::source_to_ansi(&snippet);
- eprintln!("{ansi}");
- // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip
- // ERROR rowan nodes.
- // Recovery needs to be enabled for LSP, though.
- //
- // TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.
- return None;
- }
- Some(dprint_core::formatting::format(
- || {
- let mut out = PrintItems::new();
- parsed.print(&mut out);
- out
- },
- PrintOptions {
- indent_width: if opts.indent == 0 {
- // Reasonable max length for both 2 and 4 space sized tabs.
- 3
- } else {
- opts.indent
- },
- max_width: 100,
- use_tabs: opts.indent == 0,
- new_line_text: "\n",
- },
- ))
-}
+use jrsonnet_formatter::{format, FormatOptions};
#[derive(Parser)]
#[allow(clippy::struct_excessive_bools)]
@@ -768,9 +20,7 @@
#[arg(long)]
test: bool,
/// Number of spaces to indent with
- ///
- /// 0 for guess from input (default), and use hard tabs if unable to guess.
- #[arg(long, default_value = "0")]
+ #[arg(long, default_value = "2")]
indent: u8,
/// Force hard tab for indentation
#[arg(long)]
@@ -819,7 +69,7 @@
let mut convergence_tmp;
// https://github.com/dprint/dprint/pull/423
loop {
- let Some(reformatted) = format(
+ let reformatted = match format(
&formatted,
&FormatOptions {
indent: if opts.indent == 0 || opts.hard_tabs {
@@ -828,8 +78,14 @@
opts.indent
},
},
- ) else {
- return Err(Error::Parse);
+ ) {
+ Ok(v) => v,
+ Err(e) => {
+ let snippet = e.build();
+ let ansi = hi_doc::source_to_ansi(&snippet);
+ eprintln!("{ansi}");
+ return Err(Error::Parse);
+ }
};
convergence_tmp = reformatted.trim().to_owned();
if formatted == convergence_tmp {
cmds/jrsonnet-fmt/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snapdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snap
+++ /dev/null
@@ -1,53 +0,0 @@
----
-source: cmds/jrsonnet-fmt/src/tests.rs
-expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n }\"))"
----
-{
- comments: {
- _: '',
- // Plain comment
- a: '',
-
- # Plain comment with empty line before
- b: '',
- /* Single-line multiline comment */
- c: '',
-
- /**
- * Single-line multiline doc comment
- */
- c: '',
-
- /**
- * Multiline doc
- * Comment
- */
- c: '',
-
- /*
- Multi-line
-
- comment
- */
- d: '',
-
- e: '', // Inline comment
-
- k: '',
-
- // Text after everything
- },
- comments2: {
- k: '',
- // Text after everything, but no newline above
- },
- spacing: {
- a: '',
-
- b: '',
- },
- noSpacing: {
- a: '',
- b: '',
- },
-}
cmds/jrsonnet-fmt/src/tests.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/tests.rs
+++ /dev/null
@@ -1,79 +0,0 @@
-use dprint_core::formatting::{PrintItems, PrintOptions};
-use indoc::indoc;
-
-use crate::Printable;
-
-fn reformat(input: &str) -> String {
- let (source, _) = jrsonnet_rowan_parser::parse(input);
-
- dprint_core::formatting::format(
- || {
- let mut out = PrintItems::new();
- source.print(&mut out);
- out
- },
- PrintOptions {
- indent_width: 2,
- max_width: 100,
- use_tabs: true,
- new_line_text: "\n",
- },
- )
-}
-
-#[test]
-fn complex_comments_snapshot() {
- insta::assert_snapshot!(reformat(indoc!(
- "{
- comments: {
- _: '',
- // Plain comment
- a: '',
-
- # Plain comment with empty line before
- b: '',
- /*Single-line multiline comment
-
- */
- c: '',
-
- /**Single-line multiline doc comment
-
- */
- c: '',
-
- /**Multiline doc
- Comment
- */
- c: '',
-
- /*
-
- Multi-line
-
- comment
- */
- d: '',
-
- e: '', // Inline comment
-
- k: '',
-
- // Text after everything
- },
- comments2: {
- k: '',
- // Text after everything, but no newline above
- },
- spacing: {
- a: '',
-
- b: '',
- },
- noSpacing: {
- a: '',
- b: '',
- },
- }"
- )));
-}
crates/jrsonnet-formatter/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "jrsonnet-formatter"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
+[dependencies]
+dprint-core.workspace = true
+hi-doc.workspace = true
+jrsonnet-rowan-parser.workspace = true
+
+[lints]
+workspace = true
crates/jrsonnet-formatter/src/children.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/children.rs
@@ -0,0 +1,251 @@
+// TODO: Return errors as trivia
+
+use std::{fmt::Debug, mem};
+
+use jrsonnet_rowan_parser::{
+ nodes::{CustomError, Trivia, TriviaKind},
+ AstNode, AstToken, SyntaxElement, SyntaxNode, TS,
+};
+
+pub type ChildTrivia = Vec<Result<Trivia, String>>;
+
+/// Node should have no non-trivia tokens before element
+pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {
+ let mut out = Vec::new();
+ for item in node.children_with_tokens() {
+ if Some(&item) == end {
+ break;
+ }
+
+ if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+ out.push(Ok(trivia));
+ } else if CustomError::can_cast(item.kind()) {
+ out.push(Err(item.to_string()));
+ } else if end.is_none() {
+ break;
+ } else {
+ assert!(
+ TS![, ;].contains(item.kind()),
+ "silently eaten token: {:?}",
+ item.kind()
+ );
+ }
+ }
+ out
+}
+/// Node should have no non-trivia tokens after element
+pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {
+ if start.is_none() {
+ return Vec::new();
+ }
+ let mut iter = node.children_with_tokens().peekable();
+ while iter.peek() != start {
+ iter.next();
+ }
+ iter.next();
+ let mut out = Vec::new();
+ for item in iter {
+ if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+ out.push(Ok(trivia));
+ } else if CustomError::can_cast(item.kind()) {
+ out.push(Err(item.to_string()));
+ } else {
+ assert!(
+ TS![, ;].contains(item.kind()),
+ "silently eaten token: {:?}",
+ item.kind()
+ );
+ }
+ }
+ out
+}
+
+pub fn children_between<T: AstNode + Debug>(
+ node: SyntaxNode,
+ start: Option<&SyntaxElement>,
+ end: Option<&SyntaxElement>,
+ trailing: Option<ChildTrivia>,
+) -> (Vec<Child<T>>, EndingComments) {
+ let mut iter = node.children_with_tokens().peekable();
+ if start.is_some() {
+ while iter.peek() != start {
+ iter.next();
+ }
+ iter.next();
+ }
+ children(
+ iter.take_while(|i| Some(i) != end),
+ start.is_none() && end.is_none(),
+ trailing,
+ )
+}
+
+pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {
+ count_newlines_before(tt)
+ + prev_inline
+ .map(count_newlines_after)
+ .unwrap_or_default()
+
+ // First for previous item end, second for current item
+ >= 2
+}
+
+fn count_newlines_before(tt: &ChildTrivia) -> usize {
+ let mut nl_count = 0;
+ for t in tt {
+ match t {
+ Ok(t) => match t.kind() {
+ TriviaKind::Whitespace => {
+ nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+ }
+ _ => break,
+ },
+ Err(_) => {
+ nl_count += 1;
+ }
+ }
+ }
+ nl_count
+}
+fn count_newlines_after(tt: &ChildTrivia) -> usize {
+ let mut nl_count = 0;
+ for t in tt.iter().rev() {
+ match t {
+ Ok(t) => match t.kind() {
+ TriviaKind::Whitespace => {
+ nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+ }
+ TriviaKind::SingleLineHashComment | TriviaKind::SingleLineSlashComment => {
+ nl_count += 1;
+ break;
+ }
+ _ => {}
+ },
+ Err(_) => nl_count += 1,
+ }
+ }
+ nl_count
+}
+
+pub fn children<T: AstNode + Debug>(
+ items: impl Iterator<Item = SyntaxElement>,
+ loose: bool,
+ mut trailing: Option<ChildTrivia>,
+) -> (Vec<Child<T>>, EndingComments) {
+ let mut out = Vec::new();
+ let mut current_child = None::<Child<T>>;
+ let mut next = ChildTrivia::new();
+ // Previous element ended, do not add more inline comments
+ let mut started_next = false;
+ let mut had_some = false;
+
+ for item in items {
+ if let Some(value) = item.as_node().cloned().and_then(T::cast) {
+ let before_trivia = if let Some(trailing) = trailing.take() {
+ assert!(next.is_empty());
+ trailing
+ } else {
+ mem::take(&mut next)
+ };
+ let last_child = current_child.replace(Child {
+ // First item should not start with newline
+ should_start_with_newline: had_some
+ && should_start_with_newline(
+ current_child.as_ref().map(|c| &c.inline_trivia),
+ &before_trivia,
+ ),
+ before_trivia,
+ value,
+ inline_trivia: Vec::new(),
+ });
+ if let Some(last_child) = last_child {
+ out.push(last_child);
+ }
+ had_some = true;
+ started_next = false;
+ } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+ let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment
+ || trivia.kind() == TriviaKind::SingleLineSlashComment;
+ if trailing.is_some() {
+ // Someone have already parsed trivia for us
+ continue;
+ } else if started_next
+ || current_child.is_none()
+ || trivia.text().contains('\n') && !is_single_line_comment
+ {
+ next.push(Ok(trivia.clone()));
+ started_next = true;
+ } else {
+ let cur = current_child.as_mut().expect("checked not none");
+ cur.inline_trivia.push(Ok(trivia));
+ if is_single_line_comment {
+ started_next = true;
+ }
+ }
+ had_some = true;
+ } else if CustomError::can_cast(item.kind()) {
+ next.push(Err(item.to_string()));
+ } else if loose {
+ if had_some {
+ break;
+ }
+ started_next = true;
+ } else {
+ assert!(
+ TS![, ;].contains(item.kind()),
+ "silently eaten token: {:?}",
+ item.kind()
+ );
+ }
+ }
+
+ let ending_comments = EndingComments {
+ should_start_with_newline: should_start_with_newline(
+ current_child.as_ref().map(|c| &c.inline_trivia),
+ &next,
+ ),
+ trivia: next,
+ };
+
+ if let Some(current_child) = current_child {
+ out.push(current_child);
+ }
+
+ (out, ending_comments)
+}
+
+#[derive(Debug)]
+pub struct Child<T> {
+ /// If this child has two newlines above in source code, so it needs to have it in the output
+ pub should_start_with_newline: bool,
+ /// Comment before item, i.e
+ ///
+ /// ```ignore
+ /// // Comment
+ /// item
+ /// ```
+ pub before_trivia: ChildTrivia,
+ pub value: T,
+ /// Comment after line, but located at same line
+ ///
+ /// ```ignore
+ /// item1, // Inline comment
+ /// // Not inline comment
+ /// item2,
+ /// ```
+ pub inline_trivia: ChildTrivia,
+}
+
+pub struct EndingComments {
+ /// If this child has two newlines above in source code, so it needs to have it in the output
+ pub should_start_with_newline: bool,
+ pub trivia: ChildTrivia,
+}
+impl EndingComments {
+ pub fn is_empty(&self) -> bool {
+ !self.should_start_with_newline && self.trivia.is_empty()
+ }
+ pub fn extract_trailing(&mut self) -> ChildTrivia {
+ mem::take(&mut self.trivia)
+ }
+}
crates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/comments.rs
@@ -0,0 +1,181 @@
+use std::string::String;
+
+use dprint_core::formatting::PrintItems;
+use jrsonnet_rowan_parser::{nodes::TriviaKind, AstToken};
+
+use crate::{children::ChildTrivia, p, pi};
+
+pub enum CommentLocation {
+ /// Above local, field, other things
+ AboveItem,
+ /// After item
+ ItemInline,
+ /// After all items in object
+ EndOfItems,
+}
+
+#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
+pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation, out: &mut PrintItems) {
+ for c in comments {
+ let Ok(c) = c else {
+ let mut text = c.as_ref().unwrap_err() as &str;
+ while !text.is_empty() {
+ let pos = text.find(['\n', '\t']).unwrap_or(text.len());
+ let sliced = &text[..pos];
+ p!(out, string(sliced.to_string()));
+ text = &text[pos..];
+ if !text.is_empty() {
+ match text.as_bytes()[0] {
+ b'\n' => p!(out, nl),
+ b'\t' => p!(out, tab),
+ _ => unreachable!(),
+ }
+ text = &text[1..];
+ }
+ }
+ continue;
+ };
+ match c.kind() {
+ TriviaKind::Whitespace => {}
+ TriviaKind::MultiLineComment => {
+ let mut text = c
+ .text()
+ .strip_prefix("/*")
+ .expect("ml comment starts with /*")
+ .strip_suffix("*/")
+ .expect("ml comment ends with */");
+ // doc-style comment, /**
+ let doc = if text.starts_with('*') {
+ text = &text[1..];
+ true
+ } else {
+ false
+ };
+ // Is comment starts with text immediatly, i.e /*text
+ let mut immediate_start = true;
+ let mut lines = text
+ .split('\n')
+ .map(|l| l.trim_end().to_string())
+ .skip_while(|l| {
+ if l.is_empty() {
+ immediate_start = false;
+ true
+ } else {
+ false
+ }
+ })
+ .collect::<Vec<_>>();
+ while lines.last().is_some_and(String::is_empty) {
+ lines.pop();
+ }
+ if lines.len() == 1 && !doc {
+ if matches!(loc, CommentLocation::ItemInline) {
+ p!(out, str(" "));
+ }
+ p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl);
+ } else if !lines.is_empty() {
+ fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
+ let offset = a
+ .bytes()
+ .zip(b.bytes())
+ .take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))
+ .count();
+ &a[..offset]
+ }
+ // First line is not empty, extract ws prefix of it
+ let mut common_ws_padding = (if immediate_start && lines.len() > 1 {
+ common_ws_prefix(&lines[1], &lines[1])
+ } else {
+ common_ws_prefix(&lines[0], &lines[0])
+ })
+ .to_string();
+ for line in lines
+ .iter()
+ .skip(if immediate_start { 2 } else { 1 })
+ .filter(|l| !l.is_empty())
+ {
+ common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();
+ }
+ for line in lines
+ .iter_mut()
+ .skip(usize::from(immediate_start))
+ .filter(|l| !l.is_empty())
+ {
+ *line = line
+ .strip_prefix(&common_ws_padding)
+ .expect("all non-empty lines start with this padding")
+ .to_string();
+ }
+
+ p!(out, str("/*"));
+ if doc {
+ p!(out, str("*"));
+ }
+ p!(out, nl);
+ for mut line in lines {
+ if doc {
+ p!(out, str(" *"));
+ }
+ if line.is_empty() {
+ p!(out, nl);
+ } else {
+ if doc {
+ p!(out, str(" "));
+ }
+ while let Some(new_line) = line.strip_prefix('\t') {
+ if doc {
+ p!(out, str(" "));
+ } else {
+ p!(out, tab);
+ }
+ line = new_line.to_string();
+ }
+ p!(out, string(line.to_string()) nl);
+ }
+ }
+ if doc {
+ p!(out, str(" "));
+ }
+ p!(out, str("*/") nl);
+ }
+ }
+ // TODO: Keep common padding for multiple continous lines of single-line comments
+ // I.e
+ // ```
+ // # Line1
+ // # Line2
+ // ```
+ // Should be reformatted as
+ // ```
+ // # Line1
+ // # Line2
+ // ```
+ // But currently comment formatter is not aware of continous comment lines, and reformats it as
+ // ```
+ // # Line1
+ // # Line2
+ // ```
+ TriviaKind::SingleLineHashComment => {
+ if matches!(loc, CommentLocation::ItemInline) {
+ p!(out, str(" "));
+ }
+ p!(out, str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));
+ if !matches!(loc, CommentLocation::ItemInline) {
+ p!(out, nl);
+ }
+ }
+ TriviaKind::SingleLineSlashComment => {
+ if matches!(loc, CommentLocation::ItemInline) {
+ p!(out, str(" "));
+ }
+ p!(out, str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));
+ if !matches!(loc, CommentLocation::ItemInline) {
+ p!(out, nl);
+ }
+ }
+ // Garbage in - garbage out
+ TriviaKind::ErrorCommentTooShort => p!(out, str("/*/")),
+ TriviaKind::ErrorCommentUnterminated => p!(out, string(c.text().to_string())),
+ }
+ }
+}
crates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -0,0 +1,740 @@
+use std::{any::type_name, rc::Rc};
+
+use children::{children_between, trivia_before};
+use dprint_core::formatting::{
+ condition_helpers::is_multiple_lines, condition_resolvers::true_resolver,
+ ConditionResolverContext, LineNumber, PrintItems, PrintOptions,
+};
+use hi_doc::{Formatting, SnippetBuilder};
+use jrsonnet_rowan_parser::{
+ nodes::{
+ Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
+ DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
+ Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,
+ UnaryOperator, Visibility,
+ },
+ AstNode, AstToken as _, SyntaxToken,
+};
+
+use crate::{
+ children::trivia_after,
+ comments::{format_comments, CommentLocation},
+};
+
+mod children;
+mod comments;
+#[cfg(test)]
+mod tests;
+
+pub trait Printable {
+ fn print(&self, out: &mut PrintItems);
+}
+
+macro_rules! pi {
+ (@i; $($t:tt)*) => {{
+ #[allow(unused_mut)]
+ let mut o = dprint_core::formatting::PrintItems::new();
+ pi!(@s; o: $($t)*);
+ o
+ }};
+ (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{
+ $o.push_string($e.to_owned());
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{
+ $o.push_string($e);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: nl $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::NewLine);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: tab $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::Tab);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: >i $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::StartIndent);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: <i $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::FinishIndent);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: info($v:expr) $($t:tt)*) => {{
+ $o.push_info($v);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{
+ $o.push_condition(dprint_core::formatting::conditions::if_true(
+ $s,
+ $cond.clone(),
+ {
+ let mut o = PrintItems::new();
+ p!(o, $($i)*);
+ o
+ },
+ ));
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{
+ $o.push_condition(dprint_core::formatting::conditions::if_true_or(
+ $s,
+ $cond.clone(),
+ {
+ let mut o = PrintItems::new();
+ p!(o, $($i)*);
+ o
+ },
+ {
+ let mut o = PrintItems::new();
+ p!(o, $($e)*);
+ o
+ },
+ ));
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{
+ $o.push_condition(dprint_core::formatting::conditions::if_true_or(
+ $s,
+ $cond.clone(),
+ {
+ let o = PrintItems::new();
+ o
+ },
+ {
+ let mut o = PrintItems::new();
+ p!(o, $($e)*);
+ o
+ },
+ ));
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{
+ $expr.print($o);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{
+ $o.extend($expr);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{
+ if $e {
+ pi!(@s; $o: $($then)*);
+ }
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{
+ if $e {
+ pi!(@s; $o: $($then)*);
+ } else {
+ pi!(@s; $o: $($else)*);
+ }
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $i:ident:) => {}
+}
+macro_rules! p {
+ ($o:ident, $($t:tt)*) => {
+ pi!(@s; $o: $($t)*)
+ };
+}
+pub(crate) use p;
+pub(crate) use pi;
+
+impl<P> Printable for Option<P>
+where
+ P: Printable,
+{
+ fn print(&self, out: &mut PrintItems) {
+ if let Some(v) = self {
+ v.print(out);
+ } else {
+ p!(
+ out,
+ string(format!(
+ "/*missing {}*/",
+ type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")
+ ),)
+ );
+ }
+ }
+}
+
+impl Printable for SyntaxToken {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(self.to_string()));
+ }
+}
+
+impl Printable for Text {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(format!("{}", self)));
+ }
+}
+impl Printable for Number {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(format!("{}", self)));
+ }
+}
+
+impl Printable for Name {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, { self.ident_lit() });
+ }
+}
+
+impl Printable for DestructRest {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("..."));
+ if let Some(name) = self.into() {
+ p!(out, { name });
+ }
+ }
+}
+
+impl Printable for Destruct {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::DestructFull(f) => {
+ p!(out, { f.name() });
+ }
+ Self::DestructSkip(_) => p!(out, str("?")),
+ Self::DestructArray(a) => {
+ p!(out, str("[") >i nl);
+ for el in a.destruct_array_parts() {
+ match el {
+ DestructArrayPart::DestructArrayElement(e) => {
+ p!(out, {e.destruct()} str(",") nl);
+ }
+ DestructArrayPart::DestructRest(d) => {
+ p!(out, {d} str(",") nl);
+ }
+ }
+ }
+ p!(out, <i str("]"));
+ }
+ Self::DestructObject(o) => {
+ p!(out, str("{") >i nl);
+ for item in o.destruct_object_fields() {
+ p!(out, { item.field() });
+ if let Some(des) = item.destruct() {
+ p!(out, str(": ") {des});
+ }
+ if let Some(def) = item.expr() {
+ p!(out, str(" = ") {def});
+ }
+ p!(out, str(",") nl);
+ }
+ if let Some(rest) = o.destruct_rest() {
+ p!(out, {rest} nl);
+ }
+ p!(out, <i str("}"));
+ }
+ }
+ }
+}
+
+impl Printable for FieldName {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::FieldNameFixed(f) => {
+ if let Some(id) = f.id() {
+ p!(out, { id });
+ } else if let Some(str) = f.text() {
+ p!(out, { str });
+ } else {
+ p!(out, str("/*missing FieldName*/"));
+ }
+ }
+ Self::FieldNameDynamic(d) => {
+ p!(out, str("[") {d.expr()} str("]"));
+ }
+ }
+ }
+}
+
+impl Printable for Visibility {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(self.to_string()));
+ }
+}
+
+impl Printable for ObjLocal {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("local ") {self.bind()});
+ }
+}
+
+impl Printable for Assertion {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("assert ") {self.condition()});
+ if self.colon_token().is_some() || self.message().is_some() {
+ p!(out, str(": ") {self.message()});
+ }
+ }
+}
+
+impl Printable for ParamsDesc {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("(") >i nl);
+ for param in self.params() {
+ p!(out, { param.destruct() });
+ if param.assign_token().is_some() || param.expr().is_some() {
+ p!(out, str(" = ") {param.expr()});
+ }
+ p!(out, str(",") nl);
+ }
+ p!(out, <i str(")"));
+ }
+}
+impl Printable for ArgsDesc {
+ fn print(&self, out: &mut PrintItems) {
+ let start = LineNumber::new("start");
+ let end = LineNumber::new("end");
+ let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {
+ is_multiple_lines(condition_context, start, end).map(|v| !v)
+ });
+ p!(out, str("(") info(start) if("start args", multi_line, >i nl));
+ let (children, end_comments) = children_between::<Arg>(
+ self.syntax().clone(),
+ self.l_paren_token().map(Into::into).as_ref(),
+ self.r_paren_token().map(Into::into).as_ref(),
+ None,
+ );
+ let mut args = children.into_iter().peekable();
+ while let Some(ele) = args.next() {
+ if ele.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);
+ let arg = ele.value;
+ if arg.name().is_some() || arg.assign_token().is_some() {
+ p!(out, {arg.name()} str(" = "));
+ }
+ let comma_between = if args.peek().is_some() {
+ true_resolver()
+ } else {
+ multi_line.clone()
+ };
+ p!(out, {arg.expr()} if("arg comma", comma_between, str(",") if_not("between args", multi_line, str(" "))));
+ format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);
+ p!(out, if("between args", multi_line, nl));
+ }
+ if end_comments.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
+ p!(out, if("end args", multi_line, <i info(end)) str(")"));
+ }
+}
+impl Printable for SliceDesc {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("["));
+ if self.from().is_some() {
+ p!(out, { self.from() });
+ }
+ p!(out, str(":"));
+ if self.end().is_some() {
+ p!(out, { self.end().map(|e| e.expr()) });
+ }
+ // Keep only one : in case if we don't need step
+ if self.step().is_some() {
+ p!(out, str(":") {self.step().map(|e|e.expr())});
+ }
+ p!(out, str("]"));
+ }
+}
+
+impl Printable for Member {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::MemberBindStmt(b) => {
+ p!(out, { b.obj_local() });
+ }
+ Self::MemberAssertStmt(ass) => {
+ p!(out, { ass.assertion() });
+ }
+ Self::MemberFieldNormal(n) => {
+ p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});
+ }
+ Self::MemberFieldMethod(m) => {
+ p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});
+ }
+ }
+ }
+}
+
+impl Printable for ObjBody {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::ObjBodyComp(l) => {
+ let (children, mut end_comments) = children_between::<Member>(
+ l.syntax().clone(),
+ l.l_brace_token().map(Into::into).as_ref(),
+ Some(
+ &(l.comp_specs()
+ .next()
+ .expect("at least one spec is defined")
+ .syntax()
+ .clone())
+ .into(),
+ ),
+ None,
+ );
+ let trailing_for_comp = end_comments.extract_trailing();
+ p!(out, str("{") >i nl);
+ for mem in children {
+ if mem.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
+ p!(out, {mem.value} str(","));
+ format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
+ p!(out, nl);
+ }
+
+ if end_comments.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
+
+ let (compspecs, end_comments) = children_between::<CompSpec>(
+ l.syntax().clone(),
+ l.member_comps()
+ .last()
+ .map(|m| m.syntax().clone())
+ .map(Into::into)
+ .or_else(|| l.l_brace_token().map(Into::into))
+ .as_ref(),
+ l.r_brace_token().map(Into::into).as_ref(),
+ Some(trailing_for_comp),
+ );
+ for mem in compspecs {
+ if mem.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
+ p!(out, { mem.value });
+ format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
+ }
+ if end_comments.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
+
+ p!(out, nl <i str("}"));
+ }
+ Self::ObjBodyMemberList(l) => {
+ let (children, end_comments) = children_between::<Member>(
+ l.syntax().clone(),
+ l.l_brace_token().map(Into::into).as_ref(),
+ l.r_brace_token().map(Into::into).as_ref(),
+ None,
+ );
+ if children.is_empty() && end_comments.is_empty() {
+ p!(out, str("{ }"));
+ return;
+ }
+ p!(out, str("{") >i nl);
+ for (i, mem) in children.into_iter().enumerate() {
+ if mem.should_start_with_newline && i != 0 {
+ p!(out, nl);
+ }
+ format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
+ p!(out, {mem.value} str(","));
+ format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
+ p!(out, nl);
+ }
+
+ if end_comments.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
+ p!(out, <i str("}"));
+ }
+ }
+ }
+}
+impl Printable for UnaryOperator {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(self.text().to_string()));
+ }
+}
+impl Printable for BinaryOperator {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(self.text().to_string()));
+ }
+}
+impl Printable for Bind {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::BindDestruct(d) => {
+ p!(out, {d.into()} str(" = ") {d.value()});
+ }
+ Self::BindFunction(f) => {
+ p!(out, {f.name()} {f.params()} str(" = ") {f.value()});
+ }
+ }
+ }
+}
+impl Printable for Literal {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(self.syntax().to_string()));
+ }
+}
+impl Printable for ImportKind {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, string(self.syntax().to_string()));
+ }
+}
+impl Printable for ForSpec {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});
+ }
+}
+impl Printable for IfSpec {
+ fn print(&self, out: &mut PrintItems) {
+ p!(out, str("if ") {self.expr()});
+ }
+}
+impl Printable for CompSpec {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::ForSpec(f) => f.print(out),
+ Self::IfSpec(i) => i.print(out),
+ }
+ }
+}
+impl Printable for Expr {
+ fn print(&self, out: &mut PrintItems) {
+ let (stmts, _ending) = children_between::<Stmt>(
+ self.syntax().clone(),
+ None,
+ self.expr_base()
+ .as_ref()
+ .map(ExprBase::syntax)
+ .cloned()
+ .map(Into::into)
+ .as_ref(),
+ None,
+ );
+ for stmt in stmts {
+ p!(out, { stmt.value });
+ }
+ p!(out, { self.expr_base() });
+ let (suffixes, _ending) = children_between::<Suffix>(
+ self.syntax().clone(),
+ self.expr_base()
+ .as_ref()
+ .map(ExprBase::syntax)
+ .cloned()
+ .map(Into::into)
+ .as_ref(),
+ None,
+ None,
+ );
+ for suffix in suffixes {
+ p!(out, { suffix.value });
+ }
+ }
+}
+impl Printable for Suffix {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::SuffixIndex(i) => {
+ if i.question_mark_token().is_some() {
+ p!(out, str("?"));
+ }
+ p!(out, str(".") {i.index()});
+ }
+ Self::SuffixIndexExpr(e) => {
+ if e.question_mark_token().is_some() {
+ p!(out, str(".?"));
+ }
+ p!(out, str("[") {e.index()} str("]"));
+ }
+ Self::SuffixSlice(d) => {
+ p!(out, { d.slice_desc() });
+ }
+ Self::SuffixApply(a) => {
+ p!(out, { a.args_desc() });
+ }
+ }
+ }
+}
+impl Printable for Stmt {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::StmtLocal(l) => {
+ let (binds, end_comments) = children_between::<Bind>(
+ l.syntax().clone(),
+ l.local_kw_token().map(Into::into).as_ref(),
+ l.semi_token().map(Into::into).as_ref(),
+ None,
+ );
+ if binds.len() == 1 {
+ let bind = &binds[0];
+ format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);
+ p!(out, str("local ") {bind.value});
+ // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
+ } else {
+ p!(out,str("local") >i nl);
+ for bind in binds {
+ if bind.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);
+ p!(out, {bind.value} str(","));
+ format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);
+ p!(out, nl);
+ }
+ if end_comments.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
+ p!(out,<i);
+ }
+ p!(out,str(";") nl);
+ }
+ Self::StmtAssert(a) => {
+ p!(out, {a.assertion()} str(";") nl);
+ }
+ }
+ }
+}
+impl Printable for ExprBase {
+ fn print(&self, out: &mut PrintItems) {
+ match self {
+ Self::ExprBinary(b) => {
+ p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});
+ }
+ Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),
+ // Self::ExprSlice(s) => {
+ // p!(new: {s.expr()} {s.slice_desc()})
+ // }
+ // Self::ExprIndex(i) => {
+ // p!(new: {i.expr()} str(".") {i.index()})
+ // }
+ // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),
+ // Self::ExprApply(a) => {
+ // let mut pi = p!(new: {a.expr()} {a.args_desc()});
+ // if a.tailstrict_kw_token().is_some() {
+ // p!(out,str(" tailstrict"));
+ // }
+ // pi
+ // }
+ Self::ExprObjExtend(ex) => {
+ p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});
+ }
+ Self::ExprParened(p) => {
+ p!(out, str("(") {p.expr()} str(")"));
+ }
+ Self::ExprString(s) => p!(out, { s.text() }),
+ Self::ExprNumber(n) => p!(out, { n.number() }),
+ Self::ExprArray(a) => {
+ p!(out, str("[") >i nl);
+ for el in a.exprs() {
+ p!(out, {el} str(",") nl);
+ }
+ p!(out, <i str("]"));
+ }
+ Self::ExprObject(obj) => {
+ p!(out, { obj.obj_body() });
+ }
+ Self::ExprArrayComp(arr) => {
+ p!(out, str("[") {arr.expr()});
+ for spec in arr.comp_specs() {
+ p!(out, str(" ") {spec});
+ }
+ p!(out, str("]"));
+ }
+ Self::ExprImport(v) => {
+ p!(out, {v.import_kind()} str(" ") {v.text()});
+ }
+ Self::ExprVar(n) => p!(out, { n.name() }),
+ // Self::ExprLocal(l) => {
+ // }
+ Self::ExprIfThenElse(ite) => {
+ p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});
+ if ite.else_kw_token().is_some() || ite.else_().is_some() {
+ p!(out, str(" else ") {ite.else_().map(|t| t.expr())});
+ }
+ }
+ Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),
+ // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),
+ Self::ExprError(e) => p!(out, str("error ") {e.expr()}),
+ Self::ExprLiteral(l) => {
+ p!(out, { l.literal() });
+ }
+ }
+ }
+}
+
+impl Printable for SourceFile {
+ fn print(&self, out: &mut PrintItems) {
+ let before = trivia_before(
+ self.syntax().clone(),
+ self.expr()
+ .map(|e| e.syntax().clone())
+ .map(Into::into)
+ .as_ref(),
+ );
+ let after = trivia_after(
+ self.syntax().clone(),
+ self.expr()
+ .map(|e| e.syntax().clone())
+ .map(Into::into)
+ .as_ref(),
+ );
+ format_comments(&before, CommentLocation::AboveItem, out);
+ p!(out, {self.expr()} nl);
+ format_comments(&after, CommentLocation::EndOfItems, out);
+ }
+}
+
+pub struct FormatOptions {
+ // 0 for hard tabs
+ pub indent: u8,
+}
+pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {
+ let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
+ if !errors.is_empty() {
+ let mut builder = hi_doc::SnippetBuilder::new(input);
+ for error in errors {
+ builder
+ .error(hi_doc::Text::fragment(
+ format!("{:?}", error.error),
+ Formatting::default(),
+ ))
+ .range(
+ error.range.start().into()
+ ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),
+ )
+ .build();
+ }
+ // let snippet = builder.build();
+ return Err(builder);
+ // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip
+ // ERROR rowan nodes.
+ // Recovery needs to be enabled for LSP, though.
+ }
+ Ok(dprint_core::formatting::format(
+ || {
+ let mut out = PrintItems::new();
+ parsed.print(&mut out);
+ out
+ },
+ PrintOptions {
+ indent_width: if opts.indent == 0 {
+ // Reasonable max length for both 2 and 4 space sized tabs.
+ 3
+ } else {
+ opts.indent
+ },
+ max_width: 100,
+ use_tabs: opts.indent == 0,
+ new_line_text: "\n",
+ },
+ ))
+}
crates/jrsonnet-formatter/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snap
@@ -0,0 +1,53 @@
+---
+source: cmds/jrsonnet-fmt/src/tests.rs
+expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n }\"))"
+---
+{
+ comments: {
+ _: '',
+ // Plain comment
+ a: '',
+
+ # Plain comment with empty line before
+ b: '',
+ /* Single-line multiline comment */
+ c: '',
+
+ /**
+ * Single-line multiline doc comment
+ */
+ c: '',
+
+ /**
+ * Multiline doc
+ * Comment
+ */
+ c: '',
+
+ /*
+ Multi-line
+
+ comment
+ */
+ d: '',
+
+ e: '', // Inline comment
+
+ k: '',
+
+ // Text after everything
+ },
+ comments2: {
+ k: '',
+ // Text after everything, but no newline above
+ },
+ spacing: {
+ a: '',
+
+ b: '',
+ },
+ noSpacing: {
+ a: '',
+ b: '',
+ },
+}
crates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/tests.rs
@@ -0,0 +1,79 @@
+use dprint_core::formatting::{PrintItems, PrintOptions};
+use indoc::indoc;
+
+use crate::Printable;
+
+fn reformat(input: &str) -> String {
+ let (source, _) = jrsonnet_rowan_parser::parse(input);
+
+ dprint_core::formatting::format(
+ || {
+ let mut out = PrintItems::new();
+ source.print(&mut out);
+ out
+ },
+ PrintOptions {
+ indent_width: 2,
+ max_width: 100,
+ use_tabs: true,
+ new_line_text: "\n",
+ },
+ )
+}
+
+#[test]
+fn complex_comments_snapshot() {
+ insta::assert_snapshot!(reformat(indoc!(
+ "{
+ comments: {
+ _: '',
+ // Plain comment
+ a: '',
+
+ # Plain comment with empty line before
+ b: '',
+ /*Single-line multiline comment
+
+ */
+ c: '',
+
+ /**Single-line multiline doc comment
+
+ */
+ c: '',
+
+ /**Multiline doc
+ Comment
+ */
+ c: '',
+
+ /*
+
+ Multi-line
+
+ comment
+ */
+ d: '',
+
+ e: '', // Inline comment
+
+ k: '',
+
+ // Text after everything
+ },
+ comments2: {
+ k: '',
+ // Text after everything, but no newline above
+ },
+ spacing: {
+ a: '',
+
+ b: '',
+ },
+ noSpacing: {
+ a: '',
+ b: '',
+ },
+ }"
+ )));
+}