mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-14 02:49:40 +00:00
Make missing documentation linting more robust
Add some more cases for warning about missing documentation, and also add a test to make sure it doesn't die in the future.
This commit is contained in:
parent
3a3bf8bdef
commit
af995ce1e7
@ -12,6 +12,8 @@
|
|||||||
// and injected into each crate the compiler builds. Keep it small.
|
// and injected into each crate the compiler builds. Keep it small.
|
||||||
|
|
||||||
pub mod intrinsic {
|
pub mod intrinsic {
|
||||||
|
#[allow(missing_doc)];
|
||||||
|
|
||||||
pub use intrinsic::rusti::visit_tydesc;
|
pub use intrinsic::rusti::visit_tydesc;
|
||||||
|
|
||||||
// FIXME (#3727): remove this when the interface has settled and the
|
// FIXME (#3727): remove this when the interface has settled and the
|
||||||
|
@ -95,8 +95,7 @@ pub enum lint {
|
|||||||
unused_mut,
|
unused_mut,
|
||||||
unnecessary_allocation,
|
unnecessary_allocation,
|
||||||
|
|
||||||
missing_struct_doc,
|
missing_doc,
|
||||||
missing_trait_doc,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn level_to_str(lv: level) -> &'static str {
|
pub fn level_to_str(lv: level) -> &'static str {
|
||||||
@ -268,17 +267,10 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[
|
|||||||
default: warn
|
default: warn
|
||||||
}),
|
}),
|
||||||
|
|
||||||
("missing_struct_doc",
|
("missing_doc",
|
||||||
LintSpec {
|
LintSpec {
|
||||||
lint: missing_struct_doc,
|
lint: missing_doc,
|
||||||
desc: "detects missing documentation for structs",
|
desc: "detects missing documentation for public members",
|
||||||
default: allow
|
|
||||||
}),
|
|
||||||
|
|
||||||
("missing_trait_doc",
|
|
||||||
LintSpec {
|
|
||||||
lint: missing_trait_doc,
|
|
||||||
desc: "detects missing documentation for traits",
|
|
||||||
default: allow
|
default: allow
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@ -302,6 +294,9 @@ struct Context {
|
|||||||
curr: SmallIntMap<(level, LintSource)>,
|
curr: SmallIntMap<(level, LintSource)>,
|
||||||
// context we're checking in (used to access fields like sess)
|
// context we're checking in (used to access fields like sess)
|
||||||
tcx: ty::ctxt,
|
tcx: ty::ctxt,
|
||||||
|
// Just a simple flag if we're currently recursing into a trait
|
||||||
|
// implementation. This is only used by the lint_missing_doc() pass
|
||||||
|
in_trait_impl: bool,
|
||||||
// When recursing into an attributed node of the ast which modifies lint
|
// When recursing into an attributed node of the ast which modifies lint
|
||||||
// levels, this stack keeps track of the previous lint levels of whatever
|
// levels, this stack keeps track of the previous lint levels of whatever
|
||||||
// was modified.
|
// was modified.
|
||||||
@ -311,7 +306,15 @@ struct Context {
|
|||||||
// Others operate directly on @ast::item structures (or similar). Finally,
|
// Others operate directly on @ast::item structures (or similar). Finally,
|
||||||
// others still are added to the Session object via `add_lint`, and these
|
// others still are added to the Session object via `add_lint`, and these
|
||||||
// are all passed with the lint_session visitor.
|
// are all passed with the lint_session visitor.
|
||||||
visitors: ~[visit::vt<@mut Context>],
|
//
|
||||||
|
// This is a pair so every visitor can visit every node. When a lint pass is
|
||||||
|
// registered, another visitor is created which stops at all items which can
|
||||||
|
// alter the attributes of the ast. This "item stopping visitor" is the
|
||||||
|
// second element of the pair, while the original visitor is the first
|
||||||
|
// element. This means that when visiting a node, the original recursive
|
||||||
|
// call can used the original visitor's method, although the recursing
|
||||||
|
// visitor supplied to the method is the item stopping visitor.
|
||||||
|
visitors: ~[(visit::vt<@mut Context>, visit::vt<@mut Context>)],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@ -429,19 +432,21 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_lint(&mut self, v: visit::vt<@mut Context>) {
|
fn add_lint(&mut self, v: visit::vt<@mut Context>) {
|
||||||
self.visitors.push(item_stopping_visitor(v));
|
self.visitors.push((v, item_stopping_visitor(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process(@mut self, n: AttributedNode) {
|
fn process(@mut self, n: AttributedNode) {
|
||||||
|
// see comment of the `visitors` field in the struct for why there's a
|
||||||
|
// pair instead of just one visitor.
|
||||||
match n {
|
match n {
|
||||||
Item(it) => {
|
Item(it) => {
|
||||||
for self.visitors.each |v| {
|
for self.visitors.each |&(orig, stopping)| {
|
||||||
visit::visit_item(it, self, *v);
|
(orig.visit_item)(it, self, stopping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Crate(c) => {
|
Crate(c) => {
|
||||||
for self.visitors.each |v| {
|
for self.visitors.each |&(_, stopping)| {
|
||||||
visit::visit_crate(c, self, *v);
|
visit::visit_crate(c, self, stopping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Can't use visit::visit_method_helper because the
|
// Can't use visit::visit_method_helper because the
|
||||||
@ -449,9 +454,9 @@ impl Context {
|
|||||||
// to be a no-op, so manually invoke visit_fn.
|
// to be a no-op, so manually invoke visit_fn.
|
||||||
Method(m) => {
|
Method(m) => {
|
||||||
let fk = visit::fk_method(copy m.ident, &m.generics, m);
|
let fk = visit::fk_method(copy m.ident, &m.generics, m);
|
||||||
for self.visitors.each |v| {
|
for self.visitors.each |&(orig, stopping)| {
|
||||||
visit::visit_fn(&fk, &m.decl, &m.body, m.span, m.id,
|
(orig.visit_fn)(&fk, &m.decl, &m.body, m.span, m.id,
|
||||||
self, *v);
|
self, stopping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -495,16 +500,16 @@ pub fn each_lint(sess: session::Session,
|
|||||||
// This is used to make the simple visitors used for the lint passes
|
// This is used to make the simple visitors used for the lint passes
|
||||||
// not traverse into subitems, since that is handled by the outer
|
// not traverse into subitems, since that is handled by the outer
|
||||||
// lint visitor.
|
// lint visitor.
|
||||||
fn item_stopping_visitor<E: Copy>(v: visit::vt<E>) -> visit::vt<E> {
|
fn item_stopping_visitor<E: Copy>(outer: visit::vt<E>) -> visit::vt<E> {
|
||||||
visit::mk_vt(@visit::Visitor {
|
visit::mk_vt(@visit::Visitor {
|
||||||
visit_item: |_i, _e, _v| { },
|
visit_item: |_i, _e, _v| { },
|
||||||
visit_fn: |fk, fd, b, s, id, e, v| {
|
visit_fn: |fk, fd, b, s, id, e, v| {
|
||||||
match *fk {
|
match *fk {
|
||||||
visit::fk_method(*) => {}
|
visit::fk_method(*) => {}
|
||||||
_ => visit::visit_fn(fk, fd, b, s, id, e, v)
|
_ => (outer.visit_fn)(fk, fd, b, s, id, e, v)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.. **(ty_stopping_visitor(v))})
|
.. **(ty_stopping_visitor(outer))})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ty_stopping_visitor<E>(v: visit::vt<E>) -> visit::vt<E> {
|
fn ty_stopping_visitor<E>(v: visit::vt<E>) -> visit::vt<E> {
|
||||||
@ -972,68 +977,84 @@ fn lint_unnecessary_allocations() -> visit::vt<@mut Context> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lint_missing_struct_doc() -> visit::vt<@mut Context> {
|
fn lint_missing_doc() -> visit::vt<@mut Context> {
|
||||||
visit::mk_vt(@visit::Visitor {
|
fn check_attrs(cx: @mut Context, attrs: &[ast::attribute],
|
||||||
visit_struct_field: |field, cx: @mut Context, vt| {
|
sp: span, msg: &str) {
|
||||||
let relevant = match field.node.kind {
|
if !attrs.any(|a| a.node.is_sugared_doc) {
|
||||||
ast::named_field(_, vis) => vis != ast::private,
|
cx.span_lint(missing_doc, sp, msg);
|
||||||
ast::unnamed_field => false,
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if relevant {
|
visit::mk_vt(@visit::Visitor {
|
||||||
let mut has_doc = false;
|
visit_struct_method: |m, cx, vt| {
|
||||||
for field.node.attrs.each |attr| {
|
if m.vis == ast::public {
|
||||||
if attr.node.is_sugared_doc {
|
check_attrs(cx, m.attrs, m.span,
|
||||||
has_doc = true;
|
"missing documentation for a method");
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !has_doc {
|
|
||||||
cx.span_lint(missing_struct_doc, field.span, "missing documentation \
|
|
||||||
for a field.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
visit::visit_struct_method(m, cx, vt);
|
||||||
visit::visit_struct_field(field, cx, vt);
|
|
||||||
},
|
},
|
||||||
.. *visit::default_visitor()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lint_missing_trait_doc() -> visit::vt<@mut Context> {
|
visit_ty_method: |m, cx, vt| {
|
||||||
visit::mk_vt(@visit::Visitor {
|
// All ty_method objects are linted about because they're part of a
|
||||||
visit_trait_method: |method, cx: @mut Context, vt| {
|
// trait (no visibility)
|
||||||
let mut has_doc = false;
|
check_attrs(cx, m.attrs, m.span,
|
||||||
let span = match copy *method {
|
"missing documentation for a method");
|
||||||
ast::required(m) => {
|
visit::visit_ty_method(m, cx, vt);
|
||||||
for m.attrs.each |attr| {
|
},
|
||||||
if attr.node.is_sugared_doc {
|
|
||||||
has_doc = true;
|
visit_fn: |fk, d, b, sp, id, cx, vt| {
|
||||||
break;
|
// Only warn about explicitly public methods. Soon implicit
|
||||||
}
|
// public-ness will hopefully be going away.
|
||||||
|
match *fk {
|
||||||
|
visit::fk_method(_, _, m) if m.vis == ast::public => {
|
||||||
|
// If we're in a trait implementation, no need to duplicate
|
||||||
|
// documentation
|
||||||
|
if !cx.in_trait_impl {
|
||||||
|
check_attrs(cx, m.attrs, sp,
|
||||||
|
"missing documentation for a method");
|
||||||
}
|
}
|
||||||
m.span
|
}
|
||||||
},
|
|
||||||
ast::provided(m) => {
|
_ => {}
|
||||||
if m.vis == ast::private {
|
}
|
||||||
has_doc = true;
|
visit::visit_fn(fk, d, b, sp, id, cx, vt);
|
||||||
} else {
|
},
|
||||||
for m.attrs.each |attr| {
|
|
||||||
if attr.node.is_sugared_doc {
|
visit_item: |it, cx, vt| {
|
||||||
has_doc = true;
|
match it.node {
|
||||||
break;
|
// Go ahead and match the fields here instead of using
|
||||||
|
// visit_struct_field while we have access to the enclosing
|
||||||
|
// struct's visibility
|
||||||
|
ast::item_struct(sdef, _) if it.vis == ast::public => {
|
||||||
|
check_attrs(cx, it.attrs, it.span,
|
||||||
|
"missing documentation for a struct");
|
||||||
|
for sdef.fields.each |field| {
|
||||||
|
match field.node.kind {
|
||||||
|
ast::named_field(_, vis) if vis != ast::private => {
|
||||||
|
check_attrs(cx, field.node.attrs, field.span,
|
||||||
|
"missing documentation for a field");
|
||||||
}
|
}
|
||||||
|
ast::unnamed_field | ast::named_field(*) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.span
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ast::item_trait(*) if it.vis == ast::public => {
|
||||||
|
check_attrs(cx, it.attrs, it.span,
|
||||||
|
"missing documentation for a trait");
|
||||||
|
}
|
||||||
|
|
||||||
|
ast::item_fn(*) if it.vis == ast::public => {
|
||||||
|
check_attrs(cx, it.attrs, it.span,
|
||||||
|
"missing documentation for a function");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
};
|
};
|
||||||
if !has_doc {
|
|
||||||
cx.span_lint(missing_trait_doc, span, "missing documentation \
|
visit::visit_item(it, cx, vt);
|
||||||
for a method.");
|
|
||||||
}
|
|
||||||
visit::visit_trait_method(method, cx, vt);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.. *visit::default_visitor()
|
.. *visit::default_visitor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1045,6 +1066,7 @@ pub fn check_crate(tcx: ty::ctxt, crate: @ast::crate) {
|
|||||||
tcx: tcx,
|
tcx: tcx,
|
||||||
lint_stack: ~[],
|
lint_stack: ~[],
|
||||||
visitors: ~[],
|
visitors: ~[],
|
||||||
|
in_trait_impl: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Install defaults.
|
// Install defaults.
|
||||||
@ -1066,8 +1088,7 @@ pub fn check_crate(tcx: ty::ctxt, crate: @ast::crate) {
|
|||||||
cx.add_lint(lint_unused_mut());
|
cx.add_lint(lint_unused_mut());
|
||||||
cx.add_lint(lint_session());
|
cx.add_lint(lint_session());
|
||||||
cx.add_lint(lint_unnecessary_allocations());
|
cx.add_lint(lint_unnecessary_allocations());
|
||||||
cx.add_lint(lint_missing_struct_doc());
|
cx.add_lint(lint_missing_doc());
|
||||||
cx.add_lint(lint_missing_trait_doc());
|
|
||||||
|
|
||||||
// Actually perform the lint checks (iterating the ast)
|
// Actually perform the lint checks (iterating the ast)
|
||||||
do cx.with_lint_attrs(crate.node.attrs) {
|
do cx.with_lint_attrs(crate.node.attrs) {
|
||||||
@ -1076,6 +1097,12 @@ pub fn check_crate(tcx: ty::ctxt, crate: @ast::crate) {
|
|||||||
visit::visit_crate(crate, cx, visit::mk_vt(@visit::Visitor {
|
visit::visit_crate(crate, cx, visit::mk_vt(@visit::Visitor {
|
||||||
visit_item: |it, cx: @mut Context, vt| {
|
visit_item: |it, cx: @mut Context, vt| {
|
||||||
do cx.with_lint_attrs(it.attrs) {
|
do cx.with_lint_attrs(it.attrs) {
|
||||||
|
match it.node {
|
||||||
|
ast::item_impl(_, Some(*), _, _) => {
|
||||||
|
cx.in_trait_impl = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
check_item_ctypes(cx, it);
|
check_item_ctypes(cx, it);
|
||||||
check_item_non_camel_case_types(cx, it);
|
check_item_non_camel_case_types(cx, it);
|
||||||
check_item_default_methods(cx, it);
|
check_item_default_methods(cx, it);
|
||||||
@ -1083,6 +1110,7 @@ pub fn check_crate(tcx: ty::ctxt, crate: @ast::crate) {
|
|||||||
|
|
||||||
cx.process(Item(it));
|
cx.process(Item(it));
|
||||||
visit::visit_item(it, cx, vt);
|
visit::visit_item(it, cx, vt);
|
||||||
|
cx.in_trait_impl = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
visit_fn: |fk, decl, body, span, id, cx, vt| {
|
visit_fn: |fk, decl, body, span, id, cx, vt| {
|
||||||
|
72
src/test/compile-fail/lint-missing-doc.rs
Normal file
72
src/test/compile-fail/lint-missing-doc.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
// When denying at the crate level, be sure to not get random warnings from the
|
||||||
|
// injected intrinsics by the compiler.
|
||||||
|
#[deny(missing_doc)];
|
||||||
|
|
||||||
|
struct Foo {
|
||||||
|
a: int,
|
||||||
|
priv b: int,
|
||||||
|
pub c: int, // doesn't matter, Foo is private
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PubFoo { //~ ERROR: missing documentation
|
||||||
|
a: int, //~ ERROR: missing documentation
|
||||||
|
priv b: int,
|
||||||
|
pub c: int, //~ ERROR: missing documentation
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_doc)]
|
||||||
|
pub struct PubFoo2 {
|
||||||
|
a: int,
|
||||||
|
pub c: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dox
|
||||||
|
pub fn foo() {}
|
||||||
|
pub fn foo2() {} //~ ERROR: missing documentation
|
||||||
|
fn foo3() {}
|
||||||
|
#[allow(missing_doc)] pub fn foo4() {}
|
||||||
|
|
||||||
|
/// dox
|
||||||
|
pub trait A {}
|
||||||
|
trait B {}
|
||||||
|
pub trait C {} //~ ERROR: missing documentation
|
||||||
|
#[allow(missing_doc)] pub trait D {}
|
||||||
|
|
||||||
|
trait Bar {
|
||||||
|
/// dox
|
||||||
|
pub fn foo();
|
||||||
|
fn foo2(); //~ ERROR: missing documentation
|
||||||
|
pub fn foo3(); //~ ERROR: missing documentation
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Foo {
|
||||||
|
pub fn foo() {} //~ ERROR: missing documentation
|
||||||
|
/// dox
|
||||||
|
pub fn foo1() {}
|
||||||
|
fn foo2() {}
|
||||||
|
#[allow(missing_doc)] pub fn foo3() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_doc)]
|
||||||
|
trait F {
|
||||||
|
pub fn a();
|
||||||
|
fn b(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should need to redefine documentation for implementations of traits
|
||||||
|
impl F for Foo {
|
||||||
|
pub fn a() {}
|
||||||
|
fn b(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
Loading…
Reference in New Issue
Block a user