-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Add Style/LineBreak Cops #2277
Add Style/LineBreak Cops #2277
Conversation
class MultilineList < Cop | ||
MSG = 'Move all elements below the first line of a multi-line list.' | ||
|
||
def on_def(node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to handle defs
nodes too, and you can do this by using the OnMethodDef
mixin.
6f099cb
to
6314d47
Compare
jonas054 Thank you very much for the review. I've addressed all of your comments and amended my commit. The autocorrect method is also a lot simpler because I am using the first element as the offense (instead of the parent node) which means inserting a newline is much simpler. PTAL |
@@ -525,6 +525,13 @@ Style/MethodName: | |||
- snake_case | |||
- camelCase | |||
|
|||
Style/MultilineList: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cop is enabled by default but each part is disabled by default. I think it would be better to do it the other way around, because then people can find it when they look in disabled.yml
for extra things to turn on.
That's much better. I've added some more comments, mostly because I didn't get around to do it earlier. |
6314d47
to
762e5f4
Compare
@jonas054 I've addressed your comments. Thanks again for the review. I'm learning a lot about Ruby code parsing :) PTAL |
@jonas054 I am taking a look at some issues w/ the array assignment part. After making your changes I am getting some infinite loops for some implicit arrays in setters. I am investigating to see if I can write a simple test case and resolve the issue. |
762e5f4
to
778707d
Compare
|
||
def assignment_on_same_line?(node) | ||
source = node.loc.expression.source_line[0...node.loc.column] | ||
source =~ /\s*\=\s*$/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonas054 I was unable to convert this using the lhs, rhs
code you suggested because it does not work for some "assignment" types. It does work for masgn
but not for a send
type. For example:
config.values =
:a,
:b,
:c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Well, both assignment_on_same_line?
and method_uses_parens?
handle more than one node type, and then you have to do it with regexp matching or add handling of the different cases. I guess it's OK as it is.
@jonas054 I resolved the issue w/ implicit arrays and added a test case to demonstrate the issue. PTAL |
return if children.size < 2 | ||
|
||
line = start.loc.line | ||
min = children.min_by { |n| n.loc.line } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonas054 I have a question related to this code. Is there a standard way you check for "does this node span multiple lines"? For example, the following test case won't pass this implementation:
something('foo', bar: 1,
baz: 2)
The reason is that even though there are two children, they both start on the same line. While I could make some kind of check to see if any of the individual children have a "\n" in their source, it seems like there might be a better way to address this issue. Do you know if there is some way I can go about check this so that even these cases are tagged as offenses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that the node has a begin
and an end
, which I think it will in this case, you can use node.loc.begin.line
and node.loc.end.line
. The same will hold true for other attributes off of loc
.
778707d
to
65d66c9
Compare
@rrosenblum I've incorporated your comments. I resolved the issue with elements spanning multiple lines by using the |
check_children(node, node.children) | ||
elsif method_uses_parens?(node.parent, node) | ||
check_children(node, node.children, node.parent) | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else return
isn't really needed.
65d66c9
to
84a4788
Compare
Incorporated comments. Removed the |
👍 Looks good! |
ping @jonas054 @rrosenblum I rebased off master again. Is there anything else I need to do here to get this change merged to master? |
@panthomakos the build is failing for an incorrect format in the CHANGELOG. There is a missing colon after the link to the PR. Should be:
|
3750050
to
715d285
Compare
@rrosenblum fixed the CHANGELOG formatting |
it 'autocorrects the offense' do | ||
new_source = autocorrect_source(cop, source) | ||
|
||
expect(new_source).to eq( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like the correct auto-correction to me. The example given in the documentation comment shows
# good with EnableMethodParams
def method(
foo, bar,
baz)
do_something
end
The auto-correct places the first element left justified with the def
.
def foo(
bar,
baz)
do_something
end
Should it be indented or is it relying on another cop to handle that indention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It relies on Style/FirstParameterIndetation
:
http://www.rubydoc.info/gems/rubocop/0.29.1/RuboCop/Cop/Style/FirstParameterIndentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any additional alignment should then be fixed by Style/AlignParameters
:
http://www.rubydoc.info/gems/rubocop/0.29.1/RuboCop/Cop/Style/AlignParameters
I haven't had time to review the code, but I can tell you that I definitely don't like the name of the cop, as it's not clear what it refers to. Certainly, the term "list" is not used often in Ruby. |
@bbatsov I suppose the other option here is to have 4 separate cops that indicate that a line break is necessary, something like: |
Probably this should be a few separate cops. @jonas054 Thoughts? |
I hadn't thought about that while reviewing, but sure, why not? It's more simple in a way to have four separate cops than one cop with four enable properties. And the naming should be more natural. |
715d285
to
8b68270
Compare
8b68270
to
6974e6f
Compare
👍 Looks good to me. |
Looks good. Just remove the Btw, there should probably a set of complementary checks for the final parenthesis, so you can enforce either this style:
or this one (which is my preference):
|
* `Style/FirstArrayElementLineBreak` checks for a line break before the first element in a multi-line array. * `Style/FirstHashElementLineBreak` checks for a line break before the first element in multi-line hash. * `Style/FirstMethodArgumentLineBreak` checks for a line break before the first argument in a multi-line method call. * `Style/FirstMethodParameterLineBreak` checks for a line break before the first parameter in a multi-line method parameter definition. When large multi-line lists are present in a code base it's often rather arbitrary to align them based on the first element: ``` long_variable = { foo: 'foo', bar: 'bar', baz: 'baz' } ``` In this example if the variable name is refactored the entire list formatting also needs to change. This makes diffs, in particular, very hard to read due to the large number of formatting-only changes: ``` short = { foo: 'foo', bar: 'bar', baz: 'baz' } ``` These alignments also greatly reduce the available space on a line for each element, especially if there is a maximum line length enforcement. This collection of cops along with any appropriate Align or Indent cop (for example `HashAlign` and `HashIndent`) allows you to enforce a more diff-friendly and short-line style: ``` variable = { foo: 'foo', bar: 'bar', baz: 'baz' } ``` Care has been taken to ensure that DSL-like method calls are ignored and do not result in invalid Ruby. I have also added tests for all use-cases I could find in some large Ruby and Rails code-bases.
6974e6f
to
e672339
Compare
@bbatsov Removed period from commit message. I prefer the second as well. I was thinking about adding that set of cops after this PR was complete. |
Looking forward to those. :-) |
This cop is inspired by a comment in the `Style/LineBreaks` cop PR: rubocop#2277 (comment) This cop checks that the closing brace in an array literal is symmetrical with respect to the opening brace and the array elements. I am very open to suggestions on naming and ways to clarify the offense messages. # bad [ :a, :b ] # bad [ :a, :b ] # good [ :a, :b ] #good [ :a, :b ] My intention is to expand this to account for hashes, method parameters and method arguments once the language and approach is validated.
When large multi-line lists are present in a code base it's often
rather arbitrary to align them based on the first line/element:
In this example if the variable name is refactored the entire list
formatting also needs to change. This makes diffs, in particular, very
hard to read due to the large number of formatting-only changes:
These alignments also greatly reduce the available space on a line for
each element, especially if there is a maximum line length enforcement.
The
Style/MultilineList
cop along with any appropriate Align or Indentcop (for example
HashAlign
andHashIndent
) allows you to enforce amore diff-friendly and short-line style:
Mutually-exclusive configuration options are provided for each of method
parameters, method arguments, array elements and hash elements. Care has
been taken to ensure that DSL-like method calls are ignored and do not
result in invalid Ruby.
I have added tests for all use-cases I could find in some large Ruby and
Rails code-bases.