diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5a043b8bc..a5f357b1035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### New features * [#8451](https://github.com/rubocop-hq/rubocop/issues/8451): Add new `Style/RedundantSelfAssignment` cop. ([@fatkodima][]) +* [#8384](https://github.com/rubocop-hq/rubocop/issues/8384): Add new `Layout/EmptyLineAfterMultilineCondition` cop. ([@fatkodima][]) * [#8390](https://github.com/rubocop-hq/rubocop/pull/8390): Add new `Style/SoleNestedConditional` cop. ([@fatkodima][]) * [#8562](https://github.com/rubocop-hq/rubocop/pull/8562): Add new `Style/KeywordParametersOrder` cop. ([@fatkodima][]) * [#8486](https://github.com/rubocop-hq/rubocop/pull/8486): Add new `Style/CombinableLoops` cop. ([@fatkodima][]) @@ -15,11 +16,13 @@ * Add new `Lint/TrailingCommaInAttributeDeclaration` cop. ([@drenmi][]) * [#8578](https://github.com/rubocop-hq/rubocop/pull/8578): Add `:restore_registry` context and `stub_cop_class` helper class. ([@marcandre][]) * [#8579](https://github.com/rubocop-hq/rubocop/pull/8579): Add `Cop.documentation_url`. ([@marcandre][]) - +* [#8510](https://github.com/rubocop-hq/rubocop/pull/8510): Add `RegexpNode#each_capture` and `parsed_tree`. ([@marcandre][]) +* [#8365](https://github.com/rubocop-hq/rubocop/pull/8365): Cops defining `on_send` can be optimized by defining the constant `RESTRICT_ON_SEND` with a list of acceptable method names. ([@marcandre][]) ### Bug fixes * [#8508](https://github.com/rubocop-hq/rubocop/pull/8508): Fix a false positive for `Style/CaseLikeIf` when conditional contains comparison with a class. Mark `Style/CaseLikeIf` as not safe. ([@fatkodima][]) +* [#8618](https://github.com/rubocop-hq/rubocop/issues/8618): Fix an infinite loop error for `Layout/EmptyLineBetweenDefs`. ([@fatkodima][]) * [#8534](https://github.com/rubocop-hq/rubocop/issues/8534): Fix `Lint/BinaryOperatorWithIdenticalOperands` for binary operators used as unary operators. ([@marcandre][]) * [#8537](https://github.com/rubocop-hq/rubocop/pull/8537): Allow a trailing comment as a description comment for `Bundler/GemComment`. ([@pocke][]) * [#8507](https://github.com/rubocop-hq/rubocop/issues/8507): Fix `Style/RescueModifier` to handle parentheses around rescue modifiers. ([@dsavochkin][]) @@ -30,13 +33,18 @@ * [#7705](https://github.com/rubocop-hq/rubocop/issues/7705): Fix `Style/OneLineConditional` cop to handle if/then/elsif/then/else/end cases. Add `AlwaysCorrectToMultiline` config option to this cop to always convert offenses to the multi-line form (false by default). ([@Lykos][], [@dsavochkin][]) * [#8590](https://github.com/rubocop-hq/rubocop/issues/8590): Fix an error when auto-correcting encoding mismatch file. ([@koic][]) * [#8321](https://github.com/rubocop-hq/rubocop/issues/8321): Enable auto-correction for `Layout/{Def}EndAlignment`, `Lint/EmptyEnsure`, `Style/ClassAndModuleChildren`. ([@marcandre][]) +* [#8583](https://github.com/rubocop-hq/rubocop/issues/8583): Fix `Style/RedundantRegexpEscape` false positive for line continuations. ([@owst][]) +* [#8593](https://github.com/rubocop-hq/rubocop/issues/8593): Fix `Style/RedundantRegexpCharacterClass` false positive for interpolated multi-line expressions. ([@owst][]) +* [#8624](https://github.com/rubocop-hq/rubocop/pull/8624): Fix an error with the `Style/CaseLikeIf` cop where it does not properly handle overridden equality methods with no arguments. ([@Skipants][]) ### Changes +* [#8413](https://github.com/rubocop-hq/rubocop/issues/8413): Pending cops warning now contains snippet that can be directly copied into `.rubocop.yml` as well as a notice about `NewCops: enable` config option. ([@colszowka][]) * [#8362](https://github.com/rubocop-hq/rubocop/issues/8362): Add numbers of correctable offenses to summary. ([@nguyenquangminh0711][]) * [#8513](https://github.com/rubocop-hq/rubocop/pull/8513): Clarify the ruby warning mentioned in the `Lint/ShadowingOuterLocalVariable` documentation. ([@chocolateboy][]) * [#8517](https://github.com/rubocop-hq/rubocop/pull/8517): Make `Style/HashTransformKeys` and `Style/HashTransformValues` aware of `to_h` with block. ([@eugeneius][]) * [#8529](https://github.com/rubocop-hq/rubocop/pull/8529): Mark `Lint/FrozenStringLiteralComment` as `Safe`, but with unsafe auto-correction. ([@marcandre][]) +* [#8602](https://github.com/rubocop-hq/rubocop/pull/8602): Fix usage of `to_enum(:scan, regexp)` to work on TruffleRuby. ([@jaimerave][]) ## 0.89.1 (2020-08-10) @@ -4806,9 +4814,12 @@ [@knejad]: https://github.com/knejad [@iamravitejag]: https://github.com/iamravitejag [@volfgox]: https://github.com/volfgox +[@colszowka]: https://github.com/colszowka [@dsavochkin]: https://github.com/dmytro-savochkin [@sonalinavlakhe]: https://github.com/sonalinavlakhe [@wcmonty]: https://github.com/wcmonty [@nguyenquangminh0711]: https://github.com/nguyenquangminh0711 [@chocolateboy]: https://github.com/chocolateboy [@Lykos]: https://github.com/Lykos +[@jaimerave]: https://github.com/jaimerave +[@Skipants]: https://github.com/Skipants diff --git a/config/default.yml b/config/default.yml index e6b6b1f733a..71e4a20a5e6 100644 --- a/config/default.yml +++ b/config/default.yml @@ -435,6 +435,14 @@ Layout/EmptyLineAfterMagicComment: Enabled: true VersionAdded: '0.49' +Layout/EmptyLineAfterMultilineCondition: + Description: 'Enforces empty line after multiline condition.' + # This is disabled, because this style is not very common in practice. + Enabled: false + VersionAdded: '0.90' + Reference: + - https://github.com/airbnb/ruby#multiline-if-newline + Layout/EmptyLineBetweenDefs: Description: 'Use empty lines between defs.' StyleGuide: '#empty-lines-between-methods' diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 723124af31a..549891f6004 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -105,6 +105,7 @@ In the following section you find all available cops: * xref:cops_layout.adoc#layoutemptycomment[Layout/EmptyComment] * xref:cops_layout.adoc#layoutemptylineafterguardclause[Layout/EmptyLineAfterGuardClause] * xref:cops_layout.adoc#layoutemptylineaftermagiccomment[Layout/EmptyLineAfterMagicComment] +* xref:cops_layout.adoc#layoutemptylineaftermultilinecondition[Layout/EmptyLineAfterMultilineCondition] * xref:cops_layout.adoc#layoutemptylinebetweendefs[Layout/EmptyLineBetweenDefs] * xref:cops_layout.adoc#layoutemptylines[Layout/EmptyLines] * xref:cops_layout.adoc#layoutemptylinesaroundaccessmodifier[Layout/EmptyLinesAroundAccessModifier] diff --git a/docs/modules/ROOT/pages/cops_layout.adoc b/docs/modules/ROOT/pages/cops_layout.adoc index dda6e8b7582..1f7627a61c2 100644 --- a/docs/modules/ROOT/pages/cops_layout.adoc +++ b/docs/modules/ROOT/pages/cops_layout.adoc @@ -1244,6 +1244,74 @@ end * https://rubystyle.guide#separate-magic-comments-from-code +== Layout/EmptyLineAfterMultilineCondition + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Disabled +| Yes +| Yes +| 0.90 +| - +|=== + +This cop enforces empty line after multiline condition. + +=== Examples + +[source,ruby] +---- +# bad +if multiline && + condition + do_something +end + +# good +if multiline && + condition + + do_something +end + +# bad +case x +when foo, + bar + do_something +end + +# good +case x +when foo, + bar + + do_something +end + +# bad +begin + do_something +rescue FooError, + BarError + handle_error +end + +# good +begin + do_something +rescue FooError, + BarError + + handle_error +end +---- + +=== References + +* https://github.com/airbnb/ruby#multiline-if-newline + == Layout/EmptyLineBetweenDefs |=== diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index cc700e28f7a..44ff2ab2b4c 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -3630,6 +3630,7 @@ end # good def test return unless something + work end @@ -4971,9 +4972,19 @@ when reading code. [source,ruby] ---- +# bad a do b end.c + +# good +a { b }.c + +# good +foo = a do + b +end +foo.c ---- === References diff --git a/docs/modules/ROOT/pages/development.adoc b/docs/modules/ROOT/pages/development.adoc index 790d7291c99..fc103207bc7 100644 --- a/docs/modules/ROOT/pages/development.adoc +++ b/docs/modules/ROOT/pages/development.adoc @@ -210,6 +210,9 @@ def on_send(node) end ---- +The `on_send` callback is the most used and can be optimized by restricting the acceptable +method names with a constant `RESTRICT_ON_SEND`. + And the final cop code will look like something like this: [source,ruby] @@ -232,6 +235,8 @@ module RuboCop (send (send $(...) :empty?) :!) PATTERN + RESTRICT_ON_SEND = [:!].freeze # optimization: don't call `on_send` unless + # the method name is in this list def on_send(node) return unless not_empty_call?(node) diff --git a/lib/rubocop.rb b/lib/rubocop.rb index c458bd750e8..c3f0c03fb61 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -9,6 +9,7 @@ require 'unicode/display_width/no_string_ext' require 'rubocop-ast' require_relative 'rubocop/ast_aliases' +require_relative 'rubocop/ext/regexp_node' require_relative 'rubocop/version' @@ -167,6 +168,7 @@ require_relative 'rubocop/cop/layout/empty_comment' require_relative 'rubocop/cop/layout/empty_line_after_guard_clause' require_relative 'rubocop/cop/layout/empty_line_after_magic_comment' +require_relative 'rubocop/cop/layout/empty_line_after_multiline_condition' require_relative 'rubocop/cop/layout/empty_line_between_defs' require_relative 'rubocop/cop/layout/empty_lines_around_access_modifier' require_relative 'rubocop/cop/layout/empty_lines_around_arguments' diff --git a/lib/rubocop/cached_data.rb b/lib/rubocop/cached_data.rb index c6688ca7e2a..e7fec3412ca 100644 --- a/lib/rubocop/cached_data.rb +++ b/lib/rubocop/cached_data.rb @@ -4,6 +4,7 @@ module RuboCop # Converts RuboCop objects to and from the serialization format JSON. + # @api private class CachedData def initialize(filename) @filename = filename diff --git a/lib/rubocop/cli/command.rb b/lib/rubocop/cli/command.rb index 3cfe0f9dc50..775e929a2de 100644 --- a/lib/rubocop/cli/command.rb +++ b/lib/rubocop/cli/command.rb @@ -3,6 +3,7 @@ module RuboCop class CLI # Home of subcommands in the CLI. + # @api private module Command class << self # Find the command with a given name and run it in an environment. diff --git a/lib/rubocop/cli/command/auto_genenerate_config.rb b/lib/rubocop/cli/command/auto_genenerate_config.rb index 41c5641c8aa..5dd87a554f0 100644 --- a/lib/rubocop/cli/command/auto_genenerate_config.rb +++ b/lib/rubocop/cli/command/auto_genenerate_config.rb @@ -4,6 +4,7 @@ module RuboCop class CLI module Command # Generate a configuration file acting as a TODO list. + # @api private class AutoGenerateConfig < Base self.command_name = :auto_gen_config diff --git a/lib/rubocop/cli/command/base.rb b/lib/rubocop/cli/command/base.rb index b4fbb66b198..f35798a4889 100644 --- a/lib/rubocop/cli/command/base.rb +++ b/lib/rubocop/cli/command/base.rb @@ -4,6 +4,7 @@ module RuboCop class CLI module Command # A subcommand in the CLI. + # @api private class Base attr_reader :env diff --git a/lib/rubocop/cli/command/execute_runner.rb b/lib/rubocop/cli/command/execute_runner.rb index f4dff2ffd10..9895c1650a9 100644 --- a/lib/rubocop/cli/command/execute_runner.rb +++ b/lib/rubocop/cli/command/execute_runner.rb @@ -4,6 +4,7 @@ module RuboCop class CLI module Command # Run all the selected cops and report the result. + # @api private class ExecuteRunner < Base include Formatter::TextUtil diff --git a/lib/rubocop/cli/command/init_dotfile.rb b/lib/rubocop/cli/command/init_dotfile.rb index d51047b21e7..36c34f5a654 100644 --- a/lib/rubocop/cli/command/init_dotfile.rb +++ b/lib/rubocop/cli/command/init_dotfile.rb @@ -4,6 +4,7 @@ module RuboCop class CLI module Command # Generate a .rubocop.yml file in the current directory. + # @api private class InitDotfile < Base DOTFILE = ConfigLoader::DOTFILE diff --git a/lib/rubocop/cli/command/show_cops.rb b/lib/rubocop/cli/command/show_cops.rb index 11343190417..e99069b3ea8 100644 --- a/lib/rubocop/cli/command/show_cops.rb +++ b/lib/rubocop/cli/command/show_cops.rb @@ -5,6 +5,7 @@ class CLI module Command # Shows the given cops, or all cops by default, and their configurations # for the current directory. + # @api private class ShowCops < Base self.command_name = :show_cops diff --git a/lib/rubocop/cli/command/version.rb b/lib/rubocop/cli/command/version.rb index 81190286afc..84435d3f256 100644 --- a/lib/rubocop/cli/command/version.rb +++ b/lib/rubocop/cli/command/version.rb @@ -4,6 +4,7 @@ module RuboCop class CLI module Command # Display version. + # @api private class Version < Base self.command_name = :version diff --git a/lib/rubocop/cli/environment.rb b/lib/rubocop/cli/environment.rb index 06996727a71..70276eb9587 100644 --- a/lib/rubocop/cli/environment.rb +++ b/lib/rubocop/cli/environment.rb @@ -3,6 +3,7 @@ module RuboCop class CLI # Execution environment for a CLI command. + # @api private class Environment attr_reader :options, :config_store, :paths diff --git a/lib/rubocop/comment_config.rb b/lib/rubocop/comment_config.rb index 526b333dc85..e11ee85afa8 100644 --- a/lib/rubocop/comment_config.rb +++ b/lib/rubocop/comment_config.rb @@ -4,12 +4,17 @@ module RuboCop # This class parses the special `rubocop:disable` comments in a source # and provides a way to check if each cop is enabled at arbitrary line. class CommentConfig + # @api private REDUNDANT_DISABLE = 'Lint/RedundantCopDisableDirective' + # @api private COP_NAME_PATTERN = '([A-Z]\w+/)?(?:[A-Z]\w+)' + # @api private COP_NAMES_PATTERN = "(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}" + # @api private COPS_PATTERN = "(all|#{COP_NAMES_PATTERN})" + # @api private COMMENT_DIRECTIVE_REGEXP = Regexp.new( "# rubocop : ((?:disable|enable|todo))\\b #{COPS_PATTERN}" .gsub(' ', '\s*') diff --git a/lib/rubocop/config_loader.rb b/lib/rubocop/config_loader.rb index e33f4135106..27e13106c55 100644 --- a/lib/rubocop/config_loader.rb +++ b/lib/rubocop/config_loader.rb @@ -140,22 +140,33 @@ def project_root @project_root ||= find_project_root end + PENDING_BANNER = <<~BANNER + The following cops were added to RuboCop, but are not configured. Please set Enabled to either `true` or `false` in your `.rubocop.yml` file. + + Please also note that can also opt-in to new cops by default by adding this to your config: + AllCops: + NewCops: enable + BANNER + def warn_on_pending_cops(pending_cops) return if pending_cops.empty? - warn Rainbow('The following cops were added to RuboCop, but are not ' \ - 'configured. Please set Enabled to either `true` or ' \ - '`false` in your `.rubocop.yml` file:').yellow + warn Rainbow(PENDING_BANNER).yellow pending_cops.each do |cop| - version = cop.metadata['VersionAdded'] || 'N/A' - - warn Rainbow(" - #{cop.name} (#{version})").yellow + warn_pending_cop cop end warn Rainbow('For more information: https://docs.rubocop.org/rubocop/versioning.html').yellow end + def warn_pending_cop(cop) + version = cop.metadata['VersionAdded'] || 'N/A' + + warn Rainbow("#{cop.name}: # (new in #{version})").yellow + warn Rainbow(' Enabled: true').yellow + end + # Merges the given configuration with the default one. def merge_with_default(config, config_file, unset_nil: true) resolver.merge_with_default(config, config_file, unset_nil: unset_nil) diff --git a/lib/rubocop/config_loader_resolver.rb b/lib/rubocop/config_loader_resolver.rb index 0c7e3d7098e..4a3bf7fef2d 100644 --- a/lib/rubocop/config_loader_resolver.rb +++ b/lib/rubocop/config_loader_resolver.rb @@ -5,6 +5,7 @@ module RuboCop # A help class for ConfigLoader that handles configuration resolution. + # @api private class ConfigLoaderResolver def resolve_requires(path, hash) config_dir = File.dirname(path) diff --git a/lib/rubocop/config_obsoletion.rb b/lib/rubocop/config_obsoletion.rb index 56c743affab..aa372f868cd 100644 --- a/lib/rubocop/config_obsoletion.rb +++ b/lib/rubocop/config_obsoletion.rb @@ -2,6 +2,7 @@ module RuboCop # This class handles obsolete configuration. + # @api private class ConfigObsoletion RENAMED_COPS = { 'Layout/AlignArguments' => 'Layout/ArgumentAlignment', diff --git a/lib/rubocop/config_validator.rb b/lib/rubocop/config_validator.rb index b72bcfe0b5e..81fb923e69e 100644 --- a/lib/rubocop/config_validator.rb +++ b/lib/rubocop/config_validator.rb @@ -8,11 +8,14 @@ module RuboCop class ConfigValidator extend Forwardable + # @api private COMMON_PARAMS = %w[Exclude Include Severity inherit_mode AutoCorrect StyleGuide Details].freeze + # @api private INTERNAL_PARAMS = %w[Description StyleGuide VersionAdded VersionChanged VersionRemoved Reference Safe SafeAutoCorrect].freeze + # @api private NEW_COPS_VALUES = %w[pending disable enable].freeze def_delegators :@config, :smart_loaded_path, :for_all_cops diff --git a/lib/rubocop/cop/base.rb b/lib/rubocop/cop/base.rb index c8525373377..888e328421f 100644 --- a/lib/rubocop/cop/base.rb +++ b/lib/rubocop/cop/base.rb @@ -46,6 +46,9 @@ class Base # rubocop:disable Metrics/ClassLength # Consider creation API private InvestigationReport = Struct.new(:cop, :processed_source, :offenses, :corrector) + # List of methods names to restrict calls for `on_send` / `on_csend` + RESTRICT_ON_SEND = Set[].freeze + # List of cops that should not try to autocorrect at the same # time as this cop # @@ -287,6 +290,10 @@ def currently_disabled_lines @currently_disabled_lines ||= Set.new end + private_class_method def self.restrict_on_send + @restrict_on_send ||= self::RESTRICT_ON_SEND.to_set.freeze + end + # Called before any investigation def begin_investigation(processed_source) @current_offenses = [] diff --git a/lib/rubocop/cop/commissioner.rb b/lib/rubocop/cop/commissioner.rb index 3050122efa6..d6c6a3dfc65 100644 --- a/lib/rubocop/cop/commissioner.rb +++ b/lib/rubocop/cop/commissioner.rb @@ -7,6 +7,9 @@ module Cop class Commissioner include RuboCop::AST::Traversal + RESTRICTED_CALLBACKS = %i[on_send on_csend].freeze + private_constant :RESTRICTED_CALLBACKS + # How a Commissioner returns the results of the investigation # as a list of Cop::InvestigationReport and any errors caught # during the investigation. @@ -42,7 +45,8 @@ def initialize(cops, forces = [], options = {}) @cops = cops @forces = forces @options = options - @callbacks = {} + @callbacks = Hash.new { |h, k| h[k] = cops_callbacks_for(k) } + @restricted_map = {} reset end @@ -57,10 +61,17 @@ def initialize(cops, forces = [], options = {}) method_name = :"on_#{node_type}" next unless method_defined?(method_name) - define_method(method_name) do |node| - trigger_responding_cops(method_name, node) - super(node) unless NO_CHILD_NODES.include?(node_type) + if RESTRICTED_CALLBACKS.include?(method_name) + trigger_restricted = "trigger_restricted_cops(:on_#{node_type}, node)" end + + class_eval(<<~RUBY, __FILE__, __LINE__ + 1) + def on_#{node_type}(node) + trigger_responding_cops(:on_#{node_type}, node) + #{trigger_restricted} + #{'super(node)' unless NO_CHILD_NODES.include?(node_type)} + end + RUBY end # @return [InvestigationReport] @@ -83,9 +94,6 @@ def investigate(processed_source) private def trigger_responding_cops(callback, node) - @callbacks[callback] ||= @cops.select do |cop| - cop.respond_to?(callback) - end @callbacks[callback].each do |cop| with_cop_error_handling(cop, node) do cop.send(callback, node) @@ -97,6 +105,38 @@ def reset @errors = [] end + def cops_callbacks_for(callback) + callbacks = @cops.select do |cop| + cop.respond_to?(callback) + end + if RESTRICTED_CALLBACKS.include?(callback) + @restricted_map[callback] = restricted_map(callbacks) + end + callbacks + end + + def trigger_restricted_cops(event, node) + name = node.method_name + @restricted_map.fetch(event)[name]&.each do |cop| + with_cop_error_handling(cop, node) do + cop.send(event, node) + end + end + end + + # Note: mutates `callbacks` in place + def restricted_map(callbacks) + map = {} + callbacks.select! do |cop| + restrictions = cop.class.send :restrict_on_send + restrictions.each do |name| + (map[name] ||= []) << cop + end + restrictions.empty? + end + map + end + def invoke(callback, cops, *args) cops.each do |cop| with_cop_error_handling(cop) do diff --git a/lib/rubocop/cop/correctors/empty_line_corrector.rb b/lib/rubocop/cop/correctors/empty_line_corrector.rb index 5b0bb3803bb..ab68bdea247 100644 --- a/lib/rubocop/cop/correctors/empty_line_corrector.rb +++ b/lib/rubocop/cop/correctors/empty_line_corrector.rb @@ -16,8 +16,8 @@ def correct(corrector, node) end end - def insert_before(node) - ->(corrector) { corrector.insert_before(node, "\n") } + def insert_before(corrector, node) + corrector.insert_before(node, "\n") end end end diff --git a/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb b/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb index 7a0d973cfb0..50830f1839e 100644 --- a/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +++ b/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb @@ -8,12 +8,17 @@ class MultilineLiteralBraceCorrector include MultilineLiteralBraceLayout include RangeHelp - def initialize(node, processed_source) + def self.correct(corrector, node, processed_source) + new(corrector, node, processed_source).call + end + + def initialize(corrector, node, processed_source) + @corrector = corrector @node = node @processed_source = processed_source end - def call(corrector) + def call if closing_brace_on_same_line?(node) correct_same_line_brace(corrector) else @@ -29,7 +34,7 @@ def call(corrector) private - attr_reader :node, :processed_source + attr_reader :corrector, :node, :processed_source def correct_same_line_brace(corrector) corrector.insert_before(node.loc.end, "\n") diff --git a/lib/rubocop/cop/correctors/percent_literal_corrector.rb b/lib/rubocop/cop/correctors/percent_literal_corrector.rb index d2b1a0d0db9..433977454fb 100644 --- a/lib/rubocop/cop/correctors/percent_literal_corrector.rb +++ b/lib/rubocop/cop/correctors/percent_literal_corrector.rb @@ -13,23 +13,18 @@ def initialize(config, preferred_delimiters) @preferred_delimiters = preferred_delimiters end - def correct(node, char) + def correct(corrector, node, char) escape = escape_words?(node) char = char.upcase if escape delimiters = delimiters_for("%#{char}") contents = new_contents(node, escape, delimiters) - wrap_contents(node, contents, char, delimiters) + wrap_contents(corrector, node, contents, char, delimiters) end private - def wrap_contents(node, contents, char, delimiters) - lambda do |corrector| - corrector.replace( - node, - "%#{char}#{delimiters[0]}#{contents}#{delimiters[1]}" - ) - end + def wrap_contents(corrector, node, contents, char, delimiters) + corrector.replace(node, "%#{char}#{delimiters[0]}#{contents}#{delimiters[1]}") end def escape_words?(node) diff --git a/lib/rubocop/cop/correctors/punctuation_corrector.rb b/lib/rubocop/cop/correctors/punctuation_corrector.rb index 451f7716ac7..2a4df3f610c 100644 --- a/lib/rubocop/cop/correctors/punctuation_corrector.rb +++ b/lib/rubocop/cop/correctors/punctuation_corrector.rb @@ -5,12 +5,12 @@ module Cop # This auto-corrects punctuation class PunctuationCorrector class << self - def remove_space(space_before) - ->(corrector) { corrector.remove(space_before) } + def remove_space(corrector, space_before) + corrector.remove(space_before) end - def add_space(token) - ->(corrector) { corrector.replace(token.pos, "#{token.pos.source} ") } + def add_space(corrector, token) + corrector.replace(token.pos, "#{token.pos.source} ") end def swap_comma(corrector, range) diff --git a/lib/rubocop/cop/generator.rb b/lib/rubocop/cop/generator.rb index 9eaf2ef5a8c..14aab3e9e49 100644 --- a/lib/rubocop/cop/generator.rb +++ b/lib/rubocop/cop/generator.rb @@ -6,6 +6,7 @@ module Cop # # This generator will take a cop name and generate a source file # and spec file when given a valid qualified cop name. + # @api private class Generator # Note: RDoc 5.1.0 or lower has the following issue. # https://github.com/rubocop-hq/rubocop/issues/7043 diff --git a/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb b/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb new file mode 100644 index 00000000000..1c739c0878b --- /dev/null +++ b/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Layout + # This cop enforces empty line after multiline condition. + # + # @example + # # bad + # if multiline && + # condition + # do_something + # end + # + # # good + # if multiline && + # condition + # + # do_something + # end + # + # # bad + # case x + # when foo, + # bar + # do_something + # end + # + # # good + # case x + # when foo, + # bar + # + # do_something + # end + # + # # bad + # begin + # do_something + # rescue FooError, + # BarError + # handle_error + # end + # + # # good + # begin + # do_something + # rescue FooError, + # BarError + # + # handle_error + # end + # + class EmptyLineAfterMultilineCondition < Base + include RangeHelp + include RescueNode + extend AutoCorrector + + MSG = 'Use empty line after multiline condition.' + + def on_if(node) + return if node.ternary? + + if node.modifier_form? + check_condition(node.condition) unless next_sibling_empty?(node) + else + check_condition(node.condition) + end + end + + def on_while(node) + check_condition(node.condition) + end + alias on_until on_while + + def on_while_post(node) + return if next_sibling_empty?(node) + + check_condition(node.condition) + end + alias on_until_post on_while_post + + def on_case(node) + node.each_when do |when_node| + last_condition = when_node.conditions.last + + next if !multiline_when_condition?(when_node) || + next_line_empty?(last_condition.last_line) + + add_offense(when_node, &autocorrect(last_condition)) + end + end + + def on_rescue(node) + _body, *resbodies, _else = *node + + resbodies.each do |resbody| + rescued_exceptions = rescued_exceptions(resbody) + next if !multiline_rescue_exceptions?(rescued_exceptions) || + next_line_empty?(rescued_exceptions.last.last_line) + + add_offense(resbody, &autocorrect(rescued_exceptions.last)) + end + end + + private + + def check_condition(condition) + return unless condition.multiline? + return if next_line_empty?(condition.last_line) + + add_offense(condition, &autocorrect(condition)) + end + + def next_line_empty?(line) + processed_source[line].blank? + end + + def next_sibling_empty?(node) + next_sibling = node.parent.children[node.sibling_index + 1] + next_sibling.nil? + end + + def multiline_when_condition?(when_node) + when_node.conditions.first.first_line != when_node.conditions.last.last_line + end + + def multiline_rescue_exceptions?(exception_nodes) + return false if exception_nodes.size <= 1 + + first, *_rest, last = *exception_nodes + first.first_line != last.last_line + end + + def autocorrect(node) + lambda do |corrector| + range = range_by_whole_lines(node.source_range) + corrector.insert_after(range, "\n") + end + end + end + end + end +end diff --git a/lib/rubocop/cop/layout/empty_line_between_defs.rb b/lib/rubocop/cop/layout/empty_line_between_defs.rb index b7212981e95..7e7c2f7c9b1 100644 --- a/lib/rubocop/cop/layout/empty_line_between_defs.rb +++ b/lib/rubocop/cop/layout/empty_line_between_defs.rb @@ -59,18 +59,19 @@ def check_defs(nodes) location = nodes.last.loc.keyword.join(nodes.last.loc.name) add_offense(location) do |corrector| - autocorrect(corrector, nodes.last) + autocorrect(corrector, *nodes) end end - def autocorrect(corrector, node) - prev_def = prev_node(node) - + def autocorrect(corrector, prev_def, node) # finds position of first newline end_pos = prev_def.loc.end.end_pos source_buffer = prev_def.loc.end.source_buffer newline_pos = source_buffer.source.index("\n", end_pos) + # Handle the case when multiple one-liners are on the same line. + newline_pos = end_pos + 1 if newline_pos > node.source_range.begin_pos + count = blank_lines_count_between(prev_def, node) if count > maximum_empty_lines @@ -114,16 +115,12 @@ def maximum_empty_lines Array(cop_config['NumberOfEmptyLines']).last end - def prev_node(node) - return nil unless node.sibling_index.positive? - - node.parent.children[node.sibling_index - 1] - end - def lines_between_defs(first_def_node, second_def_node) - line_range = def_end(first_def_node)..(def_start(second_def_node) - 2) + begin_line_num = def_end(first_def_node) + end_line_num = def_start(second_def_node) - 2 + return [] if end_line_num.negative? - processed_source.lines[line_range] + processed_source.lines[begin_line_num..end_line_num] end def def_start(node) diff --git a/lib/rubocop/cop/layout/first_array_element_line_break.rb b/lib/rubocop/cop/layout/first_array_element_line_break.rb index 4a4bfe9f31e..7d0004431c1 100644 --- a/lib/rubocop/cop/layout/first_array_element_line_break.rb +++ b/lib/rubocop/cop/layout/first_array_element_line_break.rb @@ -17,8 +17,9 @@ module Layout # :a, # :b] # - class FirstArrayElementLineBreak < Cop + class FirstArrayElementLineBreak < Base include FirstElementLineBreak + extend AutoCorrector MSG = 'Add a line break before the first element of a ' \ 'multi-line array.' @@ -29,10 +30,6 @@ def on_array(node) check_children_line_break(node, node.children) end - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end - private def assignment_on_same_line?(node) diff --git a/lib/rubocop/cop/layout/first_hash_element_line_break.rb b/lib/rubocop/cop/layout/first_hash_element_line_break.rb index f890a4b61cc..1c63f2ca29a 100644 --- a/lib/rubocop/cop/layout/first_hash_element_line_break.rb +++ b/lib/rubocop/cop/layout/first_hash_element_line_break.rb @@ -16,8 +16,9 @@ module Layout # { # a: 1, # b: 2 } - class FirstHashElementLineBreak < Cop + class FirstHashElementLineBreak < Base include FirstElementLineBreak + extend AutoCorrector MSG = 'Add a line break before the first element of a ' \ 'multi-line hash.' @@ -27,10 +28,6 @@ def on_hash(node) # If it doesn't, Style/FirstMethodArgumentLineBreak will handle it check_children_line_break(node, node.children) if node.loc.begin end - - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end end end end diff --git a/lib/rubocop/cop/layout/first_method_argument_line_break.rb b/lib/rubocop/cop/layout/first_method_argument_line_break.rb index d655c95e64a..3f7a25668e3 100644 --- a/lib/rubocop/cop/layout/first_method_argument_line_break.rb +++ b/lib/rubocop/cop/layout/first_method_argument_line_break.rb @@ -20,8 +20,9 @@ module Layout # # ignored # method foo, bar, # baz - class FirstMethodArgumentLineBreak < Cop + class FirstMethodArgumentLineBreak < Base include FirstElementLineBreak + extend AutoCorrector MSG = 'Add a line break before the first argument of a ' \ 'multi-line method argument list.' @@ -42,10 +43,6 @@ def on_send(node) end alias on_csend on_send alias on_super on_send - - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end end end end diff --git a/lib/rubocop/cop/layout/first_method_parameter_line_break.rb b/lib/rubocop/cop/layout/first_method_parameter_line_break.rb index 47525aa81ce..67c0d0227cf 100644 --- a/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +++ b/lib/rubocop/cop/layout/first_method_parameter_line_break.rb @@ -26,8 +26,9 @@ module Layout # bar # do_something # end - class FirstMethodParameterLineBreak < Cop + class FirstMethodParameterLineBreak < Base include FirstElementLineBreak + extend AutoCorrector MSG = 'Add a line break before the first parameter of a ' \ 'multi-line method parameter list.' @@ -36,10 +37,6 @@ def on_def(node) check_method_line_break(node, node.arguments) end alias on_defs on_def - - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end end end end diff --git a/lib/rubocop/cop/layout/hash_alignment.rb b/lib/rubocop/cop/layout/hash_alignment.rb index e2548909426..6d343725fe9 100644 --- a/lib/rubocop/cop/layout/hash_alignment.rb +++ b/lib/rubocop/cop/layout/hash_alignment.rb @@ -175,9 +175,10 @@ module Layout # do_something({foo: 1, # bar: 2}) # - class HashAlignment < Cop + class HashAlignment < Base include HashAlignmentStyles include RangeHelp + extend AutoCorrector MESSAGES = { KeyAlignment => 'Align the keys of a hash literal if ' \ 'they span more than one line.', @@ -212,13 +213,6 @@ def on_hash(node) # rubocop:todo Metrics/CyclomaticComplexity check_pairs(node) end - def autocorrect(node) - delta = column_deltas[alignment_for(node).first.class][node] - return if delta.nil? - - correct_node(node, delta) - end - attr_accessor :offences_by, :column_deltas private @@ -254,7 +248,11 @@ def check_pairs(node) def add_offences format, offences = offences_by.min_by { |_, v| v.length } (offences || []).each do |offence| - add_offense(offence, message: MESSAGES[format]) + add_offense(offence, message: MESSAGES[format]) do |corrector| + delta = column_deltas[alignment_for(offence).first.class][offence] + + correct_node(corrector, offence, delta) unless delta.nil? + end end end @@ -293,25 +291,26 @@ def alignment_for_colons new_alignment('EnforcedColonStyle') end - def correct_node(node, delta) + def correct_node(corrector, node, delta) # We can't use the instance variable inside the lambda. That would # just give each lambda the same reference and they would all get the # last value of each. A local variable fixes the problem. if !node.value - correct_no_value(delta[:key] || 0, node.source_range) + delta_value = delta[:key] || 0 + correct_no_value(corrector, delta_value, node.source_range) else - correct_key_value(delta, node.key.source_range, + correct_key_value(corrector, delta, node.key.source_range, node.value.source_range, node.loc.operator) end end - def correct_no_value(key_delta, key) - ->(corrector) { adjust(corrector, key_delta, key) } + def correct_no_value(corrector, key_delta, key) + adjust(corrector, key_delta, key) end - def correct_key_value(delta, key, value, separator) + def correct_key_value(corrector, delta, key, value, separator) # We can't use the instance variable inside the lambda. That would # just give each lambda the same reference and they would all get the # last value of each. Some local variables fix the problem. @@ -322,11 +321,9 @@ def correct_key_value(delta, key, value, separator) key_column = key.column key_delta = -key_column if key_delta < -key_column - lambda do |corrector| - adjust(corrector, key_delta, key) - adjust(corrector, separator_delta, separator) - adjust(corrector, value_delta, value) - end + adjust(corrector, key_delta, key) + adjust(corrector, separator_delta, separator) + adjust(corrector, value_delta, value) end def new_alignment(key) diff --git a/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb b/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb index 28c56afb942..c4e27b64339 100644 --- a/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +++ b/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb @@ -50,12 +50,17 @@ module Layout # 123, # ) # - class HeredocArgumentClosingParenthesis < Cop + class HeredocArgumentClosingParenthesis < Base include RangeHelp + extend AutoCorrector MSG = 'Put the closing parenthesis for a method call with a ' \ 'HEREDOC parameter on the same line as the HEREDOC opening.' + def self.autocorrect_incompatible_with + [Style::TrailingCommaInArguments] + end + def on_send(node) heredoc_arg = extract_heredoc_argument(node) return unless heredoc_arg @@ -65,9 +70,13 @@ def on_send(node) return unless outermost_send.loc.end return unless heredoc_arg.first_line != outermost_send.loc.end.line - add_offense(outermost_send, location: :end) + add_offense(outermost_send.loc.end) do |corrector| + autocorrect(corrector, outermost_send) + end end + private + # Autocorrection note: # # Commas are a bit tricky to handle when the method call is @@ -94,22 +103,14 @@ def on_send(node) # SQL # third_array_value, # ] - def autocorrect(node) - lambda do |corrector| - fix_closing_parenthesis(node, corrector) + def autocorrect(corrector, node) + fix_closing_parenthesis(node, corrector) - remove_internal_trailing_comma(node, corrector) if internal_trailing_comma?(node) + remove_internal_trailing_comma(node, corrector) if internal_trailing_comma?(node) - fix_external_trailing_comma(node, corrector) if external_trailing_comma?(node) - end + fix_external_trailing_comma(node, corrector) if external_trailing_comma?(node) end - def self.autocorrect_incompatible_with - [Style::TrailingCommaInArguments] - end - - private - def outermost_send_on_same_line(heredoc) previous = heredoc current = previous.parent diff --git a/lib/rubocop/cop/layout/heredoc_indentation.rb b/lib/rubocop/cop/layout/heredoc_indentation.rb index 8f1bb8d9568..37b9aa5dd81 100644 --- a/lib/rubocop/cop/layout/heredoc_indentation.rb +++ b/lib/rubocop/cop/layout/heredoc_indentation.rb @@ -21,9 +21,9 @@ module Layout # something # RUBY # - # - class HeredocIndentation < Cop + class HeredocIndentation < Base include Heredoc + extend AutoCorrector TYPE_MSG = 'Use %d spaces for indentation in a ' \ 'heredoc by using `<<~` instead of `%s`.' @@ -34,8 +34,9 @@ def on_heredoc(node) return if body.strip.empty? body_indent_level = indent_level(body) + heredoc_indent_type = heredoc_indent_type(node) - if heredoc_indent_type(node) == '~' + if heredoc_indent_type == '~' expected_indent_level = base_indent_level(node) + indentation_width return if expected_indent_level == body_indent_level else @@ -44,12 +45,16 @@ def on_heredoc(node) return if line_too_long?(node) - add_offense(node, location: :heredoc_body) + register_offense(node, heredoc_indent_type) end - def autocorrect(node) - lambda do |corrector| - if heredoc_indent_type(node) == '~' + private + + def register_offense(node, heredoc_indent_type) + message = message(heredoc_indent_type) + + add_offense(node.loc.heredoc_body, message: message) do |corrector| + if heredoc_indent_type == '~' adjust_squiggly(corrector, node) else adjust_minus(corrector, node) @@ -57,10 +62,8 @@ def autocorrect(node) end end - private - - def message(node) - current_indent_type = "<<#{heredoc_indent_type(node)}" + def message(heredoc_indent_type) + current_indent_type = "<<#{heredoc_indent_type}" if current_indent_type == '<<~' width_message(indentation_width) diff --git a/lib/rubocop/cop/layout/initial_indentation.rb b/lib/rubocop/cop/layout/initial_indentation.rb index f54732da753..5d5bc83a427 100644 --- a/lib/rubocop/cop/layout/initial_indentation.rb +++ b/lib/rubocop/cop/layout/initial_indentation.rb @@ -17,21 +17,20 @@ module Layout # def foo; end # end # - class InitialIndentation < Cop + class InitialIndentation < Base include RangeHelp + extend AutoCorrector MSG = 'Indentation of first line in file detected.' - def investigate(_processed_source) + def on_new_investigation space_before(first_token) do |space| - add_offense(space, location: first_token.pos) + add_offense(first_token.pos) do |corrector| + corrector.remove(space) + end end end - def autocorrect(range) - ->(corrector) { corrector.remove(range) } - end - private def first_token diff --git a/lib/rubocop/cop/layout/leading_comment_space.rb b/lib/rubocop/cop/layout/leading_comment_space.rb index 5939666d9b2..ae822360909 100644 --- a/lib/rubocop/cop/layout/leading_comment_space.rb +++ b/lib/rubocop/cop/layout/leading_comment_space.rb @@ -49,31 +49,33 @@ module Layout # #ruby=2.7.0 # #ruby-gemset=myproject # - class LeadingCommentSpace < Cop + class LeadingCommentSpace < Base include RangeHelp + extend AutoCorrector MSG = 'Missing space after `#`.' - def investigate(processed_source) + def on_new_investigation 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) next if gemfile_ruby_comment?(comment) - add_offense(comment) + add_offense(comment) do |corrector| + expr = comment.loc.expression + + corrector.insert_after(hash_mark(expr), ' ') + end end end - def autocorrect(comment) - expr = comment.loc.expression - hash_mark = range_between(expr.begin_pos, expr.begin_pos + 1) + private - ->(corrector) { corrector.insert_after(hash_mark, ' ') } + def hash_mark(expr) + range_between(expr.begin_pos, expr.begin_pos + 1) end - private - def allowed_on_first_line?(comment) shebang?(comment) || rackup_config_file? && rackup_options?(comment) end diff --git a/lib/rubocop/cop/layout/leading_empty_lines.rb b/lib/rubocop/cop/layout/leading_empty_lines.rb index b859674df60..c1b72c7d1e2 100644 --- a/lib/rubocop/cop/layout/leading_empty_lines.rb +++ b/lib/rubocop/cop/layout/leading_empty_lines.rb @@ -27,23 +27,18 @@ module Layout # # good # # (start of file) # # a comment - class LeadingEmptyLines < Cop + class LeadingEmptyLines < Base + extend AutoCorrector + MSG = 'Unnecessary blank line at the beginning of the source.' - def investigate(processed_source) + def on_new_investigation token = processed_source.tokens[0] return unless token && token.line > 1 - add_offense(processed_source.tokens[0], - location: processed_source.tokens[0].pos) - end - - def autocorrect(node) - range = Parser::Source::Range.new(processed_source.buffer, - 0, - node.begin_pos) + add_offense(token.pos) do |corrector| + range = Parser::Source::Range.new(processed_source.buffer, 0, token.begin_pos) - lambda do |corrector| corrector.remove(range) end end diff --git a/lib/rubocop/cop/layout/multiline_array_brace_layout.rb b/lib/rubocop/cop/layout/multiline_array_brace_layout.rb index c406d8bf589..122a4d1d55e 100644 --- a/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +++ b/lib/rubocop/cop/layout/multiline_array_brace_layout.rb @@ -88,8 +88,9 @@ module Layout # # good # [ :a, # :b ] - class MultilineArrayBraceLayout < Cop + class MultilineArrayBraceLayout < Base include MultilineLiteralBraceLayout + extend AutoCorrector SAME_LINE_MESSAGE = 'The closing array brace must be on the same ' \ 'line as the last array element when the opening brace is on the ' \ @@ -108,10 +109,6 @@ class MultilineArrayBraceLayout < Cop def on_array(node) check_brace_layout(node) end - - def autocorrect(node) - MultilineLiteralBraceCorrector.new(node, processed_source) - end end end end diff --git a/lib/rubocop/cop/layout/multiline_array_line_breaks.rb b/lib/rubocop/cop/layout/multiline_array_line_breaks.rb index 2ad5f2f029c..3d1f4e4da04 100644 --- a/lib/rubocop/cop/layout/multiline_array_line_breaks.rb +++ b/lib/rubocop/cop/layout/multiline_array_line_breaks.rb @@ -20,8 +20,9 @@ module Layout # b, # c # ] - class MultilineArrayLineBreaks < Cop + class MultilineArrayLineBreaks < Base include MultilineElementLineBreaks + extend AutoCorrector MSG = 'Each item in a multi-line array must start ' \ 'on a separate line.' @@ -29,10 +30,6 @@ class MultilineArrayLineBreaks < Cop def on_array(node) check_line_breaks(node, node.children) end - - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end end end end diff --git a/lib/rubocop/cop/layout/multiline_assignment_layout.rb b/lib/rubocop/cop/layout/multiline_assignment_layout.rb index badfaf878c5..64863887f04 100644 --- a/lib/rubocop/cop/layout/multiline_assignment_layout.rb +++ b/lib/rubocop/cop/layout/multiline_assignment_layout.rb @@ -31,10 +31,11 @@ module Layout # foo = if expression # 'bar' # end - class MultilineAssignmentLayout < Cop + class MultilineAssignmentLayout < Base include CheckAssignment include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector NEW_LINE_OFFENSE = 'Right hand side of multi-line assignment is on ' \ 'the same line as the assignment operator `=`.' @@ -63,24 +64,19 @@ def check_by_enforced_style(node, rhs) def check_new_line_offense(node, rhs) return unless node.loc.operator.line == rhs.first_line - add_offense(node, message: NEW_LINE_OFFENSE) + add_offense(node, message: NEW_LINE_OFFENSE) do |corrector| + corrector.insert_after(node.loc.operator, "\n") + end end def check_same_line_offense(node, rhs) return unless node.loc.operator.line != rhs.first_line - add_offense(node, message: SAME_LINE_OFFENSE) - end - - def autocorrect(node) - case style - when :new_line - ->(corrector) { corrector.insert_after(node.loc.operator, "\n") } - when :same_line - range = range_between(node.loc.operator.end_pos, - extract_rhs(node).source_range.begin_pos) - - ->(corrector) { corrector.replace(range, ' ') } + add_offense(node, message: SAME_LINE_OFFENSE) do |corrector| + range = range_between( + node.loc.operator.end_pos, extract_rhs(node).source_range.begin_pos + ) + corrector.replace(range, ' ') end end diff --git a/lib/rubocop/cop/layout/multiline_block_layout.rb b/lib/rubocop/cop/layout/multiline_block_layout.rb index 25e26778db6..3050f4795a4 100644 --- a/lib/rubocop/cop/layout/multiline_block_layout.rb +++ b/lib/rubocop/cop/layout/multiline_block_layout.rb @@ -48,8 +48,9 @@ module Layout # foo(i) # bar(i) # } - class MultilineBlockLayout < Cop + class MultilineBlockLayout < Base include RangeHelp + extend AutoCorrector MSG = 'Block body expression is on the same line as ' \ 'the block start.' @@ -70,23 +71,6 @@ def on_block(node) add_offense_for_expression(node, node.body, MSG) end - def autocorrect(node) - lambda do |corrector| - unless args_on_beginning_line?(node) - autocorrect_arguments(corrector, node) - expr_before_body = node.arguments.source_range.end - end - - return unless node.body - - expr_before_body ||= node.loc.begin - - if expr_before_body.line == node.body.first_line - autocorrect_body(corrector, node, node.body) - end - end - end - private def args_on_beginning_line?(node) @@ -116,7 +100,25 @@ def characters_needed_for_space_and_pipes(node) def add_offense_for_expression(node, expr, msg) expression = expr.source_range range = range_between(expression.begin_pos, expression.end_pos) - add_offense(node, location: range, message: msg) + + add_offense(range, message: msg) do |corrector| + autocorrect(corrector, node) + end + end + + def autocorrect(corrector, node) + unless args_on_beginning_line?(node) + autocorrect_arguments(corrector, node) + expr_before_body = node.arguments.source_range.end + end + + return unless node.body + + expr_before_body ||= node.loc.begin + + return unless expr_before_body.line == node.body.first_line + + autocorrect_body(corrector, node, node.body) end def autocorrect_arguments(corrector, node) diff --git a/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb b/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb index 39f301eaeab..b86ff804856 100644 --- a/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +++ b/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb @@ -88,8 +88,9 @@ module Layout # # good # { a: 1, # b: 2 } - class MultilineHashBraceLayout < Cop + class MultilineHashBraceLayout < Base include MultilineLiteralBraceLayout + extend AutoCorrector SAME_LINE_MESSAGE = 'Closing hash brace must be on the same line as ' \ 'the last hash element when opening brace is on the same line as ' \ @@ -108,10 +109,6 @@ class MultilineHashBraceLayout < Cop def on_hash(node) check_brace_layout(node) end - - def autocorrect(node) - MultilineLiteralBraceCorrector.new(node, processed_source) - end end end end diff --git a/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb b/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb index 50a1168a160..f3436804004 100644 --- a/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +++ b/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb @@ -20,8 +20,9 @@ module Layout # b: 2, # c: 3 # } - class MultilineHashKeyLineBreaks < Cop + class MultilineHashKeyLineBreaks < Base include MultilineElementLineBreaks + extend AutoCorrector MSG = 'Each key in a multi-line hash must start on a ' \ 'separate line.' @@ -35,10 +36,6 @@ def on_hash(node) check_line_breaks(node, node.children) if node.loc.begin end - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end - private def starts_with_curly_brace?(node) diff --git a/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb b/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb index 390c1adda34..0bcc72e521a 100644 --- a/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +++ b/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb @@ -19,8 +19,9 @@ module Layout # b, # c # ) - class MultilineMethodArgumentLineBreaks < Cop - include(MultilineElementLineBreaks) + class MultilineMethodArgumentLineBreaks < Base + include MultilineElementLineBreaks + extend AutoCorrector MSG = 'Each argument in a multi-line method call must start ' \ 'on a separate line.' @@ -41,10 +42,6 @@ def on_send(node) check_line_breaks(node, args) end - - def autocorrect(node) - EmptyLineCorrector.insert_before(node) - end end end end diff --git a/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb b/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb index 6b874613695..3f6821143c1 100644 --- a/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +++ b/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb @@ -88,8 +88,9 @@ module Layout # # good # foo(a, # b) - class MultilineMethodCallBraceLayout < Cop + class MultilineMethodCallBraceLayout < Base include MultilineLiteralBraceLayout + extend AutoCorrector SAME_LINE_MESSAGE = 'Closing method call brace must be on the ' \ 'same line as the last argument when opening brace is on the same ' \ @@ -109,10 +110,6 @@ def on_send(node) check_brace_layout(node) end - def autocorrect(node) - MultilineLiteralBraceCorrector.new(node, processed_source) - end - private def children(node) diff --git a/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb b/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb index 3510f2ea89f..d8f6b7c3629 100644 --- a/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +++ b/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb @@ -100,8 +100,9 @@ module Layout # def foo(a, # b) # end - class MultilineMethodDefinitionBraceLayout < Cop + class MultilineMethodDefinitionBraceLayout < Base include MultilineLiteralBraceLayout + extend AutoCorrector SAME_LINE_MESSAGE = 'Closing method definition brace must be on the ' \ 'same line as the last parameter when opening brace is on the same ' \ @@ -121,10 +122,6 @@ def on_def(node) check_brace_layout(node.arguments) end alias on_defs on_def - - def autocorrect(node) - MultilineLiteralBraceCorrector.new(node, processed_source) - end end end end diff --git a/lib/rubocop/cop/layout/rescue_ensure_alignment.rb b/lib/rubocop/cop/layout/rescue_ensure_alignment.rb index e02359db9d4..ad0889bbc20 100644 --- a/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +++ b/lib/rubocop/cop/layout/rescue_ensure_alignment.rb @@ -21,8 +21,9 @@ module Layout # rescue # puts 'error' # end - class RescueEnsureAlignment < Cop + class RescueEnsureAlignment < Base include RangeHelp + extend AutoCorrector MSG = '`%s` at %d, %d is not ' \ 'aligned with `%s` at ' \ @@ -41,19 +42,7 @@ def on_ensure(node) check(node) end - def autocorrect(node) - whitespace = whitespace_range(node) - # Some inline node is sitting before current node. - return nil unless whitespace.source.strip.empty? - - alignment_node = alignment_node(node) - return false if alignment_node.nil? - - new_column = alignment_node.loc.column - ->(corrector) { corrector.replace(whitespace, ' ' * new_column) } - end - - def investigate(processed_source) + def on_new_investigation @modifier_locations = processed_source.tokens.each_with_object([]) do |token, locations| next unless token.rescue_modifier? @@ -73,15 +62,23 @@ def check(node) alignment_loc = alignment_node.loc.expression kw_loc = node.loc.keyword - return if - alignment_loc.column == kw_loc.column || - alignment_loc.line == kw_loc.line + return if alignment_loc.column == kw_loc.column || alignment_loc.line == kw_loc.line add_offense( - node, - location: kw_loc, - message: format_message(alignment_node, alignment_loc, kw_loc) - ) + kw_loc, message: format_message(alignment_node, alignment_loc, kw_loc) + ) do |corrector| + autocorrect(corrector, node, alignment_node) + end + end + + def autocorrect(corrector, node, alignment_node) + whitespace = whitespace_range(node) + # Some inline node is sitting before current node. + return nil unless whitespace.source.strip.empty? + + new_column = alignment_node.loc.column + + corrector.replace(whitespace, ' ' * new_column) end def format_message(alignment_node, alignment_loc, kw_loc) diff --git a/lib/rubocop/cop/layout/space_after_colon.rb b/lib/rubocop/cop/layout/space_after_colon.rb index 9fc02b56428..62d49b6fc8a 100644 --- a/lib/rubocop/cop/layout/space_after_colon.rb +++ b/lib/rubocop/cop/layout/space_after_colon.rb @@ -13,7 +13,9 @@ module Layout # # # good # def f(a:, b: 2); {a: 3}; end - class SpaceAfterColon < Cop + class SpaceAfterColon < Base + extend AutoCorrector + MSG = 'Space missing after colon.' def on_pair(node) @@ -21,7 +23,7 @@ def on_pair(node) colon = node.loc.operator - add_offense(colon, location: colon) unless followed_by_space?(colon) + register_offense(colon) unless followed_by_space?(colon) end def on_kwoptarg(node) @@ -29,15 +31,17 @@ def on_kwoptarg(node) # optional keyword argument's name, so must construct one. colon = node.loc.name.end.resize(1) - add_offense(colon, location: colon) unless followed_by_space?(colon) - end - - def autocorrect(range) - ->(corrector) { corrector.insert_after(range, ' ') } + register_offense(colon) unless followed_by_space?(colon) end private + def register_offense(colon) + add_offense(colon) do |corrector| + corrector.insert_after(colon, ' ') + end + end + def followed_by_space?(colon) /\s/.match?(colon.source_buffer.source[colon.end_pos]) end diff --git a/lib/rubocop/cop/layout/space_after_comma.rb b/lib/rubocop/cop/layout/space_after_comma.rb index 96eed5d6e67..773ac114715 100644 --- a/lib/rubocop/cop/layout/space_after_comma.rb +++ b/lib/rubocop/cop/layout/space_after_comma.rb @@ -14,12 +14,9 @@ module Layout # # good # [1, 2] # { foo:bar, } - class SpaceAfterComma < Cop + class SpaceAfterComma < Base include SpaceAfterPunctuation - - def autocorrect(comma) - PunctuationCorrector.add_space(comma) - end + extend AutoCorrector def space_style_before_rcurly cfg = config.for_cop('Layout/SpaceInsideHashLiteralBraces') diff --git a/lib/rubocop/cop/layout/space_after_method_name.rb b/lib/rubocop/cop/layout/space_after_method_name.rb index 900810ca0e0..b801b87fd18 100644 --- a/lib/rubocop/cop/layout/space_after_method_name.rb +++ b/lib/rubocop/cop/layout/space_after_method_name.rb @@ -14,8 +14,9 @@ module Layout # # good # def func(x) end # def method=(y) end - class SpaceAfterMethodName < Cop + class SpaceAfterMethodName < Base include RangeHelp + extend AutoCorrector MSG = 'Do not put a space between a method name and the opening ' \ 'parenthesis.' @@ -29,13 +30,11 @@ def on_def(node) expr.begin_pos) return unless pos_before_left_paren.source.start_with?(' ') - add_offense(pos_before_left_paren, location: pos_before_left_paren) + add_offense(pos_before_left_paren) do |corrector| + corrector.remove(pos_before_left_paren) + end end alias on_defs on_def - - def autocorrect(pos_before_left_paren) - ->(corrector) { corrector.remove(pos_before_left_paren) } - end end end end diff --git a/lib/rubocop/cop/layout/space_after_not.rb b/lib/rubocop/cop/layout/space_after_not.rb index 9af88cb9fe4..1f49efa13b1 100644 --- a/lib/rubocop/cop/layout/space_after_not.rb +++ b/lib/rubocop/cop/layout/space_after_not.rb @@ -11,29 +11,27 @@ module Layout # # # good # !something - class SpaceAfterNot < Cop + class SpaceAfterNot < Base include RangeHelp + extend AutoCorrector MSG = 'Do not leave space between `!` and its argument.' def on_send(node) return unless node.prefix_bang? && whitespace_after_operator?(node) - add_offense(node) + add_offense(node) do |corrector| + corrector.remove( + range_between(node.loc.selector.end_pos, node.receiver.source_range.begin_pos) + ) + end end + private + def whitespace_after_operator?(node) node.receiver.loc.column - node.loc.column > 1 end - - def autocorrect(node) - lambda do |corrector| - corrector.remove( - range_between(node.loc.selector.end_pos, - node.receiver.source_range.begin_pos) - ) - end - end end end end diff --git a/lib/rubocop/cop/layout/space_after_semicolon.rb b/lib/rubocop/cop/layout/space_after_semicolon.rb index 65f85c98f9c..acd067d5036 100644 --- a/lib/rubocop/cop/layout/space_after_semicolon.rb +++ b/lib/rubocop/cop/layout/space_after_semicolon.rb @@ -11,12 +11,9 @@ module Layout # # # good # x = 1; y = 2 - class SpaceAfterSemicolon < Cop + class SpaceAfterSemicolon < Base include SpaceAfterPunctuation - - def autocorrect(semicolon) - PunctuationCorrector.add_space(semicolon) - end + extend AutoCorrector def space_style_before_rcurly cfg = config.for_cop('Layout/SpaceInsideBlockBraces') diff --git a/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb b/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb index 0e6f5eb7ee0..2cf1d752d81 100644 --- a/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +++ b/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb @@ -27,10 +27,11 @@ module Layout # def some_method(arg1=:default, arg2=nil, arg3=[]) # # do something... # end - class SpaceAroundEqualsInParameterDefault < Cop + class SpaceAroundEqualsInParameterDefault < Base include SurroundingSpace include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector MSG = 'Surrounding space %s in default value assignment.' @@ -40,13 +41,6 @@ def on_optarg(node) check_optarg(arg, equals, value) end - def autocorrect(range) - m = range.source.match(/=\s*(\S+)/) - rest = m ? m.captures[0] : '' - replacement = style == :space ? ' = ' : '=' - ->(corrector) { corrector.replace(range, replacement + rest) } - end - private def check_optarg(arg, equals, value) @@ -65,14 +59,25 @@ def check_optarg(arg, equals, value) def incorrect_style_detected(arg, value, space_on_both_sides, no_surrounding_space) range = range_between(arg.end_pos, value.begin_pos) - add_offense(range, location: range) do - if style == :space && no_surrounding_space || - style == :no_space && space_on_both_sides - opposite_style_detected - else - unrecognized_style_detected - end + + if style == :space && no_surrounding_space || + style == :no_space && space_on_both_sides + return unless opposite_style_detected + else + return unless unrecognized_style_detected end + + add_offense(range) do |corrector| + autocorrect(corrector, range) + end + end + + def autocorrect(corrector, range) + m = range.source.match(/=\s*(\S+)/) + rest = m ? m.captures[0] : '' + replacement = style == :space ? ' = ' : '=' + + corrector.replace(range, replacement + rest) end def space_on_both_sides?(arg, equals) diff --git a/lib/rubocop/cop/layout/space_around_keyword.rb b/lib/rubocop/cop/layout/space_around_keyword.rb index d5b2bca045b..344a9b57ace 100644 --- a/lib/rubocop/cop/layout/space_around_keyword.rb +++ b/lib/rubocop/cop/layout/space_around_keyword.rb @@ -24,7 +24,9 @@ module Layout # end # # something = 123 if test - class SpaceAroundKeyword < Cop + class SpaceAroundKeyword < Base + extend AutoCorrector + MSG_BEFORE = 'Space before keyword `%s` is missing.' MSG_AFTER = 'Space after keyword `%s` is missing.' @@ -129,14 +131,6 @@ def on_defined?(node) check(node, [:keyword].freeze) end - def autocorrect(range) - if space_before_missing?(range) - ->(corrector) { corrector.insert_before(range, ' ') } - else - ->(corrector) { corrector.insert_after(range, ' ') } - end - end - private def check(node, locations, begin_keyword = DO) @@ -162,8 +156,11 @@ def check_begin(node, range, begin_keyword) def check_end(node, range, begin_keyword) return if begin_keyword == DO && !do?(node) + return unless space_before_missing?(range) - offense(range, MSG_BEFORE) if space_before_missing?(range) + add_offense(range, message: format(MSG_BEFORE, range: range.source)) do |corrector| + corrector.insert_before(range, ' ') + end end def do?(node) @@ -171,15 +168,17 @@ def do?(node) end def check_keyword(node, range) - offense(range, MSG_BEFORE) if space_before_missing?(range) && - !preceded_by_operator?(node, range) - offense(range, MSG_AFTER) if space_after_missing?(range) - end + if space_before_missing?(range) && !preceded_by_operator?(node, range) + add_offense(range, message: format(MSG_BEFORE, range: range.source)) do |corrector| + corrector.insert_before(range, ' ') + end + end - def offense(range, msg) - add_offense(range, - location: range, - message: format(msg, range: range.source)) + return unless space_after_missing?(range) + + add_offense(range, message: format(MSG_AFTER, range: range.source)) do |corrector| + corrector.insert_after(range, ' ') + end end def space_before_missing?(range) diff --git a/lib/rubocop/cop/layout/space_around_operators.rb b/lib/rubocop/cop/layout/space_around_operators.rb index e1a7e1922ce..0273120ca62 100644 --- a/lib/rubocop/cop/layout/space_around_operators.rb +++ b/lib/rubocop/cop/layout/space_around_operators.rb @@ -48,10 +48,11 @@ module Layout # # # good # a ** b - class SpaceAroundOperators < Cop + class SpaceAroundOperators < Base include PrecedingFollowingAlignment include RangeHelp include RationalLiteral + extend AutoCorrector IRREGULAR_METHODS = %i[[] ! []=].freeze EXCESSIVE_SPACE = ' ' @@ -132,18 +133,6 @@ def on_special_asgn(node) alias on_and_asgn on_assignment alias on_op_asgn on_special_asgn - def autocorrect(range) - lambda do |corrector| - if /\*\*/.match?(range.source) && !space_around_exponent_operator? - corrector.replace(range, '**') - elsif range.source.end_with?("\n") - corrector.replace(range, " #{range.source.strip}\n") - else - enclose_operator_with_space(corrector, range) - end - end - end - private def regular_operator?(send_node) @@ -161,7 +150,9 @@ def check_operator(type, operator, right_operand) return if with_space.source.start_with?("\n") offense(type, operator, with_space, right_operand) do |msg| - add_offense(with_space, location: operator, message: msg) + add_offense(operator, message: msg) do |corrector| + autocorrect(corrector, with_space) + end end end @@ -170,6 +161,16 @@ def offense(type, operator, with_space, right_operand) yield msg if msg end + def autocorrect(corrector, range) + if /\*\*/.match?(range.source) && !space_around_exponent_operator? + corrector.replace(range, '**') + elsif range.source.end_with?("\n") + corrector.replace(range, " #{range.source.strip}\n") + else + enclose_operator_with_space(corrector, range) + end + end + def enclose_operator_with_space(corrector, range) operator = range.source diff --git a/lib/rubocop/cop/layout/space_before_block_braces.rb b/lib/rubocop/cop/layout/space_before_block_braces.rb index 0a6a896f40f..ccc2b32eab6 100644 --- a/lib/rubocop/cop/layout/space_before_block_braces.rb +++ b/lib/rubocop/cop/layout/space_before_block_braces.rb @@ -41,9 +41,10 @@ module Layout # # # good # 7.times{} - class SpaceBeforeBlockBraces < Cop + class SpaceBeforeBlockBraces < Base include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector MISSING_MSG = 'Space missing to the left of {.' DETECTED_MSG = 'Space detected to the left of {.' @@ -75,29 +76,22 @@ def on_block(node) end end - def autocorrect(range) - lambda do |corrector| - case range.source - when /\s/ then corrector.remove(range) - else corrector.insert_before(range, ' ') - end - end - end - private def check_empty(left_brace, space_plus_brace, used_style) return if style_for_empty_braces == used_style - config_to_allow_offenses['EnforcedStyleForEmptyBraces'] = - used_style.to_s + config_to_allow_offenses['EnforcedStyleForEmptyBraces'] = used_style.to_s if style_for_empty_braces == :space - add_offense(left_brace, location: left_brace, message: MISSING_MSG) + add_offense(left_brace, message: MISSING_MSG) do |corrector| + autocorrect(corrector, left_brace) + end else - space = range_between(space_plus_brace.begin_pos, - left_brace.begin_pos) - add_offense(space, location: space, message: DETECTED_MSG) + space = range_between(space_plus_brace.begin_pos, left_brace.begin_pos) + add_offense(space, message: DETECTED_MSG) do |corrector| + autocorrect(corrector, space) + end end end @@ -110,16 +104,23 @@ def check_non_empty(left_brace, space_plus_brace, used_style) end def space_missing(left_brace) - add_offense(left_brace, location: left_brace, message: MISSING_MSG) do - opposite_style_detected + add_offense(left_brace, message: MISSING_MSG) do |corrector| + autocorrect(corrector, left_brace) end end def space_detected(left_brace, space_plus_brace) - space = range_between(space_plus_brace.begin_pos, - left_brace.begin_pos) - add_offense(space, location: space, message: DETECTED_MSG) do - opposite_style_detected + space = range_between(space_plus_brace.begin_pos, left_brace.begin_pos) + + add_offense(space, message: DETECTED_MSG) do |corrector| + autocorrect(corrector, space) + end + end + + def autocorrect(corrector, range) + case range.source + when /\s/ then corrector.remove(range) + else corrector.insert_before(range, ' ') end end diff --git a/lib/rubocop/cop/layout/space_before_comma.rb b/lib/rubocop/cop/layout/space_before_comma.rb index f241f02ce55..8d31ae37d0b 100644 --- a/lib/rubocop/cop/layout/space_before_comma.rb +++ b/lib/rubocop/cop/layout/space_before_comma.rb @@ -15,12 +15,10 @@ module Layout # [1, 2, 3] # a(1, 2) # each { |a, b| } - class SpaceBeforeComma < Cop + # + class SpaceBeforeComma < Base include SpaceBeforePunctuation - - def autocorrect(space) - PunctuationCorrector.remove_space(space) - end + extend AutoCorrector def kind(token) 'comma' if token.comma? diff --git a/lib/rubocop/cop/layout/space_before_comment.rb b/lib/rubocop/cop/layout/space_before_comment.rb index cd0825602dc..3623c4ec45b 100644 --- a/lib/rubocop/cop/layout/space_before_comment.rb +++ b/lib/rubocop/cop/layout/space_before_comment.rb @@ -12,20 +12,23 @@ module Layout # # # good # 1 + 1 # this operation does ... - class SpaceBeforeComment < Cop + class SpaceBeforeComment < Base + extend AutoCorrector + MSG = 'Put a space before an end-of-line comment.' - def investigate(processed_source) + def on_new_investigation processed_source.tokens.each_cons(2) do |token1, token2| next unless token2.comment? next unless token1.line == token2.line + next unless token1.pos.end == token2.pos.begin - add_offense(token2.pos, location: token2.pos) if token1.pos.end == token2.pos.begin - end - end + range = token2.pos - def autocorrect(range) - ->(corrector) { corrector.insert_before(range, ' ') } + add_offense(range) do |corrector| + corrector.insert_before(range, ' ') + end + end end end end diff --git a/lib/rubocop/cop/layout/space_before_first_arg.rb b/lib/rubocop/cop/layout/space_before_first_arg.rb index 994f7a6ca91..55e311f6ed6 100644 --- a/lib/rubocop/cop/layout/space_before_first_arg.rb +++ b/lib/rubocop/cop/layout/space_before_first_arg.rb @@ -21,9 +21,10 @@ module Layout # something y, z # something 'hello' # - class SpaceBeforeFirstArg < Cop + class SpaceBeforeFirstArg < Base include PrecedingFollowingAlignment include RangeHelp + extend AutoCorrector MSG = 'Put one space between the method name and ' \ 'the first argument.' @@ -39,14 +40,12 @@ def on_send(node) return if space.length == 1 return unless expect_params_after_method_name?(node) - add_offense(space, location: space) + add_offense(space) do |corrector| + corrector.replace(space, ' ') + end end alias on_csend on_send - def autocorrect(range) - ->(corrector) { corrector.replace(range, ' ') } - end - private def regular_method_call_with_arguments?(node) diff --git a/lib/rubocop/cop/layout/space_before_semicolon.rb b/lib/rubocop/cop/layout/space_before_semicolon.rb index 9be6599b019..a793b907806 100644 --- a/lib/rubocop/cop/layout/space_before_semicolon.rb +++ b/lib/rubocop/cop/layout/space_before_semicolon.rb @@ -11,12 +11,9 @@ module Layout # # # good # x = 1; y = 2 - class SpaceBeforeSemicolon < Cop + class SpaceBeforeSemicolon < Base include SpaceBeforePunctuation - - def autocorrect(space) - PunctuationCorrector.remove_space(space) - end + extend AutoCorrector def kind(token) 'semicolon' if token.semicolon? diff --git a/lib/rubocop/cop/layout/space_in_lambda_literal.rb b/lib/rubocop/cop/layout/space_in_lambda_literal.rb index 7f39424fcc1..528c7da9cac 100644 --- a/lib/rubocop/cop/layout/space_in_lambda_literal.rb +++ b/lib/rubocop/cop/layout/space_in_lambda_literal.rb @@ -19,9 +19,10 @@ module Layout # # # good # a = -> (x, y) { x + y } - class SpaceInLambdaLiteral < Cop + class SpaceInLambdaLiteral < Base include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector MSG_REQUIRE_SPACE = 'Use a space between `->` and ' \ '`(` in lambda literals.' @@ -31,24 +32,15 @@ class SpaceInLambdaLiteral < Cop def on_send(node) return unless arrow_lambda_with_args?(node) + lambda_node = range_of_offense(node) + if style == :require_space && !space_after_arrow?(node) - add_offense(node, - location: range_of_offense(node), - message: MSG_REQUIRE_SPACE) + add_offense(lambda_node, message: MSG_REQUIRE_SPACE) do |corrector| + corrector.insert_before(node.parent.children[1], ' ') + end elsif style == :require_no_space && space_after_arrow?(node) - add_offense(node, - location: range_of_offense(node), - message: MSG_REQUIRE_NO_SPACE) - end - end - - def autocorrect(lambda_node) - children = lambda_node.parent.children - lambda do |corrector| - if style == :require_space - corrector.insert_before(children[1], ' ') - else - corrector.remove(space_after_arrow(lambda_node)) + add_offense(lambda_node, message: MSG_REQUIRE_NO_SPACE) do |corrector| + corrector.remove(space_after_arrow(node)) end end end diff --git a/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb b/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb index ebd658d6047..c93c064fd2a 100644 --- a/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +++ b/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb @@ -67,9 +67,10 @@ module Layout # foo = [ ] # bar = [ ] # - class SpaceInsideArrayLiteralBrackets < Cop + class SpaceInsideArrayLiteralBrackets < Base include SurroundingSpace include ConfigurableEnforcedStyle + extend AutoCorrector MSG = '%s space inside array brackets.' EMPTY_MSG = '%s space inside empty array brackets.' @@ -86,26 +87,22 @@ def on_array(node) issue_offenses(node, left, right, start_ok, end_ok) end - def autocorrect(node) + private + + def autocorrect(corrector, node) left, right = array_brackets(node) - lambda do |corrector| - if empty_brackets?(left, right) - SpaceCorrector.empty_corrections(processed_source, corrector, - empty_config, left, right) - elsif style == :no_space - SpaceCorrector.remove_space(processed_source, corrector, - left, right) - elsif style == :space - SpaceCorrector.add_space(processed_source, corrector, left, right) - else - compact_corrections(corrector, node, left, right) - end + if empty_brackets?(left, right) + SpaceCorrector.empty_corrections(processed_source, corrector, empty_config, left, right) + elsif style == :no_space + SpaceCorrector.remove_space(processed_source, corrector, left, right) + elsif style == :space + SpaceCorrector.add_space(processed_source, corrector, left, right) + else + compact_corrections(corrector, node, left, right) end end - private - def array_brackets(node) [left_array_bracket(node), right_array_bracket(node)] end diff --git a/lib/rubocop/cop/layout/space_inside_block_braces.rb b/lib/rubocop/cop/layout/space_inside_block_braces.rb index c053cba99dc..a443d90a5b1 100644 --- a/lib/rubocop/cop/layout/space_inside_block_braces.rb +++ b/lib/rubocop/cop/layout/space_inside_block_braces.rb @@ -76,10 +76,11 @@ module Layout # # good # [1, 2, 3].each {|n| n * 2 } # - class SpaceInsideBlockBraces < Cop + class SpaceInsideBlockBraces < Base include ConfigurableEnforcedStyle include SurroundingSpace include RangeHelp + extend AutoCorrector def on_block(node) return if node.keywords? @@ -97,17 +98,6 @@ def on_block(node) check_inside(node, left_brace, right_brace) end - def autocorrect(range) - lambda do |corrector| - case range.source - when /\s/ then corrector.remove(range) - when '{}' then corrector.replace(range, '{ }') - when '{|' then corrector.replace(range, '{ |') - else corrector.insert_before(range, ' ') - end - end - end - private def check_inside(node, left_brace, right_brace) @@ -216,7 +206,9 @@ def space_inside_right_brace(right_brace) def no_space(begin_pos, end_pos, msg) if style == :space - offense(begin_pos, end_pos, msg) { opposite_style_detected } + return unless opposite_style_detected + + offense(begin_pos, end_pos, msg) else correct_style_detected end @@ -224,15 +216,24 @@ def no_space(begin_pos, end_pos, msg) def space(begin_pos, end_pos, msg) if style == :no_space - offense(begin_pos, end_pos, msg) { opposite_style_detected } + return unless opposite_style_detected + + offense(begin_pos, end_pos, msg) else correct_style_detected end end - def offense(begin_pos, end_pos, msg, &block) + def offense(begin_pos, end_pos, msg) range = range_between(begin_pos, end_pos) - add_offense(range, location: range, message: msg, &block) + add_offense(range, message: msg) do |corrector| + case range.source + when /\s/ then corrector.remove(range) + when '{}' then corrector.replace(range, '{ }') + when '{|' then corrector.replace(range, '{ |') + else corrector.insert_before(range, ' ') + end + end end def style_for_empty_braces diff --git a/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb b/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb index cff64b4b0f1..93d309037ae 100644 --- a/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +++ b/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb @@ -63,10 +63,11 @@ module Layout # foo = { } # foo = { } # - class SpaceInsideHashLiteralBraces < Cop + class SpaceInsideHashLiteralBraces < Base include SurroundingSpace include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector MSG = 'Space inside %s.' @@ -81,16 +82,6 @@ def on_hash(node) end end - def autocorrect(range) - lambda do |corrector| - case range.source - when /\s/ then corrector.remove(range) - when '{' then corrector.insert_after(range, ' ') - else corrector.insert_before(range, ' ') - end - end - end - private def hash_literal_with_braces(node) @@ -137,14 +128,20 @@ def incorrect_style_detected(token1, token2, expect_space, is_empty_braces) brace = (token1.text == '{' ? token1 : token2).pos range = expect_space ? brace : space_range(brace) - add_offense( - range, - location: range, - message: message(brace, is_empty_braces, expect_space) - ) do - style = expect_space ? :no_space : :space - ambiguous_or_unexpected_style_detected(style, - token1.text == token2.text) + + style = expect_space ? :no_space : :space + return unless ambiguous_or_unexpected_style_detected(style, token1.text == token2.text) + + add_offense(range, message: message(brace, is_empty_braces, expect_space)) do |corrector| + autocorrect(corrector, range) + end + end + + def autocorrect(corrector, range) + case range.source + when /\s/ then corrector.remove(range) + when '{' then corrector.insert_after(range, ' ') + else corrector.insert_before(range, ' ') end end diff --git a/lib/rubocop/cop/layout/space_inside_parens.rb b/lib/rubocop/cop/layout/space_inside_parens.rb index 18e9489d21f..faa20773cc2 100644 --- a/lib/rubocop/cop/layout/space_inside_parens.rb +++ b/lib/rubocop/cop/layout/space_inside_parens.rb @@ -31,34 +31,29 @@ module Layout # g = ( a + 3 ) # y() # - class SpaceInsideParens < Cop + class SpaceInsideParens < Base include SurroundingSpace include RangeHelp include ConfigurableEnforcedStyle + extend AutoCorrector MSG = 'Space inside parentheses detected.' MSG_SPACE = 'No space inside parentheses detected.' - def investigate(processed_source) + def on_new_investigation @processed_source = processed_source if style == :space each_missing_space(processed_source.tokens) do |range| - add_offense(range, location: range, message: MSG_SPACE) + add_offense(range, message: MSG_SPACE) do |corrector| + corrector.insert_before(range, ' ') + end end else each_extraneous_space(processed_source.tokens) do |range| - add_offense(range, location: range) - end - end - end - - def autocorrect(range) - lambda do |corrector| - if style == :space - corrector.insert_before(range, ' ') - else - corrector.remove(range) + add_offense(range) do |corrector| + corrector.remove(range) + end end end end diff --git a/lib/rubocop/cop/layout/space_inside_range_literal.rb b/lib/rubocop/cop/layout/space_inside_range_literal.rb index c061481b6cd..4419dba0fd7 100644 --- a/lib/rubocop/cop/layout/space_inside_range_literal.rb +++ b/lib/rubocop/cop/layout/space_inside_range_literal.rb @@ -17,7 +17,9 @@ module Layout # # # good # 'a'..'z' - class SpaceInsideRangeLiteral < Cop + class SpaceInsideRangeLiteral < Base + extend AutoCorrector + MSG = 'Space inside range literal.' def on_irange(node) @@ -28,21 +30,6 @@ def on_erange(node) check(node) end - def autocorrect(node) - expression = node.source - operator = node.loc.operator.source - operator_escaped = operator.gsub(/\./, '\.') - - lambda do |corrector| - corrector.replace( - node, - expression - .sub(/\s+#{operator_escaped}/, operator) - .sub(/#{operator_escaped}\s+/, operator) - ) - end - end - private def check(node) @@ -55,7 +42,11 @@ def check(node) return unless /(\s#{escaped_op})|(#{escaped_op}\s)/.match?(expression) - add_offense(node) + add_offense(node) do |corrector| + corrector.replace( + node, expression.sub(/\s+#{escaped_op}/, op).sub(/#{escaped_op}\s+/, op) + ) + end end end end diff --git a/lib/rubocop/cop/layout/space_inside_reference_brackets.rb b/lib/rubocop/cop/layout/space_inside_reference_brackets.rb index 05d30e94569..e1240c96657 100644 --- a/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +++ b/lib/rubocop/cop/layout/space_inside_reference_brackets.rb @@ -53,9 +53,10 @@ module Layout # # good # foo[ ] # - class SpaceInsideReferenceBrackets < Cop + class SpaceInsideReferenceBrackets < Base include SurroundingSpace include ConfigurableEnforcedStyle + extend AutoCorrector MSG = '%s space inside reference brackets.' EMPTY_MSG = '%s space inside empty reference brackets.' @@ -83,24 +84,20 @@ def on_send(node) end end - def autocorrect(node) - lambda do |corrector| - left, right = reference_brackets(node) - - if empty_brackets?(left, right) - SpaceCorrector.empty_corrections(processed_source, corrector, - empty_config, left, right) - elsif style == :no_space - SpaceCorrector.remove_space(processed_source, corrector, - left, right) - else - SpaceCorrector.add_space(processed_source, corrector, left, right) - end + private + + def autocorrect(corrector, node) + left, right = reference_brackets(node) + + if empty_brackets?(left, right) + SpaceCorrector.empty_corrections(processed_source, corrector, empty_config, left, right) + elsif style == :no_space + SpaceCorrector.remove_space(processed_source, corrector, left, right) + else + SpaceCorrector.add_space(processed_source, corrector, left, right) end end - private - def reference_brackets(node) tokens = tokens(node) left = left_ref_bracket(node, tokens) diff --git a/lib/rubocop/cop/layout/space_inside_string_interpolation.rb b/lib/rubocop/cop/layout/space_inside_string_interpolation.rb index 83833203dda..44e09d2dde6 100644 --- a/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +++ b/lib/rubocop/cop/layout/space_inside_string_interpolation.rb @@ -18,11 +18,12 @@ module Layout # # # good # var = "This is the #{ space } example" - class SpaceInsideStringInterpolation < Cop + class SpaceInsideStringInterpolation < Base include Interpolation include SurroundingSpace include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector NO_SPACE_MSG = 'Space inside string interpolation detected.' SPACE_MSG = 'Missing space inside string interpolation detected.' @@ -40,20 +41,18 @@ def on_interpolation(begin_node) end end - def autocorrect(begin_node) - lambda do |corrector| - delims = delimiters(begin_node) + private + + def autocorrect(corrector, begin_node) + delims = delimiters(begin_node) - if style == :no_space - SpaceCorrector.remove_space(processed_source, corrector, *delims) - else - SpaceCorrector.add_space(processed_source, corrector, *delims) - end + if style == :no_space + SpaceCorrector.remove_space(processed_source, corrector, *delims) + else + SpaceCorrector.add_space(processed_source, corrector, *delims) end end - private - def delimiters(begin_node) left = processed_source.tokens[index_of_first_token(begin_node)] right = processed_source.tokens[index_of_last_token(begin_node)] diff --git a/lib/rubocop/cop/layout/trailing_empty_lines.rb b/lib/rubocop/cop/layout/trailing_empty_lines.rb index 3285b6fd0b2..d102cf87694 100644 --- a/lib/rubocop/cop/layout/trailing_empty_lines.rb +++ b/lib/rubocop/cop/layout/trailing_empty_lines.rb @@ -37,11 +37,12 @@ module Layout # class Foo; end # # EOF # - class TrailingEmptyLines < Cop + class TrailingEmptyLines < Base include ConfigurableEnforcedStyle include RangeHelp + extend AutoCorrector - def investigate(processed_source) + def on_new_investigation buffer = processed_source.buffer return if buffer.source.empty? @@ -57,28 +58,22 @@ def investigate(processed_source) return unless blank_lines != wanted_blank_lines - offense_detected(buffer, wanted_blank_lines, blank_lines, - whitespace_at_end) - end - - def autocorrect(range) - lambda do |corrector| - corrector.replace(range, style == :final_newline ? "\n" : "\n\n") - end + offense_detected(buffer, wanted_blank_lines, blank_lines, whitespace_at_end) end private - def offense_detected(buffer, wanted_blank_lines, blank_lines, - whitespace_at_end) + def offense_detected(buffer, wanted_blank_lines, blank_lines, whitespace_at_end) begin_pos = buffer.source.length - whitespace_at_end.length autocorrect_range = range_between(begin_pos, buffer.source.length) begin_pos += 1 unless whitespace_at_end.empty? report_range = range_between(begin_pos, buffer.source.length) - add_offense(autocorrect_range, - location: report_range, - message: message(wanted_blank_lines, blank_lines)) + add_offense( + report_range, message: message(wanted_blank_lines, blank_lines) + ) do |corrector| + corrector.replace(autocorrect_range, style == :final_newline ? "\n" : "\n\n") + end end def ends_in_end?(processed_source) diff --git a/lib/rubocop/cop/layout/trailing_whitespace.rb b/lib/rubocop/cop/layout/trailing_whitespace.rb index 6052b3b8399..143492fa976 100644 --- a/lib/rubocop/cop/layout/trailing_whitespace.rb +++ b/lib/rubocop/cop/layout/trailing_whitespace.rb @@ -28,12 +28,13 @@ module Layout # x = 0 # RUBY # - class TrailingWhitespace < Cop + class TrailingWhitespace < Base include RangeHelp + extend AutoCorrector MSG = 'Trailing whitespace detected.' - def investigate(processed_source) + def on_new_investigation heredoc_ranges = extract_heredoc_ranges(processed_source.ast) processed_source.lines.each_with_index do |line, index| lineno = index + 1 @@ -41,18 +42,13 @@ def investigate(processed_source) next unless line.end_with?(' ', "\t") next if skip_heredoc? && inside_heredoc?(heredoc_ranges, lineno) - range = source_range(processed_source.buffer, - lineno, - (line.rstrip.length)...(line.length)) - - add_offense(range, location: range) + range = offense_range(lineno, line) + add_offense(range) do |corrector| + corrector.remove(range) + end end end - def autocorrect(range) - ->(corrector) { corrector.remove(range) } - end - private def skip_heredoc? @@ -71,6 +67,10 @@ def extract_heredoc_ranges(ast) (body.first_line...body.last_line) end end + + def offense_range(lineno, line) + source_range(processed_source.buffer, lineno, (line.rstrip.length)...(line.length)) + end end end end diff --git a/lib/rubocop/cop/lint/duplicate_rescue_exception.rb b/lib/rubocop/cop/lint/duplicate_rescue_exception.rb index ad03f4a01cd..e06b9eb61fc 100644 --- a/lib/rubocop/cop/lint/duplicate_rescue_exception.rb +++ b/lib/rubocop/cop/lint/duplicate_rescue_exception.rb @@ -43,17 +43,6 @@ def on_rescue(node) end end end - - private - - def rescued_exceptions(resbody) - rescue_group, = *resbody - if rescue_group - rescue_group.values - else - [] - end - end end end end diff --git a/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb b/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb index 2ea3f47bb50..d645b1a7c65 100644 --- a/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +++ b/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb @@ -25,44 +25,11 @@ class MixedRegexpCaptureTypes < Base 'in a Regexp literal.' def on_regexp(node) - return if contain_non_literal?(node) - - begin - tree = Regexp::Parser.parse(node.content) - # Returns if a regular expression that cannot be processed by regexp_parser gem. - # https://github.com/rubocop-hq/rubocop/issues/8083 - rescue Regexp::Scanner::ScannerError - return - end - - return unless named_capture?(tree) - return unless numbered_capture?(tree) + return if node.each_capture(named: false).none? + return if node.each_capture(named: true).none? add_offense(node) end - - private - - def contain_non_literal?(node) - if node.respond_to?(:type) && (node.variable? || node.send_type? || node.const_type?) - return true - end - return false unless node.respond_to?(:children) - - node.children.any? { |child| contain_non_literal?(child) } - end - - def named_capture?(tree) - tree.each_expression.any? do |e| - e.instance_of?(Regexp::Expression::Group::Capture) - end - end - - def numbered_capture?(tree) - tree.each_expression.any? do |e| - e.instance_of?(Regexp::Expression::Group::Named) - end - end end end end diff --git a/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb b/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb index a5f87ae2c0c..3843da78f2b 100644 --- a/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +++ b/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb @@ -64,25 +64,15 @@ def on_nth_ref(node) private - def check_regexp(regexp) - return if contain_non_literal?(regexp) - - tree = Regexp::Parser.parse(regexp.content) - @valid_ref = regexp_captures(tree) - end - - def contain_non_literal?(node) - node.children.size != 2 || !node.children.first.str_type? - end - - def regexp_captures(tree) - named_capture = numbered_capture = 0 - tree.each_expression do |e| - if e.type?(:group) - e.respond_to?(:name) ? named_capture += 1 : numbered_capture += 1 - end - end - named_capture.positive? ? named_capture : numbered_capture + def check_regexp(node) + return if node.interpolation? + + named_capture = node.each_capture(named: true).count + @valid_ref = if named_capture.positive? + named_capture + else + node.each_capture(named: false).count + end end end end diff --git a/lib/rubocop/cop/lint/top_level_return_with_argument.rb b/lib/rubocop/cop/lint/top_level_return_with_argument.rb index 54c40557cc4..48c3dffa914 100644 --- a/lib/rubocop/cop/lint/top_level_return_with_argument.rb +++ b/lib/rubocop/cop/lint/top_level_return_with_argument.rb @@ -11,7 +11,7 @@ module Lint # # # Detected since Ruby 2.7 # return 1 # 1 is always ignored. - class TopLevelReturnWithArgument < Cop + class TopLevelReturnWithArgument < Base # This cop works by validating the ancestors of the return node. A # top-level return node's ancestors should not be of block, def, or # defs type. diff --git a/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb b/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb index 75eb867ebc0..18244df18c4 100644 --- a/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +++ b/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb @@ -31,6 +31,8 @@ def self.calculate(node) # TODO: move to rubocop-ast ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze + private_constant :BRANCH_NODES, :CONDITION_NODES, :ARGUMENT_TYPES + def initialize(node) @assignment = 0 @branch = 0 diff --git a/lib/rubocop/cop/metrics/utils/code_length_calculator.rb b/lib/rubocop/cop/metrics/utils/code_length_calculator.rb index 924dc802d10..f67256c6c77 100644 --- a/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +++ b/lib/rubocop/cop/metrics/utils/code_length_calculator.rb @@ -11,6 +11,7 @@ class CodeLengthCalculator FOLDABLE_TYPES = %i[array hash heredoc].freeze CLASSLIKE_TYPES = %i[class module].freeze + private_constant :FOLDABLE_TYPES, :CLASSLIKE_TYPES def initialize(node, processed_source, count_comments: false, foldable_types: []) @node = node diff --git a/lib/rubocop/cop/mixin/alignment.rb b/lib/rubocop/cop/mixin/alignment.rb index 7fc9a9f66c7..e639794320c 100644 --- a/lib/rubocop/cop/mixin/alignment.rb +++ b/lib/rubocop/cop/mixin/alignment.rb @@ -42,6 +42,7 @@ def check_alignment(items, base_column = nil) end end + # @api private def each_bad_alignment(items, base_column) prev_line = -1 items.each do |current| @@ -55,11 +56,13 @@ def each_bad_alignment(items, base_column) end end + # @api public def display_column(range) line = processed_source.lines[range.line - 1] Unicode::DisplayWidth.of(line[0, range.column]) end + # @api public def within?(inner, outer) inner.begin_pos >= outer.begin_pos && inner.end_pos <= outer.end_pos end diff --git a/lib/rubocop/cop/mixin/allowed_methods.rb b/lib/rubocop/cop/mixin/allowed_methods.rb index 69931699e14..8be7a048c09 100644 --- a/lib/rubocop/cop/mixin/allowed_methods.rb +++ b/lib/rubocop/cop/mixin/allowed_methods.rb @@ -7,10 +7,12 @@ module Cop module AllowedMethods private + # @api public def allowed_method?(name) allowed_methods.include?(name.to_s) end + # @api public def allowed_methods cop_config.fetch('AllowedMethods', []) end diff --git a/lib/rubocop/cop/mixin/annotation_comment.rb b/lib/rubocop/cop/mixin/annotation_comment.rb index 6da65e9412e..9a17926549b 100644 --- a/lib/rubocop/cop/mixin/annotation_comment.rb +++ b/lib/rubocop/cop/mixin/annotation_comment.rb @@ -7,12 +7,14 @@ module Style module AnnotationComment private + # @api public def annotation?(comment) _margin, first_word, colon, space, note = split_comment(comment) keyword_appearance?(first_word, colon, space) && !just_first_word_of_sentence?(first_word, colon, space, note) end + # @api public def split_comment(comment) match = comment.text.match(/^(# ?)([A-Za-z]+)(\s*:)?(\s+)?(\S+)?/) return false unless match @@ -20,14 +22,17 @@ def split_comment(comment) match.captures end + # @api public def keyword_appearance?(first_word, colon, space) first_word && keyword?(first_word.upcase) && (colon || space) end + # @api private def just_first_word_of_sentence?(first_word, colon, space, note) first_word == first_word.capitalize && !colon && space && note end + # @api public def keyword?(word) config.for_cop('Style/CommentAnnotation')['Keywords'].include?(word) end diff --git a/lib/rubocop/cop/mixin/check_line_breakable.rb b/lib/rubocop/cop/mixin/check_line_breakable.rb index 826df189c6c..8f6b0871a7a 100644 --- a/lib/rubocop/cop/mixin/check_line_breakable.rb +++ b/lib/rubocop/cop/mixin/check_line_breakable.rb @@ -54,6 +54,7 @@ def extract_breakable_node(node, max) private + # @api private def extract_breakable_node_from_elements(node, elements, max) return unless breakable_collection?(node, elements) return if safe_to_ignore?(node) @@ -65,6 +66,7 @@ def extract_breakable_node_from_elements(node, elements, max) extract_first_element_over_column_limit(node, elements, max) end + # @api private def extract_first_element_over_column_limit(node, elements, max) line = node.first_line i = 0 @@ -74,10 +76,12 @@ def extract_first_element_over_column_limit(node, elements, max) elements[i - 1] end + # @api private def within_column_limit?(element, max, line) element && element.loc.column < max && element.loc.line == line end + # @api private def safe_to_ignore?(node) return true unless max return true if already_on_multiple_lines?(node) @@ -93,6 +97,7 @@ def safe_to_ignore?(node) false end + # @api private def breakable_collection?(node, elements) # For simplicity we only want to insert breaks in normal # hashes wrapped in a set of curly braces like {foo: 1}. @@ -109,6 +114,7 @@ def breakable_collection?(node, elements) starts_with_bracket && has_second_element end + # @api private def contained_by_breakable_collection_on_same_line?(node) node.each_ancestor.find do |ancestor| # Ignore ancestors on different lines. @@ -128,6 +134,7 @@ def contained_by_breakable_collection_on_same_line?(node) false end + # @api private def contained_by_multiline_collection_that_could_be_broken_up?(node) node.each_ancestor.find do |ancestor| if (ancestor.hash_type? || ancestor.array_type?) && @@ -144,6 +151,7 @@ def contained_by_multiline_collection_that_could_be_broken_up?(node) false end + # @api private def children_could_be_broken_up?(children) return false if all_on_same_line?(children) @@ -156,12 +164,14 @@ def children_could_be_broken_up?(children) false end + # @api private def all_on_same_line?(nodes) return true if nodes.empty? nodes.first.first_line == nodes.last.last_line end + # @api private def process_args(args) # If there is a trailing hash arg without explicit braces, like this: # @@ -174,6 +184,7 @@ def process_args(args) args end + # @api private def already_on_multiple_lines?(node) node.first_line != node.last_line end diff --git a/lib/rubocop/cop/mixin/first_element_line_break.rb b/lib/rubocop/cop/mixin/first_element_line_break.rb index be595c9333b..3420fe657cc 100644 --- a/lib/rubocop/cop/mixin/first_element_line_break.rb +++ b/lib/rubocop/cop/mixin/first_element_line_break.rb @@ -31,7 +31,9 @@ def check_children_line_break(node, children, start = node) max = last_by_line(children) return if line == max.last_line - add_offense(min) + add_offense(min) do |corrector| + EmptyLineCorrector.insert_before(corrector, min) + end end def first_by_line(nodes) diff --git a/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb b/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb index 3c861664631..d34d4d95647 100644 --- a/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +++ b/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb @@ -16,7 +16,9 @@ def check_line_breaks(_node, children) last_seen_line = -1 children.each do |child| if last_seen_line >= child.first_line - add_offense(child) + add_offense(child) do |corrector| + EmptyLineCorrector.insert_before(corrector, child) + end else last_seen_line = child.last_line end diff --git a/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb b/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb index f7491cba288..e93219400ac 100644 --- a/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +++ b/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb @@ -43,30 +43,32 @@ def check(node) def check_new_line(node) return unless closing_brace_on_same_line?(node) - add_offense(node, - location: :end, - message: self.class::ALWAYS_NEW_LINE_MESSAGE) + add_offense(node.loc.end, message: self.class::ALWAYS_NEW_LINE_MESSAGE) do |corrector| + MultilineLiteralBraceCorrector.correct(corrector, node, processed_source) + end end def check_same_line(node) return if closing_brace_on_same_line?(node) - add_offense(node, - location: :end, - message: self.class::ALWAYS_SAME_LINE_MESSAGE) + add_offense(node.loc.end, message: self.class::ALWAYS_SAME_LINE_MESSAGE) do |corrector| + MultilineLiteralBraceCorrector.correct(corrector, node, processed_source) + end end def check_symmetrical(node) if opening_brace_on_same_line?(node) return if closing_brace_on_same_line?(node) - add_offense(node, location: :end, - message: self.class::SAME_LINE_MESSAGE) + add_offense(node.loc.end, message: self.class::SAME_LINE_MESSAGE) do |corrector| + MultilineLiteralBraceCorrector.correct(corrector, node, processed_source) + end else return unless closing_brace_on_same_line?(node) - add_offense(node, location: :end, - message: self.class::NEW_LINE_MESSAGE) + add_offense(node.loc.end, message: self.class::NEW_LINE_MESSAGE) do |corrector| + MultilineLiteralBraceCorrector.correct(corrector, node, processed_source) + end end end diff --git a/lib/rubocop/cop/mixin/percent_array.rb b/lib/rubocop/cop/mixin/percent_array.rb index 8d9804114a9..76190ca1707 100644 --- a/lib/rubocop/cop/mixin/percent_array.rb +++ b/lib/rubocop/cop/mixin/percent_array.rb @@ -34,14 +34,25 @@ def comments_in_array?(node) def check_percent_array(node) array_style_detected(:percent, node.values.size) - add_offense(node) if style == :brackets + + return unless style == :brackets + + add_offense(node) do |corrector| + correct_bracketed(corrector, node) + end end - def check_bracketed_array(node) + def check_bracketed_array(node, literal_prefix) return if allowed_bracket_array?(node) array_style_detected(:brackets, node.values.size) - add_offense(node) if style == :percent + + return unless style == :percent + + add_offense(node) do |corrector| + percent_literal_corrector = PercentLiteralCorrector.new(@config, @preferred_delimiters) + percent_literal_corrector.correct(corrector, node, literal_prefix) + end end end end diff --git a/lib/rubocop/cop/mixin/regexp_literal_help.rb b/lib/rubocop/cop/mixin/regexp_literal_help.rb index bba68606e9a..5f00caa6b7b 100644 --- a/lib/rubocop/cop/mixin/regexp_literal_help.rb +++ b/lib/rubocop/cop/mixin/regexp_literal_help.rb @@ -27,7 +27,7 @@ def source_with_comments_and_interpolations_blanked(child, freespace_mode) # part of the pattern source, but need to preserve their width, to allow offsets to # correctly line up with the original source: spaces have no effect, and preserve width. if child.begin_type? - replace_match_with_spaces(source, /.*/) # replace all content + replace_match_with_spaces(source, /.*/m) # replace all content elsif freespace_mode replace_match_with_spaces(source, /(?s.' - def investigate(processed_source) + def on_new_investigation each_missing_space(processed_source.tokens) do |token| - add_offense(token, location: token.pos, - message: format(MSG, token: kind(token))) + add_offense(token.pos, message: format(MSG, token: kind(token))) do |corrector| + PunctuationCorrector.add_space(corrector, token) + end end end diff --git a/lib/rubocop/cop/mixin/space_before_punctuation.rb b/lib/rubocop/cop/mixin/space_before_punctuation.rb index 9d8adc11b9d..c7d133a6150 100644 --- a/lib/rubocop/cop/mixin/space_before_punctuation.rb +++ b/lib/rubocop/cop/mixin/space_before_punctuation.rb @@ -9,10 +9,11 @@ module SpaceBeforePunctuation MSG = 'Space found before %s.' - def investigate(processed_source) + def on_new_investigation each_missing_space(processed_source.tokens) do |token, pos_before| - add_offense(pos_before, location: pos_before, - message: format(MSG, token: kind(token))) + add_offense(pos_before, message: format(MSG, token: kind(token))) do |corrector| + PunctuationCorrector.remove_space(corrector, pos_before) + end end end diff --git a/lib/rubocop/cop/mixin/surrounding_space.rb b/lib/rubocop/cop/mixin/surrounding_space.rb index 254cccd74cf..bc58cbb317c 100644 --- a/lib/rubocop/cop/mixin/surrounding_space.rb +++ b/lib/rubocop/cop/mixin/surrounding_space.rb @@ -81,8 +81,11 @@ def reposition(src, pos, step) def space_offense(node, token, side, message, command) range = side_space_range(range: token.pos, side: side) - add_offense(node, location: range, - message: format(message, command: command)) + add_offense(range, message: format(message, command: command)) do |corrector| + autocorrect(corrector, node) unless ignored_node?(node) + + ignore_node(node) + end end def empty_offenses(node, left, right, message) @@ -96,8 +99,9 @@ def empty_offenses(node, left, right, message) end def empty_offense(node, range, message, command) - add_offense(node, location: range, - message: format(message, command: command)) + add_offense(range, message: format(message, command: command)) do |corrector| + autocorrect(corrector, node) + end end def empty_brackets?(left_bracket_token, right_bracket_token) diff --git a/lib/rubocop/cop/offense.rb b/lib/rubocop/cop/offense.rb index dee95e60ca1..0da21b0fe6d 100644 --- a/lib/rubocop/cop/offense.rb +++ b/lib/rubocop/cop/offense.rb @@ -64,6 +64,7 @@ class Offense PseudoSourceRange = Struct.new(:line, :column, :source_line, :begin_pos, :end_pos) + private_constant :PseudoSourceRange NO_LOCATION = PseudoSourceRange.new(1, 0, '', 0, 1).freeze diff --git a/lib/rubocop/cop/severity.rb b/lib/rubocop/cop/severity.rb index 19d0c031a5f..c324f5e7543 100644 --- a/lib/rubocop/cop/severity.rb +++ b/lib/rubocop/cop/severity.rb @@ -6,7 +6,6 @@ module Cop class Severity include Comparable - # @api private NAMES = %i[refactor convention warning error fatal].freeze # @api private @@ -22,7 +21,6 @@ class Severity # any of `:refactor`, `:convention`, `:warning`, `:error` or `:fatal`. attr_reader :name - # @api private def self.name_from_code(code) name = code.to_sym CODE_TABLE[name] || name @@ -37,22 +35,18 @@ def initialize(name_or_code) freeze end - # @api private def to_s @name.to_s end - # @api private def code @name.to_s[0].upcase end - # @api private def level NAMES.index(name) + 1 end - # @api private def ==(other) @name == if other.is_a?(Symbol) other @@ -61,12 +55,10 @@ def ==(other) end end - # @api private def hash @name.hash end - # @api private def <=>(other) level <=> other.level end diff --git a/lib/rubocop/cop/style/case_like_if.rb b/lib/rubocop/cop/style/case_like_if.rb index 3fe33176a8a..2f9259763b2 100644 --- a/lib/rubocop/cop/style/case_like_if.rb +++ b/lib/rubocop/cop/style/case_like_if.rb @@ -115,7 +115,7 @@ def find_target_in_send_node(node) def find_target_in_equality_node(node) argument = node.arguments.first receiver = node.receiver - return unless receiver + return unless argument && receiver if argument.literal? || const_reference?(argument) receiver diff --git a/lib/rubocop/cop/style/guard_clause.rb b/lib/rubocop/cop/style/guard_clause.rb index ed88d58cc44..77cfafc5d7e 100644 --- a/lib/rubocop/cop/style/guard_clause.rb +++ b/lib/rubocop/cop/style/guard_clause.rb @@ -17,6 +17,7 @@ module Style # # good # def test # return unless something + # # work # end # diff --git a/lib/rubocop/cop/style/method_called_on_do_end_block.rb b/lib/rubocop/cop/style/method_called_on_do_end_block.rb index 3b1817b6cce..886a131bfa8 100644 --- a/lib/rubocop/cop/style/method_called_on_do_end_block.rb +++ b/lib/rubocop/cop/style/method_called_on_do_end_block.rb @@ -8,10 +8,19 @@ module Style # when reading code. # # @example - # + # # bad # a do # b # end.c + # + # # good + # a { b }.c + # + # # good + # foo = a do + # b + # end + # foo.c class MethodCalledOnDoEndBlock < Base include RangeHelp diff --git a/lib/rubocop/cop/style/redundant_regexp_character_class.rb b/lib/rubocop/cop/style/redundant_regexp_character_class.rb index 671afd0fcb1..369b8130295 100644 --- a/lib/rubocop/cop/style/redundant_regexp_character_class.rb +++ b/lib/rubocop/cop/style/redundant_regexp_character_class.rb @@ -21,9 +21,10 @@ module Style # # # good # r = /[ab]/ - class RedundantRegexpCharacterClass < Cop + class RedundantRegexpCharacterClass < Base include MatchRange include RegexpLiteralHelp + extend AutoCorrector MSG_REDUNDANT_CHARACTER_CLASS = 'Redundant single-element character class, ' \ '`%s` can be replaced with `%s`.' @@ -48,33 +49,25 @@ def on_regexp(node) next if whitespace_in_free_space_mode?(node, loc) add_offense( - node, - location: loc, - message: format( + loc, message: format( MSG_REDUNDANT_CHARACTER_CLASS, char_class: loc.source, element: without_character_class(loc) ) - ) - end - end - - def autocorrect(node) - lambda do |corrector| - each_redundant_character_class(node) do |loc| + ) do |corrector| corrector.replace(loc, without_character_class(loc)) end end end + private + def each_redundant_character_class(node) pattern_source(node).scan(PATTERN) do yield match_range(node.loc.begin.end, Regexp.last_match) end end - private - def without_character_class(loc) loc.source[1..-2] end diff --git a/lib/rubocop/cop/style/redundant_regexp_escape.rb b/lib/rubocop/cop/style/redundant_regexp_escape.rb index 4c370fe613f..43b4b56d328 100644 --- a/lib/rubocop/cop/style/redundant_regexp_escape.rb +++ b/lib/rubocop/cop/style/redundant_regexp_escape.rb @@ -32,13 +32,14 @@ module Style # # # good # /[+\-]\d/ - class RedundantRegexpEscape < Cop + class RedundantRegexpEscape < Base include RangeHelp include RegexpLiteralHelp + extend AutoCorrector MSG_REDUNDANT_ESCAPE = 'Redundant escape inside regexp literal' - ALLOWED_ALWAYS_ESCAPES = ' []^\\#'.chars.freeze + ALLOWED_ALWAYS_ESCAPES = " \n[]^\\#".chars.freeze ALLOWED_WITHIN_CHAR_CLASS_METACHAR_ESCAPES = '-'.chars.freeze ALLOWED_OUTSIDE_CHAR_CLASS_METACHAR_ESCAPES = '.*+?{}()|$'.chars.freeze @@ -46,19 +47,9 @@ def on_regexp(node) each_escape(node) do |char, index, within_character_class| next if allowed_escape?(node, char, within_character_class) - add_offense( - node, - location: escape_range_at_index(node, index), - message: MSG_REDUNDANT_ESCAPE - ) - end - end - - def autocorrect(node) - lambda do |corrector| - each_escape(node) do |char, index, within_character_class| - next if allowed_escape?(node, char, within_character_class) + location = escape_range_at_index(node, index) + add_offense(location, message: MSG_REDUNDANT_ESCAPE) do |corrector| corrector.remove_leading(escape_range_at_index(node, index), 1) end end diff --git a/lib/rubocop/cop/style/symbol_array.rb b/lib/rubocop/cop/style/symbol_array.rb index 142913892b8..9e1fa97b089 100644 --- a/lib/rubocop/cop/style/symbol_array.rb +++ b/lib/rubocop/cop/style/symbol_array.rb @@ -27,11 +27,12 @@ module Style # # # bad # %i[foo bar baz] - class SymbolArray < Cop + class SymbolArray < Base include ArrayMinSize include ArraySyntax include ConfigurableEnforcedStyle include PercentArray + extend AutoCorrector PERCENT_MSG = 'Use `%i` or `%I` for an array of symbols.' ARRAY_MSG = 'Use `[]` for an array of symbols.' @@ -44,22 +45,12 @@ def on_array(node) if bracketed_array_of?(:sym, node) return if symbols_contain_spaces?(node) - check_bracketed_array(node) + check_bracketed_array(node, 'i') elsif node.percent_literal?(:symbol) check_percent_array(node) end end - def autocorrect(node) - if style == :percent - PercentLiteralCorrector - .new(@config, @preferred_delimiters) - .correct(node, 'i') - else - correct_bracketed(node) - end - end - private def symbols_contain_spaces?(node) @@ -69,7 +60,7 @@ def symbols_contain_spaces?(node) end end - def correct_bracketed(node) + def correct_bracketed(corrector, node) syms = node.children.map do |c| if c.dsym_type? string_literal = to_string_literal(c.source) @@ -80,9 +71,7 @@ def correct_bracketed(node) end end - lambda do |corrector| - corrector.replace(node, "[#{syms.join(', ')}]") - end + corrector.replace(node, "[#{syms.join(', ')}]") end def to_symbol_literal(string) diff --git a/lib/rubocop/cop/style/word_array.rb b/lib/rubocop/cop/style/word_array.rb index f3f99cbac56..efa605115a7 100644 --- a/lib/rubocop/cop/style/word_array.rb +++ b/lib/rubocop/cop/style/word_array.rb @@ -27,11 +27,12 @@ module Style # # # bad # %w[foo bar baz] - class WordArray < Cop + class WordArray < Base include ArrayMinSize include ArraySyntax include ConfigurableEnforcedStyle include PercentArray + extend AutoCorrector PERCENT_MSG = 'Use `%w` or `%W` for an array of words.' ARRAY_MSG = 'Use `[]` for an array of words.' @@ -44,31 +45,14 @@ def on_array(node) if bracketed_array_of?(:str, node) return if complex_content?(node.values) - check_bracketed_array(node) + check_bracketed_array(node, 'w') elsif node.percent_literal?(:string) check_percent_array(node) end end - def autocorrect(node) - if style == :percent - PercentLiteralCorrector - .new(@config, @preferred_delimiters) - .correct(node, 'w') - else - correct_bracketed(node) - end - end - private - def check_bracketed_array(node) - return if allowed_bracket_array?(node) - - array_style_detected(:brackets, node.values.size) - add_offense(node) if style == :percent - end - def complex_content?(strings) strings.any? do |s| string = s.str_content.dup.force_encoding(::Encoding::UTF_8) @@ -81,7 +65,7 @@ def word_regex Regexp.new(cop_config['WordRegex']) end - def correct_bracketed(node) + def correct_bracketed(corrector, node) words = node.children.map do |word| if word.dstr_type? string_literal = to_string_literal(word.source) @@ -92,9 +76,7 @@ def correct_bracketed(node) end end - lambda do |corrector| - corrector.replace(node, "[#{words.join(', ')}]") - end + corrector.replace(node, "[#{words.join(', ')}]") end end end diff --git a/lib/rubocop/cop/utils/format_string.rb b/lib/rubocop/cop/utils/format_string.rb index 96de5f8f18f..4d130b6dca5 100644 --- a/lib/rubocop/cop/utils/format_string.rb +++ b/lib/rubocop/cop/utils/format_string.rb @@ -111,11 +111,9 @@ def max_digit_dollar_num private def parse - @source.to_enum(:scan, SEQUENCE).map do - FormatSequence.new( - Regexp.last_match - ) - end + matches = [] + @source.scan(SEQUENCE) { matches << FormatSequence.new(Regexp.last_match) } + matches end def mixed_formats? diff --git a/lib/rubocop/cop/variable_force.rb b/lib/rubocop/cop/variable_force.rb index 44a6f6982a6..0761c85be31 100644 --- a/lib/rubocop/cop/variable_force.rb +++ b/lib/rubocop/cop/variable_force.rb @@ -22,6 +22,8 @@ module Cop # # def after_declaring_variable(variable, variable_table) # end + # + # @api private class VariableForce < Force # rubocop:disable Metrics/ClassLength VARIABLE_ASSIGNMENT_TYPE = :lvasgn REGEXP_NAMED_CAPTURE_TYPE = :match_with_lvasgn diff --git a/lib/rubocop/cops_documentation_generator.rb b/lib/rubocop/cops_documentation_generator.rb index 9dffe47ceac..7f4efbf062b 100644 --- a/lib/rubocop/cops_documentation_generator.rb +++ b/lib/rubocop/cops_documentation_generator.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true # Class for generating documentation of all cops departments +# @api private class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength include ::RuboCop::Cop::Documentation # This class will only generate documentation for cops that belong to one of diff --git a/lib/rubocop/ext/regexp_node.rb b/lib/rubocop/ext/regexp_node.rb new file mode 100644 index 00000000000..3c8ede20f42 --- /dev/null +++ b/lib/rubocop/ext/regexp_node.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module RuboCop + module Ext + # Extensions to AST::RegexpNode for our cached parsed regexp info + module RegexpNode + ANY = Object.new + def ANY.==(_) + true + end + private_constant :ANY + + class << self + attr_reader :parsed_cache + end + @parsed_cache = {} + + # @return [Regexp::Expression::Root, nil] + def parsed_tree + return if interpolation? + + str = content + Ext::RegexpNode.parsed_cache[str] ||= begin + Regexp::Parser.parse(str) + rescue StandardError + nil + end + end + + def each_capture(named: ANY) + return enum_for(__method__, named: named) unless block_given? + + parsed_tree&.traverse do |event, exp, _index| + yield(exp) if event == :enter && + named == exp.respond_to?(:name) && + exp.respond_to?(:capturing?) && + exp.capturing? + end + + self + end + + AST::RegexpNode.include self + end + end +end diff --git a/lib/rubocop/file_finder.rb b/lib/rubocop/file_finder.rb index 1d4522f41ec..c550bed0c8c 100644 --- a/lib/rubocop/file_finder.rb +++ b/lib/rubocop/file_finder.rb @@ -4,6 +4,7 @@ module RuboCop # Common methods for finding files. + # @api private module FileFinder def self.root_level=(level) @root_level = level diff --git a/lib/rubocop/name_similarity.rb b/lib/rubocop/name_similarity.rb index 4289ddcff09..e597f895ae8 100644 --- a/lib/rubocop/name_similarity.rb +++ b/lib/rubocop/name_similarity.rb @@ -2,6 +2,7 @@ module RuboCop # Common functionality for finding names that are similar to a given name. + # @api private module NameSimilarity module_function diff --git a/lib/rubocop/options.rb b/lib/rubocop/options.rb index 301c6df85a4..77b1a08530b 100644 --- a/lib/rubocop/options.rb +++ b/lib/rubocop/options.rb @@ -8,6 +8,7 @@ class IncorrectCopNameError < StandardError; end class OptionArgumentError < StandardError; end # This class handles command line options. + # @api private class Options E_STDIN_NO_PATH = '-s/--stdin requires exactly one path, relative to the ' \ 'root of the project. RuboCop will use this path to determine which ' \ @@ -232,6 +233,7 @@ def long_opt_symbol(args) end # Validates option arguments and the options' compatibility with each other. + # @api private class OptionsValidator class << self # Cop name validation must be done later than option parsing, so it's not @@ -393,6 +395,7 @@ def validate_exclude_limit_option end # This module contains help texts for command line options. + # @api private module OptionsHelp MAX_EXCL = RuboCop::Options::DEFAULT_MAXIMUM_EXCLUSION_ITEMS.to_s FORMATTER_OPTION_LIST = RuboCop::Formatter::FormatterSet::BUILTIN_FORMATTERS_FOR_KEYS.keys diff --git a/lib/rubocop/remote_config.rb b/lib/rubocop/remote_config.rb index 98bcb9842b3..585eebf7b41 100644 --- a/lib/rubocop/remote_config.rb +++ b/lib/rubocop/remote_config.rb @@ -5,6 +5,7 @@ module RuboCop # Common methods and behaviors for dealing with remote config files. + # @api private class RemoteConfig attr_reader :uri diff --git a/lib/rubocop/result_cache.rb b/lib/rubocop/result_cache.rb index 50995cdaee3..6ff42276351 100644 --- a/lib/rubocop/result_cache.rb +++ b/lib/rubocop/result_cache.rb @@ -6,6 +6,7 @@ module RuboCop # Provides functionality for caching rubocop runs. + # @api private class ResultCache NON_CHANGING = %i[color format formatters out debug fail_level auto_correct cache fail_fast stdin parallel].freeze diff --git a/lib/rubocop/runner.rb b/lib/rubocop/runner.rb index feb698b0fc2..c053155a6a4 100644 --- a/lib/rubocop/runner.rb +++ b/lib/rubocop/runner.rb @@ -17,6 +17,7 @@ def initialize(path, offenses) end end + # @api private MAX_ITERATIONS = 200 attr_reader :errors, :warnings diff --git a/lib/rubocop/string_interpreter.rb b/lib/rubocop/string_interpreter.rb index 731aad233b7..a2215328030 100644 --- a/lib/rubocop/string_interpreter.rb +++ b/lib/rubocop/string_interpreter.rb @@ -17,6 +17,9 @@ class StringInterpreter u\{[^}]*\} | # extended unicode escape . # any other escaped char )/x.freeze + + private_constant :STRING_ESCAPES, :STRING_ESCAPE_REGEX + class << self def interpret(string) # We currently don't handle \cx, \C-x, and \M-x diff --git a/lib/rubocop/target_finder.rb b/lib/rubocop/target_finder.rb index ff5dcc9cfea..3f6f45ed397 100644 --- a/lib/rubocop/target_finder.rb +++ b/lib/rubocop/target_finder.rb @@ -3,6 +3,7 @@ module RuboCop # This class finds target files to inspect by scanning the directory tree # and picking ruby files. + # @api private class TargetFinder def initialize(config_store, options = {}) @config_store = config_store diff --git a/lib/rubocop/target_ruby.rb b/lib/rubocop/target_ruby.rb index 75f12beaefe..d9abb5b2e01 100644 --- a/lib/rubocop/target_ruby.rb +++ b/lib/rubocop/target_ruby.rb @@ -2,6 +2,7 @@ module RuboCop # The kind of Ruby that code inspected by RuboCop is written in. + # @api private class TargetRuby KNOWN_RUBIES = [2.4, 2.5, 2.6, 2.7, 2.8].freeze DEFAULT_VERSION = KNOWN_RUBIES.first @@ -12,6 +13,7 @@ class TargetRuby private_constant :KNOWN_RUBIES, :OBSOLETE_RUBIES # A place where information about a target ruby version is found. + # @api private class Source attr_reader :version, :name @@ -26,6 +28,7 @@ def to_s end # The target ruby version may be configured in RuboCop's config. + # @api private class RuboCopConfig < Source def name "`TargetRubyVersion` parameter (in #{@config.smart_loaded_path})" @@ -39,6 +42,7 @@ def find_version end # The target ruby version may be found in a .ruby-version file. + # @api private class RubyVersionFile < Source FILENAME = '.ruby-version' @@ -68,6 +72,7 @@ def ruby_version_file end # The lock file of Bundler may identify the target ruby version. + # @api private class BundlerLockFile < Source def name "`#{bundler_lock_file_path}`" @@ -108,6 +113,7 @@ def bundler_lock_file_path end # If all else fails, a default version will be picked. + # @api private class Default < Source def name 'default' diff --git a/lib/rubocop/version.rb b/lib/rubocop/version.rb index 1dcfc9f02f8..9e6bc1504e1 100644 --- a/lib/rubocop/version.rb +++ b/lib/rubocop/version.rb @@ -9,6 +9,7 @@ module Version 'rubocop-ast %s, ' \ 'running on %s %s %s)' + # @api private def self.version(debug: false) if debug format(MSG, version: STRING, parser_version: Parser::VERSION, diff --git a/lib/rubocop/yaml_duplication_checker.rb b/lib/rubocop/yaml_duplication_checker.rb index 6430d387fec..e54f8996107 100644 --- a/lib/rubocop/yaml_duplication_checker.rb +++ b/lib/rubocop/yaml_duplication_checker.rb @@ -2,6 +2,7 @@ module RuboCop # Find duplicated keys from YAML. + # @api private module YAMLDuplicationChecker def self.check(yaml_string, filename, &on_duplicated) # Ruby 2.6+ diff --git a/spec/rubocop/cli/cli_options_spec.rb b/spec/rubocop/cli/cli_options_spec.rb index 7c8dc969d5f..3ed5a4c5fc2 100644 --- a/spec/rubocop/cli/cli_options_spec.rb +++ b/spec/rubocop/cli/cli_options_spec.rb @@ -272,7 +272,7 @@ class SomeCop < Cop end let(:pending_cop_warning) { <<~PENDING_COP_WARNING } - The following cops were added to RuboCop, but are not configured. Please set Enabled to either `true` or `false` in your `.rubocop.yml` file: + The following cops were added to RuboCop, but are not configured. Please set Enabled to either `true` or `false` in your `.rubocop.yml` file. PENDING_COP_WARNING let(:inspected_output) { <<~INSPECTED_OUTPUT } @@ -327,9 +327,9 @@ class SomeCop < Cop remaining_range = pending_cop_warning.length..-(inspected_output.length + 1) - pending_cops = output[remaining_range].split("\n") + pending_cops = output[remaining_range] - expect(pending_cops).to include(' - Style/SomeCop (0.80)') + expect(pending_cops).to include("Style/SomeCop: # (new in 0.80)\n Enabled: true") manual_url = output[remaining_range].split("\n").last @@ -346,9 +346,9 @@ class SomeCop < Cop remaining_range = pending_cop_warning.length..-(inspected_output.length + 1) - pending_cops = output[remaining_range].split("\n") + pending_cops = output[remaining_range] - expect(pending_cops).to include(' - Style/SomeCop (N/A)') + expect(pending_cops).to include("Style/SomeCop: # (new in N/A)\n Enabled: true") manual_url = output[remaining_range].split("\n").last diff --git a/spec/rubocop/cop/commissioner_spec.rb b/spec/rubocop/cop/commissioner_spec.rb index 3434c457bc2..9c584ad2f96 100644 --- a/spec/rubocop/cop/commissioner_spec.rb +++ b/spec/rubocop/cop/commissioner_spec.rb @@ -7,15 +7,18 @@ end let(:report) { commissioner.investigate(processed_source) } - let(:cop_offenses) { [] } - let(:cop_report) do - RuboCop::Cop::Base::InvestigationReport - .new(nil, processed_source, cop_offenses, nil) + let(:cop_class) do + stub_const('Fake::FakeCop', Class.new(RuboCop::Cop::Base) do + def on_int(node); end + alias_method :on_def, :on_int + alias_method :on_send, :on_int + alias_method :on_csend, :on_int + end) end let(:cop) do - # rubocop:disable RSpec/VerifiedDoubles - double(RuboCop::Cop::Base, complete_investigation: cop_report).as_null_object - # rubocop:enable RSpec/VerifiedDoubles + cop_class.new.tap do |c| + allow(c).to receive(:complete_investigation).and_return(cop_report) + end end let(:cops) { [cop] } let(:options) { {} } @@ -28,6 +31,15 @@ def method end RUBY let(:processed_source) { parse_source(source, 'file.rb') } + let(:cop_offenses) { [] } + let(:cop_report) do + RuboCop::Cop::Base::InvestigationReport + .new(nil, processed_source, cop_offenses, nil) + end + + around do |example| + RuboCop::Cop::Registry.with_temporary_global { example.run } + end context 'when a cop reports offenses' do let(:cop_offenses) { [Object.new] } @@ -42,6 +54,40 @@ def method offenses end + context 'traverses the AST with on_send / on_csend' do + let(:source) { 'foo; var = bar; var&.baz' } + + context 'for unrestricted cops' do + it 'calls on_send all method calls' do + expect(cop).to receive(:on_send).twice + expect(cop).to receive(:on_csend).once + offenses + end + end + + context 'for a restricted cop' do + before { stub_const("#{cop_class}::RESTRICT_ON_SEND", restrictions) } + + let(:restrictions) { [:bar] } + + it 'calls on_send for the right method calls' do + expect(cop).to receive(:on_send).once + expect(cop).not_to receive(:on_csend) + offenses + end + + context 'on both csend and send' do + let(:restrictions) { %i[bar baz] } + + it 'calls on_send for the right method calls' do + expect(cop).to receive(:on_send).once + expect(cop).to receive(:on_csend).once + offenses + end + end + end + end + it 'stores all errors raised by the cops' do allow(cop).to receive(:on_int) { raise RuntimeError } diff --git a/spec/rubocop/cop/layout/empty_line_after_multiline_condition_spec.rb b/spec/rubocop/cop/layout/empty_line_after_multiline_condition_spec.rb new file mode 100644 index 00000000000..1ad2514d0a7 --- /dev/null +++ b/spec/rubocop/cop/layout/empty_line_after_multiline_condition_spec.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Layout::EmptyLineAfterMultilineCondition do + subject(:cop) { described_class.new } + + it 'registers an offense when no new line after `if` with multiline condition' do + expect_offense(<<~RUBY) + if multiline && + ^^^^^^^^^^^^ Use empty line after multiline condition. + condition + do_something + end + RUBY + end + + it 'does not register an offense when new line after `if` with multiline condition' do + expect_no_offenses(<<~RUBY) + if multiline && + condition + + do_something + end + RUBY + end + + it 'does not register an offense for `if` with single line condition' do + expect_no_offenses(<<~RUBY) + if singleline + do_something + end + RUBY + end + + it 'registers an offense when no new line after modifier `if` with multiline condition' do + expect_offense(<<~RUBY) + do_something if multiline && + ^^^^^^^^^^^^ Use empty line after multiline condition. + condition + do_something_else + RUBY + end + + it 'does not register an offense when new line after modifier `if` with multiline condition' do + expect_no_offenses(<<~RUBY) + do_something if multiline && + condition + + do_something_else + RUBY + end + + it 'does not register an offense when modifier `if` with multiline condition'\ + 'is the last child of its parent' do + expect_no_offenses(<<~RUBY) + def m + do_something if multiline && + condition + end + RUBY + end + + it 'registers an offense when no new line after `elsif` with multiline condition' do + expect_offense(<<~RUBY) + if condition + do_something + elsif multiline && + ^^^^^^^^^^^^ Use empty line after multiline condition. + condition + do_something_else + end + RUBY + end + + it 'does not register an offense when new line after `elsif` with multiline condition' do + expect_no_offenses(<<~RUBY) + if condition + do_something + elsif multiline && + condition + + do_something_else + end + RUBY + end + + it 'registers an offense when no new line after `while` with multiline condition' do + expect_offense(<<~RUBY) + while multiline && + ^^^^^^^^^^^^ Use empty line after multiline condition. + condition + do_something + end + RUBY + end + + it 'registers an offense when no new line after `until` with multiline condition' do + expect_offense(<<~RUBY) + until multiline && + ^^^^^^^^^^^^ Use empty line after multiline condition. + condition + do_something + end + RUBY + end + + it 'does not register an offense when new line after `while` with multiline condition' do + expect_no_offenses(<<~RUBY) + while multiline && + condition + + do_something + end + RUBY + end + + it 'does not register an offense for `while` with single line condition' do + expect_no_offenses(<<~RUBY) + while singleline + do_something + end + RUBY + end + + it 'registers an offense when no new line after modifier `while` with multiline condition' do + expect_offense(<<~RUBY) + begin + do_something + end while multiline && + ^^^^^^^^^^^^ Use empty line after multiline condition. + condition + do_something_else + RUBY + end + + it 'does not register an offense when new line after modifier `while` with multiline condition' do + expect_no_offenses(<<~RUBY) + begin + do_something + end while multiline && + condition + + do_something_else + RUBY + end + + it 'does not register an offense when modifier `while` with multiline condition'\ + 'is the last child of its parent' do + expect_no_offenses(<<~RUBY) + def m + begin + do_something + end while multiline && + condition + end + RUBY + end + + it 'registers an offense when no new line after `when` with multiline condition' do + expect_offense(<<~RUBY) + case x + when foo, + ^^^^^^^^^ Use empty line after multiline condition. + bar + do_something + end + RUBY + end + + it 'does not register an offense when new line after `when` with multiline condition' do + expect_no_offenses(<<~RUBY) + case x + when foo, + bar + + do_something + end + RUBY + end + + it 'does not register an offense for `when` with singleline condition' do + expect_no_offenses(<<~RUBY) + case x + when foo, bar + do_something + end + RUBY + end + + it 'registers an offense when no new line after `rescue` with multiline exceptions' do + expect_offense(<<~RUBY) + begin + do_something + rescue FooError, + ^^^^^^^^^^^^^^^^ Use empty line after multiline condition. + BarError + handle_error + end + RUBY + end + + it 'does not register an offense when new line after `rescue` with multiline exceptions' do + expect_no_offenses(<<~RUBY) + begin + do_something + rescue FooError, + BarError + + handle_error + end + RUBY + end + + it 'does not register an offense for `rescue` with singleline exceptions' do + expect_no_offenses(<<~RUBY) + begin + do_something + rescue FooError + handle_error + end + RUBY + end +end diff --git a/spec/rubocop/cop/layout/empty_line_between_defs_spec.rb b/spec/rubocop/cop/layout/empty_line_between_defs_spec.rb index bffd802a9ef..c49a64be753 100644 --- a/spec/rubocop/cop/layout/empty_line_between_defs_spec.rb +++ b/spec/rubocop/cop/layout/empty_line_between_defs_spec.rb @@ -276,6 +276,19 @@ def o RUBY end + it 'registers an offense for multiple one-liners on the same line' do + expect_offense(<<~RUBY) + def a; end; def b; end + ^^^^^ Use empty lines between method definitions. + RUBY + + expect_correction(<<~RUBY) + def a; end; + + def b; end + RUBY + end + context 'when AllowAdjacentOneLineDefs is enabled' do let(:cop_config) { { 'AllowAdjacentOneLineDefs' => true } } diff --git a/spec/rubocop/cop/layout/trailing_empty_lines_spec.rb b/spec/rubocop/cop/layout/trailing_empty_lines_spec.rb index 70fc20b91be..a9eb0062efa 100644 --- a/spec/rubocop/cop/layout/trailing_empty_lines_spec.rb +++ b/spec/rubocop/cop/layout/trailing_empty_lines_spec.rb @@ -30,38 +30,38 @@ end it 'registers an offense for multiple trailing blank lines' do - inspect_source(<<~RUBY) + offenses = inspect_source(<<~RUBY) x = 0 RUBY - expect(cop.offenses.size).to eq(1) - expect(cop.messages).to eq(['3 trailing blank lines detected.']) + expect(offenses.size).to eq(1) + expect(offenses.first.message).to eq('3 trailing blank lines detected.') end it 'registers an offense for multiple blank lines in an empty file' do - inspect_source(<<~RUBY) + offenses = inspect_source(<<~RUBY) RUBY - expect(cop.offenses.size).to eq(1) - expect(cop.messages).to eq(['3 trailing blank lines detected.']) + expect(offenses.size).to eq(1) + expect(offenses.first.message).to eq('3 trailing blank lines detected.') end it 'registers an offense for no final newline after assignment' do - inspect_source('x = 0') - expect(cop.messages).to eq(['Final newline missing.']) + offenses = inspect_source('x = 0') + expect(offenses.first.message).to eq('Final newline missing.') end it 'registers an offense for no final newline after block comment' do - inspect_source("puts 'testing rubocop when final new line is missing " \ - "after block comments'\n\n=begin\nfirst line\nsecond " \ - "line\nthird line\n=end") + offenses = inspect_source("puts 'testing rubocop when final new line is missing " \ + "after block comments'\n\n=begin\nfirst line\nsecond " \ + "line\nthird line\n=end") - expect(cop.messages).to eq(['Final newline missing.']) + expect(offenses.first.message).to eq('Final newline missing.') end it 'auto-corrects unwanted blank lines' do @@ -98,40 +98,38 @@ let(:cop_config) { { 'EnforcedStyle' => 'final_blank_line' } } it 'registers an offense for final newline' do - inspect_source(<<~RUBY) + offenses = inspect_source(<<~RUBY) x = 0 RUBY - expect(cop.messages).to eq(['Trailing blank line missing.']) + expect(offenses.first.message).to eq('Trailing blank line missing.') end it 'registers an offense for multiple trailing blank lines' do - inspect_source(<<~RUBY) + offenses = inspect_source(<<~RUBY) x = 0 RUBY - expect(cop.offenses.size).to eq(1) - expect(cop.messages) - .to eq(['3 trailing blank lines instead of 1 detected.']) + expect(offenses.size).to eq(1) + expect(offenses.first.message).to eq('3 trailing blank lines instead of 1 detected.') end it 'registers an offense for multiple blank lines in an empty file' do - inspect_source(<<~RUBY) + offenses = inspect_source(<<~RUBY) RUBY - expect(cop.offenses.size).to eq(1) - expect(cop.messages) - .to eq(['3 trailing blank lines instead of 1 detected.']) + expect(offenses.size).to eq(1) + expect(offenses.first.message).to eq('3 trailing blank lines instead of 1 detected.') end it 'registers an offense for no final newline' do - inspect_source('x = 0') - expect(cop.messages).to eq(['Final newline missing.']) + offenses = inspect_source('x = 0') + expect(offenses.first.message).to eq('Final newline missing.') end it 'accepts final blank line' do diff --git a/spec/rubocop/cop/layout/trailing_whitespace_spec.rb b/spec/rubocop/cop/layout/trailing_whitespace_spec.rb index 53f67120302..66dc1ecb998 100644 --- a/spec/rubocop/cop/layout/trailing_whitespace_spec.rb +++ b/spec/rubocop/cop/layout/trailing_whitespace_spec.rb @@ -4,62 +4,62 @@ let(:cop_config) { { 'AllowInHeredoc' => false } } it 'registers an offense for a line ending with space' do - inspect_source('x = 0 ') - expect(cop.offenses.size).to eq(1) + offenses = inspect_source('x = 0 ') + expect(offenses.size).to eq(1) end it 'registers an offense for a blank line with space' do - inspect_source(' ') - expect(cop.offenses.size).to eq(1) + offenses = inspect_source(' ') + expect(offenses.size).to eq(1) end it 'registers an offense for a line ending with tab' do - inspect_source("x = 0\t") - expect(cop.offenses.size).to eq(1) + offenses = inspect_source("x = 0\t") + expect(offenses.size).to eq(1) end it 'registers an offense for trailing whitespace in a heredoc string' do - inspect_source(['x = < { 'Width' => 2 }) end - it 'registers an offense when the right side has mulitple arrays' do + it 'registers an offense when the right side has mulitiple arrays' do expect_offense(<<~RUBY) a, b, c = [1, 2], [3, 4], [5, 6] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use parallel assignment. diff --git a/spec/rubocop/cop/style/redundant_regexp_character_class_spec.rb b/spec/rubocop/cop/style/redundant_regexp_character_class_spec.rb index e12a9238d64..5fdd836a79d 100644 --- a/spec/rubocop/cop/style/redundant_regexp_character_class_spec.rb +++ b/spec/rubocop/cop/style/redundant_regexp_character_class_spec.rb @@ -224,6 +224,16 @@ end end + context 'with a multi-line interpolation' do + it 'ignores offenses in the interpolated expression' do + expect_no_offenses(<<~'RUBY') + /#{Regexp.union( + %w"( ) { } [ ] < > $ ! ^ ` ... + * ? ," + )}/o + RUBY + end + end + context 'with a character class containing a space' do context 'when not using free-spaced mode' do it 'registers an offense and corrects' do diff --git a/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb b/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb index f7aacb27eb9..f56596ec2b0 100644 --- a/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb +++ b/spec/rubocop/cop/style/redundant_regexp_escape_spec.rb @@ -16,6 +16,18 @@ end end + context 'with a line continuation' do + it 'does not register an offense' do + expect_no_offenses("foo = /a\\\nb/") + end + end + + context 'with a line continuation within a character class' do + it 'does not register an offense' do + expect_no_offenses("foo = /[a\\\nb]/") + end + end + [ ('a'..'z').to_a - %w[c n p u x], ('A'..'Z').to_a - %w[C M P], @@ -277,6 +289,9 @@ end end + # Avoid an empty character class + next if char == "\n" + context "with an escaped '#{char}' inside a character class" do it 'does not register an offense' do expect_no_offenses("foo = /[\\#{char}]/") diff --git a/spec/rubocop/ext/regexp_node_spec.rb b/spec/rubocop/ext/regexp_node_spec.rb new file mode 100644 index 00000000000..bb1e4f13f93 --- /dev/null +++ b/spec/rubocop/ext/regexp_node_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'timeout' + +RSpec.describe RuboCop::Ext::RegexpNode do + let(:source) { '/(hello)(?world)(?:not captured)/' } + let(:processed_source) { parse_source(source) } + let(:ast) { processed_source.ast } + let(:node) { ast } + + describe '#each_capture' do + subject(:captures) { node.each_capture(**arg).to_a } + + let(:named) { be_instance_of(Regexp::Expression::Group::Named) } + let(:positional) { be_instance_of(Regexp::Expression::Group::Capture) } + + context 'when called without argument' do + let(:arg) { {} } + + it { is_expected.to match [positional, named] } + end + + context 'when called with a `named: false`' do + let(:arg) { { named: false } } + + it { is_expected.to match [positional] } + end + + context 'when called with a `named: true`' do + let(:arg) { { named: true } } + + it { is_expected.to match [named] } + end + end +end diff --git a/spec/rubocop/formatter/junit_formatter_spec.rb b/spec/rubocop/formatter/junit_formatter_spec.rb index 63191b08ca8..fc711b3a7c5 100644 --- a/spec/rubocop/formatter/junit_formatter_spec.rb +++ b/spec/rubocop/formatter/junit_formatter_spec.rb @@ -12,18 +12,16 @@ describe '#file_finished' do before do cop.add_offense( - nil, - location: Parser::Source::Range.new(source_buffer, 0, 1), + Parser::Source::Range.new(source_buffer, 0, 1), message: 'message 1' ) - cop.add_offense( - nil, - location: Parser::Source::Range.new(source_buffer, 9, 10), + offenses = cop.add_offense( + Parser::Source::Range.new(source_buffer, 9, 10), message: 'message 2' ) - formatter.file_finished('test_1', cop.offenses) - formatter.file_finished('test_2', cop.offenses) + formatter.file_finished('test_1', offenses) + formatter.file_finished('test_2', offenses) formatter.finished(nil) end diff --git a/tasks/check_commit.rake b/tasks/check_commit.rake index 3c02e5a04b6..2e58e9334c2 100644 --- a/tasks/check_commit.rake +++ b/tasks/check_commit.rake @@ -11,10 +11,15 @@ desc 'Check files modified in commit (default: HEAD) with rspec and rubocop' RuboCop::RakeTask.new(:check_commit, :commit) do |t, args| commit = args[:commit] || 'HEAD' paths = commit_paths(commit) + paths.reject { |p| p.start_with?(/docs|Gemfile|README|CHANGELOG/) } specs = paths.select { |p| p.start_with?('spec') } - puts "Checking: #{paths.join(' ')}" - RuboCop::SpecRunner.new(specs).run_specs + if specs.empty? + puts 'Caution: No spec was changed!' + else + puts "Checking: #{paths.join(' ')}" + RuboCop::SpecRunner.new(specs, parallel: false).run_specs + end t.patterns = paths end diff --git a/tasks/spec_runner.rake b/tasks/spec_runner.rake index 1e14c9789e0..0e1d03841ca 100644 --- a/tasks/spec_runner.rake +++ b/tasks/spec_runner.rake @@ -13,18 +13,20 @@ module RuboCop class SpecRunner attr_reader :rspec_args - def initialize(rspec_args = %w[spec], external_encoding: 'UTF-8', internal_encoding: nil) + def initialize(rspec_args = %w[spec], parallel: true, + external_encoding: 'UTF-8', internal_encoding: nil) @rspec_args = rspec_args @previous_external_encoding = Encoding.default_external @previous_internal_encoding = Encoding.default_internal @temporary_external_encoding = external_encoding @temporary_internal_encoding = internal_encoding + @parallel = parallel end def run_specs n_failures = with_encoding do - if Process.respond_to?(:fork) + if @parallel && Process.respond_to?(:fork) parallel_runner_klass.new(rspec_args).execute else ::RSpec::Core::Runner.run(rspec_args)