languages: Fix nested object methods missing from outline panel (#50754)
Shorthand methods (`methodName() {}`) inside nested object literals were
not shown in the Outline panel. The outline query only captured
method_definition when it was a direct child of a variable_declarator's
object, so nested objects (e.g. `deep: { subFn() {} }`) were missed.
Extended the outline query to match method_definition in any object
node, not just top-level variable_declarator objects. Applied the fix to
TypeScript, JavaScript, and TSX outline definitions. Added tests for the
issue reproduction and edge cases.
Supporting evidence:
<img width="1352" height="812" alt="image"
src="https://github.com/user-attachments/assets/64868a70-abd3-4935-9c03-4c809b55262b"
/>
Fixes #48711
Release Notes:
- Fixed nested object methods not appearing in the Outline panel for
JavaScript and TypeScript files
This commit is contained in:
1 parent
b8bec905de
commit
f2cead49b1
4 files changed
+246
-42
No files matched your search
@@ -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
|
||||
[
|
||||
|
||||
@@ -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
|
||||
[
|
||||
|
||||
@@ -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
|
||||
[
|
||||
|
||||
@@ -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 [
|
||||
|
||||
Reference in new issue
Block a user