aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock45
-rw-r--r--crates/hir/Cargo.toml2
-rw-r--r--crates/hir/src/code_model.rs103
-rw-r--r--crates/hir/src/doc_links.rs233
-rw-r--r--crates/hir/src/lib.rs7
-rw-r--r--crates/ide/Cargo.toml2
-rw-r--r--crates/ide/src/hover.rs597
-rw-r--r--crates/ide/src/lib.rs1
-rw-r--r--crates/ide/src/link_rewrite.rs81
-rw-r--r--crates/ide/src/mock_analysis.rs2
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/main.rs2
-rw-r--r--xtask/tests/tidy.rs1
12 files changed, 1015 insertions, 61 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 69affa0ea..015f84ba8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -497,6 +497,8 @@ dependencies = [
497 "rustc-hash", 497 "rustc-hash",
498 "stdx", 498 "stdx",
499 "syntax", 499 "syntax",
500 "tt",
501 "url",
500] 502]
501 503
502[[package]] 504[[package]]
@@ -596,6 +598,8 @@ dependencies = [
596 "log", 598 "log",
597 "oorandom", 599 "oorandom",
598 "profile", 600 "profile",
601 "pulldown-cmark",
602 "pulldown-cmark-to-cmark",
599 "rustc-hash", 603 "rustc-hash",
600 "ssr", 604 "ssr",
601 "stdx", 605 "stdx",
@@ -825,6 +829,12 @@ dependencies = [
825] 829]
826 830
827[[package]] 831[[package]]
832name = "memchr"
833version = "2.3.3"
834source = "registry+https://github.com/rust-lang/crates.io-index"
835checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
836
837[[package]]
828name = "memmap" 838name = "memmap"
829version = "0.7.0" 839version = "0.7.0"
830source = "registry+https://github.com/rust-lang/crates.io-index" 840source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1141,6 +1151,26 @@ dependencies = [
1141] 1151]
1142 1152
1143[[package]] 1153[[package]]
1154name = "pulldown-cmark"
1155version = "0.7.2"
1156source = "registry+https://github.com/rust-lang/crates.io-index"
1157checksum = "ca36dea94d187597e104a5c8e4b07576a8a45aa5db48a65e12940d3eb7461f55"
1158dependencies = [
1159 "bitflags",
1160 "memchr",
1161 "unicase",
1162]
1163
1164[[package]]
1165name = "pulldown-cmark-to-cmark"
1166version = "5.0.0"
1167source = "registry+https://github.com/rust-lang/crates.io-index"
1168checksum = "32accf4473121d8c0b508ca5673363703762d6cc59cf25af1df48f653346f736"
1169dependencies = [
1170 "pulldown-cmark",
1171]
1172
1173[[package]]
1144name = "quote" 1174name = "quote"
1145version = "1.0.7" 1175version = "1.0.7"
1146source = "registry+https://github.com/rust-lang/crates.io-index" 1176source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1693,6 +1723,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1693checksum = "ca4d39065b45f658d33013f7cc93ee050708cd543f6e07dd15b4293fcf217e12" 1723checksum = "ca4d39065b45f658d33013f7cc93ee050708cd543f6e07dd15b4293fcf217e12"
1694 1724
1695[[package]] 1725[[package]]
1726name = "unicase"
1727version = "2.6.0"
1728source = "registry+https://github.com/rust-lang/crates.io-index"
1729checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
1730dependencies = [
1731 "version_check",
1732]
1733
1734[[package]]
1696name = "unicode-bidi" 1735name = "unicode-bidi"
1697version = "0.3.4" 1736version = "0.3.4"
1698source = "registry+https://github.com/rust-lang/crates.io-index" 1737source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1735,6 +1774,12 @@ dependencies = [
1735] 1774]
1736 1775
1737[[package]] 1776[[package]]
1777name = "version_check"
1778version = "0.9.2"
1779source = "registry+https://github.com/rust-lang/crates.io-index"
1780checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1781
1782[[package]]
1738name = "vfs" 1783name = "vfs"
1739version = "0.0.0" 1784version = "0.0.0"
1740dependencies = [ 1785dependencies = [
diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml
index 60a48170e..72f941c46 100644
--- a/crates/hir/Cargo.toml
+++ b/crates/hir/Cargo.toml
@@ -15,6 +15,7 @@ rustc-hash = "1.1.0"
15either = "1.5.3" 15either = "1.5.3"
16arrayvec = "0.5.1" 16arrayvec = "0.5.1"
17itertools = "0.9.0" 17itertools = "0.9.0"
18url = "2.1.1"
18 19
19stdx = { path = "../stdx", version = "0.0.0" } 20stdx = { path = "../stdx", version = "0.0.0" }
20syntax = { path = "../syntax", version = "0.0.0" } 21syntax = { path = "../syntax", version = "0.0.0" }
@@ -23,3 +24,4 @@ profile = { path = "../profile", version = "0.0.0" }
23hir_expand = { path = "../hir_expand", version = "0.0.0" } 24hir_expand = { path = "../hir_expand", version = "0.0.0" }
24hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
25hir_ty = { path = "../hir_ty", version = "0.0.0" } 26hir_ty = { path = "../hir_ty", version = "0.0.0" }
27tt = { path = "../tt", version = "0.0.0" }
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 3d92d0c0d..94dd7f6f5 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -20,7 +20,7 @@ use hir_def::{
20 type_ref::{Mutability, TypeRef}, 20 type_ref::{Mutability, TypeRef},
21 AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, 21 AdtId, AssocContainerId, ConstId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule,
22 ImplId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StaticId, StructId, 22 ImplId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StaticId, StructId,
23 TraitId, TypeAliasId, TypeParamId, UnionId, 23 TraitId, TypeAliasId, TypeParamId, UnionId, VariantId,
24}; 24};
25use hir_expand::{ 25use hir_expand::{
26 diagnostics::DiagnosticSink, 26 diagnostics::DiagnosticSink,
@@ -39,9 +39,11 @@ use syntax::{
39 ast::{self, AttrsOwner, NameOwner}, 39 ast::{self, AttrsOwner, NameOwner},
40 AstNode, SmolStr, 40 AstNode, SmolStr,
41}; 41};
42use tt::{Ident, Leaf, Literal, TokenTree};
42 43
43use crate::{ 44use crate::{
44 db::{DefDatabase, HirDatabase}, 45 db::{DefDatabase, HirDatabase},
46 doc_links::Resolvable,
45 has_source::HasSource, 47 has_source::HasSource,
46 HirDisplay, InFile, Name, 48 HirDisplay, InFile, Name,
47}; 49};
@@ -122,6 +124,31 @@ impl Crate {
122 pub fn all(db: &dyn HirDatabase) -> Vec<Crate> { 124 pub fn all(db: &dyn HirDatabase) -> Vec<Crate> {
123 db.crate_graph().iter().map(|id| Crate { id }).collect() 125 db.crate_graph().iter().map(|id| Crate { id }).collect()
124 } 126 }
127
128 /// Try to get the root URL of the documentation of a crate.
129 pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option<String> {
130 // Look for #![doc(html_root_url = "...")]
131 let attrs = db.attrs(AttrDef::from(self.root_module(db)).into());
132 let doc_attr_q = attrs.by_key("doc");
133
134 if !doc_attr_q.exists() {
135 return None;
136 }
137
138 let doc_url = doc_attr_q.tt_values().map(|tt| {
139 let name = tt.token_trees.iter()
140 .skip_while(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident, ..})) if ident == "html_root_url"))
141 .skip(2)
142 .next();
143
144 match name {
145 Some(TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..}))) => Some(text),
146 _ => None
147 }
148 }).flat_map(|t| t).next();
149
150 doc_url.map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/")
151 }
125} 152}
126 153
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -198,7 +225,6 @@ impl ModuleDef {
198 ModuleDef::Function(it) => Some(it.name(db)), 225 ModuleDef::Function(it) => Some(it.name(db)),
199 ModuleDef::EnumVariant(it) => Some(it.name(db)), 226 ModuleDef::EnumVariant(it) => Some(it.name(db)),
200 ModuleDef::TypeAlias(it) => Some(it.name(db)), 227 ModuleDef::TypeAlias(it) => Some(it.name(db)),
201
202 ModuleDef::Module(it) => it.name(db), 228 ModuleDef::Module(it) => it.name(db),
203 ModuleDef::Const(it) => it.name(db), 229 ModuleDef::Const(it) => it.name(db),
204 ModuleDef::Static(it) => it.name(db), 230 ModuleDef::Static(it) => it.name(db),
@@ -1771,3 +1797,76 @@ pub trait HasVisibility {
1771 vis.is_visible_from(db.upcast(), module.id) 1797 vis.is_visible_from(db.upcast(), module.id)
1772 } 1798 }
1773} 1799}
1800
1801impl Resolvable for ModuleDef {
1802 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
1803 Some(match self {
1804 ModuleDef::Module(m) => ModuleId::from(m.clone()).resolver(db),
1805 ModuleDef::Function(f) => FunctionId::from(f.clone()).resolver(db),
1806 ModuleDef::Adt(adt) => AdtId::from(adt.clone()).resolver(db),
1807 ModuleDef::EnumVariant(ev) => {
1808 GenericDefId::from(GenericDef::from(ev.clone())).resolver(db)
1809 }
1810 ModuleDef::Const(c) => GenericDefId::from(GenericDef::from(c.clone())).resolver(db),
1811 ModuleDef::Static(s) => StaticId::from(s.clone()).resolver(db),
1812 ModuleDef::Trait(t) => TraitId::from(t.clone()).resolver(db),
1813 ModuleDef::TypeAlias(t) => ModuleId::from(t.module(db)).resolver(db),
1814 // FIXME: This should be a resolver relative to `std/core`
1815 ModuleDef::BuiltinType(_t) => None?,
1816 })
1817 }
1818
1819 fn try_into_module_def(self) -> Option<ModuleDef> {
1820 Some(self)
1821 }
1822}
1823
1824impl Resolvable for TypeParam {
1825 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
1826 Some(Into::<ModuleId>::into(self.module(db)).resolver(db))
1827 }
1828
1829 fn try_into_module_def(self) -> Option<ModuleDef> {
1830 None
1831 }
1832}
1833
1834impl Resolvable for MacroDef {
1835 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
1836 Some(Into::<ModuleId>::into(self.module(db)?).resolver(db))
1837 }
1838
1839 fn try_into_module_def(self) -> Option<ModuleDef> {
1840 None
1841 }
1842}
1843
1844impl Resolvable for Field {
1845 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
1846 Some(Into::<VariantId>::into(Into::<VariantDef>::into(self.parent_def(db))).resolver(db))
1847 }
1848
1849 fn try_into_module_def(self) -> Option<ModuleDef> {
1850 None
1851 }
1852}
1853
1854impl Resolvable for ImplDef {
1855 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
1856 Some(Into::<ModuleId>::into(self.module(db)).resolver(db))
1857 }
1858
1859 fn try_into_module_def(self) -> Option<ModuleDef> {
1860 None
1861 }
1862}
1863
1864impl Resolvable for Local {
1865 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver> {
1866 Some(Into::<ModuleId>::into(self.module(db)).resolver(db))
1867 }
1868
1869 fn try_into_module_def(self) -> Option<ModuleDef> {
1870 None
1871 }
1872}
diff --git a/crates/hir/src/doc_links.rs b/crates/hir/src/doc_links.rs
new file mode 100644
index 000000000..dd2379bfc
--- /dev/null
+++ b/crates/hir/src/doc_links.rs
@@ -0,0 +1,233 @@
1//! Resolves links in markdown documentation.
2
3use std::iter::once;
4
5use itertools::Itertools;
6use url::Url;
7
8use crate::{db::HirDatabase, Adt, AsName, Crate, Hygiene, ItemInNs, ModPath, ModuleDef};
9use hir_def::{db::DefDatabase, resolver::Resolver};
10use syntax::ast::Path;
11
12pub fn resolve_doc_link<T: Resolvable + Clone, D: DefDatabase + HirDatabase>(
13 db: &D,
14 definition: &T,
15 link_text: &str,
16 link_target: &str,
17) -> Option<(String, String)> {
18 try_resolve_intra(db, definition, link_text, &link_target).or_else(|| {
19 if let Some(definition) = definition.clone().try_into_module_def() {
20 try_resolve_path(db, &definition, &link_target)
21 .map(|target| (target, link_text.to_string()))
22 } else {
23 None
24 }
25 })
26}
27
28/// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`).
29///
30/// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md).
31fn try_resolve_intra<T: Resolvable, D: DefDatabase + HirDatabase>(
32 db: &D,
33 definition: &T,
34 link_text: &str,
35 link_target: &str,
36) -> Option<(String, String)> {
37 // Set link_target for implied shortlinks
38 let link_target =
39 if link_target.is_empty() { link_text.trim_matches('`') } else { link_target };
40
41 let doclink = IntraDocLink::from(link_target);
42
43 // Parse link as a module path
44 let path = Path::parse(doclink.path).ok()?;
45 let modpath = ModPath::from_src(path, &Hygiene::new_unhygienic()).unwrap();
46
47 // Resolve it relative to symbol's location (according to the RFC this should consider small scopes)
48 let resolver = definition.resolver(db)?;
49
50 let resolved = resolver.resolve_module_path_in_items(db, &modpath);
51 let (defid, namespace) = match doclink.namespace {
52 // FIXME: .or(resolved.macros)
53 None => resolved
54 .types
55 .map(|t| (t.0, Namespace::Types))
56 .or(resolved.values.map(|t| (t.0, Namespace::Values)))?,
57 Some(ns @ Namespace::Types) => (resolved.types?.0, ns),
58 Some(ns @ Namespace::Values) => (resolved.values?.0, ns),
59 // FIXME:
60 Some(Namespace::Macros) => None?,
61 };
62
63 // Get the filepath of the final symbol
64 let def: ModuleDef = defid.into();
65 let module = def.module(db)?;
66 let krate = module.krate();
67 let ns = match namespace {
68 Namespace::Types => ItemInNs::Types(defid),
69 Namespace::Values => ItemInNs::Values(defid),
70 // FIXME:
71 Namespace::Macros => None?,
72 };
73 let import_map = db.import_map(krate.into());
74 let path = import_map.path_of(ns)?;
75
76 Some((
77 get_doc_url(db, &krate)?
78 .join(&format!("{}/", krate.display_name(db)?))
79 .ok()?
80 .join(&path.segments.iter().map(|name| name.to_string()).join("/"))
81 .ok()?
82 .join(&get_symbol_filename(db, &def)?)
83 .ok()?
84 .into_string(),
85 strip_prefixes_suffixes(link_text).to_string(),
86 ))
87}
88
89/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
90fn try_resolve_path(db: &dyn HirDatabase, moddef: &ModuleDef, link_target: &str) -> Option<String> {
91 if !link_target.contains("#") && !link_target.contains(".html") {
92 return None;
93 }
94 let ns = ItemInNs::Types(moddef.clone().into());
95
96 let module = moddef.module(db)?;
97 let krate = module.krate();
98 let import_map = db.import_map(krate.into());
99 let base = once(format!("{}", krate.display_name(db)?))
100 .chain(import_map.path_of(ns)?.segments.iter().map(|name| format!("{}", name)))
101 .join("/");
102
103 get_doc_url(db, &krate)
104 .and_then(|url| url.join(&base).ok())
105 .and_then(|url| {
106 get_symbol_filename(db, moddef).as_deref().map(|f| url.join(f).ok()).flatten()
107 })
108 .and_then(|url| url.join(link_target).ok())
109 .map(|url| url.into_string())
110}
111
112/// Strip prefixes, suffixes, and inline code marks from the given string.
113fn strip_prefixes_suffixes(mut s: &str) -> &str {
114 s = s.trim_matches('`');
115
116 [
117 (TYPES.0.iter(), TYPES.1.iter()),
118 (VALUES.0.iter(), VALUES.1.iter()),
119 (MACROS.0.iter(), MACROS.1.iter()),
120 ]
121 .iter()
122 .for_each(|(prefixes, suffixes)| {
123 prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
124 suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
125 });
126 let s = s.trim_start_matches("@").trim();
127 s
128}
129
130fn get_doc_url(db: &dyn HirDatabase, krate: &Crate) -> Option<Url> {
131 krate
132 .get_html_root_url(db)
133 .or_else(||
134 // Fallback to docs.rs
135 // FIXME: Specify an exact version here. This may be difficult, as multiple versions of the same crate could exist.
136 Some(format!("https://docs.rs/{}/*/", krate.display_name(db)?)))
137 .and_then(|s| Url::parse(&s).ok())
138}
139
140/// Get the filename and extension generated for a symbol by rustdoc.
141///
142/// Example: `struct.Shard.html`
143fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
144 Some(match definition {
145 ModuleDef::Adt(adt) => match adt {
146 Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
147 Adt::Enum(e) => format!("enum.{}.html", e.name(db)),
148 Adt::Union(u) => format!("union.{}.html", u.name(db)),
149 },
150 ModuleDef::Module(_) => "index.html".to_string(),
151 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
152 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
153 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
154 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
155 ModuleDef::EnumVariant(ev) => {
156 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
157 }
158 ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?),
159 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
160 })
161}
162
163struct IntraDocLink<'s> {
164 path: &'s str,
165 namespace: Option<Namespace>,
166}
167
168impl<'s> From<&'s str> for IntraDocLink<'s> {
169 fn from(s: &'s str) -> Self {
170 Self { path: strip_prefixes_suffixes(s), namespace: Namespace::from_intra_spec(s) }
171 }
172}
173
174#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
175enum Namespace {
176 Types,
177 Values,
178 Macros,
179}
180
181static TYPES: ([&str; 7], [&str; 0]) =
182 (["type", "struct", "enum", "mod", "trait", "union", "module"], []);
183static VALUES: ([&str; 8], [&str; 1]) =
184 (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]);
185static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]);
186
187impl Namespace {
188 /// Extract the specified namespace from an intra-doc-link if one exists.
189 ///
190 /// # Examples
191 ///
192 /// * `struct MyStruct` -> `Namespace::Types`
193 /// * `panic!` -> `Namespace::Macros`
194 /// * `fn@from_intra_spec` -> `Namespace::Values`
195 fn from_intra_spec(s: &str) -> Option<Self> {
196 [
197 (Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())),
198 (Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())),
199 (Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())),
200 ]
201 .iter()
202 .filter(|(_ns, (prefixes, suffixes))| {
203 prefixes
204 .clone()
205 .map(|prefix| {
206 s.starts_with(*prefix)
207 && s.chars()
208 .nth(prefix.len() + 1)
209 .map(|c| c == '@' || c == ' ')
210 .unwrap_or(false)
211 })
212 .any(|cond| cond)
213 || suffixes
214 .clone()
215 .map(|suffix| {
216 s.starts_with(*suffix)
217 && s.chars()
218 .nth(suffix.len() + 1)
219 .map(|c| c == '@' || c == ' ')
220 .unwrap_or(false)
221 })
222 .any(|cond| cond)
223 })
224 .map(|(ns, (_, _))| *ns)
225 .next()
226 }
227}
228
229/// Sealed trait used solely for the generic bound on [`resolve_doc_link`].
230pub trait Resolvable {
231 fn resolver<D: DefDatabase + HirDatabase>(&self, db: &D) -> Option<Resolver>;
232 fn try_into_module_def(self) -> Option<ModuleDef>;
233}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 78d8651cb..d1f4d7813 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -27,6 +27,7 @@ pub mod diagnostics;
27 27
28mod from_id; 28mod from_id;
29mod code_model; 29mod code_model;
30mod doc_links;
30 31
31mod has_source; 32mod has_source;
32 33
@@ -37,6 +38,7 @@ pub use crate::{
37 Function, GenericDef, HasAttrs, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, 38 Function, GenericDef, HasAttrs, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef,
38 ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility, 39 ScopeDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility,
39 }, 40 },
41 doc_links::resolve_doc_link,
40 has_source::HasSource, 42 has_source::HasSource,
41 semantics::{original_range, PathResolution, Semantics, SemanticsScope}, 43 semantics::{original_range, PathResolution, Semantics, SemanticsScope},
42}; 44};
@@ -47,13 +49,14 @@ pub use hir_def::{
47 body::scope::ExprScopes, 49 body::scope::ExprScopes,
48 builtin_type::BuiltinType, 50 builtin_type::BuiltinType,
49 docs::Documentation, 51 docs::Documentation,
52 item_scope::ItemInNs,
50 nameres::ModuleSource, 53 nameres::ModuleSource,
51 path::ModPath, 54 path::ModPath,
52 type_ref::{Mutability, TypeRef}, 55 type_ref::{Mutability, TypeRef},
53}; 56};
54pub use hir_expand::{ 57pub use hir_expand::{
55 name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc, /* FIXME */ MacroDefId, 58 name::AsName, name::Name, HirFileId, InFile, MacroCallId, MacroCallLoc,
56 MacroFile, Origin, 59 /* FIXME */ MacroDefId, MacroFile, Origin,
57}; 60};
58pub use hir_ty::display::HirDisplay; 61pub use hir_ty::display::HirDisplay;
59 62
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index 336e9d2aa..e61c276df 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -16,6 +16,8 @@ itertools = "0.9.0"
16log = "0.4.8" 16log = "0.4.8"
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18oorandom = "11.1.2" 18oorandom = "11.1.2"
19pulldown-cmark-to-cmark = "5.0.0"
20pulldown-cmark = {version = "0.7.2", default-features = false}
19 21
20stdx = { path = "../stdx", version = "0.0.0" } 22stdx = { path = "../stdx", version = "0.0.0" }
21syntax = { path = "../syntax", version = "0.0.0" } 23syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index c75b2a510..b012e4900 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,6 +14,7 @@ use test_utils::mark;
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::rewrite_links,
17 markup::Markup, 18 markup::Markup,
18 runnables::runnable, 19 runnables::runnable,
19 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
@@ -92,7 +93,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
92 }; 93 };
93 if let Some(definition) = definition { 94 if let Some(definition) = definition {
94 if let Some(markup) = hover_for_definition(db, definition) { 95 if let Some(markup) = hover_for_definition(db, definition) {
95 res.markup = markup; 96 let markup = rewrite_links(db, &markup.as_str(), &definition);
97 res.markup = Markup::from(markup);
96 if let Some(action) = show_implementations_action(db, definition) { 98 if let Some(action) = show_implementations_action(db, definition) {
97 res.actions.push(action); 99 res.actions.push(action);
98 } 100 }
@@ -425,6 +427,7 @@ fn main() {
425"#, 427"#,
426 expect![[r#" 428 expect![[r#"
427 *iter* 429 *iter*
430
428 ```rust 431 ```rust
429 Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>> 432 Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
430 ``` 433 ```
@@ -443,6 +446,11 @@ fn main() { let foo_test = fo<|>o(); }
443"#, 446"#,
444 expect![[r#" 447 expect![[r#"
445 *foo* 448 *foo*
449
450 ```rust
451 test
452 ```
453
446 ```rust 454 ```rust
447 pub fn foo() -> u32 455 pub fn foo() -> u32
448 ``` 456 ```
@@ -487,6 +495,11 @@ fn main() { let foo_test = fo<|>o(); }
487 "#, 495 "#,
488 expect![[r#" 496 expect![[r#"
489 *foo* 497 *foo*
498
499 ```rust
500 test
501 ```
502
490 ```rust 503 ```rust
491 pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str 504 pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str
492 ``` 505 ```
@@ -504,6 +517,11 @@ fn main() { }
504"#, 517"#,
505 expect![[r#" 518 expect![[r#"
506 *foo* 519 *foo*
520
521 ```rust
522 test
523 ```
524
507 ```rust 525 ```rust
508 pub fn foo(a: u32, b: u32) -> u32 526 pub fn foo(a: u32, b: u32) -> u32
509 ``` 527 ```
@@ -525,20 +543,27 @@ pub fn foo<|>(_: &Path) {}
525 543
526fn main() { } 544fn main() { }
527"#, 545"#,
528 expect![[r#" 546 expect![[r##"
529 *foo* 547 *foo*
548
549 ```rust
550 test
551 ```
552
530 ```rust 553 ```rust
531 pub fn foo(_: &Path) 554 pub fn foo(_: &Path)
532 ``` 555 ```
533 ___ 556
557 ---
534 558
535 # Example 559 # Example
560
536 ``` 561 ```
537 # use std::path::Path; 562 # use std::path::Path;
538 # 563 #
539 foo(Path::new("hello, world!")) 564 foo(Path::new("hello, world!"))
540 ``` 565 ```
541 "#]], 566 "##]],
542 ); 567 );
543 } 568 }
544 569
@@ -555,8 +580,9 @@ fn main() {
555"#, 580"#,
556 expect![[r#" 581 expect![[r#"
557 *field_a* 582 *field_a*
583
558 ```rust 584 ```rust
559 Foo 585 test::Foo
560 ``` 586 ```
561 587
562 ```rust 588 ```rust
@@ -576,8 +602,9 @@ fn main() {
576"#, 602"#,
577 expect![[r#" 603 expect![[r#"
578 *field_a* 604 *field_a*
605
579 ```rust 606 ```rust
580 Foo 607 test::Foo
581 ``` 608 ```
582 609
583 ```rust 610 ```rust
@@ -593,6 +620,11 @@ fn main() {
593 r#"const foo<|>: u32 = 123;"#, 620 r#"const foo<|>: u32 = 123;"#,
594 expect![[r#" 621 expect![[r#"
595 *foo* 622 *foo*
623
624 ```rust
625 test
626 ```
627
596 ```rust 628 ```rust
597 const foo: u32 = 123 629 const foo: u32 = 123
598 ``` 630 ```
@@ -602,6 +634,11 @@ fn main() {
602 r#"static foo<|>: u32 = 456;"#, 634 r#"static foo<|>: u32 = 456;"#,
603 expect![[r#" 635 expect![[r#"
604 *foo* 636 *foo*
637
638 ```rust
639 test
640 ```
641
605 ```rust 642 ```rust
606 static foo: u32 643 static foo: u32
607 ``` 644 ```
@@ -620,6 +657,7 @@ fn main() {
620}"#, 657}"#,
621 expect![[r#" 658 expect![[r#"
622 *zz* 659 *zz*
660
623 ```rust 661 ```rust
624 Test<i32, u8> 662 Test<i32, u8>
625 ``` 663 ```
@@ -638,8 +676,9 @@ fn main() { So<|>me(12); }
638"#, 676"#,
639 expect![[r#" 677 expect![[r#"
640 *Some* 678 *Some*
679
641 ```rust 680 ```rust
642 Option 681 test::Option
643 ``` 682 ```
644 683
645 ```rust 684 ```rust
@@ -657,6 +696,7 @@ fn main() { let b<|>ar = Some(12); }
657"#, 696"#,
658 expect![[r#" 697 expect![[r#"
659 *bar* 698 *bar*
699
660 ```rust 700 ```rust
661 Option<i32> 701 Option<i32>
662 ``` 702 ```
@@ -675,14 +715,16 @@ enum Option<T> {
675"#, 715"#,
676 expect![[r#" 716 expect![[r#"
677 *None* 717 *None*
718
678 ```rust 719 ```rust
679 Option 720 test::Option
680 ``` 721 ```
681 722
682 ```rust 723 ```rust
683 None 724 None
684 ``` 725 ```
685 ___ 726
727 ---
686 728
687 The None variant 729 The None variant
688 "#]], 730 "#]],
@@ -700,14 +742,16 @@ fn main() {
700"#, 742"#,
701 expect![[r#" 743 expect![[r#"
702 *Some* 744 *Some*
745
703 ```rust 746 ```rust
704 Option 747 test::Option
705 ``` 748 ```
706 749
707 ```rust 750 ```rust
708 Some 751 Some
709 ``` 752 ```
710 ___ 753
754 ---
711 755
712 The Some variant 756 The Some variant
713 "#]], 757 "#]],
@@ -720,6 +764,7 @@ fn main() {
720 r#"fn func(foo: i32) { fo<|>o; }"#, 764 r#"fn func(foo: i32) { fo<|>o; }"#,
721 expect![[r#" 765 expect![[r#"
722 *foo* 766 *foo*
767
723 ```rust 768 ```rust
724 i32 769 i32
725 ``` 770 ```
@@ -733,6 +778,7 @@ fn main() {
733 r#"fn func(fo<|>o: i32) {}"#, 778 r#"fn func(fo<|>o: i32) {}"#,
734 expect![[r#" 779 expect![[r#"
735 *foo* 780 *foo*
781
736 ```rust 782 ```rust
737 i32 783 i32
738 ``` 784 ```
@@ -746,6 +792,7 @@ fn main() {
746 r#"fn func(foo: i32) { if true { <|>foo; }; }"#, 792 r#"fn func(foo: i32) { if true { <|>foo; }; }"#,
747 expect![[r#" 793 expect![[r#"
748 *foo* 794 *foo*
795
749 ```rust 796 ```rust
750 i32 797 i32
751 ``` 798 ```
@@ -759,6 +806,7 @@ fn main() {
759 r#"fn func(<|>foo: i32) {}"#, 806 r#"fn func(<|>foo: i32) {}"#,
760 expect![[r#" 807 expect![[r#"
761 *foo* 808 *foo*
809
762 ```rust 810 ```rust
763 i32 811 i32
764 ``` 812 ```
@@ -778,6 +826,7 @@ fn main() {
778 fn f(_x<|>: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#, 826 fn f(_x<|>: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
779 expect![[r#" 827 expect![[r#"
780 *_x* 828 *_x*
829
781 ```rust 830 ```rust
782 impl Deref<Target = u8> + DerefMut<Target = u8> 831 impl Deref<Target = u8> + DerefMut<Target = u8>
783 ``` 832 ```
@@ -799,6 +848,7 @@ fn main() { let foo_<|>test = Thing::new(); }
799 "#, 848 "#,
800 expect![[r#" 849 expect![[r#"
801 *foo_test* 850 *foo_test*
851
802 ```rust 852 ```rust
803 Thing 853 Thing
804 ``` 854 ```
@@ -822,8 +872,9 @@ fn main() { let foo_test = wrapper::Thing::new<|>(); }
822"#, 872"#,
823 expect![[r#" 873 expect![[r#"
824 *new* 874 *new*
875
825 ```rust 876 ```rust
826 wrapper::Thing 877 test::wrapper::Thing
827 ``` 878 ```
828 879
829 ```rust 880 ```rust
@@ -852,6 +903,11 @@ fn main() {
852"#, 903"#,
853 expect![[r#" 904 expect![[r#"
854 *C* 905 *C*
906
907 ```rust
908 test
909 ```
910
855 ```rust 911 ```rust
856 const C: u32 = 1 912 const C: u32 = 1
857 ``` 913 ```
@@ -929,6 +985,7 @@ fn y() {
929"#, 985"#,
930 expect![[r#" 986 expect![[r#"
931 *x* 987 *x*
988
932 ```rust 989 ```rust
933 i32 990 i32
934 ``` 991 ```
@@ -946,6 +1003,11 @@ fn f() { fo<|>o!(); }
946"#, 1003"#,
947 expect![[r#" 1004 expect![[r#"
948 *foo* 1005 *foo*
1006
1007 ```rust
1008 test
1009 ```
1010
949 ```rust 1011 ```rust
950 macro_rules! foo 1012 macro_rules! foo
951 ``` 1013 ```
@@ -976,6 +1038,11 @@ id! {
976"#, 1038"#,
977 expect![[r#" 1039 expect![[r#"
978 *foo* 1040 *foo*
1041
1042 ```rust
1043 test
1044 ```
1045
979 ```rust 1046 ```rust
980 fn foo() 1047 fn foo()
981 ``` 1048 ```
@@ -992,6 +1059,7 @@ fn foo(bar:u32) { let a = id!(ba<|>r); }
992"#, 1059"#,
993 expect![[r#" 1060 expect![[r#"
994 *bar* 1061 *bar*
1062
995 ```rust 1063 ```rust
996 u32 1064 u32
997 ``` 1065 ```
@@ -1009,6 +1077,7 @@ fn foo(bar:u32) { let a = id!(ba<|>r); }
1009"#, 1077"#,
1010 expect![[r#" 1078 expect![[r#"
1011 *bar* 1079 *bar*
1080
1012 ```rust 1081 ```rust
1013 u32 1082 u32
1014 ``` 1083 ```
@@ -1067,6 +1136,11 @@ fn foo() {
1067"#, 1136"#,
1068 expect![[r#" 1137 expect![[r#"
1069 *bar* 1138 *bar*
1139
1140 ```rust
1141 test
1142 ```
1143
1070 ```rust 1144 ```rust
1071 fn bar() -> bool 1145 fn bar() -> bool
1072 ``` 1146 ```
@@ -1099,12 +1173,18 @@ fn bar() { fo<|>o(); }
1099", 1173",
1100 expect![[r#" 1174 expect![[r#"
1101 *foo* 1175 *foo*
1176
1177 ```rust
1178 test
1179 ```
1180
1102 ```rust 1181 ```rust
1103 fn foo() 1182 fn foo()
1104 ``` 1183 ```
1105 ___
1106 1184
1107 <- ` ` here 1185 ---
1186
1187 \<- ` ` here
1108 "#]], 1188 "#]],
1109 ); 1189 );
1110 } 1190 }
@@ -1115,6 +1195,11 @@ fn bar() { fo<|>o(); }
1115 r#"async fn foo<|>() {}"#, 1195 r#"async fn foo<|>() {}"#,
1116 expect![[r#" 1196 expect![[r#"
1117 *foo* 1197 *foo*
1198
1199 ```rust
1200 test
1201 ```
1202
1118 ```rust 1203 ```rust
1119 async fn foo() 1204 async fn foo()
1120 ``` 1205 ```
@@ -1124,6 +1209,11 @@ fn bar() { fo<|>o(); }
1124 r#"pub const unsafe fn foo<|>() {}"#, 1209 r#"pub const unsafe fn foo<|>() {}"#,
1125 expect![[r#" 1210 expect![[r#"
1126 *foo* 1211 *foo*
1212
1213 ```rust
1214 test
1215 ```
1216
1127 ```rust 1217 ```rust
1128 pub const unsafe fn foo() 1218 pub const unsafe fn foo()
1129 ``` 1219 ```
@@ -1133,6 +1223,11 @@ fn bar() { fo<|>o(); }
1133 r#"pub(crate) async unsafe extern "C" fn foo<|>() {}"#, 1223 r#"pub(crate) async unsafe extern "C" fn foo<|>() {}"#,
1134 expect![[r#" 1224 expect![[r#"
1135 *foo* 1225 *foo*
1226
1227 ```rust
1228 test
1229 ```
1230
1136 ```rust 1231 ```rust
1137 pub(crate) async unsafe extern "C" fn foo() 1232 pub(crate) async unsafe extern "C" fn foo()
1138 ``` 1233 ```
@@ -1210,6 +1305,11 @@ fn my() {}
1210"#, 1305"#,
1211 expect![[r#" 1306 expect![[r#"
1212 *my* 1307 *my*
1308
1309 ```rust
1310 test
1311 ```
1312
1213 ```rust 1313 ```rust
1214 mod my 1314 mod my
1215 ``` 1315 ```
@@ -1228,10 +1328,16 @@ fn foo() { let bar = Ba<|>r; }
1228"#, 1328"#,
1229 expect![[r#" 1329 expect![[r#"
1230 *Bar* 1330 *Bar*
1331
1332 ```rust
1333 test
1334 ```
1335
1231 ```rust 1336 ```rust
1232 struct Bar 1337 struct Bar
1233 ``` 1338 ```
1234 ___ 1339
1340 ---
1235 1341
1236 bar docs 1342 bar docs
1237 "#]], 1343 "#]],
@@ -1249,10 +1355,16 @@ fn foo() { let bar = Ba<|>r; }
1249"#, 1355"#,
1250 expect![[r#" 1356 expect![[r#"
1251 *Bar* 1357 *Bar*
1358
1359 ```rust
1360 test
1361 ```
1362
1252 ```rust 1363 ```rust
1253 struct Bar 1364 struct Bar
1254 ``` 1365 ```
1255 ___ 1366
1367 ---
1256 1368
1257 bar docs 1369 bar docs
1258 "#]], 1370 "#]],
@@ -1272,10 +1384,16 @@ fn foo() { let bar = Ba<|>r; }
1272"#, 1384"#,
1273 expect![[r#" 1385 expect![[r#"
1274 *Bar* 1386 *Bar*
1387
1388 ```rust
1389 test
1390 ```
1391
1275 ```rust 1392 ```rust
1276 struct Bar 1393 struct Bar
1277 ``` 1394 ```
1278 ___ 1395
1396 ---
1279 1397
1280 bar docs 0 1398 bar docs 0
1281 1399
@@ -1287,6 +1405,371 @@ fn foo() { let bar = Ba<|>r; }
1287 } 1405 }
1288 1406
1289 #[test] 1407 #[test]
1408 fn test_hover_path_link() {
1409 check(
1410 r"
1411 //- /lib.rs
1412 pub struct Foo;
1413 /// [Foo](struct.Foo.html)
1414 pub struct B<|>ar
1415 ",
1416 expect![[r#"
1417 *Bar*
1418
1419 ```rust
1420 test
1421 ```
1422
1423 ```rust
1424 pub struct Bar
1425 ```
1426
1427 ---
1428
1429 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1430 "#]],
1431 );
1432 }
1433
1434 #[test]
1435 fn test_hover_path_link_no_strip() {
1436 check(
1437 r"
1438 //- /lib.rs
1439 pub struct Foo;
1440 /// [struct Foo](struct.Foo.html)
1441 pub struct B<|>ar
1442 ",
1443 expect![[r#"
1444 *Bar*
1445
1446 ```rust
1447 test
1448 ```
1449
1450 ```rust
1451 pub struct Bar
1452 ```
1453
1454 ---
1455
1456 [struct Foo](https://docs.rs/test/*/test/struct.Foo.html)
1457 "#]],
1458 );
1459 }
1460
1461 #[ignore = "path based links currently only support documentation on ModuleDef items"]
1462 #[test]
1463 fn test_hover_path_link_field() {
1464 check(
1465 r"
1466 //- /lib.rs
1467 pub struct Foo;
1468 pub struct Bar {
1469 /// [Foo](struct.Foo.html)
1470 fie<|>ld: ()
1471 }
1472 ",
1473 expect![[r#"
1474 *field*
1475
1476 ```rust
1477 test::Bar
1478 ```
1479
1480 ```rust
1481 field: ()
1482 ```
1483
1484 ---
1485
1486 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1487 "#]],
1488 );
1489 }
1490
1491 #[test]
1492 fn test_hover_intra_link() {
1493 check(
1494 r"
1495 //- /lib.rs
1496 pub mod foo {
1497 pub struct Foo;
1498 }
1499 /// [Foo](foo::Foo)
1500 pub struct B<|>ar
1501 ",
1502 expect![[r#"
1503 *Bar*
1504
1505 ```rust
1506 test
1507 ```
1508
1509 ```rust
1510 pub struct Bar
1511 ```
1512
1513 ---
1514
1515 [Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)
1516 "#]],
1517 );
1518 }
1519
1520 #[test]
1521 fn test_hover_intra_link_html_root_url() {
1522 check(
1523 r#"
1524 //- /lib.rs
1525
1526 #![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)]
1527
1528 pub mod foo {
1529 pub struct Foo;
1530 }
1531 /// [Foo](foo::Foo)
1532 pub struct B<|>ar
1533 "#,
1534 expect![[r#"
1535 *Bar*
1536
1537 ```rust
1538 test
1539 ```
1540
1541 ```rust
1542 pub struct Bar
1543 ```
1544
1545 ---
1546
1547 [Foo](https://example.com/test/foo/struct.Foo.html)
1548 "#]],
1549 );
1550 }
1551
1552 #[test]
1553 fn test_hover_intra_link_shortlink() {
1554 check(
1555 r"
1556 //- /lib.rs
1557 pub struct Foo;
1558 /// [Foo]
1559 pub struct B<|>ar
1560 ",
1561 expect![[r#"
1562 *Bar*
1563
1564 ```rust
1565 test
1566 ```
1567
1568 ```rust
1569 pub struct Bar
1570 ```
1571
1572 ---
1573
1574 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1575 "#]],
1576 );
1577 }
1578
1579 #[test]
1580 fn test_hover_intra_link_shortlink_code() {
1581 check(
1582 r"
1583 //- /lib.rs
1584 pub struct Foo;
1585 /// [`Foo`]
1586 pub struct B<|>ar
1587 ",
1588 expect![[r#"
1589 *Bar*
1590
1591 ```rust
1592 test
1593 ```
1594
1595 ```rust
1596 pub struct Bar
1597 ```
1598
1599 ---
1600
1601 [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
1602 "#]],
1603 );
1604 }
1605
1606 #[test]
1607 fn test_hover_intra_link_namespaced() {
1608 check(
1609 r"
1610 //- /lib.rs
1611 pub struct Foo;
1612 fn Foo() {}
1613 /// [Foo()]
1614 pub struct B<|>ar
1615 ",
1616 expect![[r#"
1617 *Bar*
1618
1619 ```rust
1620 test
1621 ```
1622
1623 ```rust
1624 pub struct Bar
1625 ```
1626
1627 ---
1628
1629 [Foo](https://docs.rs/test/*/test/struct.Foo.html)
1630 "#]],
1631 );
1632 }
1633
1634 #[test]
1635 fn test_hover_intra_link_shortlink_namspaced_code() {
1636 check(
1637 r"
1638 //- /lib.rs
1639 pub struct Foo;
1640 /// [`struct Foo`]
1641 pub struct B<|>ar
1642 ",
1643 expect![[r#"
1644 *Bar*
1645
1646 ```rust
1647 test
1648 ```
1649
1650 ```rust
1651 pub struct Bar
1652 ```
1653
1654 ---
1655
1656 [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
1657 "#]],
1658 );
1659 }
1660
1661 #[test]
1662 fn test_hover_intra_link_shortlink_namspaced_code_with_at() {
1663 check(
1664 r"
1665 //- /lib.rs
1666 pub struct Foo;
1667 /// [`struct@Foo`]
1668 pub struct B<|>ar
1669 ",
1670 expect![[r#"
1671 *Bar*
1672
1673 ```rust
1674 test
1675 ```
1676
1677 ```rust
1678 pub struct Bar
1679 ```
1680
1681 ---
1682
1683 [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
1684 "#]],
1685 );
1686 }
1687
1688 #[test]
1689 fn test_hover_intra_link_reference() {
1690 check(
1691 r"
1692 //- /lib.rs
1693 pub struct Foo;
1694 /// [my Foo][foo]
1695 ///
1696 /// [foo]: Foo
1697 pub struct B<|>ar
1698 ",
1699 expect![[r#"
1700 *Bar*
1701
1702 ```rust
1703 test
1704 ```
1705
1706 ```rust
1707 pub struct Bar
1708 ```
1709
1710 ---
1711
1712 [my Foo](https://docs.rs/test/*/test/struct.Foo.html)
1713 "#]],
1714 );
1715 }
1716
1717 #[test]
1718 fn test_hover_external_url() {
1719 check(
1720 r"
1721 //- /lib.rs
1722 pub struct Foo;
1723 /// [external](https://www.google.com)
1724 pub struct B<|>ar
1725 ",
1726 expect![[r#"
1727 *Bar*
1728
1729 ```rust
1730 test
1731 ```
1732
1733 ```rust
1734 pub struct Bar
1735 ```
1736
1737 ---
1738
1739 [external](https://www.google.com)
1740 "#]],
1741 );
1742 }
1743
1744 // Check that we don't rewrite links which we can't identify
1745 #[test]
1746 fn test_hover_unknown_target() {
1747 check(
1748 r"
1749 //- /lib.rs
1750 pub struct Foo;
1751 /// [baz](Baz)
1752 pub struct B<|>ar
1753 ",
1754 expect![[r#"
1755 *Bar*
1756
1757 ```rust
1758 test
1759 ```
1760
1761 ```rust
1762 pub struct Bar
1763 ```
1764
1765 ---
1766
1767 [baz](Baz)
1768 "#]],
1769 );
1770 }
1771
1772 #[test]
1290 fn test_hover_macro_generated_struct_fn_doc_comment() { 1773 fn test_hover_macro_generated_struct_fn_doc_comment() {
1291 mark::check!(hover_macro_generated_struct_fn_doc_comment); 1774 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1292 1775
@@ -1308,16 +1791,18 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
1308"#, 1791"#,
1309 expect![[r#" 1792 expect![[r#"
1310 *foo* 1793 *foo*
1794
1311 ```rust 1795 ```rust
1312 Bar 1796 test::Bar
1313 ``` 1797 ```
1314 1798
1315 ```rust 1799 ```rust
1316 fn foo(&self) 1800 fn foo(&self)
1317 ``` 1801 ```
1318 ___
1319 1802
1320 Do the foo 1803 ---
1804
1805 Do the foo
1321 "#]], 1806 "#]],
1322 ); 1807 );
1323 } 1808 }
@@ -1344,14 +1829,16 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
1344"#, 1829"#,
1345 expect![[r#" 1830 expect![[r#"
1346 *foo* 1831 *foo*
1832
1347 ```rust 1833 ```rust
1348 Bar 1834 test::Bar
1349 ``` 1835 ```
1350 1836
1351 ```rust 1837 ```rust
1352 fn foo(&self) 1838 fn foo(&self)
1353 ``` 1839 ```
1354 ___ 1840
1841 ---
1355 1842
1356 Do the foo 1843 Do the foo
1357 "#]], 1844 "#]],
@@ -1526,7 +2013,7 @@ fn main() { let s<|>t = S{ f1:0 }; }
1526 GoToType( 2013 GoToType(
1527 [ 2014 [
1528 HoverGotoTypeData { 2015 HoverGotoTypeData {
1529 mod_path: "S", 2016 mod_path: "test::S",
1530 nav: NavigationTarget { 2017 nav: NavigationTarget {
1531 file_id: FileId( 2018 file_id: FileId(
1532 1, 2019 1,
@@ -1565,7 +2052,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; }
1565 GoToType( 2052 GoToType(
1566 [ 2053 [
1567 HoverGotoTypeData { 2054 HoverGotoTypeData {
1568 mod_path: "S", 2055 mod_path: "test::S",
1569 nav: NavigationTarget { 2056 nav: NavigationTarget {
1570 file_id: FileId( 2057 file_id: FileId(
1571 1, 2058 1,
@@ -1584,7 +2071,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; }
1584 }, 2071 },
1585 }, 2072 },
1586 HoverGotoTypeData { 2073 HoverGotoTypeData {
1587 mod_path: "Arg", 2074 mod_path: "test::Arg",
1588 nav: NavigationTarget { 2075 nav: NavigationTarget {
1589 file_id: FileId( 2076 file_id: FileId(
1590 1, 2077 1,
@@ -1623,7 +2110,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
1623 GoToType( 2110 GoToType(
1624 [ 2111 [
1625 HoverGotoTypeData { 2112 HoverGotoTypeData {
1626 mod_path: "S", 2113 mod_path: "test::S",
1627 nav: NavigationTarget { 2114 nav: NavigationTarget {
1628 file_id: FileId( 2115 file_id: FileId(
1629 1, 2116 1,
@@ -1642,7 +2129,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
1642 }, 2129 },
1643 }, 2130 },
1644 HoverGotoTypeData { 2131 HoverGotoTypeData {
1645 mod_path: "Arg", 2132 mod_path: "test::Arg",
1646 nav: NavigationTarget { 2133 nav: NavigationTarget {
1647 file_id: FileId( 2134 file_id: FileId(
1648 1, 2135 1,
@@ -1684,7 +2171,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
1684 GoToType( 2171 GoToType(
1685 [ 2172 [
1686 HoverGotoTypeData { 2173 HoverGotoTypeData {
1687 mod_path: "A", 2174 mod_path: "test::A",
1688 nav: NavigationTarget { 2175 nav: NavigationTarget {
1689 file_id: FileId( 2176 file_id: FileId(
1690 1, 2177 1,
@@ -1703,7 +2190,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
1703 }, 2190 },
1704 }, 2191 },
1705 HoverGotoTypeData { 2192 HoverGotoTypeData {
1706 mod_path: "B", 2193 mod_path: "test::B",
1707 nav: NavigationTarget { 2194 nav: NavigationTarget {
1708 file_id: FileId( 2195 file_id: FileId(
1709 1, 2196 1,
@@ -1722,7 +2209,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
1722 }, 2209 },
1723 }, 2210 },
1724 HoverGotoTypeData { 2211 HoverGotoTypeData {
1725 mod_path: "M::C", 2212 mod_path: "test::M::C",
1726 nav: NavigationTarget { 2213 nav: NavigationTarget {
1727 file_id: FileId( 2214 file_id: FileId(
1728 1, 2215 1,
@@ -1761,7 +2248,7 @@ fn main() { let s<|>t = foo(); }
1761 GoToType( 2248 GoToType(
1762 [ 2249 [
1763 HoverGotoTypeData { 2250 HoverGotoTypeData {
1764 mod_path: "Foo", 2251 mod_path: "test::Foo",
1765 nav: NavigationTarget { 2252 nav: NavigationTarget {
1766 file_id: FileId( 2253 file_id: FileId(
1767 1, 2254 1,
@@ -1801,7 +2288,7 @@ fn main() { let s<|>t = foo(); }
1801 GoToType( 2288 GoToType(
1802 [ 2289 [
1803 HoverGotoTypeData { 2290 HoverGotoTypeData {
1804 mod_path: "Foo", 2291 mod_path: "test::Foo",
1805 nav: NavigationTarget { 2292 nav: NavigationTarget {
1806 file_id: FileId( 2293 file_id: FileId(
1807 1, 2294 1,
@@ -1820,7 +2307,7 @@ fn main() { let s<|>t = foo(); }
1820 }, 2307 },
1821 }, 2308 },
1822 HoverGotoTypeData { 2309 HoverGotoTypeData {
1823 mod_path: "S", 2310 mod_path: "test::S",
1824 nav: NavigationTarget { 2311 nav: NavigationTarget {
1825 file_id: FileId( 2312 file_id: FileId(
1826 1, 2313 1,
@@ -1860,7 +2347,7 @@ fn main() { let s<|>t = foo(); }
1860 GoToType( 2347 GoToType(
1861 [ 2348 [
1862 HoverGotoTypeData { 2349 HoverGotoTypeData {
1863 mod_path: "Foo", 2350 mod_path: "test::Foo",
1864 nav: NavigationTarget { 2351 nav: NavigationTarget {
1865 file_id: FileId( 2352 file_id: FileId(
1866 1, 2353 1,
@@ -1879,7 +2366,7 @@ fn main() { let s<|>t = foo(); }
1879 }, 2366 },
1880 }, 2367 },
1881 HoverGotoTypeData { 2368 HoverGotoTypeData {
1882 mod_path: "Bar", 2369 mod_path: "test::Bar",
1883 nav: NavigationTarget { 2370 nav: NavigationTarget {
1884 file_id: FileId( 2371 file_id: FileId(
1885 1, 2372 1,
@@ -1922,7 +2409,7 @@ fn main() { let s<|>t = foo(); }
1922 GoToType( 2409 GoToType(
1923 [ 2410 [
1924 HoverGotoTypeData { 2411 HoverGotoTypeData {
1925 mod_path: "Foo", 2412 mod_path: "test::Foo",
1926 nav: NavigationTarget { 2413 nav: NavigationTarget {
1927 file_id: FileId( 2414 file_id: FileId(
1928 1, 2415 1,
@@ -1941,7 +2428,7 @@ fn main() { let s<|>t = foo(); }
1941 }, 2428 },
1942 }, 2429 },
1943 HoverGotoTypeData { 2430 HoverGotoTypeData {
1944 mod_path: "Bar", 2431 mod_path: "test::Bar",
1945 nav: NavigationTarget { 2432 nav: NavigationTarget {
1946 file_id: FileId( 2433 file_id: FileId(
1947 1, 2434 1,
@@ -1960,7 +2447,7 @@ fn main() { let s<|>t = foo(); }
1960 }, 2447 },
1961 }, 2448 },
1962 HoverGotoTypeData { 2449 HoverGotoTypeData {
1963 mod_path: "S1", 2450 mod_path: "test::S1",
1964 nav: NavigationTarget { 2451 nav: NavigationTarget {
1965 file_id: FileId( 2452 file_id: FileId(
1966 1, 2453 1,
@@ -1979,7 +2466,7 @@ fn main() { let s<|>t = foo(); }
1979 }, 2466 },
1980 }, 2467 },
1981 HoverGotoTypeData { 2468 HoverGotoTypeData {
1982 mod_path: "S2", 2469 mod_path: "test::S2",
1983 nav: NavigationTarget { 2470 nav: NavigationTarget {
1984 file_id: FileId( 2471 file_id: FileId(
1985 1, 2472 1,
@@ -2016,7 +2503,7 @@ fn foo(ar<|>g: &impl Foo) {}
2016 GoToType( 2503 GoToType(
2017 [ 2504 [
2018 HoverGotoTypeData { 2505 HoverGotoTypeData {
2019 mod_path: "Foo", 2506 mod_path: "test::Foo",
2020 nav: NavigationTarget { 2507 nav: NavigationTarget {
2021 file_id: FileId( 2508 file_id: FileId(
2022 1, 2509 1,
@@ -2056,7 +2543,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2056 GoToType( 2543 GoToType(
2057 [ 2544 [
2058 HoverGotoTypeData { 2545 HoverGotoTypeData {
2059 mod_path: "Foo", 2546 mod_path: "test::Foo",
2060 nav: NavigationTarget { 2547 nav: NavigationTarget {
2061 file_id: FileId( 2548 file_id: FileId(
2062 1, 2549 1,
@@ -2075,7 +2562,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2075 }, 2562 },
2076 }, 2563 },
2077 HoverGotoTypeData { 2564 HoverGotoTypeData {
2078 mod_path: "Bar", 2565 mod_path: "test::Bar",
2079 nav: NavigationTarget { 2566 nav: NavigationTarget {
2080 file_id: FileId( 2567 file_id: FileId(
2081 1, 2568 1,
@@ -2094,7 +2581,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2094 }, 2581 },
2095 }, 2582 },
2096 HoverGotoTypeData { 2583 HoverGotoTypeData {
2097 mod_path: "S", 2584 mod_path: "test::S",
2098 nav: NavigationTarget { 2585 nav: NavigationTarget {
2099 file_id: FileId( 2586 file_id: FileId(
2100 1, 2587 1,
@@ -2132,7 +2619,7 @@ fn foo(ar<|>g: &impl Foo<S>) {}
2132 GoToType( 2619 GoToType(
2133 [ 2620 [
2134 HoverGotoTypeData { 2621 HoverGotoTypeData {
2135 mod_path: "Foo", 2622 mod_path: "test::Foo",
2136 nav: NavigationTarget { 2623 nav: NavigationTarget {
2137 file_id: FileId( 2624 file_id: FileId(
2138 1, 2625 1,
@@ -2151,7 +2638,7 @@ fn foo(ar<|>g: &impl Foo<S>) {}
2151 }, 2638 },
2152 }, 2639 },
2153 HoverGotoTypeData { 2640 HoverGotoTypeData {
2154 mod_path: "S", 2641 mod_path: "test::S",
2155 nav: NavigationTarget { 2642 nav: NavigationTarget {
2156 file_id: FileId( 2643 file_id: FileId(
2157 1, 2644 1,
@@ -2194,7 +2681,7 @@ fn main() { let s<|>t = foo(); }
2194 GoToType( 2681 GoToType(
2195 [ 2682 [
2196 HoverGotoTypeData { 2683 HoverGotoTypeData {
2197 mod_path: "B", 2684 mod_path: "test::B",
2198 nav: NavigationTarget { 2685 nav: NavigationTarget {
2199 file_id: FileId( 2686 file_id: FileId(
2200 1, 2687 1,
@@ -2213,7 +2700,7 @@ fn main() { let s<|>t = foo(); }
2213 }, 2700 },
2214 }, 2701 },
2215 HoverGotoTypeData { 2702 HoverGotoTypeData {
2216 mod_path: "Foo", 2703 mod_path: "test::Foo",
2217 nav: NavigationTarget { 2704 nav: NavigationTarget {
2218 file_id: FileId( 2705 file_id: FileId(
2219 1, 2706 1,
@@ -2250,7 +2737,7 @@ fn foo(ar<|>g: &dyn Foo) {}
2250 GoToType( 2737 GoToType(
2251 [ 2738 [
2252 HoverGotoTypeData { 2739 HoverGotoTypeData {
2253 mod_path: "Foo", 2740 mod_path: "test::Foo",
2254 nav: NavigationTarget { 2741 nav: NavigationTarget {
2255 file_id: FileId( 2742 file_id: FileId(
2256 1, 2743 1,
@@ -2288,7 +2775,7 @@ fn foo(ar<|>g: &dyn Foo<S>) {}
2288 GoToType( 2775 GoToType(
2289 [ 2776 [
2290 HoverGotoTypeData { 2777 HoverGotoTypeData {
2291 mod_path: "Foo", 2778 mod_path: "test::Foo",
2292 nav: NavigationTarget { 2779 nav: NavigationTarget {
2293 file_id: FileId( 2780 file_id: FileId(
2294 1, 2781 1,
@@ -2307,7 +2794,7 @@ fn foo(ar<|>g: &dyn Foo<S>) {}
2307 }, 2794 },
2308 }, 2795 },
2309 HoverGotoTypeData { 2796 HoverGotoTypeData {
2310 mod_path: "S", 2797 mod_path: "test::S",
2311 nav: NavigationTarget { 2798 nav: NavigationTarget {
2312 file_id: FileId( 2799 file_id: FileId(
2313 1, 2800 1,
@@ -2348,7 +2835,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2348 GoToType( 2835 GoToType(
2349 [ 2836 [
2350 HoverGotoTypeData { 2837 HoverGotoTypeData {
2351 mod_path: "ImplTrait", 2838 mod_path: "test::ImplTrait",
2352 nav: NavigationTarget { 2839 nav: NavigationTarget {
2353 file_id: FileId( 2840 file_id: FileId(
2354 1, 2841 1,
@@ -2367,7 +2854,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2367 }, 2854 },
2368 }, 2855 },
2369 HoverGotoTypeData { 2856 HoverGotoTypeData {
2370 mod_path: "B", 2857 mod_path: "test::B",
2371 nav: NavigationTarget { 2858 nav: NavigationTarget {
2372 file_id: FileId( 2859 file_id: FileId(
2373 1, 2860 1,
@@ -2386,7 +2873,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2386 }, 2873 },
2387 }, 2874 },
2388 HoverGotoTypeData { 2875 HoverGotoTypeData {
2389 mod_path: "DynTrait", 2876 mod_path: "test::DynTrait",
2390 nav: NavigationTarget { 2877 nav: NavigationTarget {
2391 file_id: FileId( 2878 file_id: FileId(
2392 1, 2879 1,
@@ -2405,7 +2892,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
2405 }, 2892 },
2406 }, 2893 },
2407 HoverGotoTypeData { 2894 HoverGotoTypeData {
2408 mod_path: "S", 2895 mod_path: "test::S",
2409 nav: NavigationTarget { 2896 nav: NavigationTarget {
2410 file_id: FileId( 2897 file_id: FileId(
2411 1, 2898 1,
@@ -2453,7 +2940,7 @@ fn main() { let s<|>t = test().get(); }
2453 GoToType( 2940 GoToType(
2454 [ 2941 [
2455 HoverGotoTypeData { 2942 HoverGotoTypeData {
2456 mod_path: "Foo", 2943 mod_path: "test::Foo",
2457 nav: NavigationTarget { 2944 nav: NavigationTarget {
2458 file_id: FileId( 2945 file_id: FileId(
2459 1, 2946 1,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index e3af6d5bc..570790384 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -43,6 +43,7 @@ mod status;
43mod syntax_highlighting; 43mod syntax_highlighting;
44mod syntax_tree; 44mod syntax_tree;
45mod typing; 45mod typing;
46mod link_rewrite;
46 47
47use std::sync::Arc; 48use std::sync::Arc;
48 49
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs
new file mode 100644
index 000000000..ff3200eef
--- /dev/null
+++ b/crates/ide/src/link_rewrite.rs
@@ -0,0 +1,81 @@
1//! Resolves and rewrites links in markdown documentation.
2//!
3//! Most of the implementation can be found in [`hir::doc_links`].
4
5use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
6use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
7
8use hir::resolve_doc_link;
9use ide_db::{defs::Definition, RootDatabase};
10
11/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
12pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
13 let doc = Parser::new_with_broken_link_callback(
14 markdown,
15 Options::empty(),
16 Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))),
17 );
18
19 let doc = map_links(doc, |target, title: &str| {
20 // This check is imperfect, there's some overlap between valid intra-doc links
21 // and valid URLs so we choose to be too eager to try to resolve what might be
22 // a URL.
23 if target.contains("://") {
24 (target.to_string(), title.to_string())
25 } else {
26 // Two posibilities:
27 // * path-based links: `../../module/struct.MyStruct.html`
28 // * module-based links (AKA intra-doc links): `super::super::module::MyStruct`
29 let resolved = match definition {
30 Definition::ModuleDef(t) => resolve_doc_link(db, t, title, target),
31 Definition::Macro(t) => resolve_doc_link(db, t, title, target),
32 Definition::Field(t) => resolve_doc_link(db, t, title, target),
33 Definition::SelfType(t) => resolve_doc_link(db, t, title, target),
34 Definition::Local(t) => resolve_doc_link(db, t, title, target),
35 Definition::TypeParam(t) => resolve_doc_link(db, t, title, target),
36 };
37
38 match resolved {
39 Some((target, title)) => (target, title),
40 None => (target.to_string(), title.to_string()),
41 }
42 }
43 });
44 let mut out = String::new();
45 let mut options = CmarkOptions::default();
46 options.code_block_backticks = 3;
47 cmark_with_options(doc, &mut out, None, options).ok();
48 out
49}
50
51// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles.
52fn map_links<'e>(
53 events: impl Iterator<Item = Event<'e>>,
54 callback: impl Fn(&str, &str) -> (String, String),
55) -> impl Iterator<Item = Event<'e>> {
56 let mut in_link = false;
57 let mut link_target: Option<CowStr> = None;
58
59 events.map(move |evt| match evt {
60 Event::Start(Tag::Link(_link_type, ref target, _)) => {
61 in_link = true;
62 link_target = Some(target.clone());
63 evt
64 }
65 Event::End(Tag::Link(link_type, _target, _)) => {
66 in_link = false;
67 Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed("")))
68 }
69 Event::Text(s) if in_link => {
70 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
71 link_target = Some(CowStr::Boxed(link_target_s.into()));
72 Event::Text(CowStr::Boxed(link_name.into()))
73 }
74 Event::Code(s) if in_link => {
75 let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s);
76 link_target = Some(CowStr::Boxed(link_target_s.into()));
77 Event::Code(CowStr::Boxed(link_name.into()))
78 }
79 _ => evt,
80 })
81}
diff --git a/crates/ide/src/mock_analysis.rs b/crates/ide/src/mock_analysis.rs
index 363e6d27e..235796dbc 100644
--- a/crates/ide/src/mock_analysis.rs
+++ b/crates/ide/src/mock_analysis.rs
@@ -115,7 +115,7 @@ impl MockAnalysis {
115 root_crate = Some(crate_graph.add_crate_root( 115 root_crate = Some(crate_graph.add_crate_root(
116 file_id, 116 file_id,
117 edition, 117 edition,
118 None, 118 Some("test".to_string()),
119 cfg, 119 cfg,
120 env, 120 env,
121 Default::default(), 121 Default::default(),
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs
index fa315ff8e..e97784c47 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/main.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs
@@ -690,5 +690,5 @@ pub fn foo(_input: TokenStream) -> TokenStream {
690 }); 690 });
691 691
692 let value = res.get("contents").unwrap().get("value").unwrap().to_string(); 692 let value = res.get("contents").unwrap().get("value").unwrap().to_string();
693 assert_eq!(value, r#""```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#) 693 assert_eq!(value, r#""\n```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#)
694} 694}
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 4fdff62ee..3b1408222 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -68,6 +68,7 @@ See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion.
68fn check_licenses() { 68fn check_licenses() {
69 let expected = " 69 let expected = "
700BSD OR MIT OR Apache-2.0 700BSD OR MIT OR Apache-2.0
71Apache-2.0
71Apache-2.0 OR BSL-1.0 72Apache-2.0 OR BSL-1.0
72Apache-2.0 OR MIT 73Apache-2.0 OR MIT
73Apache-2.0/MIT 74Apache-2.0/MIT