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

dev-cmd/audit: add TODOs for RuboCop migrations. #7371

Merged
merged 3 commits into from Apr 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 22 additions & 25 deletions Library/Homebrew/dev-cmd/audit.rb
Expand Up @@ -240,39 +240,17 @@ def audit_style
end

def audit_file
actual_mode = formula.path.stat.mode
# Check that the file is world-readable.
if actual_mode & 0444 != 0444
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
actual: actual_mode & 0777,
wanted: "+r",
path: formula.path)
end
# Check that the file is user-writeable.
if actual_mode & 0200 != 0200
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
actual: actual_mode & 0777,
wanted: "u+w",
path: formula.path)
end
# Check that the file is *not* other-writeable.
if actual_mode & 0002 == 002
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
actual: actual_mode & 0777,
wanted: "o-w",
path: formula.path)
end

# TODO: check could be in RuboCop
problem "'DATA' was found, but no '__END__'" if text.data? && !text.end?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to give the those checks a try but I'm not sure how one would access the necessary text contents in Rubocop. Can you give me hint please @MikeMcQuaid ?
And would it belong in patches.rb or files.rb?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say probably patches.rb. If you check out the definitions in

def data?
/^[^#]*\bDATA\b/ =~ @text
end
def end?
/^__END__$/ =~ @text
end
that may help. I'm not 100% how to get the source of the entire file in rubocop; there may also be a way to only get the __END__ section from the file from rubocop (or parser.rb that it uses).

Copy link
Contributor

@mathaeus mathaeus May 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my limited knowledge about the codebase and tools I haven't figured it out for now.
I don't see a straight forward solution to read the __END__ section from the things available in audit_formula in RuboCop, i.e. the body only contains all the stuff that makes up the class, but not the stuff after it (i.e. __END__)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out: https://www.rubydoc.info/gems/rubocop/0.46.0/RuboCop/ProcessedSource#lines-instance_method
https://www.honeybadger.io/blog/data-and-end-in-ruby/
rubocop/rubocop#1541

As one of them may help. It looks like you may need to look for usage of patch :DATA, get the filename and then do a full read of the file looking for ^__END__$. Make sense?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Late follow-up :)
I'm fully back at work and didn't have a time to look at this again so far and probably won't have in the near future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, someone else can pick it up!


# TODO: check could be in RuboCop
problem "'__END__' was found, but 'DATA' is not used" if text.end? && !text.data?

# TODO: check could be in RuboCop
if text.to_s.match?(/inreplace [^\n]* do [^\n]*\n[^\n]*\.gsub![^\n]*\n\ *end/m)
problem "'inreplace ... do' was used for a single substitution (use the non-block form instead)."
end

problem "File should end with a newline" unless text.trailing_newline?

if formula.core_formula? && @versioned_formula
unversioned_formula = begin
# build this ourselves as we want e.g. homebrew/core to be present
Expand Down Expand Up @@ -479,13 +457,15 @@ def audit_keg_only
first_word = reason.split.first

if reason =~ /\A[A-Z]/ && !reason.start_with?(*whitelist)
# TODO: check could be in RuboCop
problem <<~EOS
'#{first_word}' from the keg_only reason should be '#{first_word.downcase}'.
EOS
end

return unless reason.end_with?(".")

# TODO: check could be in RuboCop
problem "keg_only reason should not end with a period."
end

Expand Down Expand Up @@ -934,58 +914,70 @@ def audit_lines
def line_problems(line, _lineno)
# Check for string interpolation of single values.
if line =~ /(system|inreplace|gsub!|change_make_var!).*[ ,]"#\{([\w.]+)\}"/
# TODO: check could be in RuboCop
problem "Don't need to interpolate \"#{Regexp.last_match(2)}\" with #{Regexp.last_match(1)}"
end

# Check for string concatenation; prefer interpolation
if line =~ /(#\{\w+\s*\+\s*['"][^}]+\})/
# TODO: check could be in RuboCop
problem "Try not to concatenate paths in string interpolation:\n #{Regexp.last_match(1)}"
end

# Prefer formula path shortcuts in Pathname+
if line =~ %r{\(\s*(prefix\s*\+\s*(['"])(bin|include|libexec|lib|sbin|share|Frameworks)[/'"])}
# TODO: check could be in RuboCop
problem(
"\"(#{Regexp.last_match(1)}...#{Regexp.last_match(2)})\" should" \
" be \"(#{Regexp.last_match(3).downcase}+...)\"",
)
end

# TODO: check could be in RuboCop
problem "Use separate make calls" if line.include?("make && make")

if line =~ /JAVA_HOME/i &&
[formula.name, *formula.deps.map(&:name)].none? { |name| name.match?(/^openjdk(@|$)/) } &&
formula.requirements.none? { |req| req.is_a?(JavaRequirement) }
# TODO: check could be in RuboCop
problem "Use `depends_on :java` to set JAVA_HOME"
end

return unless @strict

# TODO: check could be in RuboCop
problem "`env :userpaths` in formulae is deprecated" if line.include?("env :userpaths")

if line =~ /system ((["'])[^"' ]*(?:\s[^"' ]*)+\2)/
bad_system = Regexp.last_match(1)
unless %w[| < > & ; *].any? { |c| bad_system.include? c }
good_system = bad_system.gsub(" ", "\", \"")
# TODO: check could be in RuboCop
problem "Use `system #{good_system}` instead of `system #{bad_system}` "
end
end

# TODO: check could be in RuboCop
problem "`#{Regexp.last_match(1)}` is now unnecessary" if line =~ /(require ["']formula["'])/

if line.match?(%r{#\{share\}/#{Regexp.escape(formula.name)}[/'"]})
# TODO: check could be in RuboCop
problem "Use \#{pkgshare} instead of \#{share}/#{formula.name}"
end

if !@core_tap && line =~ /depends_on .+ if build\.with(out)?\?\(?["']\w+["']\)?/
# TODO: check could be in RuboCop
problem "`Use :optional` or `:recommended` instead of `#{Regexp.last_match(0)}`"
end

if line =~ %r{share(\s*[/+]\s*)(['"])#{Regexp.escape(formula.name)}(?:\2|/)}
# TODO: check could be in RuboCop
problem "Use pkgshare instead of (share#{Regexp.last_match(1)}\"#{formula.name}\")"
end

return unless @core_tap

# TODO: check could be in RuboCop
problem "`env :std` in homebrew/core formulae is deprecated" if line.include?("env :std")
end

Expand Down Expand Up @@ -1085,6 +1077,7 @@ def audit_version
if version.nil?
problem "missing version"
elsif version.blank?
# TODO: check could be in RuboCop
problem "version is set to an empty string"
elsif !version.detected_from_url?
version_text = version
Expand All @@ -1094,21 +1087,25 @@ def audit_version
end
end

# TODO: check could be in RuboCop
problem "version #{version} should not have a leading 'v'" if version.to_s.start_with?("v")

return unless version.to_s.match?(/_\d+$/)

# TODO: check could be in RuboCop
problem "version #{version} should not end with an underline and a number"
end

def audit_download_strategy
if url =~ %r{^(cvs|bzr|hg|fossil)://} || url =~ %r{^(svn)\+http://}
# TODO: check could be in RuboCop
problem "Use of the #{$&} scheme is deprecated, pass `:using => :#{Regexp.last_match(1)}` instead"
end

url_strategy = DownloadStrategyDetector.detect(url)

if using == :git || url_strategy == GitDownloadStrategy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MikeMcQuaid could you give me a hint on how to access using and specs in Rubocop? I was trying to figure it out myself, but since I'm not super familiar with Rubocop and the node helpers implemented in ../rubocops/extend/formula.rb I could need a helping hand. Thx.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This # TODO is incorrect, actually; it requires a bit too much of Homebrew's internals. I'll remove it, thanks 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(there may well be other incorrect ones as I am but a fallible human, feel free to shout/ask if you see any others)

# TODO: check could be in RuboCop
problem "Git should specify :revision when a :tag is specified." if specs[:tag] && !specs[:revision]
end

Expand Down
1 change: 1 addition & 0 deletions Library/Homebrew/rubocops.rb
Expand Up @@ -19,5 +19,6 @@
require "rubocops/lines"
require "rubocops/class"
require "rubocops/uses_from_macos"
require "rubocops/files"

require "rubocops/rubocop-cask"
39 changes: 39 additions & 0 deletions Library/Homebrew/rubocops/files.rb
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require "rubocops/extend/formula"

module RuboCop
module Cop
module FormulaAudit
class Files < FormulaCop
def audit_formula(node, _class_node, _parent_class_node, _body_node)
return unless file_path

offending_node(node)
actual_mode = File.stat(file_path).mode
# Check that the file is world-readable.
if actual_mode & 0444 != 0444
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
actual: actual_mode & 0777,
wanted: "+r",
path: file_path)
end
# Check that the file is user-writeable.
if actual_mode & 0200 != 0200
problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
actual: actual_mode & 0777,
wanted: "u+w",
path: file_path)
end
# Check that the file is *not* other-writeable.
return if actual_mode & 0002 != 002

problem format("Incorrect file permissions (%03<actual>o): chmod %<wanted>s %<path>s",
actual: actual_mode & 0777,
wanted: "o-w",
path: file_path)
end
end
end
end
end
1 change: 1 addition & 0 deletions Library/Homebrew/test/.rubocop_todo.yml
Expand Up @@ -45,6 +45,7 @@ RSpec/FilePath:
- 'rubocops/components_redundancy_spec.rb'
- 'rubocops/conflicts_spec.rb'
- 'rubocops/dependency_order_spec.rb'
- 'rubocops/files_spec.rb'
- 'rubocops/homepage_spec.rb'
- 'rubocops/options_spec.rb'
- 'rubocops/patches_spec.rb'
Expand Down
53 changes: 0 additions & 53 deletions Library/Homebrew/test/dev-cmd/audit_spec.rb
Expand Up @@ -96,52 +96,6 @@ class Foo < Formula
end

describe "#audit_file" do
specify "file permissions" do
allow(File).to receive(:umask).and_return(022)

fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
RUBY

path = fa.formula.path

path.chmod 0600
fa.audit_file
expect(fa.problems)
.to eq([
"Incorrect file permissions (600): chmod +r #{path}",
])
fa.problems.clear

path.chmod 0444
fa.audit_file
expect(fa.problems)
.to eq([
"Incorrect file permissions (444): chmod u+w #{path}",
])
fa.problems.clear

path.chmod 0646
fa.audit_file
expect(fa.problems)
.to eq([
"Incorrect file permissions (646): chmod o-w #{path}",
])
fa.problems.clear

path.chmod 0002
fa.audit_file
expect(fa.problems)
.to eq([
"Incorrect file permissions (002): chmod +r #{path}",
"Incorrect file permissions (002): chmod u+w #{path}",
"Incorrect file permissions (002): chmod o-w #{path}",
])
fa.problems.clear
end

specify "DATA but no __END__" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
Expand All @@ -167,13 +121,6 @@ class Foo < Formula
expect(fa.problems).to eq(["'__END__' was found, but 'DATA' is not used"])
end

specify "no trailing newline" do
fa = formula_auditor "foo", 'class Foo<Formula; url "file:///foo-1.0.tgz";end'

fa.audit_file
expect(fa.problems).to eq(["File should end with a newline"])
end

specify "no issue" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
Expand Down
23 changes: 23 additions & 0 deletions Library/Homebrew/test/rubocops/files_spec.rb
@@ -0,0 +1,23 @@
# frozen_string_literal: true

require "rubocops/files"

describe RuboCop::Cop::FormulaAudit::Files do
subject(:cop) { described_class.new }

context "When auditing files" do
it "when the permissions are invalid" do
filename = Formulary.core_path("test_formula")
File.open(filename, "w") do |file|
FileUtils.chmod "-rwx", filename

expect_offense(<<~RUBY, file)
class Foo < Formula
^^^^^^^^^^^^^^^^^^^ Incorrect file permissions (000): chmod +r #{filename}
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
end
end
end
end