Add doc_examples_missing_item lint - #17766
LucaCappelletti94 wants to merge 1 commit into
Conversation
99d2968 to
a1d65ac
Compare
|
Lintcheck changes for a6a1f61
This comment will be updated if you push new changes |
|
Some changes occurred in clippy_lints/src/doc cc @notriddle |
There was a problem hiding this comment.
Why is this so big?! needless_doctest_main.rs is less than a hundred lines of code and it’s doing a similar check to this one.
There was a problem hiding this comment.
I familiarized with the similar linting, refactored and trimmed down significantly. Thanks for the patience.
| /// currently being checked. | ||
| pub(super) fn owner(cx: &LateContext<'_>, check_private_items: bool) -> Option<Checker> { | ||
| let id = cx.last_node_with_lint_attrs; | ||
| if is_lint_allowed(cx, DOC_EXAMPLES_MISSING_ITEM, id) { |
There was a problem hiding this comment.
This shouldn’t be needed.
| if is_lint_allowed(cx, DOC_EXAMPLES_MISSING_ITEM, id) { | ||
| return None; | ||
| } | ||
| let (span, owner_def_id, ident) = match cx.tcx.hir_node(id) { |
There was a problem hiding this comment.
There was a problem hiding this comment.
Primarily, ignorance. I will hopefully familiarize rapidly over these utilities. Thank you for pointing it out.
| impl Checker { | ||
| pub(super) fn start_block(&mut self, tags: CodeTags) { | ||
| // Excluded blocks neither establish an example nor satisfy the name check. The shared | ||
| // CodeTags classification stays untouched so neighboring lints are unaffected. |
There was a problem hiding this comment.
The shared CodeTags classification stays untouched so neighboring lints are unaffected
No kidding. Delete this comment. It is slop.
| } | ||
|
|
||
| /// Whether one example mentions the item name as an identifier token. Malformed tokens such as | ||
| /// unterminated literals or comments are skipped, because a block that cannot compile is not an |
There was a problem hiding this comment.
a block that cannot compile is not an example
Why not? It’ll fail when you run cargo test, so it doesn’t matter if we get a “false negative” here.
| } | ||
|
|
||
| /// Applies rustdoc's hidden-line rules. Hidden lines stay doctest code, so their idents count. | ||
| fn strip_hidden_line_markers(code: &str) -> Cow<'_, str> { |
There was a problem hiding this comment.
If this lint really does need to strip hidden line markers, make it a utility function.
757488a to
4f96294
Compare
| }) | ||
| } | ||
|
|
||
| pub(super) fn check(cx: &LateContext<'_>, ident: Ident) { |
There was a problem hiding this comment.
Don’t call it “check.” It’s not actuator checking anything. It’s just reporting.
4f96294 to
7e4afc5
Compare
| pub(super) fn mentions(code: &str, ident: Ident) -> bool { | ||
| if !code.contains(ident.as_str()) { | ||
| return false; | ||
| } | ||
|
|
||
| tokenize_with_text(code).any(|(kind, text, _)| { | ||
| matches!(kind, TokenKind::Ident | TokenKind::RawIdent) | ||
| && text.strip_prefix("r#").unwrap_or(text) == ident.as_str() | ||
| }) | ||
| } |
There was a problem hiding this comment.
Looking at the lintcheck results, we might want to skip structs and traits as they would be trickier to handle, they all seem like false positives.
There was a problem hiding this comment.
I agree that this check on methods/functions has likely much lower false positives, but a few (6 our of 13) of the cases of interest in Diesel which I caught with dejadoc were on structs, therefore I would believe it still has value.
@notriddle is there any particular acceptable pattern in clippy to say "check X, but only for item kinds in set Y", so that users may decide whether they want to enable this on any particular set of items? Or should it explode into a set of lints like doc_examples_missing_*?
There was a problem hiding this comment.
You could use clippy.toml for that?
https://doc.rust-lang.org/nightly/clippy/lint_configuration.html
There was a problem hiding this comment.
I wasn't aware it was possible to configure each lint, I hadn't ever had to config it in a repo, thank you for pointing it out. I will familiarize with it and proceed as suggested.
There was a problem hiding this comment.
Added the configuration, by default parametrized as @Gri-ffin suggests, and optionally parametrizable to include all item kinds. @notriddle please do let me whether I have set it up correctly or I have missed using some of the intended patterns/methods.
There was a problem hiding this comment.
I don’t feel qualified to answer this question. I know Clippy has “restriction” lints that are allowed to flag harmless code, as long as the lint perform as advertised, but I’m not sure how open the team is to adding them.
There was a problem hiding this comment.
I removed the configuration together with the struct, enum, union and trait support, so the lint now only covers functions, methods, constants and statics. Should be good to go now.
There was a problem hiding this comment.
@Gri-ffin do the more recent updates address the earlier issues you raised satisfactorily?
There was a problem hiding this comment.
They are yes, due to policy though you should find a reviewer in #t-clippy or #llm-reviews to review this PR given it was assisted by LLM. I was just doing a drive by review.
Here is the link to the policy: https://forge.rust-lang.org/policies/llm-usage.html
There was a problem hiding this comment.
I appreciate it, just wanted to make sure the lint is now as you would expect it to be.
a43911e to
ef1b028
Compare
eb550b0 to
cecaba3
Compare
There was a problem hiding this comment.
Community review:
I have a few comments as well, nothing large.
Mostly understanding on my side.
I think the diff should show well why we require an reviewer BEFORE openeing an PR...
Lets see if someone wants to review it on zulip ✔️
| // Type and trait declarations are not checked. | ||
| /// ``` | ||
| /// let _ = 1; | ||
| /// ``` | ||
| pub enum Direction { | ||
| Up, | ||
| } | ||
|
|
||
| /// ``` | ||
| /// let _ = 1; | ||
| /// ``` | ||
| pub union Raw { | ||
| bits: u32, | ||
| } |
There was a problem hiding this comment.
this seems like an false negative.
If it is, can you move it to an module at the end and mark it as such?
There was a problem hiding this comment.
It is intended to be a negative following the other reviews, since it was explicitly asked to ignore enum, struct, union and so on. I detailed the whole PR iter here https://github.com/rust-lang/rust-clippy/pull/17766/changes#r4114976281 and I am fine with trying to find a more general solution that includes these as well.
| /// Tagged `ignore` and `compile_fail` blocks are not examples. | ||
| /// ```text | ||
| /// text_block(); | ||
| /// ``` | ||
| /// ```ignore | ||
| /// text_block(); | ||
| /// ``` | ||
| /// ```compile_fail | ||
| /// let _: u8 = text_block; | ||
| /// ``` |
There was a problem hiding this comment.
can you explain this more? Why not?
There was a problem hiding this comment.
The idea is that textual entries that do not compile are not meaningful documentation of the object, as you could write let _: u8 = text_block; and have otherwise the clippy warning pass with a completely meaningless doctest. The whole idea of the lint is to force having doctests meaningful for the item being documented.
| return None; | ||
| } | ||
|
|
||
| if !matches!( |
There was a problem hiding this comment.
based on https://github.com/rust-lang/rust-clippy/pull/17766/changes#r4067004507 this seems an intentional bail to avoid FPs.
Please add an TODO comment that this is the case and what is nessary to support these cases roughly
There was a problem hiding this comment.
You mean if we were to re-add support for Traits, Structs and so on and so forth as they were included originally?
The issue there primarily stems from doctests of Traits/Structs often not explicitly stating the syn token of the documented item when *-importing objects, and therefore causing false positives. My initial reasoning for including them was that in diesel I also found duplicated tests over structs, so there are definitively use cases for them, and I would also claim that explicitly including X when you are documenting X is apriori desirable.
Short of having some utility that is able to efficiently determine what the * imports are, I do not believe false positives are avoidable in such cases, and in my opinion it is also trivial to silence them by explicitly importing what is being documented.
It was then pointed out that as things stand, such a wide net caught too many false positives, and so the PR then went through a preliminary transformation of turning the items to be included into a configurable settings.
Then, it was asked to just remove the setting and keep only the fn, const and static cases, bringing the PR to the current state.
I hope I have provided enough context, do let me know what would be the direction you would suggest to go forward with. If you are aware of other lints having any solution to an analogous problem that I could adopt here, do let me know, otherwise the TODO collapses into "skipping anything that is not a Fn, Const and Static as people tend to systematically fail to include those explicitly in their doctests"
| use rustc_lint::LateContext; | ||
| use rustc_span::Ident; | ||
|
|
||
| pub(super) fn item_ident(cx: &LateContext<'_>, check_private_items: bool) -> Option<Ident> { |
There was a problem hiding this comment.
instead of this function being if..return None if..return None, how about we make this into an bigger if-let, since if you specify the case where you want the ident it is simpler to reason about for readers due to not having to deal with double negations, ...
I in particularly stubled over if !check_private_items
There was a problem hiding this comment.
Ok, I expect the positive case if chain might get long but I saw elsewhere in clippy lints occasionally there are fairly large ones. Will rewrite shortly.
There was a problem hiding this comment.
Cleaned up, actually found a small duplication with another lint and cleaned both. Lmk if that is okay as part of this PR or whether it would be ideal to split it.
There was a problem hiding this comment.
there are now 3 different places where tests for this exist, please crate an new directory for just this testcase to make this more obvious where things are 😉
|
Thanks @CommanderStorm for taking time to review this. I did open the review request on Zulip (hopefully in the right place), although admittedly after opening the PR as I read the request, brain though "yes let's do that" and then promptly forgot it and opened the PR nevertheless: #llm-reviews > clippy: Add doc_examples_missing_item lint - #17766 I must admit that most of the points stresses in the first diff are my own fault, not the LLMs being sloppy. It is the first time I am contributing a lint to clippy, so I have no yet assimilated its styles, patterns and go-to solutions. Of the review, the two (related) things regarding which I am unsure how to proceed and I would appreciate some additional comments on are #17766 (comment) and #17766 (comment) |
8bd2c5a to
e13283b
Compare
4d8ef0a to
a6a1f61
Compare
View all comments
fixes #9228
This PR adds
doc_examples_missing_item, a pedantic lint that warns when an exported function, method, constant or static has Rust doctest examples but none of them mentions the item by name. Such examples are usually copied from a neighbouring item and never adapted, as in diesel-rs/diesel#5213 orOnceCell'sget_mut. I ran into this failure mode while working on dejadoc.The lint tokenizes the compiled examples, hidden lines included, and looks for the item's name as an identifier. Blocks tagged
ignoreorcompile_faildo not count as examples, whileno_runandshould_panicones do. Private and#[doc(hidden)]items are skipped unlesscheck-private-itemsis set, as for the other doc lints. Trait implementation items and proc macro entry points are excluded. Struct, enum, union and trait declarations are left out, because their examples often reach them through a constructor or a method chain without naming them, which made most of their lintcheck findings noise. Trait methods are still checked.Lintcheck reports 45 findings, all on functions, and dogfood over this repository is clean.
Disclosure under the LLM policy. I used Qwen 3.8 Next to draft, then adequately iterated and cleaned up the code.
.stderrfile)cargo testpasses locallycargo dev update_lintscargo dev fmtchangelog: new lint: [
doc_examples_missing_item]