Skip to content

Commit

Permalink
More rubocop optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima authored and bbatsov committed Dec 19, 2022
1 parent 6c7bec0 commit 2dc325c
Show file tree
Hide file tree
Showing 22 changed files with 119 additions and 34 deletions.
26 changes: 24 additions & 2 deletions lib/rubocop/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Config
DEFAULT_RAILS_VERSION = 5.0
attr_reader :loaded_path

# rubocop:disable Metrics/AbcSize
def initialize(hash = {}, loaded_path = nil)
@loaded_path = loaded_path
@for_cop = Hash.new do |h, cop|
Expand All @@ -32,7 +33,11 @@ def initialize(hash = {}, loaded_path = nil)
end
@hash = hash
@validator = ConfigValidator.new(self)

@badge_config_cache = {}.compare_by_identity
@clusivity_config_exists_cache = {}
end
# rubocop:enable Metrics/AbcSize

def self.create(hash, path, check: true)
config = new(hash, path)
Expand Down Expand Up @@ -123,8 +128,25 @@ def for_cop(cop)
# @return [Config] for the given cop merged with that of its department (if any)
# Note: the 'Enabled' attribute is same as that returned by `for_cop`
def for_badge(badge)
cop_config = for_cop(badge.to_s)
fetch(badge.department_name) { return cop_config }.merge(cop_config)
@badge_config_cache[badge] ||= begin
department_config = self[badge.department_name]
cop_config = for_cop(badge.to_s)
if department_config
department_config.merge(cop_config)
else
cop_config
end
end
end

# @return [Boolean] whether config for this badge has 'Include' or 'Exclude' keys
# @api private
def clusivity_config_for_badge?(badge)
exists = @clusivity_config_exists_cache[badge.to_s]
return exists unless exists.nil?

cop_config = for_badge(badge)
@clusivity_config_exists_cache[badge.to_s] = cop_config['Include'] || cop_config['Exclude']
end

# @return [Config] for the given department name.
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop/cop/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ def active_support_extensions_enabled?
end

def relevant_file?(file)
return true unless @config.clusivity_config_for_badge?(self.class.badge)

file == RuboCop::AST::ProcessedSource::STRING_SOURCE_NAME ||
(file_name_matches_any?(file, 'Include', true) &&
!file_name_matches_any?(file, 'Exclude', false))
Expand Down
4 changes: 3 additions & 1 deletion lib/rubocop/cop/internal_affairs/cop_description.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ class CopDescription < Base
/^\s+# This cop (?<special>#{SPECIAL_WORDS.join('|')})?\s*(?<word>.+?) .*/.freeze
REPLACEMENT_REGEX = /^\s+# This cop (#{SPECIAL_WORDS.join('|')})?\s*(.+?) /.freeze

# rubocop:disable Metrics/CyclomaticComplexity
def on_class(node)
return unless (module_node = node.parent)
return unless (module_node = node.parent) && node.parent_class

description_beginning = first_comment_line(module_node)
return unless description_beginning
Expand All @@ -48,6 +49,7 @@ def on_class(node)
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity

private

Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop/cop/layout/empty_lines.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class EmptyLines < Base

def on_new_investigation
return if processed_source.tokens.empty?
# Quick check if we possibly have consecutive blank lines.
return unless processed_source.raw_source.include?("\n\n\n")

lines = Set.new
processed_source.each_token { |token| lines << token.line }
Expand Down
16 changes: 10 additions & 6 deletions lib/rubocop/cop/layout/extra_spacing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,16 @@ def ignored_range?(ast, start_pos)
def ignored_ranges(ast)
return [] unless ast

@ignored_ranges ||= on_node(:pair, ast).map do |pair|
next if pair.parent.single_line?

key, value = *pair
key.source_range.end_pos...value.source_range.begin_pos
end.compact
@ignored_ranges ||= begin
ranges = []
on_node(:pair, ast) do |pair|
next if pair.parent.single_line?

key, value = *pair
ranges << (key.source_range.end_pos...value.source_range.begin_pos)
end
ranges
end
end

def force_equal_sign_alignment?
Expand Down
4 changes: 3 additions & 1 deletion lib/rubocop/cop/layout/indentation_style.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ def string_literal_ranges(ast)
# which lines start inside a string literal?
return [] if ast.nil?

ast.each_node(:str, :dstr).with_object(Set.new) do |str, ranges|
ranges = Set.new
ast.each_node(:str, :dstr) do |str|
loc = str.location

if str.heredoc?
Expand All @@ -99,6 +100,7 @@ def string_literal_ranges(ast)
ranges << loc.expression
end
end
ranges
end

def message(_node)
Expand Down
5 changes: 5 additions & 0 deletions lib/rubocop/cop/layout/line_continuation_leading_space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ class LineContinuationLeadingSpace < Base
private_constant :LINE_1_ENDING, :LINE_2_BEGINNING,
:LEADING_STYLE_OFFENSE, :TRAILING_STYLE_OFFENSE

# rubocop:disable Metrics/AbcSize
def on_dstr(node)
# Quick check if we possibly have line continuations.
return unless node.source.include?('\\')

end_of_first_line = node.loc.expression.begin_pos - node.loc.expression.column

raw_lines(node).each_cons(2) do |raw_line_one, raw_line_two|
Expand All @@ -66,6 +70,7 @@ def on_dstr(node)
end
end
end
# rubocop:enable Metrics/AbcSize

private

Expand Down
4 changes: 3 additions & 1 deletion lib/rubocop/cop/layout/line_continuation_spacing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def string_literal_ranges(ast)
# which lines start inside a string literal?
return [] if ast.nil?

ast.each_node(:str, :dstr).with_object(Set.new) do |str, ranges|
ranges = Set.new
ast.each_node(:str, :dstr) do |str|
loc = str.location

if str.heredoc?
Expand All @@ -105,6 +106,7 @@ def string_literal_ranges(ast)
ranges << loc.expression
end
end
ranges
end

def comment_ranges(comments)
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop/cop/layout/line_length.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def on_potential_breakable_node(node)
alias on_def on_potential_breakable_node

def on_new_investigation
return unless processed_source.raw_source.include?(';')

check_for_breakable_semicolons(processed_source)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/rubocop/cop/layout/redundant_line_break.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ def configured_to_not_be_inspected?(node)
return true if other_cop_takes_precedence?(node)

!cop_config['InspectBlocks'] && (node.block_type? ||
node.each_descendant(:block).any?(&:multiline?))
any_descendant?(node, :block, &:multiline?))
end

def other_cop_takes_precedence?(node)
single_line_block_chain_enabled? && node.each_descendant(:block).any? do |block_node|
single_line_block_chain_enabled? && any_descendant?(node, :block) do |block_node|
block_node.parent.send_type? && block_node.parent.loc.dot && !block_node.multiline?
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/layout/trailing_empty_lines.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def offense_detected(buffer, wanted_blank_lines, blank_lines, whitespace_at_end)
def ends_in_end?(processed_source)
buffer = processed_source.buffer

return true if buffer.source.strip.start_with?('__END__')
return true if buffer.source.match?(/\s*__END__/)
return false if processed_source.tokens.empty?

extra = buffer.source[processed_source.tokens.last.end_pos..]
Expand Down
8 changes: 6 additions & 2 deletions lib/rubocop/cop/layout/trailing_whitespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,14 @@ def find_heredoc(line_number)
def extract_heredocs(ast)
return [] unless ast

ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
heredocs = []
ast.each_node(:str, :dstr, :xstr) do |node|
next unless node.heredoc?

body = node.location.heredoc_body
[node, body.first_line...body.last_line]
heredocs << [node, body.first_line...body.last_line]
end
heredocs
end

def offense_range(lineno, line)
Expand Down
4 changes: 2 additions & 2 deletions lib/rubocop/cop/lint/duplicate_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ def initialize(config = nil, options = nil)
def on_def(node)
# if a method definition is inside an if, it is very likely
# that a different definition is used depending on platform, etc.
return if node.ancestors.any?(&:if_type?)
return if node.each_ancestor.any?(&:if_type?)
return if possible_dsl?(node)

found_instance_method(node, node.method_name)
end

def on_defs(node)
return if node.ancestors.any?(&:if_type?)
return if node.each_ancestor.any?(&:if_type?)
return if possible_dsl?(node)

if node.receiver.const_type?
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/mixin/allowed_identifiers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def allowed_identifier?(name)
end

def allowed_identifiers
cop_config.fetch('AllowedIdentifiers', [])
cop_config.fetch('AllowedIdentifiers') { [] }
end
end
end
Expand Down
19 changes: 13 additions & 6 deletions lib/rubocop/cop/mixin/annotation_comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,25 @@ def bounds
def split_comment(comment)
# Sort keywords by reverse length so that if a keyword is in a phrase
# but also on its own, both will match properly.
keywords_regex = Regexp.new(
Regexp.union(keywords.sort_by { |w| -w.length }).source,
Regexp::IGNORECASE
)
regex = /^(# ?)(\b#{keywords_regex}\b)(\s*:)?(\s+)?(\S+)?/i

match = comment.text.match(regex)
return false unless match

match.captures
end

KEYWORDS_REGEX_CACHE = {} # rubocop:disable Layout/ClassStructure, Style/MutableConstant
private_constant :KEYWORDS_REGEX_CACHE

def regex
KEYWORDS_REGEX_CACHE[keywords] ||= begin
keywords_regex = Regexp.new(
Regexp.union(keywords.sort_by { |w| -w.length }).source,
Regexp::IGNORECASE
)
/^(# ?)(\b#{keywords_regex}\b)(\s*:)?(\s+)?(\S+)?/i
end
end

def keyword_appearance?
keyword && (colon || space)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/mixin/line_length_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def indentation_difference(line)
return 0 unless tab_indentation_width

index =
if line.start_with?(/[^\t]/)
if line.match?(/^[^\t]/)
0
else
line.index(/[^\t]/) || 0
Expand Down
8 changes: 5 additions & 3 deletions lib/rubocop/cop/mixin/method_complexity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ def check_complexity(node, method_name)
end

def complexity(body)
body.each_node(:lvasgn, *self.class::COUNTED_NODES).reduce(1) do |score, node|
score = 1
body.each_node(:lvasgn, *self.class::COUNTED_NODES) do |node|
if node.lvasgn_type?
reset_on_lvasgn(node)
next score
else
score += complexity_score_for(node)
end
score + complexity_score_for(node)
end
score
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop/cop/naming/class_and_module_camel_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ClassAndModuleCamelCase < Base
MSG = 'Use CamelCase for classes and modules.'

def on_class(node)
return unless node.loc.name.source.include?('_')

allowed = /#{cop_config['AllowedNames'].join('|')}/
name = node.loc.name.source.gsub(allowed, '')
return unless /_/.match?(name)
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/naming/inclusive_language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def create_multiple_word_message_for_file(words)

def scan_for_words(input)
masked_input = mask_input(input)
return [] unless masked_input.match?(@flagged_terms_regex)
return EMPTY_ARRAY unless masked_input.match?(@flagged_terms_regex)

masked_input.enum_for(:scan, @flagged_terms_regex).map do
match = Regexp.last_match
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop/cop/style/inverse_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class InverseMethods < Base
NEGATED_EQUALITY_METHODS = %i[!= !~].freeze
CAMEL_CASE = /[A-Z]+[a-z]+/.freeze

RESTRICT_ON_SEND = [:!].freeze

def self.autocorrect_incompatible_with
[Style::Not, Style::SymbolProc]
end
Expand Down
3 changes: 2 additions & 1 deletion lib/rubocop/cop/style/semicolon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ def self.autocorrect_incompatible_with
end

def on_new_investigation
return if processed_source.blank?
return if processed_source.blank? || !processed_source.raw_source.include?(';')

check_for_line_terminator_or_opener
end

def on_begin(node)
return if cop_config['AllowAsExpressionSeparator']
return unless node.source.include?(';')

exprs = node.children

Expand Down
30 changes: 27 additions & 3 deletions lib/rubocop/cop/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ def add_parentheses(node, corrector)
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength

def any_descendant?(node, *types)
if block_given?
node.each_descendant(*types) do |descendant|
return true if yield(descendant)
end
else
# Use a block version to avoid allocating enumerators.
node.each_descendant do # rubocop:disable Lint/UnreachableLoop
return true
end
end

false
end

def args_begin(node)
loc = node.loc
selector = if node.super_type? || node.yield_type?
Expand All @@ -71,14 +86,19 @@ def args_end(node)
def on_node(syms, sexp, excludes = [], &block)
return to_enum(:on_node, syms, sexp, excludes) unless block

yield sexp if Array(syms).include?(sexp.type)
return if Array(excludes).include?(sexp.type)
yield sexp if include_or_equal?(syms, sexp.type)
return if include_or_equal?(excludes, sexp.type)

sexp.each_child_node { |elem| on_node(syms, elem, excludes, &block) }
end

LINE_BEGINS_REGEX_CACHE = Hash.new do |hash, index|
hash[index] = /^\s{#{index}}\S/
end
private_constant :LINE_BEGINS_REGEX_CACHE

def begins_its_line?(range)
range.source_line.index(/\S/) == range.column
range.source_line.match?(LINE_BEGINS_REGEX_CACHE[range.column])
end

# Returns, for example, a bare `if` node if the given node is an `if`
Expand Down Expand Up @@ -165,6 +185,10 @@ def compatible_external_encoding_for?(src)
src = src.dup if RUBY_ENGINE == 'jruby'
src.force_encoding(Encoding.default_external).valid_encoding?
end

def include_or_equal?(source, target)
source == target || (source.is_a?(Array) && source.include?(target))
end
end
end
end

0 comments on commit 2dc325c

Please sign in to comment.