-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
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
Port audit_desc audit rules to a cop #2242
Conversation
dbfd76f
to
19f693d
Compare
Instead we want Custom Cops to be run only on formulae files. Can I modify Edit: This may not be a good idea, if we plan on adding custom cops in the future for not just brew |
and skips it if it isn't. |
@reitermarkus I too added a check to see if its a Formula But the problem is there are formulae defined outside |
Ok, so there is a |
This looks like it could do what we want: https://github.com/bbatsov/rubocop/blob/9754e06f468631e9145b72f30e23e4cc0883bdb8/lib/rubocop/cop/style/file_name.rb#L20-L27 |
Yes, just found that too. Thanks! :) |
formula_name = class_node.const_name | ||
return unless parent_class_node && parent_class_node.const_name == "Formula" && body | ||
check(node, body, formula_name) | ||
end |
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.
Can you move this to a helper method/module? We'll probably need this for every Formula cop.
3e1fbd8
to
fa79ca9
Compare
module RuboCop | ||
module Cop | ||
# Helper module for checking formula class | ||
module FormulaDef |
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.
To me it makes more sense for there to be a HomebrewCop
base class that other cops inherit from as all will want to use this code.
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.
Yeah, this makes more sense, but I was not able to inherit HomebrewCop
due to some error. Found a fix. Will push the changes.
# Helper module for checking formula class | ||
module FormulaDef | ||
def file_inside_formula_root? | ||
return false unless processed_source.path =~ %r{#{ENV["HOMEBREW_LIBRARY"]}\/Taps\/homebrew} |
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.
Don't think HOMEBREW_LIBRARY
will be set when using rubocop
outside of brew style
?
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.
Should I use %r{\/Taps\/homebrew}
without HOMEBREW_LIBRARY
?
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.
Yeh, that sounds like a sensible default. Don't need \/
inside a %r
, though!
length = call_node.children[2].source_range.size | ||
sourcerange = source_range(source_buffer, line_number, column, length) | ||
message = <<-EOS.strip_indent | ||
Description is too long. \"name: desc\" should be less than 80 characters. |
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.
Indent this two characters after message
and use EOS.undent
. Are you sure you need to escape \"
?
def check(node, body, formula_name) | ||
body.each_child_node(:send) do |call_node| | ||
_receiver, call_name, args = *call_node | ||
next unless call_name == :desc && !args.children[0].empty? |
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.
unless &&
is generally hard to parse; can you flip this to an if
?
add_offense(call_node, sourcerange, message) | ||
end | ||
|
||
match_object = description.match(/([Cc]ommand ?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.
Could make this regex case-insensitive?
02bbad4
to
93f719a
Compare
@@ -0,0 +1,19 @@ | |||
module RuboCop | |||
module Cop | |||
class HomebrewCop < Cop |
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'd call this FormulaCop
. Also, use snake_case
for the filename.
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.
Thanks.
cae3856
to
5687619
Compare
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.
Lots of comments here. I'm being really pedantic just because I want to try and get these super easy to contribute more but 👍 on all your work here.
end | ||
|
||
private | ||
|
||
def check(body) |
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.
Not totally clear to me what body
is here; could you explain so we can maybe pick a better name?
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.
body
is the RuboCop::AST::Node
instance of everything that's inside the formula's class.
The child nodes of body
are also instances of RuboCop::AST::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.
How about formula_class_body_node
?
end | ||
|
||
private | ||
|
||
def check(body) | ||
body.each_child_node(:block) do |block_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.
Not totally clear to me what block_node
is here; could you explain so we can maybe pick a better name?
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.
body.each_child_node(:block)
calls the given block for each child
node which has child.type==:block
. (We want to iterate only over blocks and find bottle
block).
The given block for body.each_child_node(:block)
takes block_node
as the argument.
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.
How about possible_bottle_block_node
or similar? Feel free to disagree, that's clearer now anyway.
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.
Could also potentially extract to a method that gets block nodes given the method name?
|
||
def file_inside_formula_root? | ||
return false unless processed_source.path =~ %r{/Taps/homebrew} | ||
true |
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.
!(processed_source.path =~ %r{/Taps/homebrew}).nil?
maybe?
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.
Yes, This is more elegant 👍
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 will exclude Formulae in all Taps other than homebrew/*
.
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.
Actually, this may not be necessary. We can use Include
and Exclude
options for individual cops in .rubocops.yml
to pass file/directory patterns to decide whether cops will run or not. I will update the PR.
@reitermarkus Where else are the Formulae located?
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.
Where else are the Formulae located?
In personal/custom Taps, and I think you can also pass Ruby files directly to brew style
.
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.
Yeah you're right. Thanks. I'll think of some way to handle this.
true | ||
end | ||
|
||
def on_class(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.
Not sure what node
is here; could you explain so we can figure out a better name?
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.
RuboCop processes the source code and creates an Abstract Syntax Tree(AST) representation, and traverses the AST composed of node
s.
As it encounters a node
of some type
, a message is sent to cop instance class hierarchy by running send(:"on_#{node.type}", node)
and by defining on_class
we can process the node and check for violations.
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.
Ok, makes sense as-is thanks.
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.
Maybe worth a comment stating that on_class
is called by RuboCop and is the main entry point.
end | ||
|
||
def on_class(node) | ||
return unless file_inside_formula_root? |
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 wonder if this could be done in a shared initializer?
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.
Can you please explain what's a shared initializer and why would we want to use it?
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 mean a def initialize
method that's shared between all FormulaCop
s but I change my mind on that now so: ignore me.
# - Checks if `desc` contains the formula name | ||
|
||
class FormulaDesc < FormulaCop | ||
def on_formula(node, class_node, _parent_class_node, body) |
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.
Not super clear what on_formula
means or the various node
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.
Inside FormulaCop
there's a method on_class
which does the following sequentially
- Checks if the file being processed is inside Taps/Homebrew/
- If so, check if current class is being inherited from
Formula
( Thus making sure that its a Homebrew Formula) - Call
on_formula
with arguments below. (on_formula
is not yet defined as we will define it inside the CustomCop which inherits fromFormulaCop
) node
is the RuboCop::AST::node instance of whole formulaclass_node
is the RuboCop::AST::node instance of formula's name_parent_class_node
is the RuboCop::AST::node instance ofFormula
body
is the RubCop::AST::node for everything thats inside the Formula
class_node
, _parent_class_node
, body
are children of node
and may be we can omit passing node
as the argument to on_formula
as its redundant.
The inheritance and on_formula
definition is so that, inside custom cop, we are only concerned with writing rules to check Homebrew Formula coding style violations, while the boilerplate code resides inside FormulaCop
.
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'm wondering if it's worth being called audit_formula
; the on_
naming scheme is better if people are familiar with RuboCop but I'd think the majority won't be.
|
||
private | ||
|
||
def check(node, body, formula_name) |
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.
Would be good to figure out a way to extract more of the Rubocop/Ruby parse tree logic here from the audit logic.
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.
Perhaps by generally splitting this into a bunch of smaller methods.
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.
While trying to split them into smaller methods, line_number
, line_begin_pos
, desc_begin_pos
, call_node
are required as arguments in every method. Can I make them instance variables?
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.
Can you push the changes? Otherwise it's hard to tell if instance variables make sense.
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.
Agreed; go for it.
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've made an additional commit and also some style checks won't be passing. I will squash the commits later once code gets reviewed.
column = desc_begin_pos+match_object.begin(0)-line_begin_pos+1 | ||
length = match_object.to_s.length | ||
sourcerange = source_range(source_buffer, line_number, column, length) | ||
add_offense(call_node, sourcerange, "Description should use \"command-line\" instead of \"#{match_object}\"") |
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.
A linebreak here would be good to make this a little shorter
end | ||
|
||
private | ||
|
||
def check(body) |
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.
How about formula_class_body_node
?
end | ||
|
||
private | ||
|
||
def check(body) | ||
body.each_child_node(:block) do |block_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.
How about possible_bottle_block_node
or similar? Feel free to disagree, that's clearer now anyway.
def check(body) | ||
body.each_child_node(:block) do |block_node| | ||
next if block_length(block_node).zero? | ||
method, _args, block_body = *block_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.
I reckon just using _
instead of _args
for unused arguments might be easier to follow
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.
If there are multiple such unused variables in the same block, instead of prefixing _
for all of them, using just _
, would it be okay?
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.
Yeh, I think using _
instead of all of them would be good 👍
end | ||
|
||
private | ||
|
||
def check(body) | ||
body.each_child_node(:block) do |block_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.
Could also potentially extract to a method that gets block nodes given the method name?
true | ||
end | ||
|
||
def on_class(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.
Ok, makes sense as-is thanks.
end | ||
|
||
def on_class(node) | ||
return unless file_inside_formula_root? |
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 mean a def initialize
method that's shared between all FormulaCop
s but I change my mind on that now so: ignore me.
def on_class(node) | ||
return unless file_inside_formula_root? | ||
class_node, parent_class_node, body = *node | ||
return unless parent_class_node && parent_class_node.const_name == "Formula" |
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.
Use if
as unless &&
is hard to follow.
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 line could also perhaps be split into a dedicated method.
true | ||
end | ||
|
||
def on_class(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.
Maybe worth a comment stating that on_class
is called by RuboCop and is the main entry point.
# - Checks if `desc` contains the formula name | ||
|
||
class FormulaDesc < FormulaCop | ||
def on_formula(node, class_node, _parent_class_node, body) |
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'm wondering if it's worth being called audit_formula
; the on_
naming scheme is better if people are familiar with RuboCop but I'd think the majority won't be.
|
||
private | ||
|
||
def check(node, body, formula_name) |
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.
Perhaps by generally splitting this into a bunch of smaller methods.
72b0b0c
to
d54504b
Compare
Library/.rubocop.yml
Outdated
@@ -8,7 +8,17 @@ AllCops: | |||
|
|||
require: ./Homebrew/rubocops.rb | |||
|
|||
Homebrew/FormulaCop: |
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.
Maybe worth putting this in another directory so it doesn't need to be disabled like this?
Library/.rubocop.yml
Outdated
Homebrew/CorrectBottleBlock: | ||
Include: | ||
- '**/Taps/homebrew/**/*.rb' |
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.
If this will need to be repeated for every cop I think the existing approach was superior.
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.
Also, as I said before, this will exclude every Formula in personal/custom Taps and Ruby files that are passed directly to brew style
.
We need to exclude HOMEBREW_LIBRARY_PATH
rather than include Taps/**
.
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 think just using '**/Taps/**/*.rb'
may be sufficient? We don't want these applying to non-HOMEBREW_LIBRARY_PATH
Ruby files.
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.
They won't apply to all Ruby files, they will only match files with class ***** < Formula
anyways.
@line_number = call_node.loc.line | ||
@line_begin_pos = call_node.source_range.source_buffer.line_range(call_node.loc.line).begin_pos | ||
@desc_begin_pos = call_node.children[2].source_range.begin_pos | ||
@call_node = call_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.
I'm thinking some of this could be extracted into a method named find_method_by_name(:desc)
which then returns the node or perhaps just the parameters? That's the type of thing I'm thinking with splitting this into smaller methods; have generic functions like find_method_by_name
and later when needed find_block_by_name
etc. which could take method name
and optional block_name
arguments and return e.g. the parameters or information as a hash. This should all live in FormulaCop
so that it's not necessary to really understand RuboCop to implement a FormulaCop
subclass and you can just call these documented, public methods. What do you think?
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.
Yes, this makes sense. Wrapping rubocop specific code into documented methods would make it easier for other maintainers to add custom cops. But, I think, (as of now) its not possible to make writing custom cops to be completely rubocop agnostic as every possible requirement cannot be known beforehand, although we can try to cover most of the common cases,
And I think closures would be a good fit in this use case. I am pushing the code, please let me know further comments.
I apologize for the delay, as I was on a vacation.
# This method is called by RuboCop and is the main entry point | ||
class_node, parent_class_node, body = *node | ||
return unless a_formula_class?(parent_class_node) | ||
audit_formula(node, class_node, parent_class_node, body) |
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.
Add a return unless respond_to?(:audit_formula)
before this, so you can remove Homebrew/FormulaCop
from the RuboCop config.
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.
Thanks! Added it.
module Cop | ||
module Homebrew | ||
class FormulaCop < Cop | ||
@registry = Cop.registry |
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.
What is this used for?
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.
Ah! That's a hack. I haven't found a solution for it yet. Without this line, there's an error undefined method `enlist' for nil:NilClass
being thrown. After further debugging, found that @registry = Cop.registry
line inside Cop
is not getting executed when FormulaCop
is inheriting from Cop
offense_source_range = source_range(source_buffer, line_number, column, length) | ||
desc_length_offense_msg = <<-EOS.undent | ||
Description is too long. "name: desc" should be less than #{max_desc_length} characters. | ||
Length is calculated as #{formula_name} + desc. (currently #{"#{formula_name}: #{description}".length}) |
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.
Indent these lines by 2 spaces.
4b1b8ab
to
7e43bb9
Compare
Library/.rubocop.yml
Outdated
Homebrew/CorrectBottleBlock: | ||
Include: | ||
- '**/Taps/homebrew/**/*.rb' |
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.
Let's remove these includes and leave the logic in the cops themselves unless there's a way to do this that avoids duplicating this line for every cop.
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.
As @reitermarkus pointed out, due to Include
in the .rubocop.yml
the cops won't be run on personal/custom Taps and the files directly passed as arguments which do not match the pattern under Include
.
To resolve this issue I need some clarification. brew style
command, runs on all the files inside HOMEBREW_LIBRARY_PATH
while brew audit
runs only on the forumlae files.
Do we want(or is it okay for) the custom cops to get executed under brew audit
and brew style
also?
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.
As @reitermarkus pointed out, due to Include in the .rubocop.yml the cops won't be run on personal/custom Taps and the files directly passed as arguments which do not match the pattern under Include.
I think it's OK for the taps to run on unnecessary files as they are still a pretty quick no-op.
Do we want(or is it okay for) the custom cops to get executed under brew audit and brew style also?
Yeh, they should be executed by all of them I think (including rubocop
obviously, just to be explicit)
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.
There are classes in files
compat/formula_specialties.rb
test/exceptions_spec.rb
test/support/fixtures/testball.rb
test/support/fixtures/testball_bottle.rb
inheriting from Formula
and are returning cop violations as they don't have formula desc
, causing the build to fail. If desc
can be added in the above files, then we don't have to repeat Include
in .rubocop.yml
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.
If desc can be added in the above files, then we don't have to repeat Include in .rubocop.yml
Why not just check if the file path contains /Library/Homebrew/
and exclude those? Didn't we have this approach already before?
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.
Well, I was trying to keep the cop code and config code separate in their respective files. But turns out, keeping the Include
logic in FormulaCop
is more elegant. Thanks @reitermarkus and @MikeMcQuaid, now Personal Taps can be passed as arguments.
I added the logic in Formula Cop and pushed the new code. I also used rebase to add another test in bottle_block_cop_spec in a past commit to pass coverage test.
audit_formula(node, class_node, parent_class_node, body) | ||
end | ||
|
||
def find_method(node, method_name) |
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.
def find_node_method_by_name(node, method_name)
maybe?
return unless method_name == :bottle | ||
check_revision?(body) | ||
def audit_formula(_node, _class_node, _parent_class_node, formula_class_body_node) | ||
check(formula_class_body_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.
Ideally this method would contain code like the following:
def audit_formula
bottle = find_block(:bottle)
return unless bottle
if method_called?(bottle, :revision)
problem "Use rebuild instead of revision in a bottle block"
end
end
The find_block
and method_called?
methods should be defined in formula_cop.rb
and the various instance variables set by on_class
or something it calls.
end | ||
end | ||
|
||
def node_line_begin_pos(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.
def line_start_column(node)
maybe?
node.source_range.source_buffer.line_range(node.loc.line).begin_pos | ||
end | ||
|
||
def node_begin_pos(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.
def start_column(node)
maybe?
node.const_name | ||
end | ||
|
||
def node_size(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.
def size(node)
maybe?
node.source_range.size | ||
end | ||
|
||
def get_source_buffer(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.
source_buffer(node)
maybe?
node.source_range.source_buffer | ||
end | ||
|
||
def get_str_content(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.
string_content(node)
maybe?
|
||
private | ||
|
||
def a_formula_class?(parent_class_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.
is_formula_class?(node)
maybe?
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.
Without the is_
, I'd say.
# - Checks for correct usage of `command-line` in `desc` | ||
# - Checks if `desc` contains the formula name | ||
class FormulaDesc < FormulaCop | ||
def audit_formula(node, class_node, _parent_class_node, body) |
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.
Ideally this method would contain code like the following:
def audit_formula
desc_call = find_method_call(:desc)
return unless desc_call
desc = parameters(desc_call).first
max_length = 80
if desc.length > max_length
problem <<-EOS.undent
Description is too long. "name: desc" should be less than #{max_length} characters.
Length is calculated as #{formula_name} + desc. (currently #{"#{formula_name}: #{desc}".length})
EOS
if desc =~ /(command ?line)/i
problem "Description should use \"command-line\" instead of \"#{$1}\""
end
...
end
The find_method_call
and parameters?
methods should be defined in formula_cop.rb
and the various instance variables set by on_class
or something it calls and by e.g. find_method_call
and parameters
so problem
can access e.g. the source_range
.
Obviously this can (and will) vary but just giving you a heads up for the level of complexity I think would be ideal for these cops to hide the implementation details.
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.
Thanks for the above sample code. 👍 Now its clear which way to implement.
desc_call = find_method_call(:desc)
we are assuming that there would be only one method call. There might be multiple calls to the same method. Should it return an array instead?
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 think having find_method_call
which always returns the first
would be a sensible first step. If it's needed later we can have find_method_calls
for e.g. checking use of things like inreplace
and then loop over them.
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.
While trying to implement the above I faced an issue. Just by having
if desc =~ /(command ?line)/i
problem "Description should use \"command-line\" instead of \"#{$1}\""
end
its difficult to set all the instance variables required for pinpointing the source_range
of the style violation. So I added a method check_regex_match
. Now the code looks like
match_object = check_regex_match(desc, /(command ?line)/i)
if match_object
problem "Description should use \"command-line\" instead of \"#{match_object}\""
end
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.
How about:
if match = regex_match_group(desc, /(command ?line)/i)
problem "Description should use \"command-line\" instead of \"#{match_object}\""
end
01296f7
to
2feded9
Compare
1e657e3
to
9c93a2a
Compare
9c93a2a
to
a693ca3
Compare
This is looking pretty good to merge for me now. @reitermarkus, any thoughts? |
Great work again @GauthamGoli! My suggestion for the next rules would be https://github.com/Homebrew/brew/blob/master/Library/Homebrew/dev-cmd/audit.rb#L251-L309; these are the ones I personally tend to wish to have in my editor the most. |
brew tests
with your changes locally?This is in continuation of the PR #1873 and issue #569
This PR ports
audit_desc
audit rules into a rubocop custom cop. The rules aredesc
exists in a Formuladesc
size is > 80desc
begins with an articlecommand-line
indesc
desc
contains the formula nameThe previous tests for the ported audit rules in
audit_spec.rb
have been removed.I am working on adding tests for the Custom Cops. I have the basic code, but I'm currently trying to figure out where to place the code for the tests to get executed.New tests for the RuboCop custom cops have been added and
Gemfile
has been updated.