ink_analyzer/analysis/
hover.rs

1//! ink! attribute hover content.
2
3mod args;
4mod macros;
5
6use ink_analyzer_ir::syntax::{AstNode, AstToken, TextRange};
7use ink_analyzer_ir::{InkArgKind, InkAttributeKind, InkFile, InkMacroKind};
8
9use crate::analysis::utils;
10use crate::Version;
11
12/// An ink! attribute hover result.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Hover {
15    /// Range the hover content applies to.
16    pub range: TextRange,
17    /// Hover text.
18    pub content: String,
19}
20
21/// Returns descriptive/informational text for the ink! attribute at the given position (if any).
22pub fn hover(file: &InkFile, range: TextRange, version: Version) -> Option<Hover> {
23    // Finds the covering ink! attribute for the text range (if any).
24    let covering_ink_attr = utils::covering_ink_attribute(file, range);
25
26    // Returns hover content only if the text range is covered by an ink! attribute.
27    covering_ink_attr.and_then(|ink_attr| {
28        // Finds the covered ink! attribute argument (if any).
29        let ink_arg = ink_attr
30            .args()
31            .iter()
32            .find(|arg| arg.text_range().contains_range(range));
33        match ink_arg {
34            // Returns hover content for the covered ink! attribute argument if it's valid.
35            Some(ink_arg) => {
36                let arg_attr_kind = InkAttributeKind::Arg(*ink_arg.kind());
37                let primary_attr_kind =
38                    (*ink_attr.kind() != arg_attr_kind).then_some(ink_attr.kind());
39                let doc = content(&arg_attr_kind, version, primary_attr_kind);
40                (!doc.is_empty()).then(|| Hover {
41                    range: ink_arg
42                        .name()
43                        .map(|ink_arg_name| ink_arg_name.syntax().text_range())
44                        .unwrap_or_else(|| ink_arg.text_range()),
45                    content: doc.to_owned(),
46                })
47            }
48            // Returns hover content based on the ink! attribute macro, ink! e2e attribute macro
49            // or "primary" ink! attribute argument for the ink! attribute.
50            None => {
51                let doc = content(ink_attr.kind(), version, None);
52                (!doc.is_empty()).then(|| Hover {
53                    range: match ink_attr.kind() {
54                        InkAttributeKind::Arg(_) => ink_attr
55                            .ink_arg_name()
56                            .map(|ink_arg_name| ink_arg_name.syntax().text_range())
57                            .unwrap_or_else(|| ink_attr.syntax().text_range()),
58                        InkAttributeKind::Macro(_) => ink_attr
59                            .ink_macro()
60                            .map(|path_segment| path_segment.syntax().text_range())
61                            .unwrap_or_else(|| ink_attr.syntax().text_range()),
62                    },
63                    content: doc.to_owned(),
64                })
65            }
66        }
67    })
68}
69
70/// Returns documentation for the ink! attribute kind.
71pub fn content(
72    attr_kind: &InkAttributeKind,
73    version: Version,
74    primary_attr_kind: Option<&InkAttributeKind>,
75) -> &'static str {
76    match attr_kind {
77        InkAttributeKind::Arg(arg_kind) => match arg_kind {
78            InkArgKind::Abi if version.is_gte_v6() => args::ABI_DOC,
79            InkArgKind::AdditionalContracts if version.is_legacy() => {
80                args::ADDITIONAL_CONTRACTS_DOC_V4
81            }
82            InkArgKind::AdditionalContracts => args::ADDITIONAL_CONTRACTS_DOC_DEPRECATED,
83            InkArgKind::Anonymous if version.is_legacy() => args::ANONYMOUS_DOC_V4,
84            InkArgKind::Anonymous => args::ANONYMOUS_DOC,
85            // We enumerate the nested args for `backend` arg here, but, at the moment,
86            // only the "top-level" `backend` arg is ever sent even when focused on a nested arg.
87            InkArgKind::Backend
88            | InkArgKind::Node
89            | InkArgKind::Url
90            | InkArgKind::RuntimeOnly
91            | InkArgKind::Sandbox
92                if version.is_v5_0() =>
93            {
94                args::BACKEND_DOC_V5_0
95            }
96            InkArgKind::Backend
97            | InkArgKind::Node
98            | InkArgKind::Url
99            | InkArgKind::RuntimeOnly
100            | InkArgKind::Sandbox
101                if version.is_gte_v5_1() =>
102            {
103                args::BACKEND_DOC
104            }
105            InkArgKind::Constructor => args::CONSTRUCTOR_DOC,
106            InkArgKind::Decode if version.is_gte_v5() => args::DECODE_DOC,
107            InkArgKind::Default => args::DEFAULT_DOC,
108            InkArgKind::Derive => args::DERIVE_DOC,
109            InkArgKind::Encode if version.is_gte_v5() => args::ENCODE_DOC,
110            InkArgKind::Env | InkArgKind::Environment if version.is_lte_v5() => {
111                args::ENV_DOC_LTE_V5
112            }
113            InkArgKind::Env | InkArgKind::Environment => args::ENV_DOC,
114            InkArgKind::Event => args::EVENT_DOC,
115            InkArgKind::Extension if version.is_legacy() => args::EXTENSION_DOC_V4,
116            InkArgKind::Extension if version.is_v5() => args::EXTENSION_DOC,
117            InkArgKind::Extension => macros::CHAIN_EXTENSION_DOC_DEPRECATED,
118            InkArgKind::Function if version.is_v5() => args::FUNCTION_DOC,
119            InkArgKind::Function if version.is_gte_v6() => macros::CHAIN_EXTENSION_DOC_DEPRECATED,
120            InkArgKind::HandleStatus if version.is_lte_v5() => args::HANDLE_STATUS_DOC,
121            InkArgKind::HandleStatus => macros::CHAIN_EXTENSION_DOC_DEPRECATED,
122            InkArgKind::Impl => args::IMPL_DOC,
123            InkArgKind::KeepAttr if version.is_legacy() => args::KEEP_ATTR_DOC_V4,
124            InkArgKind::KeepAttr
125                if matches!(
126                    primary_attr_kind,
127                    Some(InkAttributeKind::Macro(InkMacroKind::E2ETest))
128                ) =>
129            {
130                args::KEEP_ATTR_E2E_DOC_DEPRECATED
131            }
132            InkArgKind::KeepAttr => args::KEEP_ATTR_DOC,
133            InkArgKind::Message => args::MESSAGE_DOC,
134            InkArgKind::Name if version.is_gte_v6() => args::NAME_DOC,
135            InkArgKind::Namespace => args::NAMESPACE_DOC,
136            InkArgKind::Packed if version.is_gte_v6() => args::PACKED_DOC,
137            InkArgKind::Payable => args::PAYABLE_DOC,
138            InkArgKind::Selector if version.is_legacy() => args::SELECTOR_DOC_V4,
139            InkArgKind::Selector => args::SELECTOR_DOC,
140            InkArgKind::SignatureTopic if version.is_gte_v5() => args::SIGNATURE_TOPIC,
141            InkArgKind::Storage => args::STORAGE_DOC,
142            InkArgKind::Topic if version.is_legacy() => args::TOPIC_DOC_V4,
143            InkArgKind::Topic => args::TOPIC_DOC,
144            InkArgKind::TypeInfo if version.is_gte_v5() => args::TYPE_INFO_DOC,
145            _ => "",
146        },
147        InkAttributeKind::Macro(macro_kind) => match macro_kind {
148            InkMacroKind::ChainExtension if version.is_legacy() => macros::CHAIN_EXTENSION_DOC_V4,
149            InkMacroKind::ChainExtension if version.is_v5() => macros::CHAIN_EXTENSION_DOC,
150            InkMacroKind::ChainExtension => macros::CHAIN_EXTENSION_DOC_DEPRECATED,
151            InkMacroKind::Contract => macros::CONTRACT_DOC,
152            InkMacroKind::ContractRef if version.is_gte_v6() => macros::CONTRACT_REF_DOC,
153            InkMacroKind::Error if version.is_gte_v6() => macros::ERROR_DOC,
154            InkMacroKind::Event if version.is_gte_v5() => macros::EVENT_DOC,
155            InkMacroKind::ScaleDerive if version.is_gte_v5() => macros::SCALE_DERIVE_DOC,
156            InkMacroKind::StorageItem => macros::STORAGE_ITEM_DOC,
157            InkMacroKind::Test => macros::TEST_DOC,
158            InkMacroKind::TraitDefinition => macros::TRAIT_DEFINITION_DOC,
159            InkMacroKind::E2ETest => macros::E2E_TEST_DOC,
160            _ => "",
161        },
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use ink_analyzer_ir::{syntax::TextSize, InkArgKind, InkMacroKind, MinorVersion};
169    use test_utils::parse_offset_at;
170
171    #[test]
172    fn hover_works() {
173        for version in [
174            Version::Legacy,
175            Version::V5(MinorVersion::Base),
176            Version::V5(MinorVersion::Latest),
177            Version::V6,
178        ] {
179            for (code, test_cases) in [
180                // (code, pat, [(text, pat_start, pat_end)]) where:
181                // code = source code,
182                // pat = substring used to find the cursor offset (see `test_utils::parse_offset_at` doc),
183                // text = text expected to be present in the hover content (represented without whitespace for simplicity),
184                // pat_start = substring used to find the start of the hover offset (see `test_utils::parse_offset_at` doc),
185                // pat_end = substring used to find the end of the hover offset (see `test_utils::parse_offset_at` doc).
186
187                // ink! attribute macros.
188                (
189                    "#[ink::contract]",
190                    vec![
191                        (
192                            Some("<-#"),
193                            Some("<-#"),
194                            Some((
195                                InkAttributeKind::Macro(InkMacroKind::Contract),
196                                Some("<-contract"),
197                                Some("contract"),
198                            )),
199                        ),
200                        (
201                            Some("<-#"),
202                            Some("ink"),
203                            Some((
204                                InkAttributeKind::Macro(InkMacroKind::Contract),
205                                Some("<-contract"),
206                                Some("contract"),
207                            )),
208                        ),
209                        (
210                            Some("<-contract"),
211                            Some("contract"),
212                            Some((
213                                InkAttributeKind::Macro(InkMacroKind::Contract),
214                                Some("<-contract"),
215                                Some("contract"),
216                            )),
217                        ),
218                        (
219                            Some("<-#"),
220                            Some("]"),
221                            Some((
222                                InkAttributeKind::Macro(InkMacroKind::Contract),
223                                Some("<-contract"),
224                                Some("contract"),
225                            )),
226                        ),
227                    ],
228                ),
229                (
230                    r#"
231                    #[ink::contract(env=my::env::Types, keep_attr="foo,bar")]
232                    "#,
233                    vec![
234                        (
235                            Some("<-#"),
236                            Some("<-#"),
237                            Some((
238                                InkAttributeKind::Macro(InkMacroKind::Contract),
239                                Some("<-contract"),
240                                Some("contract"),
241                            )),
242                        ),
243                        (
244                            Some("<-#"),
245                            Some("ink"),
246                            Some((
247                                InkAttributeKind::Macro(InkMacroKind::Contract),
248                                Some("<-contract"),
249                                Some("contract"),
250                            )),
251                        ),
252                        (
253                            Some("<-contract"),
254                            Some("contract"),
255                            Some((
256                                InkAttributeKind::Macro(InkMacroKind::Contract),
257                                Some("<-contract"),
258                                Some("contract"),
259                            )),
260                        ),
261                        (
262                            Some("<-#"),
263                            Some("]"),
264                            Some((
265                                InkAttributeKind::Macro(InkMacroKind::Contract),
266                                Some("<-contract"),
267                                Some("contract"),
268                            )),
269                        ),
270                        (
271                            Some("<-env="),
272                            Some("(env"),
273                            Some((
274                                InkAttributeKind::Arg(InkArgKind::Env),
275                                Some("<-env="),
276                                Some("(env"),
277                            )),
278                        ),
279                        (
280                            Some("<-my::env::Types"),
281                            Some("my::env::Types"),
282                            Some((
283                                InkAttributeKind::Arg(InkArgKind::Env),
284                                Some("<-env="),
285                                Some("(env"),
286                            )),
287                        ),
288                        (
289                            Some("<-,"),
290                            Some(","),
291                            Some((
292                                InkAttributeKind::Macro(InkMacroKind::Contract),
293                                Some("<-contract"),
294                                Some("contract"),
295                            )),
296                        ),
297                        (
298                            Some("<-keep_attr"),
299                            Some("keep_attr"),
300                            Some((
301                                InkAttributeKind::Arg(InkArgKind::KeepAttr),
302                                Some("<-keep_attr"),
303                                Some("keep_attr"),
304                            )),
305                        ),
306                        (
307                            Some(r#"<-"foo,bar""#),
308                            Some(r#""foo,bar""#),
309                            Some((
310                                InkAttributeKind::Arg(InkArgKind::KeepAttr),
311                                Some("<-keep_attr"),
312                                Some("keep_attr"),
313                            )),
314                        ),
315                    ],
316                ),
317                (
318                    "#[ink::contract_ref]",
319                    vec![
320                        (
321                            Some("<-#"),
322                            Some("<-#"),
323                            if version.is_lte_v5() {
324                                None
325                            } else {
326                                Some((
327                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
328                                    Some("<-contract_ref"),
329                                    Some("contract_ref"),
330                                ))
331                            },
332                        ),
333                        (
334                            Some("<-#"),
335                            Some("ink"),
336                            if version.is_lte_v5() {
337                                None
338                            } else {
339                                Some((
340                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
341                                    Some("<-contract_ref"),
342                                    Some("contract_ref"),
343                                ))
344                            },
345                        ),
346                        (
347                            Some("<-contract_ref"),
348                            Some("contract_ref"),
349                            if version.is_lte_v5() {
350                                None
351                            } else {
352                                Some((
353                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
354                                    Some("<-contract_ref"),
355                                    Some("contract_ref"),
356                                ))
357                            },
358                        ),
359                        (
360                            Some("<-#"),
361                            Some("]"),
362                            if version.is_lte_v5() {
363                                None
364                            } else {
365                                Some((
366                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
367                                    Some("<-contract_ref"),
368                                    Some("contract_ref"),
369                                ))
370                            },
371                        ),
372                    ],
373                ),
374                (
375                    r#"
376                    #[ink::contract_ref(abi="sol", env=my::env::Types)]
377                    "#,
378                    vec![
379                        (
380                            Some("<-#"),
381                            Some("<-#"),
382                            if version.is_lte_v5() {
383                                None
384                            } else {
385                                Some((
386                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
387                                    Some("<-contract_ref"),
388                                    Some("contract_ref"),
389                                ))
390                            },
391                        ),
392                        (
393                            Some("<-#"),
394                            Some("ink"),
395                            if version.is_lte_v5() {
396                                None
397                            } else {
398                                Some((
399                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
400                                    Some("<-contract_ref"),
401                                    Some("contract_ref"),
402                                ))
403                            },
404                        ),
405                        (
406                            Some("<-contract_ref"),
407                            Some("contract_ref"),
408                            if version.is_lte_v5() {
409                                None
410                            } else {
411                                Some((
412                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
413                                    Some("<-contract_ref"),
414                                    Some("contract_ref"),
415                                ))
416                            },
417                        ),
418                        (
419                            Some("<-#"),
420                            Some("]"),
421                            if version.is_lte_v5() {
422                                None
423                            } else {
424                                Some((
425                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
426                                    Some("<-contract_ref"),
427                                    Some("contract_ref"),
428                                ))
429                            },
430                        ),
431                        (
432                            Some("<-abi"),
433                            Some("abi"),
434                            if version.is_lte_v5() {
435                                None
436                            } else {
437                                Some((
438                                    InkAttributeKind::Arg(InkArgKind::Abi),
439                                    Some("<-abi"),
440                                    Some("abi"),
441                                ))
442                            },
443                        ),
444                        (
445                            Some(r#"<-"sol""#),
446                            Some(r#""sol""#),
447                            if version.is_lte_v5() {
448                                None
449                            } else {
450                                Some((
451                                    InkAttributeKind::Arg(InkArgKind::Abi),
452                                    Some("<-abi"),
453                                    Some("abi"),
454                                ))
455                            },
456                        ),
457                        (
458                            Some("<-,"),
459                            Some(","),
460                            if version.is_lte_v5() {
461                                None
462                            } else {
463                                Some((
464                                    InkAttributeKind::Macro(InkMacroKind::ContractRef),
465                                    Some("<-contract_ref"),
466                                    Some("contract_ref"),
467                                ))
468                            },
469                        ),
470                        (
471                            Some("<-env="),
472                            Some(", env"),
473                            Some((
474                                InkAttributeKind::Arg(InkArgKind::Env),
475                                Some("<-env="),
476                                Some(", env"),
477                            )),
478                        ),
479                        (
480                            Some("<-my::env::Types"),
481                            Some("my::env::Types"),
482                            Some((
483                                InkAttributeKind::Arg(InkArgKind::Env),
484                                Some("<-env="),
485                                Some(", env"),
486                            )),
487                        ),
488                    ],
489                ),
490                (
491                    "#[ink::error]",
492                    vec![
493                        (
494                            Some("<-#"),
495                            Some("<-#"),
496                            if version.is_lte_v5() {
497                                None
498                            } else {
499                                Some((
500                                    InkAttributeKind::Macro(InkMacroKind::Error),
501                                    Some("<-error"),
502                                    Some("error"),
503                                ))
504                            },
505                        ),
506                        (
507                            Some("<-#"),
508                            Some("ink"),
509                            if version.is_lte_v5() {
510                                None
511                            } else {
512                                Some((
513                                    InkAttributeKind::Macro(InkMacroKind::Error),
514                                    Some("<-error"),
515                                    Some("error"),
516                                ))
517                            },
518                        ),
519                        (
520                            Some("<-error"),
521                            Some("error"),
522                            if version.is_lte_v5() {
523                                None
524                            } else {
525                                Some((
526                                    InkAttributeKind::Macro(InkMacroKind::Error),
527                                    Some("<-error"),
528                                    Some("error"),
529                                ))
530                            },
531                        ),
532                        (
533                            Some("<-#"),
534                            Some("]"),
535                            if version.is_lte_v5() {
536                                None
537                            } else {
538                                Some((
539                                    InkAttributeKind::Macro(InkMacroKind::Error),
540                                    Some("<-error"),
541                                    Some("error"),
542                                ))
543                            },
544                        ),
545                    ],
546                ),
547                (
548                    "#[ink_e2e::test]",
549                    vec![
550                        (
551                            Some("<-#"),
552                            Some("<-#"),
553                            Some((
554                                InkAttributeKind::Macro(InkMacroKind::E2ETest),
555                                Some("<-test"),
556                                Some("test"),
557                            )),
558                        ),
559                        (
560                            Some("<-#"),
561                            Some("ink"),
562                            Some((
563                                InkAttributeKind::Macro(InkMacroKind::E2ETest),
564                                Some("<-test"),
565                                Some("test"),
566                            )),
567                        ),
568                        (
569                            Some("<-test"),
570                            Some("test"),
571                            Some((
572                                InkAttributeKind::Macro(InkMacroKind::E2ETest),
573                                Some("<-test"),
574                                Some("test"),
575                            )),
576                        ),
577                        (
578                            Some("<-#"),
579                            Some("]"),
580                            Some((
581                                InkAttributeKind::Macro(InkMacroKind::E2ETest),
582                                Some("<-test"),
583                                Some("test"),
584                            )),
585                        ),
586                    ],
587                ),
588                // ink! attribute arguments.
589                (
590                    "#[ink(storage)]",
591                    vec![
592                        (
593                            Some("<-#"),
594                            Some("<-#"),
595                            Some((
596                                InkAttributeKind::Arg(InkArgKind::Storage),
597                                Some("<-storage"),
598                                Some("storage"),
599                            )),
600                        ),
601                        (
602                            Some("<-#"),
603                            Some("ink"),
604                            Some((
605                                InkAttributeKind::Arg(InkArgKind::Storage),
606                                Some("<-storage"),
607                                Some("storage"),
608                            )),
609                        ),
610                        (
611                            Some("<-storage"),
612                            Some("storage"),
613                            Some((
614                                InkAttributeKind::Arg(InkArgKind::Storage),
615                                Some("<-storage"),
616                                Some("storage"),
617                            )),
618                        ),
619                        (
620                            Some("<-#"),
621                            Some("]"),
622                            Some((
623                                InkAttributeKind::Arg(InkArgKind::Storage),
624                                Some("<-storage"),
625                                Some("storage"),
626                            )),
627                        ),
628                    ],
629                ),
630                (
631                    r#"#[ink(message, default, payable, selector=_, name = "name")]"#,
632                    vec![
633                        (
634                            Some("<-#"),
635                            Some("<-#"),
636                            Some((
637                                InkAttributeKind::Arg(InkArgKind::Message),
638                                Some("<-message"),
639                                Some("message"),
640                            )),
641                        ),
642                        (
643                            Some("<-#"),
644                            Some("ink"),
645                            Some((
646                                InkAttributeKind::Arg(InkArgKind::Message),
647                                Some("<-message"),
648                                Some("message"),
649                            )),
650                        ),
651                        (
652                            Some("<-message"),
653                            Some("message"),
654                            Some((
655                                InkAttributeKind::Arg(InkArgKind::Message),
656                                Some("<-message"),
657                                Some("message"),
658                            )),
659                        ),
660                        (
661                            Some("<-#"),
662                            Some("]"),
663                            Some((
664                                InkAttributeKind::Arg(InkArgKind::Message),
665                                Some("<-message"),
666                                Some("message"),
667                            )),
668                        ),
669                        (
670                            Some("<-payable"),
671                            Some("payable"),
672                            Some((
673                                InkAttributeKind::Arg(InkArgKind::Payable),
674                                Some("<-payable"),
675                                Some("payable"),
676                            )),
677                        ),
678                        (
679                            Some("<-selector"),
680                            Some("selector"),
681                            Some((
682                                InkAttributeKind::Arg(InkArgKind::Selector),
683                                Some("<-selector"),
684                                Some("selector"),
685                            )),
686                        ),
687                        (
688                            Some("<-_"),
689                            Some("_"),
690                            Some((
691                                InkAttributeKind::Arg(InkArgKind::Selector),
692                                Some("<-selector"),
693                                Some("selector"),
694                            )),
695                        ),
696                        (
697                            Some("<-name"),
698                            Some("name"),
699                            if version.is_lte_v5() {
700                                None
701                            } else {
702                                Some((
703                                    InkAttributeKind::Arg(InkArgKind::Name),
704                                    Some("<-name"),
705                                    Some("name"),
706                                ))
707                            },
708                        ),
709                        (
710                            Some(r#"<-"name""#),
711                            Some(r#""name""#),
712                            if version.is_lte_v5() {
713                                None
714                            } else {
715                                Some((
716                                    InkAttributeKind::Arg(InkArgKind::Name),
717                                    Some("<-name"),
718                                    Some("name"),
719                                ))
720                            },
721                        ),
722                    ],
723                ),
724                (
725                    "#[ink(extension=1, handle_status=true)]",
726                    vec![
727                        (
728                            Some("<-#"),
729                            Some("<-#"),
730                            Some((
731                                InkAttributeKind::Arg(InkArgKind::Extension),
732                                Some("<-extension"),
733                                Some("extension"),
734                            )),
735                        ),
736                        (
737                            Some("<-#"),
738                            Some("ink"),
739                            Some((
740                                InkAttributeKind::Arg(InkArgKind::Extension),
741                                Some("<-extension"),
742                                Some("extension"),
743                            )),
744                        ),
745                        (
746                            Some("<-extension"),
747                            Some("extension"),
748                            Some((
749                                InkAttributeKind::Arg(InkArgKind::Extension),
750                                Some("<-extension"),
751                                Some("extension"),
752                            )),
753                        ),
754                        (
755                            Some("<-#"),
756                            Some("]"),
757                            Some((
758                                InkAttributeKind::Arg(InkArgKind::Extension),
759                                Some("<-extension"),
760                                Some("extension"),
761                            )),
762                        ),
763                        (
764                            Some("<-1"),
765                            Some("1"),
766                            Some((
767                                InkAttributeKind::Arg(InkArgKind::Extension),
768                                Some("<-extension"),
769                                Some("extension"),
770                            )),
771                        ),
772                        (
773                            Some("<-handle_status"),
774                            Some("handle_status"),
775                            Some((
776                                InkAttributeKind::Arg(InkArgKind::HandleStatus),
777                                Some("<-handle_status"),
778                                Some("handle_status"),
779                            )),
780                        ),
781                        (
782                            Some("<-true"),
783                            Some("true"),
784                            Some((
785                                InkAttributeKind::Arg(InkArgKind::HandleStatus),
786                                Some("<-handle_status"),
787                                Some("handle_status"),
788                            )),
789                        ),
790                    ],
791                ),
792            ] {
793                for (pat_start, pat_end, expect_result) in test_cases {
794                    let range = TextRange::new(
795                        TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32),
796                        TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32),
797                    );
798
799                    let result = hover(&InkFile::parse(code), range, version);
800
801                    assert_eq!(
802                        result.as_ref().map(|hover_result| (
803                            hover_result.content.as_str(),
804                            hover_result.range
805                        )),
806                        expect_result.map(|(attr_kind, pat_start, pat_end)| (
807                            content(&attr_kind, version, None),
808                            TextRange::new(
809                                TextSize::from(parse_offset_at(code, pat_start).unwrap() as u32),
810                                TextSize::from(parse_offset_at(code, pat_end).unwrap() as u32)
811                            )
812                        )),
813                        "code: {code}, start: {:?}, end: {:?}, version: {:?}",
814                        pat_start,
815                        pat_end,
816                        version,
817                    );
818                }
819            }
820        }
821    }
822}