11//! "Recursive" Syntax highlighting for code in doctests and fixtures.
22
3- use std:: mem;
3+ use std:: { mem, ops :: Range } ;
44
55use either:: Either ;
66use hir:: { HasAttrs , Semantics } ;
7- use ide_db:: call_info:: ActiveParameter ;
7+ use ide_db:: { call_info:: ActiveParameter , defs :: Definition } ;
88use syntax:: {
99 ast:: { self , AstNode , AttrsOwner , DocCommentsOwner } ,
1010 match_ast, AstToken , NodeOrToken , SyntaxNode , SyntaxToken , TextRange , TextSize ,
1111} ;
1212
13- use crate :: { Analysis , HlMod , HlRange , HlTag , RootDatabase } ;
13+ use crate :: {
14+ doc_links:: extract_definitions_from_markdown, Analysis , HlMod , HlRange , HlTag , RootDatabase ,
15+ } ;
1416
1517use super :: { highlights:: Highlights , injector:: Injector } ;
1618
@@ -120,24 +122,24 @@ impl AstNode for AttrsOwnerNode {
120122fn doc_attributes < ' node > (
121123 sema : & Semantics < RootDatabase > ,
122124 node : & ' node SyntaxNode ,
123- ) -> Option < ( AttrsOwnerNode , hir:: Attrs ) > {
125+ ) -> Option < ( AttrsOwnerNode , hir:: Attrs , Definition ) > {
124126 match_ast ! {
125127 match node {
126- ast:: SourceFile ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
127- ast:: Fn ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
128- ast:: Struct ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
129- ast:: Union ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
130- ast:: RecordField ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
131- ast:: TupleField ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
132- ast:: Enum ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
133- ast:: Variant ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
134- ast:: Trait ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
135- ast:: Module ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
136- ast:: Static ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
137- ast:: Const ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
138- ast:: TypeAlias ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
139- ast:: Impl ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
140- ast:: MacroRules ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) ) ) ,
128+ ast:: SourceFile ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Module ( def ) ) ) ) ,
129+ ast:: Module ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Module ( def ) ) ) ) ,
130+ ast:: Fn ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Function ( def ) ) ) ) ,
131+ ast:: Struct ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Adt ( hir :: Adt :: Struct ( def ) ) ) ) ) ,
132+ ast:: Union ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Adt ( hir :: Adt :: Union ( def ) ) ) ) ) ,
133+ ast:: Enum ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Adt ( hir :: Adt :: Enum ( def ) ) ) ) ) ,
134+ ast:: Variant ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Variant ( def ) ) ) ) ,
135+ ast:: Trait ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Trait ( def ) ) ) ) ,
136+ ast:: Static ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Static ( def ) ) ) ) ,
137+ ast:: Const ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: Const ( def ) ) ) ) ,
138+ ast:: TypeAlias ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: ModuleDef ( hir :: ModuleDef :: TypeAlias ( def ) ) ) ) ,
139+ ast:: Impl ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: SelfType ( def ) ) ) ,
140+ ast:: RecordField ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: Field ( def ) ) ) ,
141+ ast:: TupleField ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: Field ( def ) ) ) ,
142+ ast:: MacroRules ( it) => sema. to_def( & it) . map( |def| ( AttrsOwnerNode :: new( it) , def. attrs( sema. db) , Definition :: Macro ( def ) ) ) ,
141143 // ast::MacroDef(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
142144 // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
143145 _ => return None
@@ -147,25 +149,23 @@ fn doc_attributes<'node>(
147149
148150/// Injection of syntax highlighting of doctests.
149151pub ( super ) fn doc_comment ( hl : & mut Highlights , sema : & Semantics < RootDatabase > , node : & SyntaxNode ) {
150- let ( owner, attributes) = match doc_attributes ( sema, node) {
152+ let ( owner, attributes, def ) = match doc_attributes ( sema, node) {
151153 Some ( it) => it,
152154 None => return ,
153155 } ;
154156
155- if attributes. docs ( ) . map_or ( true , |docs| !String :: from ( docs) . contains ( RUSTDOC_FENCE ) ) {
156- return ;
157- }
158- let attrs_source_map = attributes. source_map ( & owner) ;
159-
160157 let mut inj = Injector :: default ( ) ;
161158 inj. add_unmapped ( "fn doctest() {\n " ) ;
162159
160+ let attrs_source_map = attributes. source_map ( & owner) ;
161+
163162 let mut is_codeblock = false ;
164163 let mut is_doctest = false ;
165164
166165 // Replace the original, line-spanning comment ranges by new, only comment-prefix
167166 // spanning comment ranges.
168167 let mut new_comments = Vec :: new ( ) ;
168+ let mut intra_doc_links = Vec :: new ( ) ;
169169 let mut string;
170170 for attr in attributes. by_key ( "doc" ) . attrs ( ) {
171171 let src = attrs_source_map. source_of ( & attr) ;
@@ -209,7 +209,22 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
209209 is_doctest = is_codeblock && is_rust;
210210 continue ;
211211 }
212- None if !is_doctest => continue ,
212+ None if !is_doctest => {
213+ intra_doc_links. extend (
214+ extract_definitions_from_markdown ( line)
215+ . into_iter ( )
216+ . filter ( |( link, ns, _) | {
217+ validate_intra_doc_link ( sema. db , & def, link, * ns)
218+ } )
219+ . map ( |( .., Range { start, end } ) | {
220+ TextRange :: at (
221+ prev_range_start + TextSize :: from ( start as u32 ) ,
222+ TextSize :: from ( ( end - start) as u32 ) ,
223+ )
224+ } ) ,
225+ ) ;
226+ continue ;
227+ }
213228 None => ( ) ,
214229 }
215230
@@ -227,17 +242,28 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
227242 inj. add_unmapped ( "\n " ) ;
228243 }
229244 }
245+
246+ for range in intra_doc_links {
247+ hl. add ( HlRange {
248+ range,
249+ highlight : HlTag :: IntraDocLink | HlMod :: Documentation ,
250+ binding_hash : None ,
251+ } ) ;
252+ }
253+
254+ if new_comments. is_empty ( ) {
255+ return ; // no need to run an analysis on an empty file
256+ }
257+
230258 inj. add_unmapped ( "\n }" ) ;
231259
232260 let ( analysis, tmp_file_id) = Analysis :: from_single_file ( inj. text ( ) . to_string ( ) ) ;
233261
234- for h in analysis. with_db ( |db| super :: highlight ( db, tmp_file_id, None , true ) ) . unwrap ( ) {
235- for r in inj. map_range_up ( h. range ) {
236- hl. add ( HlRange {
237- range : r,
238- highlight : h. highlight | HlMod :: Injected ,
239- binding_hash : h. binding_hash ,
240- } ) ;
262+ for HlRange { range, highlight, binding_hash } in
263+ analysis. with_db ( |db| super :: highlight ( db, tmp_file_id, None , true ) ) . unwrap ( )
264+ {
265+ for range in inj. map_range_up ( range) {
266+ hl. add ( HlRange { range, highlight : highlight | HlMod :: Injected , binding_hash } ) ;
241267 }
242268 }
243269
@@ -273,3 +299,31 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
273299 }
274300 }
275301}
302+
303+ fn validate_intra_doc_link (
304+ db : & RootDatabase ,
305+ def : & Definition ,
306+ link : & str ,
307+ ns : Option < hir:: Namespace > ,
308+ ) -> bool {
309+ match def {
310+ Definition :: ModuleDef ( def) => match def {
311+ hir:: ModuleDef :: Module ( it) => it. resolve_doc_path ( db, & link, ns) ,
312+ hir:: ModuleDef :: Function ( it) => it. resolve_doc_path ( db, & link, ns) ,
313+ hir:: ModuleDef :: Adt ( it) => it. resolve_doc_path ( db, & link, ns) ,
314+ hir:: ModuleDef :: Variant ( it) => it. resolve_doc_path ( db, & link, ns) ,
315+ hir:: ModuleDef :: Const ( it) => it. resolve_doc_path ( db, & link, ns) ,
316+ hir:: ModuleDef :: Static ( it) => it. resolve_doc_path ( db, & link, ns) ,
317+ hir:: ModuleDef :: Trait ( it) => it. resolve_doc_path ( db, & link, ns) ,
318+ hir:: ModuleDef :: TypeAlias ( it) => it. resolve_doc_path ( db, & link, ns) ,
319+ hir:: ModuleDef :: BuiltinType ( _) => None ,
320+ } ,
321+ Definition :: Macro ( it) => it. resolve_doc_path ( db, & link, ns) ,
322+ Definition :: Field ( it) => it. resolve_doc_path ( db, & link, ns) ,
323+ Definition :: SelfType ( _)
324+ | Definition :: Local ( _)
325+ | Definition :: GenericParam ( _)
326+ | Definition :: Label ( _) => None ,
327+ }
328+ . is_some ( )
329+ }
0 commit comments