microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/testHarnessIntegTests

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_formatter/src/formatter.rs

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