6313: Latest proposed LSP 3.16.0 and refresh semantic tokens r=matklad a=kjeremy
Needs: https://github.com/gluon-lang/lsp-types/pull/183
Co-authored-by: kjeremy <kjeremy@gmail.com>
Co-authored-by: Jeremy A. Kolb <jkolb@ara.com>
use syntax::{
- ast::{edit::AstNodeEdit, make, AstNode, IfExpr, MatchArm},
+ ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, Expr, IfExpr, MatchArm},
SyntaxKind::WHITESPACE,
};
pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
let match_pat = match_arm.pat()?;
-
let arm_body = match_arm.expr()?;
- let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?;
+
+ let mut replace_node = None;
+ let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
+ let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
+ if let Expr::IfExpr(e) = block_expr.expr()? {
+ replace_node = Some(block_expr.syntax().clone());
+ Some(e)
+ } else {
+ None
+ }
+ })?;
+ let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
+
let cond = if_expr.condition()?;
let then_block = if_expr.then_branch()?;
let buf = format!(" if {}", cond.syntax().text());
- let target = if_expr.syntax().text_range();
acc.add(
AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
"Move condition to match guard",
- target,
+ replace_node.text_range(),
|edit| {
let then_only_expr = then_block.statements().next().is_none();
match &then_block.expr() {
Some(then_expr) if then_only_expr => {
- edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
+ edit.replace(replace_node.text_range(), then_expr.syntax().text())
}
- _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()),
+ _ if replace_node != *if_expr.syntax() => {
+ // Dedent because if_expr is in a BlockExpr
+ let replace_with = then_block.dedent(1.into()).syntax().text();
+ edit.replace(replace_node.text_range(), replace_with)
+ }
+ _ => edit.replace(replace_node.text_range(), then_block.syntax().text()),
}
edit.insert(match_pat.syntax().text_range().end(), buf);
);
}
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ <|>if x > 10 {
+ false
+ }
+ },
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
+
#[test]
fn move_arm_cond_to_match_guard_if_let_not_works() {
check_assist_not_applicable(
"#,
);
}
+
+ #[test]
+ fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
+ check_assist(
+ move_arm_cond_to_match_guard,
+ r#"
+fn main() {
+ match 92 {
+ x => {
+ if x > 10 {
+ 92;<|>
+ false
+ }
+ }
+ _ => true
+ }
+}
+"#,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => {
+ 92;
+ false
+ }
+ _ => true
+ }
+}
+"#,
+ )
+ }
}
//! Functions for string case manipulation, such as detecting the identifier case,
//! and converting it into appropriate form.
-#[derive(Debug)]
-enum DetectedCase {
- LowerCamelCase,
- UpperCamelCase,
- LowerSnakeCase,
- UpperSnakeCase,
- Unknown,
-}
-
-fn detect_case(ident: &str) -> DetectedCase {
- let trimmed_ident = ident.trim_matches('_');
- let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase());
- let mut has_lowercase = first_lowercase;
- let mut has_uppercase = false;
- let mut has_underscore = false;
-
- for chr in trimmed_ident.chars() {
- if chr == '_' {
- has_underscore = true;
- } else if chr.is_ascii_uppercase() {
- has_uppercase = true;
- } else if chr.is_ascii_lowercase() {
- has_lowercase = true;
- }
- }
-
- if has_uppercase {
- if !has_lowercase {
- if has_underscore {
- DetectedCase::UpperSnakeCase
- } else {
- // It has uppercase only and no underscores. Ex: "AABB"
- // This is a camel cased acronym.
- DetectedCase::UpperCamelCase
- }
- } else if !has_underscore {
- if first_lowercase {
- DetectedCase::LowerCamelCase
- } else {
- DetectedCase::UpperCamelCase
- }
- } else {
- // It has uppercase, it has lowercase, it has underscore.
- // No assumptions here
- DetectedCase::Unknown
- }
- } else {
- DetectedCase::LowerSnakeCase
- }
-}
+// Code that was taken from rustc was taken at commit 89fdb30,
+// from file /compiler/rustc_lint/src/nonstandard_style.rs
/// Converts an identifier to an UpperCamelCase form.
/// Returns `None` if the string is already is UpperCamelCase.
pub fn to_camel_case(ident: &str) -> Option<String> {
- let detected_case = detect_case(ident);
-
- match detected_case {
- DetectedCase::UpperCamelCase => return None,
- DetectedCase::LowerCamelCase => {
- let mut first_capitalized = false;
- let output = ident
- .chars()
- .map(|chr| {
- if !first_capitalized && chr.is_ascii_lowercase() {
- first_capitalized = true;
- chr.to_ascii_uppercase()
- } else {
- chr
- }
- })
- .collect();
- return Some(output);
- }
- _ => {}
+ if is_camel_case(ident) {
+ return None;
}
- let mut output = String::with_capacity(ident.len());
-
- let mut capital_added = false;
- for chr in ident.chars() {
- if chr.is_alphabetic() {
- if !capital_added {
- output.push(chr.to_ascii_uppercase());
- capital_added = true;
- } else {
- output.push(chr.to_ascii_lowercase());
+ // Taken from rustc.
+ let ret = ident
+ .trim_matches('_')
+ .split('_')
+ .filter(|component| !component.is_empty())
+ .map(|component| {
+ let mut camel_cased_component = String::new();
+
+ let mut new_word = true;
+ let mut prev_is_lower_case = true;
+
+ for c in component.chars() {
+ // Preserve the case if an uppercase letter follows a lowercase letter, so that
+ // `camelCase` is converted to `CamelCase`.
+ if prev_is_lower_case && c.is_uppercase() {
+ new_word = true;
+ }
+
+ if new_word {
+ camel_cased_component.push_str(&c.to_uppercase().to_string());
+ } else {
+ camel_cased_component.push_str(&c.to_lowercase().to_string());
+ }
+
+ prev_is_lower_case = c.is_lowercase();
+ new_word = false;
}
- } else if chr == '_' {
- // Skip this character and make the next one capital.
- capital_added = false;
- } else {
- // Put the characted as-is.
- output.push(chr);
- }
- }
- if output == ident {
- // While we didn't detect the correct case at the beginning, there
- // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE.
- None
- } else {
- Some(output)
- }
+ camel_cased_component
+ })
+ .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
+ // separate two components with an underscore if their boundary cannot
+ // be distinguished using a uppercase/lowercase case distinction
+ let join = if let Some(prev) = prev {
+ let l = prev.chars().last().unwrap();
+ let f = next.chars().next().unwrap();
+ !char_has_case(l) && !char_has_case(f)
+ } else {
+ false
+ };
+ (acc + if join { "_" } else { "" } + &next, Some(next))
+ })
+ .0;
+ Some(ret)
}
/// Converts an identifier to a lower_snake_case form.
/// Returns `None` if the string is already in lower_snake_case.
pub fn to_lower_snake_case(ident: &str) -> Option<String> {
- // First, assume that it's UPPER_SNAKE_CASE.
- match detect_case(ident) {
- DetectedCase::LowerSnakeCase => return None,
- DetectedCase::UpperSnakeCase => {
- return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect())
- }
- _ => {}
+ if is_lower_snake_case(ident) {
+ return None;
+ } else if is_upper_snake_case(ident) {
+ return Some(ident.to_lowercase());
}
- // Otherwise, assume that it's CamelCase.
- let lower_snake_case = stdx::to_lower_snake_case(ident);
-
- if lower_snake_case == ident {
- // While we didn't detect the correct case at the beginning, there
- // may be special cases: e.g. `a` is both valid camelCase and snake_case.
- None
- } else {
- Some(lower_snake_case)
- }
+ Some(stdx::to_lower_snake_case(ident))
}
/// Converts an identifier to an UPPER_SNAKE_CASE form.
/// Returns `None` if the string is already is UPPER_SNAKE_CASE.
pub fn to_upper_snake_case(ident: &str) -> Option<String> {
- match detect_case(ident) {
- DetectedCase::UpperSnakeCase => return None,
- DetectedCase::LowerSnakeCase => {
- return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect())
- }
- _ => {}
+ if is_upper_snake_case(ident) {
+ return None;
+ } else if is_lower_snake_case(ident) {
+ return Some(ident.to_uppercase());
+ }
+
+ Some(stdx::to_upper_snake_case(ident))
+}
+
+// Taken from rustc.
+// Modified by replacing the use of unstable feature `array_windows`.
+fn is_camel_case(name: &str) -> bool {
+ let name = name.trim_matches('_');
+ if name.is_empty() {
+ return true;
}
- // Normalize the string from whatever form it's in currently, and then just make it uppercase.
- let upper_snake_case = stdx::to_upper_snake_case(ident);
+ let mut fst = None;
+ // start with a non-lowercase letter rather than non-uppercase
+ // ones (some scripts don't have a concept of upper/lowercase)
+ !name.chars().next().unwrap().is_lowercase()
+ && !name.contains("__")
+ && !name.chars().any(|snd| {
+ let ret = match (fst, snd) {
+ (None, _) => false,
+ (Some(fst), snd) => {
+ char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_'
+ }
+ };
+ fst = Some(snd);
+
+ ret
+ })
+}
+
+fn is_lower_snake_case(ident: &str) -> bool {
+ is_snake_case(ident, char::is_uppercase)
+}
- if upper_snake_case == ident {
- // While we didn't detect the correct case at the beginning, there
- // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE.
- None
- } else {
- Some(upper_snake_case)
+fn is_upper_snake_case(ident: &str) -> bool {
+ is_snake_case(ident, char::is_lowercase)
+}
+
+// Taken from rustc.
+// Modified to allow checking for both upper and lower snake case.
+fn is_snake_case<F: Fn(char) -> bool>(ident: &str, wrong_case: F) -> bool {
+ if ident.is_empty() {
+ return true;
}
+ let ident = ident.trim_matches('_');
+
+ let mut allow_underscore = true;
+ ident.chars().all(|c| {
+ allow_underscore = match c {
+ '_' if !allow_underscore => return false,
+ '_' => false,
+ // It would be more obvious to check for the correct case,
+ // but some characters do not have a case.
+ c if !wrong_case(c) => true,
+ _ => return false,
+ };
+ true
+ })
+}
+
+// Taken from rustc.
+fn char_has_case(c: char) -> bool {
+ c.is_lowercase() || c.is_uppercase()
}
#[cfg(test)]
check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
check(to_lower_snake_case, "a", expect![[""]]);
+ check(to_lower_snake_case, "abc", expect![[""]]);
}
#[test]
check(to_camel_case, "name", expect![["Name"]]);
check(to_camel_case, "A", expect![[""]]);
check(to_camel_case, "AABB", expect![[""]]);
+ // Taken from rustc: /compiler/rustc_lint/src/nonstandard_style/tests.rs
+ check(to_camel_case, "X86_64", expect![[""]]);
+ check(to_camel_case, "x86__64", expect![["X86_64"]]);
+ check(to_camel_case, "Abc_123", expect![["Abc123"]]);
+ check(to_camel_case, "A1_b2_c3", expect![["A1B2C3"]]);
}
#[test]
check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]);
check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
check(to_upper_snake_case, "A", expect![[""]]);
+ check(to_upper_snake_case, "ABC", expect![[""]]);
+ check(to_upper_snake_case, "X86_64", expect![[""]]);
}
}
// pub(self) struct S;
// pub(self) struct S;
// pub(self) struct S;
- T![crate] | T![self] | T![super] => {
+
+ // test pub_parens_typepath
+ // struct B(pub (super::A));
+ // struct B(pub (crate::A,));
+ T![crate] | T![self] | T![super] if p.nth(2) != T![:] => {
p.bump_any();
p.bump_any();
p.expect(T![')']);
with_children(parent, new_children)
}
+#[derive(Debug, PartialEq, Eq, Hash)]
+enum InsertPos {
+ FirstChildOf(SyntaxNode),
+ // Before(SyntaxElement),
+ After(SyntaxElement),
+}
+
#[derive(Default)]
pub struct SyntaxRewriter<'a> {
f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
//FIXME: add debug_assertions that all elements are in fact from the same file.
replacements: FxHashMap<SyntaxElement, Replacement>,
+ insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
}
impl fmt::Debug for SyntaxRewriter<'_> {
impl<'a> SyntaxRewriter<'a> {
pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
- SyntaxRewriter { f: Some(Box::new(f)), replacements: FxHashMap::default() }
+ SyntaxRewriter {
+ f: Some(Box::new(f)),
+ replacements: FxHashMap::default(),
+ insertions: IndexMap::default(),
+ }
}
pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
let what = what.clone().into();
let replacement = Replacement::Delete;
self.replacements.insert(what, replacement);
}
+ pub fn insert_before<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
+ &mut self,
+ before: &T,
+ what: &U,
+ ) {
+ let before = before.clone().into();
+ let pos = match before.prev_sibling_or_token() {
+ Some(sibling) => InsertPos::After(sibling),
+ None => match before.parent() {
+ Some(parent) => InsertPos::FirstChildOf(parent),
+ None => return,
+ },
+ };
+ self.insertions.entry(pos).or_insert_with(Vec::new).push(what.clone().into());
+ }
+ pub fn insert_after<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>(
+ &mut self,
+ after: &T,
+ what: &U,
+ ) {
+ self.insertions
+ .entry(InsertPos::After(after.clone().into()))
+ .or_insert_with(Vec::new)
+ .push(what.clone().into());
+ }
+ pub fn insert_as_first_child<T: Clone + Into<SyntaxNode>, U: Clone + Into<SyntaxElement>>(
+ &mut self,
+ parent: &T,
+ what: &U,
+ ) {
+ self.insertions
+ .entry(InsertPos::FirstChildOf(parent.clone().into()))
+ .or_insert_with(Vec::new)
+ .push(what.clone().into());
+ }
+ pub fn insert_many_before<
+ T: Clone + Into<SyntaxElement>,
+ U: IntoIterator<Item = SyntaxElement>,
+ >(
+ &mut self,
+ before: &T,
+ what: U,
+ ) {
+ let before = before.clone().into();
+ let pos = match before.prev_sibling_or_token() {
+ Some(sibling) => InsertPos::After(sibling),
+ None => match before.parent() {
+ Some(parent) => InsertPos::FirstChildOf(parent),
+ None => return,
+ },
+ };
+ self.insertions.entry(pos).or_insert_with(Vec::new).extend(what);
+ }
+ pub fn insert_many_after<
+ T: Clone + Into<SyntaxElement>,
+ U: IntoIterator<Item = SyntaxElement>,
+ >(
+ &mut self,
+ after: &T,
+ what: U,
+ ) {
+ self.insertions
+ .entry(InsertPos::After(after.clone().into()))
+ .or_insert_with(Vec::new)
+ .extend(what);
+ }
+ pub fn insert_many_as_first_children<
+ T: Clone + Into<SyntaxNode>,
+ U: IntoIterator<Item = SyntaxElement>,
+ >(
+ &mut self,
+ parent: &T,
+ what: U,
+ ) {
+ self.insertions
+ .entry(InsertPos::FirstChildOf(parent.clone().into()))
+ .or_insert_with(Vec::new)
+ .extend(what)
+ }
pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) {
let what = what.clone().into();
let replacement = Replacement::Single(with.clone().into());
}
pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
- if self.f.is_none() && self.replacements.is_empty() {
+ if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() {
return node.clone();
}
self.rewrite_children(node)
///
/// Returns `None` when there are no replacements.
pub fn rewrite_root(&self) -> Option<SyntaxNode> {
+ fn element_to_node_or_parent(element: &SyntaxElement) -> SyntaxNode {
+ match element {
+ SyntaxElement::Node(it) => it.clone(),
+ SyntaxElement::Token(it) => it.parent(),
+ }
+ }
+
assert!(self.f.is_none());
self.replacements
.keys()
- .map(|element| match element {
- SyntaxElement::Node(it) => it.clone(),
- SyntaxElement::Token(it) => it.parent(),
- })
- // If we only have one replacement, we must return its parent node, since `rewrite` does
+ .map(element_to_node_or_parent)
+ .chain(self.insertions.keys().map(|pos| match pos {
+ InsertPos::FirstChildOf(it) => it.clone(),
+ InsertPos::After(it) => element_to_node_or_parent(it),
+ }))
+ // If we only have one replacement/insertion, we must return its parent node, since `rewrite` does
// not replace the node passed to it.
.map(|it| it.parent().unwrap_or(it))
.fold1(|a, b| least_common_ancestor(&a, &b).unwrap())
self.replacements.get(element).cloned()
}
+ fn insertions(&self, pos: &InsertPos) -> Option<impl Iterator<Item = SyntaxElement> + '_> {
+ self.insertions.get(pos).map(|insertions| insertions.iter().cloned())
+ }
+
fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode {
// FIXME: this could be made much faster.
let mut new_children = Vec::new();
+ if let Some(elements) = self.insertions(&InsertPos::FirstChildOf(node.clone())) {
+ new_children.extend(elements.map(element_to_green));
+ }
for child in node.children_with_tokens() {
self.rewrite_self(&mut new_children, &child);
}
) {
if let Some(replacement) = self.replacement(&element) {
match replacement {
- Replacement::Single(NodeOrToken::Node(it)) => {
- acc.push(NodeOrToken::Node(it.green().clone()))
- }
- Replacement::Single(NodeOrToken::Token(it)) => {
- acc.push(NodeOrToken::Token(it.green().clone()))
- }
+ Replacement::Single(element) => acc.push(element_to_green(element)),
Replacement::Many(replacements) => {
- acc.extend(replacements.iter().map(|it| match it {
- NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
- NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
- }))
+ acc.extend(replacements.into_iter().map(element_to_green))
}
Replacement::Delete => (),
};
- return;
+ } else {
+ match element {
+ NodeOrToken::Token(it) => acc.push(NodeOrToken::Token(it.green().clone())),
+ NodeOrToken::Node(it) => {
+ acc.push(NodeOrToken::Node(self.rewrite_children(it).green().clone()));
+ }
+ }
+ }
+ if let Some(elements) = self.insertions(&InsertPos::After(element.clone())) {
+ acc.extend(elements.map(element_to_green));
}
- let res = match element {
- NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
- NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()),
- };
- acc.push(res)
+ }
+}
+
+fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> {
+ match element {
+ NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
+ NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
}
}
impl ops::AddAssign for SyntaxRewriter<'_> {
fn add_assign(&mut self, rhs: SyntaxRewriter) {
assert!(rhs.f.is_none());
- self.replacements.extend(rhs.replacements)
+ self.replacements.extend(rhs.replacements);
+ for (pos, insertions) in rhs.insertions.into_iter() {
+ match self.insertions.entry(pos) {
+ indexmap::map::Entry::Occupied(mut occupied) => {
+ occupied.get_mut().extend(insertions)
+ }
+ indexmap::map::Entry::Vacant(vacant) => drop(vacant.insert(insertions)),
+ }
+ }
}
}
--- /dev/null
+SOURCE_FILE@0..53
+ STRUCT@0..25
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "B"
+ TUPLE_FIELD_LIST@8..24
+ L_PAREN@8..9 "("
+ TUPLE_FIELD@9..23
+ VISIBILITY@9..12
+ PUB_KW@9..12 "pub"
+ WHITESPACE@12..13 " "
+ PAREN_TYPE@13..23
+ L_PAREN@13..14 "("
+ PATH_TYPE@14..22
+ PATH@14..22
+ PATH@14..19
+ PATH_SEGMENT@14..19
+ SUPER_KW@14..19 "super"
+ COLON2@19..21 "::"
+ PATH_SEGMENT@21..22
+ NAME_REF@21..22
+ IDENT@21..22 "A"
+ R_PAREN@22..23 ")"
+ R_PAREN@23..24 ")"
+ SEMICOLON@24..25 ";"
+ WHITESPACE@25..26 "\n"
+ STRUCT@26..52
+ STRUCT_KW@26..32 "struct"
+ WHITESPACE@32..33 " "
+ NAME@33..34
+ IDENT@33..34 "B"
+ TUPLE_FIELD_LIST@34..51
+ L_PAREN@34..35 "("
+ TUPLE_FIELD@35..50
+ VISIBILITY@35..38
+ PUB_KW@35..38 "pub"
+ WHITESPACE@38..39 " "
+ TUPLE_TYPE@39..50
+ L_PAREN@39..40 "("
+ PATH_TYPE@40..48
+ PATH@40..48
+ PATH@40..45
+ PATH_SEGMENT@40..45
+ CRATE_KW@40..45 "crate"
+ COLON2@45..47 "::"
+ PATH_SEGMENT@47..48
+ NAME_REF@47..48
+ IDENT@47..48 "A"
+ COMMA@48..49 ","
+ R_PAREN@49..50 ")"
+ R_PAREN@50..51 ")"
+ SEMICOLON@51..52 ";"
+ WHITESPACE@52..53 "\n"
--- /dev/null
+struct B(pub (super::A));
+struct B(pub (crate::A,));