diff --git a/crates/hir-def/src/attrs/docs.rs b/crates/hir-def/src/attrs/docs.rs index 08de18583f75..7b701b2b0ca1 100644 --- a/crates/hir-def/src/attrs/docs.rs +++ b/crates/hir-def/src/attrs/docs.rs @@ -22,8 +22,8 @@ use hir_expand::{ }; use span::AstIdMap; use syntax::{ - AstNode, AstToken, SyntaxNode, - ast::{self, AttrDocCommentIter, IsString}, + AstNode, SyntaxNode, + ast::{self, IsString}, }; use thin_vec::ThinVec; use tt::{TextRange, TextSize}; @@ -199,10 +199,10 @@ impl Docs { )); } - fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut usize) { - let Some((doc, offset)) = comment.doc_comment() else { return }; - let offset = comment.syntax().text_range().start() + offset; - self.extend_with_doc_str(doc, offset, indent, comment.kind().shape); + fn extend_with_doc_comment(&mut self, comment: ast::DocComment, indent: &mut usize) { + let doc = comment.text(); + let offset = comment.syntax().text_range().start() + ast::DocComment::PREFIX_LEN; + self.extend_with_doc_str(doc, offset, indent, comment.shape()); } fn extend_with_doc_attr(&mut self, value: ast::String, indent: &mut usize) { @@ -591,13 +591,13 @@ fn extend_with_attrs<'a, 'db>( let mut expander = None; expand_cfg_attr_with_doc_comments::<_, Infallible>( - AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { - Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, - Either::Right(comment) => comment - .kind() - .doc - .is_some_and(|kind| (kind == ast::CommentPlacement::Inner) == expect_inner_attrs), - }), + node.children() + .filter_map(ast::AnyAttr::cast) + .filter(|attr| attr.kind().is_inner() == expect_inner_attrs) + .map(|attr| match attr { + ast::AnyAttr::Attr(it) => Either::Left(it), + ast::AnyAttr::DocComment(it) => Either::Right(it), + }), || *cfg_options.get_or_insert_with(get_cfg_options), |attr| { match attr { @@ -727,7 +727,7 @@ pub(crate) fn extract_docs<'a, 'db>( mod tests { use expect_test::expect; use hir_expand::InFile; - use syntax::{AstToken, ast}; + use syntax::{AstNode, ast}; use test_fixture::WithFixture; use thin_vec::ThinVec; use tt::{TextRange, TextSize}; @@ -911,8 +911,8 @@ mod tests { let comment = syntax::SourceFile::parse(source, span::Edition::CURRENT) .syntax_node() .descendants_with_tokens() - .filter_map(|it| it.into_token()) - .find_map(ast::Comment::cast) + .filter_map(|it| it.into_node()) + .find_map(ast::DocComment::cast) .expect("no comment in the fixture"); let mut docs = Docs { docs: String::new(), diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs index d950b6df214a..ace5fc3d757b 100644 --- a/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use syntax::{ - AstToken, Direction, SyntaxElement, TextRange, - ast::{self, Comment, CommentKind, CommentShape, Whitespace, edit::IndentLevel}, + AstToken, SyntaxToken, TextRange, + ast::{self, CommentKind, CommentShape, Whitespace, edit::IndentLevel}, }; use crate::{AssistContext, AssistId, Assists}; @@ -22,19 +22,19 @@ use crate::{AssistContext, AssistId, Assists}; // */ // ``` pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { - let comment = ctx.find_token_at_offset::()?; + let comment = ctx.find_token_at_offset::()?; // Only allow comments which are alone on their line if let Some(prev) = comment.syntax().prev_token() { Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } - match comment.kind().shape { + match comment.shape() { ast::CommentShape::Block => block_to_line(acc, comment), ast::CommentShape::Line => line_to_block(acc, comment), } } -fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { +fn block_to_line(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> { let target = comment.syntax().text_range(); acc.add( @@ -45,9 +45,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { let indentation = IndentLevel::from_token(comment.syntax()); let line_prefix = CommentKind { shape: CommentShape::Line, ..comment.kind() }.prefix(); - let text = comment.text(); - let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim(); - + let text = comment.text().trim(); let lines = text.lines().peekable(); let indent_spaces = indentation.to_string(); @@ -69,7 +67,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ) } -fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { +fn line_to_block(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> { // Find all the comments we'll be collapsing into a block let comments = relevant_line_comments(&comment); @@ -109,37 +107,26 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { /// The line -> block assist can be invoked from anywhere within a sequence of line comments. /// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will /// be joined. -pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { - // The prefix identifies the kind of comment we're dealing with - let prefix = comment.prefix(); - let same_prefix = |c: &ast::Comment| c.prefix() == prefix; +pub(crate) fn relevant_line_comments(comment: &ast::AnyComment) -> Vec { + let expected_kind = comment.kind(); + let same_kind = |c: &ast::AnyComment| c.kind() == expected_kind; // These tokens are allowed to exist between comments - let skippable = |not: &SyntaxElement| { - not.clone() - .into_token() - .and_then(Whitespace::cast) - .map(|w| !w.spans_multiple_lines()) - .unwrap_or(false) + let skippable = |not: &SyntaxToken| { + Whitespace::cast(not.clone()).map(|w| !w.spans_multiple_lines()).unwrap_or(false) }; // Find all preceding comments (in reverse order) that have the same prefix - let prev_comments = comment - .syntax() - .siblings_with_tokens(Direction::Prev) + let prev_comments = std::iter::successors(Some(comment.syntax().clone()), |it| it.prev_token()) .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten() + .map_while(ast::AnyComment::cast) + .take_while(same_kind) .skip(1); // skip the first element so we don't duplicate it in next_comments - let next_comments = comment - .syntax() - .siblings_with_tokens(Direction::Next) + let next_comments = std::iter::successors(Some(comment.syntax().clone()), |it| it.next_token()) .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten(); + .map_while(ast::AnyComment::cast) + .take_while(same_kind); let mut comments: Vec<_> = prev_comments.collect(); comments.reverse(); @@ -161,7 +148,7 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { // */ // // But since such comments aren't idiomatic we're okay with this. -pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { +pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::AnyComment) -> String { let text = comm.text(); let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text); let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); diff --git a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs index 11a3c64188d4..ae2b98423d81 100644 --- a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs +++ b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs @@ -1,10 +1,12 @@ use itertools::Itertools; use syntax::{ - AstToken, Direction, SyntaxElement, TextRange, - ast::{self, Comment, CommentPlacement, Whitespace, edit::IndentLevel}, + AstToken, TextRange, + ast::{self, AttrKind, Whitespace, edit::IndentLevel}, }; -use crate::{AssistContext, AssistId, Assists}; +use crate::{ + AssistContext, AssistId, Assists, handlers::convert_comment_block::relevant_line_comments, +}; // Assist: comment_to_doc // @@ -23,7 +25,7 @@ pub(crate) fn convert_comment_from_or_to_doc( acc: &mut Assists, ctx: &AssistContext<'_, '_>, ) -> Option<()> { - let comment = ctx.find_token_at_offset::()?; + let comment = ctx.find_token_at_offset::()?; match comment.kind().doc { Some(_) => doc_to_comment(acc, comment), @@ -31,7 +33,7 @@ pub(crate) fn convert_comment_from_or_to_doc( } } -fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { +fn doc_to_comment(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> { let target = if comment.kind().shape.is_line() { line_comments_text_range(&comment)? } else { @@ -52,7 +54,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { let prefix = format!("{indentation}//"); relevant_line_comments(&comment) .iter() - .map(|comment| comment.text()) + .map(|comment| comment.text_with_markers()) .flat_map(|text| text.lines()) .map(|line| line.replacen(line_start, &prefix, 1)) .join("\n") @@ -60,7 +62,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ast::CommentShape::Block => { let block_start = comment.prefix(); comment - .text() + .text_with_markers() .lines() .enumerate() .map(|(idx, line)| { @@ -78,7 +80,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ) } -fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacement) -> Option<()> { +fn comment_to_doc(acc: &mut Assists, comment: ast::AnyComment, style: AttrKind) -> Option<()> { let target = if comment.kind().shape.is_line() { line_comments_text_range(&comment)? } else { @@ -96,23 +98,23 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem ast::CommentShape::Line => { let indentation = IndentLevel::from_token(comment.syntax()); let line_start = match style { - CommentPlacement::Inner => format!("{indentation}//!"), - CommentPlacement::Outer => format!("{indentation}///"), + AttrKind::Inner => format!("{indentation}//!"), + AttrKind::Outer => format!("{indentation}///"), }; relevant_line_comments(&comment) .iter() - .map(|comment| comment.text()) + .map(|comment| comment.text_with_markers()) .flat_map(|text| text.lines()) .map(|line| line.replacen("//", &line_start, 1)) .join("\n") } ast::CommentShape::Block => { let block_start = match style { - CommentPlacement::Inner => "/*!", - CommentPlacement::Outer => "/**", + AttrKind::Inner => "/*!", + AttrKind::Outer => "/**", }; comment - .text() + .text_with_markers() .lines() .enumerate() .map(|(idx, line)| { @@ -176,7 +178,7 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem /// // Modules only normally get inner documentation when they are defined as a separate file. /// } /// ``` -fn can_be_doc_comment(comment: &ast::Comment) -> Option { +fn can_be_doc_comment(comment: &ast::AnyComment) -> Option { use syntax::SyntaxKind::*; // if the comment is not on its own line, then we do not propose anything. @@ -186,7 +188,7 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option { Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } // There is no previous token, this is the start of the file. - None => return Some(CommentPlacement::Inner), + None => return Some(AttrKind::Inner), } // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`, @@ -194,51 +196,10 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option { let parent = comment.syntax().parent(); let par_kind = parent.as_ref().map(|parent| parent.kind()); matches!(par_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_ALIAS | EXTERN_CRATE | USE | CONST)) - .then_some(CommentPlacement::Outer) -} - -/// The line -> block assist can be invoked from anywhere within a sequence of line comments. -/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will -/// be joined. -pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { - // The prefix identifies the kind of comment we're dealing with - let prefix = comment.prefix(); - let same_prefix = |c: &ast::Comment| c.prefix() == prefix; - - // These tokens are allowed to exist between comments - let skippable = |not: &SyntaxElement| { - not.clone() - .into_token() - .and_then(Whitespace::cast) - .map(|w| !w.spans_multiple_lines()) - .unwrap_or(false) - }; - - // Find all preceding comments (in reverse order) that have the same prefix - let prev_comments = comment - .syntax() - .siblings_with_tokens(Direction::Prev) - .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten() - .skip(1); // skip the first element so we don't duplicate it in next_comments - - let next_comments = comment - .syntax() - .siblings_with_tokens(Direction::Next) - .filter(|s| !skippable(s)) - .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) - .take_while(|opt_com| opt_com.is_some()) - .flatten(); - - let mut comments: Vec<_> = prev_comments.collect(); - comments.reverse(); - comments.extend(next_comments); - comments + .then_some(AttrKind::Outer) } -fn line_comments_text_range(comment: &ast::Comment) -> Option { +fn line_comments_text_range(comment: &ast::AnyComment) -> Option { let comments = relevant_line_comments(comment); let first = comments.first()?; let indentation = IndentLevel::from_token(first.syntax()); diff --git a/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/crates/ide-assists/src/handlers/desugar_doc_comment.rs index e6784a0c3b39..b0657ef43409 100644 --- a/crates/ide-assists/src/handlers/desugar_doc_comment.rs +++ b/crates/ide-assists/src/handlers/desugar_doc_comment.rs @@ -1,8 +1,8 @@ use either::Either; use itertools::Itertools; use syntax::{ - AstToken, TextRange, - ast::{self, CommentPlacement, Whitespace, edit::IndentLevel}, + AstNode, AstToken, TextRange, + ast::{self, AttrKind, Whitespace, edit::IndentLevel}, }; use crate::{ @@ -25,22 +25,22 @@ use crate::{ // comment"] // ``` pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { - let comment = ctx.find_token_at_offset::()?; + let comment = ctx.find_node_at_offset::()?; // Only allow doc comments - let placement = comment.kind().doc?; + let placement = comment.kind(); // Only allow comments which are alone on their line - if let Some(prev) = comment.syntax().prev_token() { + if let Some(prev) = comment.syntax().first_token().and_then(|it| it.prev_token()) { Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; } - let indentation = IndentLevel::from_token(comment.syntax()).to_string(); + let indentation = IndentLevel::from_node(comment.syntax()).to_string(); - let (target, comments) = match comment.kind().shape { + let (target, comments) = match comment.shape() { ast::CommentShape::Block => (comment.syntax().text_range(), Either::Left(comment)), ast::CommentShape::Line => { // Find all the comments we'll be desugaring - let comments = relevant_line_comments(&comment); + let comments = relevant_line_comments(&comment.token()); // Establish the target of our edit based on the comments we found ( @@ -59,26 +59,22 @@ pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_, '_> target, |edit| { let text = match comments { - Either::Left(comment) => { - let text = comment.text(); - text[comment.prefix().len()..(text.len() - "*/".len())] - .trim() - .lines() - .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) - .join("\n") - } - Either::Right(comments) => comments - .into_iter() - .map(|cm| line_comment_text(IndentLevel(0), cm)) - .collect::>() + Either::Left(comment) => comment + .text() + .trim() + .lines() + .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) .join("\n"), + Either::Right(comments) => { + comments.into_iter().map(|cm| line_comment_text(IndentLevel(0), cm)).join("\n") + } }; let hashes = "#".repeat(required_hashes(&text)); let prefix = match placement { - CommentPlacement::Inner => "#!", - CommentPlacement::Outer => "#", + AttrKind::Inner => "#!", + AttrKind::Outer => "#", }; let output = format!(r#"{prefix}[doc = r{hashes}"{text}"{hashes}]"#); diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index 46333ed72638..038d100e621e 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -76,7 +76,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - return None; } - if node.kind() == COMMENT { + if ast::AnyComment::can_cast(node.kind()) { cov_mark::hit!(extract_function_in_comment_is_not_applicable); return None; } diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs index 60a1c7ab44eb..9ff07d8dd6d2 100644 --- a/crates/ide-assists/src/handlers/extract_module.rs +++ b/crates/ide-assists/src/handlers/extract_module.rs @@ -469,7 +469,10 @@ impl Module { syntax.children_with_tokens().find(|nt| { !matches!( nt.kind(), - SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE + SyntaxKind::COMMENT + | SyntaxKind::DOC_COMMENT + | SyntaxKind::ATTR + | SyntaxKind::WHITESPACE ) }) }) diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index c1ac4f172489..8d0b9da112c3 100644 --- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -365,7 +365,7 @@ fn collect_variant_comments( for child in node.children_with_tokens() { match child.kind() { - COMMENT => { + COMMENT | DOC_COMMENT => { after_comment = true; to_insert.push(child.clone()); to_delete.push(child); diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index a5239e03fcdd..060a9dfab613 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -89,11 +89,15 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - } } else { match ctx.covering_element() { - NodeOrToken::Node(it) => it, + NodeOrToken::Node(it) if it.kind() == SyntaxKind::DOC_COMMENT => { + cov_mark::hit!(extract_var_in_comment_is_not_applicable); + return None; + } NodeOrToken::Token(it) if it.kind() == SyntaxKind::COMMENT => { cov_mark::hit!(extract_var_in_comment_is_not_applicable); return None; } + NodeOrToken::Node(it) => it, NodeOrToken::Token(it) => it.parent()?, } }; diff --git a/crates/ide-assists/src/handlers/fix_visibility.rs b/crates/ide-assists/src/handlers/fix_visibility.rs index d0f5c7c5003d..54784fa9903c 100644 --- a/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/crates/ide-assists/src/handlers/fix_visibility.rs @@ -91,6 +91,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_, ' it.kind(), syntax::SyntaxKind::WHITESPACE | syntax::SyntaxKind::COMMENT + | syntax::SyntaxKind::DOC_COMMENT | syntax::SyntaxKind::ATTR ) }) diff --git a/crates/ide-assists/src/handlers/generate_derive.rs b/crates/ide-assists/src/handlers/generate_derive.rs index ba6bb5c70bcb..b24ba4b9abcf 100644 --- a/crates/ide-assists/src/handlers/generate_derive.rs +++ b/crates/ide-assists/src/handlers/generate_derive.rs @@ -1,5 +1,5 @@ use syntax::{ - SyntaxKind::{ATTR, COMMENT, WHITESPACE}, + SyntaxKind::{ATTR, COMMENT, DOC_COMMENT, WHITESPACE}, T, ast::{self, AstNode, HasAttrs, edit::IndentLevel}, syntax_editor::{Element, Position}, @@ -55,7 +55,7 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> let after_attrs_and_comments = nominal .syntax() .children_with_tokens() - .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) + .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | DOC_COMMENT | ATTR)) .map_or(Position::first_child_of(nominal.syntax()), Position::before); editor.insert_all( diff --git a/crates/ide-assists/src/handlers/generate_documentation_template.rs b/crates/ide-assists/src/handlers/generate_documentation_template.rs index 89adda93866f..72971e55222c 100644 --- a/crates/ide-assists/src/handlers/generate_documentation_template.rs +++ b/crates/ide-assists/src/handlers/generate_documentation_template.rs @@ -3,9 +3,9 @@ use ide_db::assists::AssistId; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; use syntax::{ - AstNode, AstToken, Edition, + AstNode, Edition, algo::skip_whitespace_token, - ast::{self, HasDocComments, HasGenericArgs, HasName, edit::IndentLevel}, + ast::{self, HasAttrs, HasGenericArgs, HasName, edit::IndentLevel}, match_ast, }; @@ -96,11 +96,13 @@ pub(crate) fn generate_documentation_template( // pub fn add(a: i32, b: i32) -> i32 { a + b } // ``` pub(crate) fn generate_doc_example(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { - let tok: ast::Comment = ctx.find_token_at_offset()?; - let node = tok.syntax().parent()?; - let last_doc_token = - ast::AnyHasDocComments::cast(node.clone())?.doc_comments().last()?.syntax().clone(); - let next_token = skip_whitespace_token(last_doc_token.next_token()?, syntax::Direction::Next)?; + let doc_at_cursor: ast::DocComment = ctx.find_node_at_offset()?; + let node = doc_at_cursor.syntax().parent()?; + let last_doc_comment = ast::AnyHasAttrs::cast(node.clone())?.doc_comments().last()?; + let next_token = skip_whitespace_token( + last_doc_comment.syntax().last_token()?.next_token()?, + syntax::Direction::Next, + )?; let example = match_ast! { match node { diff --git a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 354447cf3356..79254044a960 100644 --- a/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -1,9 +1,9 @@ use crate::assist_context::{AssistContext, Assists}; use ide_db::{assists::AssistId, defs::Definition, search::SearchScope}; use syntax::{ - AstNode, AstToken, SyntaxKind, T, + AstNode, SyntaxKind, T, ast::{ - self, HasDocComments, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, + self, HasAttrs, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, syntax_factory::SyntaxFactory, }, syntax_editor::{Position, SyntaxEditor}, @@ -226,7 +226,7 @@ fn remove_items_visibility(editor: &SyntaxEditor, item: &ast::AssocItem) { fn remove_doc_comments(editor: &SyntaxEditor, item: &ast::AssocItem) { for doc in item.doc_comments() { - if let Some(next) = doc.syntax().next_token() + if let Some(next) = doc.syntax().last_token().and_then(|it| it.next_token()) && next.kind() == SyntaxKind::WHITESPACE { editor.delete(next); diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 0f12ec79ee6e..e5e735faf6f9 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -57,12 +57,7 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option TextSize { node.children_with_tokens() - .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) + .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | DOC_COMMENT | ATTR)) .map(|it| it.text_range().start()) .unwrap_or_else(|| node.text_range().start()) } diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index ee6788b16e45..1523d0ad43b8 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -112,7 +112,10 @@ fn complete_trait_impl_name( .find(|child| { !matches!( child.kind(), - SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR + SyntaxKind::COMMENT + | SyntaxKind::DOC_COMMENT + | SyntaxKind::WHITESPACE + | SyntaxKind::ATTR ) }) .unwrap_or_else(|| SyntaxElement::Node(real_file_item.clone())); diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs index 023538976308..bf7aa771c2af 100644 --- a/crates/ide-db/src/imports/insert_use.rs +++ b/crates/ide-db/src/imports/insert_use.rs @@ -573,5 +573,5 @@ fn insert_use_with_editor_( } fn is_inner_attribute(node: SyntaxNode) -> bool { - ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) + ast::AnyAttr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) } diff --git a/crates/ide-ssr/src/from_comment.rs b/crates/ide-ssr/src/from_comment.rs index 83b8c3dc81ea..c3089262ecc1 100644 --- a/crates/ide-ssr/src/from_comment.rs +++ b/crates/ide-ssr/src/from_comment.rs @@ -22,7 +22,7 @@ pub fn ssr_from_comment( let file = file_id.parse(db); file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast) }?; - let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap(); + let comment_text_without_prefix = comment.text_without_markers(); let ssr_rule = comment_text_without_prefix.parse().ok()?; let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 2f29fc31f8a8..c152d7e9cc96 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -277,19 +277,28 @@ pub(crate) struct DocCommentToken { } pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option { - (match_ast! { - match doc_token { - ast::Comment(comment) => TextSize::try_from(comment.prefix().len()).ok(), - ast::String(string) => { - doc_token.parent_ancestors().find_map(ast::Attr::cast).filter(|attr| attr.simple_name().as_deref() == Some("doc"))?; - if doc_token.parent_ancestors().find_map(ast::MacroCall::cast).filter(|mac| mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()) == Some("include_str")).is_some() { - return None; - } - string.open_quote_text_range().map(|it| it.len()) - }, - _ => None, + let prefix_len = if matches!(doc_token.kind(), INNER_DOC_COMMENT | OUTER_DOC_COMMENT) { + ast::DocComment::PREFIX_LEN + } else { + let string = ast::String::cast(doc_token.clone())?; + doc_token + .parent_ancestors() + .find_map(ast::Attr::cast) + .filter(|attr| attr.simple_name().as_deref() == Some("doc"))?; + if doc_token + .parent_ancestors() + .find_map(ast::MacroCall::cast) + .filter(|mac| { + mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()) + == Some("include_str") + }) + .is_some() + { + return None; } - }).map(|prefix_len| DocCommentToken { prefix_len, doc_token: doc_token.clone() }) + string.open_quote_text_range()?.len() + }; + Some(DocCommentToken { prefix_len, doc_token: doc_token.clone() }) } impl DocCommentToken { @@ -308,8 +317,8 @@ impl DocCommentToken { sema.descend_into_macros(doc_token).into_iter().find_map(|t| { let (node, descended_prefix_len, is_inner) = match_ast!{ match t { - ast::Comment(comment) => { - (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?, comment.is_inner()) + ast::AnyComment(comment) => { + (t.parent()?.parent()?, TextSize::try_from(comment.prefix().len()).ok()?, comment.is_inner()) }, ast::String(string) => { let attr = t.parent_ancestors().find_map(ast::Attr::cast)?; diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs index 720528d0b52f..f5755da13bdb 100644 --- a/crates/ide/src/doc_links/tests.rs +++ b/crates/ide/src/doc_links/tests.rs @@ -449,7 +449,7 @@ fn doc_links_items_simple() { check_doc_links( r#" //- /main.rs crate:main deps:krate -/// [`krate`] +//! [`krate`] //! [`Trait`] //! [`function`] //! [`CONST`] diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index 2926384c4078..079f8426cf5a 100644 --- a/crates/ide/src/extend_selection.rs +++ b/crates/ide/src/extend_selection.rs @@ -35,7 +35,8 @@ fn try_extend_selection( ) -> Option { let range = frange.range; - let string_kinds = [COMMENT, STRING, BYTE_STRING, C_STRING]; + let string_kinds = + [COMMENT, INNER_DOC_COMMENT, OUTER_DOC_COMMENT, STRING, BYTE_STRING, C_STRING]; let list_kinds = [ RECORD_PAT_FIELD_LIST, MATCH_ARM_LIST, @@ -81,7 +82,7 @@ fn try_extend_selection( if token.text_range() != range { return Some(token.text_range()); } - if let Some(comment) = ast::Comment::cast(token.clone()) + if let Some(comment) = ast::AnyComment::cast(token.clone()) && let Some(range) = extend_comments(comment) { return Some(range); @@ -292,7 +293,7 @@ fn extend_list_item(node: &SyntaxNode) -> Option { None } -fn extend_comments(comment: ast::Comment) -> Option { +fn extend_comments(comment: ast::AnyComment) -> Option { let prev = adj_comments(&comment, Direction::Prev); let next = adj_comments(&comment, Direction::Next); if prev != next { @@ -302,14 +303,14 @@ fn extend_comments(comment: ast::Comment) -> Option { } } -fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment { +fn adj_comments(comment: &ast::AnyComment, dir: Direction) -> ast::AnyComment { let mut res = comment.clone(); for element in comment.syntax().siblings_with_tokens(dir) { let token = match element.as_token() { None => break, Some(token) => token, }; - if let Some(c) = ast::Comment::cast(token.clone()) { + if let Some(c) = ast::AnyComment::cast(token.clone()) { res = c } else if token.kind() != WHITESPACE || token.text().contains("\n\n") { break; diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs index ae006d152a38..4cac61d9f6ce 100644 --- a/crates/ide/src/folding_ranges.rs +++ b/crates/ide/src/folding_ranges.rs @@ -10,8 +10,8 @@ use syntax::{ use std::hash::Hash; -const REGION_START: &str = "// region:"; -const REGION_END: &str = "// endregion"; +const REGION_START: &str = "region:"; +const REGION_END: &str = "endregion"; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldKind { @@ -109,7 +109,7 @@ pub(crate) fn folding_ranges(file: &SourceFile, add_collapsed_text: bool) -> Vec match element { NodeOrToken::Token(token) => { // Fold groups of comments - if let Some(comment) = ast::Comment::cast(token) { + if let Some(comment) = ast::AnyComment::cast(token) { if visited_comments.contains(&comment) { continue; } @@ -200,7 +200,7 @@ fn fold_kind( } match element.kind() { - COMMENT => Some(FoldKind::Comment), + COMMENT | INNER_DOC_COMMENT | OUTER_DOC_COMMENT => Some(FoldKind::Comment), ARG_LIST | PARAM_LIST | GENERIC_ARG_LIST | GENERIC_PARAM_LIST => Some(FoldKind::ArgList), ARRAY_EXPR => Some(FoldKind::Array), RET_TYPE => Some(FoldKind::ReturnType), @@ -391,8 +391,8 @@ fn eq_visibility(vis0: Option, vis1: Option) - } fn contiguous_range_for_comment( - first: ast::Comment, - visited: &mut FxHashSet, + first: ast::AnyComment, + visited: &mut FxHashSet, ) -> Option { visited.insert(first.clone()); @@ -403,33 +403,29 @@ fn contiguous_range_for_comment( } let mut last = first.clone(); - for element in first.syntax().siblings_with_tokens(Direction::Next) { - match element { - NodeOrToken::Token(token) => { - if let Some(ws) = ast::Whitespace::cast(token.clone()) - && !ws.spans_multiple_lines() - { - // Ignore whitespace without blank lines - continue; - } - if let Some(c) = ast::Comment::cast(token) - && c.kind() == group_kind - { - let text = c.text().trim_start(); - // regions are not real comments - if !(text.starts_with(REGION_START) || text.starts_with(REGION_END)) { - visited.insert(c.clone()); - last = c; - continue; - } - } - // The comment group ends because either: - // * An element of a different kind was reached - // * A comment of a different flavor was reached - break; + let next_comments = std::iter::successors(Some(first.syntax().clone()), |it| it.next_token()); + for token in next_comments { + if let Some(ws) = ast::Whitespace::cast(token.clone()) + && !ws.spans_multiple_lines() + { + // Ignore whitespace without blank lines + continue; + } + if let Some(c) = ast::AnyComment::cast(token) + && c.kind() == group_kind + { + let text = c.text().trim_start(); + // regions are not real comments + if !(text.starts_with(REGION_START) || text.starts_with(REGION_END)) { + visited.insert(c.clone()); + last = c; + continue; } - NodeOrToken::Node(_) => break, - }; + } + // The comment group ends because either: + // * An element of a different kind was reached + // * A comment of a different flavor was reached + break; } if first != last { diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 6947f00e2187..033de7dcc20b 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -71,7 +71,9 @@ pub(crate) fn goto_definition( | T![super] | T![crate] | T![Self] - | COMMENT => 4, + | COMMENT + | INNER_DOC_COMMENT + | OUTER_DOC_COMMENT => 4, // index and prefix ops T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, kind if kind.is_keyword(edition) => 2, diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 3d73b5b0f24d..92473de4e634 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -182,8 +182,12 @@ fn hover_offset( _ => 1, })?; - if let Some(doc_comment) = token_as_doc_comment(&original_token) { + if ast::Comment::can_cast(original_token.kind()) { cov_mark::hit!(no_highlight_on_comment_hover); + return None; + } + + if let Some(doc_comment) = token_as_doc_comment(&original_token) { return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| { let res = hover_for_definition( sema, diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs index 4b06f83971b2..e008a7b94066 100644 --- a/crates/ide/src/inlay_hints/chaining.rs +++ b/crates/ide/src/inlay_hints/chaining.rs @@ -34,7 +34,9 @@ pub(super) fn hints( .filter_map(NodeOrToken::into_token) .filter(|t| match t.kind() { SyntaxKind::WHITESPACE if !t.text().contains('\n') => false, - SyntaxKind::COMMENT => false, + SyntaxKind::COMMENT | SyntaxKind::OUTER_DOC_COMMENT | SyntaxKind::INNER_DOC_COMMENT => { + false + } _ => true, }); diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index a946559c3545..4c648d3cb9a0 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs @@ -186,9 +186,10 @@ fn remove_newline( } } + // We can't use `prev` and `next`, since `DOC_COMMENT` has only one token, so `token` has no siblings. if let (Some(_), Some(next)) = ( - prev.as_token().cloned().and_then(ast::Comment::cast), - next.as_token().cloned().and_then(ast::Comment::cast), + token.prev_token().and_then(ast::AnyComment::cast), + token.next_token().and_then(ast::AnyComment::cast), ) { // Removes: newline (incl. surrounding whitespace), start of the next comment edit.delete(TextRange::new( @@ -674,14 +675,14 @@ fn foo() { fn test_join_lines_doc_comments() { check_join_lines( r" +/// Hello$0 +/// world! fn foo() { - /// Hello$0 - /// world! } ", r" +/// Hello$0 world! fn foo() { - /// Hello$0 world! } ", ); diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs index c92f2bbace84..dd1c7379e790 100644 --- a/crates/ide/src/moniker.rs +++ b/crates/ide/src/moniker.rs @@ -154,7 +154,9 @@ pub(crate) fn moniker( | T![super] | T![crate] | T![Self] - | COMMENT => 2, + | COMMENT + | INNER_DOC_COMMENT + | OUTER_DOC_COMMENT => 2, kind if kind.is_trivia() => 0, _ => 1, })?; diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 9e8d772cb3d1..7a01eb9054bf 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -12,7 +12,7 @@ use ide_db::{ famous_defs::FamousDefs, ra_fixture::RaFixtureConfig, }; -use syntax::{AstNode, AstToken, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, ast}; +use syntax::{AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange}; use crate::navigation_target::UpmappingResult; use crate::{ @@ -349,15 +349,20 @@ fn definition_range_excluding_trivia( } fn is_leading_trivia_excluding_docs(token: &SyntaxToken) -> bool { - match token.kind() { - SyntaxKind::WHITESPACE => true, - SyntaxKind::COMMENT => ast::Comment::cast(token.clone()).is_none_or(|it| !it.is_outer()), - _ => false, - } + matches!( + token.kind(), + SyntaxKind::WHITESPACE | SyntaxKind::COMMENT | SyntaxKind::INNER_DOC_COMMENT + ) } fn is_trailing_trivia(token: &SyntaxToken) -> bool { - matches!(token.kind(), SyntaxKind::WHITESPACE | SyntaxKind::COMMENT) + matches!( + token.kind(), + SyntaxKind::WHITESPACE + | SyntaxKind::COMMENT + | SyntaxKind::INNER_DOC_COMMENT + | SyntaxKind::OUTER_DOC_COMMENT + ) } #[cfg(test)] diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 9fd3f005ec70..c29f18010163 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -534,7 +534,7 @@ fn descend_token( sema: &Semantics<'_, RootDatabase>, token: InRealFile, ) -> InFile> { - if token.value.kind() == COMMENT { + if ast::AnyComment::can_cast(token.value.kind()) { return token.map(NodeOrToken::Token).into(); } let ranker = Ranker::from_token(&token.value); diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 92daacd6d314..07b191231807 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -11,7 +11,7 @@ use ide_db::{ }; use span::Edition; use syntax::{ - AstNode, AstPtr, AstToken, NodeOrToken, + AstNode, AstPtr, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxNodePtr, SyntaxToken, T, ast, match_ast, }; @@ -28,15 +28,9 @@ pub(super) fn token( is_unsafe_node: &impl Fn(AstPtr>) -> bool, in_tt: bool, ) -> Option { - if let Some(comment) = ast::Comment::cast(token.clone()) { - let h = HlTag::Comment; - return Some(match comment.kind().doc { - Some(_) => h | HlMod::Documentation, - None => h.into(), - }); - } - let h = match token.kind() { + COMMENT => HlTag::Comment.into(), + INNER_DOC_COMMENT | OUTER_DOC_COMMENT => HlTag::Comment | HlMod::Documentation, STRING | BYTE_STRING | C_STRING => HlTag::StringLiteral.into(), INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), BYTE => HlTag::ByteLiteral.into(), diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 4e3c49141875..c4f53337b391 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs @@ -55,7 +55,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option Option Option { @@ -159,7 +159,7 @@ fn brace_contents_on_same_line(l_curly: &SyntaxToken) -> Option<(SyntaxToken, St } } -fn followed_by_comment(comment: &ast::Comment) -> bool { +fn followed_by_comment(comment: &ast::AnyComment) -> bool { let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { Some(it) => it, None => return false, @@ -167,7 +167,7 @@ fn followed_by_comment(comment: &ast::Comment) -> bool { if ws.spans_multiple_lines() { return false; } - ws.syntax().next_token().and_then(ast::Comment::cast).is_some() + ws.syntax().next_token().and_then(ast::AnyComment::cast).is_some() } fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs index 2eeaa25257db..acce7afb54e9 100644 --- a/crates/parser/src/grammar/attributes.rs +++ b/crates/parser/src/grammar/attributes.rs @@ -1,23 +1,29 @@ use super::*; -pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]); +pub(super) const OUTER_ATTR_FIRST: TokenSet = TokenSet::new(&[T![#], OUTER_DOC_COMMENT]); pub(super) fn inner_attrs(p: &mut Parser<'_>) { - while p.at(T![#]) && p.nth(1) == T![!] { + while p.at(INNER_DOC_COMMENT) || (p.at(T![#]) && p.nth_at(1, T![!])) { attr(p, true); } } pub(super) fn outer_attrs(p: &mut Parser<'_>) { - while p.at(T![#]) { + while p.at_ts(OUTER_ATTR_FIRST) { attr(p, false); } } fn attr(p: &mut Parser<'_>, inner: bool) { - assert!(p.at(T![#])); + if (inner && p.at(INNER_DOC_COMMENT)) || (!inner && p.at(OUTER_DOC_COMMENT)) { + let m = p.start(); + p.bump_any(); + m.complete(p, DOC_COMMENT); + return; + } let attr = p.start(); + p.bump(T![#]); if inner { diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index d419817e5cd7..661ab0562e70 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -1,4 +1,4 @@ -use crate::grammar::attributes::ATTRIBUTE_FIRST; +use crate::grammar::attributes::OUTER_ATTR_FIRST; use super::*; @@ -22,7 +22,7 @@ pub(super) fn generic_param_list(p: &mut Parser<'_>) { T![>], T![,], || "expected generic parameter".into(), - GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), + GENERIC_PARAM_FIRST.union(OUTER_ATTR_FIRST), |p| { // test generic_param_attribute // fn foo<#[lt_attr] 'a, #[t_attr] T>() {} diff --git a/crates/parser/src/grammar/items/adt.rs b/crates/parser/src/grammar/items/adt.rs index 33e19f5725b3..ec4e59539878 100644 --- a/crates/parser/src/grammar/items/adt.rs +++ b/crates/parser/src/grammar/items/adt.rs @@ -1,4 +1,4 @@ -use crate::grammar::attributes::ATTRIBUTE_FIRST; +use crate::grammar::attributes::OUTER_ATTR_FIRST; use super::*; @@ -180,7 +180,7 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) { } const TUPLE_FIELD_FIRST: TokenSet = - types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST); + types::TYPE_FIRST.union(OUTER_ATTR_FIRST).union(VISIBILITY_FIRST); // test_err tuple_field_list_recovery // struct S(struct S; diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs index 51ffcd070694..4f1c744c1d0f 100644 --- a/crates/parser/src/grammar/params.rs +++ b/crates/parser/src/grammar/params.rs @@ -1,4 +1,4 @@ -use crate::grammar::attributes::ATTRIBUTE_FIRST; +use crate::grammar::attributes::OUTER_ATTR_FIRST; use super::*; @@ -64,7 +64,7 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { } }; - if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { + if !p.at_ts(PARAM_FIRST.union(OUTER_ATTR_FIRST)) { p.error("expected value parameter"); m.abandon(p); if p.eat(T![,]) { @@ -74,7 +74,7 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { } param(p, m, flavor); if !p.eat(T![,]) { - if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { + if p.at_ts(PARAM_FIRST.union(OUTER_ATTR_FIRST)) { p.error("expected `,`"); } else { break; diff --git a/crates/parser/src/lexed_str.rs b/crates/parser/src/lexed_str.rs index d7eec6cde8c0..ec994b731bf9 100644 --- a/crates/parser/src/lexed_str.rs +++ b/crates/parser/src/lexed_str.rs @@ -195,6 +195,14 @@ impl<'a> Converter<'a> { } } + fn comment_kind(doc_style: Option) -> SyntaxKind { + match doc_style { + Some(rustc_lexer::DocStyle::Outer) => OUTER_DOC_COMMENT, + Some(rustc_lexer::DocStyle::Inner) => INNER_DOC_COMMENT, + None => COMMENT, + } + } + fn extend_token(&mut self, kind: &rustc_lexer::TokenKind, mut token_text: &str) { // A note on an intended tradeoff: // We drop some useful information here (see patterns with double dots `..`) @@ -204,14 +212,14 @@ impl<'a> Converter<'a> { let syntax_kind = { match kind { - rustc_lexer::TokenKind::LineComment { doc_style: _ } => COMMENT, - rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated } => { + rustc_lexer::TokenKind::LineComment { doc_style } => Self::comment_kind(*doc_style), + rustc_lexer::TokenKind::BlockComment { doc_style, terminated } => { if !terminated { errors.push( "Missing trailing `*/` symbols to terminate the block comment".into(), ); } - COMMENT + Self::comment_kind(*doc_style) } rustc_lexer::TokenKind::Frontmatter { diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 5604da5026e9..81a3b423e443 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -162,8 +162,10 @@ pub enum SyntaxKind { ERROR, FRONTMATTER, IDENT, + INNER_DOC_COMMENT, LIFETIME_IDENT, NEWLINE, + OUTER_DOC_COMMENT, SHEBANG, WHITESPACE, ABI, @@ -204,6 +206,7 @@ pub enum SyntaxKind { CONST_PARAM, CONTINUE_EXPR, DEREF_PAT, + DOC_COMMENT, DYN_TRAIT_TYPE, ENUM, EXPR_STMT, @@ -391,6 +394,7 @@ impl SyntaxKind { | CONST_PARAM | CONTINUE_EXPR | DEREF_PAT + | DOC_COMMENT | DYN_TRAIT_TYPE | ENUM | EXPR_STMT @@ -526,8 +530,10 @@ impl SyntaxKind { | ERROR | FRONTMATTER | IDENT + | INNER_DOC_COMMENT | LIFETIME_IDENT | NEWLINE + | OUTER_DOC_COMMENT | SHEBANG | WHITESPACE => panic!("no text for these `SyntaxKind`s"), DOLLAR => "$", @@ -1226,6 +1232,8 @@ macro_rules ! T_ { [string] => { $ crate :: SyntaxKind :: STRING }; [shebang] => { $ crate :: SyntaxKind :: SHEBANG }; [frontmatter] => { $ crate :: SyntaxKind :: FRONTMATTER }; + [inner_doc_comment] => { $ crate :: SyntaxKind :: INNER_DOC_COMMENT }; + [outer_doc_comment] => { $ crate :: SyntaxKind :: OUTER_DOC_COMMENT }; } impl ::core::marker::Copy for SyntaxKind {} diff --git a/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast b/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast index e9b74ee7f827..54341df49c86 100644 --- a/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast +++ b/crates/parser/test_data/lexer/err/unclosed_nested_block_comment_partially.rast @@ -1 +1 @@ -COMMENT "/** /*! /* comment */ */\n" error: Missing trailing `*/` symbols to terminate the block comment +OUTER_DOC_COMMENT "/** /*! /* comment */ */\n" error: Missing trailing `*/` symbols to terminate the block comment diff --git a/crates/parser/test_data/lexer/ok/single_line_comments.rast b/crates/parser/test_data/lexer/ok/single_line_comments.rast index c4e531b449f7..17ef356156f7 100644 --- a/crates/parser/test_data/lexer/ok/single_line_comments.rast +++ b/crates/parser/test_data/lexer/ok/single_line_comments.rast @@ -1,21 +1,21 @@ SHEBANG "#!/usr/bin/env bash\n" COMMENT "// hello" WHITESPACE "\n" -COMMENT "//! World" +INNER_DOC_COMMENT "//! World" WHITESPACE "\n" -COMMENT "//!! Inner line doc" +INNER_DOC_COMMENT "//!! Inner line doc" WHITESPACE "\n" -COMMENT "/// Outer line doc" +OUTER_DOC_COMMENT "/// Outer line doc" WHITESPACE "\n" COMMENT "//// Just a comment" WHITESPACE "\n\n" COMMENT "//" WHITESPACE "\n" -COMMENT "//!" +INNER_DOC_COMMENT "//!" WHITESPACE "\n" -COMMENT "//!!" +INNER_DOC_COMMENT "//!!" WHITESPACE "\n" -COMMENT "///" +OUTER_DOC_COMMENT "///" WHITESPACE "\n" COMMENT "////" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/ok/0035_weird_exprs.rast b/crates/parser/test_data/parser/ok/0035_weird_exprs.rast index 15ce6c70bea7..b96711c613ca 100644 --- a/crates/parser/test_data/parser/ok/0035_weird_exprs.rast +++ b/crates/parser/test_data/parser/ok/0035_weird_exprs.rast @@ -1,11 +1,15 @@ SOURCE_FILE - COMMENT "//! Adapted from a `rustc` test, which can be found at " + DOC_COMMENT + INNER_DOC_COMMENT "//! Adapted from a `rustc` test, which can be found at " WHITESPACE "\n" - COMMENT "//! https://github.com/rust-lang/rust/blob/6d34ec18c7d7e574553f6347ecf08e1e1c45c13d/src/test/run-pass/weird-exprs.rs." + DOC_COMMENT + INNER_DOC_COMMENT "//! https://github.com/rust-lang/rust/blob/6d34ec18c7d7e574553f6347ecf08e1e1c45c13d/src/test/run-pass/weird-exprs.rs." WHITESPACE "\n" - COMMENT "//! " + DOC_COMMENT + INNER_DOC_COMMENT "//! " WHITESPACE "\n" - COMMENT "//! Reported to rust-analyzer in https://github.com/rust-lang/rust-analyzer/issues/290" + DOC_COMMENT + INNER_DOC_COMMENT "//! Reported to rust-analyzer in https://github.com/rust-lang/rust-analyzer/issues/290" WHITESPACE "\n\n" ATTR POUND "#" diff --git a/crates/parser/test_data/parser/ok/0037_mod.rast b/crates/parser/test_data/parser/ok/0037_mod.rast index b4a3fc6292e9..59c33eba25c1 100644 --- a/crates/parser/test_data/parser/ok/0037_mod.rast +++ b/crates/parser/test_data/parser/ok/0037_mod.rast @@ -1,7 +1,8 @@ SOURCE_FILE COMMENT "// https://github.com/rust-lang/rust-analyzer/issues/357" WHITESPACE "\n\n" - COMMENT "//! docs" + DOC_COMMENT + INNER_DOC_COMMENT "//! docs" WHITESPACE "\n" MODULE COMMENT "// non-docs" diff --git a/crates/parser/test_data/parser/ok/0045_block_attrs.rast b/crates/parser/test_data/parser/ok/0045_block_attrs.rast index f26bb85df292..2dc22d12370e 100644 --- a/crates/parser/test_data/parser/ok/0045_block_attrs.rast +++ b/crates/parser/test_data/parser/ok/0045_block_attrs.rast @@ -27,7 +27,8 @@ SOURCE_FILE R_PAREN ")" R_BRACK "]" WHITESPACE "\n " - COMMENT "//! As are ModuleDoc style comments" + DOC_COMMENT + INNER_DOC_COMMENT "//! As are ModuleDoc style comments" WHITESPACE "\n " EXPR_STMT BLOCK_EXPR @@ -64,7 +65,8 @@ SOURCE_FILE R_PAREN ")" R_BRACK "]" WHITESPACE "\n " - COMMENT "//! As are ModuleDoc style comments" + DOC_COMMENT + INNER_DOC_COMMENT "//! As are ModuleDoc style comments" WHITESPACE "\n " R_CURLY "}" SEMICOLON ";" @@ -88,7 +90,8 @@ SOURCE_FILE R_PAREN ")" R_BRACK "]" WHITESPACE "\n " - COMMENT "//! As are ModuleDoc style comments" + DOC_COMMENT + INNER_DOC_COMMENT "//! As are ModuleDoc style comments" WHITESPACE "\n " R_CURLY "}" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast b/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast index 3d33eb4ff73c..8338f1416c04 100644 --- a/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast +++ b/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast @@ -8,7 +8,8 @@ SOURCE_FILE EXTERN_ITEM_LIST L_CURLY "{" WHITESPACE "\n " - COMMENT "//! This is a doc comment" + DOC_COMMENT + INNER_DOC_COMMENT "//! This is a doc comment" WHITESPACE "\n " ATTR POUND "#" diff --git a/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast b/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast index c300b7af5058..c9eeda6582af 100644 --- a/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast +++ b/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast @@ -1,6 +1,7 @@ SOURCE_FILE MACRO_RULES - COMMENT "/// Some docs" + DOC_COMMENT + OUTER_DOC_COMMENT "/// Some docs" WHITESPACE "\n" ATTR POUND "#" diff --git a/crates/parser/test_data/parser/ok/0065_comment_newline.rast b/crates/parser/test_data/parser/ok/0065_comment_newline.rast index 3ffcb48f5e42..e95ed7d38c90 100644 --- a/crates/parser/test_data/parser/ok/0065_comment_newline.rast +++ b/crates/parser/test_data/parser/ok/0065_comment_newline.rast @@ -1,6 +1,7 @@ SOURCE_FILE FN - COMMENT "/// Example" + DOC_COMMENT + OUTER_DOC_COMMENT "/// Example" WHITESPACE "\n\n" FN_KW "fn" WHITESPACE " " diff --git a/crates/syntax-bridge/src/lib.rs b/crates/syntax-bridge/src/lib.rs index 181f9a14e764..3e6e5f804e26 100644 --- a/crates/syntax-bridge/src/lib.rs +++ b/crates/syntax-bridge/src/lib.rs @@ -12,10 +12,10 @@ use rustc_hash::{FxHashMap, FxHashSet}; use span::{Edition, Span, SpanAnchor, SpanMap, SyntaxContext}; use stdx::{format_to, never}; use syntax::{ - AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement, + Parse, PreorderWithTokens, SmolStr, SyntaxElement, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, SyntaxTreeBuilder, T, TextRange, TextSize, WalkEvent, - ast::{self, make::tokens::doc_comment}, + ast::make::tokens::doc_comment, format_smolstr, }; use tt::{Punct, buffer::Cursor, token_to_literal}; @@ -250,9 +250,9 @@ where Some(leaf) => leaf.clone(), None => match token.kind(conv) { // Desugar doc comments into doc attributes - COMMENT => { + kind @ (INNER_DOC_COMMENT | OUTER_DOC_COMMENT) => { let span = conv.span_for(abs_range); - conv.convert_doc_comment(&token, span, &mut builder); + conv.convert_doc_comment(&token, kind == INNER_DOC_COMMENT, span, &mut builder); continue; } kind if kind.is_punct() && kind != UNDERSCORE => { @@ -419,13 +419,11 @@ pub fn desugar_doc_comment_text(text: &str, mode: DocCommentDesugarMode) -> (Sym fn convert_doc_comment( token: &syntax::SyntaxToken, + is_inner: bool, span: Span, mode: DocCommentDesugarMode, builder: &mut tt::TopSubtreeBuilder, ) { - let Some(comment) = ast::Comment::cast(token.clone()) else { return }; - let Some(doc) = comment.kind().doc else { return }; - let mk_ident = |s: &str| { tt::Leaf::from(tt::Ident { sym: Symbol::intern(s), span, is_raw: tt::IdentIsRaw::No }) }; @@ -433,14 +431,11 @@ fn convert_doc_comment( let mk_punct = |c: char| tt::Leaf::from(tt::Punct { char: c, spacing: tt::Spacing::Alone, span }); - let mk_doc_literal = |comment: &ast::Comment| { - let prefix_len = comment.prefix().len(); - let mut text = &comment.text()[prefix_len..]; + let mk_doc_literal = |token: &SyntaxToken| { + let text = token.text(); + let from_end = if text.starts_with("/*") && text.ends_with("*/") { 2 } else { 0 }; + let text = &text[3..text.len() - from_end]; - // Remove ending "*/" - if comment.kind().shape == ast::CommentShape::Block { - text = &text[0..text.len() - 2]; - } let (text, kind) = desugar_doc_comment_text(text, mode); let lit = tt::Literal { text_and_suffix: text, span, kind, suffix_len: 0 }; @@ -448,11 +443,11 @@ fn convert_doc_comment( }; // Make `doc="\" Comments\"" - let meta_tkns = [mk_ident("doc"), mk_punct('='), mk_doc_literal(&comment)]; + let meta_tkns = [mk_ident("doc"), mk_punct('='), mk_doc_literal(token)]; // Make `#![]` builder.push(mk_punct('#')); - if let ast::CommentPlacement::Inner = doc { + if is_inner { builder.push(mk_punct('!')); } builder.open(tt::DelimiterKind::Bracket, span); @@ -494,6 +489,7 @@ trait TokenConverter: Sized { fn convert_doc_comment( &self, token: &Self::Token, + is_inner: bool, span: Span, builder: &mut tt::TopSubtreeBuilder, ); @@ -538,9 +534,15 @@ impl SrcToken> for usize { impl TokenConverter for RawConverter<'_> { type Token = usize; - fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) { + fn convert_doc_comment( + &self, + &token: &usize, + is_inner: bool, + span: Span, + builder: &mut tt::TopSubtreeBuilder, + ) { let text = self.lexed.text(token); - convert_doc_comment(&doc_comment(text), span, self.mode, builder); + convert_doc_comment(&doc_comment(text), is_inner, span, self.mode, builder); } fn bump(&mut self) -> Option<(Self::Token, TextRange)> { @@ -574,9 +576,15 @@ impl TokenConverter for RawConverter<'_> { impl TokenConverter for StaticRawConverter<'_> { type Token = usize; - fn convert_doc_comment(&self, &token: &usize, span: Span, builder: &mut tt::TopSubtreeBuilder) { + fn convert_doc_comment( + &self, + &token: &usize, + is_inner: bool, + span: Span, + builder: &mut tt::TopSubtreeBuilder, + ) { let text = self.lexed.text(token); - convert_doc_comment(&doc_comment(text), span, self.mode, builder); + convert_doc_comment(&doc_comment(text), is_inner, span, self.mode, builder); } fn bump(&mut self) -> Option<(Self::Token, TextRange)> { @@ -752,10 +760,11 @@ where fn convert_doc_comment( &self, token: &Self::Token, + is_inner: bool, span: Span, builder: &mut tt::TopSubtreeBuilder, ) { - convert_doc_comment(token.token(), span, self.mode, builder); + convert_doc_comment(token.token(), is_inner, span, self.mode, builder); } fn bump(&mut self) -> Option<(Self::Token, TextRange)> { diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 7a24b32c87cf..91387041a111 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -87,15 +87,15 @@ GenericParam = | TypeParam TypeParam = - Attr* Name (':' TypeBoundList?)? + AnyAttr* Name (':' TypeBoundList?)? ('=' default_type:Type)? ConstParam = - Attr* 'const' Name ':' Type + AnyAttr* 'const' Name ':' Type ('=' default_val:ConstArg)? LifetimeParam = - Attr* Lifetime (':' TypeBoundList?)? + AnyAttr* Lifetime (':' TypeBoundList?)? WhereClause = 'where' predicates:(WherePred (',' WherePred)* ','?) @@ -109,7 +109,7 @@ WherePred = //*************************// MacroCall = - Attr* Path '!' TokenTree ';'? + AnyAttr* Path '!' TokenTree ';'? TokenTree = '(' ')' @@ -123,9 +123,15 @@ MacroStmts = statements:Stmt* Expr? +AnyAttr = + Attr | DocComment + Attr = '#' '!'? '[' Meta ']' +DocComment = + '#inner_doc_comment' | '#outer_doc_comment' + CfgAttrMeta = 'cfg_attr' '(' CfgPredicate ',' (Meta (',' Meta)* ','?) ')' @@ -169,7 +175,7 @@ TokenTreeMeta = SourceFile = '#shebang'? '#frontmatter'? - Attr* + AnyAttr* Item* Item = @@ -192,32 +198,32 @@ Item = | AsmExpr MacroRules = - Attr* Visibility? + AnyAttr* Visibility? 'macro_rules' '!' Name TokenTree MacroDef = - Attr* Visibility? + AnyAttr* Visibility? 'macro' Name args:TokenTree? body:TokenTree Module = - Attr* Visibility? + AnyAttr* Visibility? 'mod' Name (ItemList | ';') ItemList = - '{' Attr* Item* '}' + '{' AnyAttr* Item* '}' ExternCrate = - Attr* Visibility? + AnyAttr* Visibility? 'extern' 'crate' NameRef Rename? ';' Rename = 'as' (Name | '_') Use = - Attr* Visibility? + AnyAttr* Visibility? 'use' UseTree ';' UseTree = @@ -228,7 +234,7 @@ UseTreeList = '{' (UseTree (',' UseTree)* ','?)? '}' Fn = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'const'? 'async'? 'gen'? 'unsafe'? 'safe'? Abi? 'fn' Name GenericParamList? ParamList RetType? WhereClause? (body:BlockExpr | ';') @@ -244,13 +250,13 @@ ParamList = | '|' (Param (',' Param)* ','?)? '|' SelfParam = - Attr* ( + AnyAttr* ( ('&' Lifetime?)? 'mut'? Name | 'mut'? Name ':' Type ) Param = - Attr* ( + AnyAttr* ( Pat (':' Type)? | Type | '...' @@ -260,13 +266,13 @@ RetType = '->' Type TypeAlias = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'type' Name GenericParamList? (':' TypeBoundList?)? WhereClause? ('=' Type)? ';' Struct = - Attr* Visibility? + AnyAttr* Visibility? 'struct' Name GenericParamList? ( WhereClause? (RecordFieldList | ';') | TupleFieldList WhereClause? ';' @@ -276,7 +282,7 @@ RecordFieldList = '{' fields:(RecordField (',' RecordField)* ','?)? '}' RecordField = - Attr* Visibility? 'unsafe'? + AnyAttr* Visibility? 'unsafe'? MutRestriction? Name ':' Type ('=' default_val:ConstArg)? @@ -284,7 +290,7 @@ TupleFieldList = '(' fields:(TupleField (',' TupleField)* ','?)? ')' TupleField = - Attr* Visibility? + AnyAttr* Visibility? MutRestriction? Type @@ -296,7 +302,7 @@ MutRestriction = 'mut' VisibilityInner Enum = - Attr* Visibility? + AnyAttr* Visibility? 'enum' Name GenericParamList? WhereClause? VariantList @@ -304,11 +310,11 @@ VariantList = '{' (Variant (',' Variant)* ','?)? '}' Variant = - Attr* Visibility? + AnyAttr* Visibility? (Name | '_') FieldList? ('=' ConstArg)? Union = - Attr* Visibility? + AnyAttr* Visibility? 'union' Name GenericParamList? WhereClause? RecordFieldList @@ -326,7 +332,7 @@ VariantDef = | Variant Const = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'type'? 'const' (Name | '_') GenericParamList? ':' Type @@ -334,13 +340,13 @@ Const = WhereClause? ';' Static = - Attr* Visibility? + AnyAttr* Visibility? 'unsafe'? 'safe'? 'static' 'mut'? Name ':' Type ('=' body:Expr)? ';' Trait = - Attr* Visibility? + AnyAttr* Visibility? 'unsafe'? 'auto'? ImplRestriction? 'trait' Name GenericParamList? @@ -351,7 +357,7 @@ ImplRestriction = 'impl' VisibilityInner AssocItemList = - '{' Attr* AssocItem* '}' + '{' AnyAttr* AssocItem* '}' AssocItem = Const @@ -360,16 +366,16 @@ AssocItem = | TypeAlias Impl = - Attr* Visibility? + AnyAttr* Visibility? 'default'? 'unsafe'? 'impl' GenericParamList? ('const'? '!'? trait:Type 'for')? self_ty:Type WhereClause? AssocItemList ExternBlock = - Attr* 'unsafe'? Abi ExternItemList + AnyAttr* 'unsafe'? Abi ExternItemList ExternItemList = - '{' Attr* ExternItem* '}' + '{' AnyAttr* ExternItem* '}' ExternItem = Fn @@ -394,7 +400,7 @@ Stmt = | LetStmt LetStmt = - Attr* 'super'? 'let' Pat (':' Type)? + AnyAttr* 'super'? 'let' Pat (':' Type)? '=' initializer:Expr LetElse? ';' @@ -450,13 +456,13 @@ IncludeBytesExpr = 'builtin' '#' 'include_bytes' OffsetOfExpr = - Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' + AnyAttr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' // asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")" // global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")" // format_string := STRING_LITERAL / RAW_STRING_LITERAL AsmExpr = - Attr* 'builtin' '#' ( 'asm' | 'global_asm' | 'naked_asm' ) + AnyAttr* 'builtin' '#' ( 'asm' | 'global_asm' | 'naked_asm' ) '(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')' // operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" @@ -468,21 +474,21 @@ AsmRegSpec = '@string' | NameRef // reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr AsmRegOperand = AsmDirSpec '(' AsmRegSpec ')' AsmOperandExpr // clobber_abi := "clobber_abi(" *("," ) [","] ")" -AsmClobberAbi = Attr* 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')' +AsmClobberAbi = AnyAttr* 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')' // option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw" AsmOption = 'pure' | 'nomem' | 'readonly' | 'preserves_flags' | 'noreturn' | 'nostack' | 'att_syntax' | 'raw' | 'may_unwind' // options := "options(" option *("," option) [","] ")" -AsmOptions = Attr* 'options' '(' (AsmOption (',' AsmOption)*) ','? ')' +AsmOptions = AnyAttr* 'options' '(' (AsmOption (',' AsmOption)*) ','? ')' AsmLabel = 'label' BlockExpr AsmSym = 'sym' Path AsmConst = 'const' Expr // operand := reg_operand / clobber_abi / options AsmOperand = AsmRegOperand | AsmLabel | AsmSym | AsmConst -AsmOperandNamed = Attr* (Name '=')? AsmOperand +AsmOperandNamed = AnyAttr* (Name '=')? AsmOperand AsmPiece = AsmOperandNamed | AsmClobberAbi | AsmOptions FormatArgsExpr = - Attr* 'builtin' '#' 'format_args' '(' + AnyAttr* 'builtin' '#' 'format_args' '(' template:Expr (',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )? ')' @@ -494,7 +500,7 @@ MacroExpr = MacroCall Literal = - Attr* value:( + AnyAttr* value:( '@int_number' | '@float_number' | '@string' | '@byte_string' @@ -504,32 +510,32 @@ Literal = ) PathExpr = - Attr* Path + AnyAttr* Path StmtList = '{' - Attr* + AnyAttr* statements:Stmt* tail_expr:Expr? '}' RefExpr = - Attr* '&' (('raw' 'const'?)| ('raw'? 'mut') ) Expr + AnyAttr* '&' (('raw' 'const'?)| ('raw'? 'mut') ) Expr TryExpr = - Attr* Expr '?' + AnyAttr* Expr '?' TryBlockModifier = 'try' ('bikeshed' Type)? BlockExpr = - Attr* Label? (TryBlockModifier | 'unsafe' | ('async' 'move'?) | ('gen' 'move'?) | 'const') StmtList + AnyAttr* Label? (TryBlockModifier | 'unsafe' | ('async' 'move'?) | ('gen' 'move'?) | 'const') StmtList PrefixExpr = - Attr* op:('-' | '!' | '*') Expr + AnyAttr* op:('-' | '!' | '*') Expr BinExpr = - Attr* + AnyAttr* lhs:Expr op:( '||' | '&&' @@ -540,118 +546,118 @@ BinExpr = rhs:Expr CastExpr = - Attr* Expr 'as' Type + AnyAttr* Expr 'as' Type ParenExpr = - Attr* '(' Attr* Expr ')' + AnyAttr* '(' AnyAttr* Expr ')' ArrayExpr = - Attr* '[' Attr* ( + AnyAttr* '[' AnyAttr* ( (Expr (',' Expr)* ','?)? | Expr ';' Expr ) ']' IndexExpr = - Attr* base:Expr '[' index:Expr ']' + AnyAttr* base:Expr '[' index:Expr ']' TupleExpr = - Attr* '(' Attr* fields:(Expr (',' Expr)* ','?)? ')' + AnyAttr* '(' AnyAttr* fields:(Expr (',' Expr)* ','?)? ')' RecordExpr = Path RecordExprFieldList RecordExprFieldList = '{' - Attr* + AnyAttr* fields:(RecordExprField (',' RecordExprField)* ','?)? ('..' spread:Expr?)? '}' RecordExprField = - Attr* (NameRef ':')? Expr + AnyAttr* (NameRef ':')? Expr CallExpr = - Attr* Expr ArgList + AnyAttr* Expr ArgList ArgList = '(' args:(Expr (',' Expr)* ','?)? ')' MethodCallExpr = - Attr* receiver:Expr '.' NameRef GenericArgList? ArgList + AnyAttr* receiver:Expr '.' NameRef GenericArgList? ArgList FieldExpr = - Attr* Expr '.' NameRef + AnyAttr* Expr '.' NameRef ClosureExpr = - Attr* ForBinder? 'const'? 'static'? 'async'? 'gen'? 'move'? ParamList RetType? + AnyAttr* ForBinder? 'const'? 'static'? 'async'? 'gen'? 'move'? ParamList RetType? body:Expr ForBinder = 'for' GenericParamList IfExpr = - Attr* 'if' condition:Expr then_branch:BlockExpr + AnyAttr* 'if' condition:Expr then_branch:BlockExpr ('else' else_branch:(IfExpr | BlockExpr))? LoopExpr = - Attr* Label? 'loop' + AnyAttr* Label? 'loop' loop_body:BlockExpr ForExpr = - Attr* Label? 'for' Pat 'in' iterable:Expr + AnyAttr* Label? 'for' Pat 'in' iterable:Expr loop_body:BlockExpr WhileExpr = - Attr* Label? 'while' condition:Expr + AnyAttr* Label? 'while' condition:Expr loop_body:BlockExpr Label = Lifetime ':' BreakExpr = - Attr* 'break' Lifetime? Expr? + AnyAttr* 'break' Lifetime? Expr? ContinueExpr = - Attr* 'continue' Lifetime? + AnyAttr* 'continue' Lifetime? RangeExpr = - Attr* start:Expr? op:('..' | '..=') end:Expr? + AnyAttr* start:Expr? op:('..' | '..=') end:Expr? MatchExpr = - Attr* 'match' Expr MatchArmList + AnyAttr* 'match' Expr MatchArmList MatchArmList = '{' - Attr* + AnyAttr* arms:MatchArm* '}' MatchArm = - Attr* Pat guard:MatchGuard? '=>' Expr ','? + AnyAttr* Pat guard:MatchGuard? '=>' Expr ','? MatchGuard = 'if' condition:Expr ReturnExpr = - Attr* 'return' Expr? + AnyAttr* 'return' Expr? BecomeExpr = - Attr* 'become' Expr + AnyAttr* 'become' Expr YieldExpr = - Attr* 'yield' Expr? + AnyAttr* 'yield' Expr? YeetExpr = - Attr* 'do' 'yeet' Expr? + AnyAttr* 'do' 'yeet' Expr? LetExpr = - Attr* 'let' Pat '=' Expr + AnyAttr* 'let' Pat '=' Expr UnderscoreExpr = - Attr* '_' + AnyAttr* '_' AwaitExpr = - Attr* Expr '.' 'await' + AnyAttr* Expr '.' 'await' //*************************// // Types // @@ -768,7 +774,7 @@ LiteralPat = '-'? Literal IdentPat = - Attr* 'ref'? 'mut'? Name ('@' Pat)? + AnyAttr* 'ref'? 'mut'? Name ('@' Pat)? WildcardPat = '_' @@ -794,7 +800,7 @@ RecordPatFieldList = '}' RecordPatField = - Attr* (NameRef ':')? Pat + AnyAttr* (NameRef ':')? Pat TupleStructPat = Path '(' fields:(Pat (',' Pat)* ','?)? ')' @@ -818,7 +824,7 @@ BoxPat = 'box' Pat RestPat = - Attr* '..' + AnyAttr* '..' MacroPat = MacroCall diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 855b5a80a5f6..2f1fad2dc7e3 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -29,13 +29,11 @@ pub use self::{ TypeOrConstParam, VisibilityKind, }, operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, - token_ext::{ - AnyString, CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix, - }, + token_ext::{AnyComment, AnyString, CommentKind, CommentShape, IsString, QuoteOffsets, Radix}, traits::{ - AttrDocCommentIter, DocCommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericArgs, - HasGenericParams, HasLoopBody, HasModuleItem, HasName, HasTypeBounds, HasVisibility, - attrs_including_inner, + AttrsIter, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, + HasModuleItem, HasName, HasTypeBounds, HasVisibility, attrs_including_inner, + attrs_with_doc_including_inner, }, }; @@ -170,6 +168,14 @@ mod support { } } +#[cfg(test)] +fn doc_comment_text(owner: impl HasAttrs) -> Option { + use itertools::Itertools; + + let docs = owner.doc_comments().map(|comment| comment.text().to_owned()).join("\n"); + if docs.is_empty() { None } else { Some(docs) } +} + #[test] fn assert_ast_is_dyn_compatible() { fn _f(_: &dyn AstNode, _: &dyn HasName) {} @@ -187,7 +193,7 @@ fn test_doc_comment_none() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert!(module.doc_comments().doc_comment_text().is_none()); + assert!(doc_comment_text(module).is_none()); } #[test] @@ -203,7 +209,7 @@ fn test_outer_doc_comment_of_items() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" doc", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" doc", doc_comment_text(module).unwrap()); } #[test] @@ -219,7 +225,7 @@ fn test_inner_doc_comment_of_items() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert!(module.doc_comments().doc_comment_text().is_none()); + assert!(doc_comment_text(module).is_none()); } #[test] @@ -234,7 +240,7 @@ fn test_doc_comment_of_statics() { .ok() .unwrap(); let st = file.syntax().descendants().find_map(Static::cast).unwrap(); - assert_eq!(" Number of levels", st.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" Number of levels", doc_comment_text(st).unwrap()); } #[test] @@ -256,7 +262,7 @@ fn test_doc_comment_preserves_indents() { let module = file.syntax().descendants().find_map(Module::cast).unwrap(); assert_eq!( " doc1\n ```\n fn foo() {\n // ...\n }\n ```", - module.doc_comments().doc_comment_text().unwrap() + doc_comment_text(module).unwrap() ); } @@ -275,7 +281,7 @@ fn test_doc_comment_preserves_newlines() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this\n is\n mod\n foo", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" this\n is\n mod\n foo", doc_comment_text(module).unwrap()); } #[test] @@ -290,7 +296,7 @@ fn test_doc_comment_single_line_block_strips_suffix() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this is mod foo", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" this is mod foo", doc_comment_text(module).unwrap()); } #[test] @@ -305,7 +311,7 @@ fn test_doc_comment_single_line_block_strips_suffix_whitespace() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" this is mod foo ", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" this is mod foo ", doc_comment_text(module).unwrap()); } #[test] @@ -326,7 +332,7 @@ fn test_doc_comment_multi_line_block_strips_suffix() { let module = file.syntax().descendants().find_map(Module::cast).unwrap(); assert_eq!( "\n this\n is\n mod foo\n ", - module.doc_comments().doc_comment_text().unwrap() + doc_comment_text(module).unwrap() ); } @@ -340,7 +346,7 @@ fn test_comments_preserve_trailing_whitespace() { let def = file.syntax().descendants().find_map(Struct::cast).unwrap(); assert_eq!( " Representation of a Realm. \n In the specification these are called Realm Records.", - def.doc_comments().doc_comment_text().unwrap() + doc_comment_text(def).unwrap() ); } @@ -357,7 +363,7 @@ fn test_four_slash_line_comment() { .ok() .unwrap(); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!(" doc comment", module.doc_comments().doc_comment_text().unwrap()); + assert_eq!(" doc comment", doc_comment_text(module).unwrap()); } #[test] diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 852b13fc7a3d..e34cfa746073 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs @@ -1,7 +1,7 @@ //! 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 parser::T; +use parser::{SyntaxKind::DOC_COMMENT, T}; use std::{ fmt, iter::{self, once}, @@ -165,7 +165,7 @@ pub trait AttrsOwnerEdit: ast::HasAttrs { let mut remove_next_ws = false; for child in self.syntax().children_with_tokens() { match child.kind() { - ATTR | COMMENT => { + ATTR | COMMENT | DOC_COMMENT => { remove_next_ws = true; editor.delete(child); continue; diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 5fa56cf33c5d..ad52d8278727 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -468,7 +468,6 @@ pub struct Const { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Const {} -impl ast::HasDocComments for Const {} impl ast::HasGenericParams for Const {} impl ast::HasName for Const {} impl ast::HasVisibility for Const {} @@ -552,6 +551,19 @@ impl DerefPat { #[inline] pub fn deref_token(&self) -> Option { support::token(&self.syntax, T![deref]) } } +pub struct DocComment { + pub(crate) syntax: SyntaxNode, +} +impl DocComment { + #[inline] + pub fn inner_doc_comment_token(&self) -> Option { + support::token(&self.syntax, T![inner_doc_comment]) + } + #[inline] + pub fn outer_doc_comment_token(&self) -> Option { + support::token(&self.syntax, T![outer_doc_comment]) + } +} pub struct DynTraitType { pub(crate) syntax: SyntaxNode, } @@ -565,7 +577,6 @@ pub struct Enum { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Enum {} -impl ast::HasDocComments for Enum {} impl ast::HasGenericParams for Enum {} impl ast::HasName for Enum {} impl ast::HasVisibility for Enum {} @@ -588,7 +599,6 @@ pub struct ExternBlock { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for ExternBlock {} -impl ast::HasDocComments for ExternBlock {} impl ExternBlock { #[inline] pub fn abi(&self) -> Option { support::child(&self.syntax) } @@ -601,7 +611,6 @@ pub struct ExternCrate { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for ExternCrate {} -impl ast::HasDocComments for ExternCrate {} impl ast::HasVisibility for ExternCrate {} impl ExternCrate { #[inline] @@ -643,7 +652,6 @@ pub struct Fn { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Fn {} -impl ast::HasDocComments for Fn {} impl ast::HasGenericParams for Fn {} impl ast::HasName for Fn {} impl ast::HasVisibility for Fn {} @@ -805,7 +813,6 @@ pub struct Impl { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Impl {} -impl ast::HasDocComments for Impl {} impl ast::HasGenericParams for Impl {} impl ast::HasVisibility for Impl {} impl Impl { @@ -1002,7 +1009,6 @@ pub struct MacroCall { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for MacroCall {} -impl ast::HasDocComments for MacroCall {} impl MacroCall { #[inline] pub fn path(&self) -> Option { support::child(&self.syntax) } @@ -1017,7 +1023,6 @@ pub struct MacroDef { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for MacroDef {} -impl ast::HasDocComments for MacroDef {} impl ast::HasName for MacroDef {} impl ast::HasVisibility for MacroDef {} impl MacroDef { @@ -1047,7 +1052,6 @@ pub struct MacroRules { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for MacroRules {} -impl ast::HasDocComments for MacroRules {} impl ast::HasName for MacroRules {} impl ast::HasVisibility for MacroRules {} impl MacroRules { @@ -1141,7 +1145,6 @@ pub struct Module { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Module {} -impl ast::HasDocComments for Module {} impl ast::HasName for Module {} impl ast::HasVisibility for Module {} impl Module { @@ -1472,7 +1475,6 @@ pub struct RecordField { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for RecordField {} -impl ast::HasDocComments for RecordField {} impl ast::HasName for RecordField {} impl ast::HasVisibility for RecordField {} impl RecordField { @@ -1667,7 +1669,6 @@ pub struct SourceFile { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for SourceFile {} -impl ast::HasDocComments for SourceFile {} impl ast::HasModuleItem for SourceFile {} impl SourceFile { #[inline] @@ -1681,7 +1682,6 @@ pub struct Static { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Static {} -impl ast::HasDocComments for Static {} impl ast::HasName for Static {} impl ast::HasVisibility for Static {} impl Static { @@ -1720,7 +1720,6 @@ pub struct Struct { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Struct {} -impl ast::HasDocComments for Struct {} impl ast::HasGenericParams for Struct {} impl ast::HasName for Struct {} impl ast::HasVisibility for Struct {} @@ -1762,7 +1761,6 @@ pub struct Trait { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Trait {} -impl ast::HasDocComments for Trait {} impl ast::HasGenericParams for Trait {} impl ast::HasName for Trait {} impl ast::HasTypeBounds for Trait {} @@ -1822,7 +1820,6 @@ pub struct TupleField { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for TupleField {} -impl ast::HasDocComments for TupleField {} impl ast::HasVisibility for TupleField {} impl TupleField { #[inline] @@ -1880,7 +1877,6 @@ pub struct TypeAlias { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for TypeAlias {} -impl ast::HasDocComments for TypeAlias {} impl ast::HasGenericParams for TypeAlias {} impl ast::HasName for TypeAlias {} impl ast::HasTypeBounds for TypeAlias {} @@ -1979,7 +1975,6 @@ pub struct Union { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Union {} -impl ast::HasDocComments for Union {} impl ast::HasGenericParams for Union {} impl ast::HasName for Union {} impl ast::HasVisibility for Union {} @@ -2006,7 +2001,6 @@ pub struct Use { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Use {} -impl ast::HasDocComments for Use {} impl ast::HasVisibility for Use {} impl Use { #[inline] @@ -2059,7 +2053,6 @@ pub struct Variant { pub(crate) syntax: SyntaxNode, } impl ast::HasAttrs for Variant {} -impl ast::HasDocComments for Variant {} impl ast::HasName for Variant {} impl ast::HasVisibility for Variant {} impl Variant { @@ -2171,11 +2164,16 @@ pub enum Adt { Union(Union), } impl ast::HasAttrs for Adt {} -impl ast::HasDocComments for Adt {} impl ast::HasGenericParams for Adt {} impl ast::HasName for Adt {} impl ast::HasVisibility for Adt {} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum AnyAttr { + Attr(Attr), + DocComment(DocComment), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AsmOperand { AsmConst(AsmConst), @@ -2200,7 +2198,6 @@ pub enum AssocItem { TypeAlias(TypeAlias), } impl ast::HasAttrs for AssocItem {} -impl ast::HasDocComments for AssocItem {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CfgPredicate { @@ -2257,7 +2254,6 @@ pub enum ExternItem { TypeAlias(TypeAlias), } impl ast::HasAttrs for ExternItem {} -impl ast::HasDocComments for ExternItem {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum FieldList { @@ -2374,7 +2370,6 @@ pub enum VariantDef { Variant(Variant), } impl ast::HasAttrs for VariantDef {} -impl ast::HasDocComments for VariantDef {} impl ast::HasName for VariantDef {} impl ast::HasVisibility for VariantDef {} pub struct AnyHasArgList { @@ -2395,15 +2390,6 @@ impl AnyHasAttrs { AnyHasAttrs { syntax: node.syntax().clone() } } } -pub struct AnyHasDocComments { - pub(crate) syntax: SyntaxNode, -} -impl AnyHasDocComments { - #[inline] - pub fn new(node: T) -> AnyHasDocComments { - AnyHasDocComments { syntax: node.syntax().clone() } - } -} pub struct AnyHasGenericArgs { pub(crate) syntax: SyntaxNode, } @@ -3683,6 +3669,38 @@ impl fmt::Debug for DerefPat { f.debug_struct("DerefPat").field("syntax", &self.syntax).finish() } } +impl AstNode for DocComment { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + DOC_COMMENT + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == DOC_COMMENT } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for DocComment { + fn hash(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for DocComment {} +impl PartialEq for DocComment { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for DocComment { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for DocComment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DocComment").field("syntax", &self.syntax).finish() + } +} impl AstNode for DynTraitType { #[inline] fn kind() -> SyntaxKind @@ -7909,6 +7927,34 @@ impl AstNode for Adt { } } } +impl From for AnyAttr { + #[inline] + fn from(node: Attr) -> AnyAttr { AnyAttr::Attr(node) } +} +impl From for AnyAttr { + #[inline] + fn from(node: DocComment) -> AnyAttr { AnyAttr::DocComment(node) } +} +impl AstNode for AnyAttr { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, ATTR | DOC_COMMENT) } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + ATTR => AnyAttr::Attr(Attr { syntax }), + DOC_COMMENT => AnyAttr::DocComment(DocComment { syntax }), + _ => return None, + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + AnyAttr::Attr(it) => &it.syntax, + AnyAttr::DocComment(it) => &it.syntax, + } + } +} impl From for AsmOperand { #[inline] fn from(node: AsmConst) -> AsmOperand { AsmOperand::AsmConst(node) } @@ -9455,136 +9501,6 @@ impl From for AnyHasAttrs { #[inline] fn from(node: YieldExpr) -> AnyHasAttrs { AnyHasAttrs { syntax: node.syntax } } } -impl ast::HasDocComments for AnyHasDocComments {} -impl AstNode for AnyHasDocComments { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - matches!( - kind, - CONST - | ENUM - | EXTERN_BLOCK - | EXTERN_CRATE - | FN - | IMPL - | MACRO_CALL - | MACRO_DEF - | MACRO_RULES - | MODULE - | RECORD_FIELD - | SOURCE_FILE - | STATIC - | STRUCT - | TRAIT - | TUPLE_FIELD - | TYPE_ALIAS - | UNION - | USE - | VARIANT - ) - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - Self::can_cast(syntax.kind()).then_some(AnyHasDocComments { syntax }) - } - #[inline] - fn syntax(&self) -> &SyntaxNode { &self.syntax } -} -impl hash::Hash for AnyHasDocComments { - fn hash(&self, state: &mut H) { self.syntax.hash(state); } -} -impl Eq for AnyHasDocComments {} -impl PartialEq for AnyHasDocComments { - fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } -} -impl Clone for AnyHasDocComments { - fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } -} -impl fmt::Debug for AnyHasDocComments { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AnyHasDocComments").field("syntax", &self.syntax).finish() - } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Const) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Enum) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: ExternBlock) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: ExternCrate) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Fn) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Impl) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: MacroCall) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: MacroDef) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: MacroRules) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Module) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: RecordField) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: SourceFile) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Static) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Struct) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Trait) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: TupleField) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: TypeAlias) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Union) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Use) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} -impl From for AnyHasDocComments { - #[inline] - fn from(node: Variant) -> AnyHasDocComments { AnyHasDocComments { syntax: node.syntax } } -} impl ast::HasGenericArgs for AnyHasGenericArgs {} impl AstNode for AnyHasGenericArgs { #[inline] @@ -10066,6 +9982,11 @@ impl std::fmt::Display for Adt { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for AnyAttr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for AsmOperand { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -10336,6 +10257,11 @@ impl std::fmt::Display for DerefPat { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for DocComment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for DynTraitType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 672e2fd233e4..eef314175ae1 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -7,14 +7,14 @@ use std::{fmt, iter::successors}; use itertools::Itertools; use parser::SyntaxKind; -use rowan::{GreenNodeData, GreenTokenData}; +use rowan::{GreenNodeData, GreenTokenData, TextSize}; use smallvec::{SmallVec, smallvec}; use crate::{ NodeOrToken, SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxToken, T, ast::{ - self, AstNode, AstToken, HasAttrs, HasGenericArgs, HasGenericParams, HasName, - HasTypeBounds, SyntaxNode, support, + self, AnyComment, AstNode, AstToken, CommentShape, HasAttrs, HasGenericArgs, + HasGenericParams, HasName, HasTypeBounds, SyntaxNode, support, }, syntax_editor::SyntaxEditor, }; @@ -271,6 +271,55 @@ impl ast::Attr { } } +impl ast::DocComment { + // `///` or `/**` or `//!` or `/*!`, all are 3 chars. + pub const PREFIX_LEN: TextSize = TextSize::new(3); + + pub fn kind(&self) -> AttrKind { + match self.inner_doc_comment_token() { + Some(_) => AttrKind::Inner, + None => AttrKind::Outer, + } + } + + pub fn token(&self) -> AnyComment { + self.syntax + .first_token() + .and_then(ast::AnyComment::cast) + .expect("`ast::DocComment` must have a comment token") + } + + pub fn shape(&self) -> CommentShape { + CommentShape::from_text(self.text_with_markers()) + } + + /// Returns the text with the `/**...*/` or `/*!...*/` or `///...` or `//!...` markers. + pub fn text_with_markers(&self) -> &str { + text_of_first_token(&self.syntax) + } + + /// Returns the textual content of a doc comment node as a single string with prefix and suffix removed. + pub fn text(&self) -> &str { + let shape = self.shape(); + let text = &self.text_with_markers()[Self::PREFIX_LEN.into()..]; + if shape == CommentShape::Block { + // The `*/` may not exist because of recovery. + text.strip_suffix("*/").unwrap_or(text) + } else { + text + } + } +} + +impl ast::AnyAttr { + pub fn kind(&self) -> AttrKind { + match self { + ast::AnyAttr::Attr(it) => it.kind(), + ast::AnyAttr::DocComment(it) => it.kind(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum PathSegmentKind { Name(ast::NameRef), @@ -1129,8 +1178,6 @@ impl ast::HasLoopBody for ast::WhileExpr { } } -impl ast::HasAttrs for ast::AnyHasDocComments {} - impl From for ast::Item { fn from(it: ast::Adt) -> Self { match it { diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index bb0f53db2473..07240265da19 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs @@ -3,6 +3,7 @@ use std::ops::Range; use std::{borrow::Cow, num::ParseIntError}; +use parser::SyntaxKind; use rustc_literal_escaper::{ EscapeError, MixedUnit, unescape_byte, unescape_byte_str, unescape_c_str, unescape_char, unescape_str, @@ -10,47 +11,23 @@ use rustc_literal_escaper::{ use stdx::always; use crate::{ - TextRange, TextSize, - ast::{self, AstToken}, + SyntaxToken, TextRange, TextSize, + ast::{self, AstToken, AttrKind}, }; impl ast::Comment { - pub fn kind(&self) -> CommentKind { - CommentKind::from_text(self.text()) - } - - pub fn is_doc(&self) -> bool { - self.kind().doc.is_some() - } - - pub fn is_inner(&self) -> bool { - self.kind().doc == Some(CommentPlacement::Inner) - } - - pub fn is_outer(&self) -> bool { - self.kind().doc == Some(CommentPlacement::Outer) - } - - pub fn prefix(&self) -> &'static str { - self.kind().prefix() + pub fn shape(&self) -> CommentShape { + CommentShape::from_text(self.text()) } - /// Returns the textual content of a doc comment node as a single string with prefix and suffix - /// removed, plus the offset of the returned string from the beginning of the comment. - pub fn doc_comment(&self) -> Option<(&str, TextSize)> { - let kind = self.kind(); - match kind { - CommentKind { shape, doc: Some(_) } => { - let prefix = kind.prefix(); - let text = &self.text()[prefix.len()..]; - let text = if shape == CommentShape::Block { - text.strip_suffix("*/").unwrap_or(text) - } else { - text - }; - Some((text, TextSize::of(prefix))) - } - _ => None, + /// Returns the text without the `//` or `/*...*/` markers. + pub fn text_without_markers(&self) -> &str { + let text = self.text(); + let shape = CommentShape::from_text(text); + let text = &text[2..]; + match shape { + CommentShape::Block => text.strip_suffix("*/").unwrap_or(text), + CommentShape::Line => text, } } } @@ -58,7 +35,20 @@ impl ast::Comment { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct CommentKind { pub shape: CommentShape, - pub doc: Option, + pub doc: Option, +} + +impl CommentKind { + pub fn prefix(&self) -> &'static str { + match (self.shape, self.doc) { + (CommentShape::Line, None) => "//", + (CommentShape::Line, Some(AttrKind::Inner)) => "//!", + (CommentShape::Line, Some(AttrKind::Outer)) => "///", + (CommentShape::Block, None) => "/*", + (CommentShape::Block, Some(AttrKind::Inner)) => "/*!", + (CommentShape::Block, Some(AttrKind::Outer)) => "/**", + } + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -68,6 +58,11 @@ pub enum CommentShape { } impl CommentShape { + #[inline] + pub fn from_text(text: &str) -> CommentShape { + if text.starts_with("/*") { CommentShape::Block } else { CommentShape::Line } + } + pub fn is_line(self) -> bool { self == CommentShape::Line } @@ -77,37 +72,80 @@ impl CommentShape { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CommentPlacement { - Inner, - Outer, +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AnyComment { + syntax: SyntaxToken, } -impl CommentKind { - const BY_PREFIX: [(&'static str, CommentKind); 9] = [ - ("/**/", CommentKind { shape: CommentShape::Block, doc: None }), - ("/***", CommentKind { shape: CommentShape::Block, doc: None }), - ("////", CommentKind { shape: CommentShape::Line, doc: None }), - ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }), - ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }), - ("/**", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Outer) }), - ("/*!", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Inner) }), - ("//", CommentKind { shape: CommentShape::Line, doc: None }), - ("/*", CommentKind { shape: CommentShape::Block, doc: None }), - ]; - - pub(crate) fn from_text(text: &str) -> CommentKind { - let &(_prefix, kind) = CommentKind::BY_PREFIX - .iter() - .find(|&(prefix, _kind)| text.starts_with(prefix)) - .unwrap(); - kind +impl AstToken for AnyComment { + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + matches!( + kind, + SyntaxKind::COMMENT | SyntaxKind::INNER_DOC_COMMENT | SyntaxKind::OUTER_DOC_COMMENT + ) + } + + fn cast(syntax: SyntaxToken) -> Option + where + Self: Sized, + { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + + fn syntax(&self) -> &SyntaxToken { + &self.syntax + } +} + +impl AnyComment { + pub fn shape(&self) -> CommentShape { + CommentShape::from_text(self.text_with_markers()) + } + + pub fn doc_kind(&self) -> Option { + match self.syntax.kind() { + SyntaxKind::COMMENT => None, + SyntaxKind::INNER_DOC_COMMENT => Some(AttrKind::Inner), + SyntaxKind::OUTER_DOC_COMMENT => Some(AttrKind::Outer), + _ => unreachable!(), + } + } + + pub fn kind(&self) -> CommentKind { + CommentKind { shape: self.shape(), doc: self.doc_kind() } } pub fn prefix(&self) -> &'static str { - let &(prefix, _) = - CommentKind::BY_PREFIX.iter().rev().find(|(_, kind)| kind == self).unwrap(); - prefix + self.kind().prefix() + } + + pub fn is_inner(&self) -> bool { + self.doc_kind() == Some(AttrKind::Inner) + } + + pub fn is_outer(&self) -> bool { + self.doc_kind() == Some(AttrKind::Outer) + } + + /// Returns the text with the `/*...*/` or `//...` or `/**...*/` or `/*!...*/` or `///...` or `//!...` markers. + pub fn text_with_markers(&self) -> &str { + self.syntax.text() + } + + /// Returns the textual content of a doc comment node as a single string with prefix and suffix removed. + pub fn text(&self) -> &str { + let shape = self.shape(); + let prefix_len = if self.doc_kind().is_some() { 3 } else { 2 }; + let text = &self.text_with_markers()[prefix_len..]; + if shape == CommentShape::Block { + // The `*/` may not exist because of recovery. + text.strip_suffix("*/").unwrap_or(text) + } else { + text + } } } diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs index 6fe5abb84e22..59e1835b02d6 100644 --- a/crates/syntax/src/ast/traits.rs +++ b/crates/syntax/src/ast/traits.rs @@ -4,10 +4,9 @@ use either::Either; use crate::{ - SyntaxElement, SyntaxNode, SyntaxToken, T, - ast::{self, AstChildren, AstNode, AstToken, support}, + SyntaxNode, SyntaxToken, T, + ast::{self, AstChildren, AstNode, support}, match_ast, - syntax_node::SyntaxElementChildren, }; pub trait HasName: AstNode { @@ -74,6 +73,14 @@ pub trait HasAttrs: AstNode { support::children(self.syntax()) } + fn doc_comments(&self) -> AstChildren { + support::children(self.syntax()) + } + + fn attrs_with_doc(&self) -> AstChildren { + support::children(self.syntax()) + } + /// This may return the same node as called with (with `SourceFile`). The caller has the responsibility /// to avoid duplicate attributes. fn inner_attributes_node(&self) -> Option { @@ -102,68 +109,42 @@ pub trait HasAttrs: AstNode { /// Returns all attributes of this node, including inner attributes that may not be directly under this node /// but under a child. -pub fn attrs_including_inner(owner: &dyn HasAttrs) -> impl Iterator + Clone { - owner.attrs().filter(|attr| attr.kind().is_outer()).chain( +pub fn attrs_with_doc_including_inner( + owner: &dyn HasAttrs, +) -> impl Iterator + Clone { + owner.attrs_with_doc().filter(|attr| attr.kind().is_outer()).chain( owner .inner_attributes_node() .into_iter() - .flat_map(|node| support::children::(&node)) + .flat_map(|node| support::children::(&node)) .filter(|attr| attr.kind().is_inner()), ) } -pub trait HasDocComments: HasAttrs { - fn doc_comments(&self) -> DocCommentIter { - DocCommentIter { iter: self.syntax().children_with_tokens() } - } -} - -impl DocCommentIter { - pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> DocCommentIter { - DocCommentIter { iter: syntax_node.children_with_tokens() } - } - - #[cfg(test)] - pub fn doc_comment_text(self) -> Option { - let docs = itertools::Itertools::join( - &mut self.filter_map(|comment| comment.doc_comment().map(|it| it.0.to_owned())), - "\n", - ); - if docs.is_empty() { None } else { Some(docs) } - } +pub fn attrs_including_inner(owner: &dyn HasAttrs) -> impl Iterator + Clone { + AttrsIter::new(attrs_with_doc_including_inner(owner)) } -pub struct DocCommentIter { - iter: SyntaxElementChildren, +#[derive(Clone)] +pub struct AttrsIter { + inner: I, } -impl Iterator for DocCommentIter { - type Item = ast::Comment; - fn next(&mut self) -> Option { - self.iter.by_ref().find_map(|el| { - el.into_token().and_then(ast::Comment::cast).filter(ast::Comment::is_doc) - }) +impl> AttrsIter { + #[inline] + pub fn new(inner: I) -> Self { + Self { inner } } } -pub struct AttrDocCommentIter { - iter: SyntaxElementChildren, -} +impl> Iterator for AttrsIter { + type Item = ast::Attr; -impl AttrDocCommentIter { - pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> AttrDocCommentIter { - AttrDocCommentIter { iter: syntax_node.children_with_tokens() } - } -} - -impl Iterator for AttrDocCommentIter { - type Item = Either; + #[inline] fn next(&mut self) -> Option { - self.iter.find_map(|el| match el { - SyntaxElement::Node(node) => ast::Attr::cast(node).map(Either::Left), - SyntaxElement::Token(tok) => { - ast::Comment::cast(tok).filter(ast::Comment::is_doc).map(Either::Right) - } + self.inner.find_map(|attr| match attr { + ast::AnyAttr::Attr(it) => Some(it), + ast::AnyAttr::DocComment(_) => None, }) } } diff --git a/crates/syntax/src/parsing/reparsing.rs b/crates/syntax/src/parsing/reparsing.rs index 5f193f01bc73..df1d6a1713d1 100644 --- a/crates/syntax/src/parsing/reparsing.rs +++ b/crates/syntax/src/parsing/reparsing.rs @@ -384,14 +384,6 @@ fn baz $0$0 () {} " \t\t\n\n", 2, ); - do_check( - r" -/// foo $0$0omment -mod { } -", - "c", - 14, - ); do_check( r#" fn -> &str { "Hello$0$0" } diff --git a/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast b/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast index f56af1a5c05b..8952e88e7002 100644 --- a/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast +++ b/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast @@ -40,7 +40,8 @@ SOURCE_FILE@0..611 R_PAREN@81..82 ")" R_BRACK@82..83 "]" WHITESPACE@83..92 "\n " - COMMENT@92..122 "//! Nor are ModuleDoc ..." + DOC_COMMENT@92..122 + INNER_DOC_COMMENT@92..122 "//! Nor are ModuleDoc ..." WHITESPACE@122..127 "\n " R_CURLY@127..128 "}" SEMICOLON@128..129 ";" @@ -86,7 +87,8 @@ SOURCE_FILE@0..611 R_PAREN@210..211 ")" R_BRACK@211..212 "]" WHITESPACE@212..221 "\n " - COMMENT@221..251 "//! Nor are ModuleDoc ..." + DOC_COMMENT@221..251 + INNER_DOC_COMMENT@221..251 "//! Nor are ModuleDoc ..." WHITESPACE@251..256 "\n " R_CURLY@256..257 "}" WHITESPACE@257..262 "\n " @@ -116,7 +118,8 @@ SOURCE_FILE@0..611 R_PAREN@300..301 ")" R_BRACK@301..302 "]" WHITESPACE@302..311 "\n " - COMMENT@311..341 "//! Nor are ModuleDoc ..." + DOC_COMMENT@311..341 + INNER_DOC_COMMENT@311..341 "//! Nor are ModuleDoc ..." WHITESPACE@341..346 "\n " R_CURLY@346..347 "}" WHITESPACE@347..353 "\n " @@ -143,7 +146,8 @@ SOURCE_FILE@0..611 R_PAREN@428..429 ")" R_BRACK@429..430 "]" WHITESPACE@430..439 "\n " - COMMENT@439..468 "//! So are ModuleDoc ..." + DOC_COMMENT@439..468 + INNER_DOC_COMMENT@439..468 "//! So are ModuleDoc ..." WHITESPACE@468..473 "\n " R_CURLY@473..474 "}" WHITESPACE@474..479 "\n " @@ -181,7 +185,8 @@ SOURCE_FILE@0..611 R_PAREN@562..563 ")" R_BRACK@563..564 "]" WHITESPACE@564..573 "\n " - COMMENT@573..602 "//! So are ModuleDoc ..." + DOC_COMMENT@573..602 + INNER_DOC_COMMENT@573..602 "//! So are ModuleDoc ..." WHITESPACE@602..607 "\n " R_CURLY@607..608 "}" WHITESPACE@608..609 "\n" diff --git a/xtask/src/codegen/grammar.rs b/xtask/src/codegen/grammar.rs index 257429c42661..0553234a5c89 100644 --- a/xtask/src/codegen/grammar.rs +++ b/xtask/src/codegen/grammar.rs @@ -672,6 +672,8 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String { [string] => { $crate::SyntaxKind::STRING }; [shebang] => { $crate::SyntaxKind::SHEBANG }; [frontmatter] => { $crate::SyntaxKind::FRONTMATTER }; + [inner_doc_comment] => { $crate::SyntaxKind::INNER_DOC_COMMENT }; + [outer_doc_comment] => { $crate::SyntaxKind::OUTER_DOC_COMMENT }; } impl ::core::marker::Copy for SyntaxKind {} @@ -938,7 +940,13 @@ fn lower_rule(acc: &mut Vec, grammar: &Grammar, label: Option<&String>, r Rule::Rep(inner) => { if let Rule::Node(node) = &**inner { let ty = grammar[*node].name.clone(); - let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty))); + let name = label.cloned().unwrap_or_else(|| { + if ty == "AnyAttr" { + "attrs".to_owned() + } else { + pluralize(&to_lower_snake_case(&ty)) + } + }); let field = Field::Node { name, ty, cardinality: Cardinality::Many }; acc.push(field); return; @@ -1089,35 +1097,6 @@ fn extract_struct_traits(ast: &mut AstSrc) { extract_struct_trait(node, name, methods); } } - - let nodes_with_doc_comments = [ - "SourceFile", - "Fn", - "Struct", - "Union", - "RecordField", - "TupleField", - "Enum", - "Variant", - "Trait", - "Module", - "Static", - "Const", - "TypeAlias", - "Impl", - "ExternBlock", - "ExternCrate", - "MacroCall", - "MacroRules", - "MacroDef", - "Use", - ]; - - for node in &mut ast.nodes { - if nodes_with_doc_comments.contains(&&*node.name) { - node.traits.push("HasDocComments".into()); - } - } } fn extract_struct_trait(node: &mut AstNodeSrc, trait_name: &str, methods: &[&str]) {