diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index 30642f6cc6..a244d32149 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs @@ -390,7 +390,9 @@ impl AttrsWithOwner { if let InFile { file_id, value: ModuleSource::SourceFile(file) } = mod_data.definition_source(db) { - map.merge(AttrSourceMap::new(InFile::new(file_id, &file))); + map.append_module_inline_attrs(AttrSourceMap::new(InFile::new( + file_id, &file, + ))); } return map; } @@ -552,6 +554,11 @@ fn inner_attributes( pub struct AttrSourceMap { source: Vec>, file_id: HirFileId, + /// If this map is for a module, this will be the [`HirFileId`] of the module's definition site, + /// while `file_id` will be the one of the module declaration site. + /// The usize is the index into `source` from which point on the entries reside in the def site + /// file. + mod_def_site_file_id: Option<(HirFileId, usize)>, } impl AttrSourceMap { @@ -559,11 +566,19 @@ impl AttrSourceMap { Self { source: collect_attrs(owner.value).map(|(_, it)| it).collect(), file_id: owner.file_id, + mod_def_site_file_id: None, } } - fn merge(&mut self, other: Self) { + /// Append a second source map to this one, this is required for modules, whose outline and inline + /// attributes can reside in different files + fn append_module_inline_attrs(&mut self, other: Self) { + assert!(self.mod_def_site_file_id.is_none() && other.mod_def_site_file_id.is_none()); + let len = self.source.len(); self.source.extend(other.source); + if other.file_id != self.file_id { + self.mod_def_site_file_id = Some((other.file_id, len)); + } } /// Maps the lowered `Attr` back to its original syntax node. @@ -577,9 +592,15 @@ impl AttrSourceMap { } fn source_of_id(&self, id: AttrId) -> InFile<&Either> { + let ast_idx = id.ast_index as usize; + let file_id = match self.mod_def_site_file_id { + Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id, + _ => self.file_id, + }; + self.source - .get(id.ast_index as usize) - .map(|it| InFile::new(self.file_id, it)) + .get(ast_idx) + .map(|it| InFile::new(file_id, it)) .unwrap_or_else(|| panic!("cannot find attr at index {:?}", id)) } } 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 6e87147e8b..a04a211825 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -46,6 +46,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd //! fn test() {} //! ``` +mod outline_module; + /// ``` /// let _ = "early doctests should not go boom"; /// ``` @@ -170,4 +172,6 @@ It is beyond me why you'd use these when you got /// ``` [`block_comments`] tests these without indentation */ -pub fn block_comments2() {} \ No newline at end of file +pub fn block_comments2() {} + + \ 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 c9c23318f5..4beab9909c 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -641,11 +641,14 @@ fn main() { fn test_highlight_doc_comment() { check_highlighting( r#" +//- /main.rs //! This is a module to test doc injection. //! ``` //! fn test() {} //! ``` +mod outline_module; + /// ``` /// let _ = "early doctests should not go boom"; /// ``` @@ -771,6 +774,13 @@ pub fn block_comments() {} [`block_comments`] tests these without indentation */ pub fn block_comments2() {} + +//- /outline_module.rs +//! This is an outline module whose purpose is to test that its inline attribute injection does not +//! spill into its parent. +//! ``` +//! fn test() {} +//! ``` "# .trim(), expect_file!["./test_data/highlight_doctest.html"],