rubocop 1.36.0 → 1.39.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +31 -3
  4. data/lib/rubocop/arguments_env.rb +17 -0
  5. data/lib/rubocop/arguments_file.rb +17 -0
  6. data/lib/rubocop/cli/command/execute_runner.rb +7 -7
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +8 -1
  8. data/lib/rubocop/comment_config.rb +36 -1
  9. data/lib/rubocop/cop/commissioner.rb +3 -1
  10. data/lib/rubocop/cop/generator.rb +1 -2
  11. data/lib/rubocop/cop/internal_affairs/create_empty_file.rb +37 -0
  12. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +111 -0
  13. data/lib/rubocop/cop/internal_affairs.rb +2 -0
  14. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -0
  15. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  16. data/lib/rubocop/cop/layout/indentation_width.rb +1 -1
  17. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +29 -8
  18. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -1
  19. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +13 -9
  20. data/lib/rubocop/cop/layout/space_inside_array_percent_literal.rb +3 -0
  21. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +30 -3
  22. data/lib/rubocop/cop/layout/space_inside_percent_literal_delimiters.rb +34 -0
  23. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +6 -2
  24. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  25. data/lib/rubocop/cop/lint/duplicate_magic_comment.rb +73 -0
  26. data/lib/rubocop/cop/lint/duplicate_methods.rb +28 -9
  27. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +25 -6
  28. data/lib/rubocop/cop/lint/empty_class.rb +3 -1
  29. data/lib/rubocop/cop/lint/empty_conditional_body.rb +20 -8
  30. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  31. data/lib/rubocop/cop/lint/nested_method_definition.rb +50 -1
  32. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  33. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +4 -5
  34. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +1 -1
  35. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +23 -1
  36. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +7 -0
  37. data/lib/rubocop/cop/lint/redundant_require_statement.rb +38 -10
  38. data/lib/rubocop/cop/lint/require_parentheses.rb +1 -1
  39. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +3 -2
  40. data/lib/rubocop/cop/lint/shadowed_exception.rb +0 -10
  41. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +3 -0
  42. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  43. data/lib/rubocop/cop/lint/unreachable_loop.rb +1 -1
  44. data/lib/rubocop/cop/lint/unused_method_argument.rb +4 -0
  45. data/lib/rubocop/cop/metrics/abc_size.rb +1 -1
  46. data/lib/rubocop/cop/mixin/comments_help.rb +12 -0
  47. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +4 -0
  48. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +6 -3
  49. data/lib/rubocop/cop/mixin/range_help.rb +23 -0
  50. data/lib/rubocop/cop/mixin/rescue_node.rb +3 -1
  51. data/lib/rubocop/cop/mixin/surrounding_space.rb +10 -8
  52. data/lib/rubocop/cop/mixin/visibility_help.rb +40 -5
  53. data/lib/rubocop/cop/naming/inclusive_language.rb +1 -1
  54. data/lib/rubocop/cop/registry.rb +10 -4
  55. data/lib/rubocop/cop/style/access_modifier_declarations.rb +5 -7
  56. data/lib/rubocop/cop/style/accessor_grouping.rb +7 -3
  57. data/lib/rubocop/cop/style/block_delimiters.rb +2 -2
  58. data/lib/rubocop/cop/style/character_literal.rb +1 -1
  59. data/lib/rubocop/cop/style/class_equality_comparison.rb +8 -6
  60. data/lib/rubocop/cop/style/collection_compact.rb +12 -3
  61. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  62. data/lib/rubocop/cop/style/endless_method.rb +1 -1
  63. data/lib/rubocop/cop/style/explicit_block_argument.rb +4 -0
  64. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  65. data/lib/rubocop/cop/style/guard_clause.rb +62 -21
  66. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +1 -0
  67. data/lib/rubocop/cop/style/hash_each_methods.rb +32 -10
  68. data/lib/rubocop/cop/style/hash_except.rb +4 -0
  69. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  70. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +25 -2
  71. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +13 -2
  72. data/lib/rubocop/cop/style/module_function.rb +28 -6
  73. data/lib/rubocop/cop/style/negated_if_else_condition.rb +7 -1
  74. data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
  75. data/lib/rubocop/cop/style/object_then.rb +3 -0
  76. data/lib/rubocop/cop/style/operator_method_call.rb +53 -0
  77. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  78. data/lib/rubocop/cop/style/redundant_begin.rb +1 -0
  79. data/lib/rubocop/cop/style/redundant_condition.rb +5 -2
  80. data/lib/rubocop/cop/style/redundant_each.rb +116 -0
  81. data/lib/rubocop/cop/style/redundant_initialize.rb +3 -1
  82. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +8 -1
  83. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +12 -3
  84. data/lib/rubocop/cop/style/redundant_string_escape.rb +181 -0
  85. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -1
  86. data/lib/rubocop/cop/style/static_class.rb +32 -1
  87. data/lib/rubocop/cop/style/symbol_array.rb +2 -0
  88. data/lib/rubocop/cop/style/symbol_proc.rb +1 -1
  89. data/lib/rubocop/cop/style/word_array.rb +2 -0
  90. data/lib/rubocop/cop/team.rb +3 -4
  91. data/lib/rubocop/cop/util.rb +1 -1
  92. data/lib/rubocop/cop/variable_force/variable_table.rb +1 -1
  93. data/lib/rubocop/cops_documentation_generator.rb +2 -1
  94. data/lib/rubocop/ext/processed_source.rb +2 -0
  95. data/lib/rubocop/formatter/disabled_config_formatter.rb +8 -2
  96. data/lib/rubocop/formatter/offense_count_formatter.rb +8 -5
  97. data/lib/rubocop/formatter/worst_offenders_formatter.rb +6 -3
  98. data/lib/rubocop/options.rb +19 -15
  99. data/lib/rubocop/rspec/cop_helper.rb +21 -1
  100. data/lib/rubocop/rspec/shared_contexts.rb +14 -1
  101. data/lib/rubocop/runner.rb +15 -11
  102. data/lib/rubocop/server/cache.rb +5 -1
  103. data/lib/rubocop/server/cli.rb +9 -2
  104. data/lib/rubocop/server/client_command/exec.rb +5 -0
  105. data/lib/rubocop/server/core.rb +18 -1
  106. data/lib/rubocop/server/socket_reader.rb +5 -1
  107. data/lib/rubocop/server.rb +1 -1
  108. data/lib/rubocop/version.rb +8 -3
  109. data/lib/rubocop.rb +4 -0
  110. metadata +13 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bc2d4bd29fd7e81f4d7118fef9f55bcd2f086fd20ce383d85c2a1b7b26ea4e9
4
- data.tar.gz: 9cb3cef8736b03dd0834b94fefe4415a301d78d842df184a1a1d290973376070
3
+ metadata.gz: 881807be0f0f6eea20bd13bd8f08fdb158a5b155c8d3a22fd748683a68c1fef1
4
+ data.tar.gz: 2a8e7d4b02f1ba468e02a4c20f8e3b830ce2a1d8ff0210f86aaeafab597c8c88
5
5
  SHA512:
6
- metadata.gz: abeba580bae46458f6427c3d21c6532e95ff88e7d981d196f5013fee29c8f911146f229daa8e6f92d2f28bb14790edc46373a4366f9881700df18a7f5a1352fa
7
- data.tar.gz: 4bee5fc42db940e95cd6e3fba83680e6d7fd573a8ed3b7c65136a48d5f3b67ba8d299ecdd30a422afb95961d84c9b0452e3d1b6c1952e0edee1cea97ca704952
6
+ metadata.gz: 11ea36bec754b07912e06b5481f964d10a6dbf5bfd56f99d8a8842ca46d56ae03a342a283d843336f977760c28a697ee995aa98268254a55b1218fa57a9fb5c7
7
+ data.tar.gz: b5096cb4589d9bafdab0b6ab409ea9e7841dc06b274aeb7f7d5a26e703cb216c2a428d695fb2a087637467f1cdd5ac005398c66ee3e7d28d49cb4b203a87667d
data/README.md CHANGED
@@ -53,7 +53,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
53
53
  in your `Gemfile`:
54
54
 
55
55
  ```rb
56
- gem 'rubocop', '~> 1.36', require: false
56
+ gem 'rubocop', '~> 1.39', require: false
57
57
  ```
58
58
 
59
59
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -1707,6 +1707,11 @@ Lint/DuplicateHashKey:
1707
1707
  VersionAdded: '0.34'
1708
1708
  VersionChanged: '0.77'
1709
1709
 
1710
+ Lint/DuplicateMagicComment:
1711
+ Description: 'Check for duplicated magic comments.'
1712
+ Enabled: pending
1713
+ VersionAdded: '1.37'
1714
+
1710
1715
  Lint/DuplicateMethods:
1711
1716
  Description: 'Check for duplicate method definitions.'
1712
1717
  Enabled: true
@@ -1957,6 +1962,8 @@ Lint/NestedMethodDefinition:
1957
1962
  Description: 'Do not use nested method definitions.'
1958
1963
  StyleGuide: '#no-nested-methods'
1959
1964
  Enabled: true
1965
+ AllowedMethods: []
1966
+ AllowedPatterns: []
1960
1967
  VersionAdded: '0.32'
1961
1968
 
1962
1969
  Lint/NestedPercentLiteral:
@@ -2021,7 +2028,9 @@ Lint/OrAssignmentToConstant:
2021
2028
  Lint/OrderedMagicComments:
2022
2029
  Description: 'Checks the proper ordering of magic comments and whether a magic comment is not placed before a shebang.'
2023
2030
  Enabled: true
2031
+ SafeAutoCorrect: false
2024
2032
  VersionAdded: '0.53'
2033
+ VersionChanged: '1.37'
2025
2034
 
2026
2035
  Lint/OutOfRangeRegexpRef:
2027
2036
  Description: 'Checks for out of range reference for Regexp because it always returns nil.'
@@ -2799,6 +2808,7 @@ Naming/MethodParameterName:
2799
2808
  - by
2800
2809
  - db
2801
2810
  - id
2811
+ - if
2802
2812
  - in
2803
2813
  - io
2804
2814
  - ip
@@ -3439,7 +3449,7 @@ Style/Copyright:
3439
3449
 
3440
3450
  Style/DateTime:
3441
3451
  Description: 'Use Time over DateTime.'
3442
- StyleGuide: '#date--time'
3452
+ StyleGuide: '#date-time'
3443
3453
  Enabled: false
3444
3454
  VersionAdded: '0.51'
3445
3455
  VersionChanged: '0.92'
@@ -3810,8 +3820,9 @@ Style/HashExcept:
3810
3820
  Checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
3811
3821
  that can be replaced with `Hash#except` method.
3812
3822
  Enabled: pending
3823
+ Safe: false
3813
3824
  VersionAdded: '1.7'
3814
- VersionChanged: '1.31'
3825
+ VersionChanged: '1.39'
3815
3826
 
3816
3827
  Style/HashLikeCase:
3817
3828
  Description: >-
@@ -3850,7 +3861,7 @@ Style/HashSyntax:
3850
3861
  - never
3851
3862
  # accepts both shorthand and explicit use of hash literal value.
3852
3863
  - either
3853
- # like "always", but will avoid mixing styles in a single hash
3864
+ # like "either", but will avoid mixing styles in a single hash
3854
3865
  - consistent
3855
3866
  # Force hashes that have a symbol value to use hash rockets
3856
3867
  UseHashRocketsWithSymbolValues: false
@@ -4498,6 +4509,12 @@ Style/OpenStructUse:
4498
4509
  Enabled: pending
4499
4510
  VersionAdded: '1.23'
4500
4511
 
4512
+ Style/OperatorMethodCall:
4513
+ Description: 'Checks for redundant dot before operator method call.'
4514
+ StyleGuide: '#operator-method-call'
4515
+ Enabled: pending
4516
+ VersionAdded: '1.37'
4517
+
4501
4518
  Style/OptionHash:
4502
4519
  Description: "Don't use option hashes when you can use keyword arguments."
4503
4520
  Enabled: false
@@ -4681,6 +4698,12 @@ Style/RedundantConditional:
4681
4698
  Enabled: true
4682
4699
  VersionAdded: '0.50'
4683
4700
 
4701
+ Style/RedundantEach:
4702
+ Description: 'Checks for redundant `each`.'
4703
+ Enabled: pending
4704
+ Safe: false
4705
+ VersionAdded: '1.38'
4706
+
4684
4707
  Style/RedundantException:
4685
4708
  Description: "Checks for an obsolete RuntimeException argument in raise/fail."
4686
4709
  StyleGuide: '#no-explicit-runtimeerror'
@@ -4792,6 +4815,11 @@ Style/RedundantSortBy:
4792
4815
  Enabled: true
4793
4816
  VersionAdded: '0.36'
4794
4817
 
4818
+ Style/RedundantStringEscape:
4819
+ Description: 'Checks for redundant escapes in string literals.'
4820
+ Enabled: pending
4821
+ VersionAdded: '1.37'
4822
+
4795
4823
  Style/RegexpLiteral:
4796
4824
  Description: 'Use / or %r around regular expressions.'
4797
4825
  StyleGuide: '#percent-r'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # This is a class that reads optional command line arguments to rubocop from environment variable.
5
+ # @api private
6
+ class ArgumentsEnv
7
+ def self.read_as_arguments
8
+ if (arguments = ENV.fetch('RUBOCOP_OPTS', '')).empty?
9
+ []
10
+ else
11
+ require 'shellwords'
12
+
13
+ Shellwords.split(arguments)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ # This is a class that reads optional command line arguments to rubocop from .rubocop file.
5
+ # @api private
6
+ class ArgumentsFile
7
+ def self.read_as_arguments
8
+ if File.exist?('.rubocop') && !File.directory?('.rubocop')
9
+ require 'shellwords'
10
+
11
+ File.read('.rubocop').shellsplit
12
+ else
13
+ []
14
+ end
15
+ end
16
+ end
17
+ end
@@ -41,13 +41,13 @@ module RuboCop
41
41
 
42
42
  def with_redirect
43
43
  if @options[:stderr]
44
- orig_stdout = $stdout.dup
45
- $stdout.reopen($stderr)
46
-
47
- result = yield
48
-
49
- $stdout.reopen(orig_stdout)
50
- result
44
+ orig_stdout = $stdout
45
+ begin
46
+ $stdout = $stderr
47
+ yield
48
+ ensure
49
+ $stdout = orig_stdout
50
+ end
51
51
  else
52
52
  yield
53
53
  end
@@ -73,7 +73,14 @@ module RuboCop
73
73
  def all_extensions
74
74
  return [] unless lockfile.dependencies.any?
75
75
 
76
- extensions = @config_store.for_pwd.for_all_cops['SuggestExtensions'] || {}
76
+ extensions = @config_store.for_pwd.for_all_cops['SuggestExtensions']
77
+ case extensions
78
+ when true
79
+ extensions = ConfigLoader.default_configuration.for_all_cops['SuggestExtensions']
80
+ when false, nil
81
+ extensions = {}
82
+ end
83
+
77
84
  extensions.select { |_, v| (Array(v) & dependent_gems).any? }.keys
78
85
  end
79
86
 
@@ -4,10 +4,31 @@ module RuboCop
4
4
  # This class parses the special `rubocop:disable` comments in a source
5
5
  # and provides a way to check if each cop is enabled at arbitrary line.
6
6
  class CommentConfig
7
+ extend Forwardable
8
+
9
+ CONFIG_DISABLED_LINE_RANGE_MIN = -Float::INFINITY
10
+
11
+ # This class provides an API compatible with RuboCop::DirectiveComment
12
+ # to be used for cops that are disabled in the config file
13
+ class ConfigDisabledCopDirectiveComment
14
+ attr_reader :text, :loc, :line_number
15
+
16
+ Loc = Struct.new(:expression)
17
+ Expression = Struct.new(:line)
18
+
19
+ def initialize(cop_name)
20
+ @text = "# rubocop:disable #{cop_name}"
21
+ @line_number = CONFIG_DISABLED_LINE_RANGE_MIN
22
+ @loc = Loc.new(Expression.new(CONFIG_DISABLED_LINE_RANGE_MIN))
23
+ end
24
+ end
25
+
7
26
  CopAnalysis = Struct.new(:line_ranges, :start_line_number)
8
27
 
9
28
  attr_reader :processed_source
10
29
 
30
+ def_delegators :@processed_source, :config, :registry
31
+
11
32
  def initialize(processed_source)
12
33
  @processed_source = processed_source
13
34
  end
@@ -25,7 +46,11 @@ module RuboCop
25
46
  end
26
47
 
27
48
  def extra_enabled_comments
28
- extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: Hash.new(0))
49
+ disable_count = Hash.new(0)
50
+ registry.disabled(config).each do |cop|
51
+ disable_count[cop.cop_name] += 1
52
+ end
53
+ extra_enabled_comments_with_names(extras: Hash.new { |h, k| h[k] = [] }, names: disable_count)
29
54
  end
30
55
 
31
56
  def comment_only_line?(line_number)
@@ -50,6 +75,7 @@ module RuboCop
50
75
 
51
76
  def analyze # rubocop:todo Metrics/AbcSize
52
77
  analyses = Hash.new { |hash, key| hash[key] = CopAnalysis.new([], nil) }
78
+ inject_disabled_cops_directives(analyses)
53
79
 
54
80
  each_directive do |directive|
55
81
  directive.cop_names.each do |cop_name|
@@ -64,6 +90,15 @@ module RuboCop
64
90
  end
65
91
  end
66
92
 
93
+ def inject_disabled_cops_directives(analyses)
94
+ registry.disabled(config).each do |cop|
95
+ analyses[cop.cop_name] = analyze_cop(
96
+ analyses[cop.cop_name],
97
+ DirectiveComment.new(ConfigDisabledCopDirectiveComment.new(cop.cop_name))
98
+ )
99
+ end
100
+ end
101
+
67
102
  def analyze_cop(analysis, directive)
68
103
  # Disabling cops after comments like `#=SomeDslDirective` does not related to single line
69
104
  if !comment_only_line?(directive.line_number) || directive.single_line?
@@ -159,9 +159,11 @@ module RuboCop
159
159
  def with_cop_error_handling(cop, node = nil)
160
160
  yield
161
161
  rescue StandardError => e
162
- raise e if @options[:raise_error]
162
+ raise e if @options[:raise_error] # For internal testing
163
163
 
164
164
  err = ErrorWithAnalyzedFileLocation.new(cause: e, node: node, cop: cop)
165
+ raise err if @options[:raise_cop_error] # From user-input option
166
+
165
167
  @errors << err
166
168
  end
167
169
  end
@@ -206,9 +206,8 @@ module RuboCop
206
206
  end
207
207
 
208
208
  def snake_case(camel_case_string)
209
- return 'rspec' if camel_case_string == 'RSpec'
210
-
211
209
  camel_case_string
210
+ .gsub('RSpec', 'Rspec')
212
211
  .gsub(%r{([^A-Z/])([A-Z]+)}, '\1_\2')
213
212
  .gsub(%r{([A-Z])([A-Z][^A-Z\d/]+)}, '\1_\2')
214
213
  .downcase
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Checks for uses of `create_file` with empty string second argument.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # create_file(path, '')
12
+ #
13
+ # # good
14
+ # create_empty_file(path)
15
+ #
16
+ class CreateEmptyFile < Base
17
+ extend AutoCorrector
18
+
19
+ MSG = 'Use `%<replacement>s`.'
20
+ RESTRICT_ON_SEND = %i[create_file].freeze
21
+
22
+ def on_send(node)
23
+ return if node.receiver
24
+ return unless (argument = node.arguments[1])
25
+ return unless argument.str_type? && argument.value.empty?
26
+
27
+ replacement = "create_empty_file(#{node.first_argument.source})"
28
+ message = format(MSG, replacement: replacement)
29
+
30
+ add_offense(node, message: message) do |corrector|
31
+ corrector.replace(node, replacement)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Use `RUBY` for heredoc delimiter of example Ruby code.
7
+ #
8
+ # Some editors may apply better syntax highlighting by using appropriate language names for
9
+ # the delimiter.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # expect_offense(<<~CODE)
14
+ # example_ruby_code
15
+ # CODE
16
+ #
17
+ # # good
18
+ # expect_offense(<<~RUBY)
19
+ # example_ruby_code
20
+ # RUBY
21
+ class ExampleHeredocDelimiter < Base
22
+ extend AutoCorrector
23
+
24
+ EXPECTED_HEREDOC_DELIMITER = 'RUBY'
25
+
26
+ MSG = 'Use `RUBY` for heredoc delimiter of example Ruby code.'
27
+
28
+ RESTRICT_ON_SEND = %i[
29
+ expect_correction
30
+ expect_no_corrections
31
+ expect_no_offenses
32
+ expect_offense
33
+ ].freeze
34
+
35
+ # @param node [RuboCop::AST::SendNode]
36
+ # @return [void]
37
+ def on_send(node)
38
+ heredoc_node = heredoc_node_from(node)
39
+ return unless heredoc_node
40
+ return if expected_heredoc_delimiter?(heredoc_node)
41
+ return if expected_heredoc_delimiter_in_body?(heredoc_node)
42
+
43
+ add_offense(heredoc_node) do |corrector|
44
+ autocorrect(corrector, heredoc_node)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # @param corrector [RuboCop::Cop::Corrector]
51
+ # @param node [RuboCop::AST::StrNode]
52
+ # @return [void]
53
+ def autocorrect(corrector, node)
54
+ [
55
+ heredoc_openning_delimiter_range_from(node),
56
+ heredoc_closing_delimiter_range_from(node)
57
+ ].each do |range|
58
+ corrector.replace(range, EXPECTED_HEREDOC_DELIMITER)
59
+ end
60
+ end
61
+
62
+ # @param node [RuboCop::AST::StrNode]
63
+ # @return [Boolean]
64
+ def expected_heredoc_delimiter_in_body?(node)
65
+ node.location.heredoc_body.source.lines.any? do |line|
66
+ line.strip == EXPECTED_HEREDOC_DELIMITER
67
+ end
68
+ end
69
+
70
+ # @param node [RuboCop::AST::StrNode]
71
+ # @return [Boolean]
72
+ def expected_heredoc_delimiter?(node)
73
+ heredoc_delimiter_string_from(node) == EXPECTED_HEREDOC_DELIMITER
74
+ end
75
+
76
+ # @param node [RuboCop::AST::SendNode]
77
+ # @return [RuboCop::AST::StrNode, nil]
78
+ def heredoc_node_from(node)
79
+ return unless node.first_argument.respond_to?(:heredoc?)
80
+ return unless node.first_argument.heredoc?
81
+
82
+ node.first_argument
83
+ end
84
+
85
+ # @param node [RuboCop::AST::StrNode]
86
+ # @return [String]
87
+ def heredoc_delimiter_string_from(node)
88
+ node.source[Heredoc::OPENING_DELIMITER, 2]
89
+ end
90
+
91
+ # @param node [RuboCop::AST::StrNode]
92
+ # @return [Parser::Source::Range]
93
+ def heredoc_openning_delimiter_range_from(node)
94
+ match_data = node.source.match(Heredoc::OPENING_DELIMITER)
95
+ node.location.expression.begin.adjust(
96
+ begin_pos: match_data.begin(2),
97
+ end_pos: match_data.end(2)
98
+ )
99
+ end
100
+
101
+ # @param node [RuboCop::AST::StrNode]
102
+ # @return [Parser::Source::Range]
103
+ def heredoc_closing_delimiter_range_from(node)
104
+ node.location.heredoc_end.end.adjust(
105
+ begin_pos: -heredoc_delimiter_string_from(node).length
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'internal_affairs/cop_description'
4
+ require_relative 'internal_affairs/create_empty_file'
4
5
  require_relative 'internal_affairs/empty_line_between_expect_offense_and_correction'
5
6
  require_relative 'internal_affairs/example_description'
7
+ require_relative 'internal_affairs/example_heredoc_delimiter'
6
8
  require_relative 'internal_affairs/inherit_deprecated_cop_class'
7
9
  require_relative 'internal_affairs/location_line_equality_comparison'
8
10
  require_relative 'internal_affairs/method_name_end_with'
@@ -162,6 +162,7 @@ module RuboCop
162
162
  check_alignment([node.first_argument], indent)
163
163
  end
164
164
  alias on_csend on_send
165
+ alias on_super on_send
165
166
 
166
167
  private
167
168
 
@@ -90,7 +90,7 @@ module RuboCop
90
90
  # which lines start inside a string literal?
91
91
  return [] if ast.nil?
92
92
 
93
- ast.each_node(:str, :dstr).each_with_object(Set.new) do |str, ranges|
93
+ ast.each_node(:str, :dstr).with_object(Set.new) do |str, ranges|
94
94
  loc = str.location
95
95
 
96
96
  if str.heredoc?
@@ -343,7 +343,7 @@ module RuboCop
343
343
  end
344
344
 
345
345
  def skip_check?(base_loc, body_node)
346
- return true if ignored_line?(base_loc)
346
+ return true if allowed_line?(base_loc)
347
347
  return true unless body_node
348
348
 
349
349
  # Don't check if expression is on same line as "then" keyword, etc.
@@ -42,6 +42,14 @@ module RuboCop
42
42
  # ' long'
43
43
  class LineContinuationLeadingSpace < Base
44
44
  include RangeHelp
45
+ extend AutoCorrector
46
+
47
+ LINE_1_ENDING = /['"]\s*\\\n/.freeze
48
+ LINE_2_BEGINNING = /\A\s*['"]/.freeze
49
+ LEADING_STYLE_OFFENSE = /(?<trailing_spaces>\s+)(?<ending>#{LINE_1_ENDING})/.freeze
50
+ TRAILING_STYLE_OFFENSE = /(?<beginning>#{LINE_2_BEGINNING})(?<leading_spaces>\s+)/.freeze
51
+ private_constant :LINE_1_ENDING, :LINE_2_BEGINNING,
52
+ :LEADING_STYLE_OFFENSE, :TRAILING_STYLE_OFFENSE
45
53
 
46
54
  def on_dstr(node)
47
55
  end_of_first_line = node.loc.expression.begin_pos - node.loc.expression.column
@@ -52,9 +60,9 @@ module RuboCop
52
60
  next unless continuation?(raw_line_one)
53
61
 
54
62
  if enforced_style_leading?
55
- investigate_leading_style(raw_line_one, end_of_first_line)
63
+ investigate_leading_style(raw_line_one, raw_line_two, end_of_first_line)
56
64
  else
57
- investigate_trailing_style(raw_line_two, end_of_first_line)
65
+ investigate_trailing_style(raw_line_one, raw_line_two, end_of_first_line)
58
66
  end
59
67
  end
60
68
  end
@@ -65,24 +73,37 @@ module RuboCop
65
73
  processed_source.raw_source.lines[node.first_line - 1, line_range(node).size]
66
74
  end
67
75
 
68
- def investigate_leading_style(first_line, end_of_first_line)
69
- matches = first_line.match(/(?<trailing_spaces>\s+)(?<ending>['"]\s*\\\n)/)
76
+ def investigate_leading_style(first_line, second_line, end_of_first_line)
77
+ matches = first_line.match(LEADING_STYLE_OFFENSE)
70
78
  return if matches.nil?
71
79
 
72
- add_offense(leading_offense_range(end_of_first_line, matches))
80
+ offense_range = leading_offense_range(end_of_first_line, matches)
81
+ add_offense(offense_range) do |corrector|
82
+ insert_pos = end_of_first_line + second_line[LINE_2_BEGINNING].length
83
+ autocorrect(corrector, offense_range, insert_pos, matches[:trailing_spaces])
84
+ end
73
85
  end
74
86
 
75
- def investigate_trailing_style(second_line, end_of_first_line)
76
- matches = second_line.match(/\A(?<beginning>\s*['"])(?<leading_spaces>\s+)/)
87
+ def investigate_trailing_style(first_line, second_line, end_of_first_line)
88
+ matches = second_line.match(TRAILING_STYLE_OFFENSE)
77
89
  return if matches.nil?
78
90
 
79
- add_offense(trailing_offense_range(end_of_first_line, matches))
91
+ offense_range = trailing_offense_range(end_of_first_line, matches)
92
+ add_offense(offense_range) do |corrector|
93
+ insert_pos = end_of_first_line - first_line[LINE_1_ENDING].length
94
+ autocorrect(corrector, offense_range, insert_pos, matches[:leading_spaces])
95
+ end
80
96
  end
81
97
 
82
98
  def continuation?(line)
83
99
  line.end_with?("\\\n")
84
100
  end
85
101
 
102
+ def autocorrect(corrector, offense_range, insert_pos, spaces)
103
+ corrector.remove(offense_range)
104
+ corrector.replace(range_between(insert_pos, insert_pos), spaces)
105
+ end
106
+
86
107
  def leading_offense_range(end_of_first_line, matches)
87
108
  end_pos = end_of_first_line - matches[:ending].length
88
109
  begin_pos = end_pos - matches[:trailing_spaces].length
@@ -92,7 +92,7 @@ module RuboCop
92
92
  # which lines start inside a string literal?
93
93
  return [] if ast.nil?
94
94
 
95
- ast.each_node(:str, :dstr).each_with_object(Set.new) do |str, ranges|
95
+ ast.each_node(:str, :dstr).with_object(Set.new) do |str, ranges|
96
96
  loc = str.location
97
97
 
98
98
  if str.heredoc?
@@ -169,19 +169,22 @@ module RuboCop
169
169
 
170
170
  def qualifies_for_compact?(node, token, side: :right)
171
171
  if side == :right
172
- multi_dimensional_array?(node, token) && !next_to_bracket?(token)
172
+ multi_dimensional_array?(node, token) && token.space_before?
173
173
  else
174
- multi_dimensional_array?(node, token, side: :left) &&
175
- !next_to_bracket?(token, side: :left)
174
+ multi_dimensional_array?(node, token, side: :left) && token.space_after?
176
175
  end
177
176
  end
178
177
 
179
178
  def multi_dimensional_array?(node, token, side: :right)
180
- i = index_for(node, token)
179
+ offset = side == :right ? -1 : +1
180
+ i = index_for(node, token) + offset
181
+ # TODO: change this type check once
182
+ # https://github.com/rubocop/rubocop-ast/pull/240 is merged
183
+ i += offset while processed_source.tokens_within(node)[i].new_line?
181
184
  if side == :right
182
- processed_source.tokens_within(node)[i - 1].right_bracket?
185
+ processed_source.tokens_within(node)[i].right_bracket?
183
186
  else
184
- processed_source.tokens_within(node)[i + 1].left_array_bracket?
187
+ processed_source.tokens_within(node)[i].left_array_bracket?
185
188
  end
186
189
  end
187
190
 
@@ -200,12 +203,13 @@ module RuboCop
200
203
  end
201
204
 
202
205
  def compact_corrections(corrector, node, left, right)
203
- if qualifies_for_compact?(node, left, side: :left)
206
+ if multi_dimensional_array?(node, left, side: :left)
204
207
  compact(corrector, left, :right)
205
208
  elsif !left.space_after?
206
209
  corrector.insert_after(left.pos, ' ')
207
210
  end
208
- if qualifies_for_compact?(node, right)
211
+
212
+ if multi_dimensional_array?(node, right)
209
213
  compact(corrector, right, :left)
210
214
  elsif !right.space_before?
211
215
  corrector.insert_before(right.pos, ' ')
@@ -213,7 +217,7 @@ module RuboCop
213
217
  end
214
218
 
215
219
  def compact(corrector, bracket, side)
216
- range = side_space_range(range: bracket.pos, side: side)
220
+ range = side_space_range(range: bracket.pos, side: side, include_newlines: true)
217
221
  corrector.remove(range)
218
222
  end
219
223
  end
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # Checks for unnecessary additional spaces inside array percent literals
7
7
  # (i.e. %i/%w).
8
8
  #
9
+ # Note that blank percent literals (e.g. `%i( )`) are checked by
10
+ # `Layout/SpaceInsidePercentLiteralDelimiters`.
11
+ #
9
12
  # @example
10
13
  #
11
14
  # # bad