Fix code actions migration (#40303)
Closes #40270 Release Notes: - Fixed an issue with the settings migration to flatten `code_actions` format steps where comments would cause enabled code actions to be omitted from the migrated settings. If you were effected, restoring the settings file backup and allowing the migration to re-run will result in a valid settings file - Fixed an issue where automated settings and keymap file updates would occasionally assume 4-space indentation
This commit is contained in:
@@ -103,7 +103,7 @@ pub(crate) mod m_2025_07_08 {
|
||||
pub(crate) mod m_2025_10_01 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
pub(crate) use settings::flatten_code_actions_formatters;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_10_02 {
|
||||
|
||||
@@ -1,109 +1,74 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
use crate::patterns::migrate_language_setting;
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
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;
|
||||
pub fn flatten_code_actions_formatters(value: &mut Value) -> Result<()> {
|
||||
migrate_language_setting(value, |value, _path| {
|
||||
let Some(obj) = value.as_object_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
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);
|
||||
}
|
||||
for key in ["formatter", "format_on_save"] {
|
||||
let Some(formatter) = obj.get_mut(key) else {
|
||||
continue;
|
||||
};
|
||||
let new_formatter = match formatter {
|
||||
Value::Array(arr) => {
|
||||
let mut new_arr = Vec::new();
|
||||
let mut found_code_actions = false;
|
||||
for item in arr {
|
||||
let Some(obj) = item.as_object() else {
|
||||
new_arr.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
let code_actions_obj = obj
|
||||
.get("code_actions")
|
||||
.and_then(|code_actions| code_actions.as_object());
|
||||
let Some(code_actions) = code_actions_obj else {
|
||||
new_arr.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
found_code_actions = true;
|
||||
for (name, enabled) in code_actions {
|
||||
if !enabled.as_bool().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
new_arr.push(serde_json::json!({
|
||||
"code_action": name
|
||||
}));
|
||||
}
|
||||
}
|
||||
if !found_code_actions {
|
||||
continue;
|
||||
}
|
||||
Value::Array(new_arr)
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
let mut new_arr = Vec::new();
|
||||
let code_actions_obj = obj
|
||||
.get("code_actions")
|
||||
.and_then(|code_actions| code_actions.as_object());
|
||||
let Some(code_actions) = code_actions_obj else {
|
||||
continue;
|
||||
};
|
||||
for (name, enabled) in code_actions {
|
||||
if !enabled.as_bool().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
new_arr.push(serde_json::json!({
|
||||
"code_action": name
|
||||
}));
|
||||
}
|
||||
if new_arr.len() == 1 {
|
||||
new_arr.pop().unwrap()
|
||||
} else {
|
||||
Value::Array(new_arr)
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
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;
|
||||
obj.insert(key.to_string(), new_formatter);
|
||||
}
|
||||
}
|
||||
Some((replace_range, code_actions_str))
|
||||
return Ok(());
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::patterns::migrate_language_setting;
|
||||
|
||||
pub fn remove_formatters_on_save(value: &mut Value) -> Result<()> {
|
||||
remove_formatters_on_save_inner(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
remove_formatters_on_save_inner(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
migrate_language_setting(value, remove_formatters_on_save_inner)
|
||||
}
|
||||
|
||||
fn remove_formatters_on_save_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::patterns::migrate_language_setting;
|
||||
|
||||
pub fn remove_code_actions_on_format(value: &mut Value) -> Result<()> {
|
||||
remove_code_actions_on_format_inner(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
remove_code_actions_on_format_inner(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
migrate_language_setting(value, remove_code_actions_on_format_inner)
|
||||
}
|
||||
|
||||
fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
|
||||
|
||||
+498
-375
@@ -74,6 +74,7 @@ fn run_migrations(text: &str, migrations: &[MigrationType]) -> Result<Option<Str
|
||||
|
||||
let mut current_text = text.to_string();
|
||||
let mut result: Option<String> = None;
|
||||
let json_indent_size = settings::infer_json_indent_size(¤t_text);
|
||||
for migration in migrations.iter() {
|
||||
let migrated_text = match migration {
|
||||
MigrationType::TreeSitter(patterns, query) => migrate(¤t_text, patterns, query)?,
|
||||
@@ -92,7 +93,7 @@ fn run_migrations(text: &str, migrations: &[MigrationType]) -> Result<Option<Str
|
||||
settings::update_value_in_json_text(
|
||||
&mut current,
|
||||
&mut vec![],
|
||||
2,
|
||||
json_indent_size,
|
||||
&old_value,
|
||||
&new_value,
|
||||
&mut edits,
|
||||
@@ -204,10 +205,7 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
migrations::m_2025_07_08::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_07_08,
|
||||
),
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
),
|
||||
MigrationType::Json(migrations::m_2025_10_01::flatten_code_actions_formatters),
|
||||
MigrationType::Json(migrations::m_2025_10_02::remove_formatters_on_save),
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_03::SETTINGS_PATTERNS,
|
||||
@@ -328,10 +326,6 @@ 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
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2025_10_03,
|
||||
migrations::m_2025_10_03::SETTINGS_PATTERNS
|
||||
@@ -351,10 +345,11 @@ mod tests {
|
||||
use super::*;
|
||||
use unindent::Unindent as _;
|
||||
|
||||
#[track_caller]
|
||||
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_str_eq!(expected, migrated);
|
||||
}
|
||||
_ => {
|
||||
pretty_assertions::assert_eq!(migrated.as_deref(), expected);
|
||||
@@ -372,6 +367,7 @@ mod tests {
|
||||
assert_migrated_correctly(migrated, output);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_migrate_settings_with_migrations(
|
||||
migrations: &[MigrationType],
|
||||
input: &str,
|
||||
@@ -1343,24 +1339,28 @@ mod tests {
|
||||
fn test_flatten_code_action_formatters_basic_array() {
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"included-2": true,
|
||||
"excluded": false,
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" }
|
||||
]
|
||||
}"#
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -1370,21 +1370,25 @@ mod tests {
|
||||
fn test_flatten_code_action_formatters_basic_object() {
|
||||
assert_migrate_settings(
|
||||
&r#"{
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"excluded": false,
|
||||
"included-2": true
|
||||
}
|
||||
}
|
||||
}"#
|
||||
"formatter": {
|
||||
"code_actions": {
|
||||
"included-1": true,
|
||||
"excluded": false,
|
||||
"included-2": true
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" }
|
||||
]
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
@@ -1394,47 +1398,57 @@ mod tests {
|
||||
#[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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}"#,
|
||||
&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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
r#"{
|
||||
"formatter": [
|
||||
{ "code_action": "included-1" },
|
||||
{ "code_action": "included-2" },
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{ "code_action": "included-3" },
|
||||
{ "code_action": "included-4" },
|
||||
]
|
||||
}"#,
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1443,55 +1457,63 @@ mod tests {
|
||||
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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
"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" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "included-1"
|
||||
},
|
||||
{
|
||||
"code_action": "included-2"
|
||||
},
|
||||
{
|
||||
"language_server": "ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "included-3"
|
||||
},
|
||||
{
|
||||
"code_action": "included-4"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -1502,100 +1524,120 @@ mod tests {
|
||||
{
|
||||
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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
"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" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
"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(),
|
||||
),
|
||||
);
|
||||
@@ -1604,153 +1646,185 @@ mod tests {
|
||||
#[test]
|
||||
fn test_flatten_code_action_formatters_array_with_format_on_save_and_multiple_languages() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_01::flatten_code_actions_formatters,
|
||||
)],
|
||||
&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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
"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" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
&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(),
|
||||
),
|
||||
);
|
||||
@@ -1952,25 +2026,25 @@ mod tests {
|
||||
migrations::m_2025_10_10::remove_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"code_actions_on_format": {
|
||||
"a": true,
|
||||
"b": false,
|
||||
"c": true
|
||||
}
|
||||
}"#
|
||||
"code_actions_on_format": {
|
||||
"a": true,
|
||||
"b": false,
|
||||
"c": true
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"{
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "a"
|
||||
},
|
||||
{
|
||||
"code_action": "c"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "a"
|
||||
},
|
||||
{
|
||||
"code_action": "c"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
@@ -2163,12 +2237,12 @@ mod tests {
|
||||
]
|
||||
},
|
||||
"Python": {
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
}
|
||||
]
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.organizeImports"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
@@ -2212,4 +2286,53 @@ mod tests {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_code_action_formatters_issue() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_01::flatten_code_actions_formatters,
|
||||
)],
|
||||
&r#"
|
||||
{
|
||||
"languages": {
|
||||
"Python": {
|
||||
"language_servers": ["ruff"],
|
||||
"format_on_save": "on",
|
||||
"formatter": [
|
||||
{
|
||||
"code_actions": {
|
||||
// Fix all auto-fixable lint violations
|
||||
"source.fixAll.ruff": true,
|
||||
// Organize imports
|
||||
"source.organizeImports.ruff": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
Some(
|
||||
&r#"
|
||||
{
|
||||
"languages": {
|
||||
"Python": {
|
||||
"language_servers": ["ruff"],
|
||||
"format_on_save": "on",
|
||||
"formatter": [
|
||||
{
|
||||
"code_action": "source.fixAll.ruff"
|
||||
},
|
||||
{
|
||||
"code_action": "source.organizeImports.ruff"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ pub(crate) use settings::{
|
||||
SETTINGS_ASSISTANT_PATTERN, SETTINGS_ASSISTANT_TOOLS_PATTERN,
|
||||
SETTINGS_DUPLICATED_AGENT_PATTERN, SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN,
|
||||
SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN,
|
||||
migrate_language_setting,
|
||||
};
|
||||
|
||||
@@ -108,3 +108,24 @@ pub const SETTINGS_DUPLICATED_AGENT_PATTERN: &str = r#"(document
|
||||
(#eq? @agent1 "agent")
|
||||
(#eq? @agent2 "agent")
|
||||
)"#;
|
||||
|
||||
/// Migrate language settings,
|
||||
/// calls `migrate_fn` with the top level object as well as all language settings under the "languages" key
|
||||
/// Fails early if `migrate_fn` returns an error at any point
|
||||
pub fn migrate_language_setting(
|
||||
value: &mut serde_json::Value,
|
||||
migrate_fn: fn(&mut serde_json::Value, path: &[&str]) -> anyhow::Result<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
migrate_fn(value, &[])?;
|
||||
let languages = value
|
||||
.as_object_mut()
|
||||
.and_then(|obj| obj.get_mut("languages"))
|
||||
.and_then(|languages| languages.as_object_mut());
|
||||
if let Some(languages) = languages {
|
||||
for (language_name, language) in languages.iter_mut() {
|
||||
let path = vec!["languages", language_name];
|
||||
migrate_fn(language, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user