From c766492d2625dba65c3bd933841c71938f6dc747 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 16 Mar 2021 21:05:07 +0100 Subject: Properly handle doc attributes in doc-comment highlight injection --- crates/hir/src/lib.rs | 2 +- crates/hir_def/src/attr.rs | 8 +++- crates/ide/src/syntax_highlighting/inject.rs | 48 +++++++++++++++++++--- .../test_data/highlight_doctest.html | 18 +++++++- crates/ide/src/syntax_highlighting/tests.rs | 16 ++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) (limited to 'crates') diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index ad79a79f8..265a084f3 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -89,7 +89,7 @@ pub use crate::{ pub use { hir_def::{ adt::StructKind, - attr::{Attrs, Documentation}, + attr::{Attr, Attrs, Documentation}, body::scope::ExprScopes, find_path::PrefixKind, import_map, diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index 683c37023..e7019e0c9 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs @@ -162,7 +162,6 @@ impl RawAttrs { let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?; // FIXME hygiene let hygiene = Hygiene::new_unhygienic(); - // FIXME same index is assigned to multiple attributes Attr::from_src(attr, &hygiene).map(|attr| Attr { index, ..attr }) }); @@ -450,6 +449,13 @@ impl Attr { _ => None, } } + + pub fn string_value(&self) -> Option<&SmolStr> { + match self.input.as_ref()? { + AttrInput::Literal(it) => Some(it), + _ => None, + } + } } #[derive(Debug, Clone, Copy)] diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 086db40e5..0f1de4fb8 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -5,7 +5,7 @@ use hir::{HasAttrs, Semantics}; use ide_db::call_info::ActiveParameter; use syntax::{ ast::{self, AstNode, AttrsOwner, DocCommentsOwner}, - match_ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize, + match_ast, AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, }; use crate::{Analysis, HlMod, HlRange, HlTag, RootDatabase}; @@ -153,7 +153,6 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics, n if attributes.docs().map_or(true, |docs| !String::from(docs).contains(RUSTDOC_FENCE)) { return; } - let doc_comments = attributes.by_key("doc").attrs().map(|attr| attr.to_src(&owner)); let mut inj = Injector::default(); inj.add_unmapped("fn doctest() {\n"); @@ -164,13 +163,28 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics, n // Replace the original, line-spanning comment ranges by new, only comment-prefix // spanning comment ranges. let mut new_comments = Vec::new(); - for comment in doc_comments { - let (line, range, prefix) = match &comment { - Either::Left(_) => continue, // FIXME + let mut string; + for attr in attributes.by_key("doc").attrs() { + let src = attr.to_src(&owner); + let (line, range, prefix) = match &src { + Either::Left(it) => { + string = match find_doc_string_in_attr(attr, it) { + Some(it) => it, + None => continue, + }; + let text_range = string.syntax().text_range(); + let text_range = TextRange::new( + text_range.start() + TextSize::from(1), + text_range.end() - TextSize::from(1), + ); + let text = string.text(); + (&text[1..text.len() - 1], text_range, "") + } Either::Right(comment) => { (comment.text(), comment.syntax().text_range(), comment.prefix()) } }; + match line.find(RUSTDOC_FENCE) { Some(idx) => { is_codeblock = !is_codeblock; @@ -222,3 +236,27 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics, n }); } } + +fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option { + match it.literal() { + // #[doc = lit] + Some(lit) => match lit.kind() { + ast::LiteralKind::String(it) => Some(it), + _ => None, + }, + // #[cfg_attr(..., doc = "", ...)] + None => { + // We gotta hunt the string token manually here + let text = attr.string_value()?; + // FIXME: We just pick the first string literal that has the same text as the doc attribute + // This means technically we might highlight the wrong one + it.syntax() + .descendants_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter_map(ast::String::cast) + .find(|string| { + string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text) + }) + } + } +} diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 5e877df88..45817faf9 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -105,4 +105,20 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd ($expr:expr) => { $expr } -} \ No newline at end of file +} + +/// ```rust +/// let _ = example(&[1, 2, 3]); +/// ``` +/// +/// ``` +/// loop {} +#[cfg_attr(not(feature = "false"), doc = "loop {}")] +#[doc = "loop {}"] +/// ``` +/// +#[cfg_attr(feature = "alloc", doc = "```rust")] +#[cfg_attr(not(feature = "alloc"), doc = "```ignore")] +/// let _ = example(&alloc::vec![1, 2, 3]); +/// ``` +pub fn mix_and_match() {} \ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 9d0cd1af5..a5ef2d29b 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -541,6 +541,22 @@ macro_rules! noop { $expr } } + +/// ```rust +/// let _ = example(&[1, 2, 3]); +/// ``` +/// +/// ``` +/// loop {} +#[cfg_attr(not(feature = "false"), doc = "loop {}")] +#[doc = "loop {}"] +/// ``` +/// +#[cfg_attr(feature = "alloc", doc = "```rust")] +#[cfg_attr(not(feature = "alloc"), doc = "```ignore")] +/// let _ = example(&alloc::vec![1, 2, 3]); +/// ``` +pub fn mix_and_match() {} "# .trim(), expect_file!["./test_data/highlight_doctest.html"], -- cgit v1.2.3