mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(formatter_graphql): update oxc-graphql-parser 0.0.4 (#24039)
Updated from 0.0.1 -> 0.0.4. As part of the process of improving performance, all CST were rewritten as AST. Whilst this involved a major rewrite for the formatter, it had no impact on the user side.
This commit is contained in:
1 parent
f25694147f
commit
0e5bcc9a8e
12 files changed
+630
-698
No files matched your search
Generated
+59
-89
@@ -262,7 +262,7 @@ version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -282,9 +282,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.3"
|
||||
version = "0.16.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||
checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
@@ -336,12 +336,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "cow-utils"
|
||||
version = "0.1.3"
|
||||
@@ -651,7 +645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1106,9 +1100,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.26"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d"
|
||||
checksum = "fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
@@ -1179,9 +1173,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "json-escape-simd"
|
||||
version = "3.0.2"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e770254dd7802184595b1d30da2a15cb72569e2aca2b177aef8d22eac8a693"
|
||||
checksum = "2a18a0eb3c0a783620d2ae8cdda9590aae18b1608964ee9c7c43162562786424"
|
||||
|
||||
[[package]]
|
||||
name = "json-strip-comments"
|
||||
@@ -1352,7 +1346,7 @@ dependencies = [
|
||||
"napi-build",
|
||||
"napi-sys",
|
||||
"nohash-hasher",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -1442,7 +1436,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1557,7 +1551,7 @@ checksum = "2b87743c2f6963036971ca587539cd9235693d953dc018a734014241afb20d5b"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"postcard",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -1573,12 +1567,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxc-graphql-parser"
|
||||
version = "0.0.1"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff384edd4b618b5144b758ed42d03afa22c6aa0c263621fd8476b5990523c1f"
|
||||
checksum = "1680d78ac0c54073910aedbedfbcd9cb86b1b1f897e42af87b351186717a8736"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"rowan",
|
||||
"oxc_allocator",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -1640,7 +1634,7 @@ dependencies = [
|
||||
"oxc_estree",
|
||||
"quickcheck",
|
||||
"rand",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -1700,7 +1694,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
@@ -1740,7 +1734,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_tasks_common",
|
||||
"oxc_transformer",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -1754,7 +1748,7 @@ dependencies = [
|
||||
"oxc_index",
|
||||
"oxc_syntax",
|
||||
"petgraph",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1777,7 +1771,7 @@ dependencies = [
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1787,7 +1781,7 @@ dependencies = [
|
||||
"cow-utils",
|
||||
"oxc-browserslist",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1814,7 +1808,7 @@ dependencies = [
|
||||
"ignore",
|
||||
"oxc-schemars",
|
||||
"oxc_diagnostics",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
@@ -1839,7 +1833,7 @@ dependencies = [
|
||||
"phf",
|
||||
"pico-args",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"saphyr",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1923,7 +1917,7 @@ dependencies = [
|
||||
"oxc_tasks_common",
|
||||
"phf",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
@@ -1938,7 +1932,7 @@ dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_data_structures",
|
||||
"oxc_span",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde_json",
|
||||
"unicode-width",
|
||||
]
|
||||
@@ -2016,7 +2010,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2025,7 +2019,7 @@ version = "0.138.0"
|
||||
dependencies = [
|
||||
"oxc_ast",
|
||||
"oxc_span",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2035,7 +2029,7 @@ dependencies = [
|
||||
"cow-utils",
|
||||
"futures",
|
||||
"papaya",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2089,7 +2083,7 @@ dependencies = [
|
||||
"phf",
|
||||
"rayon",
|
||||
"rust-lapper",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"self_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2107,7 +2101,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"project-root",
|
||||
"quote",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -2136,7 +2130,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2165,7 +2159,7 @@ dependencies = [
|
||||
"oxc_syntax",
|
||||
"oxc_traverse",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2204,7 +2198,7 @@ dependencies = [
|
||||
"oxc_tasks_common",
|
||||
"oxc_transformer_plugins",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"similar-asserts",
|
||||
]
|
||||
|
||||
@@ -2242,7 +2236,7 @@ dependencies = [
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
@@ -2260,7 +2254,7 @@ dependencies = [
|
||||
"oxc_estree_tokens",
|
||||
"oxc_napi",
|
||||
"oxc_str",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2277,7 +2271,7 @@ dependencies = [
|
||||
"oxc_linter",
|
||||
"oxc_napi",
|
||||
"oxc_transformer_plugins",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -2299,7 +2293,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_tasks_common",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde_json",
|
||||
"similar 3.1.1",
|
||||
"walkdir",
|
||||
@@ -2320,7 +2314,7 @@ dependencies = [
|
||||
"oxc_semantic",
|
||||
"oxc_span",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2335,7 +2329,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"phf",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"unicode-id-start",
|
||||
]
|
||||
|
||||
@@ -2355,7 +2349,7 @@ dependencies = [
|
||||
"nodejs-built-in-modules",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"rustix",
|
||||
"self_cell",
|
||||
"serde",
|
||||
@@ -2399,7 +2393,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"self_cell",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
@@ -2416,7 +2410,7 @@ dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -2489,7 +2483,7 @@ dependencies = [
|
||||
"oxc_semantic",
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2533,7 +2527,7 @@ dependencies = [
|
||||
"oxc",
|
||||
"oxc_napi",
|
||||
"oxc_sourcemap",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2564,7 +2558,7 @@ dependencies = [
|
||||
"oxc_syntax",
|
||||
"oxc_traverse",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -2593,7 +2587,7 @@ dependencies = [
|
||||
"oxc_transformer",
|
||||
"oxc_traverse",
|
||||
"pico-args",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2610,7 +2604,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"oxc_syntax",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2657,7 +2651,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"phf",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simdutf8",
|
||||
@@ -2698,7 +2692,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_str",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
@@ -3054,18 +3048,6 @@ dependencies = [
|
||||
"str_indices",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rowan"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21"
|
||||
dependencies = [
|
||||
"countme",
|
||||
"hashbrown 0.14.5",
|
||||
"rustc-hash 1.1.0",
|
||||
"text-size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rulegen"
|
||||
version = "0.0.0"
|
||||
@@ -3081,7 +3063,7 @@ dependencies = [
|
||||
"oxc_span",
|
||||
"oxc_tasks_common",
|
||||
"proc-macro2",
|
||||
"rustc-hash 2.1.2",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
@@ -3096,12 +3078,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
@@ -3118,7 +3094,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3138,9 +3114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
@@ -3213,7 +3189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3497,15 +3473,9 @@ dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-size"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.2"
|
||||
@@ -3548,9 +3518,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.51"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327"
|
||||
checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
@@ -3568,9 +3538,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.30"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935"
|
||||
checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -3974,7 +3944,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ oxc_sourcemap = "8.0.2" # Source map generation
|
||||
|
||||
#
|
||||
allocator-api2 = "=0.2.21" # Allocator trait
|
||||
oxc-graphql-parser = "0.0.1" # GraphQL parser (lossless CST; apollo-parser 0.8.6 fork with opt-in 2025 descriptions + legacy fragment variables)
|
||||
oxc-graphql-parser = "0.0.4" # GraphQL parser (direct AST; apollo-parser 0.8.6 fork with opt-in 2025 descriptions + legacy fragment variables)
|
||||
base64 = "0.22.1" # Base64 encoding
|
||||
bitflags = "2.10.0" # Bitflag types
|
||||
bpaf = "0.9.20" # CLI parser
|
||||
|
||||
@@ -33,26 +33,31 @@ For Prettier(v3.9)'s graphql-js v17.x syntax support, see the Roadmap below.
|
||||
|
||||
`format()` / `format_to_ir()` return `Err` whenever they cannot produce output they can stand behind:
|
||||
|
||||
- oxc-graphql-parser is error-tolerant (returns a CST even for invalid input),
|
||||
but any parse error bails out; never format a broken CST
|
||||
- print-stage internal errors are also `Err`
|
||||
- `oxc-graphql-parser` is error-tolerant (returns an AST even for invalid input), but any parse error bails out; never format a broken AST.
|
||||
- Several printer shortcuts (e.g. `close_delim_start`) are sound only under this guarantee
|
||||
- The caller (oxfmt) decides what happens next
|
||||
(diagnostics for standalone files, template-as-is for embedded)
|
||||
- Diagnostics for standalone files, template-as-is for embedded
|
||||
|
||||
### Comments
|
||||
|
||||
`graphql-js` does not attach comments to the AST;
|
||||
Prettier collects them from the token stream and attaches leading/trailing/dangling per node.
|
||||
|
||||
This crate instead collects `COMMENT` trivia tokens from the CST into a positional cursor (`src/comments.rs`, mirrors `oxc_formatter_json`) and flushes them at sequence items, closing delimiters, and document tail.
|
||||
This crate instead collects comment spans into a positional cursor and flushes them at sequence items, closing delimiters, and document tail.
|
||||
|
||||
`oxc-graphql-parser` attaches pending trivia to whichever node is open when the next significant token is consumed, so node ranges may start at a preceding comment.
|
||||
All layout decisions use significant-token positions (`sig_start` / `sig_end` in `src/print/mod.rs`), never `text_range()` directly.
|
||||
Node spans are significant-token spans (trivia is never included), so layout decisions use them directly.
|
||||
|
||||
All span bridging lives in `src/print/span.rs`:
|
||||
|
||||
- the usize→u32 conversion (`to_span`), the crate-local `Spanned` trait
|
||||
- (the `oxc_span::Span` facade over the parser's own `span` fields / `span()` accessors)
|
||||
- and closing-delimiter derivation: `close_delim_start` for containers whose span ends one past their `}` / `]`
|
||||
- `find_close_after` for paren lists that have no wrapper node
|
||||
|
||||
### Strings
|
||||
|
||||
Prettier prints `StringValue` from `graphql-js`'s _cooked_ value and re-encodes it.
|
||||
`oxc-graphql-parser` hands us raw source, so `src/print/value.rs` reimplements:
|
||||
`oxc-graphql-parser`'s `StringValue.value` is cooked but not to spec (no block-string dedent / blank-line trimming, no surrogate pairing), so `src/print/string.rs` cooks from `raw` itself:
|
||||
|
||||
- the GraphQL spec `BlockStringValue` algorithm (dedent + blank-line trimming)
|
||||
- escape decoding for regular strings (incl. surrogate pairs)
|
||||
@@ -83,7 +88,7 @@ Run `clippy` and resolve all warnings.
|
||||
|
||||
### Fixtures tests
|
||||
|
||||
Snapshot tests driven by fixture files under `tests/fixtures/format/`,
|
||||
Snapshot tests driven by fixture files under `tests/fixtures/graphql/`,
|
||||
covering what the Prettier conformance suite does not:
|
||||
`# oxfmt-ignore` suppression, blank-line runs inside block strings,
|
||||
string escape re-encoding (incl. the `\r` divergence), empty `[]` / `{}` values,
|
||||
@@ -139,7 +144,7 @@ npx prettier --parser=graphql [filename]
|
||||
|
||||
A good large real-world stress input is GitHub's public GraphQL schema (~72k lines).
|
||||
It is too large and third-party to commit as a fixture,
|
||||
bug-catching shapes are distilled into `tests/fixtures/format/implements-width.graphql`):
|
||||
bug-catching shapes are distilled into `tests/fixtures/graphql/implements-width.graphql`):
|
||||
|
||||
```sh
|
||||
curl -sL https://docs.github.com/public/fpt/schema.docs.graphql -o /tmp/github-schema.graphql
|
||||
@@ -159,7 +164,7 @@ These are in Prettier's unreleased changelog (main has them, next stable will).
|
||||
|
||||
- [#18582](https://github.com/prettier/prettier/blob/main/changelog_unreleased/graphql/18582.md):
|
||||
allow `implements` lists to break. We currently implement never break
|
||||
(see `tests/fixtures/format/implements-width.graphql`),
|
||||
(see `tests/fixtures/graphql/implements-width.graphql`),
|
||||
so this is a layout divergence that will become incompatible, not a new-syntax item, lands sooner.
|
||||
- [#19171](https://github.com/prettier/prettier/blob/main/changelog_unreleased/graphql/19171.md):
|
||||
directives on directive definitions (`directive @a @b on QUERY`) + `extend directive`.
|
||||
|
||||
@@ -5,26 +5,29 @@ use oxc_formatter_core::{
|
||||
builders::{hard_line_break, text},
|
||||
write,
|
||||
};
|
||||
use oxc_graphql_parser::{Parser, SyntaxKind, cst, cst::CstNode};
|
||||
use oxc_graphql_parser::{Parser, ast::Document as GraphQLDocument};
|
||||
use oxc_span::Span;
|
||||
|
||||
use crate::{
|
||||
context::GraphqlFormatContext,
|
||||
options::GraphqlFormatOptions,
|
||||
print::{self, GraphqlFormatter},
|
||||
print::{self, GraphqlFormatter, to_span},
|
||||
};
|
||||
|
||||
/// Parse `source_text` as a GraphQL document and build its formatter IR.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`OxcDiagnostic`] when the parse produces any error.
|
||||
/// oxc-graphql-parser is error-tolerant (it returns a CST even for invalid input),
|
||||
/// but a CST with errors cannot be formatted faithfully, so a single error is enough to bail out.
|
||||
/// `oxc-graphql-parser` is error-tolerant (it still returns an AST for invalid input),
|
||||
/// but an AST with errors cannot be formatted faithfully, so a single error is enough to bail out.
|
||||
pub fn format<'a>(
|
||||
allocator: &'a Allocator,
|
||||
source_text: &str,
|
||||
options: GraphqlFormatOptions,
|
||||
) -> Result<Formatted<'a, GraphqlFormatContext<'a>>, OxcDiagnostic> {
|
||||
// Checked against the original input: `parse_document` strips the BOM
|
||||
// before copying into the arena, so spans and gap scans never see it.
|
||||
let has_bom = source_text.starts_with('\u{feff}');
|
||||
let (document, source, comments) = parse_document(allocator, source_text)?;
|
||||
|
||||
let context = GraphqlFormatContext::new(options, source, comments);
|
||||
@@ -32,8 +35,7 @@ pub fn format<'a>(
|
||||
// TODO: Use `with_capacity` for perf, like `oxc_formatter` does
|
||||
let mut buffer = VecBuffer::new(&mut state);
|
||||
|
||||
let has_bom = source.starts_with('\u{feff}');
|
||||
write!(&mut buffer, FormatGraphqlRoot { document: &document, has_bom });
|
||||
write!(&mut buffer, FormatGraphqlRoot { document, has_bom });
|
||||
|
||||
let elements = buffer.into_vec();
|
||||
let context = state.into_context();
|
||||
@@ -68,63 +70,54 @@ pub fn format_to_ir<'a>(
|
||||
let mut state = FormatState::new(context, allocator);
|
||||
let mut buffer = VecBuffer::new(&mut state);
|
||||
|
||||
write!(&mut buffer, FormatGraphqlEmbedded { document: &document });
|
||||
write!(&mut buffer, FormatGraphqlEmbedded { document });
|
||||
|
||||
// GraphQL never collects Tailwind classes.
|
||||
Ok(EmbeddedIr { ir: buffer.into_vec(), tailwind_classes: Vec::new() })
|
||||
}
|
||||
|
||||
/// Parse the source into a CST and collect comment trivia,
|
||||
/// Parse the source into a direct AST and collect comment trivia,
|
||||
/// bailing out on any parse error.
|
||||
///
|
||||
/// Copies the source into the arena so every slice taken from it carries `'a`.
|
||||
/// Copies the source into the arena so every slice taken from it carries `'a`,
|
||||
/// stripping any BOM first so offset-based scans never see it
|
||||
/// (the caller re-emits it from `has_bom`).
|
||||
fn parse_document<'a>(
|
||||
allocator: &'a Allocator,
|
||||
source_text: &str,
|
||||
) -> Result<(cst::Document, &'a str, &'a [Span]), OxcDiagnostic> {
|
||||
let source: &'a str = allocator.alloc_str(source_text);
|
||||
) -> Result<(&'a GraphQLDocument<'a>, &'a str, &'a [Span]), OxcDiagnostic> {
|
||||
let source: &'a str =
|
||||
allocator.alloc_str(source_text.strip_prefix('\u{feff}').unwrap_or(source_text));
|
||||
|
||||
// Opt-in graphql-js 16 syntax
|
||||
let tree = Parser::new(source)
|
||||
let ast = Parser::new(allocator, source)
|
||||
.allow_executable_descriptions(true)
|
||||
.allow_legacy_fragment_variables(true)
|
||||
.parse();
|
||||
if let Some(error) = tree.errors().next() {
|
||||
if let Some(error) = ast.errors().next() {
|
||||
let start = u32::try_from(error.index()).unwrap_or(0);
|
||||
let len = u32::try_from(error.data().len()).unwrap_or(0);
|
||||
return Err(OxcDiagnostic::error(format!("Syntax error: {}", error.message()))
|
||||
.with_label(Span::sized(start, len)));
|
||||
}
|
||||
|
||||
let document = tree.document();
|
||||
let comments: &'a [Span] =
|
||||
ArenaVec::from_iter_in(ast.comments().iter().copied().map(to_span), &allocator)
|
||||
.into_arena_slice();
|
||||
|
||||
// Collect comment trivia in document order.
|
||||
// GraphQL comments are `# ...` line comments.
|
||||
let comments: &'a [Span] = ArenaVec::from_iter_in(
|
||||
document
|
||||
.syntax()
|
||||
.descendants_with_tokens()
|
||||
.filter_map(oxc_graphql_parser::SyntaxElement::into_token)
|
||||
.filter(|t| t.kind() == SyntaxKind::COMMENT)
|
||||
.map(|t| {
|
||||
let range = t.text_range();
|
||||
Span::new(range.start().into(), range.end().into())
|
||||
}),
|
||||
&allocator,
|
||||
)
|
||||
.into_arena_slice();
|
||||
let document = allocator.alloc(ast.into_root());
|
||||
|
||||
Ok((document, source, comments))
|
||||
}
|
||||
|
||||
/// Emits the document's definitions followed by any trailing comments,
|
||||
/// and the final newline.
|
||||
struct FormatGraphqlRoot<'b> {
|
||||
document: &'b cst::Document,
|
||||
struct FormatGraphqlRoot<'a> {
|
||||
document: &'a GraphQLDocument<'a>,
|
||||
has_bom: bool,
|
||||
}
|
||||
|
||||
impl<'a> Format<'a, GraphqlFormatContext<'a>> for FormatGraphqlRoot<'_> {
|
||||
impl<'a> Format<'a, GraphqlFormatContext<'a>> for FormatGraphqlRoot<'a> {
|
||||
fn fmt(&self, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
if self.has_bom {
|
||||
write!(f, text("\u{feff}"));
|
||||
@@ -139,11 +132,11 @@ impl<'a> Format<'a, GraphqlFormatContext<'a>> for FormatGraphqlRoot<'_> {
|
||||
|
||||
/// Emits the document's definitions and trailing comments only;
|
||||
/// no BOM, no final newline (the parent document owns the surrounding layout).
|
||||
struct FormatGraphqlEmbedded<'b> {
|
||||
document: &'b cst::Document,
|
||||
struct FormatGraphqlEmbedded<'a> {
|
||||
document: &'a GraphQLDocument<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Format<'a, GraphqlFormatContext<'a>> for FormatGraphqlEmbedded<'_> {
|
||||
impl<'a> Format<'a, GraphqlFormatContext<'a>> for FormatGraphqlEmbedded<'a> {
|
||||
fn fmt(&self, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
print::write_document(self.document, f);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Shared printers: names, descriptions, directives, argument lists,
|
||||
//! variable definitions, types, and input value definitions.
|
||||
|
||||
use oxc_graphql_parser::{cst, cst::CstNode};
|
||||
|
||||
use oxc_formatter_core::{
|
||||
Buffer, Format,
|
||||
builders::{
|
||||
@@ -11,6 +9,10 @@ use oxc_formatter_core::{
|
||||
},
|
||||
write,
|
||||
};
|
||||
use oxc_graphql_parser::ast::{
|
||||
Argument, Directive, InputValueDefinition, Name, NamedType, StringValue, Type, Value, Variable,
|
||||
VariableDefinition,
|
||||
};
|
||||
|
||||
use crate::comments::{
|
||||
flush_leading_comments, flush_trailing_inside_comments, write_dangling_comments,
|
||||
@@ -18,39 +20,36 @@ use crate::comments::{
|
||||
|
||||
use super::{
|
||||
GraphqlFormatter, SeparatorKind, format_with,
|
||||
sig::{closing_token_start, node_text},
|
||||
span::{Spanned, find_close_after, to_span},
|
||||
string, value, write_sequence,
|
||||
};
|
||||
|
||||
pub fn write_name(name: &cst::Name, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
write!(f, text(node_text(f, name.syntax())));
|
||||
}
|
||||
|
||||
/// Whether a description string is a block string (`"""..."""`).
|
||||
fn is_block_string(sv: &cst::StringValue, f: &GraphqlFormatter<'_, '_>) -> bool {
|
||||
node_text(f, sv.syntax()).starts_with("\"\"\"")
|
||||
pub(super) fn write_name<'a>(name: &Name<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write!(f, text(name.value));
|
||||
}
|
||||
|
||||
/// Description followed by a hard line break (the default placement).
|
||||
pub fn write_description(description: Option<cst::Description>, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
let Some(description) = description else { return };
|
||||
let Some(sv) = description.string_value() else { return };
|
||||
string::write_string_value(&sv, f);
|
||||
pub(super) fn write_description<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let Some(sv) = description else { return };
|
||||
string::write_string_value(sv, f);
|
||||
write!(f, hard_line_break());
|
||||
}
|
||||
|
||||
/// Description placement for `InputValueDefinition`:
|
||||
/// non-block descriptions are followed by a soft line (they may stay inline in an
|
||||
/// argument list), block descriptions by a hard line break.
|
||||
/// Mirrors Prettier's `printDescription`.
|
||||
fn write_description_input_value(
|
||||
description: Option<cst::Description>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
/// Description placement for `InputValueDefinition`:
|
||||
/// non-block descriptions are followed by a soft line
|
||||
/// (they may stay inline in an argument list),
|
||||
/// block descriptions by a hard line break.
|
||||
fn write_description_input_value<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let Some(description) = description else { return };
|
||||
let Some(sv) = description.string_value() else { return };
|
||||
let is_block = is_block_string(&sv, f);
|
||||
string::write_string_value(&sv, f);
|
||||
let Some(sv) = description else { return };
|
||||
let is_block = sv.block;
|
||||
string::write_string_value(sv, f);
|
||||
if is_block {
|
||||
write!(f, hard_line_break());
|
||||
} else {
|
||||
@@ -60,26 +59,24 @@ fn write_description_input_value(
|
||||
|
||||
/// Directive placement style. Mirrors Prettier's `printDirectives`.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum DirectivesStyle {
|
||||
pub(super) enum DirectivesStyle {
|
||||
/// On `OperationDefinition` / `FragmentDefinition`: `group([line, joined])`.
|
||||
Definition,
|
||||
/// Everywhere else: `[" ", group(indent([softline, joined]))]`.
|
||||
Attached,
|
||||
}
|
||||
|
||||
pub fn write_directives<'a>(
|
||||
directives: Option<cst::Directives>,
|
||||
pub(super) fn write_directives<'a>(
|
||||
directives: &'a [Directive<'a>],
|
||||
style: DirectivesStyle,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let Some(directives) = directives else { return };
|
||||
let list: Vec<cst::Directive> = directives.directives().collect();
|
||||
if list.is_empty() {
|
||||
if directives.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let joined = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
for (i, directive) in list.iter().enumerate() {
|
||||
let joined = format_with(move |f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
for (i, directive) in directives.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, soft_line_break_or_space());
|
||||
}
|
||||
@@ -105,19 +102,17 @@ pub fn write_directives<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn write_directive(directive: &cst::Directive, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
fn write_directive<'a>(directive: &'a Directive<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write!(f, "@");
|
||||
if let Some(name) = directive.name() {
|
||||
write_name(&name, f);
|
||||
}
|
||||
write_arguments(directive.arguments(), f);
|
||||
write_name(&directive.name, f);
|
||||
write_arguments(&directive.arguments, f);
|
||||
}
|
||||
|
||||
/// Close an empty delimited container (`[]`, `{}`, `{ }` selection set): drains any comments
|
||||
/// pending before `close_start`, emits them block-indented when present, then writes `close`.
|
||||
/// The caller has already written the opening delimiter. Sibling of [`write_paren_list`] /
|
||||
/// `write_braced_body` for the empty case.
|
||||
pub fn write_empty_delimited<'a>(
|
||||
pub(super) fn write_empty_delimited<'a>(
|
||||
close_start: u32,
|
||||
close: &'static str,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
@@ -136,17 +131,22 @@ pub fn write_empty_delimited<'a>(
|
||||
|
||||
/// A parenthesized, comma-soft-separated list:
|
||||
/// `group(["(", indent([softline, join(...)]), softline, ")"])`.
|
||||
/// Comments pending before `)` are flushed inside the group body.
|
||||
pub fn write_paren_list<'a, T, F>(
|
||||
/// Emits nothing for an empty list.
|
||||
/// Comments pending before `)` are flushed inside the group body;
|
||||
/// the `)` position is derived by scanning past trivia
|
||||
/// from the last item's end (paren lists have no wrapper node carrying it).
|
||||
pub(super) fn write_paren_list<'a, T, F>(
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
items: &[T],
|
||||
r_paren_start: u32,
|
||||
preserve_blank: bool,
|
||||
write_item: F,
|
||||
) where
|
||||
T: CstNode,
|
||||
T: Spanned,
|
||||
F: Fn(usize, &mut GraphqlFormatter<'_, 'a>),
|
||||
{
|
||||
let Some(last) = items.last() else { return };
|
||||
let r_paren_start = find_close_after(&f.context().source_text(), last.span().end, b')');
|
||||
|
||||
write!(f, "(");
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
let last_end =
|
||||
@@ -160,119 +160,87 @@ pub fn write_paren_list<'a, T, F>(
|
||||
|
||||
/// `(arg: value, ...)` on fields, directives, and fragment spreads.
|
||||
/// Blank lines between arguments are preserved (Prettier routes these through `printSequence`).
|
||||
pub fn write_arguments(arguments: Option<cst::Arguments>, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
let Some(arguments) = arguments else { return };
|
||||
let items: Vec<cst::Argument> = arguments.arguments().collect();
|
||||
if items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let r_paren = closing_token_start(arguments.r_paren_token(), arguments.syntax());
|
||||
write_paren_list(f, &items, r_paren, true, |i, f| {
|
||||
let argument = &items[i];
|
||||
if let Some(name) = argument.name() {
|
||||
write_name(&name, f);
|
||||
}
|
||||
pub(super) fn write_arguments<'a>(arguments: &'a [Argument<'a>], f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write_paren_list(f, arguments, true, |i, f| {
|
||||
let argument = &arguments[i];
|
||||
write_name(&argument.name, f);
|
||||
write!(f, ": ");
|
||||
if let Some(v) = argument.value() {
|
||||
value::write_value(&v, f);
|
||||
if let Some(v) = argument.value.as_ref() {
|
||||
value::write_value(v, f);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// `($var: Type = default, ...)` on operations.
|
||||
/// No blank-line preservation (Prettier uses a plain `path.map` here).
|
||||
pub fn write_variable_definitions(
|
||||
variable_definitions: Option<cst::VariableDefinitions>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
pub(super) fn write_variable_definitions<'a>(
|
||||
variable_definitions: &'a [VariableDefinition<'a>],
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let Some(variable_definitions) = variable_definitions else { return };
|
||||
let items: Vec<cst::VariableDefinition> = variable_definitions.variable_definitions().collect();
|
||||
if items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let r_paren =
|
||||
closing_token_start(variable_definitions.r_paren_token(), variable_definitions.syntax());
|
||||
write_paren_list(f, &items, r_paren, false, |i, f| {
|
||||
write_variable_definition(&items[i], f);
|
||||
write_paren_list(f, variable_definitions, false, |i, f| {
|
||||
write_variable_definition(&variable_definitions[i], f);
|
||||
});
|
||||
}
|
||||
|
||||
fn write_variable_definition(
|
||||
variable_definition: &cst::VariableDefinition,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
fn write_variable_definition<'a>(
|
||||
variable_definition: &'a VariableDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
write_description(variable_definition.description(), f);
|
||||
if let Some(variable) = variable_definition.variable() {
|
||||
write_variable(&variable, f);
|
||||
}
|
||||
write_description(variable_definition.description.as_ref(), f);
|
||||
write_variable(&variable_definition.variable, f);
|
||||
write!(f, ": ");
|
||||
if let Some(ty) = variable_definition.ty() {
|
||||
write_type(&ty, f);
|
||||
if let Some(ty) = variable_definition.ty.as_ref() {
|
||||
write_type(ty, f);
|
||||
}
|
||||
write_default_value(variable_definition.default_value(), f);
|
||||
write_directives(variable_definition.directives(), DirectivesStyle::Attached, f);
|
||||
write_default_value(variable_definition.default_value.as_ref(), f);
|
||||
write_directives(&variable_definition.directives, DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
pub fn write_variable(variable: &cst::Variable, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
pub(super) fn write_variable<'a>(variable: &Variable<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write!(f, "$");
|
||||
if let Some(name) = variable.name() {
|
||||
write_name(&name, f);
|
||||
}
|
||||
write_name(&variable.name, f);
|
||||
}
|
||||
|
||||
fn write_default_value(default_value: Option<cst::DefaultValue>, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
let Some(default_value) = default_value else { return };
|
||||
fn write_default_value<'a>(default_value: Option<&'a Value<'a>>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let Some(v) = default_value else { return };
|
||||
write!(f, " = ");
|
||||
if let Some(v) = default_value.value() {
|
||||
value::write_value(&v, f);
|
||||
}
|
||||
value::write_value(v, f);
|
||||
}
|
||||
|
||||
pub fn write_type(ty: &cst::Type, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
pub(super) fn write_type<'a>(ty: &Type<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
match ty {
|
||||
cst::Type::NamedType(named) => write_named_type(named, f),
|
||||
cst::Type::ListType(list) => write_list_type(list, f),
|
||||
cst::Type::NonNullType(non_null) => {
|
||||
if let Some(named) = non_null.named_type() {
|
||||
write_named_type(&named, f);
|
||||
} else if let Some(list) = non_null.list_type() {
|
||||
write_list_type(&list, f);
|
||||
}
|
||||
Type::Named(named) => write_named_type(named, f),
|
||||
Type::List(list) => {
|
||||
write!(f, "[");
|
||||
write_type(&list.ty, f);
|
||||
write!(f, "]");
|
||||
}
|
||||
Type::NonNull(non_null) => {
|
||||
write_type(&non_null.ty, f);
|
||||
write!(f, "!");
|
||||
}
|
||||
Type::Missing(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_named_type(named: &cst::NamedType, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
if let Some(name) = named.name() {
|
||||
write_name(&name, f);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_list_type(list: &cst::ListType, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
write!(f, "[");
|
||||
if let Some(inner) = list.ty() {
|
||||
write_type(&inner, f);
|
||||
}
|
||||
write!(f, "]");
|
||||
pub(super) fn write_named_type<'a>(named: &NamedType<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write_name(&named.name, f);
|
||||
}
|
||||
|
||||
/// `name: Type = default @dir` (+ leading description) inside
|
||||
/// `ArgumentsDefinition` / `InputFieldsDefinition`.
|
||||
pub fn write_input_value_definition(
|
||||
input_value: &cst::InputValueDefinition,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
pub(super) fn write_input_value_definition<'a>(
|
||||
input_value: &'a InputValueDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
write_description_input_value(input_value.description(), f);
|
||||
if let Some(name) = input_value.name() {
|
||||
write_name(&name, f);
|
||||
}
|
||||
write_description_input_value(input_value.description.as_ref(), f);
|
||||
write_name(&input_value.name, f);
|
||||
write!(f, ": ");
|
||||
if let Some(ty) = input_value.ty() {
|
||||
write_type(&ty, f);
|
||||
if let Some(ty) = input_value.ty.as_ref() {
|
||||
write_type(ty, f);
|
||||
}
|
||||
write_default_value(input_value.default_value(), f);
|
||||
write_directives(input_value.directives(), DirectivesStyle::Attached, f);
|
||||
write_default_value(input_value.default_value.as_ref(), f);
|
||||
write_directives(&input_value.directives, DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
/// ` implements A & B`, mirroring Prettier 3.8.4's `printInterfaces`:
|
||||
@@ -282,18 +250,16 @@ pub fn write_input_value_definition(
|
||||
/// A `line` replaces the space only when a comment sits between two names;
|
||||
/// outside any group it always prints as a newline,
|
||||
/// so the list breaks exactly at the comment position (at zero extra indentation).
|
||||
pub fn write_implements_interfaces(
|
||||
implements: Option<cst::ImplementsInterfaces>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
pub(super) fn write_implements_interfaces<'a>(
|
||||
interfaces: &'a [NamedType<'a>],
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let Some(implements) = implements else { return };
|
||||
let names: Vec<cst::NamedType> = implements.named_types().collect();
|
||||
if names.is_empty() {
|
||||
if interfaces.is_empty() {
|
||||
return;
|
||||
}
|
||||
write!(f, " implements ");
|
||||
for (i, named) in names.iter().enumerate() {
|
||||
let start = super::sig::sig_start(named.syntax());
|
||||
for (i, named) in interfaces.iter().enumerate() {
|
||||
let start = to_span(named.name.span).start;
|
||||
if i > 0 {
|
||||
write!(f, " &");
|
||||
// Pending comments before this name = comments between the two names
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Document-level definition printers: operations, fragments, and the type system.
|
||||
|
||||
use oxc_graphql_parser::{cst, cst::CstNode};
|
||||
|
||||
use oxc_formatter_core::{
|
||||
Buffer,
|
||||
builders::{
|
||||
@@ -10,6 +8,11 @@ use oxc_formatter_core::{
|
||||
},
|
||||
write,
|
||||
};
|
||||
use oxc_graphql_parser::ast::{
|
||||
Definition, Directive, DirectiveDefinition, EnumValueDefinition, FieldDefinition,
|
||||
FragmentDefinition, InputValueDefinition, Name, NamedType, OperationDefinition, OperationType,
|
||||
RootOperationTypeDefinition, SchemaDefinition, SchemaExtension, StringValue,
|
||||
};
|
||||
|
||||
use crate::comments::flush_trailing_inside_comments;
|
||||
|
||||
@@ -17,226 +20,234 @@ use super::{
|
||||
GraphqlFormatter, SeparatorKind, common,
|
||||
common::DirectivesStyle,
|
||||
format_with, selection,
|
||||
sig::{closing_token_start, node_text},
|
||||
span::{Spanned, close_delim_start},
|
||||
write_sequence,
|
||||
};
|
||||
|
||||
pub fn write_definition(definition: &cst::Definition, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
pub(super) fn write_definition<'a>(
|
||||
definition: &'a Definition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
match definition {
|
||||
cst::Definition::OperationDefinition(d) => write_operation_definition(d, f),
|
||||
cst::Definition::FragmentDefinition(d) => write_fragment_definition(d, f),
|
||||
cst::Definition::DirectiveDefinition(d) => write_directive_definition(d, f),
|
||||
cst::Definition::SchemaDefinition(d) => write_schema_definition(d, f),
|
||||
cst::Definition::ScalarTypeDefinition(d) => {
|
||||
write_scalar_type(d.description(), false, d.name(), d.directives(), f);
|
||||
Definition::Operation(d) => write_operation_definition(d, f),
|
||||
Definition::Fragment(d) => write_fragment_definition(d, f),
|
||||
Definition::Directive(d) => write_directive_definition(d, f),
|
||||
Definition::Schema(d) => write_schema_definition(d, f),
|
||||
Definition::SchemaExtension(d) => write_schema_extension(d, f),
|
||||
Definition::ScalarType(d) => {
|
||||
write_scalar_type(d.description.as_ref(), false, &d.name, &d.directives, f);
|
||||
}
|
||||
cst::Definition::ScalarTypeExtension(d) => {
|
||||
write_scalar_type(None, true, d.name(), d.directives(), f);
|
||||
Definition::ScalarTypeExtension(d) => {
|
||||
write_scalar_type(None, true, &d.name, &d.directives, f);
|
||||
}
|
||||
cst::Definition::ObjectTypeDefinition(d) => write_object_like(
|
||||
d.description(),
|
||||
Definition::ObjectType(d) => write_object_like(
|
||||
d.description.as_ref(),
|
||||
false,
|
||||
"type",
|
||||
d.name(),
|
||||
d.implements_interfaces(),
|
||||
d.directives(),
|
||||
d.fields_definition(),
|
||||
&d.name,
|
||||
&d.interfaces,
|
||||
&d.directives,
|
||||
&d.fields,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::ObjectTypeExtension(d) => write_object_like(
|
||||
Definition::ObjectTypeExtension(d) => write_object_like(
|
||||
None,
|
||||
true,
|
||||
"type",
|
||||
d.name(),
|
||||
d.implements_interfaces(),
|
||||
d.directives(),
|
||||
d.fields_definition(),
|
||||
&d.name,
|
||||
&d.interfaces,
|
||||
&d.directives,
|
||||
&d.fields,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::InterfaceTypeDefinition(d) => write_object_like(
|
||||
d.description(),
|
||||
Definition::InterfaceType(d) => write_object_like(
|
||||
d.description.as_ref(),
|
||||
false,
|
||||
"interface",
|
||||
d.name(),
|
||||
d.implements_interfaces(),
|
||||
d.directives(),
|
||||
d.fields_definition(),
|
||||
&d.name,
|
||||
&d.interfaces,
|
||||
&d.directives,
|
||||
&d.fields,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::InterfaceTypeExtension(d) => write_object_like(
|
||||
Definition::InterfaceTypeExtension(d) => write_object_like(
|
||||
None,
|
||||
true,
|
||||
"interface",
|
||||
d.name(),
|
||||
d.implements_interfaces(),
|
||||
d.directives(),
|
||||
d.fields_definition(),
|
||||
&d.name,
|
||||
&d.interfaces,
|
||||
&d.directives,
|
||||
&d.fields,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::InputObjectTypeDefinition(d) => write_input_object_like(
|
||||
d.description(),
|
||||
Definition::InputObjectType(d) => write_input_object_like(
|
||||
d.description.as_ref(),
|
||||
false,
|
||||
d.name(),
|
||||
d.directives(),
|
||||
d.input_fields_definition(),
|
||||
&d.name,
|
||||
&d.directives,
|
||||
&d.fields,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::InputObjectTypeExtension(d) => write_input_object_like(
|
||||
Definition::InputObjectTypeExtension(d) => write_input_object_like(
|
||||
None,
|
||||
true,
|
||||
d.name(),
|
||||
d.directives(),
|
||||
d.input_fields_definition(),
|
||||
&d.name,
|
||||
&d.directives,
|
||||
&d.fields,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::UnionTypeDefinition(d) => write_union_like(
|
||||
d.description(),
|
||||
false,
|
||||
d.name(),
|
||||
d.directives(),
|
||||
d.union_member_types(),
|
||||
f,
|
||||
),
|
||||
cst::Definition::UnionTypeExtension(d) => {
|
||||
write_union_like(None, true, d.name(), d.directives(), d.union_member_types(), f);
|
||||
Definition::UnionType(d) => {
|
||||
write_union_like(d.description.as_ref(), false, &d.name, &d.directives, &d.members, f);
|
||||
}
|
||||
cst::Definition::EnumTypeDefinition(d) => write_enum_like(
|
||||
d.description(),
|
||||
Definition::UnionTypeExtension(d) => {
|
||||
write_union_like(None, true, &d.name, &d.directives, &d.members, f);
|
||||
}
|
||||
Definition::EnumType(d) => write_enum_like(
|
||||
d.description.as_ref(),
|
||||
false,
|
||||
d.name(),
|
||||
d.directives(),
|
||||
d.enum_values_definition(),
|
||||
&d.name,
|
||||
&d.directives,
|
||||
&d.values,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
),
|
||||
cst::Definition::EnumTypeExtension(d) => {
|
||||
write_enum_like(None, true, d.name(), d.directives(), d.enum_values_definition(), f);
|
||||
Definition::EnumTypeExtension(d) => {
|
||||
write_enum_like(
|
||||
None,
|
||||
true,
|
||||
&d.name,
|
||||
&d.directives,
|
||||
&d.values,
|
||||
close_delim_start(d.span),
|
||||
f,
|
||||
);
|
||||
}
|
||||
cst::Definition::SchemaExtension(d) => write_schema_extension(d, f),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_operation_definition(
|
||||
operation: &cst::OperationDefinition,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
fn operation_type_keyword(kind: OperationType) -> &'static str {
|
||||
match kind {
|
||||
OperationType::Query => "query",
|
||||
OperationType::Mutation => "mutation",
|
||||
OperationType::Subscription => "subscription",
|
||||
}
|
||||
}
|
||||
|
||||
fn write_operation_definition<'a>(
|
||||
operation: &'a OperationDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(operation.description(), f);
|
||||
common::write_description(operation.description.as_ref(), f);
|
||||
|
||||
let has_operation = operation.operation_type().is_some();
|
||||
let has_name = operation.name().is_some();
|
||||
// Direct-AST `operation_type` is always set (shorthand `{ ... }` parses as Query).
|
||||
// Shorthand means the operation's first significant token IS the selection set's `{`,
|
||||
// so their span starts coincide. Mirrors Prettier's
|
||||
// `locStart(node) !== locStart(node.selectionSet)` check.
|
||||
let is_shorthand =
|
||||
operation.selection_set.as_ref().is_some_and(|ss| ss.span.start == operation.span.start);
|
||||
let has_operation = !is_shorthand;
|
||||
let has_name = operation.name.is_some();
|
||||
|
||||
if let Some(operation_type) = operation.operation_type() {
|
||||
write!(f, text(node_text(f, operation_type.syntax())));
|
||||
if has_operation {
|
||||
write!(f, text(operation_type_keyword(operation.operation_type)));
|
||||
}
|
||||
if let Some(name) = operation.name() {
|
||||
if let Some(name) = operation.name.as_ref() {
|
||||
write!(f, space());
|
||||
common::write_name(&name, f);
|
||||
common::write_name(name, f);
|
||||
}
|
||||
let has_variable_definitions = operation
|
||||
.variable_definitions()
|
||||
.is_some_and(|vd| vd.variable_definitions().next().is_some());
|
||||
let has_variable_definitions = !operation.variable_definitions.is_empty();
|
||||
if has_operation && !has_name && has_variable_definitions {
|
||||
write!(f, space());
|
||||
}
|
||||
common::write_variable_definitions(operation.variable_definitions(), f);
|
||||
common::write_directives(operation.directives(), DirectivesStyle::Definition, f);
|
||||
common::write_variable_definitions(&operation.variable_definitions, f);
|
||||
common::write_directives(&operation.directives, DirectivesStyle::Definition, f);
|
||||
if has_operation || has_name {
|
||||
write!(f, space());
|
||||
}
|
||||
if let Some(selection_set) = operation.selection_set() {
|
||||
selection::write_selection_set(&selection_set, f);
|
||||
if let Some(selection_set) = operation.selection_set.as_ref() {
|
||||
selection::write_selection_set(selection_set, f);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fragment_definition(fragment: &cst::FragmentDefinition, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
common::write_description(fragment.description(), f);
|
||||
write!(f, "fragment ");
|
||||
if let Some(fragment_name) = fragment.fragment_name()
|
||||
&& let Some(name) = fragment_name.name()
|
||||
{
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
// Legacy fragment variables (graphql-js `allowLegacyFragmentVariables`).
|
||||
common::write_variable_definitions(fragment.variable_definitions(), f);
|
||||
if let Some(type_condition) = fragment.type_condition()
|
||||
&& let Some(named) = type_condition.named_type()
|
||||
{
|
||||
write!(f, " on ");
|
||||
common::write_named_type(&named, f);
|
||||
}
|
||||
common::write_directives(fragment.directives(), DirectivesStyle::Definition, f);
|
||||
write!(f, space());
|
||||
if let Some(selection_set) = fragment.selection_set() {
|
||||
selection::write_selection_set(&selection_set, f);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_directive_definition(
|
||||
directive: &cst::DirectiveDefinition,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
fn write_fragment_definition<'a>(
|
||||
fragment: &'a FragmentDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(directive.description(), f);
|
||||
write!(f, "directive @");
|
||||
if let Some(name) = directive.name() {
|
||||
common::write_name(&name, f);
|
||||
common::write_description(fragment.description.as_ref(), f);
|
||||
write!(f, "fragment ");
|
||||
common::write_name(&fragment.name, f);
|
||||
// Legacy fragment variables (graphql-js `allowLegacyFragmentVariables`).
|
||||
common::write_variable_definitions(&fragment.variable_definitions, f);
|
||||
write!(f, " on ");
|
||||
common::write_named_type(&fragment.type_condition, f);
|
||||
common::write_directives(&fragment.directives, DirectivesStyle::Definition, f);
|
||||
write!(f, space());
|
||||
if let Some(selection_set) = fragment.selection_set.as_ref() {
|
||||
selection::write_selection_set(selection_set, f);
|
||||
}
|
||||
write_arguments_definition(directive.arguments_definition(), f);
|
||||
if directive.repeatable_token().is_some() {
|
||||
}
|
||||
|
||||
fn write_directive_definition<'a>(
|
||||
directive: &'a DirectiveDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(directive.description.as_ref(), f);
|
||||
write!(f, "directive @");
|
||||
common::write_name(&directive.name, f);
|
||||
write_arguments_definition(&directive.arguments, f);
|
||||
if directive.repeatable {
|
||||
write!(f, " repeatable");
|
||||
}
|
||||
write!(f, " on ");
|
||||
if let Some(locations) = directive.directive_locations() {
|
||||
let list: Vec<cst::DirectiveLocation> = locations.directive_locations().collect();
|
||||
for (i, location) in list.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " | ");
|
||||
}
|
||||
write!(f, text(node_text(f, location.syntax())));
|
||||
let locations = directive.locations.as_slice();
|
||||
for (i, location) in locations.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " | ");
|
||||
}
|
||||
write!(f, text(location.name));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_schema_definition(schema: &cst::SchemaDefinition, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
common::write_description(schema.description(), f);
|
||||
fn write_schema_definition<'a>(schema: &'a SchemaDefinition<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
common::write_description(schema.description.as_ref(), f);
|
||||
write!(f, "schema");
|
||||
common::write_directives(schema.directives(), DirectivesStyle::Attached, f);
|
||||
common::write_directives(&schema.directives, DirectivesStyle::Attached, f);
|
||||
write!(f, " {");
|
||||
let operation_types: Vec<cst::RootOperationTypeDefinition> =
|
||||
schema.root_operation_type_definitions().collect();
|
||||
let operation_types = schema.root_operations.as_slice();
|
||||
if operation_types.is_empty() {
|
||||
write!(f, [hard_line_break(), "}"]);
|
||||
} else {
|
||||
let r_curly = closing_token_start(schema.r_curly_token(), schema.syntax());
|
||||
write_braced_body(f, &operation_types, r_curly, |i, f| {
|
||||
write_braced_body(f, operation_types, close_delim_start(schema.span), |i, f| {
|
||||
write_root_operation_type_definition(&operation_types[i], f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_schema_extension(schema: &cst::SchemaExtension, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
fn write_schema_extension<'a>(schema: &'a SchemaExtension<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write!(f, "extend schema");
|
||||
common::write_directives(schema.directives(), DirectivesStyle::Attached, f);
|
||||
let operation_types: Vec<cst::RootOperationTypeDefinition> =
|
||||
schema.root_operation_type_definitions().collect();
|
||||
common::write_directives(&schema.directives, DirectivesStyle::Attached, f);
|
||||
let operation_types = schema.root_operations.as_slice();
|
||||
if !operation_types.is_empty() {
|
||||
write!(f, " {");
|
||||
let r_curly = closing_token_start(schema.r_curly_token(), schema.syntax());
|
||||
write_braced_body(f, &operation_types, r_curly, |i, f| {
|
||||
write_braced_body(f, operation_types, close_delim_start(schema.span), |i, f| {
|
||||
write_root_operation_type_definition(&operation_types[i], f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_root_operation_type_definition(
|
||||
operation_type_definition: &cst::RootOperationTypeDefinition,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
fn write_root_operation_type_definition<'a>(
|
||||
def: &'a RootOperationTypeDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
if let Some(operation_type) = operation_type_definition.operation_type() {
|
||||
write!(f, text(node_text(f, operation_type.syntax())));
|
||||
}
|
||||
write!(f, text(operation_type_keyword(def.operation_type)));
|
||||
write!(f, ": ");
|
||||
if let Some(named) = operation_type_definition.named_type() {
|
||||
common::write_named_type(&named, f);
|
||||
}
|
||||
common::write_named_type(&def.named_type, f);
|
||||
}
|
||||
|
||||
/// The body of an already-opened `{`: a hard-line sequence (blank lines preserved),
|
||||
@@ -247,7 +258,7 @@ fn write_braced_body<'a, T, F>(
|
||||
r_curly_start: u32,
|
||||
write_item: F,
|
||||
) where
|
||||
T: CstNode,
|
||||
T: Spanned,
|
||||
F: Fn(usize, &mut GraphqlFormatter<'_, 'a>),
|
||||
{
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
@@ -259,169 +270,143 @@ fn write_braced_body<'a, T, F>(
|
||||
write!(f, [block_indent(&body), "}"]);
|
||||
}
|
||||
|
||||
fn write_scalar_type(
|
||||
description: Option<cst::Description>,
|
||||
fn write_scalar_type<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
extend: bool,
|
||||
name: Option<cst::Name>,
|
||||
directives: Option<cst::Directives>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
name: &Name<'a>,
|
||||
directives: &'a [Directive<'a>],
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(description, f);
|
||||
if extend {
|
||||
write!(f, "extend ");
|
||||
}
|
||||
write!(f, "scalar ");
|
||||
if let Some(name) = name {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_name(name, f);
|
||||
common::write_directives(directives, DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn write_object_like(
|
||||
description: Option<cst::Description>,
|
||||
fn write_object_like<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
extend: bool,
|
||||
keyword: &'static str,
|
||||
name: Option<cst::Name>,
|
||||
implements: Option<cst::ImplementsInterfaces>,
|
||||
directives: Option<cst::Directives>,
|
||||
fields: Option<cst::FieldsDefinition>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
name: &Name<'a>,
|
||||
interfaces: &'a [NamedType<'a>],
|
||||
directives: &'a [Directive<'a>],
|
||||
fields: &'a [FieldDefinition<'a>],
|
||||
r_curly_start: u32,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(description, f);
|
||||
if extend {
|
||||
write!(f, "extend ");
|
||||
}
|
||||
write!(f, [keyword, space()]);
|
||||
if let Some(name) = name {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_implements_interfaces(implements, f);
|
||||
common::write_name(name, f);
|
||||
common::write_implements_interfaces(interfaces, f);
|
||||
common::write_directives(directives, DirectivesStyle::Attached, f);
|
||||
|
||||
if let Some(fields) = fields {
|
||||
let items: Vec<cst::FieldDefinition> = fields.field_definitions().collect();
|
||||
if !items.is_empty() {
|
||||
write!(f, " {");
|
||||
let r_curly = closing_token_start(fields.r_curly_token(), fields.syntax());
|
||||
write_braced_body(f, &items, r_curly, |i, f| {
|
||||
write_field_definition(&items[i], f);
|
||||
});
|
||||
}
|
||||
if !fields.is_empty() {
|
||||
write!(f, " {");
|
||||
write_braced_body(f, fields, r_curly_start, |i, f| {
|
||||
write_field_definition(&fields[i], f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_input_object_like(
|
||||
description: Option<cst::Description>,
|
||||
fn write_input_object_like<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
extend: bool,
|
||||
name: Option<cst::Name>,
|
||||
directives: Option<cst::Directives>,
|
||||
fields: Option<cst::InputFieldsDefinition>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
name: &Name<'a>,
|
||||
directives: &'a [Directive<'a>],
|
||||
fields: &'a [InputValueDefinition<'a>],
|
||||
r_curly_start: u32,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(description, f);
|
||||
if extend {
|
||||
write!(f, "extend ");
|
||||
}
|
||||
write!(f, "input ");
|
||||
if let Some(name) = name {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_name(name, f);
|
||||
common::write_directives(directives, DirectivesStyle::Attached, f);
|
||||
|
||||
if let Some(fields) = fields {
|
||||
let items: Vec<cst::InputValueDefinition> = fields.input_value_definitions().collect();
|
||||
if !items.is_empty() {
|
||||
write!(f, " {");
|
||||
let r_curly = closing_token_start(fields.r_curly_token(), fields.syntax());
|
||||
write_braced_body(f, &items, r_curly, |i, f| {
|
||||
common::write_input_value_definition(&items[i], f);
|
||||
});
|
||||
}
|
||||
if !fields.is_empty() {
|
||||
write!(f, " {");
|
||||
write_braced_body(f, fields, r_curly_start, |i, f| {
|
||||
common::write_input_value_definition(&fields[i], f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_enum_like(
|
||||
description: Option<cst::Description>,
|
||||
fn write_enum_like<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
extend: bool,
|
||||
name: Option<cst::Name>,
|
||||
directives: Option<cst::Directives>,
|
||||
values: Option<cst::EnumValuesDefinition>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
name: &Name<'a>,
|
||||
directives: &'a [Directive<'a>],
|
||||
values: &'a [EnumValueDefinition<'a>],
|
||||
r_curly_start: u32,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
common::write_description(description, f);
|
||||
if extend {
|
||||
write!(f, "extend ");
|
||||
}
|
||||
write!(f, "enum ");
|
||||
if let Some(name) = name {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_name(name, f);
|
||||
common::write_directives(directives, DirectivesStyle::Attached, f);
|
||||
|
||||
if let Some(values) = values {
|
||||
let items: Vec<cst::EnumValueDefinition> = values.enum_value_definitions().collect();
|
||||
if !items.is_empty() {
|
||||
write!(f, " {");
|
||||
let r_curly = closing_token_start(values.r_curly_token(), values.syntax());
|
||||
write_braced_body(f, &items, r_curly, |i, f| {
|
||||
write_enum_value_definition(&items[i], f);
|
||||
});
|
||||
}
|
||||
if !values.is_empty() {
|
||||
write!(f, " {");
|
||||
write_braced_body(f, values, r_curly_start, |i, f| {
|
||||
write_enum_value_definition(&values[i], f);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_enum_value_definition(value: &cst::EnumValueDefinition, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
common::write_description(value.description(), f);
|
||||
if let Some(enum_value) = value.enum_value()
|
||||
&& let Some(name) = enum_value.name()
|
||||
{
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_directives(value.directives(), DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
// The CST handles are captured by `Fn` closures (callable multiple times),
|
||||
// so they are cloned inside; pass-by-value keeps the call sites uniform.
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
fn write_union_like<'a>(
|
||||
description: Option<cst::Description>,
|
||||
extend: bool,
|
||||
name: Option<cst::Name>,
|
||||
directives: Option<cst::Directives>,
|
||||
members: Option<cst::UnionMemberTypes>,
|
||||
fn write_enum_value_definition<'a>(
|
||||
value: &'a EnumValueDefinition<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let content = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
common::write_description(description.clone(), f);
|
||||
common::write_description(value.description.as_ref(), f);
|
||||
common::write_name(&value.value.name, f);
|
||||
common::write_directives(&value.directives, DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
fn write_union_like<'a>(
|
||||
description: Option<&StringValue<'a>>,
|
||||
extend: bool,
|
||||
name: &'a Name<'a>,
|
||||
directives: &'a [Directive<'a>],
|
||||
members: &'a [NamedType<'a>],
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let content = format_with(move |f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
common::write_description(description, f);
|
||||
let inner = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
if extend {
|
||||
write!(f, "extend ");
|
||||
}
|
||||
write!(f, "union ");
|
||||
if let Some(name) = name.clone() {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_directives(directives.clone(), DirectivesStyle::Attached, f);
|
||||
if let Some(members) = members.clone() {
|
||||
let types: Vec<cst::NamedType> = members.named_types().collect();
|
||||
if !types.is_empty() {
|
||||
write!(f, " =");
|
||||
write!(f, if_group_fits_on_line(&space()));
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
let leader = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
write!(f, [soft_line_break_or_space(), "| "]);
|
||||
});
|
||||
write!(f, if_group_breaks(&leader));
|
||||
for (i, named) in types.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, [soft_line_break_or_space(), "| "]);
|
||||
}
|
||||
common::write_named_type(named, f);
|
||||
}
|
||||
common::write_name(name, f);
|
||||
common::write_directives(directives, DirectivesStyle::Attached, f);
|
||||
if !members.is_empty() {
|
||||
write!(f, " =");
|
||||
write!(f, if_group_fits_on_line(&space()));
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
let leader = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
write!(f, [soft_line_break_or_space(), "| "]);
|
||||
});
|
||||
write!(f, indent(&body));
|
||||
}
|
||||
write!(f, if_group_breaks(&leader));
|
||||
for (i, named) in members.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, [soft_line_break_or_space(), "| "]);
|
||||
}
|
||||
common::write_named_type(named, f);
|
||||
}
|
||||
});
|
||||
write!(f, indent(&body));
|
||||
}
|
||||
});
|
||||
write!(f, group(&inner));
|
||||
@@ -429,32 +414,24 @@ fn write_union_like<'a>(
|
||||
write!(f, group(&content));
|
||||
}
|
||||
|
||||
fn write_field_definition(field: &cst::FieldDefinition, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
common::write_description(field.description(), f);
|
||||
if let Some(name) = field.name() {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
write_arguments_definition(field.arguments_definition(), f);
|
||||
fn write_field_definition<'a>(field: &'a FieldDefinition<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
common::write_description(field.description.as_ref(), f);
|
||||
common::write_name(&field.name, f);
|
||||
write_arguments_definition(&field.arguments, f);
|
||||
write!(f, ": ");
|
||||
if let Some(ty) = field.ty() {
|
||||
common::write_type(&ty, f);
|
||||
if let Some(ty) = field.ty.as_ref() {
|
||||
common::write_type(ty, f);
|
||||
}
|
||||
common::write_directives(field.directives(), DirectivesStyle::Attached, f);
|
||||
common::write_directives(&field.directives, DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
/// `(name: Type = default @dir, ...)` on field and directive definitions.
|
||||
/// Blank lines between entries are preserved (Prettier routes these through `printSequence`).
|
||||
fn write_arguments_definition(
|
||||
arguments: Option<cst::ArgumentsDefinition>,
|
||||
f: &mut GraphqlFormatter<'_, '_>,
|
||||
fn write_arguments_definition<'a>(
|
||||
arguments: &'a [InputValueDefinition<'a>],
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let Some(arguments) = arguments else { return };
|
||||
let items: Vec<cst::InputValueDefinition> = arguments.input_value_definitions().collect();
|
||||
if items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let r_paren = closing_token_start(arguments.r_paren_token(), arguments.syntax());
|
||||
common::write_paren_list(f, &items, r_paren, true, |i, f| {
|
||||
common::write_input_value_definition(&items[i], f);
|
||||
common::write_paren_list(f, arguments, true, |i, f| {
|
||||
common::write_input_value_definition(&arguments[i], f);
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
use oxc_graphql_parser::{cst, cst::CstNode};
|
||||
|
||||
use oxc_formatter_core::{
|
||||
Buffer, Format, Formatter,
|
||||
builders::{FormatWith, empty_line, hard_line_break, if_group_fits_on_line, soft_line_break},
|
||||
write,
|
||||
};
|
||||
use oxc_span::Span;
|
||||
use oxc_graphql_parser::ast::Document;
|
||||
|
||||
use crate::{
|
||||
comments::{
|
||||
@@ -16,14 +14,15 @@ use crate::{
|
||||
context::GraphqlFormatContext,
|
||||
};
|
||||
|
||||
pub mod common;
|
||||
pub mod definition;
|
||||
pub mod selection;
|
||||
mod sig;
|
||||
pub mod string;
|
||||
pub mod value;
|
||||
mod common;
|
||||
mod definition;
|
||||
mod selection;
|
||||
mod span;
|
||||
mod string;
|
||||
mod value;
|
||||
|
||||
use sig::{sig_end, sig_start};
|
||||
use span::Spanned;
|
||||
pub use span::to_span;
|
||||
|
||||
pub type GraphqlFormatter<'buf, 'a> = Formatter<'buf, 'a, GraphqlFormatContext<'a>>;
|
||||
|
||||
@@ -50,7 +49,7 @@ where
|
||||
|
||||
/// How consecutive sequence items are separated.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum SeparatorKind {
|
||||
enum SeparatorKind {
|
||||
/// One item per line (definitions, selections, fields, enum values, ...).
|
||||
Hard,
|
||||
/// Prettier's `join([ifBreak("", ", "), softline], items)`
|
||||
@@ -64,7 +63,7 @@ pub enum SeparatorKind {
|
||||
///
|
||||
/// Returns the significant end of the last item (`None` for an empty sequence),
|
||||
/// so callers can seed `flush_trailing_inside_comments` without re-scanning.
|
||||
pub fn write_sequence<'a, T, F>(
|
||||
fn write_sequence<'a, T, F>(
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
items: &[T],
|
||||
separator: SeparatorKind,
|
||||
@@ -72,13 +71,13 @@ pub fn write_sequence<'a, T, F>(
|
||||
mut write_item: F,
|
||||
) -> Option<u32>
|
||||
where
|
||||
T: CstNode,
|
||||
T: Spanned,
|
||||
F: FnMut(usize, &mut GraphqlFormatter<'_, 'a>),
|
||||
{
|
||||
let mut prev_end: Option<u32> = None;
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
let node = item.syntax();
|
||||
let start = sig_start(node);
|
||||
let item_span = item.span();
|
||||
let start = item_span.start;
|
||||
if let Some(pe) = prev_end {
|
||||
write_trailing_same_line_comment(pe, f);
|
||||
// Measure the gap up to the next pending comment (if it precedes the item),
|
||||
@@ -111,9 +110,9 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
let end = sig_end(node);
|
||||
let end = item_span.end;
|
||||
if is_suppressed_before(f, start) {
|
||||
write_suppressed_node(Span::new(start, end), f);
|
||||
write_suppressed_node(item_span, f);
|
||||
} else {
|
||||
flush_leading_comments(start, f);
|
||||
write_item(i, f);
|
||||
@@ -125,8 +124,8 @@ where
|
||||
|
||||
/// Top-level document: definitions joined by hard lines (blank lines preserved),
|
||||
/// then any comments trailing the last definition.
|
||||
pub fn write_document(document: &cst::Document, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
let defs: Vec<cst::Definition> = document.definitions().collect();
|
||||
pub fn write_document<'a>(document: &'a Document<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let defs = document.definitions.as_slice();
|
||||
if defs.is_empty() {
|
||||
// Defensive: oxc-graphql-parser errors on empty/comments-only documents,
|
||||
// so this branch is unreachable on the normal path.
|
||||
@@ -135,7 +134,7 @@ pub fn write_document(document: &cst::Document, f: &mut GraphqlFormatter<'_, '_>
|
||||
return;
|
||||
}
|
||||
|
||||
let last_end = write_sequence(f, &defs, SeparatorKind::Hard, true, |i, f| {
|
||||
let last_end = write_sequence(f, defs, SeparatorKind::Hard, true, |i, f| {
|
||||
definition::write_definition(&defs[i], f);
|
||||
});
|
||||
if let Some(last_end) = last_end {
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
//! Selection set printers: fields, fragment spreads, inline fragments.
|
||||
|
||||
use oxc_graphql_parser::{cst, cst::CstNode};
|
||||
|
||||
use oxc_formatter_core::{
|
||||
Buffer,
|
||||
builders::{block_indent, group, space},
|
||||
write,
|
||||
};
|
||||
use oxc_graphql_parser::ast::{Field, FragmentSpread, InlineFragment, Selection, SelectionSet};
|
||||
|
||||
use crate::comments::flush_trailing_inside_comments;
|
||||
|
||||
use super::{
|
||||
GraphqlFormatter, SeparatorKind, common, common::DirectivesStyle, format_with,
|
||||
sig::closing_token_start, write_sequence,
|
||||
span::close_delim_start, write_sequence,
|
||||
};
|
||||
|
||||
/// `{ selections... }`, always multi-line.
|
||||
pub fn write_selection_set<'a>(
|
||||
selection_set: &cst::SelectionSet,
|
||||
pub(super) fn write_selection_set<'a>(
|
||||
selection_set: &'a SelectionSet<'a>,
|
||||
f: &mut GraphqlFormatter<'_, 'a>,
|
||||
) {
|
||||
let selections: Vec<cst::Selection> = selection_set.selections().collect();
|
||||
let r_curly = closing_token_start(selection_set.r_curly_token(), selection_set.syntax());
|
||||
let selections = selection_set.selections.as_slice();
|
||||
let r_curly = close_delim_start(selection_set.span);
|
||||
|
||||
write!(f, "{");
|
||||
if selections.is_empty() {
|
||||
@@ -31,7 +30,7 @@ pub fn write_selection_set<'a>(
|
||||
}
|
||||
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
let last_end = write_sequence(f, &selections, SeparatorKind::Hard, true, |i, f| {
|
||||
let last_end = write_sequence(f, selections, SeparatorKind::Hard, true, |i, f| {
|
||||
write_selection(&selections[i], f);
|
||||
});
|
||||
if let Some(last_end) = last_end {
|
||||
@@ -41,56 +40,46 @@ pub fn write_selection_set<'a>(
|
||||
write!(f, [block_indent(&body), "}"]);
|
||||
}
|
||||
|
||||
fn write_selection(selection: &cst::Selection, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
fn write_selection<'a>(selection: &'a Selection<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
match selection {
|
||||
cst::Selection::Field(field) => write_field(field, f),
|
||||
cst::Selection::FragmentSpread(spread) => write_fragment_spread(spread, f),
|
||||
cst::Selection::InlineFragment(inline) => write_inline_fragment(inline, f),
|
||||
Selection::Field(field) => write_field(field, f),
|
||||
Selection::FragmentSpread(spread) => write_fragment_spread(spread, f),
|
||||
Selection::InlineFragment(inline) => write_inline_fragment(inline, f),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_field<'a>(field: &cst::Field, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
fn write_field<'a>(field: &'a Field<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let content = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
if let Some(alias) = field.alias()
|
||||
&& let Some(name) = alias.name()
|
||||
{
|
||||
common::write_name(&name, f);
|
||||
if let Some(alias) = field.alias.as_ref() {
|
||||
common::write_name(alias, f);
|
||||
write!(f, ": ");
|
||||
}
|
||||
if let Some(name) = field.name() {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_arguments(field.arguments(), f);
|
||||
common::write_directives(field.directives(), DirectivesStyle::Attached, f);
|
||||
if let Some(selection_set) = field.selection_set() {
|
||||
common::write_name(&field.name, f);
|
||||
common::write_arguments(&field.arguments, f);
|
||||
common::write_directives(&field.directives, DirectivesStyle::Attached, f);
|
||||
if let Some(selection_set) = field.selection_set.as_ref() {
|
||||
write!(f, space());
|
||||
write_selection_set(&selection_set, f);
|
||||
write_selection_set(selection_set, f);
|
||||
}
|
||||
});
|
||||
write!(f, group(&content));
|
||||
}
|
||||
|
||||
fn write_fragment_spread(spread: &cst::FragmentSpread, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
fn write_fragment_spread<'a>(spread: &'a FragmentSpread<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write!(f, "...");
|
||||
if let Some(fragment_name) = spread.fragment_name()
|
||||
&& let Some(name) = fragment_name.name()
|
||||
{
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_directives(spread.directives(), DirectivesStyle::Attached, f);
|
||||
common::write_name(&spread.name, f);
|
||||
common::write_directives(&spread.directives, DirectivesStyle::Attached, f);
|
||||
}
|
||||
|
||||
fn write_inline_fragment(inline: &cst::InlineFragment, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
fn write_inline_fragment<'a>(inline: &'a InlineFragment<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
write!(f, "...");
|
||||
if let Some(type_condition) = inline.type_condition()
|
||||
&& let Some(named) = type_condition.named_type()
|
||||
{
|
||||
if let Some(type_condition) = inline.type_condition.as_ref() {
|
||||
write!(f, " on ");
|
||||
common::write_named_type(&named, f);
|
||||
common::write_named_type(type_condition, f);
|
||||
}
|
||||
common::write_directives(inline.directives(), DirectivesStyle::Attached, f);
|
||||
if let Some(selection_set) = inline.selection_set() {
|
||||
common::write_directives(&inline.directives, DirectivesStyle::Attached, f);
|
||||
if let Some(selection_set) = inline.selection_set.as_ref() {
|
||||
write!(f, space());
|
||||
write_selection_set(&selection_set, f);
|
||||
write_selection_set(selection_set, f);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
//! CST position utilities over oxc-graphql-parser's lossless tree.
|
||||
//!
|
||||
//! `oxc-graphql-parser` attaches pending trivia (whitespace, comments, insignificant commas)
|
||||
//! to whichever node is open when the next significant token is consumed,
|
||||
//! so a node's raw `text_range()` may start or end on trivia.
|
||||
//! Every layout decision therefore uses significant-token positions computed here.
|
||||
|
||||
use oxc_graphql_parser::{SyntaxKind, SyntaxNode, SyntaxToken};
|
||||
|
||||
use oxc_span::Span;
|
||||
|
||||
use super::GraphqlFormatter;
|
||||
|
||||
/// Whether `kind` is a trivia token. In GraphQL, commas are trivia too.
|
||||
fn is_trivia(kind: SyntaxKind) -> bool {
|
||||
matches!(kind, SyntaxKind::WHITESPACE | SyntaxKind::COMMENT | SyntaxKind::COMMA)
|
||||
}
|
||||
|
||||
/// Start offset of the first significant (non-trivia) token within `node`.
|
||||
///
|
||||
/// oxc-graphql-parser attaches pending trivia to the node that is open when the next
|
||||
/// significant token is consumed, so `node.text_range().start()` may point at a
|
||||
/// comment that logically precedes the node. All layout decisions use significant
|
||||
/// token positions instead.
|
||||
pub fn sig_start(node: &SyntaxNode) -> u32 {
|
||||
let node_end = u32::from(node.text_range().end());
|
||||
let mut tok = node.first_token();
|
||||
while let Some(t) = tok {
|
||||
if u32::from(t.text_range().start()) >= node_end {
|
||||
break;
|
||||
}
|
||||
if !is_trivia(t.kind()) {
|
||||
return t.text_range().start().into();
|
||||
}
|
||||
tok = t.next_token();
|
||||
}
|
||||
node.text_range().start().into()
|
||||
}
|
||||
|
||||
/// End offset of the last significant (non-trivia) token within `node`.
|
||||
pub fn sig_end(node: &SyntaxNode) -> u32 {
|
||||
let node_start = u32::from(node.text_range().start());
|
||||
let mut tok = node.last_token();
|
||||
while let Some(t) = tok {
|
||||
if u32::from(t.text_range().end()) <= node_start {
|
||||
break;
|
||||
}
|
||||
if !is_trivia(t.kind()) {
|
||||
return t.text_range().end().into();
|
||||
}
|
||||
tok = t.prev_token();
|
||||
}
|
||||
node.text_range().end().into()
|
||||
}
|
||||
|
||||
/// Significant-token span of `node`.
|
||||
fn sig_span(node: &SyntaxNode) -> Span {
|
||||
Span::new(sig_start(node), sig_end(node))
|
||||
}
|
||||
|
||||
/// Start offset of a closing delimiter: the token's start when present,
|
||||
/// the container's significant end otherwise (error-resilient fallback).
|
||||
pub fn closing_token_start(token: Option<SyntaxToken>, container: &SyntaxNode) -> u32 {
|
||||
token.map_or_else(|| sig_end(container), |t| t.text_range().start().into())
|
||||
}
|
||||
|
||||
/// Source slice of `node`'s significant span, carrying the arena lifetime.
|
||||
pub fn node_text<'a>(f: &GraphqlFormatter<'_, 'a>, node: &SyntaxNode) -> &'a str {
|
||||
let span = sig_span(node);
|
||||
f.context().source_text().slice_range(span.start, span.end)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//! Span utilities bridging the parser AST and the formatter core.
|
||||
//!
|
||||
//! `oxc-graphql-parser` spans are usize-based ([`ast::Span`]) while the
|
||||
//! formatter core APIs take u32-based [`oxc_span::Span`]s; [`to_span`] converts.
|
||||
//! Node spans are significant-token spans (trivia is never included),
|
||||
//! so layout decisions use them directly, via [`Spanned`] when generic.
|
||||
//!
|
||||
//! Closing-delimiter positions are derived here too:
|
||||
//! - Braced/bracketed containers (`SelectionSet`, `ListValue`, ...) own their delimiter:
|
||||
//! the span's last consumed token IS the 1-byte closer, see [`close_delim_start`].
|
||||
//! - Paren lists (arguments, variable definitions) are bare `AstVec`s with no wrapper node,
|
||||
//! so the `)` is found by scanning past trivia from the last item's end, see [`find_close_after`].
|
||||
|
||||
use oxc_graphql_parser::ast;
|
||||
use oxc_span::Span;
|
||||
|
||||
/// Converts an `oxc-graphql-parser` [`ast::Span`] (usize-based) into
|
||||
/// an [`oxc_span::Span`] (u32-based) for use with the formatter core APIs.
|
||||
/// `pub`: also used by `format.rs` for the parser's comment spans (re-exported through `print`).
|
||||
#[inline]
|
||||
pub fn to_span(s: ast::Span) -> Span {
|
||||
Span::new(u32::try_from(s.start).unwrap_or(u32::MAX), u32::try_from(s.end).unwrap_or(u32::MAX))
|
||||
}
|
||||
|
||||
/// Significant span of an AST node as an [`oxc_span::Span`].
|
||||
pub(super) trait Spanned {
|
||||
fn span(&self) -> Span;
|
||||
}
|
||||
|
||||
macro_rules! impl_spanned {
|
||||
($($ty:ident),* $(,)?) => {
|
||||
$(
|
||||
impl Spanned for ast::$ty<'_> {
|
||||
fn span(&self) -> Span {
|
||||
to_span(self.span)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_spanned!(
|
||||
Argument,
|
||||
EnumValueDefinition,
|
||||
FieldDefinition,
|
||||
InputValueDefinition,
|
||||
ObjectField,
|
||||
RootOperationTypeDefinition,
|
||||
VariableDefinition,
|
||||
);
|
||||
|
||||
// Enums: forward to the parser's inherent `span()` accessors.
|
||||
// Explicit calls: the inherent method (returning `ast::Span`) would shadow
|
||||
// the trait method under plain `self.span()`.
|
||||
|
||||
impl Spanned for ast::Definition<'_> {
|
||||
fn span(&self) -> Span {
|
||||
to_span(ast::Definition::span(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::Selection<'_> {
|
||||
fn span(&self) -> Span {
|
||||
to_span(ast::Selection::span(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::Value<'_> {
|
||||
fn span(&self) -> Span {
|
||||
to_span(ast::Value::span(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Start offset of a container's 1-byte closing delimiter (`}` / `]`).
|
||||
///
|
||||
/// Sound only on a clean parse — the only case that reaches the printer,
|
||||
/// since `format()` bails on any parse error.
|
||||
/// Error-recovery spans may end elsewhere (e.g. at EOF),
|
||||
/// which would misplace comments flushed against this position.
|
||||
pub(super) fn close_delim_start(span: ast::Span) -> u32 {
|
||||
to_span(span).end.saturating_sub(1)
|
||||
}
|
||||
|
||||
/// Scans `source` from `from` for the first byte equal to `close`,
|
||||
/// treating GraphQL trivia (whitespace, insignificant commas, `#` line comments) as skippable.
|
||||
/// Returns `from` if not found
|
||||
/// (defensive fallback; the caller's parse succeeded so the closing byte is expected to exist).
|
||||
pub(super) fn find_close_after(source: &str, from: u32, close: u8) -> u32 {
|
||||
let bytes = source.as_bytes();
|
||||
let mut i = from as usize;
|
||||
while i < bytes.len() {
|
||||
let b = bytes[i];
|
||||
if b == close {
|
||||
return u32::try_from(i).unwrap_or(from);
|
||||
}
|
||||
if b == b'#' {
|
||||
// Line comment: skip to end of line (matching GraphQL's line terminators)
|
||||
while i < bytes.len() && bytes[i] != b'\n' && bytes[i] != b'\r' {
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
from
|
||||
}
|
||||
@@ -2,23 +2,25 @@
|
||||
//!
|
||||
//! Prettier prints `StringValue` from graphql-js's *cooked* `node.value`
|
||||
//! (escapes decoded, block strings dedented), then re-encodes it.
|
||||
//! `oxc-graphql-parser` hands us the raw source, so this module reimplements both the
|
||||
//! cooking (GraphQL spec string/block-string value algorithms)
|
||||
//! and Prettier's re-encoding (`"`/`\` escaped, newline as `\n`, `"""` as `\"""`).
|
||||
//! `oxc-graphql-parser`'s `StringValue.value` is cooked too, but NOT to spec:
|
||||
//! no block-string dedent / blank-line trimming, no surrogate pairing, and
|
||||
//! invalid escapes are silently dropped.
|
||||
//! So this module cooks from `raw` itself (GraphQL spec string/block-string value algorithms)
|
||||
//! and then applies Prettier's re-encoding (`"`/`\` escaped, newline as `\n`, `"""` as `\"""`).
|
||||
|
||||
use cow_utils::CowUtils;
|
||||
use oxc_graphql_parser::{cst, cst::CstNode};
|
||||
|
||||
use oxc_formatter_core::{
|
||||
Buffer,
|
||||
builders::{hard_line_break, text},
|
||||
write,
|
||||
};
|
||||
use oxc_graphql_parser::ast::StringValue;
|
||||
|
||||
use super::{GraphqlFormatter, sig::node_text};
|
||||
use super::GraphqlFormatter;
|
||||
|
||||
pub fn write_string_value(sv: &cst::StringValue, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
let raw = node_text(f, sv.syntax());
|
||||
pub(super) fn write_string_value<'a>(sv: &StringValue<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let raw = sv.raw;
|
||||
if let Some(body) = raw.strip_prefix("\"\"\"").and_then(|r| r.strip_suffix("\"\"\"")) {
|
||||
write_block_string(body, f);
|
||||
} else if let Some(body) = raw.strip_prefix('"').and_then(|r| r.strip_suffix('"')) {
|
||||
|
||||
@@ -2,39 +2,38 @@
|
||||
//!
|
||||
//! `StringValue` cooking and re-encoding lives in the sibling [`super::string`] module.
|
||||
|
||||
use oxc_graphql_parser::{cst, cst::CstNode};
|
||||
|
||||
use oxc_formatter_core::{
|
||||
Buffer, FormatContext,
|
||||
builders::{group, soft_block_indent_with_maybe_space, text},
|
||||
builders::{group, soft_block_indent, soft_block_indent_with_maybe_space, text},
|
||||
write,
|
||||
};
|
||||
use oxc_graphql_parser::ast::{ListValue, ObjectValue, Value};
|
||||
|
||||
use crate::comments::flush_trailing_inside_comments;
|
||||
|
||||
use super::{
|
||||
GraphqlFormatter, SeparatorKind, common, format_with,
|
||||
sig::{closing_token_start, node_text},
|
||||
string, write_sequence,
|
||||
GraphqlFormatter, SeparatorKind, common, format_with, span::close_delim_start, string,
|
||||
write_sequence,
|
||||
};
|
||||
|
||||
pub fn write_value(value: &cst::Value, f: &mut GraphqlFormatter<'_, '_>) {
|
||||
pub(super) fn write_value<'a>(value: &'a Value<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
match value {
|
||||
cst::Value::Variable(variable) => common::write_variable(variable, f),
|
||||
cst::Value::StringValue(sv) => string::write_string_value(sv, f),
|
||||
cst::Value::FloatValue(v) => write!(f, text(node_text(f, v.syntax()))),
|
||||
cst::Value::IntValue(v) => write!(f, text(node_text(f, v.syntax()))),
|
||||
cst::Value::BooleanValue(v) => write!(f, text(node_text(f, v.syntax()))),
|
||||
cst::Value::NullValue(_) => write!(f, "null"),
|
||||
cst::Value::EnumValue(v) => write!(f, text(node_text(f, v.syntax()))),
|
||||
cst::Value::ListValue(list) => write_list_value(list, f),
|
||||
cst::Value::ObjectValue(object) => write_object_value(object, f),
|
||||
Value::Variable(variable) => common::write_variable(variable, f),
|
||||
Value::String(sv) => string::write_string_value(sv, f),
|
||||
Value::Float(v) => write!(f, text(v.raw)),
|
||||
Value::Int(v) => write!(f, text(v.raw)),
|
||||
Value::Boolean(v) => write!(f, text(if v.value { "true" } else { "false" })),
|
||||
Value::Null(_) => write!(f, "null"),
|
||||
Value::Enum(v) => write!(f, text(v.name.value)),
|
||||
Value::List(list) => write_list_value(list, f),
|
||||
Value::Object(object) => write_object_value(object, f),
|
||||
Value::Missing(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_list_value<'a>(list: &cst::ListValue, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let values: Vec<cst::Value> = list.values().collect();
|
||||
let r_brack = closing_token_start(list.r_brack_token(), list.syntax());
|
||||
fn write_list_value<'a>(list: &'a ListValue<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let values = list.values.as_slice();
|
||||
let r_brack = close_delim_start(list.span);
|
||||
|
||||
write!(f, "[");
|
||||
if values.is_empty() {
|
||||
@@ -44,19 +43,19 @@ fn write_list_value<'a>(list: &cst::ListValue, f: &mut GraphqlFormatter<'_, 'a>)
|
||||
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
// No blank-line preservation (Prettier uses a plain `path.map` for list values).
|
||||
let last_end = write_sequence(f, &values, SeparatorKind::CommaSoftline, false, |i, f| {
|
||||
let last_end = write_sequence(f, values, SeparatorKind::CommaSoftline, false, |i, f| {
|
||||
write_value(&values[i], f);
|
||||
});
|
||||
if let Some(last_end) = last_end {
|
||||
flush_trailing_inside_comments(last_end, r_brack, f);
|
||||
}
|
||||
});
|
||||
write!(f, [group(&oxc_formatter_core::builders::soft_block_indent(&body)), "]"]);
|
||||
write!(f, [group(&soft_block_indent(&body)), "]"]);
|
||||
}
|
||||
|
||||
fn write_object_value<'a>(object: &cst::ObjectValue, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let fields: Vec<cst::ObjectField> = object.object_fields().collect();
|
||||
let r_curly = closing_token_start(object.r_curly_token(), object.syntax());
|
||||
fn write_object_value<'a>(object: &'a ObjectValue<'a>, f: &mut GraphqlFormatter<'_, 'a>) {
|
||||
let fields = object.fields.as_slice();
|
||||
let r_curly = close_delim_start(object.span);
|
||||
|
||||
write!(f, "{");
|
||||
if fields.is_empty() {
|
||||
@@ -66,14 +65,12 @@ fn write_object_value<'a>(object: &cst::ObjectValue, f: &mut GraphqlFormatter<'_
|
||||
|
||||
let bracket_spacing = f.context().options().bracket_spacing.value();
|
||||
let body = format_with(|f: &mut GraphqlFormatter<'_, 'a>| {
|
||||
let last_end = write_sequence(f, &fields, SeparatorKind::CommaSoftline, false, |i, f| {
|
||||
let last_end = write_sequence(f, fields, SeparatorKind::CommaSoftline, false, |i, f| {
|
||||
let field = &fields[i];
|
||||
if let Some(name) = field.name() {
|
||||
common::write_name(&name, f);
|
||||
}
|
||||
common::write_name(&field.name, f);
|
||||
write!(f, ": ");
|
||||
if let Some(v) = field.value() {
|
||||
write_value(&v, f);
|
||||
if let Some(v) = field.value.as_ref() {
|
||||
write_value(v, f);
|
||||
}
|
||||
});
|
||||
if let Some(last_end) = last_end {
|
||||
|
||||
Reference in new issue
Block a user