Auto merge of #85457 - jyn514:remove-doc-include, r=GuillaumeGomez
Remove `doc(include)` This nightly feature is redundant now that `extended_key_value_attributes` is stable (https://github.com/rust-lang/rust/pull/83366). `@rust-lang/rustdoc` not sure if you think this needs FCP; there was already an FCP in #82539, but technically it was for deprecating, not removing the feature altogether. This should not be merged before #83366. cc `@petrochenkov`
This commit is contained in:
commit
2c106885d5
28 changed files with 79 additions and 449 deletions
|
@ -318,7 +318,6 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
gate_doc!(
|
gate_doc!(
|
||||||
include => external_doc
|
|
||||||
cfg => doc_cfg
|
cfg => doc_cfg
|
||||||
masked => doc_masked
|
masked => doc_masked
|
||||||
notable_trait => doc_notable_trait
|
notable_trait => doc_notable_trait
|
||||||
|
|
|
@ -1068,11 +1068,11 @@ impl<'a> ExtCtxt<'a> {
|
||||||
self.resolver.check_unused_macros();
|
self.resolver.check_unused_macros();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves a path mentioned inside Rust code.
|
/// Resolves a `path` mentioned inside Rust code, returning an absolute path.
|
||||||
///
|
///
|
||||||
/// This unifies the logic used for resolving `include_X!`, and `#[doc(include)]` file paths.
|
/// This unifies the logic used for resolving `include_X!`.
|
||||||
///
|
///
|
||||||
/// Returns an absolute path to the file that `path` refers to.
|
/// FIXME: move this to `rustc_builtin_macros` and make it private.
|
||||||
pub fn resolve_path(
|
pub fn resolve_path(
|
||||||
&self,
|
&self,
|
||||||
path: impl Into<PathBuf>,
|
path: impl Into<PathBuf>,
|
||||||
|
|
|
@ -12,11 +12,11 @@ use rustc_ast::ptr::P;
|
||||||
use rustc_ast::token;
|
use rustc_ast::token;
|
||||||
use rustc_ast::tokenstream::TokenStream;
|
use rustc_ast::tokenstream::TokenStream;
|
||||||
use rustc_ast::visit::{self, AssocCtxt, Visitor};
|
use rustc_ast::visit::{self, AssocCtxt, Visitor};
|
||||||
use rustc_ast::{AstLike, AttrItem, Block, Inline, ItemKind, LitKind, MacArgs};
|
use rustc_ast::{AstLike, Block, Inline, ItemKind, MacArgs};
|
||||||
use rustc_ast::{MacCallStmt, MacStmtStyle, MetaItemKind, ModKind, NestedMetaItem};
|
use rustc_ast::{MacCallStmt, MacStmtStyle, MetaItemKind, ModKind, NestedMetaItem};
|
||||||
use rustc_ast::{NodeId, PatKind, Path, StmtKind, Unsafe};
|
use rustc_ast::{NodeId, PatKind, Path, StmtKind, Unsafe};
|
||||||
use rustc_ast_pretty::pprust;
|
use rustc_ast_pretty::pprust;
|
||||||
use rustc_attr::{self as attr, is_builtin_attr};
|
use rustc_attr::is_builtin_attr;
|
||||||
use rustc_data_structures::map_in_place::MapInPlace;
|
use rustc_data_structures::map_in_place::MapInPlace;
|
||||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||||
use rustc_data_structures::sync::Lrc;
|
use rustc_data_structures::sync::Lrc;
|
||||||
|
@ -28,15 +28,14 @@ use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS;
|
||||||
use rustc_session::lint::BuiltinLintDiagnostics;
|
use rustc_session::lint::BuiltinLintDiagnostics;
|
||||||
use rustc_session::parse::{feature_err, ParseSess};
|
use rustc_session::parse::{feature_err, ParseSess};
|
||||||
use rustc_session::Limit;
|
use rustc_session::Limit;
|
||||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
use rustc_span::symbol::{sym, Ident};
|
||||||
use rustc_span::{ExpnId, FileName, Span, DUMMY_SP};
|
use rustc_span::{ExpnId, FileName, Span};
|
||||||
|
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::io::ErrorKind;
|
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{iter, mem, slice};
|
use std::{iter, mem};
|
||||||
|
|
||||||
macro_rules! ast_fragments {
|
macro_rules! ast_fragments {
|
||||||
(
|
(
|
||||||
|
@ -1524,139 +1523,6 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
|
||||||
noop_flat_map_generic_param(param, self)
|
noop_flat_map_generic_param(param, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_attribute(&mut self, at: &mut ast::Attribute) {
|
|
||||||
// turn `#[doc(include="filename")]` attributes into `#[doc(include(file="filename",
|
|
||||||
// contents="file contents")]` attributes
|
|
||||||
if !self.cx.sess.check_name(at, sym::doc) {
|
|
||||||
return noop_visit_attribute(at, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(list) = at.meta_item_list() {
|
|
||||||
if !list.iter().any(|it| it.has_name(sym::include)) {
|
|
||||||
return noop_visit_attribute(at, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut items = vec![];
|
|
||||||
|
|
||||||
for mut it in list {
|
|
||||||
if !it.has_name(sym::include) {
|
|
||||||
items.push({
|
|
||||||
noop_visit_meta_list_item(&mut it, self);
|
|
||||||
it
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(file) = it.value_str() {
|
|
||||||
let err_count = self.cx.sess.parse_sess.span_diagnostic.err_count();
|
|
||||||
self.check_attributes(slice::from_ref(at));
|
|
||||||
if self.cx.sess.parse_sess.span_diagnostic.err_count() > err_count {
|
|
||||||
// avoid loading the file if they haven't enabled the feature
|
|
||||||
return noop_visit_attribute(at, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
let filename = match self.cx.resolve_path(&*file.as_str(), it.span()) {
|
|
||||||
Ok(filename) => filename,
|
|
||||||
Err(mut err) => {
|
|
||||||
err.emit();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.cx.source_map().load_file(&filename) {
|
|
||||||
Ok(source_file) => {
|
|
||||||
let src = source_file
|
|
||||||
.src
|
|
||||||
.as_ref()
|
|
||||||
.expect("freshly loaded file should have a source");
|
|
||||||
let src_interned = Symbol::intern(src.as_str());
|
|
||||||
|
|
||||||
let include_info = vec![
|
|
||||||
ast::NestedMetaItem::MetaItem(attr::mk_name_value_item_str(
|
|
||||||
Ident::with_dummy_span(sym::file),
|
|
||||||
file,
|
|
||||||
DUMMY_SP,
|
|
||||||
)),
|
|
||||||
ast::NestedMetaItem::MetaItem(attr::mk_name_value_item_str(
|
|
||||||
Ident::with_dummy_span(sym::contents),
|
|
||||||
src_interned,
|
|
||||||
DUMMY_SP,
|
|
||||||
)),
|
|
||||||
];
|
|
||||||
|
|
||||||
let include_ident = Ident::with_dummy_span(sym::include);
|
|
||||||
let item = attr::mk_list_item(include_ident, include_info);
|
|
||||||
items.push(ast::NestedMetaItem::MetaItem(item));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let lit_span = it.name_value_literal_span().unwrap();
|
|
||||||
|
|
||||||
if e.kind() == ErrorKind::InvalidData {
|
|
||||||
self.cx
|
|
||||||
.struct_span_err(
|
|
||||||
lit_span,
|
|
||||||
&format!("{} wasn't a utf-8 file", filename.display()),
|
|
||||||
)
|
|
||||||
.span_label(lit_span, "contains invalid utf-8")
|
|
||||||
.emit();
|
|
||||||
} else {
|
|
||||||
let mut err = self.cx.struct_span_err(
|
|
||||||
lit_span,
|
|
||||||
&format!("couldn't read {}: {}", filename.display(), e),
|
|
||||||
);
|
|
||||||
err.span_label(lit_span, "couldn't read file");
|
|
||||||
|
|
||||||
err.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut err = self
|
|
||||||
.cx
|
|
||||||
.struct_span_err(it.span(), "expected path to external documentation");
|
|
||||||
|
|
||||||
// Check if the user erroneously used `doc(include(...))` syntax.
|
|
||||||
let literal = it.meta_item_list().and_then(|list| {
|
|
||||||
if list.len() == 1 {
|
|
||||||
list[0].literal().map(|literal| &literal.kind)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let (path, applicability) = match &literal {
|
|
||||||
Some(LitKind::Str(path, ..)) => {
|
|
||||||
(path.to_string(), Applicability::MachineApplicable)
|
|
||||||
}
|
|
||||||
_ => (String::from("<path>"), Applicability::HasPlaceholders),
|
|
||||||
};
|
|
||||||
|
|
||||||
err.span_suggestion(
|
|
||||||
it.span(),
|
|
||||||
"provide a file path with `=`",
|
|
||||||
format!("include = \"{}\"", path),
|
|
||||||
applicability,
|
|
||||||
);
|
|
||||||
|
|
||||||
err.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let meta = attr::mk_list_item(Ident::with_dummy_span(sym::doc), items);
|
|
||||||
*at = ast::Attribute {
|
|
||||||
kind: ast::AttrKind::Normal(
|
|
||||||
AttrItem { path: meta.path, args: meta.kind.mac_args(meta.span), tokens: None },
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
span: at.span,
|
|
||||||
id: at.id,
|
|
||||||
style: at.style,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
noop_visit_attribute(at, self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_id(&mut self, id: &mut ast::NodeId) {
|
fn visit_id(&mut self, id: &mut ast::NodeId) {
|
||||||
if self.monotonic {
|
if self.monotonic {
|
||||||
debug_assert_eq!(*id, ast::DUMMY_NODE_ID);
|
debug_assert_eq!(*id, ast::DUMMY_NODE_ID);
|
||||||
|
|
|
@ -370,9 +370,6 @@ declare_features! (
|
||||||
/// Allows `#[doc(masked)]`.
|
/// Allows `#[doc(masked)]`.
|
||||||
(active, doc_masked, "1.21.0", Some(44027), None),
|
(active, doc_masked, "1.21.0", Some(44027), None),
|
||||||
|
|
||||||
/// Allows `#[doc(include = "some-file")]`.
|
|
||||||
(active, external_doc, "1.22.0", Some(44732), None),
|
|
||||||
|
|
||||||
/// Allows using `crate` as visibility modifier, synonymous with `pub(crate)`.
|
/// Allows using `crate` as visibility modifier, synonymous with `pub(crate)`.
|
||||||
(active, crate_visibility_modifier, "1.23.0", Some(53120), None),
|
(active, crate_visibility_modifier, "1.23.0", Some(53120), None),
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,10 @@ declare_features! (
|
||||||
(removed, const_fn, "1.54.0", Some(57563), None,
|
(removed, const_fn, "1.54.0", Some(57563), None,
|
||||||
Some("split into finer-grained feature gates")),
|
Some("split into finer-grained feature gates")),
|
||||||
|
|
||||||
|
/// Allows `#[doc(include = "some-file")]`.
|
||||||
|
(removed, external_doc, "1.54.0", Some(44732), None,
|
||||||
|
Some("use #[doc = include_str!(\"filename\")] instead, which handles macro invocations")),
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// feature-group-end: removed features
|
// feature-group-end: removed features
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
|
@ -489,7 +489,7 @@ fn has_doc(sess: &Session, attr: &ast::Attribute) -> bool {
|
||||||
|
|
||||||
if let Some(list) = attr.meta_item_list() {
|
if let Some(list) = attr.meta_item_list() {
|
||||||
for meta in list {
|
for meta in list {
|
||||||
if meta.has_name(sym::include) || meta.has_name(sym::hidden) {
|
if meta.has_name(sym::hidden) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -705,7 +705,7 @@ impl CheckAttrVisitor<'tcx> {
|
||||||
let mut is_valid = true;
|
let mut is_valid = true;
|
||||||
|
|
||||||
if let Some(list) = attr.meta().and_then(|mi| mi.meta_item_list().map(|l| l.to_vec())) {
|
if let Some(list) = attr.meta().and_then(|mi| mi.meta_item_list().map(|l| l.to_vec())) {
|
||||||
for meta in list {
|
for meta in &list {
|
||||||
if let Some(i_meta) = meta.meta_item() {
|
if let Some(i_meta) = meta.meta_item() {
|
||||||
match i_meta.name_or_empty() {
|
match i_meta.name_or_empty() {
|
||||||
sym::alias
|
sym::alias
|
||||||
|
@ -757,7 +757,6 @@ impl CheckAttrVisitor<'tcx> {
|
||||||
| sym::html_no_source
|
| sym::html_no_source
|
||||||
| sym::html_playground_url
|
| sym::html_playground_url
|
||||||
| sym::html_root_url
|
| sym::html_root_url
|
||||||
| sym::include
|
|
||||||
| sym::inline
|
| sym::inline
|
||||||
| sym::issue_tracker_base_url
|
| sym::issue_tracker_base_url
|
||||||
| sym::keyword
|
| sym::keyword
|
||||||
|
@ -792,6 +791,30 @@ impl CheckAttrVisitor<'tcx> {
|
||||||
);
|
);
|
||||||
diag.note("`doc(spotlight)` is now a no-op");
|
diag.note("`doc(spotlight)` is now a no-op");
|
||||||
}
|
}
|
||||||
|
if i_meta.has_name(sym::include) {
|
||||||
|
if let Some(value) = i_meta.value_str() {
|
||||||
|
// if there are multiple attributes, the suggestion would suggest deleting all of them, which is incorrect
|
||||||
|
let applicability = if list.len() == 1 {
|
||||||
|
Applicability::MachineApplicable
|
||||||
|
} else {
|
||||||
|
Applicability::MaybeIncorrect
|
||||||
|
};
|
||||||
|
let inner = if attr.style == AttrStyle::Inner {
|
||||||
|
"!"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
diag.span_suggestion(
|
||||||
|
attr.meta().unwrap().span,
|
||||||
|
"use `doc = include_str!` instead",
|
||||||
|
format!(
|
||||||
|
"#{}[doc = include_str!(\"{}\")]",
|
||||||
|
inner, value
|
||||||
|
),
|
||||||
|
applicability,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
diag.emit();
|
diag.emit();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -826,20 +826,6 @@ impl<'tcx> SaveContext<'tcx> {
|
||||||
// FIXME: Should save-analysis beautify doc strings itself or leave it to users?
|
// FIXME: Should save-analysis beautify doc strings itself or leave it to users?
|
||||||
result.push_str(&beautify_doc_string(val).as_str());
|
result.push_str(&beautify_doc_string(val).as_str());
|
||||||
result.push('\n');
|
result.push('\n');
|
||||||
} else if self.tcx.sess.check_name(attr, sym::doc) {
|
|
||||||
if let Some(meta_list) = attr.meta_item_list() {
|
|
||||||
meta_list
|
|
||||||
.into_iter()
|
|
||||||
.filter(|it| it.has_name(sym::include))
|
|
||||||
.filter_map(|it| it.meta_item_list().map(|l| l.to_owned()))
|
|
||||||
.flat_map(|it| it)
|
|
||||||
.filter(|meta| meta.has_name(sym::contents))
|
|
||||||
.filter_map(|meta| meta.value_str())
|
|
||||||
.for_each(|val| {
|
|
||||||
result.push_str(&val.as_str());
|
|
||||||
result.push('\n');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,6 @@
|
||||||
#![feature(const_fn_transmute)]
|
#![feature(const_fn_transmute)]
|
||||||
#![feature(abi_unadjusted)]
|
#![feature(abi_unadjusted)]
|
||||||
#![feature(adx_target_feature)]
|
#![feature(adx_target_feature)]
|
||||||
#![feature(external_doc)]
|
|
||||||
#![feature(associated_type_bounds)]
|
#![feature(associated_type_bounds)]
|
||||||
#![feature(const_caller_location)]
|
#![feature(const_caller_location)]
|
||||||
#![feature(slice_ptr_get)]
|
#![feature(slice_ptr_get)]
|
||||||
|
|
|
@ -424,9 +424,7 @@ without including it in your main documentation. For example, you could write th
|
||||||
`lib.rs` to test your README as part of your doctests:
|
`lib.rs` to test your README as part of your doctests:
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
#![feature(external_doc)]
|
#[doc = include_str!("../README.md")]
|
||||||
|
|
||||||
#[doc(include = "../README.md")]
|
|
||||||
#[cfg(doctest)]
|
#[cfg(doctest)]
|
||||||
pub struct ReadmeDoctests;
|
pub struct ReadmeDoctests;
|
||||||
```
|
```
|
||||||
|
|
|
@ -131,22 +131,6 @@ Book][unstable-masked] and [its tracking issue][issue-masked].
|
||||||
[unstable-masked]: ../unstable-book/language-features/doc-masked.html
|
[unstable-masked]: ../unstable-book/language-features/doc-masked.html
|
||||||
[issue-masked]: https://github.com/rust-lang/rust/issues/44027
|
[issue-masked]: https://github.com/rust-lang/rust/issues/44027
|
||||||
|
|
||||||
### Include external files as API documentation
|
|
||||||
|
|
||||||
As designed in [RFC 1990], Rustdoc can read an external file to use as a type's documentation. This
|
|
||||||
is useful if certain documentation is so long that it would break the flow of reading the source.
|
|
||||||
Instead of writing it all inline, writing `#[doc(include = "sometype.md")]` will ask Rustdoc to
|
|
||||||
instead read that file and use it as if it were written inline.
|
|
||||||
|
|
||||||
[RFC 1990]: https://github.com/rust-lang/rfcs/pull/1990
|
|
||||||
|
|
||||||
`#[doc(include = "...")]` currently requires the `#![feature(external_doc)]` feature gate. For more
|
|
||||||
information, see [its chapter in the Unstable Book][unstable-include] and [its tracking
|
|
||||||
issue][issue-include].
|
|
||||||
|
|
||||||
[unstable-include]: ../unstable-book/language-features/external-doc.html
|
|
||||||
[issue-include]: https://github.com/rust-lang/rust/issues/44732
|
|
||||||
|
|
||||||
## Unstable command-line arguments
|
## Unstable command-line arguments
|
||||||
|
|
||||||
These features are enabled by passing a command-line flag to Rustdoc, but the flags in question are
|
These features are enabled by passing a command-line flag to Rustdoc, but the flags in question are
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
# `external_doc`
|
|
||||||
|
|
||||||
The tracking issue for this feature is: [#44732]
|
|
||||||
|
|
||||||
The `external_doc` feature allows the use of the `include` parameter to the `#[doc]` attribute, to
|
|
||||||
include external files in documentation. Use the attribute in place of, or in addition to, regular
|
|
||||||
doc comments and `#[doc]` attributes, and `rustdoc` will load the given file when it renders
|
|
||||||
documentation for your crate.
|
|
||||||
|
|
||||||
With the following files in the same directory:
|
|
||||||
|
|
||||||
`external-doc.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# My Awesome Type
|
|
||||||
|
|
||||||
This is the documentation for this spectacular type.
|
|
||||||
```
|
|
||||||
|
|
||||||
`lib.rs`:
|
|
||||||
|
|
||||||
```no_run (needs-external-files)
|
|
||||||
#![feature(external_doc)]
|
|
||||||
|
|
||||||
#[doc(include = "external-doc.md")]
|
|
||||||
pub struct MyAwesomeType;
|
|
||||||
```
|
|
||||||
|
|
||||||
`rustdoc` will load the file `external-doc.md` and use it as the documentation for the `MyAwesomeType`
|
|
||||||
struct.
|
|
||||||
|
|
||||||
When locating files, `rustdoc` will base paths in the `src/` directory, as if they were alongside the
|
|
||||||
`lib.rs` for your crate. So if you want a `docs/` folder to live alongside the `src/` directory,
|
|
||||||
start your paths with `../docs/` for `rustdoc` to properly find the file.
|
|
||||||
|
|
||||||
This feature was proposed in [RFC #1990] and initially implemented in PR [#44781].
|
|
||||||
|
|
||||||
[#44732]: https://github.com/rust-lang/rust/issues/44732
|
|
||||||
[RFC #1990]: https://github.com/rust-lang/rfcs/pull/1990
|
|
||||||
[#44781]: https://github.com/rust-lang/rust/pull/44781
|
|
|
@ -809,7 +809,7 @@ impl AttributesExt for [ast::Attribute] {
|
||||||
// #[doc(...)]
|
// #[doc(...)]
|
||||||
if let Some(list) = attr.meta().as_ref().and_then(|mi| mi.meta_item_list()) {
|
if let Some(list) = attr.meta().as_ref().and_then(|mi| mi.meta_item_list()) {
|
||||||
for item in list {
|
for item in list {
|
||||||
// #[doc(include)]
|
// #[doc(hidden)]
|
||||||
if !item.has_name(sym::cfg) {
|
if !item.has_name(sym::cfg) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -894,9 +894,6 @@ crate enum DocFragmentKind {
|
||||||
SugaredDoc,
|
SugaredDoc,
|
||||||
/// A doc fragment created from a "raw" `#[doc=""]` attribute.
|
/// A doc fragment created from a "raw" `#[doc=""]` attribute.
|
||||||
RawDoc,
|
RawDoc,
|
||||||
/// A doc fragment created from a `#[doc(include="filename")]` attribute. Contains both the
|
|
||||||
/// given filename and the file contents.
|
|
||||||
Include { filename: Symbol },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The goal of this function is to apply the `DocFragment` transformations that are required when
|
// The goal of this function is to apply the `DocFragment` transformations that are required when
|
||||||
|
@ -930,18 +927,8 @@ impl<'a> FromIterator<&'a DocFragment> for String {
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = &'a DocFragment>,
|
T: IntoIterator<Item = &'a DocFragment>,
|
||||||
{
|
{
|
||||||
let mut prev_kind: Option<DocFragmentKind> = None;
|
|
||||||
iter.into_iter().fold(String::new(), |mut acc, frag| {
|
iter.into_iter().fold(String::new(), |mut acc, frag| {
|
||||||
if !acc.is_empty()
|
|
||||||
&& prev_kind
|
|
||||||
.take()
|
|
||||||
.map(|p| matches!(p, DocFragmentKind::Include { .. }) && p != frag.kind)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
acc.push('\n');
|
|
||||||
}
|
|
||||||
add_doc_fragment(&mut acc, &frag);
|
add_doc_fragment(&mut acc, &frag);
|
||||||
prev_kind = Some(frag.kind);
|
|
||||||
acc
|
acc
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -988,45 +975,6 @@ impl Attributes {
|
||||||
self.other_attrs.lists(name)
|
self.other_attrs.lists(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a `MetaItem` from within an attribute, looks for whether it is a
|
|
||||||
/// `#[doc(include="file")]`, and returns the filename and contents of the file as loaded from
|
|
||||||
/// its expansion.
|
|
||||||
crate fn extract_include(mi: &ast::MetaItem) -> Option<(Symbol, Symbol)> {
|
|
||||||
mi.meta_item_list().and_then(|list| {
|
|
||||||
for meta in list {
|
|
||||||
if meta.has_name(sym::include) {
|
|
||||||
// the actual compiled `#[doc(include="filename")]` gets expanded to
|
|
||||||
// `#[doc(include(file="filename", contents="file contents")]` so we need to
|
|
||||||
// look for that instead
|
|
||||||
return meta.meta_item_list().and_then(|list| {
|
|
||||||
let mut filename: Option<Symbol> = None;
|
|
||||||
let mut contents: Option<Symbol> = None;
|
|
||||||
|
|
||||||
for it in list {
|
|
||||||
if it.has_name(sym::file) {
|
|
||||||
if let Some(name) = it.value_str() {
|
|
||||||
filename = Some(name);
|
|
||||||
}
|
|
||||||
} else if it.has_name(sym::contents) {
|
|
||||||
if let Some(docs) = it.value_str() {
|
|
||||||
contents = Some(docs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(filename), Some(contents)) = (filename, contents) {
|
|
||||||
Some((filename, contents))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
crate fn has_doc_flag(&self, flag: Symbol) -> bool {
|
crate fn has_doc_flag(&self, flag: Symbol) -> bool {
|
||||||
for attr in &self.other_attrs {
|
for attr in &self.other_attrs {
|
||||||
if !attr.has_name(sym::doc) {
|
if !attr.has_name(sym::doc) {
|
||||||
|
@ -1050,18 +998,9 @@ impl Attributes {
|
||||||
let mut doc_strings: Vec<DocFragment> = vec![];
|
let mut doc_strings: Vec<DocFragment> = vec![];
|
||||||
let mut doc_line = 0;
|
let mut doc_line = 0;
|
||||||
|
|
||||||
fn update_need_backline(doc_strings: &mut Vec<DocFragment>, frag: &DocFragment) {
|
fn update_need_backline(doc_strings: &mut Vec<DocFragment>) {
|
||||||
if let Some(prev) = doc_strings.last_mut() {
|
if let Some(prev) = doc_strings.last_mut() {
|
||||||
if matches!(prev.kind, DocFragmentKind::Include { .. })
|
prev.need_backline = true;
|
||||||
|| prev.kind != frag.kind
|
|
||||||
|| prev.parent_module != frag.parent_module
|
|
||||||
{
|
|
||||||
// add a newline for extra padding between segments
|
|
||||||
prev.need_backline = prev.kind == DocFragmentKind::SugaredDoc
|
|
||||||
|| prev.kind == DocFragmentKind::RawDoc
|
|
||||||
} else {
|
|
||||||
prev.need_backline = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1087,31 +1026,12 @@ impl Attributes {
|
||||||
indent: 0,
|
indent: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
update_need_backline(&mut doc_strings, &frag);
|
update_need_backline(&mut doc_strings);
|
||||||
|
|
||||||
doc_strings.push(frag);
|
doc_strings.push(frag);
|
||||||
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
if attr.has_name(sym::doc) {
|
|
||||||
if let Some(mi) = attr.meta() {
|
|
||||||
if let Some((filename, contents)) = Attributes::extract_include(&mi) {
|
|
||||||
let line = doc_line;
|
|
||||||
doc_line += contents.as_str().lines().count();
|
|
||||||
let frag = DocFragment {
|
|
||||||
line,
|
|
||||||
span: attr.span,
|
|
||||||
doc: contents,
|
|
||||||
kind: DocFragmentKind::Include { filename },
|
|
||||||
parent_module,
|
|
||||||
need_backline: false,
|
|
||||||
indent: 0,
|
|
||||||
};
|
|
||||||
update_need_backline(&mut doc_strings, &frag);
|
|
||||||
doc_strings.push(frag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(attr.clone())
|
Some(attr.clone())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1137,10 +1057,7 @@ impl Attributes {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
add_doc_fragment(&mut out, &ori);
|
add_doc_fragment(&mut out, &ori);
|
||||||
while let Some(new_frag) = iter.next() {
|
while let Some(new_frag) = iter.next() {
|
||||||
if matches!(ori.kind, DocFragmentKind::Include { .. })
|
if new_frag.kind != ori.kind || new_frag.parent_module != ori.parent_module {
|
||||||
|| new_frag.kind != ori.kind
|
|
||||||
|| new_frag.parent_module != ori.parent_module
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
add_doc_fragment(&mut out, &new_frag);
|
add_doc_fragment(&mut out, &new_frag);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#![feature(external_doc)]
|
#[doc = include_str!("input.md")]
|
||||||
|
|
||||||
#[doc(include="input.md")]
|
|
||||||
pub struct SomeStruct;
|
pub struct SomeStruct;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#![feature(box_syntax)]
|
#![feature(box_syntax)]
|
||||||
#![feature(rustc_private)]
|
#![feature(rustc_private)]
|
||||||
#![feature(associated_type_defaults)]
|
#![feature(associated_type_defaults)]
|
||||||
#![feature(external_doc)]
|
|
||||||
|
|
||||||
extern crate rustc_graphviz;
|
extern crate rustc_graphviz;
|
||||||
// A simple rust project
|
// A simple rust project
|
||||||
|
@ -454,9 +453,9 @@ impl Iterator for SilenceGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc = include_str!("extra-docs.md")]
|
||||||
|
struct StructWithDocs;
|
||||||
|
|
||||||
trait Foo {
|
trait Foo {
|
||||||
type Bar = FrameBuffer;
|
type Bar = FrameBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(include = "extra-docs.md")]
|
|
||||||
struct StructWithDocs;
|
|
||||||
|
|
10
src/test/rustdoc-ui/doc-include-suggestion.rs
Normal file
10
src/test/rustdoc-ui/doc-include-suggestion.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
#[doc(include = "external-cross-doc.md")]
|
||||||
|
//~^ WARNING unknown `doc` attribute `include`
|
||||||
|
//~| HELP use `doc = include_str!` instead
|
||||||
|
// FIXME(#85497): make this a deny instead so it's more clear what's happening
|
||||||
|
//~| NOTE on by default
|
||||||
|
//~| WARNING previously accepted
|
||||||
|
//~| NOTE see issue #82730
|
||||||
|
pub struct NeedMoreDocs;
|
12
src/test/rustdoc-ui/doc-include-suggestion.stderr
Normal file
12
src/test/rustdoc-ui/doc-include-suggestion.stderr
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
warning: unknown `doc` attribute `include`
|
||||||
|
--> $DIR/doc-include-suggestion.rs:3:7
|
||||||
|
|
|
||||||
|
LL | #[doc(include = "external-cross-doc.md")]
|
||||||
|
| ------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- help: use `doc = include_str!` instead: `#[doc = include_str!("external-cross-doc.md")]`
|
||||||
|
|
|
||||||
|
= note: `#[warn(invalid_doc_attributes)]` on by default
|
||||||
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
|
||||||
|
= note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
|
||||||
|
|
||||||
|
warning: 1 warning emitted
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Cross-crate imported docs
|
# Cross-crate imported docs
|
||||||
|
|
||||||
This file is to make sure `#[doc(include="file.md")]` works when you re-export an item with included
|
This file is to make sure `#[doc = include_str!("file.md")]` works when you re-export an item with included
|
||||||
docs.
|
docs.
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![feature(external_doc)]
|
#[deny(missing_docs)]
|
||||||
#![deny(missing_doc)]
|
#[doc = include_str!("external-cross-doc.md")]
|
||||||
|
|
||||||
#[doc(include="external-cross-doc.md")]
|
|
||||||
pub struct NeedMoreDocs;
|
pub struct NeedMoreDocs;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# External Docs
|
# External Docs
|
||||||
|
|
||||||
This file is here to test the `#[doc(include="file")]` attribute.
|
This file is here to test the `#[doc = include_str!("file")]` attribute.
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
#![feature(external_doc)]
|
|
||||||
|
|
||||||
// @has external_doc/struct.CanHasDocs.html
|
|
||||||
// @has - '//h1' 'External Docs'
|
|
||||||
// @has - '//h2' 'Inline Docs'
|
|
||||||
#[doc(include = "auxiliary/external-doc.md")]
|
|
||||||
/// ## Inline Docs
|
|
||||||
pub struct CanHasDocs;
|
|
||||||
|
|
||||||
// @has external_doc/struct.IncludeStrDocs.html
|
// @has external_doc/struct.IncludeStrDocs.html
|
||||||
// @has - '//h1' 'External Docs'
|
// @has - '//h1' 'External Docs'
|
||||||
// @has - '//h2' 'Inline Docs'
|
// @has - '//h2' 'Inline Docs'
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![feature(external_doc)]
|
|
||||||
|
|
||||||
#![crate_name = "foo"]
|
#![crate_name = "foo"]
|
||||||
|
|
||||||
// @has foo/struct.Example.html
|
// @has foo/struct.Example.html
|
||||||
|
@ -51,7 +49,7 @@ pub struct I;
|
||||||
// @matches - '//div[@class="docblock"]/p' '(?m)a\nno whitespace\nJust some text.\Z'
|
// @matches - '//div[@class="docblock"]/p' '(?m)a\nno whitespace\nJust some text.\Z'
|
||||||
///a
|
///a
|
||||||
///no whitespace
|
///no whitespace
|
||||||
#[doc(include = "unindent.md")]
|
#[doc = include_str!("unindent.md")]
|
||||||
pub struct J;
|
pub struct J;
|
||||||
|
|
||||||
// @has foo/struct.K.html
|
// @has foo/struct.K.html
|
||||||
|
@ -60,5 +58,5 @@ pub struct J;
|
||||||
///
|
///
|
||||||
/// 4 whitespaces!
|
/// 4 whitespaces!
|
||||||
///
|
///
|
||||||
#[doc(include = "unindent.md")]
|
#[doc = include_str!("unindent.md")]
|
||||||
pub struct K;
|
pub struct K;
|
||||||
|
|
31
src/test/ui/extern/external-doc-error.rs
vendored
31
src/test/ui/extern/external-doc-error.rs
vendored
|
@ -1,31 +0,0 @@
|
||||||
// normalize-stderr-test: "not-a-file.md:.*\(" -> "not-a-file.md: $$FILE_NOT_FOUND_MSG ("
|
|
||||||
|
|
||||||
#![feature(external_doc)]
|
|
||||||
|
|
||||||
#[doc(include = "not-a-file.md")]
|
|
||||||
pub struct SomeStruct; //~^ ERROR couldn't read
|
|
||||||
|
|
||||||
#[doc(include = "auxiliary/invalid-utf8.txt")]
|
|
||||||
pub struct InvalidUtf8; //~^ ERROR wasn't a utf-8 file
|
|
||||||
|
|
||||||
#[doc(include)]
|
|
||||||
pub struct MissingPath; //~^ ERROR expected path
|
|
||||||
//~| HELP provide a file path with `=`
|
|
||||||
//~| SUGGESTION include = "<path>"
|
|
||||||
|
|
||||||
#[doc(include("../README.md"))]
|
|
||||||
pub struct InvalidPathSyntax; //~^ ERROR expected path
|
|
||||||
//~| HELP provide a file path with `=`
|
|
||||||
//~| SUGGESTION include = "../README.md"
|
|
||||||
|
|
||||||
#[doc(include = 123)]
|
|
||||||
pub struct InvalidPathType; //~^ ERROR expected path
|
|
||||||
//~| HELP provide a file path with `=`
|
|
||||||
//~| SUGGESTION include = "<path>"
|
|
||||||
|
|
||||||
#[doc(include(123))]
|
|
||||||
pub struct InvalidPathSyntaxAndType; //~^ ERROR expected path
|
|
||||||
//~| HELP provide a file path with `=`
|
|
||||||
//~| SUGGESTION include = "<path>"
|
|
||||||
|
|
||||||
fn main() {}
|
|
38
src/test/ui/extern/external-doc-error.stderr
vendored
38
src/test/ui/extern/external-doc-error.stderr
vendored
|
@ -1,38 +0,0 @@
|
||||||
error: couldn't read $DIR/not-a-file.md: $FILE_NOT_FOUND_MSG (os error 2)
|
|
||||||
--> $DIR/external-doc-error.rs:5:17
|
|
||||||
|
|
|
||||||
LL | #[doc(include = "not-a-file.md")]
|
|
||||||
| ^^^^^^^^^^^^^^^ couldn't read file
|
|
||||||
|
|
||||||
error: $DIR/auxiliary/invalid-utf8.txt wasn't a utf-8 file
|
|
||||||
--> $DIR/external-doc-error.rs:8:17
|
|
||||||
|
|
|
||||||
LL | #[doc(include = "auxiliary/invalid-utf8.txt")]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ contains invalid utf-8
|
|
||||||
|
|
||||||
error: expected path to external documentation
|
|
||||||
--> $DIR/external-doc-error.rs:11:7
|
|
||||||
|
|
|
||||||
LL | #[doc(include)]
|
|
||||||
| ^^^^^^^ help: provide a file path with `=`: `include = "<path>"`
|
|
||||||
|
|
||||||
error: expected path to external documentation
|
|
||||||
--> $DIR/external-doc-error.rs:16:7
|
|
||||||
|
|
|
||||||
LL | #[doc(include("../README.md"))]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: provide a file path with `=`: `include = "../README.md"`
|
|
||||||
|
|
||||||
error: expected path to external documentation
|
|
||||||
--> $DIR/external-doc-error.rs:21:7
|
|
||||||
|
|
|
||||||
LL | #[doc(include = 123)]
|
|
||||||
| ^^^^^^^^^^^^^ help: provide a file path with `=`: `include = "<path>"`
|
|
||||||
|
|
||||||
error: expected path to external documentation
|
|
||||||
--> $DIR/external-doc-error.rs:26:7
|
|
||||||
|
|
|
||||||
LL | #[doc(include(123))]
|
|
||||||
| ^^^^^^^^^^^^ help: provide a file path with `=`: `include = "<path>"`
|
|
||||||
|
|
||||||
error: aborting due to 6 previous errors
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#[doc(include="asdf.md")] //~ ERROR: `#[doc(include)]` is experimental
|
|
||||||
//~| ERROR: `#[doc(include)]` is experimental
|
|
||||||
fn main() {}
|
|
|
@ -1,21 +0,0 @@
|
||||||
error[E0658]: `#[doc(include)]` is experimental
|
|
||||||
--> $DIR/feature-gate-external_doc.rs:1:1
|
|
||||||
|
|
|
||||||
LL | #[doc(include="asdf.md")]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: see issue #44732 <https://github.com/rust-lang/rust/issues/44732> for more information
|
|
||||||
= help: add `#![feature(external_doc)]` to the crate attributes to enable
|
|
||||||
|
|
||||||
error[E0658]: `#[doc(include)]` is experimental
|
|
||||||
--> $DIR/feature-gate-external_doc.rs:1:1
|
|
||||||
|
|
|
||||||
LL | #[doc(include="asdf.md")]
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: see issue #44732 <https://github.com/rust-lang/rust/issues/44732> for more information
|
|
||||||
= help: add `#![feature(external_doc)]` to the crate attributes to enable
|
|
||||||
|
|
||||||
error: aborting due to 2 previous errors
|
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0658`.
|
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
use clippy_utils::attrs::is_doc_hidden;
|
use clippy_utils::attrs::is_doc_hidden;
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use if_chain::if_chain;
|
use rustc_ast::ast;
|
||||||
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
|
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
|
@ -56,20 +55,6 @@ impl MissingDoc {
|
||||||
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
|
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_include(meta: Option<MetaItem>) -> bool {
|
|
||||||
if_chain! {
|
|
||||||
if let Some(meta) = meta;
|
|
||||||
if let MetaItemKind::List(list) = meta.kind;
|
|
||||||
if let Some(meta) = list.get(0);
|
|
||||||
if let Some(name) = meta.ident();
|
|
||||||
then {
|
|
||||||
name.name == sym::include
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_missing_docs_attrs(
|
fn check_missing_docs_attrs(
|
||||||
&self,
|
&self,
|
||||||
cx: &LateContext<'_>,
|
cx: &LateContext<'_>,
|
||||||
|
@ -95,7 +80,7 @@ impl MissingDoc {
|
||||||
|
|
||||||
let has_doc = attrs
|
let has_doc = attrs
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
|
.any(|a| a.doc_str().is_some());
|
||||||
if !has_doc {
|
if !has_doc {
|
||||||
span_lint(
|
span_lint(
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![warn(clippy::missing_docs_in_private_items)]
|
#![warn(clippy::missing_docs_in_private_items)]
|
||||||
#![feature(external_doc)]
|
#![doc = include_str!("../../README.md")]
|
||||||
#![doc(include = "../../README.md")]
|
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue