ink_analyzer/analysis/
text_edit.rs

1//! A text edit.
2
3mod common;
4
5use ink_analyzer_ir::syntax::{AstNode, SyntaxKind, SyntaxToken, TextRange, TextSize};
6use ink_analyzer_ir::{InkEntity, InkFile};
7
8use super::utils;
9
10pub use common::*;
11
12/// A text edit (with an optional snippet - i.e tab stops and/or placeholders).
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct TextEdit {
15    /// Replacement text for the text edit.
16    pub text: String,
17    /// Range to which the text edit will be applied.
18    pub range: TextRange,
19    /// Formatted snippet for the text edit (includes tab stops and/or placeholders).
20    pub snippet: Option<String>,
21}
22
23impl TextEdit {
24    /// Creates text edit.
25    pub fn new(text: String, range: TextRange, snippet: Option<String>) -> Self {
26        Self {
27            text,
28            range,
29            snippet,
30        }
31    }
32
33    /// Creates text edit for inserting at the given offset.
34    pub fn insert(text: String, offset: TextSize) -> Self {
35        Self::insert_with_snippet(text, offset, None)
36    }
37
38    /// Creates text edit for inserting at the given offset (including an optional snippet).
39    pub fn insert_with_snippet(text: String, offset: TextSize, snippet: Option<String>) -> Self {
40        Self {
41            text,
42            range: TextRange::new(offset, offset),
43            snippet,
44        }
45    }
46
47    /// Creates text edit for replacing the given range.
48    pub fn replace(text: String, range: TextRange) -> Self {
49        Self::replace_with_snippet(text, range, None)
50    }
51
52    /// Creates text edit for replacing the given range (including an optional snippet) - i.e an alias of [`Self::new`].
53    pub fn replace_with_snippet(text: String, range: TextRange, snippet: Option<String>) -> Self {
54        Self::new(text, range, snippet)
55    }
56
57    /// Creates a text edit for deleting the specified range.
58    pub fn delete(range: TextRange) -> Self {
59        Self {
60            text: String::new(),
61            range,
62            snippet: None,
63        }
64    }
65}
66
67/// Format text edits (i.e. add indenting and new lines based on context).
68pub fn format_edits<'a>(
69    edits: impl Iterator<Item = TextEdit> + 'a,
70    file: &'a InkFile,
71) -> impl Iterator<Item = TextEdit> + 'a {
72    edits.map(|item| format_edit(item, file))
73}
74
75/// Format text edit (i.e. add indenting and new lines based on context).
76pub fn format_edit(mut edit: TextEdit, file: &InkFile) -> TextEdit {
77    // Adds affixes to a text edit.
78    fn add_affixes(edit: &mut TextEdit, prefix: Option<String>, suffix: Option<String>) {
79        if prefix.is_some() || suffix.is_some() {
80            edit.text = format!(
81                "{}{}{}",
82                prefix.as_deref().unwrap_or_default(),
83                edit.text,
84                suffix.as_deref().unwrap_or_default(),
85            );
86            edit.snippet = edit.snippet.as_ref().map(|snippet| {
87                format!(
88                    "{}{snippet}{}",
89                    prefix.as_deref().unwrap_or_default(),
90                    suffix.as_deref().unwrap_or_default()
91                )
92            });
93        }
94    }
95
96    // Generates affixes for edits between new lines and/or file boundaries.
97    let affix_edit_between_whitespace_or_file_boundaries =
98        |whitespace_before: Option<&str>, whitespace_after: Option<&str>| {
99            let build_affix = |ws_text: &str| {
100                (!utils::starts_with_two_or_more_newlines(ws_text)).then(|| "\n".to_owned())
101            };
102            match (
103                whitespace_before.map(|ws_before| (ws_before.contains('\n'), ws_before)),
104                whitespace_after.map(|ws_after| (ws_after.contains('\n'), ws_after)),
105            ) {
106                // Handles edits between new lines and/or file boundaries.
107                (Some((true, ws_text_before)), Some((true, ws_text_after))) => {
108                    (build_affix(ws_text_before), build_affix(ws_text_after))
109                }
110                (Some((true, ws_text_before)), None) => (build_affix(ws_text_before), None),
111                (None, Some((true, ws_text_after))) => (None, build_affix(ws_text_after)),
112                _ => (None, None),
113            }
114        };
115
116    // Handles edits inside whitespace.
117    let covering_whitespace = file
118        .syntax()
119        .covering_element(edit.range)
120        .into_token()
121        .filter(|token| {
122            token.kind() == SyntaxKind::WHITESPACE
123                && token.text_range().start() < edit.range.start()
124                && edit.range.end() < token.text_range().end()
125        });
126    if let Some(whitespace_token) = covering_whitespace {
127        if edit.text.is_empty() {
128            return edit;
129        }
130
131        let whitespace_text = whitespace_token.text();
132        let start = edit.range.start() - whitespace_token.text_range().start();
133        let (whitespace_before, whitespace_after) = if edit.range.is_empty() {
134            whitespace_text.split_at(start.into())
135        } else {
136            let end = whitespace_token.text_range().end() - edit.range.end();
137            let (whitespace_before, _) = whitespace_text.split_at(start.into());
138            let (_, whitespace_after) = whitespace_text.split_at(end.into());
139            (whitespace_before, whitespace_after)
140        };
141        let (prefix, suffix) = affix_edit_between_whitespace_or_file_boundaries(
142            Some(whitespace_before),
143            Some(whitespace_after),
144        );
145        add_affixes(&mut edit, prefix, suffix);
146        return edit;
147    }
148
149    // Determines the token right before the start of the edit offset.
150    let token_before_option = file
151        .syntax()
152        .token_at_offset(edit.range.start())
153        .left_biased()
154        .and_then(|token| {
155            if token.text_range().end() <= edit.range.start() {
156                Some(token)
157            } else {
158                token.prev_token()
159            }
160        });
161    // Determines the token right after the end of the edit offset.
162    let token_after_option = file
163        .syntax()
164        .token_at_offset(edit.range.end())
165        .right_biased()
166        .and_then(|token| {
167            if token.text_range().start() >= edit.range.end() {
168                Some(token)
169            } else {
170                token.next_token()
171            }
172        });
173
174    if edit.text.is_empty() {
175        // Handles deletes.
176        // Removes whitespace immediately following a delete if the text is surrounded by whitespace,
177        // but only when the token right after the whitespace is not a closing curly bracket
178        // (because it would otherwise break the indenting of the closing curly bracket).
179        if let Some(token_after) = token_after_option {
180            let is_whitespace_or_bof_before = token_before_option
181                .as_ref()
182                .is_none_or(|token_before| token_before.kind() == SyntaxKind::WHITESPACE);
183            let is_at_the_end_block = token_after
184                .next_token()
185                .is_some_and(|it| it.kind() == SyntaxKind::R_CURLY);
186            if is_whitespace_or_bof_before
187                && token_after.kind() == SyntaxKind::WHITESPACE
188                && !is_at_the_end_block
189            {
190                edit.range = TextRange::new(edit.range.start(), token_after.text_range().end());
191            }
192        }
193    } else {
194        // Handles insert and replace.
195        let affix_edit_after_whitespace_or_bof =
196            |token_before_option: Option<&SyntaxToken>,
197             token_after_option: Option<&SyntaxToken>| {
198                let is_whitespace_or_bof_before = token_before_option
199                    .as_ref()
200                    .is_none_or(|token_before| token_before.kind() == SyntaxKind::WHITESPACE);
201                let is_whitespace_or_eof_after = token_after_option
202                    .as_ref()
203                    .is_none_or(|token_after| token_after.kind() == SyntaxKind::WHITESPACE);
204                if is_whitespace_or_bof_before && is_whitespace_or_eof_after {
205                    // Handles edits between whitespace and/or file boundaries.
206                    affix_edit_between_whitespace_or_file_boundaries(
207                        token_before_option.map(SyntaxToken::text),
208                        token_after_option.map(SyntaxToken::text),
209                    )
210                } else if is_whitespace_or_bof_before && !is_whitespace_or_eof_after {
211                    // Handles preserving formatting for next non-whitespace item.
212                    (
213                        // No formatting prefix.
214                        None,
215                        // Adds formatting suffix only if the edit is not surrounded by whitespace
216                        // (treats end of the file like whitespace)
217                        // and its preceding whitespace contains a new line.
218                        match token_before_option {
219                            // Handles beginning of file.
220                            None => Some("\n".to_owned()),
221                            Some(token_before) => token_before.text().contains('\n').then(|| {
222                                format!(
223                                    "\n{}{}",
224                                    if utils::starts_with_two_or_more_newlines(token_before.text())
225                                    {
226                                        "\n"
227                                    } else {
228                                        ""
229                                    },
230                                    utils::end_indenting(token_before.text())
231                                )
232                            }),
233                        },
234                    )
235                } else {
236                    (None, None)
237                }
238            };
239        let (prefix, suffix) = if let Some(token_before) = token_before_option {
240            match token_before.kind() {
241                // Handles edits after whitespace.
242                SyntaxKind::WHITESPACE => affix_edit_after_whitespace_or_bof(
243                    Some(&token_before),
244                    token_after_option.as_ref(),
245                ),
246                // Handles edits at the beginning of blocks (i.e right after the opening curly bracket).
247                SyntaxKind::L_CURLY => {
248                    (
249                        // Adds formatting prefix only if the edit doesn't start with a new line
250                        // and then only add indenting if the edit doesn't start with a space (i.e ' ') or a tab (i.e. '\t').
251                        (!edit.text.starts_with('\n')).then(|| {
252                            format!(
253                                "\n{}",
254                                (!edit.text.starts_with(' ') && !edit.text.starts_with('\t'))
255                                    .then(|| {
256                                        ink_analyzer_ir::parent_ast_item(&token_before)
257                                            .map(|it| utils::item_children_indenting(it.syntax()))
258                                    })
259                                    .flatten()
260                                    .as_deref()
261                                    .unwrap_or_default()
262                            )
263                        }),
264                        // Adds formatting suffix if the edit is followed by either a non-whitespace character
265                        // or whitespace that doesn't start with at least 2 new lines
266                        // (the new lines can be interspersed with other whitespace)
267                        // and the edit doesn't end with 2 new lines.
268                        token_after_option.as_ref().and_then(|token_after| {
269                            ((token_after.kind() != SyntaxKind::WHITESPACE
270                                || !utils::starts_with_two_or_more_newlines(token_after.text()))
271                                && !edit.text.ends_with("\n\n"))
272                            .then(|| {
273                                format!(
274                                    "\n{}",
275                                    if token_after.text().starts_with('\n') {
276                                        ""
277                                    } else {
278                                        "\n"
279                                    }
280                                )
281                            })
282                        }),
283                    )
284                }
285                // Handles edits at the end of a statement or block or after a comment.
286                SyntaxKind::SEMICOLON | SyntaxKind::R_CURLY | SyntaxKind::COMMENT => {
287                    (
288                        // Adds formatting prefix only if the edit doesn't start with a new line
289                        // and then only add indenting if the edit doesn't start with a space (i.e ' ') or a tab (i.e. '\t').
290                        (!edit.text.starts_with('\n')).then(|| {
291                            format!(
292                                "\n{}{}",
293                                // Extra new line at the end of statements and blocks.
294                                if token_before.kind() == SyntaxKind::COMMENT {
295                                    ""
296                                } else {
297                                    "\n"
298                                },
299                                if !edit.text.starts_with(' ') && !edit.text.starts_with('\t') {
300                                    ink_analyzer_ir::parent_ast_item(&token_before)
301                                        .and_then(|it| utils::item_indenting(it.syntax()))
302                                } else {
303                                    None
304                                }
305                                .as_deref()
306                                .unwrap_or_default(),
307                            )
308                        }),
309                        // No formatting suffix.
310                        None,
311                    )
312                }
313                // Ignores all other cases.
314                _ => (None, None),
315            }
316        } else {
317            affix_edit_after_whitespace_or_bof(None, token_after_option.as_ref())
318        };
319
320        // Adds formatting if necessary.
321        add_affixes(&mut edit, prefix, suffix);
322    }
323    edit
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use test_utils::parse_offset_at;
330
331    #[test]
332    fn format_insert_and_replace_works() {
333        for (input, output, source, start_pat, end_pat) in [
334            // Insert after whitespace.
335            (
336                "#[ink::contract]",
337                "\n#[ink::contract]",
338                r#"
339#![cfg_attr(not(feature = "std"), no_std, no_main)]
340"#,
341                Some("\n->"),
342                Some("\n->"),
343            ),
344            (
345                "mod contract {}",
346                "mod contract {}\n\n",
347                r#"
348#![cfg_attr(not(feature = "std"), no_std, no_main)]
349
350#[cfg(test)]
351mod tests {}"#,
352                Some("<-#[cfg(test)]"),
353                Some("<-#[cfg(test)]"),
354            ),
355            (
356                "mod contract {}",
357                "\nmod contract {}\n",
358                r#"
359#![cfg_attr(not(feature = "std"), no_std, no_main)]
360
361#[cfg(test)]
362mod tests {}"#,
363                Some("<-\n#[cfg(test)]"),
364                Some("<-\n#[cfg(test)]"),
365            ),
366            (
367                "mod contract {}",
368                "\nmod contract {}\n",
369                "#![cfg_attr(not(feature = \"std\"), no_std)]\n \n#[cfg(test)]\nmod tests {}",
370                Some("<- \n#[cfg(test)]"),
371                Some("<-\n#[cfg(test)]"),
372            ),
373            (
374                "mod contract {}",
375                "mod contract {}\n",
376                r#"
377#![cfg_attr(not(feature = "std"), no_std, no_main)]
378
379
380#[cfg(test)]
381mod tests {}"#,
382                Some("<-\n#[cfg(test)]"),
383                Some("<-\n#[cfg(test)]"),
384            ),
385            (
386                "#[ink::contract]",
387                "#[ink::contract]\n",
388                "mod contract {}",
389                Some("<-mod contract {"),
390                Some("<-mod contract {"),
391            ),
392            (
393                "#[ink::contract]",
394                "#[ink::contract]\n",
395                r"
396#[doc(hidden)]
397mod contract {}",
398                Some("<-mod contract {"),
399                Some("<-mod contract {"),
400            ),
401            (
402                "#[ink::contract]",
403                "#[ink::contract]\n",
404                "#[doc(hidden)]\nmod contract {}",
405                Some("<-#[doc(hidden)]"),
406                Some("<-#[doc(hidden)]"),
407            ),
408            (
409                "#[ink::contract]",
410                "#[ink::contract]\n",
411                r"
412#[doc(hidden)]
413mod contract {}",
414                Some("<-#[doc(hidden)]"),
415                Some("<-#[doc(hidden)]"),
416            ),
417            (
418                "#[ink::contract]",
419                "#[ink::contract]\n",
420                r"
421#[doc(hidden)]
422mod contract {}",
423                Some("<-mod contract {"),
424                Some("<-mod contract {"),
425            ),
426            (
427                "#[ink(storage)]",
428                "#[ink(storage)]\n    ",
429                r"
430mod contract {
431    struct MyContract {}
432}",
433                Some("<-struct MyContract {}"),
434                Some("<-struct MyContract {}"),
435            ),
436            (
437                "#[ink(topic)]",
438                "#[ink(topic)]\n        ",
439                r"
440mod contract {
441    struct MyEvent {
442        status: bool,
443    }
444}",
445                Some("<-status: bool,"),
446                Some("<-status: bool,"),
447            ),
448            (
449                "#[ink(impl)]",
450                "#[ink(impl)]\n    ",
451                r"
452mod contract {
453    impl MyContract {}
454}",
455                Some("<-impl MyContract {}"),
456                Some("<-impl MyContract {}"),
457            ),
458            (
459                "#[ink(impl)]",
460                "#[ink(impl)]\n    ",
461                r#"
462mod contract {
463    #[ink(namespace = "my_namespace")]
464    impl MyContract {}
465}
466                "#,
467                Some(r#"<-#[ink(namespace = "my_namespace")]"#),
468                Some(r#"<-#[ink(namespace = "my_namespace")]"#),
469            ),
470            (
471                "#[ink(message)]",
472                "#[ink(message)]\n        ",
473                r"
474mod contract {
475    impl MyContract {
476        pub fn message(&self) {}
477    }
478}",
479                Some("<-pub fn message(&self) {}"),
480                Some("<-pub fn message(&self) {}"),
481            ),
482            // Insert at the beginning of block.
483            (
484                "struct MyContract {}",
485                "\n    struct MyContract {}\n",
486                r"
487mod contract {
488}",
489                Some("mod contract {"),
490                Some("mod contract {"),
491            ),
492            (
493                "status: bool,",
494                "\n        status: bool,\n",
495                r"
496mod contract {
497    struct MyContract {
498    }
499}",
500                Some("struct MyContract {"),
501                Some("struct MyContract {"),
502            ),
503            (
504                "impl MyContract {}",
505                "\n    impl MyContract {}\n",
506                r"
507mod contract {
508}",
509                Some("mod contract {"),
510                Some("mod contract {"),
511            ),
512            (
513                "pub fn message(&self) {}",
514                "\n        pub fn message(&self) {}\n",
515                r"
516mod contract {
517    impl MyContract {
518    }
519}",
520                Some("impl MyContract {"),
521                Some("impl MyContract {"),
522            ),
523            // Insert at the end of a statement or block or after a comment.
524            (
525                "struct MyEvent {}",
526                "\n\n    struct MyEvent {}",
527                r"
528mod contract {
529    struct MyContract {}
530}",
531                Some("struct MyContract {}"),
532                Some("struct MyContract {}"),
533            ),
534            (
535                "struct MyEvent {}",
536                "\n\n    struct MyEvent {}",
537                r"
538mod contract {
539    struct MyContract;
540}",
541                Some("struct MyContract;"),
542                Some("struct MyContract;"),
543            ),
544            (
545                "struct MyEvent {}",
546                "\n\n    struct MyEvent {}",
547                r"
548mod contract {
549    struct MyContract {}
550
551    struct MyOtherEvent {}
552}",
553                Some("struct MyContract {}"),
554                Some("struct MyContract {}"),
555            ),
556            (
557                "struct MyEvent {}",
558                "\n\n    struct MyEvent {}",
559                r"
560mod contract {
561    struct MyContract {}
562    struct MyOtherEvent {}
563}",
564                Some("struct MyContract {}"),
565                Some("struct MyContract {}"),
566            ),
567            (
568                "pub fn message(&self) {}",
569                "\n\n        pub fn message(&self) {}",
570                r"
571mod contract {
572    impl MyContract {
573        pub fn constructor() {}
574    }
575}",
576                Some("pub fn constructor() {}"),
577                Some("pub fn constructor() {}"),
578            ),
579            (
580                "pub fn message(&self) {}",
581                "\n\n        pub fn message(&self) {}",
582                r"
583mod contract {
584    impl MyContract {
585        pub fn constructor() {}
586
587        pub fn message2(&self) {}
588    }
589}",
590                Some("pub fn constructor() {}"),
591                Some("pub fn constructor() {}"),
592            ),
593            (
594                "pub fn message(&self) {}",
595                "\n\n        pub fn message(&self) {}",
596                r"
597mod contract {
598    impl MyContract {
599        pub fn constructor() {}
600        pub fn message2(&self) {}
601    }
602}",
603                Some("pub fn constructor() {}"),
604                Some("pub fn constructor() {}"),
605            ),
606            // Everything else should remain unchanged.
607            (
608                "(env = crate::MyEnvironment)",
609                "(env = crate::MyEnvironment)",
610                r"
611#[ink::contract]
612mod contract {}",
613                Some("#[ink::contract"),
614                Some("#[ink::contract"),
615            ),
616            (
617                "env = crate::MyEnvironment",
618                "env = crate::MyEnvironment",
619                r"
620#[ink::contract()]
621mod contract {}",
622                Some("#[ink::contract("),
623                Some("#[ink::contract("),
624            ),
625            (
626                r#", keep_attr = "foo,bar""#,
627                r#", keep_attr = "foo,bar""#,
628                r"
629#[ink::contract(env = crate::MyEnvironment)]
630mod contract {}",
631                Some("#[ink::contract(env = crate::MyEnvironment"),
632                Some("#[ink::contract(env = crate::MyEnvironment"),
633            ),
634            (
635                "crate::MyEnvironment",
636                "crate::MyEnvironment",
637                r"
638#[ink::contract(env = self::MyEnvironment)]
639mod contract {}",
640                Some("#[ink::contract(env = "),
641                Some("#[ink::contract(env = self::MyEnvironment"),
642            ),
643            (
644                " crate::MyEnvironment",
645                " crate::MyEnvironment",
646                r"
647#[ink::contract(env = self::MyEnvironment)]
648mod contract {}",
649                Some("#[ink::contract(env ="),
650                Some("#[ink::contract(env = self::MyEnvironment"),
651            ),
652            (
653                "&self",
654                "&self",
655                "pub fn message() {}",
656                Some("pub fn message("),
657                Some("pub fn message("),
658            ),
659            (
660                ", status: bool",
661                ", status: bool",
662                "pub fn message(&self) {}",
663                Some("pub fn message(&self"),
664                Some("pub fn message(&self"),
665            ),
666            (
667                " status: bool",
668                " status: bool",
669                "pub fn message(&self,) {}",
670                Some("pub fn message(&self,"),
671                Some("pub fn message(&self,"),
672            ),
673            (
674                "status: bool",
675                "status: bool",
676                "pub fn message(&self, ) {}",
677                Some("pub fn message(&self, "),
678                Some("pub fn message(&self, "),
679            ),
680            (
681                " -> u8",
682                " -> u8",
683                "pub fn message(&self) {}",
684                Some("pub fn message(&self)"),
685                Some("pub fn message(&self)"),
686            ),
687            (
688                "-> u8",
689                "-> u8",
690                "pub fn message(&self) {}",
691                Some("pub fn message(&self) "),
692                Some("pub fn message(&self) "),
693            ),
694        ] {
695            let file = InkFile::parse(source);
696            let range = TextRange::new(
697                TextSize::from(parse_offset_at(source, start_pat).unwrap() as u32),
698                TextSize::from(parse_offset_at(source, end_pat).unwrap() as u32),
699            );
700            let edit = TextEdit {
701                text: input.to_owned(),
702                range,
703                snippet: None,
704            };
705            let result = format_edit(edit, &file);
706            let expected = TextEdit {
707                text: output.to_owned(),
708                range,
709                snippet: None,
710            };
711            assert_eq!(result, expected);
712        }
713    }
714
715    #[test]
716    fn format_delete_works() {
717        for (start_pat_input, end_pat_input, pat_range_output, source) in [
718            // Removes space after delete if its surrounded by whitespace and
719            // the next token after trailing whitespace is not a closing curly bracket.
720            (
721                Some("<-#[ink::contract]"),
722                Some("#[ink::contract]"),
723                Some((Some("<-#[ink::contract]"), Some("<-mod contract {}"))),
724                "#[ink::contract]\nmod contract {}",
725            ),
726            (
727                Some("<-#[ink::contract]"),
728                Some("#[ink::contract]"),
729                Some((Some("<-#[ink::contract]"), Some("<-mod contract {}"))),
730                r"
731#[ink::contract]
732mod contract {}
733",
734            ),
735            (
736                Some("<-#[ink::contract]"),
737                Some("#[ink::contract]"),
738                Some((Some("<-#[ink::contract]"), Some("<-mod contract {}"))),
739                r"
740#[doc(hidden)]
741#[ink::contract]
742mod contract {}",
743            ),
744            (
745                Some("<-#[ink::contract]"),
746                Some("#[ink::contract]"),
747                Some((Some("<-#[ink::contract]"), Some("<-#[doc(hidden)]"))),
748                r"
749#[ink::contract]
750#[doc(hidden)]
751mod contract {}",
752            ),
753            (
754                Some("<-#[ink(storage)]"),
755                Some("#[ink(storage)]"),
756                Some((Some("<-#[ink(storage)]"), Some("<-struct MyContract {}"))),
757                r"
758mod contract {
759    #[ink(storage)]
760    struct MyContract {}
761}",
762            ),
763            (
764                Some("<-#[ink(topic)]"),
765                Some("#[ink(topic)]"),
766                Some((Some("<-#[ink(topic)]"), Some("<-status: bool,"))),
767                r"
768mod contract {
769    struct MyEvent {
770        #[ink(topic)]
771        status: bool,
772    }
773}",
774            ),
775            (
776                Some("<-#[ink(impl)]"),
777                Some("#[ink(impl)]"),
778                Some((Some("<-#[ink(impl)]"), Some("<-impl MyContract {}"))),
779                r"
780mod contract {
781    #[ink(impl)]
782    impl MyContract {}
783}",
784            ),
785            (
786                Some("<-#[ink(impl)]"),
787                Some("#[ink(impl)]"),
788                Some((
789                    Some("<-#[ink(impl)]"),
790                    Some(r#"<-#[ink(namespace = "my_namespace")]"#),
791                )),
792                r#"
793mod contract {
794    #[ink(impl)]
795    #[ink(namespace = "my_namespace")]
796    impl MyContract {}
797}"#,
798            ),
799            (
800                Some("<-#[ink(message)]"),
801                Some("#[ink(message)]"),
802                Some((
803                    Some("<-#[ink(message)]"),
804                    Some("<-pub fn message(&self) {}"),
805                )),
806                r"
807mod contract {
808    impl MyContract {
809        #[ink(message)]
810        pub fn message(&self) {}
811    }
812}",
813            ),
814            (
815                Some("<--> u8"),
816                Some("-> u8"),
817                Some((Some("<--> u8"), Some("-> u8 "))),
818                "pub fn message(&self) -> u8 {}",
819            ),
820            (
821                Some("<-struct MyEvent {}"),
822                Some("struct MyEvent {}"),
823                Some((
824                    Some("<-struct MyEvent {}"),
825                    Some("<-struct MyOtherEvent {}"),
826                )),
827                r"
828mod contract {
829    struct MyContract {}
830
831    struct MyEvent {}
832
833    struct MyOtherEvent {}
834}",
835            ),
836            (
837                Some("<-struct MyEvent {}"),
838                Some("struct MyEvent {}"),
839                Some((
840                    Some("<-struct MyEvent {}"),
841                    Some("<-struct MyOtherEvent {}"),
842                )),
843                r"
844mod contract {
845    struct MyContract {}
846    struct MyEvent {}
847    struct MyOtherEvent {}
848}",
849            ),
850            (
851                Some("<-pub fn message(&self) {}"),
852                Some("pub fn message(&self) {}"),
853                Some((
854                    Some("<-pub fn message(&self) {}"),
855                    Some("<-pub fn message2(&self) {}"),
856                )),
857                r"
858mod contract {
859    impl MyContract {
860        pub fn constructor() {}
861
862        pub fn message(&self) {}
863
864        pub fn message2(&self) {}
865    }
866}",
867            ),
868            (
869                Some("<-pub fn message(&self) {}"),
870                Some("pub fn message(&self) {}"),
871                Some((
872                    Some("<-pub fn message(&self) {}"),
873                    Some("<-pub fn message2(&self) {}"),
874                )),
875                r"
876mod contract {
877    impl MyContract {
878        pub fn constructor() {}
879        pub fn message(&self) {}
880        pub fn message2(&self) {}
881    }
882}",
883            ),
884            // Everything else should remain unchanged.
885            (
886                Some("<-struct MyContract {}"),
887                Some("struct MyContract {}"),
888                None,
889                r"
890mod contract {
891    struct MyContract {}
892}",
893            ),
894            (
895                Some("<-status: bool,"),
896                Some("status: bool,"),
897                None,
898                r"
899mod contract {
900    struct MyContract {
901        status: bool,
902    }
903}",
904            ),
905            (
906                Some("<-impl MyContract {}"),
907                Some("impl MyContract {}"),
908                None,
909                r"
910mod contract {
911    impl MyContract {}
912}",
913            ),
914            (
915                Some("<-pub fn message(&self) {}"),
916                Some("pub fn message(&self) {}"),
917                None,
918                r"
919mod contract {
920    impl MyContract {
921        pub fn message(&self) {}
922    }
923}",
924            ),
925            (
926                Some("<-struct MyEvent {}"),
927                Some("struct MyEvent {}"),
928                None,
929                r"
930mod contract {
931    struct MyContract {}
932
933    struct MyEvent {}
934}",
935            ),
936            (
937                Some("<-struct MyEvent {}"),
938                Some("struct MyEvent {}"),
939                None,
940                r"
941mod contract {
942    struct MyContract;
943
944    struct MyEvent {}
945}",
946            ),
947            (
948                Some("<-pub fn message(&self) {}"),
949                Some("pub fn message(&self) {}"),
950                None,
951                r"
952mod contract {
953    impl MyContract {
954        pub fn constructor() {}
955
956        pub fn message(&self) {}
957    }
958}",
959            ),
960            (
961                Some("<-(env = crate::MyEnvironment)"),
962                Some("(env = crate::MyEnvironment)"),
963                None,
964                r"
965#[ink::contract(env = crate::MyEnvironment)]
966mod contract {}",
967            ),
968            (
969                Some("<-env = crate::MyEnvironment"),
970                Some("env = crate::MyEnvironment"),
971                None,
972                r"
973#[ink::contract(env = crate::MyEnvironment)]
974mod contract {}",
975            ),
976            (
977                Some(r#"<-, keep_attr = "foo,bar""#),
978                Some(r#", keep_attr = "foo,bar""#),
979                None,
980                r#"
981#[ink::contract(env = crate::MyEnvironment, keep_attr = "foo,bar")]
982mod contract {}"#,
983            ),
984            (
985                Some("<-crate::MyEnvironment"),
986                Some("crate::MyEnvironment"),
987                None,
988                r"
989#[ink::contract(env = crate::MyEnvironment)]
990mod contract {}",
991            ),
992            (
993                Some("<- crate::MyEnvironment"),
994                Some(" crate::MyEnvironment"),
995                None,
996                r"
997#[ink::contract(env = crate::MyEnvironment)]
998mod contract {}",
999            ),
1000            (
1001                Some("<-&self"),
1002                Some("&self"),
1003                None,
1004                "pub fn message(&self) {}",
1005            ),
1006            (
1007                Some("<-, status: bool"),
1008                Some(", status: bool"),
1009                None,
1010                "pub fn message(&self, status: bool) {}",
1011            ),
1012            (
1013                Some("<- status: bool"),
1014                Some(" status: bool"),
1015                None,
1016                "pub fn message(&self, status: bool) {}",
1017            ),
1018            (
1019                Some("<-status: bool"),
1020                Some("status: bool"),
1021                None,
1022                "pub fn message(&self, status: bool) {}",
1023            ),
1024            (
1025                Some("<- -> u8"),
1026                Some(" -> u8"),
1027                None,
1028                "pub fn message(&self) -> u8 {}",
1029            ),
1030        ] {
1031            let file = InkFile::parse(source);
1032            let range_input = TextRange::new(
1033                TextSize::from(parse_offset_at(source, start_pat_input).unwrap() as u32),
1034                TextSize::from(parse_offset_at(source, end_pat_input).unwrap() as u32),
1035            );
1036            let range_output =
1037                pat_range_output.map_or(range_input, |(start_pat_output, end_pat_output)| {
1038                    TextRange::new(
1039                        TextSize::from(parse_offset_at(source, start_pat_output).unwrap() as u32),
1040                        TextSize::from(parse_offset_at(source, end_pat_output).unwrap() as u32),
1041                    )
1042                });
1043
1044            let edit = TextEdit {
1045                text: String::new(),
1046                range: range_input,
1047                snippet: None,
1048            };
1049            let result = format_edit(edit, &file);
1050            let expected = TextEdit {
1051                text: String::new(),
1052                range: range_output,
1053                snippet: None,
1054            };
1055            assert_eq!(result, expected);
1056        }
1057    }
1058}