plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-core/tests/formatter_test.rs
| 3d6f280 | 1 | #![allow(non_snake_case)] |
| 3d6f280 | 2 | use plum_core::formatSource; |
| 35bca24 | 3 | |
| 35bca24 | 4 | #[test] |
| 3d6f280 | 5 | fn formatsSimpleFunction() { |
| b48d3a3 | 6 | let input = "fun main() =\n x = 1\n"; |
| 3d6f280 | 7 | let result = formatSource(input).expect("formatSource should succeed"); |
| 35bca24 | 8 | assert!(result.contains("main()")); |
| 35bca24 | 9 | assert!(result.contains("x =")); |
| 35bca24 | 10 | } |
| 35bca24 | 11 | |
| 35bca24 | 12 | #[test] |
| 3d6f280 | 13 | fn formatsBinaryOperatorSpacing() { |
| 2b8194c | 14 | let input = "fun add(a: Int, b: Int) -> Int =\n a+b\n"; |
| 3d6f280 | 15 | let result = formatSource(input).expect("formatSource should succeed"); |
| 35bca24 | 16 | assert!(result.contains("a + b")); |
| 35bca24 | 17 | } |
| 35bca24 | 18 | |
| 35bca24 | 19 | #[test] |
| 3d6f280 | 20 | fn rejectsSyntaxError() { |
| 35bca24 | 21 | let input = "@@@@invalid@@@@\n"; |
| 3d6f280 | 22 | let result = formatSource(input); |
| 35bca24 | 23 | assert!(result.is_err()); |
| 35bca24 | 24 | } |
| 35bca24 | 25 | |
| 35bca24 | 26 | #[test] |
| 3d6f280 | 27 | fn idempotentOnAlreadyFormatted() { |
| b48d3a3 | 28 | let input = "fun main() =\n x = 1 + 2\n"; |
| 3d6f280 | 29 | let first = formatSource(input).expect("first pass"); |
| 3d6f280 | 30 | let second = formatSource(&first).expect("second pass"); |
| 35bca24 | 31 | assert_eq!(first, second, "formatting should be idempotent"); |
| 35bca24 | 32 | } |
| 0000000 | 33 | |
| 0000000 | 34 | /// Regression test for a real bug: Topiary inserts NO whitespace between |
| 0000000 | 35 | /// adjacent leaves unless a query says to, so keywords with no explicit |
| 0000000 | 36 | /// spacing rule glue to the next token ("module" + "test" -> "moduletest"). |
| 0000000 | 37 | /// This exercises most of the keyword vocabulary in one pass. |
| 0000000 | 38 | #[test] |
| 0000000 | 39 | fn keywordsGetSpacedCorrectly() { |
| 0000000 | 40 | let input = "\ |
| 0000000 | 41 | module test |
| 0000000 | 42 | import std/os |
| 0000000 | 43 | |
| 0000000 | 44 | type Cat(IAnimal) = |
| 0000000 | 45 | name: Str |
| 0000000 | 46 | age: Int |
| 0000000 | 47 | |
| 0000000 | 48 | fun speak(self) -> Str = |
| 0000000 | 49 | \"meow\" |
| 0000000 | 50 | |
| 0000000 | 51 | fun classify(x: Int) -> Str = |
| 0000000 | 52 | if x < 0 |
| 0000000 | 53 | return \"neg\" |
| 0000000 | 54 | else if x == 0 |
| 0000000 | 55 | return \"zero\" |
| 0000000 | 56 | else |
| 0000000 | 57 | return \"pos\" |
| 0000000 | 58 | |
| 0000000 | 59 | fun loop() -> Int = |
| 0000000 | 60 | i := 0 |
| 0000000 | 61 | while i < 5 |
| 0000000 | 62 | i = i + 1 |
| 0000000 | 63 | return i |
| 0000000 | 64 | |
| 0000000 | 65 | fun main() = |
| 0000000 | 66 | assert 1 == 1 |
| 0000000 | 67 | return |
| 0000000 | 68 | "; |
| 0000000 | 69 | let once = formatSource(input).expect("first pass"); |
| 0000000 | 70 | assert!(once.contains("module test"), "got: {once}"); |
| 0000000 | 71 | assert!(once.contains("import std/os"), "got: {once}"); |
| 0000000 | 72 | assert!(once.contains("type Cat"), "got: {once}"); |
| 0000000 | 73 | assert!(once.contains("fun speak"), "got: {once}"); |
| 0000000 | 74 | assert!(once.contains("if x < 0"), "got: {once}"); |
| 0000000 | 75 | assert!(once.contains("else if x == 0"), "got: {once}"); |
| 0000000 | 76 | let twice = formatSource(&once).expect("second pass"); |
| 0000000 | 77 | assert_eq!(once, twice, "formatting should be idempotent"); |
| 0000000 | 78 | } |
| 0000000 | 79 | |
| 0000000 | 80 | /// Regression test: `import std/os` used to format down to `import /` — the |
| 0000000 | 81 | /// `url` grammar rule matched each path segment with a bare inline regex (no |
| 0000000 | 82 | /// node of its own), and Topiary's leaf collection only ever copies a |
| 0000000 | 83 | /// child NODE's own span, silently dropping any parent text a bare regex |
| 0000000 | 84 | /// like that leaves uncovered. |
| 0000000 | 85 | #[test] |
| 0000000 | 86 | fn importPathSegmentsSurviveFormatting() { |
| 0000000 | 87 | let input = "module test\nimport std/http/content_type\n"; |
| 0000000 | 88 | let once = formatSource(input).expect("first pass"); |
| 0000000 | 89 | assert!(once.contains("import std/http/content_type"), "got: {once}"); |
| 0000000 | 90 | } |
| 0000000 | 91 | |
| 0000000 | 92 | /// Regression test: the ternary `?`/`:` had no spacing rule at all, and enum |
| 0000000 | 93 | /// variants / match cases (`| Foo`, `match ... case`) live directly under |
| 0000000 | 94 | /// their parent node (no wrapping body), so they need their own hardline |
| 0000000 | 95 | /// rules distinct from a `fn` body's. |
| 0000000 | 96 | #[test] |
| 0000000 | 97 | fn ternaryEnumAndMatchFormatCorrectlyAndIdempotently() { |
| 0000000 | 98 | let input = "\ |
| 0000000 | 99 | module test |
| 0000000 | 100 | |
| 0000000 | 101 | enum Color = |
| 0000000 | 102 | | Red |
| 0000000 | 103 | | Green |
| 0000000 | 104 | |
| 0000000 | 105 | fun isRed(self) -> Bool = |
| 0000000 | 106 | match self |
| 0000000 | 107 | Red => True |
| 0000000 | 108 | Green => False |
| 0000000 | 109 | |
| 0000000 | 110 | fun abs(x: Int) -> Int = |
| 0000000 | 111 | x < 0 ? -x : x |
| 0000000 | 112 | "; |
| 0000000 | 113 | let once = formatSource(input).expect("first pass"); |
| 0000000 | 114 | assert!(once.contains("x < 0 ? -x : x"), "got: {once}"); |
| 0000000 | 115 | assert!(once.contains("| Red"), "got: {once}"); |
| 0000000 | 116 | let twice = formatSource(&once).expect("second pass"); |
| 0000000 | 117 | assert_eq!(once, twice, "formatting should be idempotent"); |
| 0000000 | 118 | } |
| 0000000 | 119 | |
| 0000000 | 120 | /// Regression test: a standalone comment between two statements/methods used |
| 0000000 | 121 | /// to glue onto the end of the previous line on a SECOND formatting pass |
| 0000000 | 122 | /// (the naive fix, `@prepend_hardline`, would instead wrongly force even a |
| 0000000 | 123 | /// TRAILING same-line comment onto its own line) — `@prepend_input_softline` |
| 0000000 | 124 | /// preserves whichever the source actually had. |
| 0000000 | 125 | #[test] |
| 0000000 | 126 | fn commentsFormatIdempotently() { |
| 0000000 | 127 | let input = "\ |
| 0000000 | 128 | fun abs(self) -> Int = |
| 0000000 | 129 | self < 0 ? -self : self |
| 0000000 | 130 | |
| 0000000 | 131 | # a standalone comment between two functions |
| 0000000 | 132 | fun square(self) -> Int = |
| 0000000 | 133 | self * self # a trailing comment |
| 0000000 | 134 | "; |
| 0000000 | 135 | let once = formatSource(input).expect("first pass"); |
| 0000000 | 136 | let twice = formatSource(&once).expect("second pass"); |
| 0000000 | 137 | assert_eq!(once, twice, "formatting should be idempotent"); |
| 0000000 | 138 | } |