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::DirectiveExtension(it) => it.name(),
102 Self::ScalarTypeExtension(it) => it.name(),
103 Self::ObjectTypeExtension(it) => it.name(),
104 Self::InterfaceTypeExtension(it) => it.name(),
105 Self::UnionTypeExtension(it) => it.name(),
106 Self::EnumTypeExtension(it) => it.name(),
107 Self::InputObjectTypeExtension(it) => it.name(),
108 }
109 }
110
111 pub fn kind(&self) -> &'static str {
112 match self {
113 cst::Definition::OperationDefinition(_) => "OperationDefinition",
114 cst::Definition::FragmentDefinition(_) => "FragmentDefinition",
115 cst::Definition::DirectiveDefinition(_) => "DirectiveDefinition",
116 cst::Definition::ScalarTypeDefinition(_) => "ScalarTypeDefinition",
117 cst::Definition::ObjectTypeDefinition(_) => "ObjectTypeDefinition",
118 cst::Definition::InterfaceTypeDefinition(_) => "InterfaceTypeDefinition",
119 cst::Definition::UnionTypeDefinition(_) => "UnionTypeDefinition",
120 cst::Definition::EnumTypeDefinition(_) => "EnumTypeDefinition",
121 cst::Definition::InputObjectTypeDefinition(_) => "InputObjectTypeDefinition",
122 cst::Definition::SchemaDefinition(_) => "SchemaDefinition",
123 cst::Definition::SchemaExtension(_) => "SchemaExtension",
124 cst::Definition::DirectiveExtension(_) => "DirectiveExtension",
125 cst::Definition::ScalarTypeExtension(_) => "ScalarTypeExtension",
126 cst::Definition::ObjectTypeExtension(_) => "ObjectTypeExtension",
127 cst::Definition::InterfaceTypeExtension(_) => "InterfaceTypeExtension",
128 cst::Definition::UnionTypeExtension(_) => "UnionTypeExtension",
129 cst::Definition::EnumTypeExtension(_) => "EnumTypeExtension",
130 cst::Definition::InputObjectTypeExtension(_) => "InputObjectTypeExtension",
131 }
132 }
133
134 pub fn is_executable_definition(&self) -> bool {
135 matches!(
136 self,
137 Self::OperationDefinition(_) | Self::FragmentDefinition(_)
138 )
139 }
140
141 pub fn is_extension_definition(&self) -> bool {
142 matches!(
143 self,
144 Self::SchemaExtension(_)
145 | Self::DirectiveExtension(_)
146 | Self::ScalarTypeExtension(_)
147 | Self::ObjectTypeExtension(_)
148 | Self::InterfaceTypeExtension(_)
149 | Self::UnionTypeExtension(_)
150 | Self::EnumTypeExtension(_)
151 | Self::InputObjectTypeExtension(_)
152 )
153 }
154}
155
156impl From<cst::StringValue> for String {
157 fn from(val: cst::StringValue) -> Self {
158 Self::from(&val)
159 }
160}
161
162fn unescape_string(input: &str) -> String {
166 let mut output = String::with_capacity(input.len());
167
168 let mut iter = input.chars();
169 while let Some(c) = iter.next() {
170 match c {
171 '\\' => {
172 let Some(c2) = iter.next() else {
173 output.push(c);
174 break;
175 };
176
177 match c2 {
178 '"' | '\\' | '/' => output.push(c2),
179 'b' => output.push('\u{0008}'),
180 'f' => output.push('\u{000c}'),
181 'n' => output.push('\n'),
182 'r' => output.push('\r'),
183 't' => output.push('\t'),
184 'u' => {
185 let (value, length) = crate::lexer::unicode_escape(iter.as_str())
186 .expect("unicode escapes have been validated by the lexer");
187 iter.nth(length - 1);
188 output.push(value);
189 }
190 _ => (),
191 }
192 }
193 _ => output.push(c),
194 }
195 }
196
197 output
198}
199
200const ESCAPED_TRIPLE_QUOTE: &str = r#"\""""#;
201const TRIPLE_QUOTE: &str = r#"""""#;
202
203fn is_block_string(input: &str) -> bool {
204 input.starts_with(TRIPLE_QUOTE)
205}
206
207struct GraphQLLines<'a> {
210 input: &'a str,
211 finished: bool,
212}
213
214impl<'a> GraphQLLines<'a> {
215 fn new(input: &'a str) -> Self {
216 Self {
217 input,
218 finished: false,
219 }
220 }
221}
222
223impl<'a> Iterator for GraphQLLines<'a> {
224 type Item = &'a str;
225 fn next(&mut self) -> Option<Self::Item> {
226 if self.finished {
229 return None;
230 }
231
232 let Some(index) = memchr::memchr2(b'\r', b'\n', self.input.as_bytes()) else {
233 self.finished = true;
234 return Some(self.input);
235 };
236 let line = &self.input[..index];
237 let rest = match self.input.get(index..=index + 1) {
238 Some("\r\n") => &self.input[index + 2..],
239 _ => &self.input[index + 1..],
240 };
241 self.input = rest;
242 Some(line)
243 }
244}
245
246fn split_lines(input: &str) -> impl Iterator<Item = &str> {
248 GraphQLLines::new(input)
249}
250
251fn replace_into(input: &str, pattern: &str, replace: &str, output: &mut String) {
255 let mut last_index = 0;
256 for index in memchr::memmem::find_iter(input.as_bytes(), pattern.as_bytes()) {
257 output.push_str(&input[last_index..index]);
258 output.push_str(replace);
259 last_index = index + pattern.len();
260 }
261 if last_index < input.len() {
262 output.push_str(&input[last_index..]);
263 }
264}
265
266fn unescape_block_string(raw_value: &str) -> String {
272 fn is_whitespace(c: char) -> bool {
274 matches!(c, ' ' | '\t')
275 }
276 fn is_whitespace_line(line: &str) -> bool {
278 line.chars().all(is_whitespace)
279 }
280 fn count_indent(line: &str) -> usize {
282 line.chars().take_while(|&c| is_whitespace(c)).count()
283 }
284
285 let common_indent = split_lines(raw_value)
289 .skip(1)
291 .filter_map(|line| {
292 let length = line.len();
296 let indent = count_indent(line);
298 (indent < length).then_some(indent)
300 })
301 .min()
302 .unwrap_or(0);
303
304 let mut lines = split_lines(raw_value)
305 .enumerate()
306 .map(|(index, line)| {
308 if index == 0 {
310 line
311 } else {
312 &line[common_indent.min(line.len())..]
314 }
315 })
316 .skip_while(|line| is_whitespace_line(line));
319
320 let mut formatted = String::with_capacity(raw_value.len());
324
325 if let Some(line) = lines.next() {
328 replace_into(line, ESCAPED_TRIPLE_QUOTE, TRIPLE_QUOTE, &mut formatted);
330 };
331
332 let mut final_char_index = formatted.len();
333
334 for line in lines {
336 formatted.push('\n');
338 replace_into(line, ESCAPED_TRIPLE_QUOTE, TRIPLE_QUOTE, &mut formatted);
340
341 if !is_whitespace_line(line) {
343 final_char_index = formatted.len();
344 }
345 }
346
347 formatted.truncate(final_char_index);
349
350 formatted
352}
353
354impl From<&'_ cst::StringValue> for String {
356 fn from(val: &'_ cst::StringValue) -> Self {
357 let text = text_of_first_token(val.syntax());
358 if is_block_string(&text) {
361 unescape_block_string(&text[3..text.len() - 3])
362 } else {
363 unescape_string(&text[1..text.len() - 1])
364 }
365 }
366}
367
368impl TryFrom<cst::IntValue> for i32 {
369 type Error = ParseIntError;
370
371 fn try_from(val: cst::IntValue) -> Result<Self, Self::Error> {
372 Self::try_from(&val)
373 }
374}
375
376impl TryFrom<&'_ cst::IntValue> for i32 {
377 type Error = ParseIntError;
378
379 fn try_from(val: &'_ cst::IntValue) -> Result<Self, Self::Error> {
380 let text = text_of_first_token(val.syntax());
381 text.parse()
382 }
383}
384
385impl TryFrom<cst::IntValue> for f64 {
386 type Error = ParseFloatError;
387
388 fn try_from(val: cst::IntValue) -> Result<Self, Self::Error> {
389 Self::try_from(&val)
390 }
391}
392
393impl TryFrom<&'_ cst::IntValue> for f64 {
394 type Error = ParseFloatError;
395
396 fn try_from(val: &'_ cst::IntValue) -> Result<Self, Self::Error> {
397 let text = text_of_first_token(val.syntax());
398 text.parse()
399 }
400}
401
402impl TryFrom<cst::FloatValue> for f64 {
403 type Error = ParseFloatError;
404
405 fn try_from(val: cst::FloatValue) -> Result<Self, Self::Error> {
406 Self::try_from(&val)
407 }
408}
409
410impl TryFrom<&'_ cst::FloatValue> for f64 {
411 type Error = ParseFloatError;
412
413 fn try_from(val: &'_ cst::FloatValue) -> Result<Self, Self::Error> {
414 let text = text_of_first_token(val.syntax());
415 text.parse()
416 }
417}
418
419impl TryFrom<cst::BooleanValue> for bool {
420 type Error = std::str::ParseBoolError;
421
422 fn try_from(val: cst::BooleanValue) -> Result<Self, Self::Error> {
423 Self::try_from(&val)
424 }
425}
426
427impl TryFrom<&'_ cst::BooleanValue> for bool {
428 type Error = std::str::ParseBoolError;
429
430 fn try_from(val: &'_ cst::BooleanValue) -> Result<Self, Self::Error> {
431 let text = text_of_first_token(val.syntax());
432 text.parse()
433 }
434}
435
436fn text_of_first_token(node: &SyntaxNode) -> TokenText {
437 let first_token = node
438 .green()
439 .children()
440 .next()
441 .and_then(|it| it.into_token())
442 .unwrap()
443 .to_owned();
444
445 TokenText(first_token)
446}
447
448#[cfg(test)]
449mod string_tests {
450 use super::unescape_string;
451
452 #[test]
453 fn it_parses_strings() {
454 assert_eq!(unescape_string(r"simple"), "simple");
455 assert_eq!(unescape_string(r" white space "), " white space ");
456 }
457
458 #[test]
459 fn it_unescapes_strings() {
460 assert_eq!(unescape_string(r#"quote \""#), "quote \"");
461 assert_eq!(
462 unescape_string(r"escaped \n\r\b\t\f"),
463 "escaped \n\r\u{0008}\t\u{000c}"
464 );
465 assert_eq!(unescape_string(r"slashes \\ \/"), r"slashes \ /");
466 assert_eq!(
467 unescape_string("unescaped unicode outside BMP 😀"),
468 "unescaped unicode outside BMP 😀"
469 );
470 assert_eq!(
471 unescape_string(r"unicode \u1234\u5678\u90AB\uCDEF"),
472 "unicode \u{1234}\u{5678}\u{90AB}\u{CDEF}"
473 );
474 }
475}
476
477#[cfg(test)]
478mod block_string_tests {
479 use super::split_lines;
480 use super::unescape_block_string;
481
482 #[test]
483 fn it_splits_lines_by_graphql_newline_definition() {
484 let plain_newlines: Vec<_> = split_lines(
485 r#"source text
486 with some
487 new
488
489
490 lines
491 "#,
492 )
493 .collect();
494
495 assert_eq!(
496 plain_newlines,
497 [
498 "source text",
499 " with some",
500 " new",
501 "",
502 "",
503 " lines",
504 " ",
505 ]
506 );
507
508 let different_endings: Vec<_> =
509 split_lines("with\nand\r\nand\rall in the same\r\nstring").collect();
510 assert_eq!(
511 different_endings,
512 ["with", "and", "and", "all in the same", "string",]
513 );
514
515 let empty_string: Vec<_> = split_lines("").collect();
516 assert_eq!(empty_string, [""]);
517
518 let empty_line: Vec<_> = split_lines("\n\r\r\n").collect();
519 assert_eq!(empty_line, ["", "", "", ""]);
520 }
521
522 #[test]
523 fn it_normalizes_block_string_newlines() {
524 assert_eq!(unescape_block_string("multi\nline"), "multi\nline");
525 assert_eq!(unescape_block_string("multi\r\nline"), "multi\nline");
526 assert_eq!(unescape_block_string("multi\rline"), "multi\nline");
527 }
528
529 #[test]
530 fn it_does_not_unescape_block_strings() {
531 assert_eq!(
532 unescape_block_string(r"escaped \n\r\b\t\f"),
533 r"escaped \n\r\b\t\f"
534 );
535 assert_eq!(unescape_block_string(r"slashes \\ \/"), r"slashes \\ \/");
536 assert_eq!(
537 unescape_block_string("unescaped unicode outside BMP \u{1f600}"),
538 "unescaped unicode outside BMP \u{1f600}"
539 );
540 }
541
542 #[test]
543 fn it_dedents_block_strings() {
544 assert_eq!(
545 unescape_block_string(" intact whitespace with one line "),
546 " intact whitespace with one line "
547 );
548
549 assert_eq!(
550 unescape_block_string(
551 r"
552 This is
553 indented
554 quite a lot
555 "
556 ),
557 r"This is
558indented
559quite a lot"
560 );
561
562 assert_eq!(
563 unescape_block_string(
564 r"
565
566 spans
567 multiple
568 lines
569
570 "
571 ),
572 r"spans
573 multiple
574 lines"
575 );
576 }
577}