From 5ed458497ebb80c6d24dc48d90b0696ff7163127 Mon Sep 17 00:00:00 2001 From: KyleBarton Date: Mon, 3 Nov 2025 14:38:05 -0800 Subject: [PATCH] Copy outline improvements from typescript over to tsx as well (#41862) Closes #4483 Release Notes: - Interprets outline of tsx files with the same grammar as typescript, including improvements from #39797 --- crates/languages/src/tsx/outline.scm | 145 +++++++++++-- crates/languages/src/typescript.rs | 306 ++++++++++++++------------- 2 files changed, 283 insertions(+), 168 deletions(-) diff --git a/crates/languages/src/tsx/outline.scm b/crates/languages/src/tsx/outline.scm index f4261b9697d376f517b717bc942387190e0b6dde..54d29007c7b7eb57c0bcaefc2c1e0ab75e4d9a6c 100644 --- a/crates/languages/src/tsx/outline.scm +++ b/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 diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 334fd4c4a717d2b0a9890611ff5cc21f3d898aeb..50688c6f20a7a1e4cdbae01cdc56c09eefde4edf 100644 --- a/crates/languages/src/typescript.rs +++ b/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::>(), - &[ - ("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::>(), + &[ + ("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::>(), - &[ - ("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::>(), + &[ + ("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::>(), - &[ - ("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::>(), + &[ + ("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::>(), - &[ - ("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::>(), + &[ + ("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]