Class: RubocopPlus::Commands::Rubo

Inherits:
Cri::CommandRunner
  • Object
show all
Defined in:
lib/commands/rubo.rb,
lib/commands/rubo/helpers/misc.rb,
lib/commands/rubo/helpers/output.rb,
lib/commands/rubo/helpers/validations.rb

Overview

Helpers for performing validations are grouped in this file.

Instance Method Summary collapse

Instance Method Details

#check_if_results_exist!Object

Raises:

  • (error)


17
18
19
20
21
22
23
24
25
# File 'lib/commands/rubo/helpers/validations.rb', line 17

def check_if_results_exist!
  error = RubocopPlus::Commands::BadOutput

  check_if_violations_count_exists?
  raise error, "The '#{output_folder_name}' folder does not exist." unless File.directory?(output_folder_name)
  raise error, "Missing #{style_issues_text_file_name}" unless File.exist?(style_issues_text_file_name)
  raise error, "Missing #{style_issues_html_file_name}" unless File.exist?(style_issues_html_file_name)
  raise error, "Missing #{style_counts_text_file_name}" unless File.exist?(style_counts_text_file_name)
end

#check_if_rubocop_command_exists!Object



13
14
15
# File 'lib/commands/rubo/helpers/validations.rb', line 13

def check_if_rubocop_command_exists!
  raise RubocopPlus::Commands::NoRubocopCommand unless rubocop_command_exists?
end

#check_if_total_violations_count_still_present_and_valid!Object

This method is called when the user runs ‘rubo –total-violations`. That option is used to open the “total violations counts” file and print the number. However, the user can run `rubo`, wait for a bit, delete his rubocop folder and THEN run `rubo –total-violations`. This is a little different from the other checks where we are IMMEDIATELY checking the results of the rubo command. In those checks, we’re trying to make sure the rubo command worked correctly and was successfully able to write results. In this check, we’re making sure the files are still around before we try and open them.

Raises:

  • (error)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/commands/rubo/helpers/validations.rb', line 41

def check_if_total_violations_count_still_present_and_valid!
  error = RubocopPlus::Commands::OutputMissing
  prefix = "Cannot proceed."
  raise error, "#{prefix}  The '#{output_folder_name}' folder does not exist." unless File.directory?(output_folder_name)

  file_name = total_violations_count_text_file_name
  raise error, "#{prefix}  The #{file_name} file is missing." unless File.exist?(file_name)

  violations = File.read(file_name)
  raise error, "#{prefix}  The #{file_name} file is empty." if violations.nil? || violations.strip.empty?

  violations = violations.strip
  raise error, "#{prefix}  The #{file_name} file contains non-digit characters." unless /^\d*$/.match?(violations)
end

#check_if_violations_count_exists?Boolean

Returns:

  • (Boolean)

Raises:

  • (error)


27
28
29
30
31
32
33
# File 'lib/commands/rubo/helpers/validations.rb', line 27

def check_if_violations_count_exists?
  error = RubocopPlus::Commands::BadOutput
  raise error, "Missing #{total_violations_count_text_file_name}" unless File.exist?(total_violations_count_text_file_name)

  violations = File.read(total_violations_count_text_file_name)
  raise error, "#{total_violations_count_text_file_name} is empty" if violations.nil? || violations.strip.empty?
end

#check_if_yml_has_correct_content!Object



9
10
11
# File 'lib/commands/rubo/helpers/validations.rb', line 9

def check_if_yml_has_correct_content!
  raise RubocopPlus::Commands::IncorrectYml unless rubocop_yml_has_correct_content?
end

#check_if_yml_present!Object



5
6
7
# File 'lib/commands/rubo/helpers/validations.rb', line 5

def check_if_yml_present!
  raise RubocopPlus::Commands::MissingYml unless rubocop_yml_present?
end

#checkstyle_optionsObject

Generates the XML reports necessary to use the Checkstyle Plugin on Jenkins. It is only needed for the CI so return an empty string unless the –checkstyle option is present.



65
66
67
68
69
70
71
# File 'lib/commands/rubo.rb', line 65

def checkstyle_options
  return "" unless options[:checkstyle]

  cmd = " --require rubocop/formatter/checkstyle_formatter"
  cmd += " --format RuboCop::Formatter::CheckstyleFormatter"
  cmd + " --out #{output_folder_name}/checkstyle.xml"
end

#delete_rubocop_folderObject

While using SublimeText with the SFTP plugin, we noticed the SFTP plugin would not automatically download newer versions of the generated rubocop files at times. Rubocop should overwrite the files, but we goahead and explicity delete the folder in the hopes that will help resolve the file syncing issues for editors (we’re not 100% sure this helps).



76
77
78
# File 'lib/commands/rubo.rb', line 76

def delete_rubocop_folder
  FileUtils.remove_dir(output_folder_name, true)
end

#determine_total_violations_countObject

Read the total number of violations from the output. Unfortunately, rubocop doesn’t have a direct way to generate this number, but it does produce a file with the output burried inside it. We can search the file and figure out the violations count. The file will have a line that looks like this:

1234  Total


19
20
21
22
23
# File 'lib/commands/rubo/helpers/output.rb', line 19

def determine_total_violations_count
  total_line = ""
  File.readlines(style_counts_text_file_name).each { |line| total_line = line if line.include?("Total") }
  @total_violations = total_line.empty? ? 0 : total_line.split.first.to_i
end

#generate_total_violations_fileObject

This method writes a single integer to a file which represents the total number of violations. The value in this file can be used to fail builds on the CI/CD pipeline which have too many violations. Rubocop doesn’t appear to have a formatter that will write the total number of violations to a file. Fortunately, we can pull this information out of some other files and write the output ourselves.



38
39
40
# File 'lib/commands/rubo/helpers/output.rb', line 38

def generate_total_violations_file
  File.open(total_violations_count_text_file_name, 'w') { |f| f.print determine_total_violations_count }
end

#initialize_projectObject



42
43
44
45
46
47
# File 'lib/commands/rubo.rb', line 42

def initialize_project
  backup_rubocop_yml_if_present
  template_path = File.expand_path('../templates/.rubocop.yml', __dir__)
  FileUtils.cp(template_path, ".rubocop.yml")
  puts ".rubocop.yml created"
end

#installed_version_of_gemObject



25
26
27
# File 'lib/commands/rubo/helpers/misc.rb', line 25

def installed_version_of_gem
  Gem::Version.new(RubocopPlus::VERSION)
end

#invoke_rubocopObject

Execute rubocop with a tailored set of output files. Rails checks are activated in the default config/rubocop.yml file so we do not explicitly activate them there.



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/commands/rubo.rb', line 51

def invoke_rubocop
  cmd = is_in_gemfile_dot_lock? ? "bundle exec rubocop" : "rubocop"
  # Make sure we always use the specific version of rubocop.  Without this line, the latest version of rubocop
  # will be used if a user installs a version that is newer than what we want to use.
  cmd += " _#{RubocopPlus::RUBOCOP_VERSION}_"
  cmd += " --format simple --out #{style_issues_text_file_name}"
  cmd += " --format html --out #{style_issues_html_file_name}"
  cmd += " --format offenses --out #{style_counts_text_file_name}"
  cmd += checkstyle_options
  system cmd
end

#is_in_gemfile_dot_lock?Boolean

The Gemfile.lock is auto generated when the ‘bundle’ command is run. It doesn’t contain superfluous comments or any references to a gem unless the gem is being used. However, many terms like ‘rails’ is used in multiple gem name so we need to be smart about checking boundaries. Each gem line in Gemfile.lock always starts with a couple spaces, followed by the gem name, followed by a single space or newline chracter.

Returns:

  • (Boolean)


13
14
15
16
17
18
19
20
21
22
23
# File 'lib/commands/rubo/helpers/misc.rb', line 13

def is_in_gemfile_dot_lock?
  return false unless File.exist?("Gemfile.lock")

  # Match logic
  #   ^               - match from beginning of string
  #   \s*             - match 0-N white space characters
  #   rubocop_plus    - match the gem_name exactly once
  #   \s              - match one white space character (it will always be a space or a \n)
  File.foreach("Gemfile.lock") { |line| return true if /^\s*rubocop_plus\s/.match?(line) }
  false
end

#output_folder_nameObject



33
34
35
# File 'lib/commands/rubo/helpers/misc.rb', line 33

def output_folder_name
  "rubocop"
end

#rubocop_command_exists?Boolean

Returns:

  • (Boolean)


62
63
64
65
# File 'lib/commands/rubo/helpers/misc.rb', line 62

def rubocop_command_exists?
  path = Which('rubocop')
  !(path.nil? || path.empty?)
end

#rubocop_version_nameObject



29
30
31
# File 'lib/commands/rubo/helpers/misc.rb', line 29

def rubocop_version_name
  Gem::Version.new(RubocopPlus::RUBOCOP_VERSION)
end

#rubocop_yml_has_correct_content?Boolean

Perform some sanity checks to make sure .rubocop.yml has expected content.

Returns:

  • (Boolean)


54
55
56
57
58
59
60
# File 'lib/commands/rubo/helpers/misc.rb', line 54

def rubocop_yml_has_correct_content?
  # If we're running from inside the rubocop_plus folder, ignore this check.
  return true if Dir.pwd.include?("rubocop_plus")

  text = File.read(".rubocop.yml")
  text.include?("inherit_gem:") && text.include?("rubocop_plus:")
end

#rubocop_yml_present?Boolean

Returns:

  • (Boolean)


5
6
7
# File 'lib/commands/rubo/helpers/misc.rb', line 5

def rubocop_yml_present?
  File.file?(".rubocop.yml")
end

#runObject



19
20
21
22
23
24
25
26
27
28
# File 'lib/commands/rubo.rb', line 19

def run
  prepare_options
  validate_pre_run!
  write_starting_message
  delete_rubocop_folder
  invoke_rubocop
  generate_total_violations_file
  validate_post_run!
  write_results_message
end

#style_counts_text_file_nameObject



45
46
47
# File 'lib/commands/rubo/helpers/misc.rb', line 45

def style_counts_text_file_name
  "#{output_folder_name}/style-counts.txt"
end

#style_issues_html_file_nameObject



41
42
43
# File 'lib/commands/rubo/helpers/misc.rb', line 41

def style_issues_html_file_name
  "#{output_folder_name}/style-issues.html"
end

#style_issues_text_file_nameObject



37
38
39
# File 'lib/commands/rubo/helpers/misc.rb', line 37

def style_issues_text_file_name
  "#{output_folder_name}/style-issues.txt"
end

#total_violations_countObject

Return the total violations count from the last rubocop run and remember the result. This result should not do any validation to make sure the file exists, or is correct. That is checked in other parts of the system.



27
28
29
30
31
32
# File 'lib/commands/rubo/helpers/output.rb', line 27

def total_violations_count
  return @total_violations unless @total_violations.nil?

  first_line = File.read(total_violations_count_text_file_name)
  @total_violations = first_line.strip.to_i
end

#total_violations_count_text_file_nameObject



49
50
51
# File 'lib/commands/rubo/helpers/misc.rb', line 49

def total_violations_count_text_file_name
  "#{output_folder_name}/total-violations-count.txt"
end

#validate_post_run!Object



38
39
40
# File 'lib/commands/rubo.rb', line 38

def validate_post_run!
  check_if_results_exist!
end

#validate_pre_run!Object

Perform all of the checks to make sure rubo can actually run. This method is called by the ‘run’ method when ‘rubo’ is called. It’s also called with ‘rubo –validate’ is called.



32
33
34
35
36
# File 'lib/commands/rubo.rb', line 32

def validate_pre_run!
  check_if_yml_present!
  check_if_yml_has_correct_content!
  check_if_rubocop_command_exists!
end

#write_results_messageObject



5
6
7
8
9
# File 'lib/commands/rubo/helpers/output.rb', line 5

def write_results_message
  prefix = "#{total_violations_count} issues found, results written to rubocop/"
  prefix = total_violations_count.zero? ? prefix.rubo_green : prefix.rubo_red
  puts "#{prefix} (rubocop_plus v#{installed_version_of_gem} / rubocop v#{rubocop_version_name})"
end

#write_starting_messageObject



11
12
13
# File 'lib/commands/rubo/helpers/output.rb', line 11

def write_starting_message
  print "Running rubocop ... "
end