dprint_plugin_json/generation/
generate.rs

1use super::super::configuration::Configuration;
2use super::context::Context;
3use super::token_finder::TokenFinder;
4use dprint_core::formatting::conditions::if_true_or;
5use dprint_core::formatting::ir_helpers::SingleLineOptions;
6use dprint_core::formatting::*;
7use dprint_core_macros::sc;
8use jsonc_parser::ast::*;
9use jsonc_parser::common::Range;
10use jsonc_parser::common::Ranged;
11use jsonc_parser::tokens::TokenAndRange;
12use std::collections::HashSet;
13use std::rc::Rc;
14use text_lines::TextLines;
15
16use crate::configuration::*;
17
18pub fn generate(
19  parse_result: jsonc_parser::ParseResult,
20  text: &str,
21  config: &Configuration,
22  is_jsonc: bool,
23) -> PrintItems {
24  let comments = parse_result.comments.unwrap();
25  let tokens = parse_result.tokens.unwrap();
26  let node_value = parse_result.value;
27  let text_info = TextLines::new(text);
28  let mut context = Context {
29    config,
30    text,
31    text_info,
32    is_jsonc,
33    handled_comments: HashSet::new(),
34    parent_stack: Vec::new(),
35    current_node: None,
36    comments: &comments,
37    token_finder: TokenFinder::new(&tokens),
38  };
39
40  let mut items = PrintItems::new();
41  if let Some(node_value) = &node_value {
42    items.extend(gen_node(node_value.into(), &mut context));
43    items.extend(gen_trailing_comments_as_statements(node_value, &mut context));
44  } else if let Some(comments) = comments.get(&0) {
45    items.extend(gen_comments_as_statements(comments.iter(), None, &mut context));
46  }
47  items.push_condition(conditions::if_true(
48    "endOfFileNewLine",
49    Rc::new(|context| Some(context.writer_info.column_number > 0 || context.writer_info.line_number > 0)),
50    Signal::NewLine.into(),
51  ));
52
53  items
54}
55
56fn gen_node<'a>(node: Node<'a, 'a>, context: &mut Context<'a, '_>) -> PrintItems {
57  gen_node_with_inner(node, context, |items, _| items)
58}
59
60fn gen_node_with_inner<'a>(
61  node: Node<'a, 'a>,
62  context: &mut Context<'a, '_>,
63  inner_gen: impl FnOnce(PrintItems, &mut Context<'a, '_>) -> PrintItems,
64) -> PrintItems {
65  // store info
66  let past_current_node = context.current_node.replace(node);
67  let parent_end = past_current_node.as_ref().map(|n| n.end());
68  let node_end = node.end();
69  let is_root = past_current_node.is_none();
70
71  if let Some(past_current_node) = past_current_node {
72    context.parent_stack.push(past_current_node);
73  }
74
75  // generate item
76  let mut items = PrintItems::new();
77
78  // get the leading comments
79  if let Some(comments) = context.comments.get(&node.start()) {
80    items.extend(gen_comments_as_leading(&node, comments.iter(), context));
81  }
82
83  // generate the node
84  if has_ignore_comment(&node, context) {
85    items.push_force_current_line_indentation();
86    items.extend(inner_gen(
87      ir_helpers::gen_from_raw_string(node.text(context.text)),
88      context,
89    ));
90  } else {
91    items.extend(inner_gen(gen_node_inner(&node, context), context))
92  }
93
94  // get the trailing comments
95  if is_root || parent_end.is_some() && parent_end.unwrap() != node_end {
96    if let Some(comments) = context.comments.get(&node_end) {
97      items.extend(gen_comments_as_trailing(&node, comments.iter(), context));
98    }
99  }
100
101  context.current_node = context.parent_stack.pop();
102
103  return items;
104
105  #[inline]
106  fn gen_node_inner<'a>(node: &Node<'a, 'a>, context: &mut Context<'a, '_>) -> PrintItems {
107    match node {
108      Node::Array(node) => gen_array(node, context),
109      Node::BooleanLit(node) => node.value.to_string().into(),
110      Node::NullKeyword(_) => "null".into(),
111      Node::NumberLit(node) => node.value.to_string().into(),
112      Node::Object(node) => gen_object(node, context),
113      Node::ObjectProp(node) => gen_object_prop(node, context),
114      Node::StringLit(node) => gen_string_lit(node, context),
115      Node::WordLit(node) => gen_word_lit(node, context),
116    }
117  }
118}
119
120fn gen_array<'a>(node: &'a Array<'a>, context: &mut Context<'a, '_>) -> PrintItems {
121  let force_multi_lines = !context.config.array_prefer_single_line
122    && (should_break_up_single_line(node, context)
123      || context.text_info.line_index(node.start())
124        < node
125          .elements
126          .first()
127          .map(|p| context.text_info.line_index(p.start()))
128          .unwrap_or_else(|| context.text_info.line_index(node.start())));
129
130  gen_surrounded_by_tokens(
131    |context| {
132      let mut items = PrintItems::new();
133      items.extend(gen_comma_separated_values(
134        GenCommaSeparatedValuesOptions {
135          nodes: node.elements.iter().map(|x| Some(x.into())).collect(),
136          prefer_hanging: false,
137          force_use_new_lines: force_multi_lines,
138          allow_blank_lines: true,
139          single_line_space_at_start: false,
140          single_line_space_at_end: false,
141          custom_single_line_separator: None,
142          multi_line_options: ir_helpers::MultiLineOptions::surround_newlines_indented(),
143          force_possible_newline_at_start: false,
144        },
145        context,
146      ));
147      items
148    },
149    GenSurroundedByTokensOptions {
150      open_token: sc!("["),
151      close_token: sc!("]"),
152      range: node.range,
153      first_member: node.elements.first().map(|f| f.range()),
154      prefer_single_line_when_empty: true,
155    },
156    context,
157  )
158}
159
160fn gen_object<'a>(obj: &'a Object, context: &mut Context<'a, '_>) -> PrintItems {
161  let force_multi_lines = !context.config.object_prefer_single_line
162    && (should_break_up_single_line(obj, context)
163      || context.text_info.line_index(obj.start())
164        < obj
165          .properties
166          .first()
167          .map(|p| context.text_info.line_index(p.start()))
168          .unwrap_or_else(|| context.text_info.line_index(obj.end())));
169
170  gen_surrounded_by_tokens(
171    |context| {
172      let mut items = PrintItems::new();
173      items.extend(gen_comma_separated_values(
174        GenCommaSeparatedValuesOptions {
175          nodes: obj.properties.iter().map(|x| Some(Node::ObjectProp(x))).collect(),
176          prefer_hanging: false,
177          force_use_new_lines: force_multi_lines,
178          allow_blank_lines: true,
179          single_line_space_at_start: true,
180          single_line_space_at_end: true,
181          custom_single_line_separator: None,
182          multi_line_options: ir_helpers::MultiLineOptions::surround_newlines_indented(),
183          force_possible_newline_at_start: false,
184        },
185        context,
186      ));
187      items
188    },
189    GenSurroundedByTokensOptions {
190      open_token: sc!("{"),
191      close_token: sc!("}"),
192      range: obj.range,
193      first_member: obj.properties.first().map(|f| f.range()),
194      prefer_single_line_when_empty: false,
195    },
196    context,
197  )
198}
199
200fn gen_object_prop<'a>(node: &'a ObjectProp, context: &mut Context<'a, '_>) -> PrintItems {
201  let mut items = PrintItems::new();
202  items.extend(gen_node((&node.name).into(), context));
203  items.push_sc(sc!(": "));
204  items.extend(gen_node((&node.value).into(), context));
205
206  items
207}
208
209const DOUBLE_QUOTE_SC: &'static StringContainer = sc!("\"");
210
211fn gen_string_lit<'a>(node: &'a StringLit, context: &mut Context<'a, '_>) -> PrintItems {
212  let text = node.text(context.text);
213  let is_double_quotes = text.starts_with('"');
214  let mut items = PrintItems::new();
215  let text = &text[1..text.len() - 1];
216  items.push_sc(DOUBLE_QUOTE_SC);
217  if is_double_quotes {
218    items.push_string(text.to_string());
219  } else {
220    let text = text.replace("\\'", "'");
221    items.push_string(text.replace('"', "\\\""));
222  }
223  items.push_sc(DOUBLE_QUOTE_SC);
224  items
225}
226
227fn gen_word_lit<'a>(node: &'a WordLit<'a>, _: &mut Context<'a, '_>) -> PrintItems {
228  // this will be a property name that's not a string literal
229  let mut items = PrintItems::new();
230  items.push_sc(DOUBLE_QUOTE_SC);
231  items.push_string(node.value.to_string());
232  items.push_sc(DOUBLE_QUOTE_SC);
233  items
234}
235
236struct GenCommaSeparatedValuesOptions<'a> {
237  nodes: Vec<Option<Node<'a, 'a>>>,
238  prefer_hanging: bool,
239  force_use_new_lines: bool,
240  allow_blank_lines: bool,
241  single_line_space_at_start: bool,
242  single_line_space_at_end: bool,
243  custom_single_line_separator: Option<PrintItems>,
244  multi_line_options: ir_helpers::MultiLineOptions,
245  force_possible_newline_at_start: bool,
246}
247
248fn gen_comma_separated_values<'a>(
249  opts: GenCommaSeparatedValuesOptions<'a>,
250  context: &mut Context<'a, '_>,
251) -> PrintItems {
252  let nodes = opts.nodes;
253  let indent_width = context.config.indent_width;
254  let compute_lines_span = opts.allow_blank_lines && opts.force_use_new_lines; // save time otherwise
255  ir_helpers::gen_separated_values(
256    |is_multi_line_or_hanging_ref| {
257      let mut generated_nodes = Vec::new();
258      let nodes_count = nodes.len();
259      for (i, value) in nodes.into_iter().enumerate() {
260        let (allow_inline_multi_line, allow_inline_single_line) = if let Some(value) = &value {
261          (value.kind() == NodeKind::Object, false)
262        } else {
263          (false, false)
264        };
265        let lines_span = if compute_lines_span {
266          value.as_ref().map(|x| ir_helpers::LinesSpan {
267            start_line: context.start_line_with_comments(x),
268            end_line: context.end_line_with_comments(x),
269          })
270        } else {
271          None
272        };
273        let items = ir_helpers::new_line_group({
274          let is_final_node = i == nodes_count - 1;
275          let use_comma_for_last = !is_final_node
276            || match context.config.trailing_commas {
277              TrailingCommaKind::Always => true,
278              TrailingCommaKind::Maintain => match &value {
279                Some(value) => context.token_finder.get_next_token_if_comma(&value.range()).is_some(),
280                None => false,
281              },
282              TrailingCommaKind::Jsonc => context.is_jsonc,
283              TrailingCommaKind::Never => false,
284            };
285          let maybe_comma = if !is_final_node {
286            ",".into()
287          } else if use_comma_for_last {
288            let is_multi_line = is_multi_line_or_hanging_ref.create_resolver();
289            if_true_or("is_multi_line", is_multi_line, ",".into(), PrintItems::new()).into()
290          } else {
291            PrintItems::new()
292          };
293          gen_comma_separated_value(value, maybe_comma, context)
294        });
295        generated_nodes.push(ir_helpers::GeneratedValue {
296          items,
297          lines_span,
298          allow_inline_multi_line,
299          allow_inline_single_line,
300        });
301      }
302
303      generated_nodes
304    },
305    ir_helpers::GenSeparatedValuesOptions {
306      prefer_hanging: opts.prefer_hanging,
307      force_use_new_lines: opts.force_use_new_lines,
308      allow_blank_lines: opts.allow_blank_lines,
309      single_line_options: SingleLineOptions {
310        space_at_start: opts.single_line_space_at_start,
311        space_at_end: opts.single_line_space_at_end,
312        separator: opts
313          .custom_single_line_separator
314          .unwrap_or_else(|| Signal::SpaceOrNewLine.into()),
315      },
316      indent_width,
317      multi_line_options: opts.multi_line_options,
318      force_possible_newline_at_start: opts.force_possible_newline_at_start,
319    },
320  )
321  .items
322}
323
324fn gen_comma_separated_value<'a>(
325  value: Option<Node<'a, 'a>>,
326  generated_comma: PrintItems,
327  context: &mut Context<'a, '_>,
328) -> PrintItems {
329  let mut items = PrintItems::new();
330  let comma_token = get_comma_token(&value, context);
331
332  if let Some(element) = value {
333    let generated_comma = generated_comma.into_rc_path();
334    items.extend(gen_node_with_inner(element, context, move |mut items, _| {
335      // this Rc clone is necessary because we can't move the captured generated_comma out of this closure
336      items.push_optional_path(generated_comma);
337      items
338    }));
339  } else {
340    items.extend(generated_comma);
341  }
342
343  // get the trailing comments after the comma token
344  if let Some(comma_token) = comma_token {
345    items.extend(gen_trailing_comments(comma_token, context));
346  }
347
348  return items;
349
350  fn get_comma_token<'a, 'b>(element: &Option<Node>, context: &mut Context<'a, 'b>) -> Option<&'b TokenAndRange<'a>> {
351    if let Some(element) = element {
352      context.token_finder.get_next_token_if_comma(element)
353    } else {
354      None
355    }
356  }
357}
358
359struct GenSurroundedByTokensOptions {
360  open_token: &'static StringContainer,
361  close_token: &'static StringContainer,
362  range: Range,
363  first_member: Option<Range>,
364  prefer_single_line_when_empty: bool,
365}
366
367fn gen_surrounded_by_tokens<'a, 'b>(
368  gen_inner: impl FnOnce(&mut Context<'a, 'b>) -> PrintItems,
369  opts: GenSurroundedByTokensOptions,
370  context: &mut Context<'a, 'b>,
371) -> PrintItems {
372  let open_token_end = opts.range.start + opts.open_token.text.len();
373  let close_token_start = opts.range.end - opts.close_token.text.len();
374
375  // assert the tokens are in the place the caller says they are
376  #[cfg(debug_assertions)]
377  context.assert_text(opts.range.start, open_token_end, opts.open_token.text);
378  #[cfg(debug_assertions)]
379  context.assert_text(close_token_start, opts.range.end, opts.close_token.text);
380
381  // generate
382  let mut items = PrintItems::new();
383  let open_token_start_line = context.text_info.line_index(opts.range.start);
384
385  items.push_sc(opts.open_token);
386  if let Some(first_member) = opts.first_member {
387    let first_member_start_line = context.text_info.line_index(first_member.start);
388    if open_token_start_line < first_member_start_line {
389      if let Some(trailing_comments) = context.comments.get(&open_token_end) {
390        items.extend(gen_first_line_trailing_comment(
391          open_token_start_line,
392          trailing_comments.iter(),
393          context,
394        ));
395      }
396    }
397    items.extend(gen_inner(context));
398
399    let before_trailing_comments_lc = LineAndColumn::new("beforeTrailingComments");
400    items.push_line_and_column(before_trailing_comments_lc);
401    items.extend(ir_helpers::with_indent(gen_trailing_comments_as_statements(
402      &Range::from_byte_index(open_token_end),
403      context,
404    )));
405    if let Some(leading_comments) = context.comments.get(&close_token_start) {
406      items.extend(ir_helpers::with_indent(gen_comments_as_statements(
407        leading_comments.iter(),
408        None,
409        context,
410      )));
411    }
412    items.push_condition(conditions::if_true(
413      "newLineIfHasCommentsAndNotStartOfNewLine",
414      Rc::new(move |context| {
415        let had_comments = !condition_helpers::is_at_same_position(context, before_trailing_comments_lc)?;
416        Some(had_comments && !context.writer_info.is_start_of_line())
417      }),
418      Signal::NewLine.into(),
419    ));
420  } else {
421    let range_end_line = context.text_info.line_index(opts.range.end);
422    let is_single_line = open_token_start_line == range_end_line;
423    if let Some(comments) = context.comments.get(&open_token_end) {
424      // generate the trailing comment on the first line only if multi-line and if a comment line
425      if !is_single_line {
426        items.extend(gen_first_line_trailing_comment(
427          open_token_start_line,
428          comments.iter(),
429          context,
430        ));
431      }
432
433      // generate the comments
434      if has_unhandled_comment(comments.iter(), context) {
435        if is_single_line {
436          let indent_width = context.config.indent_width;
437          items.extend(
438            ir_helpers::gen_separated_values(
439              |_| {
440                let mut generated_comments = Vec::new();
441                for c in comments.iter() {
442                  let start_line = context.text_info.line_index(c.start());
443                  let end_line = context.text_info.line_index(c.end());
444                  if let Some(items) = gen_comment(c, context) {
445                    generated_comments.push(ir_helpers::GeneratedValue {
446                      items,
447                      lines_span: Some(ir_helpers::LinesSpan { start_line, end_line }),
448                      allow_inline_multi_line: false,
449                      allow_inline_single_line: false,
450                    });
451                  }
452                }
453                generated_comments
454              },
455              ir_helpers::GenSeparatedValuesOptions {
456                prefer_hanging: false,
457                force_use_new_lines: !is_single_line,
458                allow_blank_lines: true,
459                single_line_options: ir_helpers::SingleLineOptions {
460                  space_at_start: false,
461                  space_at_end: false,
462                  separator: Signal::SpaceOrNewLine.into(),
463                },
464                indent_width,
465                multi_line_options: ir_helpers::MultiLineOptions::surround_newlines_indented(),
466                force_possible_newline_at_start: false,
467              },
468            )
469            .items,
470          );
471        } else {
472          items.push_signal(Signal::NewLine);
473          items.extend(ir_helpers::with_indent(gen_comments_as_statements(
474            comments.iter(),
475            None,
476            context,
477          )));
478          items.push_signal(Signal::NewLine);
479        }
480      }
481    } else if !is_single_line && !opts.prefer_single_line_when_empty {
482      items.push_signal(Signal::NewLine);
483    }
484  }
485
486  items.push_sc(opts.close_token);
487
488  return items;
489
490  fn gen_first_line_trailing_comment<'a: 'b, 'b>(
491    open_token_start_line: usize,
492    comments: impl Iterator<Item = &'b Comment<'a>>,
493    context: &mut Context,
494  ) -> PrintItems {
495    let mut items = PrintItems::new();
496    let mut comments = comments;
497    if let Some(first_comment) = comments.next() {
498      if first_comment.kind() == CommentKind::Line
499        && context.text_info.line_index(first_comment.start()) == open_token_start_line
500      {
501        if let Some(generated_comment) = gen_comment(first_comment, context) {
502          items.push_signal(Signal::StartForceNoNewLines);
503          items.push_space();
504          items.extend(generated_comment);
505          items.push_signal(Signal::FinishForceNoNewLines);
506        }
507      }
508    }
509    items
510  }
511}
512
513// Comments
514
515fn has_unhandled_comment<'a: 'b, 'b>(
516  mut comments: impl Iterator<Item = &'b Comment<'a>>,
517  context: &mut Context,
518) -> bool {
519  comments.any(|c| !context.has_handled_comment(c))
520}
521
522fn gen_trailing_comments(node: &dyn Ranged, context: &mut Context) -> PrintItems {
523  if let Some(trailing_comments) = context.comments.get(&node.end()) {
524    gen_comments_as_trailing(node, trailing_comments.iter(), context)
525  } else {
526    PrintItems::new()
527  }
528}
529
530fn gen_trailing_comments_as_statements(node: &dyn Ranged, context: &mut Context) -> PrintItems {
531  let unhandled_comments = get_trailing_comments_as_statements(node, context);
532  gen_comments_as_statements(unhandled_comments.into_iter(), Some(node), context)
533}
534
535fn get_trailing_comments_as_statements<'a, 'b>(
536  node: &dyn Ranged,
537  context: &mut Context<'a, 'b>,
538) -> Vec<&'b Comment<'a>> {
539  let mut comments = Vec::new();
540  let node_end_line = context.text_info.line_index(node.end());
541  if let Some(trailing_comments) = context.comments.get(&node.end()) {
542    for comment in trailing_comments.iter() {
543      if !context.has_handled_comment(comment) && node_end_line < context.text_info.line_index(comment.end()) {
544        comments.push(comment);
545      }
546    }
547  }
548  comments
549}
550
551fn gen_comments_as_statements<'a: 'b, 'b>(
552  comments: impl Iterator<Item = &'b Comment<'a>>,
553  last_node: Option<&dyn Ranged>,
554  context: &mut Context<'a, 'b>,
555) -> PrintItems {
556  let mut last_node = last_node;
557  let mut items = PrintItems::new();
558  for comment in comments {
559    if !context.has_handled_comment(comment) {
560      items.extend(gen_comment_based_on_last_node(
561        comment,
562        &last_node,
563        GenCommentBasedOnLastNodeOptions {
564          separate_with_newlines: true,
565        },
566        context,
567      ));
568      last_node = Some(comment);
569    }
570  }
571  items
572}
573
574fn gen_comments_as_leading<'a: 'b, 'b>(
575  node: &dyn Ranged,
576  comments: impl Iterator<Item = &'b Comment<'a>>,
577  context: &mut Context,
578) -> PrintItems {
579  let mut items = PrintItems::new();
580  let comments = comments.filter(|c| !context.has_handled_comment(c)).collect::<Vec<_>>();
581
582  if !comments.is_empty() {
583    let last_comment = comments.last().unwrap();
584    let last_comment_end_line = context.text_info.line_index(last_comment.end());
585    let last_comment_kind = last_comment.kind();
586    items.extend(gen_comment_collection(comments.into_iter(), None, Some(node), context));
587
588    let node_start_line = context.text_info.line_index(node.start());
589    if node_start_line > last_comment_end_line {
590      items.push_signal(Signal::NewLine);
591
592      if node_start_line - 1 > last_comment_end_line {
593        items.push_signal(Signal::NewLine);
594      }
595    } else if last_comment_kind == CommentKind::Block && node_start_line == last_comment_end_line {
596      items.push_signal(Signal::SpaceIfNotTrailing);
597    }
598  }
599
600  items
601}
602
603fn gen_comments_as_trailing<'a: 'b, 'b>(
604  node: &dyn Ranged,
605  comments: impl Iterator<Item = &'b Comment<'a>>,
606  context: &mut Context,
607) -> PrintItems {
608  // use the roslyn definition of trailing comments
609  let node_end_line = context.text_info.line_index(node.end());
610  let trailing_comments_on_same_line = comments
611    .filter(|c| context.text_info.line_index(c.start()) <= node_end_line)
612    .collect::<Vec<_>>();
613
614  let first_unhandled_comment = trailing_comments_on_same_line
615    .iter()
616    .find(|c| !context.has_handled_comment(c));
617  let mut items = PrintItems::new();
618
619  if let Some(Comment::Block(_)) = first_unhandled_comment {
620    items.push_space();
621  }
622
623  items.extend(gen_comment_collection(
624    trailing_comments_on_same_line.into_iter(),
625    Some(node),
626    None,
627    context,
628  ));
629
630  items
631}
632
633fn gen_comment_collection<'a: 'b, 'b>(
634  comments: impl Iterator<Item = &'b Comment<'a>>,
635  last_node: Option<&dyn Ranged>,
636  next_node: Option<&dyn Ranged>,
637  context: &mut Context,
638) -> PrintItems {
639  let mut last_node = last_node;
640  let mut items = PrintItems::new();
641  let next_node_start_line = next_node.map(|n| context.text_info.line_index(n.start()));
642
643  for comment in comments {
644    if !context.has_handled_comment(comment) {
645      items.extend(gen_comment_based_on_last_node(
646        comment,
647        &last_node,
648        GenCommentBasedOnLastNodeOptions {
649          separate_with_newlines: if let Some(next_node_start_line) = next_node_start_line {
650            context.text_info.line_index(comment.start()) != next_node_start_line
651          } else {
652            false
653          },
654        },
655        context,
656      ));
657      last_node = Some(comment);
658    }
659  }
660
661  items
662}
663
664struct GenCommentBasedOnLastNodeOptions {
665  separate_with_newlines: bool,
666}
667
668fn gen_comment_based_on_last_node(
669  comment: &Comment,
670  last_node: &Option<&dyn Ranged>,
671  opts: GenCommentBasedOnLastNodeOptions,
672  context: &mut Context,
673) -> PrintItems {
674  let mut items = PrintItems::new();
675  let mut pushed_ignore_new_lines = false;
676
677  if let Some(last_node) = last_node {
678    let comment_start_line = context.text_info.line_index(comment.start());
679    let last_node_end_line = context.text_info.line_index(last_node.end());
680
681    if opts.separate_with_newlines || comment_start_line > last_node_end_line {
682      items.push_signal(Signal::NewLine);
683
684      if comment_start_line > last_node_end_line + 1 {
685        items.push_signal(Signal::NewLine);
686      }
687    } else if comment.kind() == CommentKind::Line {
688      items.push_signal(Signal::StartForceNoNewLines);
689      items.push_space();
690      pushed_ignore_new_lines = true;
691    } else if last_node.text(context.text).starts_with("/*") {
692      items.push_space();
693    }
694  }
695
696  if let Some(generated_comment) = gen_comment(comment, context) {
697    items.extend(generated_comment);
698  }
699
700  if pushed_ignore_new_lines {
701    items.push_signal(Signal::FinishForceNoNewLines);
702  }
703
704  items
705}
706
707fn gen_comment(comment: &Comment, context: &mut Context) -> Option<PrintItems> {
708  // only generate if handled
709  if context.has_handled_comment(comment) {
710    return None;
711  }
712
713  // mark handled and generate
714  context.mark_comment_handled(comment);
715  Some(match comment {
716    Comment::Block(comment) => ir_helpers::gen_js_like_comment_block(comment.text),
717    Comment::Line(comment) => {
718      ir_helpers::gen_js_like_comment_line(comment.text, context.config.comment_line_force_space_after_slashes)
719    }
720  })
721}
722
723fn has_ignore_comment(node: &dyn Ranged, context: &Context) -> bool {
724  if let Some(last_comment) = context.comments.get(&(node.start())).and_then(|c| c.last()) {
725    ir_helpers::text_has_dprint_ignore(last_comment.text(), &context.config.ignore_node_comment_text)
726  } else {
727    false
728  }
729}
730
731fn should_break_up_single_line(ranged: &impl Ranged, context: &Context) -> bool {
732  // This is a massive performance improvement when formatting huge single line files.
733  // Basically, if the node is on a single line and will for sure format as multi-line, then
734  // say it's multi-line right away and avoid creating print items to figure that out.
735  let range = ranged.range();
736
737  // Obviously this line_width * 2 is not always accurate as it doesn't take into account whitespace,
738  // but will provide a good enough and fast way to quickly tell if it's long without having basically
739  // any false positives (unless someone is being silly).
740  context.text_info.line_index(range.start) == context.text_info.line_index(range.end)
741    && range.width() > (context.config.line_width * 2) as usize
742}