diff --git a/crates/grammars/src/javascript/outline.scm b/crates/grammars/src/javascript/outline.scm index 7b8e4b2d46c9b88e6b719ceea5bb64eeb19af518..ce6d9e6bd9469c5896b61fbfb7676f9efbe66006 100644 --- a/crates/grammars/src/javascript/outline.scm +++ b/crates/grammars/src/javascript/outline.scm @@ -144,20 +144,19 @@ "(" @context ")" @context)) @item) -; Object literal methods -(variable_declarator - value: (object - (method_definition - [ - "get" - "set" - "async" - "*" - ]* @context - name: (_) @name - parameters: (formal_parameters - "(" @context - ")" @context)) @item)) +; Object literal methods (including nested objects) +(object + (method_definition + [ + "get" + "set" + "async" + "*" + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item) (public_field_definition [ diff --git a/crates/grammars/src/tsx/outline.scm b/crates/grammars/src/tsx/outline.scm index 37991965256a0def9b0458958ac4e50c6f337af6..2c08c30ad587ffc6568a5d6bfcd6e983bfbcfa1f 100644 --- a/crates/grammars/src/tsx/outline.scm +++ b/crates/grammars/src/tsx/outline.scm @@ -150,20 +150,19 @@ "(" @context ")" @context)) @item) -; Object literal methods -(variable_declarator - value: (object - (method_definition - [ - "get" - "set" - "async" - "*" - ]* @context - name: (_) @name - parameters: (formal_parameters - "(" @context - ")" @context)) @item)) +; Object literal methods (including nested objects) +(object + (method_definition + [ + "get" + "set" + "async" + "*" + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item) (public_field_definition [ diff --git a/crates/grammars/src/typescript/outline.scm b/crates/grammars/src/typescript/outline.scm index 37991965256a0def9b0458958ac4e50c6f337af6..2c08c30ad587ffc6568a5d6bfcd6e983bfbcfa1f 100644 --- a/crates/grammars/src/typescript/outline.scm +++ b/crates/grammars/src/typescript/outline.scm @@ -150,20 +150,19 @@ "(" @context ")" @context)) @item) -; Object literal methods -(variable_declarator - value: (object - (method_definition - [ - "get" - "set" - "async" - "*" - ]* @context - name: (_) @name - parameters: (formal_parameters - "(" @context - ")" @context)) @item)) +; Object literal methods (including nested objects) +(object + (method_definition + [ + "get" + "set" + "async" + "*" + ]* @context + name: (_) @name + parameters: (formal_parameters + "(" @context + ")" @context)) @item) (public_field_definition [ diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 714191ace093aa4c592316692dae3db0cdc24223..a83e36270d2ca1b91bca8f15725021939226e83a 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -1087,6 +1087,213 @@ mod tests { } } + #[gpui::test] + async fn test_outline_with_nested_object_methods(cx: &mut TestAppContext) { + for language in [ + crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ), + crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()), + crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into()), + ] { + let text = r#" + // Reproduction from https://github.com/zed-industries/zed/issues/48711 + const a = { + p01: '01', + fn01: () => {}, + fn02() {}, + deep: { + subFn01: () => {}, + subFn02() {}, + subP03: '03', + deep2: { + subFn01: () => {}, + subFn02() {}, + subP03: '03', + }, + }, + }; + + // Edge case: async methods in nested objects + const b = { + async topAsync() {}, + nested: { async nestedAsync() {} }, + }; + + // Edge case: object literal in function argument + foo({ bar() {}, inner: { baz() {} } }); + "# + .unindent(); + + let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx)); + cx.run_until_parked(); + let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None)); + + let items: Vec<_> = outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect(); + + assert_eq!( + items, + &[ + ("const a", 0), + ("p01", 1), + ("fn01", 1), + ("fn02()", 1), + ("deep", 1), + ("subFn01", 2), + ("subFn02()", 2), + ("subP03", 2), + ("deep2", 2), + ("subFn01", 3), + ("subFn02()", 3), + ("subP03", 3), + ("const b", 0), + ("async topAsync()", 1), + ("nested", 1), + ("async nestedAsync()", 2), + ("bar()", 0), + ("inner", 0), + ("baz()", 1), + ] + ); + } + } + + #[gpui::test] + async fn test_outline_with_complex_nested_objects(cx: &mut TestAppContext) { + for language in [ + crate::language( + "typescript", + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), + ), + crate::language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into()), + crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into()), + ] { + let text = r#" + const config = { + init() {}, + destroy() {}, + api: { + baseUrl: "x", + fetchData() {}, + async submitForm() {}, + errorHandler() {}, + }, + features: { + auth: { + login() {}, + logout() {}, + refreshToken() {}, + }, + cache: { + get() {}, + set() {}, + invalidate() {}, + }, + }, + watch: { + value() {}, + }, + computed: { + fullName() {}, + displayValue() {}, + }, + }; + + registerPlugin({ + name: "my-plugin", + setup() {}, + teardown() {}, + hooks: { + beforeMount() {}, + mounted() {}, + beforeUnmount() {}, + }, + }); + + export const store = { + state: {}, + mutations: { + setUser() {}, + clearUser() {}, + }, + actions: { + async fetchUser() {}, + logout() {}, + }, + getters: { + currentUser() {}, + isAuthenticated() {}, + }, + }; + + function registerPlugin(_plugin: unknown) {} + "# + .unindent(); + + let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx)); + cx.run_until_parked(); + let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None)); + + let items: Vec<_> = outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect(); + + assert_eq!( + items, + &[ + ("const config", 0), + ("init()", 1), + ("destroy()", 1), + ("api", 1), + ("baseUrl", 2), + ("fetchData()", 2), + ("async submitForm()", 2), + ("errorHandler()", 2), + ("features", 1), + ("auth", 2), + ("login()", 3), + ("logout()", 3), + ("refreshToken()", 3), + ("cache", 2), + ("get()", 3), + ("set()", 3), + ("invalidate()", 3), + ("watch", 1), + ("value()", 2), + ("computed", 1), + ("fullName()", 2), + ("displayValue()", 2), + ("name", 0), + ("setup()", 0), + ("teardown()", 0), + ("hooks", 0), + ("beforeMount()", 1), + ("mounted()", 1), + ("beforeUnmount()", 1), + ("const store", 0), + ("state", 1), + ("mutations", 1), + ("setUser()", 2), + ("clearUser()", 2), + ("actions", 1), + ("async fetchUser()", 2), + ("logout()", 2), + ("getters", 1), + ("currentUser()", 2), + ("isAuthenticated()", 2), + ("function registerPlugin( )", 0), + ] + ); + } + } + #[gpui::test] async fn test_outline_with_computed_property_names(cx: &mut TestAppContext) { for language in [