settings: Flatten code actions formatters object (#39375)

Closes #ISSUE

Release Notes:

- settings: Changed code action format in `formatter` and
`format_on_save` settings.

**Previous format:**
```
{
  "code_actions": {
    "source.organizeImports": true,
    "source.fixAll": true
  }
}
```

**New format:**
```
[
  {"code_action": "source.organizeImports"},
  {"code_action": "source.fixAll"}
]
```

After #39246, code actions run sequentially in order. The structure now
reflects this and aligns with other formatter options (e.g., language
servers).

Both the `formatter` and `format_on_save` settings will be
auto-migrated.
This commit is contained in:
Ben Kunkle
2025-10-02 14:48:15 +00:00
committed by GitHub
parent 179fb21778
commit 6fbbdb3512
8 changed files with 878 additions and 348 deletions
+6
View File
@@ -99,3 +99,9 @@ pub(crate) mod m_2025_07_08 {
pub(crate) use settings::SETTINGS_PATTERNS;
}
pub(crate) mod m_2025_10_01 {
mod settings;
pub(crate) use settings::SETTINGS_PATTERNS;
}
@@ -0,0 +1,109 @@
use std::ops::Range;
use tree_sitter::{Query, QueryMatch};
use crate::MigrationPatterns;
pub const SETTINGS_PATTERNS: MigrationPatterns =
&[(FORMATTER_PATTERN, migrate_code_action_formatters)];
const FORMATTER_PATTERN: &str = r#"
(object
(pair
key: (string (string_content) @formatter) (#any-of? @formatter "formatter" "format_on_save")
value: [
(array
(object
(pair
key: (string (string_content) @code-actions-key) (#eq? @code-actions-key "code_actions")
value: (object
((pair) @code-action ","?)*
)
)
) @code-actions-obj
) @formatter-array
(object
(pair
key: (string (string_content) @code-actions-key) (#eq? @code-actions-key "code_actions")
value: (object
((pair) @code-action ","?)*
)
)
) @code-actions-obj
]
)
)
"#;
pub fn migrate_code_action_formatters(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let code_actions_obj_ix = query.capture_index_for_name("code-actions-obj")?;
let code_actions_obj_node = mat.nodes_for_capture_index(code_actions_obj_ix).next()?;
let mut code_actions = vec![];
let code_actions_ix = query.capture_index_for_name("code-action")?;
for code_action_node in mat.nodes_for_capture_index(code_actions_ix) {
let Some(enabled) = code_action_node
.child_by_field_name("value")
.map(|n| n.kind() != "false")
else {
continue;
};
if !enabled {
continue;
}
let Some(name) = code_action_node
.child_by_field_name("key")
.and_then(|n| n.child(1))
.map(|n| &contents[n.byte_range()])
else {
continue;
};
code_actions.push(name);
}
let indent = query
.capture_index_for_name("formatter")
.and_then(|ix| mat.nodes_for_capture_index(ix).next())
.map(|node| node.start_position().column + 1)
.unwrap_or(2);
let mut code_actions_str = code_actions
.into_iter()
.map(|code_action| format!(r#"{{ "code_action": "{}" }}"#, code_action))
.collect::<Vec<_>>()
.join(&format!(",\n{}", " ".repeat(indent)));
let is_array = query
.capture_index_for_name("formatter-array")
.map(|ix| mat.nodes_for_capture_index(ix).count() > 0)
.unwrap_or(false);
if !is_array {
code_actions_str.insert_str(0, &" ".repeat(indent));
code_actions_str.insert_str(0, "[\n");
code_actions_str.push('\n');
code_actions_str.push_str(&" ".repeat(indent.saturating_sub(2)));
code_actions_str.push_str("]");
}
let mut replace_range = code_actions_obj_node.byte_range();
if is_array && code_actions_str.is_empty() {
let mut cursor = code_actions_obj_node.parent().unwrap().walk();
cursor.goto_first_child();
while cursor.node().id() != code_actions_obj_node.id() && cursor.goto_next_sibling() {}
while cursor.goto_next_sibling()
&& (cursor.node().is_extra()
|| cursor.node().is_missing()
|| cursor.node().kind() == "comment")
{}
if cursor.node().kind() == "," {
// found comma, delete up to next node
while cursor.goto_next_sibling()
&& (cursor.node().is_extra() || cursor.node().is_missing())
{}
replace_range.end = cursor.node().range().start_byte;
}
}
Some((replace_range, code_actions_str))
}
+434 -1
View File
@@ -164,6 +164,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
migrations::m_2025_07_08::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_07_08,
),
(
migrations::m_2025_10_01::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_10_01,
),
];
run_migrations(text, migrations)
}
@@ -278,6 +282,10 @@ define_query!(
SETTINGS_QUERY_2025_07_08,
migrations::m_2025_07_08::SETTINGS_PATTERNS
);
define_query!(
SETTINGS_QUERY_2025_10_01,
migrations::m_2025_10_01::SETTINGS_PATTERNS
);
// custom query
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
@@ -291,6 +299,18 @@ static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new
#[cfg(test)]
mod tests {
use super::*;
use unindent::Unindent as _;
fn assert_migrated_correctly(migrated: Option<String>, expected: Option<&str>) {
match (&migrated, &expected) {
(Some(migrated), Some(expected)) => {
pretty_assertions::assert_str_eq!(migrated, expected);
}
_ => {
pretty_assertions::assert_eq!(migrated.as_deref(), expected);
}
}
}
fn assert_migrate_keymap(input: &str, output: Option<&str>) {
let migrated = migrate_keymap(input).unwrap();
@@ -299,7 +319,7 @@ mod tests {
fn assert_migrate_settings(input: &str, output: Option<&str>) {
let migrated = migrate_settings(input).unwrap();
pretty_assertions::assert_eq!(migrated.as_deref(), output);
assert_migrated_correctly(migrated, output);
}
fn assert_migrate_settings_with_migrations(
@@ -1263,4 +1283,417 @@ mod tests {
),
);
}
#[test]
fn test_flatten_code_action_formatters_basic_array() {
assert_migrate_settings(
&r#"{
"formatter": [
{
"code_actions": {
"included-1": true,
"included-2": true,
"excluded": false,
}
}
]
}"#
.unindent(),
Some(
&r#"{
"formatter": [
{ "code_action": "included-1" },
{ "code_action": "included-2" }
]
}"#
.unindent(),
),
);
}
#[test]
fn test_flatten_code_action_formatters_basic_object() {
assert_migrate_settings(
&r#"{
"formatter": {
"code_actions": {
"included-1": true,
"excluded": false,
"included-2": true
}
}
}"#
.unindent(),
Some(
&r#"{
"formatter": [
{ "code_action": "included-1" },
{ "code_action": "included-2" }
]
}"#
.unindent(),
),
);
}
#[test]
fn test_flatten_code_action_formatters_array_with_multiple_action_blocks() {
assert_migrate_settings(
r#"{
"formatter": [
{
"code_actions": {
"included-1": true,
"included-2": true,
"excluded": false,
}
},
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
]
}"#,
Some(
r#"{
"formatter": [
{ "code_action": "included-1" },
{ "code_action": "included-2" },
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
]
}"#,
),
);
}
#[test]
fn test_flatten_code_action_formatters_array_with_multiple_action_blocks_in_languages() {
assert_migrate_settings(
&r#"{
"languages": {
"Rust": {
"formatter": [
{
"code_actions": {
"included-1": true,
"included-2": true,
"excluded": false,
}
},
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
]
}
}
}"#
.unindent(),
Some(
&r#"{
"languages": {
"Rust": {
"formatter": [
{ "code_action": "included-1" },
{ "code_action": "included-2" },
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
]
}
}
}"#
.unindent(),
),
);
}
#[test]
fn test_flatten_code_action_formatters_array_with_multiple_action_blocks_in_defaults_and_multiple_languages()
{
assert_migrate_settings(
&r#"{
"formatter": {
"code_actions": {
"default-1": true,
"default-2": true,
"default-3": true,
"default-4": true,
}
}
"languages": {
"Rust": {
"formatter": [
{
"code_actions": {
"included-1": true,
"included-2": true,
"excluded": false,
}
},
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
]
},
"Python": {
"formatter": [
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
]
}
}
}"#
.unindent(),
Some(
&r#"{
"formatter": [
{ "code_action": "default-1" },
{ "code_action": "default-2" },
{ "code_action": "default-3" },
{ "code_action": "default-4" }
]
"languages": {
"Rust": {
"formatter": [
{ "code_action": "included-1" },
{ "code_action": "included-2" },
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
]
},
"Python": {
"formatter": [
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
]
}
}
}"#
.unindent(),
),
);
}
#[test]
fn test_flatten_code_action_formatters_array_with_format_on_save_and_multiple_languages() {
assert_migrate_settings(
&r#"{
"formatter": {
"code_actions": {
"default-1": true,
"default-2": true,
"default-3": true,
"default-4": true,
}
},
"format_on_save": [
{
"code_actions": {
"included-1": true,
"included-2": true,
"excluded": false,
}
},
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
],
"languages": {
"Rust": {
"format_on_save": "prettier",
"formatter": [
{
"code_actions": {
"included-1": true,
"included-2": true,
"excluded": false,
}
},
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
]
},
"Python": {
"format_on_save": {
"code_actions": {
"on-save-1": true,
"on-save-2": true,
}
},
"formatter": [
{
"language_server": "ruff"
},
{
"code_actions": {
"excluded": false,
"excluded-2": false,
}
}
// some comment
,
{
"code_actions": {
"excluded": false,
"included-3": true,
"included-4": true,
}
},
]
}
}
}"#
.unindent(),
Some(
&r#"{
"formatter": [
{ "code_action": "default-1" },
{ "code_action": "default-2" },
{ "code_action": "default-3" },
{ "code_action": "default-4" }
],
"format_on_save": [
{ "code_action": "included-1" },
{ "code_action": "included-2" },
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
],
"languages": {
"Rust": {
"format_on_save": "prettier",
"formatter": [
{ "code_action": "included-1" },
{ "code_action": "included-2" },
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
]
},
"Python": {
"format_on_save": [
{ "code_action": "on-save-1" },
{ "code_action": "on-save-2" }
],
"formatter": [
{
"language_server": "ruff"
},
{ "code_action": "included-3" },
{ "code_action": "included-4" },
]
}
}
}"#
.unindent(),
),
);
}
}