Copy outline improvements from typescript over to tsx as well (#41862)

KyleBarton created

Closes #4483 

Release Notes:

- Interprets outline of tsx files with the same grammar as typescript,
including improvements from #39797

Change summary

crates/languages/src/tsx/outline.scm | 145 ++++++++++++--
crates/languages/src/typescript.rs   | 306 +++++++++++++++--------------
2 files changed, 283 insertions(+), 168 deletions(-)

Detailed changes

crates/languages/src/tsx/outline.scm 🔗

@@ -34,18 +34,64 @@
 (export_statement
     (lexical_declaration
         ["let" "const"] @context
-        ; Multiple names may be exported - @item is on the declarator to keep
-        ; ranges distinct.
         (variable_declarator
-            name: (_) @name) @item))
+            name: (identifier) @name) @item))
 
+; Exported array destructuring
+(export_statement
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (array_pattern
+                [
+                    (identifier) @name @item
+                    (assignment_pattern left: (identifier) @name @item)
+                    (rest_pattern (identifier) @name @item)
+                ]))))
+
+; Exported object destructuring
+(export_statement
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (object_pattern
+                [(shorthand_property_identifier_pattern) @name @item
+                 (pair_pattern
+                     value: (identifier) @name @item)
+                 (pair_pattern
+                     value: (assignment_pattern left: (identifier) @name @item))
+                 (rest_pattern (identifier) @name @item)]))))
+
+(program
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (identifier) @name) @item))
+
+; Top-level array destructuring
 (program
     (lexical_declaration
         ["let" "const"] @context
-        ; Multiple names may be defined - @item is on the declarator to keep
-        ; ranges distinct.
         (variable_declarator
-            name: (_) @name) @item))
+            name: (array_pattern
+                [
+                    (identifier) @name @item
+                    (assignment_pattern left: (identifier) @name @item)
+                    (rest_pattern (identifier) @name @item)
+                ]))))
+
+; Top-level object destructuring
+(program
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (object_pattern
+                [(shorthand_property_identifier_pattern) @name @item
+                 (pair_pattern
+                     value: (identifier) @name @item)
+                 (pair_pattern
+                     value: (assignment_pattern left: (identifier) @name @item))
+                 (rest_pattern (identifier) @name @item)]))))
 
 (class_declaration
     "class" @context
@@ -56,21 +102,38 @@
     "class" @context
     name: (_) @name) @item
 
-(method_definition
-    [
-        "get"
-        "set"
-        "async"
-        "*"
-        "readonly"
-        "static"
-        (override_modifier)
-        (accessibility_modifier)
-    ]* @context
-    name: (_) @name
-    parameters: (formal_parameters
-      "(" @context
-      ")" @context)) @item
+; Method definitions in classes (not in object literals)
+(class_body
+    (method_definition
+        [
+            "get"
+            "set"
+            "async"
+            "*"
+            "readonly"
+            "static"
+            (override_modifier)
+            (accessibility_modifier)
+        ]* @context
+        name: (_) @name
+        parameters: (formal_parameters
+          "(" @context
+          ")" @context)) @item)
+
+; Object literal methods
+(variable_declarator
+    value: (object
+        (method_definition
+            [
+                "get"
+                "set"
+                "async"
+                "*"
+            ]* @context
+            name: (_) @name
+            parameters: (formal_parameters
+              "(" @context
+              ")" @context)) @item))
 
 (public_field_definition
     [
@@ -124,4 +187,44 @@
     )
 ) @item
 
+; Object properties
+(pair
+    key: [
+        (property_identifier) @name
+        (string (string_fragment) @name)
+        (number) @name
+        (computed_property_name) @name
+    ]) @item
+
+
+; Nested variables in function bodies
+(statement_block
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (identifier) @name) @item))
+
+; Nested array destructuring in functions
+(statement_block
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (array_pattern
+                [
+                    (identifier) @name @item
+                    (assignment_pattern left: (identifier) @name @item)
+                    (rest_pattern (identifier) @name @item)
+                ]))))
+
+; Nested object destructuring in functions
+(statement_block
+    (lexical_declaration
+        ["let" "const"] @context
+        (variable_declarator
+            name: (object_pattern
+                [(shorthand_property_identifier_pattern) @name @item
+                 (pair_pattern value: (identifier) @name @item)
+                 (pair_pattern value: (assignment_pattern left: (identifier) @name @item))
+                 (rest_pattern (identifier) @name @item)]))))
+
 (comment) @annotation

crates/languages/src/typescript.rs 🔗

@@ -1103,12 +1103,14 @@ mod tests {
 
     #[gpui::test]
     async fn test_outline(cx: &mut TestAppContext) {
-        let language = crate::language(
-            "typescript",
-            tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
-        );
-
-        let text = r#"
+        for language in [
+            crate::language(
+                "typescript",
+                tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
+            ),
+            crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()),
+        ] {
+            let text = r#"
             function a() {
               // local variables are included
               let a1 = 1;
@@ -1121,35 +1123,38 @@ mod tests {
             // exported variables are included
             export const d = e;
         "#
-        .unindent();
-
-        let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
-        let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
-        assert_eq!(
-            outline
-                .items
-                .iter()
-                .map(|item| (item.text.as_str(), item.depth))
-                .collect::<Vec<_>>(),
-            &[
-                ("function a()", 0),
-                ("let a1", 1),
-                ("async function a2()", 1),
-                ("let b", 0),
-                ("function getB()", 0),
-                ("const d", 0),
-            ]
-        );
+            .unindent();
+
+            let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
+            let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
+            assert_eq!(
+                outline
+                    .items
+                    .iter()
+                    .map(|item| (item.text.as_str(), item.depth))
+                    .collect::<Vec<_>>(),
+                &[
+                    ("function a()", 0),
+                    ("let a1", 1),
+                    ("async function a2()", 1),
+                    ("let b", 0),
+                    ("function getB()", 0),
+                    ("const d", 0),
+                ]
+            );
+        }
     }
 
     #[gpui::test]
     async fn test_outline_with_destructuring(cx: &mut TestAppContext) {
-        let language = crate::language(
-            "typescript",
-            tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
-        );
-
-        let text = r#"
+        for language in [
+            crate::language(
+                "typescript",
+                tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
+            ),
+            crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()),
+        ] {
+            let text = r#"
             // Top-level destructuring
             const { a1, a2 } = a;
             const [b1, b2] = b;
@@ -1177,55 +1182,58 @@ mod tests {
               }
             }
         "#
-        .unindent();
-
-        let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
-        let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
-        assert_eq!(
-            outline
-                .items
-                .iter()
-                .map(|item| (item.text.as_str(), item.depth))
-                .collect::<Vec<_>>(),
-            &[
-                ("const a1", 0),
-                ("const a2", 0),
-                ("const b1", 0),
-                ("const b2", 0),
-                ("const c1", 0),
-                ("const c2", 0),
-                ("const rest1", 0),
-                ("const d1", 0),
-                ("const e1", 0),
-                ("const h1", 0),
-                ("const rest2", 0),
-                ("function processData()", 0),
-                ("const c1", 1),
-                ("const c2", 1),
-                ("const d1", 1),
-                ("const d2", 1),
-                ("const d3", 1),
-                ("const g1", 1),
-                ("const x", 1),
-                ("const y", 1),
-                ("class DataHandler", 0),
-                ("method()", 1),
-                ("const a1", 2),
-                ("const a2", 2),
-                ("const b1", 2),
-                ("const b2", 2),
-            ]
-        );
+            .unindent();
+
+            let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
+            let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
+            assert_eq!(
+                outline
+                    .items
+                    .iter()
+                    .map(|item| (item.text.as_str(), item.depth))
+                    .collect::<Vec<_>>(),
+                &[
+                    ("const a1", 0),
+                    ("const a2", 0),
+                    ("const b1", 0),
+                    ("const b2", 0),
+                    ("const c1", 0),
+                    ("const c2", 0),
+                    ("const rest1", 0),
+                    ("const d1", 0),
+                    ("const e1", 0),
+                    ("const h1", 0),
+                    ("const rest2", 0),
+                    ("function processData()", 0),
+                    ("const c1", 1),
+                    ("const c2", 1),
+                    ("const d1", 1),
+                    ("const d2", 1),
+                    ("const d3", 1),
+                    ("const g1", 1),
+                    ("const x", 1),
+                    ("const y", 1),
+                    ("class DataHandler", 0),
+                    ("method()", 1),
+                    ("const a1", 2),
+                    ("const a2", 2),
+                    ("const b1", 2),
+                    ("const b2", 2),
+                ]
+            );
+        }
     }
 
     #[gpui::test]
     async fn test_outline_with_object_properties(cx: &mut TestAppContext) {
-        let language = crate::language(
-            "typescript",
-            tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
-        );
-
-        let text = r#"
+        for language in [
+            crate::language(
+                "typescript",
+                tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
+            ),
+            crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()),
+        ] {
+            let text = r#"
             // Object with function properties
             const o = { m() {}, async n() {}, g: function* () {}, h: () => {}, k: function () {} };
 
@@ -1246,47 +1254,50 @@ mod tests {
                 return local;
             }
         "#
-        .unindent();
-
-        let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
-        let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
-        assert_eq!(
-            outline
-                .items
-                .iter()
-                .map(|item| (item.text.as_str(), item.depth))
-                .collect::<Vec<_>>(),
-            &[
-                ("const o", 0),
-                ("m()", 1),
-                ("async n()", 1),
-                ("g", 1),
-                ("h", 1),
-                ("k", 1),
-                ("const p", 0),
-                ("p1", 1),
-                ("p2", 1),
-                ("p3", 1),
-                ("const q", 0),
-                ("r", 1),
-                ("s", 2),
-                ("t", 1),
-                ("function getData()", 0),
-                ("const local", 1),
-                ("x", 2),
-                ("y", 2),
-            ]
-        );
+            .unindent();
+
+            let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
+            let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
+            assert_eq!(
+                outline
+                    .items
+                    .iter()
+                    .map(|item| (item.text.as_str(), item.depth))
+                    .collect::<Vec<_>>(),
+                &[
+                    ("const o", 0),
+                    ("m()", 1),
+                    ("async n()", 1),
+                    ("g", 1),
+                    ("h", 1),
+                    ("k", 1),
+                    ("const p", 0),
+                    ("p1", 1),
+                    ("p2", 1),
+                    ("p3", 1),
+                    ("const q", 0),
+                    ("r", 1),
+                    ("s", 2),
+                    ("t", 1),
+                    ("function getData()", 0),
+                    ("const local", 1),
+                    ("x", 2),
+                    ("y", 2),
+                ]
+            );
+        }
     }
 
     #[gpui::test]
     async fn test_outline_with_computed_property_names(cx: &mut TestAppContext) {
-        let language = crate::language(
-            "typescript",
-            tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
-        );
-
-        let text = r#"
+        for language in [
+            crate::language(
+                "typescript",
+                tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
+            ),
+            crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()),
+        ] {
+            let text = r#"
             // Symbols as object keys
             const sym = Symbol("test");
             const obj1 = {
@@ -1323,40 +1334,41 @@ mod tests {
                 }
             };
         "#
-        .unindent();
-
-        let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
-        let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
-        assert_eq!(
-            outline
-                .items
-                .iter()
-                .map(|item| (item.text.as_str(), item.depth))
-                .collect::<Vec<_>>(),
-            &[
-                ("const sym", 0),
-                ("const obj1", 0),
-                ("[sym]", 1),
-                ("[Symbol(\"inline\")]", 1),
-                ("normalKey", 1),
-                ("enum Color", 0),
-                ("const obj2", 0),
-                ("[Color.Red]", 1),
-                ("[Color.Blue]", 1),
-                ("regularProp", 1),
-                ("const key", 0),
-                ("const obj3", 0),
-                ("[key]", 1),
-                ("[\"string\" + \"concat\"]", 1),
-                ("[1 + 1]", 1),
-                ("static", 1),
-                ("const obj4", 0),
-                ("[sym]", 1),
-                ("nested", 2),
-                ("regular", 1),
-                ("[key]", 2),
-            ]
-        );
+            .unindent();
+
+            let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
+            let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
+            assert_eq!(
+                outline
+                    .items
+                    .iter()
+                    .map(|item| (item.text.as_str(), item.depth))
+                    .collect::<Vec<_>>(),
+                &[
+                    ("const sym", 0),
+                    ("const obj1", 0),
+                    ("[sym]", 1),
+                    ("[Symbol(\"inline\")]", 1),
+                    ("normalKey", 1),
+                    ("enum Color", 0),
+                    ("const obj2", 0),
+                    ("[Color.Red]", 1),
+                    ("[Color.Blue]", 1),
+                    ("regularProp", 1),
+                    ("const key", 0),
+                    ("const obj3", 0),
+                    ("[key]", 1),
+                    ("[\"string\" + \"concat\"]", 1),
+                    ("[1 + 1]", 1),
+                    ("static", 1),
+                    ("const obj4", 0),
+                    ("[sym]", 1),
+                    ("nested", 2),
+                    ("regular", 1),
+                    ("[key]", 2),
+                ]
+            );
+        }
     }
 
     #[gpui::test]