settings.rs

  1use std::ops::Range;
  2use tree_sitter::{Query, QueryMatch};
  3
  4use crate::MigrationPatterns;
  5
  6pub const SETTINGS_PATTERNS: MigrationPatterns =
  7    &[(FORMATTER_PATTERN, migrate_code_action_formatters)];
  8
  9const FORMATTER_PATTERN: &str = r#"
 10        (object
 11            (pair
 12                key: (string (string_content) @formatter) (#any-of? @formatter "formatter" "format_on_save")
 13                value: [
 14                    (array
 15                        (object
 16                            (pair
 17                                key: (string (string_content) @code-actions-key) (#eq? @code-actions-key "code_actions")
 18                                value: (object
 19                                    ((pair) @code-action ","?)*
 20                                )
 21                            )
 22                        ) @code-actions-obj
 23                    ) @formatter-array
 24                    (object
 25                        (pair
 26                            key: (string (string_content) @code-actions-key) (#eq? @code-actions-key "code_actions")
 27                            value: (object
 28                                ((pair) @code-action ","?)*
 29                            )
 30                        )
 31                    ) @code-actions-obj
 32                ]
 33            )
 34        )
 35"#;
 36
 37pub fn migrate_code_action_formatters(
 38    contents: &str,
 39    mat: &QueryMatch,
 40    query: &Query,
 41) -> Option<(Range<usize>, String)> {
 42    let code_actions_obj_ix = query.capture_index_for_name("code-actions-obj")?;
 43    let code_actions_obj_node = mat.nodes_for_capture_index(code_actions_obj_ix).next()?;
 44
 45    let mut code_actions = vec![];
 46
 47    let code_actions_ix = query.capture_index_for_name("code-action")?;
 48    for code_action_node in mat.nodes_for_capture_index(code_actions_ix) {
 49        let Some(enabled) = code_action_node
 50            .child_by_field_name("value")
 51            .map(|n| n.kind() != "false")
 52        else {
 53            continue;
 54        };
 55        if !enabled {
 56            continue;
 57        }
 58        let Some(name) = code_action_node
 59            .child_by_field_name("key")
 60            .and_then(|n| n.child(1))
 61            .map(|n| &contents[n.byte_range()])
 62        else {
 63            continue;
 64        };
 65        code_actions.push(name);
 66    }
 67
 68    let indent = query
 69        .capture_index_for_name("formatter")
 70        .and_then(|ix| mat.nodes_for_capture_index(ix).next())
 71        .map(|node| node.start_position().column + 1)
 72        .unwrap_or(2);
 73
 74    let mut code_actions_str = code_actions
 75        .into_iter()
 76        .map(|code_action| format!(r#"{{ "code_action": "{}" }}"#, code_action))
 77        .collect::<Vec<_>>()
 78        .join(&format!(",\n{}", " ".repeat(indent)));
 79    let is_array = query
 80        .capture_index_for_name("formatter-array")
 81        .map(|ix| mat.nodes_for_capture_index(ix).count() > 0)
 82        .unwrap_or(false);
 83    if !is_array {
 84        code_actions_str.insert_str(0, &" ".repeat(indent));
 85        code_actions_str.insert_str(0, "[\n");
 86        code_actions_str.push('\n');
 87        code_actions_str.push_str(&" ".repeat(indent.saturating_sub(2)));
 88        code_actions_str.push_str("]");
 89    }
 90    let mut replace_range = code_actions_obj_node.byte_range();
 91    if is_array && code_actions_str.is_empty() {
 92        let mut cursor = code_actions_obj_node.parent().unwrap().walk();
 93        cursor.goto_first_child();
 94        while cursor.node().id() != code_actions_obj_node.id() && cursor.goto_next_sibling() {}
 95        while cursor.goto_next_sibling()
 96            && (cursor.node().is_extra()
 97                || cursor.node().is_missing()
 98                || cursor.node().kind() == "comment")
 99        {}
100        if cursor.node().kind() == "," {
101            // found comma, delete up to next node
102            while cursor.goto_next_sibling()
103                && (cursor.node().is_extra() || cursor.node().is_missing())
104            {}
105            replace_range.end = cursor.node().range().start_byte;
106        }
107    }
108    Some((replace_range, code_actions_str))
109}