Skip to content

Commit

Permalink
[Fix #4097] Add require English for special globals
Browse files Browse the repository at this point in the history
When replacing short global variables with descriptive aliases from English
stdlib module, ensure English module is required.

Adds a new configuration `RequireEnglish` (default `true`) for
`Style/SpecialGlobalVars` cop. When enabled, this will add a
`require 'English'` statement if not already present at the top of the
file.
  • Loading branch information
biinari authored and bbatsov committed Aug 3, 2021
1 parent 9498fee commit c3e717f
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 116 deletions.
1 change: 1 addition & 0 deletions changelog/fix_add_require_english_for_special_globals.md
@@ -0,0 +1 @@
* [#4097](https://github.com/rubocop/rubocop/issues/4097): Add require English for special globals. ([@biinari][])
1 change: 1 addition & 0 deletions config/default.yml
Expand Up @@ -4575,6 +4575,7 @@ Style/SpecialGlobalVars:
VersionAdded: '0.13'
VersionChanged: '0.36'
SafeAutoCorrect: false
RequireEnglish: true
EnforcedStyle: use_english_names
SupportedStyles:
- use_perl_names
Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop.rb
Expand Up @@ -111,6 +111,7 @@
require_relative 'rubocop/cop/mixin/preceding_following_alignment'
require_relative 'rubocop/cop/mixin/preferred_delimiters'
require_relative 'rubocop/cop/mixin/rational_literal'
require_relative 'rubocop/cop/mixin/require_library'
require_relative 'rubocop/cop/mixin/rescue_node'
require_relative 'rubocop/cop/mixin/safe_assignment'
require_relative 'rubocop/cop/mixin/space_after_punctuation'
Expand Down Expand Up @@ -144,6 +145,7 @@
require_relative 'rubocop/cop/correctors/parentheses_corrector'
require_relative 'rubocop/cop/correctors/percent_literal_corrector'
require_relative 'rubocop/cop/correctors/punctuation_corrector'
require_relative 'rubocop/cop/correctors/require_library_corrector'
require_relative 'rubocop/cop/correctors/space_corrector'
require_relative 'rubocop/cop/correctors/string_literal_corrector'
require_relative 'rubocop/cop/correctors/unused_arg_corrector'
Expand Down
23 changes: 23 additions & 0 deletions lib/rubocop/cop/correctors/require_library_corrector.rb
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module RuboCop
module Cop
# This class ensures a require statement is present for a standard library
# determined by the variable library_name
class RequireLibraryCorrector
extend RangeHelp

class << self
def correct(corrector, node, library_name)
node = node.parent while node.parent?
node = node.children.first if node.begin_type?
corrector.insert_before(node, require_statement(library_name))
end

def require_statement(library_name)
"require '#{library_name}'\n"
end
end
end
end
end
59 changes: 59 additions & 0 deletions lib/rubocop/cop/mixin/require_library.rb
@@ -0,0 +1,59 @@
# frozen_string_literal: true

module RuboCop
module Cop
# Ensure a require statement is present for a standard library determined
# by variable library_name
module RequireLibrary
extend NodePattern::Macros

def ensure_required(corrector, node, library_name)
node = node.parent while node.parent&.parent?

if node.parent&.begin_type?
return if @required_libs.include?(library_name)

remove_subsequent_requires(corrector, node, library_name)
end

RequireLibraryCorrector.correct(corrector, node, library_name)
end

def remove_subsequent_requires(corrector, node, library_name)
node.right_siblings.each do |sibling|
next unless require_library_name?(sibling, library_name)

range = range_by_whole_lines(sibling.source_range, include_final_newline: true)
corrector.remove(range)
end
end

def on_send(node)
return if node.parent&.parent?

name = require_any_library?(node)
return if name.nil?

@required_libs.add(name)
end

private

def on_new_investigation
# Holds the required files at top-level
@required_libs = Set.new
super
end

# @!method require_any_library?(node)
def_node_matcher :require_any_library?, <<~PATTERN
(send {(const {nil? cbase} :Kernel) nil?} :require (str $_))
PATTERN

# @!method require_library_name?(node, library_name)
def_node_matcher :require_library_name?, <<~PATTERN
(send {(const {nil? cbase} :Kernel) nil?} :require (str %1))
PATTERN
end
end
end
21 changes: 21 additions & 0 deletions lib/rubocop/cop/style/special_global_vars.rb
Expand Up @@ -5,9 +5,14 @@ module Cop
module Style
#
# This cop looks for uses of Perl-style global variables.
# Correcting to global variables in the 'English' library
# will add a require statement to the top of the file if
# enabled by RequireEnglish config.
#
# @example EnforcedStyle: use_english_names (default)
# # good
# require 'English' # or this could be in another file.
#
# puts $LOAD_PATH
# puts $LOADED_FEATURES
# puts $PROGRAM_NAME
Expand Down Expand Up @@ -50,6 +55,8 @@ module Style
#
class SpecialGlobalVars < Base
include ConfigurableEnforcedStyle
include RangeHelp
include RequireLibrary
extend AutoCorrector

MSG_BOTH = 'Prefer `%<prefer>s` from the stdlib \'English\' ' \
Expand Down Expand Up @@ -90,6 +97,8 @@ class SpecialGlobalVars < Base
# Anything *not* in this set is provided by the English library.
NON_ENGLISH_VARS = Set.new(%i[$LOAD_PATH $LOADED_FEATURES $PROGRAM_NAME ARGV]).freeze

LIBRARY_NAME = 'English'

def on_gvar(node)
global_var, = *node

Expand Down Expand Up @@ -117,6 +126,8 @@ def message(global_var)
def autocorrect(corrector, node, global_var)
node = node.parent while node.parent&.begin_type? && node.parent.children.one?

ensure_required(corrector, node, LIBRARY_NAME) if should_require_english?(global_var)

corrector.replace(node, replacement(node, global_var))
end

Expand Down Expand Up @@ -172,6 +183,16 @@ def english_name_replacement(preferred_name, node)

"{#{preferred_name}}"
end

def add_require_english?
cop_config['RequireEnglish']
end

def should_require_english?(global_var)
style == :use_english_names &&
add_require_english? &&
!NON_ENGLISH_VARS.include?(preferred_names(global_var).first)
end
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions spec/rubocop/cli/auto_gen_config_spec.rb
Expand Up @@ -1140,7 +1140,7 @@ def function(arg1, arg2, arg3, arg4, arg5, arg6, arg7)
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# Configuration parameters: RequireEnglish, EnforcedStyle.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
Enabled: false
Expand All @@ -1165,7 +1165,7 @@ def function(arg1, arg2, arg3, arg4, arg5, arg6, arg7)
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# Configuration parameters: RequireEnglish, EnforcedStyle.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
Exclude:
Expand Down
2 changes: 2 additions & 0 deletions spec/rubocop/cli/options_spec.rb
Expand Up @@ -1800,6 +1800,7 @@ def f
1 file inspected, 1 offense detected, 1 offense corrected
====================
require 'English'
p $INPUT_RECORD_SEPARATOR
RESULT
ensure
Expand All @@ -1823,6 +1824,7 @@ def f
====================
RESULT
expect($stdout.string).to eq(<<~RESULT.chomp)
require 'English'
p $INPUT_RECORD_SEPARATOR
RESULT
ensure
Expand Down

0 comments on commit c3e717f

Please sign in to comment.