microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.25.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/compiler/qsc_formatter/src/formatter.rs

906lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use qsc_data_structures::span::Span;
5use qsc_frontend::{
6 keyword::Keyword,
7 lex::{
8 Delim, InterpolatedEnding, InterpolatedStart,
9 concrete::{self, ConcreteToken, ConcreteTokenKind},
10 cooked::{ClosedBinOp, StringToken, TokenKind},
11 },
12};
13
14#[cfg(test)]
15mod tests;
16
17// Public functions
18
19/// Applies formatting rules to the give code str and returns
20/// the formatted string.
21#[must_use]
22pub fn format_str(code: &str) -> String {
23 let mut edits = calculate_format_edits(code);
24 edits.sort_by_key(|edit| edit.span.hi); // sort edits by their span's hi value from lowest to highest
25 edits.reverse(); // sort from highest to lowest so that that as edits are applied they don't invalidate later applications of edits
26 let mut new_code = String::from(code);
27
28 for edit in edits {
29 let range = (edit.span.lo as usize)..(edit.span.hi as usize);
30 new_code.replace_range(range, &edit.new_text);
31 }
32
33 new_code
34}
35
36/// Applies formatting rules to the given code str, generating edits where
37/// the source code needs to be changed to comply with the format rules.
38#[must_use]
39pub fn calculate_format_edits(code: &str) -> Vec<TextEdit> {
40 let tokens = concrete::ConcreteTokenIterator::new(code);
41 let mut edits = vec![];
42
43 let mut formatter = Formatter {
44 code,
45 indent_level: 0,
46 delim_newlines_stack: vec![],
47 type_param_state: TypeParameterListState::NoState,
48 spec_decl_state: SpecDeclState::NoState,
49 import_export_state: ImportExportState::NoState,
50 };
51
52 // The sliding window used is over three adjacent tokens
53 #[allow(unused_assignments)]
54 let mut one = None;
55 let mut two = None;
56 let mut three = None;
57
58 for token in tokens {
59 // Advance the token window
60 one = two;
61 two = three;
62 three = Some(token);
63
64 let mut edits_for_triple = match (&one, &two, &three) {
65 (Some(one), Some(two), Some(three)) => {
66 if matches!(one.kind, ConcreteTokenKind::WhiteSpace) {
67 // first token is whitespace, continue scanning
68 continue;
69 } else if matches!(two.kind, ConcreteTokenKind::WhiteSpace) {
70 // whitespace in the middle
71 formatter.apply_rules(one, get_token_contents(code, two), three)
72 } else {
73 // one, two are adjacent tokens with no whitespace in the middle
74 formatter.apply_rules(one, "", two)
75 }
76 }
77 (None, None, Some(three)) => {
78 // Remove any whitespace at the start of a file
79 if matches!(three.kind, ConcreteTokenKind::WhiteSpace) {
80 vec![TextEdit::new("", three.span.lo, three.span.hi)]
81 } else {
82 vec![]
83 }
84 }
85 _ => {
86 // not enough tokens to apply a rule
87 continue;
88 }
89 };
90
91 edits.append(&mut edits_for_triple);
92 }
93
94 edits
95}
96
97// Public types
98
99#[derive(Debug)]
100pub struct TextEdit {
101 pub new_text: String,
102 pub span: Span,
103}
104
105impl TextEdit {
106 fn new(new_text: &str, lo: u32, hi: u32) -> Self {
107 Self {
108 new_text: new_text.to_string(),
109 span: Span { lo, hi },
110 }
111 }
112}
113
114// Private types
115
116/// This is to keep track of whether the formatter is currently
117/// processing a sequence with newline separators or single-space
118/// separator.
119#[derive(Clone, Copy)]
120enum NewlineContext {
121 /// The formatter is not in a sequence, so separators should
122 /// use their default logic: newlines for `;` and single
123 /// spaces for `,`.
124 NoContext,
125 /// In a sequence that uses newline separators.
126 Newlines,
127 /// In a sequence that uses single-space separators.
128 Spaces,
129}
130
131/// This is to keep track of whether or not the formatter
132/// is currently in a callable's type-parameter list. This
133/// is necessary to disambiguate the `<` and `>` characters
134/// that delimit the type-parameter list from the binary
135/// comparison operators.
136#[derive(Clone, Copy)]
137enum TypeParameterListState {
138 /// Not in a type-parameter list.
139 NoState,
140 /// Not in a list but have seen the callable keyword,
141 /// either `function` or `operation`.
142 SeenCallableKeyword,
143 /// Not in a list but have seen the callable identifier.
144 SeenCallableName,
145 /// In the type-parameter list, or have seen the
146 /// starting `<`.
147 InTypeParamList,
148}
149
150/// Whether or not we are currently handling an import or export statement.
151#[derive(Clone, Copy, Debug)]
152enum ImportExportState {
153 /// Yes, this is an import or export statement.
154 HandlingImportExportStatement,
155 /// No, this is not an import or export statement.
156 NoState,
157}
158
159/// This is to keep track of whether or not the formatter
160/// is currently processing a functor specialization
161/// declaration.
162#[derive(Clone, Copy)]
163enum SpecDeclState {
164 /// Not in a location relevant to this state.
165 NoState,
166 /// Formatter is on the specialization keyword.
167 /// (it is the left-hand token)
168 OnSpecKeyword,
169 /// Formatter is on the specialization ellipse.
170 /// (it is the left-hand token)
171 OnEllipse,
172}
173
174/// Enum for a token's status as a delimiter.
175/// `<` and `>` are delimiters only with type-parameter lists,
176/// which is determined using the `TypeParameterListState` enum.
177#[derive(Clone, Copy)]
178enum Delimiter {
179 // The token is an open delimiter. i.e. `{`, `[`, `(`, and sometimes `<`.
180 Open,
181 // The token is a close delimiter. i.e. `}`, `]`, `)`, and sometimes `>`.
182 Close,
183 // The token is not a delimiter.
184 NonDelim,
185}
186
187impl Delimiter {
188 /// Constructs a Delimiter from a token, given the current type-parameter state.
189 fn from_concrete_token_kind(
190 kind: &ConcreteTokenKind,
191 type_param_state: TypeParameterListState,
192 import_export_state: ImportExportState,
193 ) -> Delimiter {
194 match kind {
195 ConcreteTokenKind::Syntax(TokenKind::Open(_)) => Delimiter::Open,
196 ConcreteTokenKind::Syntax(TokenKind::Lt)
197 if matches!(type_param_state, TypeParameterListState::InTypeParamList) =>
198 {
199 Delimiter::Open
200 }
201 ConcreteTokenKind::Syntax(TokenKind::Close(_)) => Delimiter::Close,
202 ConcreteTokenKind::Syntax(TokenKind::Gt)
203 if matches!(type_param_state, TypeParameterListState::InTypeParamList) =>
204 {
205 Delimiter::Close
206 }
207 ConcreteTokenKind::Syntax(TokenKind::Keyword(Keyword::Import | Keyword::Export)) => {
208 Delimiter::Open
209 }
210 ConcreteTokenKind::Syntax(TokenKind::Semi)
211 if matches!(
212 import_export_state,
213 ImportExportState::HandlingImportExportStatement
214 ) =>
215 {
216 Delimiter::Close
217 }
218 _ => Delimiter::NonDelim,
219 }
220 }
221}
222
223struct Formatter<'a> {
224 code: &'a str,
225 indent_level: usize,
226 delim_newlines_stack: Vec<NewlineContext>,
227 type_param_state: TypeParameterListState,
228 spec_decl_state: SpecDeclState,
229 import_export_state: ImportExportState,
230}
231
232#[allow(clippy::too_many_lines)]
233impl Formatter<'_> {
234 fn apply_rules(
235 &mut self,
236 left: &ConcreteToken,
237 whitespace: &str,
238 right: &ConcreteToken,
239 ) -> Vec<TextEdit> {
240 use ConcreteTokenKind::*;
241 use TokenKind::*;
242 use qsc_frontend::keyword::Keyword;
243 use qsc_frontend::lex::cooked::ClosedBinOp;
244
245 let mut edits = vec![];
246 // when we get here, neither left nor right should be whitespace
247
248 let are_newlines_in_spaces = whitespace.contains('\n');
249 let does_right_required_newline = matches!(right.kind, Syntax(cooked_right) if is_newline_keyword_or_ampersat(cooked_right));
250
251 // Save the left token's status as a delimiter before updating the delimiter state
252 let left_delim_state = Delimiter::from_concrete_token_kind(
253 &left.kind,
254 self.type_param_state,
255 self.import_export_state,
256 );
257
258 self.update_spec_decl_state(&left.kind);
259 self.update_type_param_state(&left.kind, &right.kind);
260 self.update_import_export_state(&left.kind);
261
262 // Save the right token's status as a delimiter after updating the delimiter state
263 let right_delim_state = Delimiter::from_concrete_token_kind(
264 &right.kind,
265 self.type_param_state,
266 self.import_export_state,
267 );
268
269 let newline_context = self.update_indent_level(
270 left_delim_state,
271 right_delim_state,
272 are_newlines_in_spaces,
273 does_right_required_newline,
274 matches!(right.kind, Comment),
275 );
276
277 #[allow(clippy::match_same_arms)]
278 match (left.kind, right.kind) {
279 (Comment | Syntax(DocComment), _) => {
280 // remove whitespace at the ends of comments
281 effect_trim_comment(left, &mut edits, self.code);
282 effect_correct_indentation(left, whitespace, right, &mut edits, self.indent_level);
283 }
284 (_, Comment | Syntax(DocComment)) if matches!(left_delim_state, Delimiter::Open) => {
285 effect_correct_indentation(left, whitespace, right, &mut edits, self.indent_level);
286 }
287 (_, Comment | Syntax(DocComment)) => {
288 if are_newlines_in_spaces {
289 effect_correct_indentation(
290 left,
291 whitespace,
292 right,
293 &mut edits,
294 self.indent_level,
295 );
296 }
297 // else do nothing, preserving the user's spaces before the comment
298 }
299 (Syntax(cooked_left), Syntax(cooked_right)) => match (cooked_left, cooked_right) {
300 (ClosedBinOp(ClosedBinOp::Minus), _) | (_, ClosedBinOp(ClosedBinOp::Minus)) => {
301 // This case is used to ignore the spacing around a `-`.
302 // This is done because we currently don't have the architecture
303 // to be able to differentiate between the unary `-` and the binary `-`
304 // which would have different spacing rules.
305 }
306 (_, ClosedBinOp(ClosedBinOp::Star))
307 if matches!(
308 self.import_export_state,
309 ImportExportState::HandlingImportExportStatement
310 ) =>
311 {
312 // if this is a star and we are in an import/export, then it isn't actually a
313 // binop and it's a glob import
314 effect_no_space(left, whitespace, right, &mut edits);
315 }
316 (Semi, _) if matches!(newline_context, NewlineContext::Spaces) => {
317 effect_single_space(left, whitespace, right, &mut edits);
318 }
319 (Semi, _) => {
320 effect_correct_indentation(
321 left,
322 whitespace,
323 right,
324 &mut edits,
325 self.indent_level,
326 );
327 }
328 (_, Semi) => {
329 effect_no_space(left, whitespace, right, &mut edits);
330 }
331 (Open(l), Close(r)) if l == r => {
332 // close empty delimiter blocks, i.e. (), [], {}
333 effect_no_space(left, whitespace, right, &mut edits);
334 }
335 (Lt, Gt)
336 if matches!(
337 self.type_param_state,
338 TypeParameterListState::InTypeParamList
339 ) =>
340 {
341 // close empty delimiter blocks <>
342 effect_no_space(left, whitespace, right, &mut edits);
343 }
344 (_, _)
345 if matches!(left_delim_state, Delimiter::Open)
346 && matches!(newline_context, NewlineContext::Newlines) =>
347 {
348 effect_correct_indentation(
349 left,
350 whitespace,
351 right,
352 &mut edits,
353 self.indent_level,
354 );
355 }
356 (Comma, _) if matches!(newline_context, NewlineContext::Newlines) => {
357 effect_correct_indentation(
358 left,
359 whitespace,
360 right,
361 &mut edits,
362 self.indent_level,
363 );
364 }
365 (Comma, _) => {
366 effect_single_space(left, whitespace, right, &mut edits);
367 }
368 (_, _)
369 if matches!(right_delim_state, Delimiter::Close)
370 && matches!(newline_context, NewlineContext::Newlines) =>
371 {
372 effect_correct_indentation(
373 left,
374 whitespace,
375 right,
376 &mut edits,
377 self.indent_level,
378 );
379 }
380 (Open(Delim::Bracket | Delim::Paren), _)
381 | (_, Close(Delim::Bracket | Delim::Paren)) => {
382 effect_no_space(left, whitespace, right, &mut edits);
383 }
384 (Lt, _) | (_, Gt)
385 if matches!(
386 self.type_param_state,
387 TypeParameterListState::InTypeParamList
388 ) =>
389 {
390 effect_no_space(left, whitespace, right, &mut edits);
391 }
392 (Open(Delim::Brace), _) | (_, Close(Delim::Brace)) => {
393 effect_single_space(left, whitespace, right, &mut edits);
394 }
395 (At, Ident) => {
396 effect_no_space(left, whitespace, right, &mut edits);
397 }
398 (Keyword(Keyword::Internal), _) => {
399 effect_single_space(left, whitespace, right, &mut edits);
400 }
401 (Keyword(Keyword::Adjoint), Keyword(Keyword::Controlled))
402 | (Keyword(Keyword::Controlled), Keyword(Keyword::Adjoint)) => {
403 effect_single_space(left, whitespace, right, &mut edits);
404 }
405 (_, _) if does_right_required_newline => {
406 effect_correct_indentation(
407 left,
408 whitespace,
409 right,
410 &mut edits,
411 self.indent_level,
412 );
413 }
414 (
415 _,
416 Keyword(
417 Keyword::Until
418 | Keyword::In
419 | Keyword::As
420 | Keyword::Elif
421 | Keyword::Else
422 | Keyword::Apply,
423 ),
424 ) => {
425 effect_single_space(left, whitespace, right, &mut edits);
426 }
427 (
428 _,
429 Keyword(
430 Keyword::Auto
431 | Keyword::Distribute
432 | Keyword::Intrinsic
433 | Keyword::Invert
434 | Keyword::Slf,
435 ),
436 ) => {
437 effect_single_space(left, whitespace, right, &mut edits);
438 }
439 (Close(Delim::Brace), _)
440 if is_newline_after_brace(cooked_right, right_delim_state) =>
441 {
442 effect_correct_indentation(
443 left,
444 whitespace,
445 right,
446 &mut edits,
447 self.indent_level,
448 );
449 }
450 (String(StringToken::Interpolated(_, InterpolatedEnding::LBrace)), _)
451 | (_, String(StringToken::Interpolated(InterpolatedStart::RBrace, _))) => {
452 effect_no_space(left, whitespace, right, &mut edits);
453 }
454 (DotDotDot, _) if matches!(self.spec_decl_state, SpecDeclState::OnEllipse) => {
455 // Special-case specialization declaration ellipses to have a space after
456 effect_single_space(left, whitespace, right, &mut edits);
457 }
458 (_, Open(Delim::Brace)) => {
459 // Special-case braces to have a leading single space with values
460 if is_prefix(cooked_left) {
461 effect_no_space(left, whitespace, right, &mut edits);
462 } else {
463 effect_single_space(left, whitespace, right, &mut edits);
464 }
465 }
466 (_, _) if matches!(right_delim_state, Delimiter::Open) => {
467 // Otherwise, all open delims have the same logic
468 if is_value_token_left(cooked_left, left_delim_state) || is_prefix(cooked_left)
469 {
470 // i.e. foo() or foo[3]
471 effect_no_space(left, whitespace, right, &mut edits);
472 } else {
473 // i.e. let x = (1, 2, 3);
474 effect_single_space(left, whitespace, right, &mut edits);
475 }
476 }
477 (_, DotDotDot) => {
478 if is_value_token_left(cooked_left, left_delim_state) {
479 effect_no_space(left, whitespace, right, &mut edits);
480 } else {
481 effect_single_space(left, whitespace, right, &mut edits);
482 }
483 }
484 (_, Keyword(Keyword::Is)) => {
485 effect_single_space(left, whitespace, right, &mut edits);
486 }
487 (_, Keyword(keyword)) if is_starter_keyword(keyword) => {
488 effect_single_space(left, whitespace, right, &mut edits);
489 }
490 (_, _) if is_value_token_right(cooked_right, right_delim_state) => {
491 if is_prefix(cooked_left) {
492 effect_no_space(left, whitespace, right, &mut edits);
493 } else {
494 effect_single_space(left, whitespace, right, &mut edits);
495 }
496 }
497 (_, _) if is_suffix(cooked_right) => {
498 effect_no_space(left, whitespace, right, &mut edits);
499 }
500 (_, _) if is_prefix_with_space(cooked_right) => {
501 if is_prefix(cooked_left) {
502 effect_no_space(left, whitespace, right, &mut edits);
503 } else {
504 effect_single_space(left, whitespace, right, &mut edits);
505 }
506 }
507 (_, _) if is_prefix_without_space(cooked_right) => {
508 effect_no_space(left, whitespace, right, &mut edits);
509 }
510 (_, Keyword(keyword)) if is_prefix_keyword(keyword) => {
511 effect_single_space(left, whitespace, right, &mut edits);
512 }
513 (_, WSlash) if are_newlines_in_spaces => {
514 effect_correct_indentation(
515 left,
516 whitespace,
517 right,
518 &mut edits,
519 self.indent_level + 1,
520 );
521 }
522 (_, _) if is_bin_op(cooked_right) => {
523 effect_single_space(left, whitespace, right, &mut edits);
524 }
525 _ => {}
526 },
527 _ => {}
528 }
529 edits
530 }
531
532 fn update_spec_decl_state(&mut self, left_kind: &ConcreteTokenKind) {
533 use ConcreteTokenKind::*;
534 use TokenKind::*;
535 use qsc_frontend::keyword::Keyword;
536
537 match left_kind {
538 Comment => {
539 // Comments don't update state
540 }
541 Syntax(Keyword(Keyword::Body | Keyword::Adjoint | Keyword::Controlled)) => {
542 self.spec_decl_state = SpecDeclState::OnSpecKeyword;
543 }
544 Syntax(DotDotDot) if matches!(self.spec_decl_state, SpecDeclState::OnSpecKeyword) => {
545 self.spec_decl_state = SpecDeclState::OnEllipse;
546 }
547 _ => {
548 self.spec_decl_state = SpecDeclState::NoState;
549 }
550 }
551 }
552
553 fn update_import_export_state(&mut self, left_kind: &ConcreteTokenKind) {
554 use ConcreteTokenKind::*;
555 use TokenKind::*;
556 use qsc_frontend::keyword::Keyword;
557
558 match left_kind {
559 Comment => {
560 // Comments don't update state
561 }
562 Syntax(Keyword(Keyword::Import | Keyword::Export)) => {
563 self.import_export_state = ImportExportState::HandlingImportExportStatement;
564 }
565 Syntax(Semi) => {
566 self.import_export_state = ImportExportState::NoState;
567 }
568 _ => (),
569 }
570 }
571
572 /// Updates the `type_param_state` of the `FormatterState` based
573 /// on the left and right token kinds.
574 fn update_type_param_state(
575 &mut self,
576 left_kind: &ConcreteTokenKind,
577 right_kind: &ConcreteTokenKind,
578 ) {
579 use ConcreteTokenKind::*;
580 use TokenKind::*;
581 use qsc_frontend::{keyword::Keyword, lex::cooked::ClosedBinOp};
582
583 // If we are leaving a type param list, reset the state
584 if matches!(left_kind, Syntax(Gt))
585 && matches!(
586 self.type_param_state,
587 TypeParameterListState::InTypeParamList
588 )
589 {
590 self.type_param_state = TypeParameterListState::NoState;
591 }
592
593 match right_kind {
594 Comment => {
595 // comments don't update state
596 }
597 Syntax(Keyword(Keyword::Operation | Keyword::Function)) => {
598 self.type_param_state = TypeParameterListState::SeenCallableKeyword;
599 }
600 Syntax(Ident)
601 if matches!(
602 self.type_param_state,
603 TypeParameterListState::SeenCallableKeyword
604 ) =>
605 {
606 self.type_param_state = TypeParameterListState::SeenCallableName;
607 }
608 Syntax(Lt)
609 if matches!(
610 self.type_param_state,
611 TypeParameterListState::SeenCallableName
612 ) =>
613 {
614 self.type_param_state = TypeParameterListState::InTypeParamList;
615 }
616 Syntax(AposIdent | Comma | Gt | ClosedBinOp(ClosedBinOp::Plus) | Ident | Colon)
617 if matches!(
618 self.type_param_state,
619 TypeParameterListState::InTypeParamList
620 ) =>
621 {
622 // type param identifiers and commas don't take us out of the type parameter list context
623 // Gt only takes us out of the list once we are past it (it is the left-hand token)
624 }
625 _ => {
626 self.type_param_state = TypeParameterListState::NoState;
627 }
628 }
629 }
630
631 /// Updates the indent level and manages the `delim_newlines_stack`
632 /// of the `FormatterState`.
633 /// Returns the current newline context.
634 fn update_indent_level(
635 &mut self,
636 left_delim_state: Delimiter,
637 right_delim_state: Delimiter,
638 are_newlines_in_spaces: bool,
639 does_right_required_newline: bool,
640 is_right_comment: bool,
641 ) -> NewlineContext {
642 let mut newline_context = self
643 .delim_newlines_stack
644 .last()
645 .map_or(NewlineContext::NoContext, |b| *b);
646
647 match (left_delim_state, right_delim_state) {
648 (Delimiter::Open, Delimiter::Close) => {
649 // Don't change the indentation if empty sequence.
650 }
651 (Delimiter::Open, _) if are_newlines_in_spaces => {
652 newline_context = NewlineContext::Newlines;
653 self.delim_newlines_stack.push(newline_context);
654 self.indent_level += 1;
655 }
656 (Delimiter::Open, _) if does_right_required_newline => {
657 newline_context = NewlineContext::Newlines;
658 self.delim_newlines_stack.push(newline_context);
659 self.indent_level += 1;
660 }
661 (Delimiter::Open, _) if is_right_comment => {
662 newline_context = NewlineContext::Newlines;
663 self.delim_newlines_stack.push(newline_context);
664 self.indent_level += 1;
665 }
666 (Delimiter::Open, _) => {
667 newline_context = NewlineContext::Spaces;
668 self.delim_newlines_stack.push(newline_context);
669 }
670 (_, Delimiter::Close) => {
671 self.delim_newlines_stack.pop();
672 if matches!(newline_context, NewlineContext::Newlines) {
673 self.indent_level = self.indent_level.saturating_sub(1);
674 }
675 }
676 _ => {}
677 }
678
679 newline_context
680 }
681}
682
683// Helper Functions
684
685fn make_indent_string(level: usize) -> String {
686 " ".repeat(level)
687}
688
689fn get_token_contents<'a>(code: &'a str, token: &ConcreteToken) -> &'a str {
690 &code[token.span.lo as usize..token.span.hi as usize]
691}
692
693// Rule Conditions
694
695fn is_bin_op(cooked: TokenKind) -> bool {
696 matches!(
697 cooked,
698 TokenKind::Bar
699 | TokenKind::BinOpEq(_)
700 | TokenKind::ClosedBinOp(_)
701 | TokenKind::Colon
702 | TokenKind::Eq
703 | TokenKind::EqEq
704 | TokenKind::FatArrow
705 | TokenKind::Gt
706 | TokenKind::Gte
707 | TokenKind::LArrow
708 | TokenKind::Lt
709 | TokenKind::Lte
710 | TokenKind::Ne
711 | TokenKind::Question
712 | TokenKind::RArrow
713 | TokenKind::WSlash
714 | TokenKind::WSlashEq
715 | TokenKind::Keyword(Keyword::And | Keyword::Or)
716 )
717}
718
719fn is_prefix_with_space(cooked: TokenKind) -> bool {
720 matches!(cooked, TokenKind::TildeTildeTilde)
721}
722
723fn is_prefix_without_space(cooked: TokenKind) -> bool {
724 matches!(
725 cooked,
726 TokenKind::ColonColon
727 | TokenKind::Dot
728 | TokenKind::DotDot
729 | TokenKind::ClosedBinOp(ClosedBinOp::Caret)
730 )
731}
732
733fn is_prefix(cooked: TokenKind) -> bool {
734 is_prefix_with_space(cooked)
735 || is_prefix_without_space(cooked)
736 || matches!(cooked, TokenKind::DotDotDot)
737}
738
739fn is_suffix(cooked: TokenKind) -> bool {
740 matches!(cooked, TokenKind::Bang | TokenKind::Comma)
741}
742
743fn is_prefix_keyword(keyword: Keyword) -> bool {
744 use Keyword::*;
745 matches!(keyword, Not | AdjointUpper | ControlledUpper)
746}
747
748fn is_keyword_value(keyword: Keyword) -> bool {
749 use Keyword::*;
750 matches!(
751 keyword,
752 True | False | Zero | One | PauliI | PauliX | PauliY | PauliZ | Underscore
753 // Adj and Ctl are not really values, but have the same spacing as values
754 | Adj | Ctl
755 )
756}
757
758fn is_newline_keyword_or_ampersat(cooked: TokenKind) -> bool {
759 use Keyword::*;
760 matches!(
761 cooked,
762 TokenKind::At
763 | TokenKind::Keyword(
764 Internal
765 | Operation
766 | Function
767 | Newtype
768 | Struct
769 | Namespace
770 | Open
771 | Body
772 | Adjoint
773 | Controlled
774 | Let
775 | Mutable
776 | Set
777 | Use
778 | Borrow
779 | Fixup
780 | Import
781 | Export
782 )
783 )
784}
785
786fn is_starter_keyword(keyword: Keyword) -> bool {
787 use Keyword::*;
788 matches!(
789 keyword,
790 For | While | Repeat | If | Within | New | Return | Fail
791 )
792}
793
794fn is_newline_after_brace(cooked: TokenKind, delim_state: Delimiter) -> bool {
795 is_value_token_right(cooked, delim_state)
796 || matches!(cooked, TokenKind::Keyword(keyword) if is_starter_keyword(keyword))
797 || matches!(cooked, TokenKind::Keyword(keyword) if is_prefix_keyword(keyword))
798}
799
800/// Note that this does not include interpolated string literals
801fn is_value_lit(cooked: TokenKind) -> bool {
802 matches!(
803 cooked,
804 TokenKind::BigInt(_)
805 | TokenKind::Float
806 | TokenKind::Int(_)
807 | TokenKind::String(StringToken::Normal)
808 )
809}
810
811fn is_value_token_left(cooked: TokenKind, delim_state: Delimiter) -> bool {
812 // a closed delim represents a value on the left
813 if matches!(delim_state, Delimiter::Close) {
814 return true;
815 }
816
817 match cooked {
818 _ if is_value_lit(cooked) => true,
819 TokenKind::Keyword(keyword) if is_keyword_value(keyword) => true,
820 TokenKind::Ident
821 | TokenKind::AposIdent
822 | TokenKind::String(StringToken::Interpolated(_, InterpolatedEnding::Quote)) => true,
823 _ => false,
824 }
825}
826
827fn is_value_token_right(cooked: TokenKind, delim_state: Delimiter) -> bool {
828 // an open delim represents a value on the right
829 if matches!(delim_state, Delimiter::Open) {
830 return true;
831 }
832
833 match cooked {
834 _ if is_value_lit(cooked) => true,
835 TokenKind::Keyword(keyword) if is_keyword_value(keyword) => true,
836 TokenKind::Ident
837 | TokenKind::AposIdent
838 | TokenKind::String(StringToken::Interpolated(InterpolatedStart::DollarQuote, _)) => true,
839 _ => false,
840 }
841}
842
843// Rule Effects
844
845fn effect_no_space(
846 left: &ConcreteToken,
847 whitespace: &str,
848 right: &ConcreteToken,
849 edits: &mut Vec<TextEdit>,
850) {
851 if !whitespace.is_empty() {
852 edits.push(TextEdit::new("", left.span.hi, right.span.lo));
853 }
854}
855
856fn effect_single_space(
857 left: &ConcreteToken,
858 whitespace: &str,
859 right: &ConcreteToken,
860 edits: &mut Vec<TextEdit>,
861) {
862 if whitespace != " " {
863 edits.push(TextEdit::new(" ", left.span.hi, right.span.lo));
864 }
865}
866
867fn effect_trim_comment(left: &ConcreteToken, edits: &mut Vec<TextEdit>, code: &str) {
868 let comment_contents = get_token_contents(code, left);
869 let new_comment_contents = comment_contents.trim_end();
870 if comment_contents != new_comment_contents {
871 edits.push(TextEdit::new(
872 new_comment_contents,
873 left.span.lo,
874 left.span.hi,
875 ));
876 }
877}
878
879fn effect_correct_indentation(
880 left: &ConcreteToken,
881 whitespace: &str,
882 right: &ConcreteToken,
883 edits: &mut Vec<TextEdit>,
884 indent_level: usize,
885) {
886 let mut count_newlines = whitespace.chars().filter(|c| *c == '\n').count();
887
888 // There should always be at least one newline
889 if count_newlines < 1 {
890 count_newlines = 1;
891 }
892
893 let mut new_whitespace = if whitespace.contains("\r\n") {
894 "\r\n".repeat(count_newlines)
895 } else {
896 "\n".repeat(count_newlines)
897 };
898 new_whitespace.push_str(&make_indent_string(indent_level));
899 if whitespace != new_whitespace {
900 edits.push(TextEdit::new(
901 new_whitespace.as_str(),
902 left.span.hi,
903 right.span.lo,
904 ));
905 }
906}
907