mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 21:35:20 +00:00
Merge branch 'master' into feature/issue/1856
# Conflicts: # crates/ra_assists/src/ast_editor.rs
This commit is contained in:
commit
81efd696cc
53 changed files with 1180 additions and 957 deletions
|
@ -1,14 +1,21 @@
|
|||
//! This module contains functions for editing syntax trees. As the trees are
|
||||
//! immutable, all function here return a fresh copy of the tree, instead of
|
||||
//! doing an in-place modification.
|
||||
use std::{iter, ops::RangeInclusive};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use std::ops::RangeInclusive;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
algo,
|
||||
ast::{self, make, AstNode},
|
||||
InsertPosition, SyntaxElement,
|
||||
ast::{
|
||||
self,
|
||||
make::{self, tokens},
|
||||
AstNode, TypeBoundsOwner,
|
||||
},
|
||||
AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
|
||||
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
|
||||
SyntaxNode, T,
|
||||
};
|
||||
|
||||
impl ast::FnDef {
|
||||
|
@ -31,6 +38,218 @@ impl ast::FnDef {
|
|||
}
|
||||
}
|
||||
|
||||
impl ast::ItemList {
|
||||
#[must_use]
|
||||
pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
|
||||
let mut res = self.clone();
|
||||
if !self.syntax().text().contains_char('\n') {
|
||||
res = res.make_multiline();
|
||||
}
|
||||
items.for_each(|it| res = res.append_item(it));
|
||||
res
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
|
||||
let (indent, position) = match self.impl_items().last() {
|
||||
Some(it) => (
|
||||
leading_indent(it.syntax()).unwrap_or_default().to_string(),
|
||||
InsertPosition::After(it.syntax().clone().into()),
|
||||
),
|
||||
None => match self.l_curly() {
|
||||
Some(it) => (
|
||||
" ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
|
||||
InsertPosition::After(it),
|
||||
),
|
||||
None => return self.clone(),
|
||||
},
|
||||
};
|
||||
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
|
||||
let to_insert: ArrayVec<[SyntaxElement; 2]> =
|
||||
[ws.ws().into(), item.syntax().clone().into()].into();
|
||||
insert_children(self, position, to_insert.into_iter())
|
||||
}
|
||||
|
||||
fn l_curly(&self) -> Option<SyntaxElement> {
|
||||
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
|
||||
}
|
||||
|
||||
fn make_multiline(&self) -> ast::ItemList {
|
||||
let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
|
||||
Some(it) => it,
|
||||
None => return self.clone(),
|
||||
};
|
||||
let sibling = match l_curly.next_sibling_or_token() {
|
||||
Some(it) => it,
|
||||
None => return self.clone(),
|
||||
};
|
||||
let existing_ws = match sibling.as_token() {
|
||||
None => None,
|
||||
Some(tok) if tok.kind() != WHITESPACE => None,
|
||||
Some(ws) => {
|
||||
if ws.text().contains('\n') {
|
||||
return self.clone();
|
||||
}
|
||||
Some(ws.clone())
|
||||
}
|
||||
};
|
||||
|
||||
let indent = leading_indent(self.syntax()).unwrap_or("".into());
|
||||
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
|
||||
let to_insert = iter::once(ws.ws().into());
|
||||
match existing_ws {
|
||||
None => insert_children(self, InsertPosition::After(l_curly), to_insert),
|
||||
Some(ws) => {
|
||||
replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::RecordFieldList {
|
||||
#[must_use]
|
||||
pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList {
|
||||
self.insert_field(InsertPosition::Last, field)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn insert_field(
|
||||
&self,
|
||||
position: InsertPosition<&'_ ast::RecordField>,
|
||||
field: &ast::RecordField,
|
||||
) -> ast::RecordFieldList {
|
||||
let is_multiline = self.syntax().text().contains_char('\n');
|
||||
let ws;
|
||||
let space = if is_multiline {
|
||||
ws = tokens::WsBuilder::new(&format!(
|
||||
"\n{} ",
|
||||
leading_indent(self.syntax()).unwrap_or("".into())
|
||||
));
|
||||
ws.ws()
|
||||
} else {
|
||||
tokens::single_space()
|
||||
};
|
||||
|
||||
let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
|
||||
to_insert.push(space.into());
|
||||
to_insert.push(field.syntax().clone().into());
|
||||
to_insert.push(tokens::comma().into());
|
||||
|
||||
macro_rules! after_l_curly {
|
||||
() => {{
|
||||
let anchor = match self.l_curly() {
|
||||
Some(it) => it,
|
||||
None => return self.clone(),
|
||||
};
|
||||
InsertPosition::After(anchor)
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! after_field {
|
||||
($anchor:expr) => {
|
||||
if let Some(comma) = $anchor
|
||||
.syntax()
|
||||
.siblings_with_tokens(Direction::Next)
|
||||
.find(|it| it.kind() == T![,])
|
||||
{
|
||||
InsertPosition::After(comma)
|
||||
} else {
|
||||
to_insert.insert(0, tokens::comma().into());
|
||||
InsertPosition::After($anchor.syntax().clone().into())
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let position = match position {
|
||||
InsertPosition::First => after_l_curly!(),
|
||||
InsertPosition::Last => {
|
||||
if !is_multiline {
|
||||
// don't insert comma before curly
|
||||
to_insert.pop();
|
||||
}
|
||||
match self.fields().last() {
|
||||
Some(it) => after_field!(it),
|
||||
None => after_l_curly!(),
|
||||
}
|
||||
}
|
||||
InsertPosition::Before(anchor) => {
|
||||
InsertPosition::Before(anchor.syntax().clone().into())
|
||||
}
|
||||
InsertPosition::After(anchor) => after_field!(anchor),
|
||||
};
|
||||
|
||||
insert_children(self, position, to_insert.iter().cloned())
|
||||
}
|
||||
|
||||
fn l_curly(&self) -> Option<SyntaxElement> {
|
||||
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::TypeParam {
|
||||
#[must_use]
|
||||
pub fn remove_bounds(&self) -> ast::TypeParam {
|
||||
let colon = match self.colon_token() {
|
||||
Some(it) => it,
|
||||
None => return self.clone(),
|
||||
};
|
||||
let end = match self.type_bound_list() {
|
||||
Some(it) => it.syntax().clone().into(),
|
||||
None => colon.clone().into(),
|
||||
};
|
||||
replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
|
||||
N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
|
||||
}
|
||||
|
||||
fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
|
||||
while let Some(start) =
|
||||
node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT)
|
||||
{
|
||||
let end = match &start.next_sibling_or_token() {
|
||||
Some(el) if el.kind() == WHITESPACE => el.clone(),
|
||||
Some(_) | None => start.clone(),
|
||||
};
|
||||
node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty());
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn replace_descendants<N: AstNode, D: AstNode>(
|
||||
parent: &N,
|
||||
replacement_map: impl Iterator<Item = (D, D)>,
|
||||
) -> N {
|
||||
let map = replacement_map
|
||||
.map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
|
||||
.collect::<FxHashMap<_, _>>();
|
||||
let new_syntax = algo::replace_descendants(parent.syntax(), &map);
|
||||
N::cast(new_syntax).unwrap()
|
||||
}
|
||||
|
||||
// Note this is copy-pasted from fmt. It seems like fmt should be a separate
|
||||
// crate, but basic tree building should be this crate. However, tree building
|
||||
// might want to call into fmt...
|
||||
fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
|
||||
let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token());
|
||||
for token in prev_tokens {
|
||||
if let Some(ws) = ast::Whitespace::cast(token.clone()) {
|
||||
let ws_text = ws.text();
|
||||
if let Some(pos) = ws_text.rfind('\n') {
|
||||
return Some(ws_text[pos + 1..].into());
|
||||
}
|
||||
}
|
||||
if token.text().contains('\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn insert_children<N: AstNode>(
|
||||
parent: &N,
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
//! Various extension methods to ast Nodes, which are hard to code-generate.
|
||||
//! Extensions for various expressions live in a sibling `expr_extensions` module.
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
ast::{self, child_opt, children, AstNode, SyntaxNode},
|
||||
ast::{self, child_opt, children, AstChildren, AstNode, AttrInput, SyntaxNode},
|
||||
SmolStr, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxToken, T,
|
||||
|
@ -38,62 +36,37 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
|
|||
}
|
||||
|
||||
impl ast::Attr {
|
||||
pub fn is_inner(&self) -> bool {
|
||||
let tt = match self.value() {
|
||||
None => return false,
|
||||
Some(tt) => tt,
|
||||
};
|
||||
|
||||
let prev = match tt.syntax().prev_sibling() {
|
||||
None => return false,
|
||||
Some(prev) => prev,
|
||||
};
|
||||
|
||||
prev.kind() == T![!]
|
||||
}
|
||||
|
||||
pub fn as_atom(&self) -> Option<SmolStr> {
|
||||
let tt = self.value()?;
|
||||
let (_bra, attr, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
|
||||
if attr.kind() == IDENT {
|
||||
Some(attr.as_token()?.text().clone())
|
||||
} else {
|
||||
None
|
||||
pub fn as_simple_atom(&self) -> Option<SmolStr> {
|
||||
match self.input() {
|
||||
None => self.simple_name(),
|
||||
Some(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_call(&self) -> Option<(SmolStr, ast::TokenTree)> {
|
||||
let tt = self.value()?;
|
||||
let (_bra, attr, args, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
|
||||
let args = ast::TokenTree::cast(args.as_node()?.clone())?;
|
||||
if attr.kind() == IDENT {
|
||||
Some((attr.as_token()?.text().clone(), args))
|
||||
} else {
|
||||
None
|
||||
pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> {
|
||||
match self.input() {
|
||||
Some(AttrInput::TokenTree(tt)) => Some((self.simple_name()?, tt)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_named(&self) -> Option<SmolStr> {
|
||||
let tt = self.value()?;
|
||||
let attr = tt.syntax().children_with_tokens().nth(1)?;
|
||||
if attr.kind() == IDENT {
|
||||
Some(attr.as_token()?.text().clone())
|
||||
} else {
|
||||
None
|
||||
pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> {
|
||||
match self.input() {
|
||||
Some(AttrInput::Literal(lit)) => {
|
||||
let key = self.simple_name()?;
|
||||
// FIXME: escape? raw string?
|
||||
let value = lit.syntax().first_token()?.text().trim_matches('"').into();
|
||||
Some((key, value))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> {
|
||||
let tt = self.value()?;
|
||||
let tt_node = tt.syntax();
|
||||
let attr = tt_node.children_with_tokens().nth(1)?;
|
||||
if attr.kind() == IDENT {
|
||||
let key = attr.as_token()?.text().clone();
|
||||
let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?;
|
||||
let val = val_node.as_token()?.text().trim_start_matches('"').trim_end_matches('"');
|
||||
Some((key, SmolStr::new(val)))
|
||||
} else {
|
||||
None
|
||||
pub fn simple_name(&self) -> Option<SmolStr> {
|
||||
let path = self.path()?;
|
||||
match (path.segment(), path.qualifier()) {
|
||||
(Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +176,16 @@ impl ast::ImplBlock {
|
|||
}
|
||||
}
|
||||
|
||||
impl ast::AttrsOwner for ast::ImplItem {
|
||||
fn attrs(&self) -> AstChildren<ast::Attr> {
|
||||
match self {
|
||||
ast::ImplItem::FnDef(it) => it.attrs(),
|
||||
ast::ImplItem::TypeAliasDef(it) => it.attrs(),
|
||||
ast::ImplItem::ConstDef(it) => it.attrs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum StructKind {
|
||||
Tuple(ast::TupleFieldDefList),
|
||||
|
|
|
@ -166,11 +166,52 @@ impl AstNode for Attr {
|
|||
}
|
||||
}
|
||||
impl Attr {
|
||||
pub fn value(&self) -> Option<TokenTree> {
|
||||
pub fn path(&self) -> Option<Path> {
|
||||
AstChildren::new(&self.syntax).next()
|
||||
}
|
||||
pub fn input(&self) -> Option<AttrInput> {
|
||||
AstChildren::new(&self.syntax).next()
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum AttrInput {
|
||||
Literal(Literal),
|
||||
TokenTree(TokenTree),
|
||||
}
|
||||
impl From<Literal> for AttrInput {
|
||||
fn from(node: Literal) -> AttrInput {
|
||||
AttrInput::Literal(node)
|
||||
}
|
||||
}
|
||||
impl From<TokenTree> for AttrInput {
|
||||
fn from(node: TokenTree) -> AttrInput {
|
||||
AttrInput::TokenTree(node)
|
||||
}
|
||||
}
|
||||
impl AstNode for AttrInput {
|
||||
fn can_cast(kind: SyntaxKind) -> bool {
|
||||
match kind {
|
||||
LITERAL | TOKEN_TREE => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
let res = match syntax.kind() {
|
||||
LITERAL => AttrInput::Literal(Literal { syntax }),
|
||||
TOKEN_TREE => AttrInput::TokenTree(TokenTree { syntax }),
|
||||
_ => return None,
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
fn syntax(&self) -> &SyntaxNode {
|
||||
match self {
|
||||
AttrInput::Literal(it) => &it.syntax,
|
||||
AttrInput::TokenTree(it) => &it.syntax,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AttrInput {}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AwaitExpr {
|
||||
pub(crate) syntax: SyntaxNode,
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ pub trait AttrsOwner: AstNode {
|
|||
children(self)
|
||||
}
|
||||
fn has_atom_attr(&self, atom: &str) -> bool {
|
||||
self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
|
||||
self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue