rubocop 0.74.0 → 0.75.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/config/default.yml +28 -4
  4. data/lib/rubocop.rb +6 -1
  5. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +1 -12
  6. data/lib/rubocop/comment_config.rb +3 -2
  7. data/lib/rubocop/config.rb +4 -0
  8. data/lib/rubocop/config_loader.rb +20 -2
  9. data/lib/rubocop/config_loader_resolver.rb +2 -2
  10. data/lib/rubocop/config_obsoletion.rb +12 -0
  11. data/lib/rubocop/cop/autocorrect_logic.rb +2 -2
  12. data/lib/rubocop/cop/cop.rb +4 -3
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +43 -17
  14. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +2 -2
  15. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
  16. data/lib/rubocop/cop/generator.rb +3 -3
  17. data/lib/rubocop/cop/generator/configuration_injector.rb +9 -4
  18. data/lib/rubocop/cop/generator/require_file_injector.rb +1 -1
  19. data/lib/rubocop/cop/layout/block_alignment.rb +2 -2
  20. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +1 -1
  21. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +22 -7
  22. data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +2 -2
  23. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +2 -2
  24. data/lib/rubocop/cop/layout/extra_spacing.rb +0 -6
  25. data/lib/rubocop/cop/layout/indent_assignment.rb +10 -1
  26. data/lib/rubocop/cop/layout/indent_heredoc.rb +1 -1
  27. data/lib/rubocop/cop/layout/multiline_block_layout.rb +24 -2
  28. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +5 -1
  29. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +1 -1
  30. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +7 -0
  31. data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +2 -0
  32. data/lib/rubocop/cop/lint/assignment_in_condition.rb +17 -4
  33. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  34. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +10 -36
  35. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  36. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +91 -0
  37. data/lib/rubocop/cop/lint/unneeded_cop_disable_directive.rb +1 -1
  38. data/lib/rubocop/cop/lint/unused_block_argument.rb +22 -6
  39. data/lib/rubocop/cop/lint/unused_method_argument.rb +23 -5
  40. data/lib/rubocop/cop/lint/void.rb +3 -22
  41. data/lib/rubocop/cop/message_annotator.rb +16 -7
  42. data/lib/rubocop/cop/migration/department_name.rb +44 -0
  43. data/lib/rubocop/cop/mixin/alignment.rb +1 -1
  44. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  45. data/lib/rubocop/cop/mixin/safe_mode.rb +2 -0
  46. data/lib/rubocop/cop/naming/method_name.rb +12 -1
  47. data/lib/rubocop/cop/naming/variable_name.rb +1 -0
  48. data/lib/rubocop/cop/offense.rb +18 -7
  49. data/lib/rubocop/cop/registry.rb +22 -1
  50. data/lib/rubocop/cop/style/access_modifier_declarations.rb +1 -0
  51. data/lib/rubocop/cop/style/block_delimiters.rb +2 -1
  52. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +29 -10
  53. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  54. data/lib/rubocop/cop/style/commented_keyword.rb +8 -2
  55. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -4
  56. data/lib/rubocop/cop/style/documentation_method.rb +44 -0
  57. data/lib/rubocop/cop/style/double_cop_disable_directive.rb +8 -2
  58. data/lib/rubocop/cop/style/expand_path_arguments.rb +1 -1
  59. data/lib/rubocop/cop/style/format_string_token.rb +18 -69
  60. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +18 -33
  61. data/lib/rubocop/cop/style/if_unless_modifier.rb +51 -15
  62. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  63. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -25
  64. data/lib/rubocop/cop/style/mixin_usage.rb +11 -1
  65. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
  66. data/lib/rubocop/cop/style/nested_modifier.rb +18 -2
  67. data/lib/rubocop/cop/style/or_assignment.rb +6 -1
  68. data/lib/rubocop/cop/style/parentheses_around_condition.rb +14 -0
  69. data/lib/rubocop/cop/style/redundant_parentheses.rb +13 -4
  70. data/lib/rubocop/cop/style/redundant_return.rb +12 -0
  71. data/lib/rubocop/cop/style/redundant_self.rb +18 -1
  72. data/lib/rubocop/cop/style/rescue_modifier.rb +24 -0
  73. data/lib/rubocop/cop/style/safe_navigation.rb +17 -0
  74. data/lib/rubocop/cop/style/semicolon.rb +11 -0
  75. data/lib/rubocop/cop/style/single_line_methods.rb +8 -1
  76. data/lib/rubocop/cop/style/ternary_parentheses.rb +19 -0
  77. data/lib/rubocop/cop/utils/format_string.rb +128 -0
  78. data/lib/rubocop/cop/variable_force/variable.rb +15 -2
  79. data/lib/rubocop/core_ext/string.rb +0 -24
  80. data/lib/rubocop/formatter/clang_style_formatter.rb +8 -3
  81. data/lib/rubocop/formatter/emacs_style_formatter.rb +22 -9
  82. data/lib/rubocop/formatter/file_list_formatter.rb +1 -1
  83. data/lib/rubocop/formatter/formatter_set.rb +16 -15
  84. data/lib/rubocop/formatter/pacman_formatter.rb +80 -0
  85. data/lib/rubocop/formatter/simple_text_formatter.rb +16 -4
  86. data/lib/rubocop/formatter/tap_formatter.rb +17 -4
  87. data/lib/rubocop/magic_comment.rb +4 -0
  88. data/lib/rubocop/options.rb +5 -16
  89. data/lib/rubocop/runner.rb +14 -8
  90. data/lib/rubocop/version.rb +1 -1
  91. metadata +6 -3
  92. data/lib/rubocop/cop/mixin/ignored_method_patterns.rb +0 -19
@@ -120,6 +120,7 @@ module RuboCop
120
120
  corrector.remove(begin_range(node, body))
121
121
  corrector.remove(end_range(node, body))
122
122
  corrector.insert_before(method_call.loc.dot, '&')
123
+ handle_comments(corrector, node, method_call)
123
124
 
124
125
  add_safe_nav_to_all_methods_in_chain(corrector, method_call, body)
125
126
  end
@@ -127,6 +128,22 @@ module RuboCop
127
128
 
128
129
  private
129
130
 
131
+ def handle_comments(corrector, node, method_call)
132
+ return if processed_source.comments.empty?
133
+
134
+ corrector.insert_before(method_call.loc.expression,
135
+ "#{comments(node).join("\n")}\n")
136
+ end
137
+
138
+ def comments(node)
139
+ comments = processed_source.comments.select do |comment|
140
+ comment.loc.first_line >= node.loc.first_line &&
141
+ comment.loc.last_line <= node.loc.last_line
142
+ end
143
+
144
+ comments.map(&:text)
145
+ end
146
+
130
147
  def allowed_if_condition?(node)
131
148
  node.else? || node.elsif? || node.ternary?
132
149
  end
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # This cop checks for multiple expressions placed on the same line.
7
7
  # It also checks for lines terminated with a semicolon.
8
8
  #
9
+ # This cop has `AllowAsExpressionSeparator` configuration option.
10
+ # It allows `;` to separate several expressions on the same line.
11
+ #
9
12
  # @example
10
13
  # # bad
11
14
  # foo = 1; bar = 2;
@@ -15,6 +18,14 @@ module RuboCop
15
18
  # foo = 1
16
19
  # bar = 2
17
20
  # baz = 3
21
+ #
22
+ # @example AllowAsExpressionSeparator: false (default)
23
+ # # bad
24
+ # foo = 1; bar = 2
25
+ #
26
+ # @example AllowAsExpressionSeparator: true
27
+ # # good
28
+ # foo = 1; bar = 2
18
29
  class Semicolon < Cop
19
30
  include RangeHelp
20
31
 
@@ -13,10 +13,17 @@ module RuboCop
13
13
  # def @table.columns; super; end
14
14
  #
15
15
  # # good
16
- # def no_op; end
17
16
  # def self.resource_class=(klass); end
18
17
  # def @table.columns; end
19
18
  #
19
+ # @example AllowIfMethodIsEmpty: true (default)
20
+ # # good
21
+ # def no_op; end
22
+ #
23
+ # @example AllowIfMethodIsEmpty: false
24
+ # # bad
25
+ # def no_op; end
26
+ #
20
27
  class SingleLineMethods < Cop
21
28
  include Alignment
22
29
 
@@ -8,6 +8,11 @@ module RuboCop
8
8
  # parentheses using `EnforcedStyle`. Omission is only enforced when
9
9
  # removing the parentheses won't cause a different behavior.
10
10
  #
11
+ # `AllowSafeAssignment` option for safe assignment.
12
+ # By safe assignment we mean putting parentheses around
13
+ # an assignment to indicate "I know I'm using an assignment
14
+ # as a condition. It's not a mistake."
15
+ #
11
16
  # @example EnforcedStyle: require_no_parentheses (default)
12
17
  # # bad
13
18
  # foo = (bar?) ? a : b
@@ -40,6 +45,15 @@ module RuboCop
40
45
  # foo = bar? ? a : b
41
46
  # foo = bar.baz? ? a : b
42
47
  # foo = (bar && baz) ? a : b
48
+ #
49
+ # @example AllowSafeAssignment: true (default)
50
+ # # good
51
+ # foo = (bar = baz) ? a : b
52
+ #
53
+ # @example AllowSafeAssignment: false
54
+ # # bad
55
+ # foo = (bar = baz) ? a : b
56
+ #
43
57
  class TernaryParentheses < Cop
44
58
  include SafeAssignment
45
59
  include ConfigurableEnforcedStyle
@@ -53,11 +67,16 @@ module RuboCop
53
67
  ' complex conditions.'
54
68
 
55
69
  def on_if(node)
70
+ return if only_closing_parenthesis_is_last_line?(node.condition)
56
71
  return unless node.ternary? && !infinite_loop? && offense?(node)
57
72
 
58
73
  add_offense(node, location: node.source_range)
59
74
  end
60
75
 
76
+ def only_closing_parenthesis_is_last_line?(condition)
77
+ condition.source.split("\n").last == ')'
78
+ end
79
+
61
80
  def autocorrect(node)
62
81
  condition = node.condition
63
82
 
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Utils
6
+ # Parses {Kernel#sprintf} format strings.
7
+ class FormatString
8
+ DIGIT_DOLLAR = /(\d+)\$/.freeze
9
+ FLAG = /[ #0+-]|#{DIGIT_DOLLAR}/.freeze
10
+ NUMBER_ARG = /\*#{DIGIT_DOLLAR}?/.freeze
11
+ NUMBER = /\d+|#{NUMBER_ARG}/.freeze
12
+ WIDTH = /(?<width>#{NUMBER})/.freeze
13
+ PRECISION = /\.(?<precision>#{NUMBER})/.freeze
14
+ TYPE = /(?<type>[bBdiouxXeEfgGaAcps])/.freeze
15
+ NAME = /<(?<name>\w+)>/.freeze
16
+ TEMPLATE_NAME = /\{(?<name>\w+)\}/.freeze
17
+
18
+ SEQUENCE = /
19
+ % (?<type>%)
20
+ | % (?<flags>#{FLAG}*)
21
+ (?:
22
+ (?: #{WIDTH}? #{PRECISION}? #{NAME}?
23
+ | #{WIDTH}? #{NAME} #{PRECISION}?
24
+ | #{NAME} (?<more_flags>#{FLAG}*) #{WIDTH}? #{PRECISION}?
25
+ ) #{TYPE}
26
+ | #{WIDTH}? #{PRECISION}? #{TEMPLATE_NAME}
27
+ )
28
+ /x.freeze
29
+
30
+ # The syntax of a format sequence is as follows.
31
+ #
32
+ # ```
33
+ # %[flags][width][.precision]type
34
+ # ```
35
+ #
36
+ # A format sequence consists of a percent sign, followed by optional
37
+ # flags, width, and precision indicators, then terminated with a field
38
+ # type character.
39
+ #
40
+ # For more complex formatting, Ruby supports a reference by name.
41
+ #
42
+ # @see https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-format
43
+ class FormatSequence
44
+ attr_reader :begin_pos, :end_pos
45
+ attr_reader :flags, :width, :precision, :name, :type
46
+
47
+ def initialize(string, **opts)
48
+ @source = string
49
+ @begin_pos = opts[:begin_pos]
50
+ @end_pos = opts[:end_pos]
51
+ @flags = opts[:flags]
52
+ @width = opts[:width]
53
+ @precision = opts[:precision]
54
+ @name = opts[:name]
55
+ @type = opts[:type]
56
+ end
57
+
58
+ def percent?
59
+ type == '%'
60
+ end
61
+
62
+ def annotated?
63
+ name && @source.include?('<')
64
+ end
65
+
66
+ def template?
67
+ name && @source.include?('{')
68
+ end
69
+
70
+ # Number of arguments required for the format sequence
71
+ def arity
72
+ @source.scan('*').count + 1
73
+ end
74
+
75
+ def max_digit_dollar_num
76
+ @source.scan(DIGIT_DOLLAR).map do |(digit_dollar_num)|
77
+ digit_dollar_num.to_i
78
+ end.max
79
+ end
80
+
81
+ def style
82
+ if annotated?
83
+ :annotated
84
+ elsif template?
85
+ :template
86
+ else
87
+ :unannotated
88
+ end
89
+ end
90
+ end
91
+
92
+ def initialize(string)
93
+ @source = string
94
+ end
95
+
96
+ def format_sequences
97
+ @format_sequences ||= parse
98
+ end
99
+
100
+ def named_interpolation?
101
+ format_sequences.any?(&:name)
102
+ end
103
+
104
+ def max_digit_dollar_num
105
+ format_sequences.map(&:max_digit_dollar_num).max
106
+ end
107
+
108
+ private
109
+
110
+ def parse
111
+ @source.to_enum(:scan, SEQUENCE).map do
112
+ match = Regexp.last_match
113
+ FormatSequence.new(
114
+ match[0],
115
+ begin_pos: match.begin(0),
116
+ end_pos: match.end(0),
117
+ flags: match[:flags].to_s + match[:more_flags].to_s,
118
+ width: match[:width],
119
+ precision: match[:precision],
120
+ name: match[:name],
121
+ type: match[:type]
122
+ )
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -37,7 +37,7 @@ module RuboCop
37
37
  !@references.empty?
38
38
  end
39
39
 
40
- # rubocop:disable Metrics/AbcSize
40
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
41
41
  def reference!(node)
42
42
  reference = Reference.new(node, @scope)
43
43
  @references << reference
@@ -50,6 +50,13 @@ module RuboCop
50
50
  assignment.reference!(node)
51
51
  end
52
52
 
53
+ # Modifier if/unless conditions are special. Assignments made in
54
+ # them do not put the assigned variable in scope to the left of the
55
+ # if/unless keyword. A preceding assignment is needed to put the
56
+ # variable in scope. For this reason we skip to the next assignment
57
+ # here.
58
+ next if in_modifier_if?(assignment)
59
+
53
60
  break if !assignment.branch || assignment.branch == reference.branch
54
61
 
55
62
  unless assignment.branch.may_run_incompletely?
@@ -57,7 +64,13 @@ module RuboCop
57
64
  end
58
65
  end
59
66
  end
60
- # rubocop:enable Metrics/AbcSize
67
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
68
+
69
+ def in_modifier_if?(assignment)
70
+ parent = assignment.node.parent
71
+ parent = parent.parent if parent&.begin_type?
72
+ parent&.if_type? && parent&.modifier_form?
73
+ end
61
74
 
62
75
  def capture_with_block!
63
76
  @captured_by_block = true
@@ -20,28 +20,4 @@ class String
20
20
  empty? || strip.empty?
21
21
  end
22
22
  end
23
-
24
- unless method_defined? :strip_indent
25
- # The method strips the whitespace preceding the base indentation.
26
- # Useful for HEREDOCs and other multi-line strings.
27
- #
28
- # @example
29
- #
30
- # code = <<~END
31
- # def test
32
- # some_method
33
- # other_method
34
- # end
35
- # END
36
- #
37
- # #=> "def\n some_method\n \nother_method\nend"
38
- #
39
- # @todo Replace call sites with squiggly heredocs when required Ruby
40
- # version is >= 2.3.0
41
- def strip_indent
42
- leading_space = scan(/^[ \t]*(?=\S)/).min
43
- indent = leading_space ? leading_space.size : 0
44
- gsub(/^[ \t]{#{indent}}/, '')
45
- end
46
- end
47
23
  end
@@ -15,9 +15,14 @@ module RuboCop
15
15
  private
16
16
 
17
17
  def report_offense(file, offense)
18
- output.printf("%s:%d:%d: %s: %s\n",
19
- cyan(smart_path(file)), offense.line, offense.real_column,
20
- colored_severity_code(offense), message(offense))
18
+ output.printf(
19
+ "%<path>s:%<line>d:%<column>d: %<severity>s: %<message>s\n",
20
+ path: cyan(smart_path(file)),
21
+ line: offense.line,
22
+ column: offense.real_column,
23
+ severity: colored_severity_code(offense),
24
+ message: message(offense)
25
+ )
21
26
 
22
27
  # rubocop:disable Lint/HandleExceptions
23
28
  begin
@@ -8,17 +8,30 @@ module RuboCop
8
8
  class EmacsStyleFormatter < BaseFormatter
9
9
  def file_finished(file, offenses)
10
10
  offenses.each do |o|
11
- message = if o.corrected?
12
- "[Corrected] #{o.message}"
13
- else
14
- o.message
15
- end
16
-
17
- output.printf("%s:%d:%d: %s: %s\n",
18
- file, o.line, o.real_column, o.severity.code,
19
- message.tr("\n", ' '))
11
+ output.printf(
12
+ "%<path>s:%<line>d:%<column>d: %<severity>s: %<message>s\n",
13
+ path: file,
14
+ line: o.line,
15
+ column: o.real_column,
16
+ severity: o.severity.code,
17
+ message: message(o)
18
+ )
20
19
  end
21
20
  end
21
+
22
+ private
23
+
24
+ def message(offense)
25
+ message =
26
+ if offense.corrected_with_todo?
27
+ "[Todo] #{offense.message}"
28
+ elsif offense.corrected?
29
+ "[Corrected] #{offense.message}"
30
+ else
31
+ offense.message
32
+ end
33
+ message.tr("\n", ' ')
34
+ end
22
35
  end
23
36
  end
24
37
  end
@@ -13,7 +13,7 @@ module RuboCop
13
13
  def file_finished(file, offenses)
14
14
  return if offenses.empty?
15
15
 
16
- output.printf("%s\n", file)
16
+ output.printf("%<path>s\n", path: file)
17
17
  end
18
18
  end
19
19
  end
@@ -9,20 +9,21 @@ module RuboCop
9
9
  # which invoke same method of each formatters.
10
10
  class FormatterSet < Array
11
11
  BUILTIN_FORMATTERS_FOR_KEYS = {
12
- 'progress' => ProgressFormatter,
13
- 'simple' => SimpleTextFormatter,
14
- 'clang' => ClangStyleFormatter,
15
- 'fuubar' => FuubarStyleFormatter,
16
- 'emacs' => EmacsStyleFormatter,
17
- 'json' => JSONFormatter,
18
- 'html' => HTMLFormatter,
19
- 'files' => FileListFormatter,
20
- 'offenses' => OffenseCountFormatter,
21
- 'disabled' => DisabledLinesFormatter,
22
- 'worst' => WorstOffendersFormatter,
23
- 'tap' => TapFormatter,
24
- 'quiet' => QuietFormatter,
25
- 'autogenconf' => AutoGenConfigFormatter
12
+ '[a]utogenconf' => AutoGenConfigFormatter,
13
+ '[c]lang' => ClangStyleFormatter,
14
+ '[d]isabled' => DisabledLinesFormatter,
15
+ '[e]macs' => EmacsStyleFormatter,
16
+ '[fi]les' => FileListFormatter,
17
+ '[fu]ubar' => FuubarStyleFormatter,
18
+ '[h]tml' => HTMLFormatter,
19
+ '[j]son' => JSONFormatter,
20
+ '[o]ffenses' => OffenseCountFormatter,
21
+ '[pa]cman' => PacmanFormatter,
22
+ '[p]rogress' => ProgressFormatter,
23
+ '[q]uiet' => QuietFormatter,
24
+ '[s]imple' => SimpleTextFormatter,
25
+ '[t]ap' => TapFormatter,
26
+ '[w]orst' => WorstOffendersFormatter
26
27
  }.freeze
27
28
 
28
29
  FORMATTER_APIS = %i[started finished].freeze
@@ -81,7 +82,7 @@ module RuboCop
81
82
 
82
83
  def builtin_formatter_class(specified_key)
83
84
  matching_keys = BUILTIN_FORMATTERS_FOR_KEYS.keys.select do |key|
84
- key.start_with?(specified_key)
85
+ key =~ /^\[#{specified_key}\]/ || specified_key == key.delete('[]')
85
86
  end
86
87
 
87
88
  raise %(No formatter for "#{specified_key}") if matching_keys.empty?
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Formatter
5
+ # This formatter prints a PACDOT per every file to be analyzed.
6
+ # Pacman will "eat" one PACDOT per file when no offense is detected.
7
+ # Otherwise it will print a Ghost.
8
+ # This is inspired by the Pacman formatter for RSpec by Carlos Rojas.
9
+ # https://github.com/go-labs/rspec_pacman_formatter
10
+ class PacmanFormatter < ClangStyleFormatter
11
+ include TextUtil
12
+ attr_accessor :progress_line
13
+
14
+ FALLBACK_TERMINAL_WIDTH = 80
15
+ GHOST = 'ᗣ'
16
+ PACMAN = Rainbow('ᗧ').yellow.bright
17
+ PACDOT = Rainbow('•').yellow.bright
18
+
19
+ def initialize(output, options = {})
20
+ super
21
+ @progress_line = ''
22
+ @total_files = 0
23
+ @repetitions = 0
24
+ end
25
+
26
+ def started(target_files)
27
+ super
28
+ @total_files = target_files.size
29
+ output.puts "Eating #{pluralize(target_files.size, 'file')}"
30
+ update_progress_line
31
+ end
32
+
33
+ def file_started(_file, _options)
34
+ step(PACMAN)
35
+ end
36
+
37
+ def file_finished(file, offenses)
38
+ count_stats(offenses) unless offenses.empty?
39
+ next_step(offenses)
40
+ report_file(file, offenses)
41
+ end
42
+
43
+ def next_step(offenses)
44
+ return step('.') if offenses.empty?
45
+
46
+ ghost_color = COLOR_FOR_SEVERITY[offenses.last.severity.name]
47
+ step(colorize(GHOST, ghost_color))
48
+ end
49
+
50
+ def cols
51
+ @cols ||= begin
52
+ _height, width = $stdout.winsize
53
+ width.nil? || width.zero? ? FALLBACK_TERMINAL_WIDTH : width
54
+ end
55
+ end
56
+
57
+ def update_progress_line
58
+ return pacdots(@total_files) unless @total_files > cols
59
+ return pacdots(cols) unless (@total_files / cols).eql?(@repetitions)
60
+
61
+ pacdots((@total_files - (cols * @repetitions)))
62
+ end
63
+
64
+ def pacdots(number)
65
+ @progress_line = PACDOT * number
66
+ end
67
+
68
+ def step(character)
69
+ regex = /#{Regexp.quote(PACMAN)}|#{Regexp.quote(PACDOT)}/
70
+ @progress_line = @progress_line.sub(regex, character)
71
+ output.printf("%<line>s\r", line: @progress_line)
72
+ return unless @progress_line[-1] =~ /ᗣ|\./
73
+
74
+ @repetitions += 1
75
+ output.puts
76
+ update_progress_line
77
+ end
78
+ end
79
+ end
80
+ end