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

Projects
None yet
5 participants
@phansch
Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 9, 2018

Collaborator

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 👍

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Jan 12, 2018

Collaborator
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
Collaborator

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
@clarcharr

This comment has been minimized.

Show comment
Hide comment
@clarcharr

clarcharr Jan 14, 2018

Contributor

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

Contributor

clarcharr commented Jan 14, 2018

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

@phansch phansch changed the title from Warn on empty lines after outer attributes to WIP: Warn on empty lines after outer attributes Jan 18, 2018

@phansch

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 23, 2018

Collaborator

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?

Collaborator

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 from WIP: Warn on empty lines after outer attributes to Warn on empty lines after outer attributes Jan 23, 2018

@phansch

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 23, 2018

Collaborator

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
Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Jan 24, 2018

Collaborator

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

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 24, 2018

Collaborator

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:

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Jan 24, 2018

Collaborator

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

Collaborator

oli-obk commented Jan 24, 2018

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

@phansch

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 26, 2018

Collaborator

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.

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Jan 26, 2018

Collaborator

What will happen if somebody makes a file like

#[allow(foo)]

fn main() {}
Collaborator

oli-obk commented Jan 26, 2018

What will happen if somebody makes a file like

#[allow(foo)]

fn main() {}
@phansch

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 26, 2018

Collaborator

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

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 27, 2018

Collaborator

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?

Collaborator

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

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 28, 2018

Collaborator

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.

Collaborator

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.

@oli-obk

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Jan 29, 2018

Collaborator

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

sgtm

Collaborator

oli-obk commented Jan 29, 2018

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

sgtm

phansch added some commits Jan 8, 2018

Add workaround for hidden outer attribute
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

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 29, 2018

Collaborator

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.

Collaborator

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-nursery:master Jan 30, 2018

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@oli-obk

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Jan 30, 2018

Collaborator

Great! Thanks

Collaborator

oli-obk commented Jan 30, 2018

Great! Thanks

@phansch

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 30, 2018

Collaborator

Thank you!

Collaborator

phansch commented Jan 30, 2018

Thank you!

@phansch phansch deleted the phansch:newline_after_attributes branch Jan 30, 2018

@TheIronBorn

This comment has been minimized.

Show comment
Hide comment
@TheIronBorn

TheIronBorn Jan 31, 2018

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

TheIronBorn commented Jan 31, 2018

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
@clarcharr

This comment has been minimized.

Show comment
Hide comment
@clarcharr

clarcharr Jan 31, 2018

Contributor

This should special case doc comments.

Contributor

clarcharr commented Jan 31, 2018

This should special case doc comments.

@phansch

This comment has been minimized.

Show comment
Hide comment
@phansch

phansch Jan 31, 2018

Collaborator

I will have a look 👍

Collaborator

phansch commented Jan 31, 2018

I will have a look 👍

@ozkriff

This comment has been minimized.

Show comment
Hide comment

ozkriff commented Feb 1, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment