diff --git a/lib/rubocop/comment_config.rb b/lib/rubocop/comment_config.rb index d15799fd0d7..526b333dc85 100644 --- a/lib/rubocop/comment_config.rb +++ b/lib/rubocop/comment_config.rb @@ -128,9 +128,7 @@ def directive_on_comment_line?(comment) end def each_directive - return if processed_source.comments.nil? - - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| directive = directive_parts(comment) next unless directive diff --git a/lib/rubocop/cop/correctors/line_break_corrector.rb b/lib/rubocop/cop/correctors/line_break_corrector.rb index d71df9f53aa..a4fa54dd3bc 100644 --- a/lib/rubocop/cop/correctors/line_break_corrector.rb +++ b/lib/rubocop/cop/correctors/line_break_corrector.rb @@ -16,7 +16,7 @@ def correct_trailing_body(configured_width:, corrector:, node:, processed_source:) @processed_source = processed_source range = first_part_of(node.to_a.last) - eol_comment = end_of_line_comment(node.source_range.line) + eol_comment = processed_source.comment_at_line(node.source_range.line) break_line_before(range: range, node: node, corrector: corrector, configured_width: configured_width) diff --git a/lib/rubocop/cop/layout/class_structure.rb b/lib/rubocop/cop/layout/class_structure.rb index 0b5c59cbded..3fa226199cd 100644 --- a/lib/rubocop/cop/layout/class_structure.rb +++ b/lib/rubocop/cop/layout/class_structure.rb @@ -269,15 +269,11 @@ def end_position_for(node) end def begin_pos_with_comment(node) - annotation_line = node.first_line - 1 first_comment = nil + (node.first_line - 1).downto(1) do |annotation_line| + break unless (comment = processed_source.comment_at_line(annotation_line)) - processed_source.comments_before_line(annotation_line) - .reverse_each do |comment| - if comment.location.line == annotation_line - first_comment = comment - annotation_line -= 1 - end + first_comment = comment end start_line_position(first_comment || node) diff --git a/lib/rubocop/cop/layout/comment_indentation.rb b/lib/rubocop/cop/layout/comment_indentation.rb index 55387baa6be..e03987335f6 100644 --- a/lib/rubocop/cop/layout/comment_indentation.rb +++ b/lib/rubocop/cop/layout/comment_indentation.rb @@ -39,7 +39,7 @@ class CommentIndentation < Cop 'instead of %d).' def investigate(processed_source) - processed_source.each_comment { |comment| check(comment) } + processed_source.comments.each { |comment| check(comment) } end def autocorrect(comment) diff --git a/lib/rubocop/cop/layout/extra_spacing.rb b/lib/rubocop/cop/layout/extra_spacing.rb index aaa97f4b9f3..b6d0a0fc16a 100644 --- a/lib/rubocop/cop/layout/extra_spacing.rb +++ b/lib/rubocop/cop/layout/extra_spacing.rb @@ -40,6 +40,7 @@ class ExtraSpacing < Base def on_new_investigation return if processed_source.blank? + @aligned_comments = aligned_locations(processed_source.comments.map(&:loc)) @corrected = Set.new if force_equal_sign_alignment? processed_source.tokens.each_cons(2) do |token1, token2| @@ -49,6 +50,18 @@ def on_new_investigation private + def aligned_locations(locs) + return [] if locs.empty? + + aligned = Set[locs.first.line, locs.last.line] + locs.each_cons(3) do |before, loc, after| + col = loc.column + aligned << loc.line if col == before.column || # rubocop:disable Style/MultipleComparison + col == after.column + end + aligned + end + def check_tokens(ast, token1, token2) return if token2.type == :tNL @@ -95,7 +108,7 @@ def extra_space_range(token1, token2) def aligned_tok?(token) if token.comment? - aligned_comments?(token) + @aligned_comments.include?(token.line) else aligned_with_something?(token.pos) end @@ -119,26 +132,6 @@ def ignored_ranges(ast) end.compact end - def aligned_comments?(comment_token) - ix = processed_source.comments.index do |comment| - comment.loc.expression.begin_pos == comment_token.begin_pos - end - aligned_with_previous_comment?(ix) || aligned_with_next_comment?(ix) - end - - def aligned_with_previous_comment?(index) - index.positive? && comment_column(index - 1) == comment_column(index) - end - - def aligned_with_next_comment?(index) - index < processed_source.comments.length - 1 && - comment_column(index + 1) == comment_column(index) - end - - def comment_column(index) - processed_source.comments[index].loc.column - end - def force_equal_sign_alignment? cop_config['ForceEqualSignAlignment'] end diff --git a/lib/rubocop/cop/layout/leading_comment_space.rb b/lib/rubocop/cop/layout/leading_comment_space.rb index efdf2f2773a..5939666d9b2 100644 --- a/lib/rubocop/cop/layout/leading_comment_space.rb +++ b/lib/rubocop/cop/layout/leading_comment_space.rb @@ -55,7 +55,7 @@ class LeadingCommentSpace < Cop MSG = 'Missing space after `#`.' def investigate(processed_source) - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| next unless /\A#+[^#\s=:+-]/.match?(comment.text) next if comment.loc.line == 1 && allowed_on_first_line?(comment) next if doxygen_comment_style?(comment) diff --git a/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb b/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb index b6669cd0498..eaa845713a6 100644 --- a/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +++ b/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb @@ -41,13 +41,12 @@ def initialize(config = nil, options = nil, offenses = nil) def on_new_investigation return unless offenses_to_check - comments = processed_source.comments cop_disabled_line_ranges = processed_source.disabled_line_ranges redundant_cops = Hash.new { |h, k| h[k] = Set.new } each_redundant_disable(cop_disabled_line_ranges, - offenses_to_check, comments) do |comment, redundant_cop| + offenses_to_check) do |comment, redundant_cop| redundant_cops[comment].add(redundant_cop) end @@ -88,25 +87,25 @@ def directive_range_in_list(range, ranges) newlines: false) end - def each_redundant_disable(cop_disabled_line_ranges, offenses, comments, + def each_redundant_disable(cop_disabled_line_ranges, offenses, &block) disabled_ranges = cop_disabled_line_ranges[COP_NAME] || [0..0] cop_disabled_line_ranges.each do |cop, line_ranges| each_already_disabled(line_ranges, - disabled_ranges, comments) do |comment| + disabled_ranges) do |comment| yield comment, cop end - each_line_range(line_ranges, disabled_ranges, offenses, comments, + each_line_range(line_ranges, disabled_ranges, offenses, cop, &block) end end - def each_line_range(line_ranges, disabled_ranges, offenses, comments, + def each_line_range(line_ranges, disabled_ranges, offenses, cop) line_ranges.each_with_index do |line_range, ix| - comment = comments.find { |c| c.loc.line == line_range.begin } + comment = processed_source.comment_at_line(line_range.begin) next if ignore_offense?(disabled_ranges, line_range) redundant_cop = find_redundant(comment, offenses, cop, line_range, @@ -115,7 +114,7 @@ def each_line_range(line_ranges, disabled_ranges, offenses, comments, end end - def each_already_disabled(line_ranges, disabled_ranges, comments) + def each_already_disabled(line_ranges, disabled_ranges) line_ranges.each_cons(2) do |previous_range, range| next if ignore_offense?(disabled_ranges, range) next if previous_range.end != range.begin @@ -124,14 +123,12 @@ def each_already_disabled(line_ranges, disabled_ranges, comments) # the end of the previous range, it means that the cop was # already disabled by an earlier comment. So it's redundant # whether there are offenses or not. - redundant_comment = comments.find do |c| - c.loc.line == range.begin && - # Comments disabling all cops don't count since it's reasonable - # to disable a few select cops first and then all cops further - # down in the code. - !all_disabled?(c) - end - yield redundant_comment if redundant_comment + comment = processed_source.comment_at_line(range.begin) + + # Comments disabling all cops don't count since it's reasonable + # to disable a few select cops first and then all cops further + # down in the code. + yield comment if comment && !all_disabled?(comment) end end diff --git a/lib/rubocop/cop/migration/department_name.rb b/lib/rubocop/cop/migration/department_name.rb index ff30e3938f1..6aac2797a47 100644 --- a/lib/rubocop/cop/migration/department_name.rb +++ b/lib/rubocop/cop/migration/department_name.rb @@ -20,7 +20,7 @@ class DepartmentName < Base DISABLING_COPS_CONTENT_TOKEN = %r{[A-z]+/[A-z]+|all}.freeze def on_new_investigation - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| next if comment.text !~ DISABLE_COMMENT_FORMAT offset = Regexp.last_match(1).length diff --git a/lib/rubocop/cop/mixin/alignment.rb b/lib/rubocop/cop/mixin/alignment.rb index 034267a11a1..7fc9a9f66c7 100644 --- a/lib/rubocop/cop/mixin/alignment.rb +++ b/lib/rubocop/cop/mixin/alignment.rb @@ -64,8 +64,9 @@ def within?(inner, outer) inner.begin_pos >= outer.begin_pos && inner.end_pos <= outer.end_pos end + # @deprecated Use processed_source.comment_at_line(line) def end_of_line_comment(line) - processed_source.find_comment { |c| c.loc.line == line } + processed_source.line_with_comment?(line) end end end diff --git a/lib/rubocop/cop/mixin/check_line_breakable.rb b/lib/rubocop/cop/mixin/check_line_breakable.rb index ac73e244820..21d8c04c513 100644 --- a/lib/rubocop/cop/mixin/check_line_breakable.rb +++ b/lib/rubocop/cop/mixin/check_line_breakable.rb @@ -59,7 +59,7 @@ def extract_breakable_node_from_elements(node, elements, max) return if safe_to_ignore?(node) line = processed_source.lines[node.first_line - 1] - return if processed_source.commented?(node.loc.begin) + return if processed_source.line_with_comment?(node.loc.line) return if line.length <= max extract_first_element_over_column_limit(node, elements, max) diff --git a/lib/rubocop/cop/mixin/line_length_help.rb b/lib/rubocop/cop/mixin/line_length_help.rb index 8bfcb35cfff..30ad7890d94 100644 --- a/lib/rubocop/cop/mixin/line_length_help.rb +++ b/lib/rubocop/cop/mixin/line_length_help.rb @@ -12,9 +12,7 @@ def ignore_cop_directives? def directive_on_source_line?(line_index) source_line_number = line_index + processed_source.buffer.first_line - comment = - processed_source.comments - .detect { |e| e.location.line == source_line_number } + comment = processed_source.comment_at_line(source_line_number) return false unless comment diff --git a/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb b/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb index 0bbbd6a5d45..f7491cba288 100644 --- a/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +++ b/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb @@ -27,8 +27,7 @@ def new_line_needed_before_closing_brace?(node) last_element_line = last_element_range_with_trailing_comma(node).last_line - last_element_commented = - processed_source.comments.any? { |c| c.loc.line == last_element_line } + last_element_commented = processed_source.comment_at_line(last_element_line) last_element_commented && (node.chained? || node.argument?) end diff --git a/lib/rubocop/cop/mixin/percent_array.rb b/lib/rubocop/cop/mixin/percent_array.rb index e2448b6c6c1..8d9804114a9 100644 --- a/lib/rubocop/cop/mixin/percent_array.rb +++ b/lib/rubocop/cop/mixin/percent_array.rb @@ -28,12 +28,8 @@ def message(_node) end def comments_in_array?(node) - comments = processed_source.comments - array_range = node.source_range.to_a - - comments.any? do |comment| - !(comment.loc.expression.to_a & array_range).empty? - end + line_span = node.source_range.first_line...node.source_range.last_line + processed_source.each_comment_in_lines(line_span).any? end def check_percent_array(node) diff --git a/lib/rubocop/cop/mixin/statement_modifier.rb b/lib/rubocop/cop/mixin/statement_modifier.rb index 44a5e8df218..587b8ff0286 100644 --- a/lib/rubocop/cop/mixin/statement_modifier.rb +++ b/lib/rubocop/cop/mixin/statement_modifier.rb @@ -19,14 +19,14 @@ def single_line_as_modifier?(node) def non_eligible_node?(node) node.modifier_form? || node.nonempty_line_count > 3 || - processed_source.commented?(node.loc.end) + processed_source.line_with_comment?(node.loc.last_line) end def non_eligible_body?(body) body.nil? || body.empty_source? || body.begin_type? || - processed_source.commented?(body.source_range) + processed_source.contains_comment?(body.source_range) end def non_eligible_condition?(condition) diff --git a/lib/rubocop/cop/mixin/trailing_comma.rb b/lib/rubocop/cop/mixin/trailing_comma.rb index ea402797369..285138d7f53 100644 --- a/lib/rubocop/cop/mixin/trailing_comma.rb +++ b/lib/rubocop/cop/mixin/trailing_comma.rb @@ -74,10 +74,8 @@ def should_have_comma?(style, node) end def inside_comment?(range, comma_offset) - processed_source.comments.any? do |comment| - comment_offset = comment.loc.expression.begin_pos - range.begin_pos - comment_offset >= 0 && comment_offset < comma_offset - end + comment = processed_source.comment_at_line(range.line) + comment && comment.loc.expression.begin_pos < range.begin_pos + comma_offset end # Returns true if the node has round/square/curly brackets. diff --git a/lib/rubocop/cop/style/ascii_comments.rb b/lib/rubocop/cop/style/ascii_comments.rb index b8f4b61c183..af3383b9be2 100644 --- a/lib/rubocop/cop/style/ascii_comments.rb +++ b/lib/rubocop/cop/style/ascii_comments.rb @@ -21,7 +21,7 @@ class AsciiComments < Base MSG = 'Use only ascii symbols in comments.' def on_new_investigation - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| next if comment.text.ascii_only? next if only_allowed_non_ascii_chars?(comment.text) diff --git a/lib/rubocop/cop/style/block_comments.rb b/lib/rubocop/cop/style/block_comments.rb index 55a1facf2f1..3c06c075b6a 100644 --- a/lib/rubocop/cop/style/block_comments.rb +++ b/lib/rubocop/cop/style/block_comments.rb @@ -25,7 +25,7 @@ class BlockComments < Base END_LENGTH = "\n=end".length def on_new_investigation - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| next unless comment.document? add_offense(comment) do |corrector| diff --git a/lib/rubocop/cop/style/commented_keyword.rb b/lib/rubocop/cop/style/commented_keyword.rb index d11ee5e8874..4b83211fa27 100644 --- a/lib/rubocop/cop/style/commented_keyword.rb +++ b/lib/rubocop/cop/style/commented_keyword.rb @@ -38,7 +38,7 @@ class CommentedKeyword < Cop '`%s` keyword.' def investigate(processed_source) - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| add_offense(comment) if offensive?(comment) end end diff --git a/lib/rubocop/cop/style/empty_case_condition.rb b/lib/rubocop/cop/style/empty_case_condition.rb index 2bbde732417..b737681b30c 100644 --- a/lib/rubocop/cop/style/empty_case_condition.rb +++ b/lib/rubocop/cop/style/empty_case_condition.rb @@ -73,7 +73,7 @@ def correct_case_when(corrector, case_node, when_nodes) corrector.replace(case_range, 'if') - keep_first_when_comment(case_node, when_nodes.first, corrector) + keep_first_when_comment(case_range, corrector) when_nodes[1..-1].each do |when_node| corrector.replace(when_node.loc.keyword, 'elsif') @@ -93,15 +93,14 @@ def correct_when_conditions(corrector, when_nodes) end end - def keep_first_when_comment(case_node, first_when_node, corrector) - comment = processed_source.comments_before_line( - first_when_node.loc.keyword.line - ).map(&:text).join("\n") + def keep_first_when_comment(case_range, corrector) + indent = ' ' * case_range.column + comments = processed_source.each_comment_in_lines( + case_range.first_line...case_range.last_line + ).map { |comment| "#{indent}#{comment.text}\n" }.join - line = range_by_whole_lines(case_node.source_range) - - corrector.insert_before(line, "#{comment}\n") if !comment.empty? && - !case_node.parent + line_beginning = case_range.adjust(begin_pos: -case_range.column) + corrector.insert_before(line_beginning, comments) end end end diff --git a/lib/rubocop/cop/style/empty_else.rb b/lib/rubocop/cop/style/empty_else.rb index d05975feb4b..71e1b0093fb 100644 --- a/lib/rubocop/cop/style/empty_else.rb +++ b/lib/rubocop/cop/style/empty_else.rb @@ -138,21 +138,16 @@ def nil_check(node) def autocorrect(corrector, node) return false if autocorrect_forbidden?(node.type.to_s) - return false if comment_in_else?(node) + return false if comment_in_else?(node.loc) end_pos = base_node(node).loc.end.begin_pos corrector.remove(range_between(node.loc.else.begin_pos, end_pos)) end - def comment_in_else?(node) - range = else_line_range(node.loc) - processed_source.find_comment { |c| range.include?(c.loc.line) } - end - - def else_line_range(loc) - return 0..0 if loc.else.nil? || loc.end.nil? + def comment_in_else?(loc) + return false if loc.else.nil? || loc.end.nil? - loc.else.first_line..loc.end.first_line + processed_source.contains_comment?(loc.else.join(loc.end)) end def base_node(node) diff --git a/lib/rubocop/cop/style/if_unless_modifier.rb b/lib/rubocop/cop/style/if_unless_modifier.rb index a6097020f3b..27bbcc34d43 100644 --- a/lib/rubocop/cop/style/if_unless_modifier.rb +++ b/lib/rubocop/cop/style/if_unless_modifier.rb @@ -159,10 +159,7 @@ def to_normal_form(node) end def first_line_comment(node) - comment = - processed_source.find_comment { |c| c.loc.line == node.loc.line } - - comment ? comment.loc.expression.source : nil + processed_source.comment_at_line(node.loc.line)&.text end end end diff --git a/lib/rubocop/cop/style/inline_comment.rb b/lib/rubocop/cop/style/inline_comment.rb index 60ee37fc492..ecdee0b6771 100644 --- a/lib/rubocop/cop/style/inline_comment.rb +++ b/lib/rubocop/cop/style/inline_comment.rb @@ -21,7 +21,7 @@ class InlineComment < Base MSG = 'Avoid trailing inline comments.' def on_new_investigation - processed_source.each_comment do |comment| + processed_source.comments.each do |comment| next if comment_line?(processed_source[comment.loc.line - 1]) || comment.text.match?(/\A# rubocop:(enable|disable)/) diff --git a/lib/rubocop/cop/style/safe_navigation.rb b/lib/rubocop/cop/style/safe_navigation.rb index ab61770d035..d49abd7ca39 100644 --- a/lib/rubocop/cop/style/safe_navigation.rb +++ b/lib/rubocop/cop/style/safe_navigation.rb @@ -136,10 +136,10 @@ def handle_comments(corrector, node, method_call) end def comments(node) - processed_source.comments.select do |comment| - comment.loc.first_line > node.loc.first_line && - comment.loc.last_line < node.loc.last_line - end + processed_source.each_comment_in_lines( + node.loc.first_line... + node.loc.last_line + ).to_a end def allowed_if_condition?(node) diff --git a/lib/rubocop/cop/style/single_line_methods.rb b/lib/rubocop/cop/style/single_line_methods.rb index 90650cac72f..5cf2611978b 100644 --- a/lib/rubocop/cop/style/single_line_methods.rb +++ b/lib/rubocop/cop/style/single_line_methods.rb @@ -73,7 +73,7 @@ def each_part(body) def move_comment(node, corrector) LineBreakCorrector.move_comment( - eol_comment: end_of_line_comment(node.source_range.line), + eol_comment: processed_source.comment_at_line(node.source_range.line), node: node, corrector: corrector ) end diff --git a/lib/rubocop/cop/util.rb b/lib/rubocop/cop/util.rb index 6b1b6ffb9bc..d6f1cc62ea4 100644 --- a/lib/rubocop/cop/util.rb +++ b/lib/rubocop/cop/util.rb @@ -14,10 +14,12 @@ module Util module_function + # This is a bad API def comment_line?(line_source) /^\s*#/.match?(line_source) end + # @deprecated Use `ProcessedSource#line_with_comment?`, `contains_comment` or similar def comment_lines?(node) processed_source[line_range(node)].any? { |line| comment_line?(line) } end diff --git a/rubocop.gemspec b/rubocop.gemspec index c997a70af21..97c70e7e3bc 100644 --- a/rubocop.gemspec +++ b/rubocop.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 4.0') s.add_runtime_dependency('regexp_parser', '>= 1.7') s.add_runtime_dependency('rexml') - s.add_runtime_dependency('rubocop-ast', '>= 0.1.0', '< 1.0') + s.add_runtime_dependency('rubocop-ast', '>= 0.3.0', '< 1.0') s.add_runtime_dependency('ruby-progressbar', '~> 1.7') s.add_runtime_dependency('unicode-display_width', '>= 1.4.0', '< 2.0') diff --git a/spec/rubocop/cop/style/empty_case_condition_spec.rb b/spec/rubocop/cop/style/empty_case_condition_spec.rb index 6661d079158..4e23413f517 100644 --- a/spec/rubocop/cop/style/empty_case_condition_spec.rb +++ b/spec/rubocop/cop/style/empty_case_condition_spec.rb @@ -49,33 +49,40 @@ context 'with multiple when branches and an `else` with code comments' do let(:source) do <<~RUBY - case - ^^^^ Do not use empty `case` condition, instead use an `if` expression. - # condition a - # This is a multi-line comment - when 1 == 2 - foo - # condition b - when 1 == 1 - bar - # condition c - else - baz + def example + # Comment before everything + case # first comment + ^^^^ Do not use empty `case` condition, instead use an `if` expression. + # condition a + # This is a multi-line comment + when 1 == 2 + foo + # condition b + when 1 == 1 + bar + # condition c + else + baz + end end RUBY end let(:corrected_source) do <<~RUBY - # condition a - # This is a multi-line comment - if 1 == 2 - foo - # condition b - elsif 1 == 1 - bar - # condition c - else - baz + def example + # Comment before everything + # first comment + # condition a + # This is a multi-line comment + if 1 == 2 + foo + # condition b + elsif 1 == 1 + bar + # condition c + else + baz + end end RUBY end diff --git a/spec/rubocop/cop/style/safe_navigation_spec.rb b/spec/rubocop/cop/style/safe_navigation_spec.rb index 2e3f054cf8b..a0a0e436b4f 100644 --- a/spec/rubocop/cop/style/safe_navigation_spec.rb +++ b/spec/rubocop/cop/style/safe_navigation_spec.rb @@ -674,18 +674,19 @@ it 'does not lose comments within if expression' do expect_offense(<<~RUBY, variable: variable) - if %{variable} - ^^^^{variable} Use safe navigation (`&.`) instead [...] + if %{variable} # hello + ^^^^{variable}^^^^^^^^ Use safe navigation (`&.`) instead [...] # this is a comment # another comment %{variable}.bar - end + end # bye! RUBY expect_correction(<<~RUBY) + # hello # this is a comment # another comment - #{variable}&.bar + #{variable}&.bar # bye! RUBY end