mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
go: fix table test detection in large files
This commit is contained in:
parent
a737361b20
commit
c791f4f85b
3 changed files with 388 additions and 33 deletions
|
|
@ -85,24 +85,26 @@
|
|||
(#eq? @_key_type "string"))
|
||||
]
|
||||
body: (literal_value
|
||||
[
|
||||
([
|
||||
(literal_element
|
||||
(literal_value
|
||||
(keyed_element
|
||||
((keyed_element
|
||||
(literal_element
|
||||
(identifier) @_field_name)
|
||||
(literal_element
|
||||
[
|
||||
(interpreted_string_literal) @run @_table_test_case_name
|
||||
(raw_string_literal) @run @_table_test_case_name
|
||||
]))))
|
||||
]))
|
||||
","?)+))
|
||||
(keyed_element
|
||||
(literal_element
|
||||
[
|
||||
(interpreted_string_literal) @run @_table_test_case_name
|
||||
(raw_string_literal) @run @_table_test_case_name
|
||||
]))
|
||||
]))))
|
||||
] @_run_item
|
||||
","?)+))))
|
||||
(for_statement
|
||||
(range_clause
|
||||
left: (expression_list
|
||||
|
|
@ -127,8 +129,7 @@
|
|||
(selector_expression
|
||||
operand: (identifier) @_tc_var
|
||||
(#eq? @_tc_var @_loop_var_inner)
|
||||
field: (field_identifier) @_field_check
|
||||
(#eq? @_field_check @_field_name))
|
||||
field: (field_identifier) @_field_check)
|
||||
(identifier) @_arg_var
|
||||
(#eq? @_arg_var @_loop_var_outer)
|
||||
]
|
||||
|
|
@ -162,24 +163,26 @@
|
|||
(#eq? @_key_type "string"))
|
||||
]
|
||||
body: (literal_value
|
||||
[
|
||||
([
|
||||
(literal_element
|
||||
(literal_value
|
||||
(keyed_element
|
||||
((keyed_element
|
||||
(literal_element
|
||||
(identifier) @_field_name)
|
||||
(literal_element
|
||||
[
|
||||
(interpreted_string_literal) @run @_table_test_case_name
|
||||
(raw_string_literal) @run @_table_test_case_name
|
||||
]))))
|
||||
]))
|
||||
","?)+))
|
||||
(keyed_element
|
||||
(literal_element
|
||||
[
|
||||
(interpreted_string_literal) @run @_table_test_case_name
|
||||
(raw_string_literal) @run @_table_test_case_name
|
||||
]))
|
||||
])))
|
||||
] @_run_item
|
||||
","?)+)))
|
||||
body: (block
|
||||
(statement_list
|
||||
(expression_statement
|
||||
|
|
@ -194,8 +197,7 @@
|
|||
(selector_expression
|
||||
operand: (identifier) @_tc_var
|
||||
(#eq? @_tc_var @_loop_var_inner)
|
||||
field: (field_identifier) @_field_check
|
||||
(#eq? @_field_check @_field_name))
|
||||
field: (field_identifier) @_field_check)
|
||||
(identifier) @_arg_var
|
||||
(#eq? @_arg_var @_loop_var_outer)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -586,6 +586,74 @@ fn adjust_runs(
|
|||
|
||||
pub(crate) struct GoContextProvider;
|
||||
|
||||
pub(crate) struct GoRunnableResolver;
|
||||
|
||||
impl RunnableResolver for GoRunnableResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
local_captures: &[RunnableMatchCapture],
|
||||
shared_captures: &[RunnableMatchCapture],
|
||||
buffer: &BufferSnapshot,
|
||||
) -> Option<ResolvedRunnable> {
|
||||
const FIELD_CHECK: &str = "_field_check";
|
||||
const FIELD_NAME: &str = "_field_name";
|
||||
const TABLE_TEST_CASE_NAME: &str = "_table_test_case_name";
|
||||
|
||||
let text_for_capture = |capture: &RunnableMatchCapture| -> String {
|
||||
buffer.text_for_range(capture.range()).collect()
|
||||
};
|
||||
|
||||
// A candidate can have multiple string fields (e.g. `{ name: "x", anotherStr: "y" }`);
|
||||
// each keyed_element contributes one `@_field_name` + one `@run` pair, in
|
||||
// source order. The pair we want is the one whose name text equals
|
||||
// `@_field_check` — that's the field `t.Run(tc.<field>, ...)` selects on.
|
||||
let reference_text = shared_captures.iter().find_map(|capture| {
|
||||
(capture.name() == Some(FIELD_CHECK)).then(|| text_for_capture(capture))
|
||||
});
|
||||
|
||||
let mut name_captures: smallvec::SmallVec<[&RunnableMatchCapture; 2]> =
|
||||
smallvec::SmallVec::new();
|
||||
let mut run_captures: smallvec::SmallVec<[&RunnableMatchCapture; 2]> =
|
||||
smallvec::SmallVec::new();
|
||||
for capture in local_captures {
|
||||
if capture.name() == Some(FIELD_NAME) {
|
||||
name_captures.push(capture);
|
||||
} else if capture.is_run() {
|
||||
run_captures.push(capture);
|
||||
}
|
||||
}
|
||||
|
||||
let pair_index = match reference_text.as_deref() {
|
||||
Some(reference) => name_captures.iter().position(|capture| {
|
||||
let range = capture.range();
|
||||
range.len() == reference.len() && buffer.text_for_range(range).equals_str(reference)
|
||||
}),
|
||||
None => (!run_captures.is_empty()).then_some(0),
|
||||
}?;
|
||||
let run_capture = *run_captures.get(pair_index)?;
|
||||
let matched_name_capture = name_captures.get(pair_index).copied();
|
||||
let run_range = run_capture.range();
|
||||
|
||||
let mut extra_captures: smallvec::SmallVec<[(String, String); 2]> =
|
||||
smallvec::SmallVec::new();
|
||||
if let Some(name_capture) = matched_name_capture
|
||||
&& let Some(name) = name_capture.name()
|
||||
{
|
||||
extra_captures.push((name.to_string(), text_for_capture(name_capture)));
|
||||
}
|
||||
for capture in local_captures {
|
||||
if capture.name() == Some(TABLE_TEST_CASE_NAME) && capture.range() == run_range {
|
||||
extra_captures.push((TABLE_TEST_CASE_NAME.to_string(), text_for_capture(capture)));
|
||||
}
|
||||
}
|
||||
|
||||
Some(ResolvedRunnable {
|
||||
run_range,
|
||||
extra_captures,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE"));
|
||||
const GO_MODULE_ROOT_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("GO_MODULE_ROOT"));
|
||||
|
|
@ -894,10 +962,22 @@ mod tests {
|
|||
use gpui::{AppContext, Hsla, TestAppContext};
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
fn go_language() -> Arc<Language> {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = Arc::try_unwrap(language)
|
||||
.ok()
|
||||
.expect("test language arc should be unique");
|
||||
Arc::new(
|
||||
language
|
||||
.with_context_provider(Some(Arc::new(GoContextProvider)))
|
||||
.with_runnable_resolver(Some(Arc::new(GoRunnableResolver))),
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_go_label_for_completion() {
|
||||
let adapter = Arc::new(GoLspAdapter);
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let theme = SyntaxTheme::new_test([
|
||||
("type", Hsla::default()),
|
||||
|
|
@ -985,7 +1065,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_test_main_ignored(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let example_test = r#"
|
||||
package main
|
||||
|
|
@ -1019,7 +1099,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_testify_suite_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let testify_suite = r#"
|
||||
package main
|
||||
|
|
@ -1072,7 +1152,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_runnable_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let interpreted_string_subtest = r#"
|
||||
package main
|
||||
|
|
@ -1161,7 +1241,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_example_test_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let example_test = r#"
|
||||
package main
|
||||
|
|
@ -1198,7 +1278,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_slice_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
|
@ -1272,27 +1352,54 @@ mod tests {
|
|||
);
|
||||
|
||||
let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
|
||||
// This is currently broken; see #39148
|
||||
// let go_table_test_count = tag_strings
|
||||
// .iter()
|
||||
// .filter(|&tag| tag == "go-table-test-case")
|
||||
// .count();
|
||||
let go_table_test_count = tag_strings
|
||||
.iter()
|
||||
.filter(|&tag| tag == "go-table-test-case")
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
go_test_count == 1,
|
||||
"Should find exactly 1 go-test, found: {}",
|
||||
go_test_count
|
||||
);
|
||||
// assert!(
|
||||
// go_table_test_count == 3,
|
||||
// "Should find exactly 3 go-table-test-case, found: {}",
|
||||
// go_table_test_count
|
||||
// );
|
||||
assert!(
|
||||
go_table_test_count == 3,
|
||||
"Should find exactly 3 go-table-test-case, found: {}",
|
||||
go_table_test_count
|
||||
);
|
||||
|
||||
let Some(first_case_offset) = table_test.find("anotherStr: \"foo\"") else {
|
||||
panic!("missing first table test case");
|
||||
};
|
||||
let first_case_offset = first_case_offset + "anotherStr".len();
|
||||
let first_case_runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot
|
||||
.runnable_ranges(first_case_offset..first_case_offset)
|
||||
.collect()
|
||||
});
|
||||
let table_test_case_names: Vec<_> = first_case_runnables
|
||||
.iter()
|
||||
.filter(|runnable| {
|
||||
runnable
|
||||
.runnable
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| tag.0 == "go-table-test-case")
|
||||
})
|
||||
.filter_map(|runnable| runnable.extra_captures.get("_table_test_case_name"))
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
table_test_case_names,
|
||||
vec!["\"test case 1\""],
|
||||
"Should only return the table test case containing the requested range"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_slice_without_explicit_variable_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
|
@ -1351,17 +1458,26 @@ mod tests {
|
|||
);
|
||||
|
||||
let go_test_count = tag_strings.iter().filter(|&tag| tag == "go-test").count();
|
||||
let go_table_test_count = tag_strings
|
||||
.iter()
|
||||
.filter(|&tag| tag == "go-table-test-case-without-explicit-variable")
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
go_test_count == 1,
|
||||
"Should find exactly 1 go-test, found: {}",
|
||||
go_test_count
|
||||
);
|
||||
assert!(
|
||||
go_table_test_count == 3,
|
||||
"Should find exactly 3 go-table-test-case-without-explicit-variable, found: {}",
|
||||
go_table_test_count
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_map_without_explicit_variable_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
|
@ -1435,7 +1551,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_slice_ignored(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
|
@ -1485,7 +1601,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_map_detection(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
|
@ -1574,7 +1690,7 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_map_ignored(cx: &mut TestAppContext) {
|
||||
let language = language("go", tree_sitter_go::LANGUAGE.into());
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
|
@ -1622,6 +1738,241 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_stress(cx: &mut TestAppContext) {
|
||||
let language = go_language();
|
||||
|
||||
let mut entries = String::new();
|
||||
for i in 0..100 {
|
||||
entries.push_str(&format!(
|
||||
" {{ name: \"case {}\", value: {} }},\n",
|
||||
i, i
|
||||
));
|
||||
}
|
||||
let table_test = format!(
|
||||
r#"
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStress(t *testing.T) {{
|
||||
testCases := []struct{{
|
||||
name string
|
||||
value int
|
||||
}}{{
|
||||
{entries} }}
|
||||
|
||||
for _, tc := range testCases {{
|
||||
t.Run(tc.name, func(t *testing.T) {{
|
||||
_ = tc.value
|
||||
}})
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
entries = entries
|
||||
);
|
||||
|
||||
let buffer = cx.new(|cx| {
|
||||
crate::Buffer::local(table_test.clone(), cx).with_language(language.clone(), cx)
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
let go_table_test_count = tag_strings
|
||||
.iter()
|
||||
.filter(|&tag| tag == "go-table-test-case")
|
||||
.count();
|
||||
|
||||
assert_eq!(
|
||||
go_table_test_count, 100,
|
||||
"Should emit one go-table-test-case per row (got {}); tree-sitter match_limit overflow has regressed",
|
||||
go_table_test_count
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_mismatched_field(cx: &mut TestAppContext) {
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMismatchedField(t *testing.T) {
|
||||
testCases := []struct{
|
||||
name string
|
||||
}{
|
||||
{ name: "test case 1" },
|
||||
{ name: "test case 2" },
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
// test code here
|
||||
})
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
|
||||
let tag_strings: Vec<String> = runnables
|
||||
.iter()
|
||||
.flat_map(|r| &r.runnable.tags)
|
||||
.map(|tag| tag.0.to_string())
|
||||
.collect();
|
||||
|
||||
let go_table_test_count = tag_strings
|
||||
.iter()
|
||||
.filter(|&tag| tag == "go-table-test-case")
|
||||
.count();
|
||||
|
||||
assert_eq!(
|
||||
go_table_test_count, 0,
|
||||
"Should not emit table-test runnables when t.Run uses a missing row field"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_slice_picks_correct_field_when_not_first(cx: &mut TestAppContext) {
|
||||
// The subtest-name field `name` is declared AFTER `anotherStr`, but `t.Run(tc.name, ...)`
|
||||
// still selects on `name`. The resolver must match `@_field_check` text to the right
|
||||
// `@_field_name` regardless of source order; "first string field wins" would be a bug.
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExample(t *testing.T) {
|
||||
testCases := []struct{
|
||||
anotherStr string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
anotherStr: "alpha",
|
||||
name: "case alpha",
|
||||
},
|
||||
{
|
||||
anotherStr: "beta",
|
||||
name: "case beta",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_ = tc.anotherStr
|
||||
})
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let case_offset = table_test
|
||||
.find("anotherStr: \"alpha\"")
|
||||
.expect("source should contain the first case body");
|
||||
let first_case_runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(case_offset..case_offset).collect()
|
||||
});
|
||||
|
||||
let case_names: Vec<_> = first_case_runnables
|
||||
.iter()
|
||||
.filter(|runnable| {
|
||||
runnable
|
||||
.runnable
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| tag.0 == "go-table-test-case")
|
||||
})
|
||||
.filter_map(|runnable| runnable.extra_captures.get("_table_test_case_name"))
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
case_names,
|
||||
vec!["\"case alpha\""],
|
||||
"Resolver should pick the field matching `tc.name`, not the first string field"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_go_table_test_map_extras_include_case_name(cx: &mut TestAppContext) {
|
||||
// For map-based table tests, `t.Run(name, ...)` uses the loop variable (not a
|
||||
// selector_expression), so the query never captures `@_field_check`. The resolver's
|
||||
// `reference_text` is None and it falls back to the first `@run` in the group.
|
||||
// Verify each row's `_table_test_case_name` extra is the map key for that row.
|
||||
let language = go_language();
|
||||
|
||||
let table_test = r#"
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExample(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
fail bool
|
||||
}{
|
||||
"test failure": {fail: true},
|
||||
"test success": {fail: false},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_ = tc.fail
|
||||
})
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let buffer =
|
||||
cx.new(|cx| crate::Buffer::local(table_test, cx).with_language(language.clone(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let all_runnables: Vec<_> = buffer.update(cx, |buffer, _| {
|
||||
let snapshot = buffer.snapshot();
|
||||
snapshot.runnable_ranges(0..table_test.len()).collect()
|
||||
});
|
||||
let all_case_names: Vec<_> = all_runnables
|
||||
.iter()
|
||||
.filter(|runnable| {
|
||||
runnable
|
||||
.runnable
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| tag.0 == "go-table-test-case")
|
||||
})
|
||||
.filter_map(|runnable| runnable.extra_captures.get("_table_test_case_name"))
|
||||
.cloned()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
all_case_names,
|
||||
vec!["\"test failure\"", "\"test success\""],
|
||||
"Map-based table tests should surface each row's key as `_table_test_case_name`"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_subtest_name() {
|
||||
// Interpreted string literal
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
|
|||
let css_lsp_adapter = Arc::new(css::CssLspAdapter::new(node.clone()));
|
||||
let eslint_adapter = Arc::new(eslint::EsLintLspAdapter::new(node.clone(), fs.clone()));
|
||||
let go_context_provider = Arc::new(go::GoContextProvider);
|
||||
let go_runnable_resolver: Arc<dyn RunnableResolver> = Arc::new(go::GoRunnableResolver);
|
||||
let go_lsp_adapter = Arc::new(go::GoLspAdapter);
|
||||
let json_context_provider = Arc::new(JsonTaskProvider);
|
||||
let json_lsp_adapter = Arc::new(json::JsonLspAdapter::new(languages.clone(), node.clone()));
|
||||
|
|
@ -117,6 +118,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
|
|||
name: "go",
|
||||
adapters: vec![go_lsp_adapter.clone()],
|
||||
context: Some(go_context_provider.clone()),
|
||||
runnable_resolver: Some(go_runnable_resolver.clone()),
|
||||
semantic_token_rules: Some(go::semantic_token_rules()),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue