Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn on empty lines after outer attributes #2340

Merged
merged 4 commits into from
Jan 30, 2018

Conversation

phansch
Copy link
Member

@phansch phansch commented Jan 8, 2018

This adds a new lint to warn on empty lines after outer attributes.

Still fairly new to Rust, so I'm not sure if the empty line detection could be shortened or if the tests cover all edge cases, but it seems to work so far 👍 .

Note that rustc already handles attributes with a missing item: libsyntax/parse/parser.rb#L3938

Closes #1165

@phansch phansch force-pushed the newline_after_attributes branch from 77997d8 to b6720d8 Compare January 8, 2018 23:36
@phansch
Copy link
Member Author

phansch commented Jan 9, 2018

Looks like by reusing the is_relevant_item method, it only works for functions currently. I will have another look once I'm back from vacation on the 18th. I plan to spend more time on rust-clippy, it has been really nice so far 👍

@oli-obk
Copy link
Contributor

oli-obk commented Jan 12, 2018

error: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
  --> tests/cc_seme.rs:1:1
   |
1  | / #![feature(plugin)]
2  | | #![plugin(clippy)]
3  | |
4  | | #[allow(dead_code)]
...  |
14 | |
15 | | fn main() {
   | |_
   |
   = note: `-D empty-line-after-outer-attr` implied by `-D clippy`
   = help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.179/index.html#empty_line_after_outer_attr

@clarfonthey
Copy link
Contributor

There should also be a test case for items with a comment between the attribute and item.

@phansch phansch changed the title Warn on empty lines after outer attributes WIP: Warn on empty lines after outer attributes Jan 18, 2018
@phansch phansch force-pushed the newline_after_attributes branch 2 times, most recently from 469eb49 to ec77e33 Compare January 23, 2018 20:33
@phansch
Copy link
Member Author

phansch commented Jan 23, 2018

The lint now works for all members of syntax::ast::Item_, I added test cases for enum, struct and mod. Should I add test cases for all the other Item_ kinds as well or would that be too much?

@phansch phansch changed the title WIP: Warn on empty lines after outer attributes Warn on empty lines after outer attributes Jan 23, 2018
@phansch
Copy link
Member Author

phansch commented Jan 23, 2018

I'm not really sure why cc_seme.rs produces the warning, I tried to reproduce it in a UI test, but there it seems to work. It also only seems to happen during compilation: when I run cargo test a second time, the warning is gone. Do you have some ideas how I could debug it? @oli-obk

edit: ok, nevermind I found out how to reproduce it locally:

cargo build --features debugging
cp target/debug/cargo-clippy ~/rust/cargo/bin/cargo-clippy
cp target/debug/clippy-driver ~/rust/cargo/bin/clippy-driver
~/rust/cargo/bin/cargo-clippy clippy --all -- -D clippy

@oli-obk
Copy link
Contributor

oli-obk commented Jan 24, 2018

Should I add test cases for all the other Item_ kinds as well or would that be too much?

Nah, the current tests are sufficient

Do you have some ideas how I could debug it?

That is very weird. Something must be converting the inner attribute into an outer attribute. You can try dumping the crate AST and look at that

@phansch
Copy link
Member Author

phansch commented Jan 24, 2018

Ok this seems like a weird problem.

I managed to dump the AST of cc_seme, but both attributes are inner attributes as expected.
Next I included the meta_item_list of the attribute in the error message and this is what I got:

warning: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute? 
Some([Spanned { node: MetaItem(MetaItem { name: dead_code, node: Word, span: tests/cc_seme.rs:1:1: 1:1 }), span: tests/cc_seme.rs:1:1: 1:1 }])

The attribute seems to be an allow(dead_code) with a span from 1 to 1. I managed to rule out feature(plugin) as a cause because it works if there is only feature(plugin) and plugin(clippy) in the file.

I did some more shots in the dark and now I'm sure that this is caused by the main method, in combination with feature(plugin) and plugin(clippy). For example this test will cause the warning to appear:

// ./test/weird_spans.rs
#![feature(plugin)]
#![plugin(clippy)]

fn main() {
    assert!(true);
}

So, somewhere an allow(dead_code) or deny(dead_code) with a span of 1:1 is inserted that does not seem to show up in the AST. That causes the warning. And this only happens if there is a main method. Naming the main method something else will not cause the warning.

I will try to dig deeper in the next days, but it looks like it will take some more time to get this merged :shipit:

@oli-obk
Copy link
Contributor

oli-obk commented Jan 24, 2018

Oh... just throw in a macro check for now. The attribute should probably have some expansion info.

@phansch phansch force-pushed the newline_after_attributes branch from ec77e33 to 642b321 Compare January 26, 2018 06:52
@phansch
Copy link
Member Author

phansch commented Jan 26, 2018

Unfortunately, I was unable to find any macro expansion info in the spans I'm using. The is_macro check didn't catch that attribute, either. So I added a check for spans that have both locations at 0. I'm not sure that's the best way to do it, but it seems to fix the warning.

@oli-obk
Copy link
Contributor

oli-obk commented Jan 26, 2018

What will happen if somebody makes a file like

#[allow(foo)]

fn main() {}

@phansch
Copy link
Member Author

phansch commented Jan 26, 2018

duh, you are right, that would not be detected then 👍

I added a debugging commit, because I'm running out of time for today and want to pick it up again on Sunday.
Travis should show all kinds of information about the attribute. One thing that stands out to me is attr.span.end_point(): tests/weird_hidden_attribute.rs:1:1: 9:4287916127, but I'm not sure how to use that information, yet. I will try to figure out what happens during compilation.

I also added a separate test file at tests/weird_hidden_attribute.rs and it can be executed separately with CARGO_INCREMENTAL=1 TESTNAME=weird_hidden_attribute cargo test

@phansch
Copy link
Member Author

phansch commented Jan 27, 2018

Looking at the output and the Attribute docs again, it says:

attr.span.end_point() tests/weird_hidden_attribute.rs:1:1: 9:4287916577
attr.span.next_point() tests/weird_hidden_attribute.rs:1:2: 1:2

So, maybe it makes sense to check if the attribute span end_point is after the next_point, which should not happen with normal attributes, I guess?

@phansch
Copy link
Member Author

phansch commented Jan 28, 2018

I will update this comment for the next few hours.

Starting with this piece of code:

#![feature(plugin)]
#![plugin(clippy)]

fn main() {
    "hmm"
}

I managed to get the expanded macro with rustc -Z unstable-options --pretty=expanded and this contains an #[allow(dead_code)] above the main function

#![feature(prelude_import)]
#![no_std]
#![feature(plugin)]
#![plugin(clippy)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;

#[allow(dead_code)]
fn main() { "hmm" }
<snip>

So, when iterating over the items, it picks up the allow for the main function, because it gets included for main somewhere. I added a allow(dead_code) into the original code and the expanded output now contains two allow(dead_code) for the main function.

#![feature(prelude_import)]
#![no_std]
#![feature(plugin)]
#![plugin(clippy)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;

#[allow(dead_code)]
#[allow(dead_code)]
fn main() { "hmm" }
<snip>

The most interesting part is, is that one of the allow(dead_code) attributes has a span of 1:1 and the other has the correct span.
I think with snippet_opt I should be able to work around this, as the span of attributes that have been inserted by expansion always seems to be 1:1.
Additionally, I can use snippet_opt with the 1:1 span and check what it contains.

@phansch phansch force-pushed the newline_after_attributes branch from 4c40d7c to b956c4d Compare January 28, 2018 16:40
@oli-obk
Copy link
Contributor

oli-obk commented Jan 29, 2018

Additionally, I can use snippet_opt with the 1:1 span and check what it contains.

sgtm

@phansch phansch force-pushed the newline_after_attributes branch from b956c4d to 7160ccb Compare January 29, 2018 07:45
If the snippet is empty, it's an attribute that was inserted during macro
expansion and we want to ignore those, because they could come from external
sources that the user has no control over.
For some reason these attributes don't have any expansion info on them, so
we have to check it this way until there is a better way.
@phansch phansch force-pushed the newline_after_attributes branch from 7160ccb to 3d54e56 Compare January 29, 2018 09:04
@phansch
Copy link
Member Author

phansch commented Jan 29, 2018

I added an is_empty check for the attribute spans 👍 . From what I can see that should be enough. I'm just not sure how I could add a proper test for that. I guess it could also be fine without a test as tests/cc_seme.rs will show a warning.

@oli-obk oli-obk merged commit 39d1d60 into rust-lang:master Jan 30, 2018
@oli-obk
Copy link
Contributor

oli-obk commented Jan 30, 2018

Great! Thanks

@phansch
Copy link
Member Author

phansch commented Jan 30, 2018

Thank you!

@phansch phansch deleted the newline_after_attributes branch January 30, 2018 23:29
@TheIronBorn
Copy link

Seeing what looks like a false positive on this:

/// A mapping from coordinates in the source sequence to coordinates in the sequence after
/// the delta is applied.

// TODO: this doesn't need the new strings, so it should either be based on a new structure
// like Delta but missing the strings, or perhaps the two subsets it's synthesized from.
pub struct Transformer<'a, N: NodeInfo + 'a> {
// warning: src/delta.rs:447: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make // it an inner attribute?
// note: src/delta.rs:447: #[warn(empty_line_after_outer_attr)] on by default
// help: src/delta.rs:447: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.184/index.html#empty_line_after_outer_attr
// warning: src/delta.rs:448: Found an empty line after an outer attribute. Perhaps you forgot to add a '!' to make it an inner attribute?
// help: src/delta.rs:448: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.184/index.html#empty_line_after_outer_attr

@clarfonthey
Copy link
Contributor

This should special case doc comments.

@phansch
Copy link
Member Author

phansch commented Jan 31, 2018

I will have a look 👍

@ozkriff
Copy link

ozkriff commented Feb 1, 2018

btw, example on https://rust-lang-nursery.github.io/rust-clippy/v0.0.184/index.html#empty_line_after_outer_attr is broken - there're no empty lines:

image

@phansch phansch added the T-macros Type: Issues with macros and macro expansion label Apr 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-macros Type: Issues with macros and macro expansion
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants