1use crate::cst;
2use crate::cst::CstNode;
3use crate::SyntaxNode;
4use crate::TokenText;
5use rowan::GreenToken;
6use rowan::SyntaxKind;
7use std::num::ParseFloatError;
8use std::num::ParseIntError;
9
10impl cst::Name {
11 pub fn text(&self) -> TokenText {
12 text_of_first_token(self.syntax())
13 }
14}
15
16impl cst::Variable {
17 pub fn text(&self) -> TokenText {
18 self.name()
19 .expect("Cannot get variable's NAME token")
20 .text()
21 }
22}
23
24impl cst::EnumValue {
25 pub fn text(&self) -> TokenText {
26 self.name()
27 .expect("Cannot get enum value's NAME token")
28 .text()
29 }
30}
31
32impl cst::DirectiveLocation {
33 pub fn text(self) -> Option<TokenText> {
34 let txt = if self.query_token().is_some() {
35 Some("QUERY")
36 } else if self.mutation_token().is_some() {
37 Some("MUTATION")
38 } else if self.subscription_token().is_some() {
39 Some("SUBSCRIPTION")
40 } else if self.field_token().is_some() {
41 Some("FIELD")
42 } else if self.fragment_definition_token().is_some() {
43 Some("FRAGMENT_DEFINITION")
44 } else if self.fragment_spread_token().is_some() {
45 Some("FRAGMENT_SPREAD")
46 } else if self.inline_fragment_token().is_some() {
47 Some("INLINE_FRAGMENT")
48 } else if self.variable_definition_token().is_some() {
49 Some("VARIABLE_DEFINITION")
50 } else if self.schema_token().is_some() {
51 Some("SCHEMA")
52 } else if self.scalar_token().is_some() {
53 Some("SCALAR")
54 } else if self.object_token().is_some() {
55 Some("OBJECT")
56 } else if self.field_definition_token().is_some() {
57 Some("FIELD_DEFINITION")
58 } else if self.argument_definition_token().is_some() {
59 Some("ARGUMENT_DEFINITION")
60 } else if self.interface_token().is_some() {
61 Some("INTERFACE")
62 } else if self.union_token().is_some() {
63 Some("UNION")
64 } else if self.enum_token().is_some() {
65 Some("ENUM")
66 } else if self.enum_value_token().is_some() {
67 Some("ENUM_VALUE")
68 } else if self.input_object_token().is_some() {
69 Some("INPUT_OBJECT")
70 } else if self.input_field_definition_token().is_some() {
71 Some("INPUT_FIELD_DEFINITION")
72 } else {
73 None
74 };
75
76 txt.map(|txt| {
77 TokenText(GreenToken::new(
78 SyntaxKind(crate::SyntaxKind::DIRECTIVE_LOCATION as u16),
79 txt,
80 ))
81 })
82 }
83}
84
85impl cst::Definition {
86 pub fn name(&self) -> Option<cst::Name> {
89 match self {
90 Self::OperationDefinition(it) => it.name(),
91 Self::FragmentDefinition(it) => it.fragment_name()?.name(),
92 Self::DirectiveDefinition(it) => it.name(),
93 Self::SchemaDefinition(_) => None,
94 Self::ScalarTypeDefinition(it) => it.name(),
95 Self::ObjectTypeDefinition(it) => it.name(),
96 Self::InterfaceTypeDefinition(it) => it.name(),
97 Self::UnionTypeDefinition(it) => it.name(),
98 Self::EnumTypeDefinition(it) => it.name(),
99 Self::InputObjectTypeDefinition(it) => it.name(),
100 Self::SchemaExtension(_) => None,
101 Self::ScalarTypeExtension(it) => it.name(),
102 Self::ObjectTypeExtension(it) => it.name(),
103 Self::InterfaceTypeExtension(it) => it.name(),
104 Self::UnionTypeExtension(it) => it.name(),
105 Self::EnumTypeExtension(it) => it.name(),
106 Self::InputObjectTypeExtension(it) => it.name(),
107 }
108 }
109
110 pub fn kind(&self) -> &'static str {
111 match self {
112 cst::Definition::OperationDefinition(_) => "OperationDefinition",
113 cst::Definition::FragmentDefinition(_) => "FragmentDefinition",
114 cst::Definition::DirectiveDefinition(_) => "DirectiveDefinition",
115 cst::Definition::ScalarTypeDefinition(_) => "ScalarTypeDefinition",
116 cst::Definition::ObjectTypeDefinition(_) => "ObjectTypeDefinition",
117 cst::Definition::InterfaceTypeDefinition(_) => "InterfaceTypeDefinition",
118 cst::Definition::UnionTypeDefinition(_) => "UnionTypeDefinition",
119 cst::Definition::EnumTypeDefinition(_) => "EnumTypeDefinition",
120 cst::Definition::InputObjectTypeDefinition(_) => "InputObjectTypeDefinition",
121 cst::Definition::SchemaDefinition(_) => "SchemaDefinition",
122 cst::Definition::SchemaExtension(_) => "SchemaExtension",
123 cst::Definition::ScalarTypeExtension(_) => "ScalarTypeExtension",
124 cst::Definition::ObjectTypeExtension(_) => "ObjectTypeExtension",
125 cst::Definition::InterfaceTypeExtension(_) => "InterfaceTypeExtension",
126 cst::Definition::UnionTypeExtension(_) => "UnionTypeExtension",
127 cst::Definition::EnumTypeExtension(_) => "EnumTypeExtension",
128 cst::Definition::InputObjectTypeExtension(_) => "InputObjectTypeExtension",
129 }
130 }
131
132 pub fn is_executable_definition(&self) -> bool {
133 matches!(
134 self,
135 Self::OperationDefinition(_) | Self::FragmentDefinition(_)
136 )
137 }
138
139 pub fn is_extension_definition(&self) -> bool {
140 matches!(
141 self,
142 Self::SchemaExtension(_)
143 | Self::ScalarTypeExtension(_)
144 | Self::ObjectTypeExtension(_)
145 | Self::InterfaceTypeExtension(_)
146 | Self::UnionTypeExtension(_)
147 | Self::EnumTypeExtension(_)
148 | Self::InputObjectTypeExtension(_)
149 )
150 }
151}
152
153impl From<cst::StringValue> for String {
154 fn from(val: cst::StringValue) -> Self {
155 Self::from(&val)
156 }
157}
158
159fn unescape_string(input: &str) -> String {
163 fn hex4(iter: &mut std::str::Chars<'_>) -> u32 {
165 iter.by_ref().take(4).fold(0, |acc, c| {
166 let digit = c.to_digit(16).unwrap();
167 (acc << 4) + digit
168 })
169 }
170
171 let mut output = String::with_capacity(input.len());
172
173 let mut iter = input.chars();
174 while let Some(c) = iter.next() {
175 match c {
176 '\\' => {
177 let Some(c2) = iter.next() else {
178 output.push(c);
179 break;
180 };
181
182 let mut unicode = || {
184 let value = if iter.clone().next() == Some('{') {
185 iter.next(); let mut value = 0;
188 loop {
189 let c = iter.next().unwrap();
190 if c == '}' {
191 break;
192 }
193 value = (value << 4) + c.to_digit(16).unwrap();
194 }
195 value
196 } else {
197 let value = hex4(&mut iter);
198 if (0xD800..=0xDBFF).contains(&value) {
199 iter.next(); iter.next(); let trail = hex4(&mut iter);
205 (value - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000
206 } else {
207 value
208 }
209 };
210 char::from_u32(value).unwrap()
211 };
212
213 match c2 {
214 '"' | '\\' | '/' => output.push(c2),
215 'b' => output.push('\u{0008}'),
216 'f' => output.push('\u{000c}'),
217 'n' => output.push('\n'),
218 'r' => output.push('\r'),
219 't' => output.push('\t'),
220 'u' => output.push(unicode()),
221 _ => (),
222 }
223 }
224 _ => output.push(c),
225 }
226 }
227
228 output
229}
230
231const ESCAPED_TRIPLE_QUOTE: &str = r#"\""""#;
232const TRIPLE_QUOTE: &str = r#"""""#;
233
234fn is_block_string(input: &str) -> bool {
235 input.starts_with(TRIPLE_QUOTE)
236}
237
238struct GraphQLLines<'a> {
241 input: &'a str,
242 finished: bool,
243}
244
245impl<'a> GraphQLLines<'a> {
246 fn new(input: &'a str) -> Self {
247 Self {
248 input,
249 finished: false,
250 }
251 }
252}
253
254impl<'a> Iterator for GraphQLLines<'a> {
255 type Item = &'a str;
256 fn next(&mut self) -> Option<Self::Item> {
257 if self.finished {
260 return None;
261 }
262
263 let Some(index) = memchr::memchr2(b'\r', b'\n', self.input.as_bytes()) else {
264 self.finished = true;
265 return Some(self.input);
266 };
267 let line = &self.input[..index];
268 let rest = match self.input.get(index..=index + 1) {
269 Some("\r\n") => &self.input[index + 2..],
270 _ => &self.input[index + 1..],
271 };
272 self.input = rest;
273 Some(line)
274 }
275}
276
277fn split_lines(input: &str) -> impl Iterator<Item = &str> {
279 GraphQLLines::new(input)
280}
281
282fn replace_into(input: &str, pattern: &str, replace: &str, output: &mut String) {
286 let mut last_index = 0;
287 for index in memchr::memmem::find_iter(input.as_bytes(), pattern.as_bytes()) {
288 output.push_str(&input[last_index..index]);
289 output.push_str(replace);
290 last_index = index + pattern.len();
291 }
292 if last_index < input.len() {
293 output.push_str(&input[last_index..]);
294 }
295}
296
297fn unescape_block_string(raw_value: &str) -> String {
303 fn is_whitespace(c: char) -> bool {
305 matches!(c, ' ' | '\t')
306 }
307 fn is_whitespace_line(line: &str) -> bool {
309 line.chars().all(is_whitespace)
310 }
311 fn count_indent(line: &str) -> usize {
313 line.chars().take_while(|&c| is_whitespace(c)).count()
314 }
315
316 let common_indent = split_lines(raw_value)
320 .skip(1)
322 .filter_map(|line| {
323 let length = line.len();
327 let indent = count_indent(line);
329 (indent < length).then_some(indent)
331 })
332 .min()
333 .unwrap_or(0);
334
335 let mut lines = split_lines(raw_value)
336 .enumerate()
337 .map(|(index, line)| {
339 if index == 0 {
341 line
342 } else {
343 &line[common_indent.min(line.len())..]
345 }
346 })
347 .skip_while(|line| is_whitespace_line(line));
350
351 let mut formatted = String::with_capacity(raw_value.len());
355
356 if let Some(line) = lines.next() {
359 replace_into(line, ESCAPED_TRIPLE_QUOTE, TRIPLE_QUOTE, &mut formatted);
361 };
362
363 let mut final_char_index = formatted.len();
364
365 for line in lines {
367 formatted.push('\n');
369 replace_into(line, ESCAPED_TRIPLE_QUOTE, TRIPLE_QUOTE, &mut formatted);
371
372 if !is_whitespace_line(line) {
374 final_char_index = formatted.len();
375 }
376 }
377
378 formatted.truncate(final_char_index);
380
381 formatted
383}
384
385impl From<&'_ cst::StringValue> for String {
387 fn from(val: &'_ cst::StringValue) -> Self {
388 let text = text_of_first_token(val.syntax());
389 if is_block_string(&text) {
392 unescape_block_string(&text[3..text.len() - 3])
393 } else {
394 unescape_string(&text[1..text.len() - 1])
395 }
396 }
397}
398
399impl TryFrom<cst::IntValue> for i32 {
400 type Error = ParseIntError;
401
402 fn try_from(val: cst::IntValue) -> Result<Self, Self::Error> {
403 Self::try_from(&val)
404 }
405}
406
407impl TryFrom<&'_ cst::IntValue> for i32 {
408 type Error = ParseIntError;
409
410 fn try_from(val: &'_ cst::IntValue) -> Result<Self, Self::Error> {
411 let text = text_of_first_token(val.syntax());
412 text.parse()
413 }
414}
415
416impl TryFrom<cst::IntValue> for f64 {
417 type Error = ParseFloatError;
418
419 fn try_from(val: cst::IntValue) -> Result<Self, Self::Error> {
420 Self::try_from(&val)
421 }
422}
423
424impl TryFrom<&'_ cst::IntValue> for f64 {
425 type Error = ParseFloatError;
426
427 fn try_from(val: &'_ cst::IntValue) -> Result<Self, Self::Error> {
428 let text = text_of_first_token(val.syntax());
429 text.parse()
430 }
431}
432
433impl TryFrom<cst::FloatValue> for f64 {
434 type Error = ParseFloatError;
435
436 fn try_from(val: cst::FloatValue) -> Result<Self, Self::Error> {
437 Self::try_from(&val)
438 }
439}
440
441impl TryFrom<&'_ cst::FloatValue> for f64 {
442 type Error = ParseFloatError;
443
444 fn try_from(val: &'_ cst::FloatValue) -> Result<Self, Self::Error> {
445 let text = text_of_first_token(val.syntax());
446 text.parse()
447 }
448}
449
450impl TryFrom<cst::BooleanValue> for bool {
451 type Error = std::str::ParseBoolError;
452
453 fn try_from(val: cst::BooleanValue) -> Result<Self, Self::Error> {
454 Self::try_from(&val)
455 }
456}
457
458impl TryFrom<&'_ cst::BooleanValue> for bool {
459 type Error = std::str::ParseBoolError;
460
461 fn try_from(val: &'_ cst::BooleanValue) -> Result<Self, Self::Error> {
462 let text = text_of_first_token(val.syntax());
463 text.parse()
464 }
465}
466
467fn text_of_first_token(node: &SyntaxNode) -> TokenText {
468 let first_token = node
469 .green()
470 .children()
471 .next()
472 .and_then(|it| it.into_token())
473 .unwrap()
474 .to_owned();
475
476 TokenText(first_token)
477}
478
479#[cfg(test)]
480mod string_tests {
481 use super::unescape_string;
482
483 #[test]
484 fn it_parses_strings() {
485 assert_eq!(unescape_string(r"simple"), "simple");
486 assert_eq!(unescape_string(r" white space "), " white space ");
487 }
488
489 #[test]
490 fn it_unescapes_strings() {
491 assert_eq!(unescape_string(r#"quote \""#), "quote \"");
492 assert_eq!(
493 unescape_string(r"escaped \n\r\b\t\f"),
494 "escaped \n\r\u{0008}\t\u{000c}"
495 );
496 assert_eq!(unescape_string(r"slashes \\ \/"), r"slashes \ /");
497 assert_eq!(
498 unescape_string("unescaped unicode outside BMP 😀"),
499 "unescaped unicode outside BMP 😀"
500 );
501 assert_eq!(
502 unescape_string(r"unicode \u1234\u5678\u90AB\uCDEF"),
503 "unicode \u{1234}\u{5678}\u{90AB}\u{CDEF}"
504 );
505 assert_eq!(
506 unescape_string(r"variable width \u{0} \u{2708} \u{1F4A9} \u{10FFFF}"),
507 "variable width \u{0} \u{2708} \u{1F4A9} \u{10FFFF}"
508 );
509 assert_eq!(
510 unescape_string(r"surrogate pair \uD83D\uDCA9"),
511 "surrogate pair \u{1F4A9}"
512 );
513 assert_eq!(
514 unescape_string(r"extremes \uD800\uDC00 \uDBFF\uDFFF"),
515 "extremes \u{10000} \u{10FFFF}"
516 );
517 }
518}
519
520#[cfg(test)]
521mod block_string_tests {
522 use super::split_lines;
523 use super::unescape_block_string;
524
525 #[test]
526 fn it_splits_lines_by_graphql_newline_definition() {
527 let plain_newlines: Vec<_> = split_lines(
528 r#"source text
529 with some
530 new
531
532
533 lines
534 "#,
535 )
536 .collect();
537
538 assert_eq!(
539 plain_newlines,
540 [
541 "source text",
542 " with some",
543 " new",
544 "",
545 "",
546 " lines",
547 " ",
548 ]
549 );
550
551 let different_endings: Vec<_> =
552 split_lines("with\nand\r\nand\rall in the same\r\nstring").collect();
553 assert_eq!(
554 different_endings,
555 ["with", "and", "and", "all in the same", "string",]
556 );
557
558 let empty_string: Vec<_> = split_lines("").collect();
559 assert_eq!(empty_string, [""]);
560
561 let empty_line: Vec<_> = split_lines("\n\r\r\n").collect();
562 assert_eq!(empty_line, ["", "", "", ""]);
563 }
564
565 #[test]
566 fn it_normalizes_block_string_newlines() {
567 assert_eq!(unescape_block_string("multi\nline"), "multi\nline");
568 assert_eq!(unescape_block_string("multi\r\nline"), "multi\nline");
569 assert_eq!(unescape_block_string("multi\rline"), "multi\nline");
570 }
571
572 #[test]
573 fn it_does_not_unescape_block_strings() {
574 assert_eq!(
575 unescape_block_string(r"escaped \n\r\b\t\f"),
576 r"escaped \n\r\b\t\f"
577 );
578 assert_eq!(unescape_block_string(r"slashes \\ \/"), r"slashes \\ \/");
579 assert_eq!(
580 unescape_block_string("unescaped unicode outside BMP \u{1f600}"),
581 "unescaped unicode outside BMP \u{1f600}"
582 );
583 }
584
585 #[test]
586 fn it_dedents_block_strings() {
587 assert_eq!(
588 unescape_block_string(" intact whitespace with one line "),
589 " intact whitespace with one line "
590 );
591
592 assert_eq!(
593 unescape_block_string(
594 r"
595 This is
596 indented
597 quite a lot
598 "
599 ),
600 r"This is
601indented
602quite a lot"
603 );
604
605 assert_eq!(
606 unescape_block_string(
607 r"
608
609 spans
610 multiple
611 lines
612
613 "
614 ),
615 r"spans
616 multiple
617 lines"
618 );
619 }
620}