mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 21:43:37 +00:00
Merge #1085
1085: add ast::tokens r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
d21a677715
11 changed files with 293 additions and 277 deletions
|
@ -5,7 +5,7 @@ use crate::{Assist, AssistId, AssistCtx};
|
||||||
use hir::Resolver;
|
use hir::Resolver;
|
||||||
use hir::db::HirDatabase;
|
use hir::db::HirDatabase;
|
||||||
use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
|
use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
|
||||||
use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner};
|
use ra_syntax::ast::{self, AstNode, AstToken, FnDef, ImplItem, ImplItemKind, NameOwner};
|
||||||
use ra_db::FilePosition;
|
use ra_db::FilePosition;
|
||||||
use ra_fmt::{leading_indent, reindent};
|
use ra_fmt::{leading_indent, reindent};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
use hir::{
|
use hir::{
|
||||||
db::HirDatabase,
|
db::HirDatabase,
|
||||||
source_binder::function_from_child_node
|
source_binder::function_from_child_node,
|
||||||
};
|
};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast::{
|
ast::{self, AstNode, AstToken, PatKind, ExprKind},
|
||||||
self,
|
|
||||||
AstNode,
|
|
||||||
PatKind,
|
|
||||||
ExprKind
|
|
||||||
},
|
|
||||||
TextRange,
|
TextRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
//!
|
//!
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
AstNode,
|
|
||||||
SyntaxNode, SyntaxKind::*, SyntaxToken, SyntaxKind,
|
SyntaxNode, SyntaxKind::*, SyntaxToken, SyntaxKind,
|
||||||
ast,
|
ast::{self, AstNode, AstToken},
|
||||||
algo::generate,
|
algo::generate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
created: "2019-02-18T09:22:24.062138085Z"
|
created: "2019-04-02T07:43:12.954637543Z"
|
||||||
creator: insta@0.6.2
|
creator: insta@0.7.4
|
||||||
source: crates/ra_ide_api/src/completion/completion_item.rs
|
source: crates/ra_ide_api/src/completion/completion_item.rs
|
||||||
expression: kind_completions
|
expression: kind_completions
|
||||||
---
|
---
|
||||||
|
@ -33,6 +33,9 @@ expression: kind_completions
|
||||||
delete: [180; 180),
|
delete: [180; 180),
|
||||||
insert: "S",
|
insert: "S",
|
||||||
kind: EnumVariant,
|
kind: EnumVariant,
|
||||||
detail: "(S)"
|
detail: "(S)",
|
||||||
|
documentation: Documentation(
|
||||||
|
""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use ra_db::SourceDatabase;
|
use ra_db::SourceDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
Direction, SyntaxNode, TextRange, TextUnit, AstNode, SyntaxElement,
|
Direction, SyntaxNode, TextRange, TextUnit, SyntaxElement,
|
||||||
algo::{find_covering_element, find_token_at_offset, TokenAtOffset},
|
algo::{find_covering_element, find_token_at_offset, TokenAtOffset},
|
||||||
SyntaxKind::*, SyntaxToken,
|
SyntaxKind::*, SyntaxToken,
|
||||||
ast::Comment,
|
ast::{self, AstNode, AstToken},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{FileRange, db::RootDatabase};
|
use crate::{FileRange, db::RootDatabase};
|
||||||
|
@ -55,7 +55,7 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
|
||||||
if token.range() != range {
|
if token.range() != range {
|
||||||
return Some(token.range());
|
return Some(token.range());
|
||||||
}
|
}
|
||||||
if let Some(comment) = Comment::cast(token) {
|
if let Some(comment) = ast::Comment::cast(token) {
|
||||||
if let Some(range) = extend_comments(comment) {
|
if let Some(range) = extend_comments(comment) {
|
||||||
return Some(range);
|
return Some(range);
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_comments(comment: Comment) -> Option<TextRange> {
|
fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
|
||||||
let prev = adj_comments(comment, Direction::Prev);
|
let prev = adj_comments(comment, Direction::Prev);
|
||||||
let next = adj_comments(comment, Direction::Next);
|
let next = adj_comments(comment, Direction::Next);
|
||||||
if prev != next {
|
if prev != next {
|
||||||
|
@ -186,14 +186,14 @@ fn extend_comments(comment: Comment) -> Option<TextRange> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adj_comments(comment: Comment, dir: Direction) -> Comment {
|
fn adj_comments(comment: ast::Comment, dir: Direction) -> ast::Comment {
|
||||||
let mut res = comment;
|
let mut res = comment;
|
||||||
for element in comment.syntax().siblings_with_tokens(dir) {
|
for element in comment.syntax().siblings_with_tokens(dir) {
|
||||||
let token = match element.as_token() {
|
let token = match element.as_token() {
|
||||||
None => break,
|
None => break,
|
||||||
Some(token) => token,
|
Some(token) => token,
|
||||||
};
|
};
|
||||||
if let Some(c) = Comment::cast(token) {
|
if let Some(c) = ast::Comment::cast(token) {
|
||||||
res = c
|
res = c
|
||||||
} else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
|
} else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
AstNode, SourceFile, SyntaxNode, TextRange, Direction, SyntaxElement,
|
SourceFile, SyntaxNode, TextRange, Direction, SyntaxElement,
|
||||||
SyntaxKind::{self, *},
|
SyntaxKind::{self, *},
|
||||||
ast::{self, VisibilityOwner, Comment},
|
ast::{self, AstNode, AstToken, VisibilityOwner},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -139,8 +139,8 @@ fn contiguous_range_for_group_unless<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contiguous_range_for_comment<'a>(
|
fn contiguous_range_for_comment<'a>(
|
||||||
first: Comment<'a>,
|
first: ast::Comment<'a>,
|
||||||
visited: &mut FxHashSet<Comment<'a>>,
|
visited: &mut FxHashSet<ast::Comment<'a>>,
|
||||||
) -> Option<TextRange> {
|
) -> Option<TextRange> {
|
||||||
visited.insert(first);
|
visited.insert(first);
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ fn contiguous_range_for_comment<'a>(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(c) = Comment::cast(token) {
|
if let Some(c) = ast::Comment::cast(token) {
|
||||||
if c.flavor() == group_flavor {
|
if c.flavor() == group_flavor {
|
||||||
visited.insert(c);
|
visited.insert(c);
|
||||||
last = c;
|
last = c;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
SourceFile, TextRange, TextUnit, AstNode, SyntaxNode, SyntaxElement, SyntaxToken,
|
SourceFile, TextRange, TextUnit, SyntaxNode, SyntaxElement, SyntaxToken,
|
||||||
SyntaxKind::{self, WHITESPACE, COMMA, R_CURLY, R_PAREN, R_BRACK},
|
SyntaxKind::{self, WHITESPACE, COMMA, R_CURLY, R_PAREN, R_BRACK},
|
||||||
algo::{find_covering_element, non_trivia_sibling},
|
algo::{find_covering_element, non_trivia_sibling},
|
||||||
ast,
|
ast::{self, AstNode, AstToken},
|
||||||
Direction,
|
Direction,
|
||||||
};
|
};
|
||||||
use ra_fmt::{
|
use ra_fmt::{
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ra_syntax::{
|
||||||
AstNode, SourceFile, SyntaxKind::*,
|
AstNode, SourceFile, SyntaxKind::*,
|
||||||
TextUnit, TextRange, SyntaxToken,
|
TextUnit, TextRange, SyntaxToken,
|
||||||
algo::{find_node_at_offset, find_token_at_offset, TokenAtOffset},
|
algo::{find_node_at_offset, find_token_at_offset, TokenAtOffset},
|
||||||
ast::{self},
|
ast::{self, AstToken},
|
||||||
};
|
};
|
||||||
use ra_fmt::leading_indent;
|
use ra_fmt::leading_indent;
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
//! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s
|
//! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s
|
||||||
mod generated;
|
mod generated;
|
||||||
|
mod traits;
|
||||||
|
mod tokens;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
pub use self::generated::*;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
syntax_node::{SyntaxNode, SyntaxNodeChildren, TreeArc, RaTypes, SyntaxToken, SyntaxElement, SyntaxElementChildren},
|
syntax_node::{SyntaxNode, SyntaxNodeChildren, TreeArc, RaTypes, SyntaxToken, SyntaxElement},
|
||||||
SmolStr,
|
SmolStr,
|
||||||
SyntaxKind::*,
|
SyntaxKind::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
generated::*,
|
||||||
|
traits::*,
|
||||||
|
tokens::*,
|
||||||
|
};
|
||||||
|
|
||||||
/// The main trait to go from untyped `SyntaxNode` to a typed ast. The
|
/// The main trait to go from untyped `SyntaxNode` to a typed ast. The
|
||||||
/// conversion itself has zero runtime cost: ast and syntax nodes have exactly
|
/// conversion itself has zero runtime cost: ast and syntax nodes have exactly
|
||||||
/// the same representation: a pointer to the tree root and a pointer to the
|
/// the same representation: a pointer to the tree root and a pointer to the
|
||||||
|
@ -25,134 +32,32 @@ pub trait AstNode:
|
||||||
fn syntax(&self) -> &SyntaxNode;
|
fn syntax(&self) -> &SyntaxNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TypeAscriptionOwner: AstNode {
|
|
||||||
fn ascribed_type(&self) -> Option<&TypeRef> {
|
|
||||||
child_opt(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NameOwner: AstNode {
|
|
||||||
fn name(&self) -> Option<&Name> {
|
|
||||||
child_opt(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VisibilityOwner: AstNode {
|
|
||||||
fn visibility(&self) -> Option<&Visibility> {
|
|
||||||
child_opt(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait LoopBodyOwner: AstNode {
|
|
||||||
fn loop_body(&self) -> Option<&Block> {
|
|
||||||
child_opt(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ArgListOwner: AstNode {
|
|
||||||
fn arg_list(&self) -> Option<&ArgList> {
|
|
||||||
child_opt(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FnDefOwner: AstNode {
|
|
||||||
fn functions(&self) -> AstChildren<FnDef> {
|
|
||||||
children(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ItemOrMacro<'a> {
|
|
||||||
Item(&'a ModuleItem),
|
|
||||||
Macro(&'a MacroCall),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ModuleItemOwner: AstNode {
|
|
||||||
fn items(&self) -> AstChildren<ModuleItem> {
|
|
||||||
children(self)
|
|
||||||
}
|
|
||||||
fn items_with_macros(&self) -> ItemOrMacroIter {
|
|
||||||
ItemOrMacroIter(self.syntax().children())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>);
|
pub struct AstChildren<'a, N> {
|
||||||
|
inner: SyntaxNodeChildren<'a>,
|
||||||
|
ph: PhantomData<N>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for ItemOrMacroIter<'a> {
|
impl<'a, N> AstChildren<'a, N> {
|
||||||
type Item = ItemOrMacro<'a>;
|
fn new(parent: &'a SyntaxNode) -> Self {
|
||||||
fn next(&mut self) -> Option<ItemOrMacro<'a>> {
|
AstChildren { inner: parent.children(), ph: PhantomData }
|
||||||
loop {
|
|
||||||
let n = self.0.next()?;
|
|
||||||
if let Some(item) = ModuleItem::cast(n) {
|
|
||||||
return Some(ItemOrMacro::Item(item));
|
|
||||||
}
|
|
||||||
if let Some(call) = MacroCall::cast(n) {
|
|
||||||
return Some(ItemOrMacro::Macro(call));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TypeParamsOwner: AstNode {
|
impl<'a, N: AstNode + 'a> Iterator for AstChildren<'a, N> {
|
||||||
fn type_param_list(&self) -> Option<&TypeParamList> {
|
type Item = &'a N;
|
||||||
child_opt(self)
|
fn next(&mut self) -> Option<&'a N> {
|
||||||
}
|
self.inner.by_ref().find_map(N::cast)
|
||||||
|
|
||||||
fn where_clause(&self) -> Option<&WhereClause> {
|
|
||||||
child_opt(self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TypeBoundsOwner: AstNode {
|
pub trait AstToken<'a> {
|
||||||
fn type_bound_list(&self) -> Option<&TypeBoundList> {
|
fn cast(token: SyntaxToken<'a>) -> Option<Self>
|
||||||
child_opt(self)
|
where
|
||||||
}
|
Self: Sized;
|
||||||
}
|
fn syntax(&self) -> SyntaxToken<'a>;
|
||||||
|
fn text(&self) -> &'a SmolStr {
|
||||||
pub trait AttrsOwner: AstNode {
|
self.syntax().text()
|
||||||
fn attrs(&self) -> AstChildren<Attr> {
|
|
||||||
children(self)
|
|
||||||
}
|
|
||||||
fn has_atom_attr(&self, atom: &str) -> bool {
|
|
||||||
self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DocCommentsOwner: AstNode {
|
|
||||||
fn doc_comments(&self) -> CommentIter {
|
|
||||||
CommentIter { iter: self.syntax().children_with_tokens() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the textual content of a doc comment block as a single string.
|
|
||||||
/// That is, strips leading `///` (+ optional 1 character of whitespace)
|
|
||||||
/// and joins lines.
|
|
||||||
fn doc_comment_text(&self) -> Option<std::string::String> {
|
|
||||||
let docs = self
|
|
||||||
.doc_comments()
|
|
||||||
.filter(|comment| comment.is_doc_comment())
|
|
||||||
.map(|comment| {
|
|
||||||
let prefix_len = comment.prefix().len();
|
|
||||||
|
|
||||||
let line = comment.text().as_str();
|
|
||||||
|
|
||||||
// Determine if the prefix or prefix + 1 char is stripped
|
|
||||||
let pos =
|
|
||||||
if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
|
|
||||||
prefix_len + 1
|
|
||||||
} else {
|
|
||||||
prefix_len
|
|
||||||
};
|
|
||||||
|
|
||||||
line[pos..].to_owned()
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
if docs.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(docs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,111 +108,6 @@ impl Attr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct Comment<'a>(SyntaxToken<'a>);
|
|
||||||
|
|
||||||
impl<'a> Comment<'a> {
|
|
||||||
pub fn cast(token: SyntaxToken<'a>) -> Option<Self> {
|
|
||||||
if token.kind() == COMMENT {
|
|
||||||
Some(Comment(token))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn syntax(&self) -> SyntaxToken<'a> {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&self) -> &'a SmolStr {
|
|
||||||
self.0.text()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flavor(&self) -> CommentFlavor {
|
|
||||||
let text = self.text();
|
|
||||||
if text.starts_with("///") {
|
|
||||||
CommentFlavor::Doc
|
|
||||||
} else if text.starts_with("//!") {
|
|
||||||
CommentFlavor::ModuleDoc
|
|
||||||
} else if text.starts_with("//") {
|
|
||||||
CommentFlavor::Line
|
|
||||||
} else {
|
|
||||||
CommentFlavor::Multiline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_doc_comment(&self) -> bool {
|
|
||||||
self.flavor().is_doc_comment()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prefix(&self) -> &'static str {
|
|
||||||
self.flavor().prefix()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CommentIter<'a> {
|
|
||||||
iter: SyntaxElementChildren<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for CommentIter<'a> {
|
|
||||||
type Item = Comment<'a>;
|
|
||||||
fn next(&mut self) -> Option<Comment<'a>> {
|
|
||||||
self.iter.by_ref().find_map(|el| el.as_token().and_then(Comment::cast))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum CommentFlavor {
|
|
||||||
Line,
|
|
||||||
Doc,
|
|
||||||
ModuleDoc,
|
|
||||||
Multiline,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommentFlavor {
|
|
||||||
pub fn prefix(&self) -> &'static str {
|
|
||||||
use self::CommentFlavor::*;
|
|
||||||
match *self {
|
|
||||||
Line => "//",
|
|
||||||
Doc => "///",
|
|
||||||
ModuleDoc => "//!",
|
|
||||||
Multiline => "/*",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_doc_comment(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
CommentFlavor::Doc | CommentFlavor::ModuleDoc => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Whitespace<'a>(SyntaxToken<'a>);
|
|
||||||
|
|
||||||
impl<'a> Whitespace<'a> {
|
|
||||||
pub fn cast(token: SyntaxToken<'a>) -> Option<Self> {
|
|
||||||
if token.kind() == WHITESPACE {
|
|
||||||
Some(Whitespace(token))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn syntax(&self) -> SyntaxToken<'a> {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&self) -> &'a SmolStr {
|
|
||||||
self.0.text()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spans_multiple_lines(&self) -> bool {
|
|
||||||
let text = self.text();
|
|
||||||
text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Name {
|
impl Name {
|
||||||
pub fn text(&self) -> &SmolStr {
|
pub fn text(&self) -> &SmolStr {
|
||||||
let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
|
let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
|
||||||
|
@ -468,29 +268,6 @@ fn children<P: AstNode, C: AstNode>(parent: &P) -> AstChildren<C> {
|
||||||
AstChildren::new(parent.syntax())
|
AstChildren::new(parent.syntax())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AstChildren<'a, N> {
|
|
||||||
inner: SyntaxNodeChildren<'a>,
|
|
||||||
ph: PhantomData<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, N> AstChildren<'a, N> {
|
|
||||||
fn new(parent: &'a SyntaxNode) -> Self {
|
|
||||||
AstChildren { inner: parent.children(), ph: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, N: AstNode + 'a> Iterator for AstChildren<'a, N> {
|
|
||||||
type Item = &'a N;
|
|
||||||
fn next(&mut self) -> Option<&'a N> {
|
|
||||||
loop {
|
|
||||||
if let Some(n) = N::cast(self.inner.next()?) {
|
|
||||||
return Some(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum StructFlavor<'a> {
|
pub enum StructFlavor<'a> {
|
||||||
Tuple(&'a PosFieldDefList),
|
Tuple(&'a PosFieldDefList),
|
||||||
|
|
92
crates/ra_syntax/src/ast/tokens.rs
Normal file
92
crates/ra_syntax/src/ast/tokens.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
use crate::{
|
||||||
|
SyntaxToken,
|
||||||
|
SyntaxKind::{COMMENT, WHITESPACE},
|
||||||
|
ast::AstToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Comment<'a>(SyntaxToken<'a>);
|
||||||
|
|
||||||
|
impl<'a> AstToken<'a> for Comment<'a> {
|
||||||
|
fn cast(token: SyntaxToken<'a>) -> Option<Self> {
|
||||||
|
if token.kind() == COMMENT {
|
||||||
|
Some(Comment(token))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn syntax(&self) -> SyntaxToken<'a> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Comment<'a> {
|
||||||
|
pub fn flavor(&self) -> CommentFlavor {
|
||||||
|
let text = self.text();
|
||||||
|
if text.starts_with("///") {
|
||||||
|
CommentFlavor::OuterDoc
|
||||||
|
} else if text.starts_with("//!") {
|
||||||
|
CommentFlavor::InnerDoc
|
||||||
|
} else if text.starts_with("//") {
|
||||||
|
CommentFlavor::Line
|
||||||
|
} else {
|
||||||
|
CommentFlavor::Multiline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_doc_comment(&self) -> bool {
|
||||||
|
self.flavor().is_doc_comment()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prefix(&self) -> &'static str {
|
||||||
|
self.flavor().prefix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum CommentFlavor {
|
||||||
|
Line,
|
||||||
|
OuterDoc,
|
||||||
|
InnerDoc,
|
||||||
|
Multiline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommentFlavor {
|
||||||
|
pub fn prefix(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
CommentFlavor::Line => "//",
|
||||||
|
CommentFlavor::OuterDoc => "///",
|
||||||
|
CommentFlavor::InnerDoc => "//!",
|
||||||
|
CommentFlavor::Multiline => "/*",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_doc_comment(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
CommentFlavor::OuterDoc | CommentFlavor::InnerDoc => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Whitespace<'a>(SyntaxToken<'a>);
|
||||||
|
|
||||||
|
impl<'a> AstToken<'a> for Whitespace<'a> {
|
||||||
|
fn cast(token: SyntaxToken<'a>) -> Option<Self> {
|
||||||
|
if token.kind() == WHITESPACE {
|
||||||
|
Some(Whitespace(token))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn syntax(&self) -> SyntaxToken<'a> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Whitespace<'a> {
|
||||||
|
pub fn spans_multiple_lines(&self) -> bool {
|
||||||
|
let text = self.text();
|
||||||
|
text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
|
||||||
|
}
|
||||||
|
}
|
150
crates/ra_syntax/src/ast/traits.rs
Normal file
150
crates/ra_syntax/src/ast/traits.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
syntax_node::{SyntaxNodeChildren, SyntaxElementChildren},
|
||||||
|
ast::{self, child_opt, children, AstNode, AstToken, AstChildren},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait TypeAscriptionOwner: AstNode {
|
||||||
|
fn ascribed_type(&self) -> Option<&ast::TypeRef> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NameOwner: AstNode {
|
||||||
|
fn name(&self) -> Option<&ast::Name> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait VisibilityOwner: AstNode {
|
||||||
|
fn visibility(&self) -> Option<&ast::Visibility> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LoopBodyOwner: AstNode {
|
||||||
|
fn loop_body(&self) -> Option<&ast::Block> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ArgListOwner: AstNode {
|
||||||
|
fn arg_list(&self) -> Option<&ast::ArgList> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FnDefOwner: AstNode {
|
||||||
|
fn functions(&self) -> AstChildren<ast::FnDef> {
|
||||||
|
children(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ItemOrMacro<'a> {
|
||||||
|
Item(&'a ast::ModuleItem),
|
||||||
|
Macro(&'a ast::MacroCall),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ModuleItemOwner: AstNode {
|
||||||
|
fn items(&self) -> AstChildren<ast::ModuleItem> {
|
||||||
|
children(self)
|
||||||
|
}
|
||||||
|
fn items_with_macros(&self) -> ItemOrMacroIter {
|
||||||
|
ItemOrMacroIter(self.syntax().children())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ItemOrMacroIter<'a>(SyntaxNodeChildren<'a>);
|
||||||
|
|
||||||
|
impl<'a> Iterator for ItemOrMacroIter<'a> {
|
||||||
|
type Item = ItemOrMacro<'a>;
|
||||||
|
fn next(&mut self) -> Option<ItemOrMacro<'a>> {
|
||||||
|
loop {
|
||||||
|
let n = self.0.next()?;
|
||||||
|
if let Some(item) = ast::ModuleItem::cast(n) {
|
||||||
|
return Some(ItemOrMacro::Item(item));
|
||||||
|
}
|
||||||
|
if let Some(call) = ast::MacroCall::cast(n) {
|
||||||
|
return Some(ItemOrMacro::Macro(call));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TypeParamsOwner: AstNode {
|
||||||
|
fn type_param_list(&self) -> Option<&ast::TypeParamList> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn where_clause(&self) -> Option<&ast::WhereClause> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TypeBoundsOwner: AstNode {
|
||||||
|
fn type_bound_list(&self) -> Option<&ast::TypeBoundList> {
|
||||||
|
child_opt(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AttrsOwner: AstNode {
|
||||||
|
fn attrs(&self) -> AstChildren<ast::Attr> {
|
||||||
|
children(self)
|
||||||
|
}
|
||||||
|
fn has_atom_attr(&self, atom: &str) -> bool {
|
||||||
|
self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DocCommentsOwner: AstNode {
|
||||||
|
fn doc_comments(&self) -> CommentIter {
|
||||||
|
CommentIter { iter: self.syntax().children_with_tokens() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the textual content of a doc comment block as a single string.
|
||||||
|
/// That is, strips leading `///` (+ optional 1 character of whitespace)
|
||||||
|
/// and joins lines.
|
||||||
|
fn doc_comment_text(&self) -> Option<String> {
|
||||||
|
let mut has_comments = false;
|
||||||
|
let docs = self
|
||||||
|
.doc_comments()
|
||||||
|
.filter(|comment| comment.is_doc_comment())
|
||||||
|
.map(|comment| {
|
||||||
|
has_comments = true;
|
||||||
|
let prefix_len = comment.prefix().len();
|
||||||
|
|
||||||
|
let line = comment.text().as_str();
|
||||||
|
|
||||||
|
// Determine if the prefix or prefix + 1 char is stripped
|
||||||
|
let pos =
|
||||||
|
if line.chars().nth(prefix_len).map(|c| c.is_whitespace()).unwrap_or(false) {
|
||||||
|
prefix_len + 1
|
||||||
|
} else {
|
||||||
|
prefix_len
|
||||||
|
};
|
||||||
|
|
||||||
|
line[pos..].to_owned()
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
if has_comments {
|
||||||
|
Some(docs)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CommentIter<'a> {
|
||||||
|
iter: SyntaxElementChildren<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for CommentIter<'a> {
|
||||||
|
type Item = ast::Comment<'a>;
|
||||||
|
fn next(&mut self) -> Option<ast::Comment<'a>> {
|
||||||
|
self.iter.by_ref().find_map(|el| el.as_token().and_then(ast::Comment::cast))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue