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 let mut output = String::with_capacity(input.len());
164
165 let mut iter = input.chars();
166 while let Some(c) = iter.next() {
167 match c {
168 '\\' => {
169 let Some(c2) = iter.next() else {
170 output.push(c);
171 break;
172 };
173
174 let mut unicode = || {
177 let value = iter.by_ref().take(4).fold(0, |acc, c| {
180 let digit = c.to_digit(16).unwrap();
181 (acc << 4) + digit
182 });
183 char::from_u32(value).unwrap()
185 };
186
187 match c2 {
188 '"' | '\\' | '/' => output.push(c2),
189 'b' => output.push('\u{0008}'),
190 'f' => output.push('\u{000c}'),
191 'n' => output.push('\n'),
192 'r' => output.push('\r'),
193 't' => output.push('\t'),
194 'u' => output.push(unicode()),
195 _ => (),
196 }
197 }
198 _ => output.push(c),
199 }
200 }
201
202 output
203}
204
205const ESCAPED_TRIPLE_QUOTE: &str = r#"\""""#;
206const TRIPLE_QUOTE: &str = r#"""""#;
207
208fn is_block_string(input: &str) -> bool {
209 input.starts_with(TRIPLE_QUOTE)
210}
211
212struct GraphQLLines<'a> {
215 input: &'a str,
216 finished: bool,
217}
218
219impl<'a> GraphQLLines<'a> {
220 fn new(input: &'a str) -> Self {
221 Self {
222 input,
223 finished: false,
224 }
225 }
226}
227
228impl<'a> Iterator for GraphQLLines<'a> {
229 type Item = &'a str;
230 fn next(&mut self) -> Option<Self::Item> {
231 if self.finished {
234 return None;
235 }
236
237 let Some(index) = memchr::memchr2(b'\r', b'\n', self.input.as_bytes()) else {
238 self.finished = true;
239 return Some(self.input);
240 };
241 let line = &self.input[..index];
242 let rest = match self.input.get(index..=index + 1) {
243 Some("\r\n") => &self.input[index + 2..],
244 _ => &self.input[index + 1..],
245 };
246 self.input = rest;
247 Some(line)
248 }
249}
250
251fn split_lines(input: &str) -> impl Iterator<Item = &str> {
253 GraphQLLines::new(input)
254}
255
256fn replace_into(input: &str, pattern: &str, replace: &str, output: &mut String) {
260 let mut last_index = 0;
261 for index in memchr::memmem::find_iter(input.as_bytes(), pattern.as_bytes()) {
262 output.push_str(&input[last_index..index]);
263 output.push_str(replace);
264 last_index = index + pattern.len();
265 }
266 if last_index < input.len() {
267 output.push_str(&input[last_index..]);
268 }
269}
270
271fn unescape_block_string(raw_value: &str) -> String {
277 fn is_whitespace(c: char) -> bool {
279 matches!(c, ' ' | '\t')
280 }
281 fn is_whitespace_line(line: &str) -> bool {
283 line.chars().all(is_whitespace)
284 }
285 fn count_indent(line: &str) -> usize {
287 line.chars().take_while(|&c| is_whitespace(c)).count()
288 }
289
290 let common_indent = split_lines(raw_value)
294 .skip(1)
296 .filter_map(|line| {
297 let length = line.len();
301 let indent = count_indent(line);
303 (indent < length).then_some(indent)
305 })
306 .min()
307 .unwrap_or(0);
308
309 let mut lines = split_lines(raw_value)
310 .enumerate()
311 .map(|(index, line)| {
313 if index == 0 {
315 line
316 } else {
317 &line[common_indent.min(line.len())..]
319 }
320 })
321 .skip_while(|line| is_whitespace_line(line));
324
325 let mut formatted = String::with_capacity(raw_value.len());
329
330 if let Some(line) = lines.next() {
333 replace_into(line, ESCAPED_TRIPLE_QUOTE, TRIPLE_QUOTE, &mut formatted);
335 };
336
337 let mut final_char_index = formatted.len();
338
339 for line in lines {
341 formatted.push('\n');
343 replace_into(line, ESCAPED_TRIPLE_QUOTE, TRIPLE_QUOTE, &mut formatted);
345
346 if !is_whitespace_line(line) {
348 final_char_index = formatted.len();
349 }
350 }
351
352 formatted.truncate(final_char_index);
354
355 formatted
357}
358
359impl From<&'_ cst::StringValue> for String {
361 fn from(val: &'_ cst::StringValue) -> Self {
362 let text = text_of_first_token(val.syntax());
363 if is_block_string(&text) {
366 unescape_block_string(&text[3..text.len() - 3])
367 } else {
368 unescape_string(&text[1..text.len() - 1])
369 }
370 }
371}
372
373impl TryFrom<cst::IntValue> for i32 {
374 type Error = ParseIntError;
375
376 fn try_from(val: cst::IntValue) -> Result<Self, Self::Error> {
377 Self::try_from(&val)
378 }
379}
380
381impl TryFrom<&'_ cst::IntValue> for i32 {
382 type Error = ParseIntError;
383
384 fn try_from(val: &'_ cst::IntValue) -> Result<Self, Self::Error> {
385 let text = text_of_first_token(val.syntax());
386 text.parse()
387 }
388}
389
390impl TryFrom<cst::IntValue> for f64 {
391 type Error = ParseFloatError;
392
393 fn try_from(val: cst::IntValue) -> Result<Self, Self::Error> {
394 Self::try_from(&val)
395 }
396}
397
398impl TryFrom<&'_ cst::IntValue> for f64 {
399 type Error = ParseFloatError;
400
401 fn try_from(val: &'_ cst::IntValue) -> Result<Self, Self::Error> {
402 let text = text_of_first_token(val.syntax());
403 text.parse()
404 }
405}
406
407impl TryFrom<cst::FloatValue> for f64 {
408 type Error = ParseFloatError;
409
410 fn try_from(val: cst::FloatValue) -> Result<Self, Self::Error> {
411 Self::try_from(&val)
412 }
413}
414
415impl TryFrom<&'_ cst::FloatValue> for f64 {
416 type Error = ParseFloatError;
417
418 fn try_from(val: &'_ cst::FloatValue) -> Result<Self, Self::Error> {
419 let text = text_of_first_token(val.syntax());
420 text.parse()
421 }
422}
423
424impl TryFrom<cst::BooleanValue> for bool {
425 type Error = std::str::ParseBoolError;
426
427 fn try_from(val: cst::BooleanValue) -> Result<Self, Self::Error> {
428 Self::try_from(&val)
429 }
430}
431
432impl TryFrom<&'_ cst::BooleanValue> for bool {
433 type Error = std::str::ParseBoolError;
434
435 fn try_from(val: &'_ cst::BooleanValue) -> Result<Self, Self::Error> {
436 let text = text_of_first_token(val.syntax());
437 text.parse()
438 }
439}
440
441fn text_of_first_token(node: &SyntaxNode) -> TokenText {
442 let first_token = node
443 .green()
444 .children()
445 .next()
446 .and_then(|it| it.into_token())
447 .unwrap()
448 .to_owned();
449
450 TokenText(first_token)
451}
452
453#[cfg(test)]
454mod string_tests {
455 use super::unescape_string;
456
457 #[test]
458 fn it_parses_strings() {
459 assert_eq!(unescape_string(r"simple"), "simple");
460 assert_eq!(unescape_string(r" white space "), " white space ");
461 }
462
463 #[test]
464 fn it_unescapes_strings() {
465 assert_eq!(unescape_string(r#"quote \""#), "quote \"");
466 assert_eq!(
467 unescape_string(r"escaped \n\r\b\t\f"),
468 "escaped \n\r\u{0008}\t\u{000c}"
469 );
470 assert_eq!(unescape_string(r"slashes \\ \/"), r"slashes \ /");
471 assert_eq!(
472 unescape_string("unescaped unicode outside BMP 😀"),
473 "unescaped unicode outside BMP 😀"
474 );
475 assert_eq!(
476 unescape_string(r"unicode \u1234\u5678\u90AB\uCDEF"),
477 "unicode \u{1234}\u{5678}\u{90AB}\u{CDEF}"
478 );
479 }
480}
481
482#[cfg(test)]
483mod block_string_tests {
484 use super::split_lines;
485 use super::unescape_block_string;
486
487 #[test]
488 fn it_splits_lines_by_graphql_newline_definition() {
489 let plain_newlines: Vec<_> = split_lines(
490 r#"source text
491 with some
492 new
493
494
495 lines
496 "#,
497 )
498 .collect();
499
500 assert_eq!(
501 plain_newlines,
502 [
503 "source text",
504 " with some",
505 " new",
506 "",
507 "",
508 " lines",
509 " ",
510 ]
511 );
512
513 let different_endings: Vec<_> =
514 split_lines("with\nand\r\nand\rall in the same\r\nstring").collect();
515 assert_eq!(
516 different_endings,
517 ["with", "and", "and", "all in the same", "string",]
518 );
519
520 let empty_string: Vec<_> = split_lines("").collect();
521 assert_eq!(empty_string, [""]);
522
523 let empty_line: Vec<_> = split_lines("\n\r\r\n").collect();
524 assert_eq!(empty_line, ["", "", "", ""]);
525 }
526
527 #[test]
528 fn it_normalizes_block_string_newlines() {
529 assert_eq!(unescape_block_string("multi\nline"), "multi\nline");
530 assert_eq!(unescape_block_string("multi\r\nline"), "multi\nline");
531 assert_eq!(unescape_block_string("multi\rline"), "multi\nline");
532 }
533
534 #[test]
535 fn it_does_not_unescape_block_strings() {
536 assert_eq!(
537 unescape_block_string(r"escaped \n\r\b\t\f"),
538 r"escaped \n\r\b\t\f"
539 );
540 assert_eq!(unescape_block_string(r"slashes \\ \/"), r"slashes \\ \/");
541 assert_eq!(
542 unescape_block_string("unescaped unicode outside BMP \u{1f600}"),
543 "unescaped unicode outside BMP \u{1f600}"
544 );
545 }
546
547 #[test]
548 fn it_dedents_block_strings() {
549 assert_eq!(
550 unescape_block_string(" intact whitespace with one line "),
551 " intact whitespace with one line "
552 );
553
554 assert_eq!(
555 unescape_block_string(
556 r"
557 This is
558 indented
559 quite a lot
560 "
561 ),
562 r"This is
563indented
564quite a lot"
565 );
566
567 assert_eq!(
568 unescape_block_string(
569 r"
570
571 spans
572 multiple
573 lines
574
575 "
576 ),
577 r"spans
578 multiple
579 lines"
580 );
581 }
582}