From 3c30d10e55b30a18b67a0f1007ea8de5795367c9 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Mon, 20 Jul 2020 19:28:25 +0300 Subject: [PATCH] Add new `Style/StringConcatenation` cop --- CHANGELOG.md | 1 + config/default.yml | 7 ++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_style.adoc | 31 +++++++ lib/rubocop.rb | 1 + .../cli/command/auto_genenerate_config.rb | 2 +- lib/rubocop/cli/command/show_cops.rb | 2 +- lib/rubocop/comment_config.rb | 2 +- lib/rubocop/config_loader_resolver.rb | 2 +- .../cop/correctors/line_break_corrector.rb | 6 +- .../correctors/percent_literal_corrector.rb | 2 +- .../cop/correctors/punctuation_corrector.rb | 2 +- .../cop/generator/configuration_injector.rb | 4 +- lib/rubocop/cop/layout/block_alignment.rb | 2 +- .../cop/lint/heredoc_method_call_position.rb | 2 +- lib/rubocop/cop/style/hash_syntax.rb | 2 +- lib/rubocop/cop/style/infinite_loop.rb | 2 +- .../cop/style/multiline_memoization.rb | 4 +- lib/rubocop/cop/style/string_concatenation.rb | 92 +++++++++++++++++++ lib/rubocop/cop/style/symbol_array.rb | 2 +- lib/rubocop/cop/style/symbol_proc.rb | 2 +- .../style/trailing_method_end_statement.rb | 2 +- spec/rubocop/cli/cli_options_spec.rb | 6 +- spec/rubocop/cli_spec.rb | 12 +-- spec/rubocop/config_loader_spec.rb | 2 +- spec/rubocop/config_store_spec.rb | 2 +- spec/rubocop/cop/layout/line_length_spec.rb | 10 +- spec/rubocop/cop/range_help_spec.rb | 2 +- .../cop/style/string_concatenation_spec.rb | 44 +++++++++ spec/rubocop/cop/utils/format_string_spec.rb | 2 +- .../support/multiline_literal_brace_helper.rb | 2 +- tasks/cops_documentation.rake | 4 +- 32 files changed, 218 insertions(+), 41 deletions(-) create mode 100644 lib/rubocop/cop/style/string_concatenation.rb create mode 100644 spec/rubocop/cop/style/string_concatenation_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 6027d5abef8..28d6d0c23ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [#8322](https://github.com/rubocop-hq/rubocop/pull/8322): Support autocorrect for `Style/CaseEquality` cop. ([@fatkodima][]) * [#8339](https://github.com/rubocop-hq/rubocop/pull/8339): Add `Config#for_badge` as an efficient way to get a cop's config merged with its department's. ([@marcandre][]) +* [#5067](https://github.com/rubocop-hq/rubocop/issues/5067): Add new `Style/StringConcatenation` cop. ([@fatkodima][]) ### Bug fixes diff --git a/config/default.yml b/config/default.yml index 9a86c01f6e9..f2695fbee6e 100644 --- a/config/default.yml +++ b/config/default.yml @@ -3939,6 +3939,13 @@ Style/StderrPuts: Enabled: true VersionAdded: '0.51' +Style/StringConcatenation: + Description: 'Checks for places where string concatenation can be replaced with string interpolation.' + StyleGuide: '#string-interpolation' + Enabled: pending + Safe: false + VersionAdded: '0.89' + Style/StringHashKeys: Description: 'Prefer symbols instead of strings as hash keys.' StyleGuide: '#symbols-as-keys' diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 17a9797ca3f..20c3dc76d0a 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -471,6 +471,7 @@ In the following section you find all available cops: * xref:cops_style.adoc#stylespecialglobalvars[Style/SpecialGlobalVars] * xref:cops_style.adoc#stylestabbylambdaparentheses[Style/StabbyLambdaParentheses] * xref:cops_style.adoc#stylestderrputs[Style/StderrPuts] +* xref:cops_style.adoc#stylestringconcatenation[Style/StringConcatenation] * xref:cops_style.adoc#stylestringhashkeys[Style/StringHashKeys] * xref:cops_style.adoc#stylestringliterals[Style/StringLiterals] * xref:cops_style.adoc#stylestringliteralsininterpolation[Style/StringLiteralsInInterpolation] diff --git a/docs/modules/ROOT/pages/cops_style.adoc b/docs/modules/ROOT/pages/cops_style.adoc index fef5d0893cb..cfc15bc03df 100644 --- a/docs/modules/ROOT/pages/cops_style.adoc +++ b/docs/modules/ROOT/pages/cops_style.adoc @@ -8944,6 +8944,37 @@ warn('hello') * https://rubystyle.guide#warn +== Style/StringConcatenation + +|=== +| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged + +| Pending +| No +| Yes (Unsafe) +| 0.89 +| - +|=== + +This cop checks for places where string concatenation +can be replaced with string interpolation. + +=== Examples + +[source,ruby] +---- +# bad +email_with_name = user.name + ' <' + user.email + '>' + +# good +email_with_name = "#{user.name} <#{user.email}>" +email_with_name = format('%s <%s>', user.name, user.email) +---- + +=== References + +* https://rubystyle.guide#string-interpolation + == Style/StringHashKeys |=== diff --git a/lib/rubocop.rb b/lib/rubocop.rb index 4092de4cea6..5ffa0017c75 100644 --- a/lib/rubocop.rb +++ b/lib/rubocop.rb @@ -516,6 +516,7 @@ require_relative 'rubocop/cop/style/special_global_vars' require_relative 'rubocop/cop/style/stabby_lambda_parentheses' require_relative 'rubocop/cop/style/stderr_puts' +require_relative 'rubocop/cop/style/string_concatenation' require_relative 'rubocop/cop/style/string_hash_keys' require_relative 'rubocop/cop/style/string_literals' require_relative 'rubocop/cop/style/string_literals_in_interpolation' diff --git a/lib/rubocop/cli/command/auto_genenerate_config.rb b/lib/rubocop/cli/command/auto_genenerate_config.rb index dd37a40598a..41c5641c8aa 100644 --- a/lib/rubocop/cli/command/auto_genenerate_config.rb +++ b/lib/rubocop/cli/command/auto_genenerate_config.rb @@ -113,7 +113,7 @@ def add_inheritance_from_auto_generated_file(config_file) return if files.include?(AUTO_GENERATED_FILE) files.unshift(AUTO_GENERATED_FILE) - file_string = "\n - " + files.join("\n - ") if files.size > 1 + file_string = "\n - #{files.join("\n - ")}" if files.size > 1 rubocop_yml_contents = existing_configuration(config_file) end diff --git a/lib/rubocop/cli/command/show_cops.rb b/lib/rubocop/cli/command/show_cops.rb index 1e0a57a5700..da0cee88356 100644 --- a/lib/rubocop/cli/command/show_cops.rb +++ b/lib/rubocop/cli/command/show_cops.rb @@ -68,7 +68,7 @@ def cops_of_department(cops, department) def config_lines(cop) cnf = @config.for_cop(cop) - cnf.to_yaml.lines.to_a.drop(1).map { |line| ' ' + line } + cnf.to_yaml.lines.to_a.drop(1).map { |line| " #{line}" } end end end diff --git a/lib/rubocop/comment_config.rb b/lib/rubocop/comment_config.rb index 91f58591ed9..7a98195f754 100644 --- a/lib/rubocop/comment_config.rb +++ b/lib/rubocop/comment_config.rb @@ -11,7 +11,7 @@ class CommentConfig COPS_PATTERN = "(all|#{COP_NAMES_PATTERN})" COMMENT_DIRECTIVE_REGEXP = Regexp.new( - ('# rubocop : ((?:disable|enable|todo))\b ' + COPS_PATTERN) + "# rubocop : ((?:disable|enable|todo))\\b #{COPS_PATTERN}" .gsub(' ', '\s*') ) diff --git a/lib/rubocop/config_loader_resolver.rb b/lib/rubocop/config_loader_resolver.rb index 232d62297ea..ad864b3c2ae 100644 --- a/lib/rubocop/config_loader_resolver.rb +++ b/lib/rubocop/config_loader_resolver.rb @@ -201,7 +201,7 @@ def handle_disabled_by_default(config, new_default_configuration) next unless dept_params['Enabled'] new_default_configuration.each do |cop, params| - next unless cop.start_with?(dept + '/') + next unless cop.start_with?("#{dept}/") # Retain original default configuration for cops in the department. params['Enabled'] = ConfigLoader.default_configuration[cop]['Enabled'] diff --git a/lib/rubocop/cop/correctors/line_break_corrector.rb b/lib/rubocop/cop/correctors/line_break_corrector.rb index 420e0b55607..d71df9f53aa 100644 --- a/lib/rubocop/cop/correctors/line_break_corrector.rb +++ b/lib/rubocop/cop/correctors/line_break_corrector.rb @@ -29,8 +29,8 @@ def break_line_before(range:, node:, corrector:, indent_steps: 1, configured_width:) corrector.insert_before( range, - "\n" + ' ' * (node.loc.keyword.column + - indent_steps * configured_width) + "\n#{' ' * (node.loc.keyword.column + + indent_steps * configured_width)}" ) end @@ -39,7 +39,7 @@ def move_comment(eol_comment:, node:, corrector:) text = eol_comment.loc.expression.source corrector.insert_before(node, - text + "\n" + (' ' * node.loc.keyword.column)) + "#{text}\n#{' ' * node.loc.keyword.column}") corrector.remove(eol_comment) end diff --git a/lib/rubocop/cop/correctors/percent_literal_corrector.rb b/lib/rubocop/cop/correctors/percent_literal_corrector.rb index 1731125557e..41428b0e7ec 100644 --- a/lib/rubocop/cop/correctors/percent_literal_corrector.rb +++ b/lib/rubocop/cop/correctors/percent_literal_corrector.rb @@ -110,7 +110,7 @@ def substitute_escaped_delimiters(content, delimiters) def end_content(source) result = /\A(\s*)\]/.match(source.split("\n").last) - ("\n" + result[1]) if result + "\n#{result[1]}" if result end end end diff --git a/lib/rubocop/cop/correctors/punctuation_corrector.rb b/lib/rubocop/cop/correctors/punctuation_corrector.rb index e57c0676d64..b83f657831b 100644 --- a/lib/rubocop/cop/correctors/punctuation_corrector.rb +++ b/lib/rubocop/cop/correctors/punctuation_corrector.rb @@ -10,7 +10,7 @@ def remove_space(space_before) end def add_space(token) - ->(corrector) { corrector.replace(token.pos, token.pos.source + ' ') } + ->(corrector) { corrector.replace(token.pos, "#{token.pos.source} ") } end def swap_comma(range) diff --git a/lib/rubocop/cop/generator/configuration_injector.rb b/lib/rubocop/cop/generator/configuration_injector.rb index 71c51141336..53cb65e209e 100644 --- a/lib/rubocop/cop/generator/configuration_injector.rb +++ b/lib/rubocop/cop/generator/configuration_injector.rb @@ -25,9 +25,9 @@ def inject target_line = find_target_line if target_line configuration_entries.insert(target_line, - new_configuration_entry + "\n") + "#{new_configuration_entry}\n") else - configuration_entries.push("\n" + new_configuration_entry) + configuration_entries.push("\n#{new_configuration_entry}") end File.write(configuration_file_path, configuration_entries.join) diff --git a/lib/rubocop/cop/layout/block_alignment.rb b/lib/rubocop/cop/layout/block_alignment.rb index 2cdf9ab8042..a5b2cd2c3b9 100644 --- a/lib/rubocop/cop/layout/block_alignment.rb +++ b/lib/rubocop/cop/layout/block_alignment.rb @@ -213,7 +213,7 @@ def alt_start_msg(start_loc, source_line_column) start_loc.column == source_line_column[:column] '' else - ' or ' + format_source_line_column(source_line_column) + " or #{format_source_line_column(source_line_column)}" end end diff --git a/lib/rubocop/cop/lint/heredoc_method_call_position.rb b/lib/rubocop/cop/lint/heredoc_method_call_position.rb index 9d43e89a128..f23a5c69eac 100644 --- a/lib/rubocop/cop/lint/heredoc_method_call_position.rb +++ b/lib/rubocop/cop/lint/heredoc_method_call_position.rb @@ -148,7 +148,7 @@ def call_range_to_safely_reposition(node, heredoc) end def trailing_comma?(call_source, call_line_source) - call_source + ',' == call_line_source + "#{call_source}," == call_line_source end end end diff --git a/lib/rubocop/cop/style/hash_syntax.rb b/lib/rubocop/cop/style/hash_syntax.rb index a28b2adf389..d6df3726e21 100644 --- a/lib/rubocop/cop/style/hash_syntax.rb +++ b/lib/rubocop/cop/style/hash_syntax.rb @@ -174,7 +174,7 @@ def autocorrect_ruby19(corrector, pair_node) corrector.replace( range, - range.source.sub(/^:(.*\S)\s*=>\s*$/, space.to_s + '\1: ') + range.source.sub(/^:(.*\S)\s*=>\s*$/, "#{space}\\1: ") ) hash_node = pair_node.parent diff --git a/lib/rubocop/cop/style/infinite_loop.rb b/lib/rubocop/cop/style/infinite_loop.rb index 74597f2032e..baa89fb56d4 100644 --- a/lib/rubocop/cop/style/infinite_loop.rb +++ b/lib/rubocop/cop/style/infinite_loop.rb @@ -99,7 +99,7 @@ def replace_source(range, replacement) def modifier_replacement(node) body = node.body if node.single_line? - 'loop { ' + body.source + ' }' + "loop { #{body.source} }" else indentation = body.source_range.source_line[LEADING_SPACE] diff --git a/lib/rubocop/cop/style/multiline_memoization.rb b/lib/rubocop/cop/style/multiline_memoization.rb index 4baa0620f95..a2d98bb3058 100644 --- a/lib/rubocop/cop/style/multiline_memoization.rb +++ b/lib/rubocop/cop/style/multiline_memoization.rb @@ -77,13 +77,13 @@ def keyword_begin_str(node, node_buf) if node_buf.source[node.loc.begin.end_pos] == "\n" 'begin' else - "begin\n" + (' ' * (node.loc.column + indent)) + "begin\n#{' ' * (node.loc.column + indent)}" end end def keyword_end_str(node, node_buf) if /[^\s)]/.match?(node_buf.source_line(node.loc.end.line)) - "\n" + (' ' * node.loc.column) + 'end' + "\n#{' ' * node.loc.column}end" else 'end' end diff --git a/lib/rubocop/cop/style/string_concatenation.rb b/lib/rubocop/cop/style/string_concatenation.rb new file mode 100644 index 00000000000..90ae9bf758f --- /dev/null +++ b/lib/rubocop/cop/style/string_concatenation.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Style + # This cop checks for places where string concatenation + # can be replaced with string interpolation. + # + # @example + # # bad + # email_with_name = user.name + ' <' + user.email + '>' + # + # # good + # email_with_name = "#{user.name} <#{user.email}>" + # email_with_name = format('%s <%s>', user.name, user.email) + # + class StringConcatenation < Base + include Util + extend AutoCorrector + + MSG = 'Prefer string interpolation instead of string concatenation.' + + def_node_matcher :string_concatenation?, <<~PATTERN + { + (send str_type? :+ _) + (send _ :+ str_type?) + } + PATTERN + + def on_send(node) + return unless node.method?(:+) + return unless string_concatenation?(node) + + topmost_plus_node = find_topmost_plus_node(node) + + parts = [] + collect_parts(topmost_plus_node, parts) + + add_offense(topmost_plus_node) do |corrector| + corrector.replace(topmost_plus_node, replacement(parts)) + end + end + + private + + def find_topmost_plus_node(node) + current = node + while (parent = current.parent) && plus_node?(parent) + current = parent + end + current + end + + def collect_parts(node, parts) + return unless node + + if plus_node?(node) + collect_parts(node.receiver, parts) + collect_parts(node.first_argument, parts) + else + parts << node + end + end + + def plus_node?(node) + node.send_type? && node.method?(:+) + end + + def replacement(parts) + interpolated_parts = + parts.map do |part| + if part.str_type? + if single_quoted?(part) + part.value.gsub('\\') { '\\\\' } + else + escape_string(part.value) + end + else + "\#{#{part.source}}" + end + end + + "\"#{interpolated_parts.join}\"" + end + + def single_quoted?(str_node) + str_node.source.start_with?("'") + end + end + end + end +end diff --git a/lib/rubocop/cop/style/symbol_array.rb b/lib/rubocop/cop/style/symbol_array.rb index 0e07be22e23..142913892b8 100644 --- a/lib/rubocop/cop/style/symbol_array.rb +++ b/lib/rubocop/cop/style/symbol_array.rb @@ -74,7 +74,7 @@ def correct_bracketed(node) if c.dsym_type? string_literal = to_string_literal(c.source) - ':' + trim_string_interporation_escape_character(string_literal) + ":#{trim_string_interporation_escape_character(string_literal)}" else to_symbol_literal(c.value.to_s) end diff --git a/lib/rubocop/cop/style/symbol_proc.rb b/lib/rubocop/cop/style/symbol_proc.rb index 73b8feda88a..eab782682a2 100644 --- a/lib/rubocop/cop/style/symbol_proc.rb +++ b/lib/rubocop/cop/style/symbol_proc.rb @@ -84,7 +84,7 @@ def autocorrect_with_args(corrector, node, args, method_name) arg_range = args.last.source_range arg_range = range_with_surrounding_comma(arg_range, :right) replacement = " &:#{method_name}" - replacement = ',' + replacement unless arg_range.source.end_with?(',') + replacement = ",#{replacement}" unless arg_range.source.end_with?(',') corrector.insert_after(arg_range, replacement) corrector.remove(block_range_with_space(node)) end diff --git a/lib/rubocop/cop/style/trailing_method_end_statement.rb b/lib/rubocop/cop/style/trailing_method_end_statement.rb index d4f3309eb20..b1510748d04 100644 --- a/lib/rubocop/cop/style/trailing_method_end_statement.rb +++ b/lib/rubocop/cop/style/trailing_method_end_statement.rb @@ -45,7 +45,7 @@ def on_def(node) add_offense(node.loc.end) do |corrector| corrector.insert_before( node.loc.end, - "\n" + ' ' * node.loc.keyword.column + "\n#{' ' * node.loc.keyword.column}" ) end end diff --git a/spec/rubocop/cli/cli_options_spec.rb b/spec/rubocop/cli/cli_options_spec.rb index 27dd30c3bff..ec3b7cdf044 100644 --- a/spec/rubocop/cli/cli_options_spec.rb +++ b/spec/rubocop/cli/cli_options_spec.rb @@ -144,7 +144,7 @@ it 'exits cleanly' do expect(cli.run(['-v'])).to eq(0) expect(cli.run(['--version'])).to eq(0) - expect($stdout.string).to eq((RuboCop::Version::STRING + "\n") * 2) + expect($stdout.string).to eq("#{RuboCop::Version::STRING}\n" * 2) end end @@ -537,7 +537,7 @@ class SomeCop < Cop context 'when a namespace is given' do it 'runs all enabled cops in that namespace' do create_file('example.rb', ['if x== 100000000000000 ', - ' ' + '#' * 130, + " #{'#' * 130}", "\ty", 'end']) expect(cli.run(%w[-f offenses --only Layout example.rb])).to eq(1) @@ -559,7 +559,7 @@ class SomeCop < Cop context 'when three namespaces are given' do it 'runs all enabled cops in those namespaces' do create_file('example.rb', ['if x== 100000000000000 ', - ' # ' + '-' * 130, + " # #{'-' * 130}", "\ty", 'end']) create_file('.rubocop.yml', <<~YAML) diff --git a/spec/rubocop/cli_spec.rb b/spec/rubocop/cli_spec.rb index d3f3814ccfd..cb2b0b564c6 100644 --- a/spec/rubocop/cli_spec.rb +++ b/spec/rubocop/cli_spec.rb @@ -216,7 +216,7 @@ def and_with_args 'y("123")', 'def func', ' # rubocop: enable Layout/LineLength,Style/StringLiterals', - ' ' + '#' * 130, + " #{'#' * 130}", ' x(123456)', ' y("123")', 'end'] @@ -300,7 +300,7 @@ def and_with_args 'def func', ' # rubocop: enable Layout/LineLength, ' \ 'Style/StringLiterals', - ' ' + '#' * 130, + " #{'#' * 130}", ' x(123456)', ' y("123")', ' # rubocop: enable Style/NumericLiterals', @@ -329,7 +329,7 @@ def and_with_args create_file('example.rb', ['# frozen_string_literal: true', '', - 'a' * 130 + ' # rubocop:disable Layout/LineLength', + "#{'a' * 130} # rubocop:disable Layout/LineLength", '#' * 130, 'y("123", 123456) # rubocop:disable Style/StringLiterals,' \ 'Style/NumericLiterals']) @@ -345,7 +345,7 @@ def and_with_args create_file('example.rb', ['# frozen_string_literal: true', '', - 'a' * 130 + ' # rubocop:disable LineLength', + "#{'a' * 130} # rubocop:disable LineLength", '#' * 130, 'y("123") # rubocop:disable StringLiterals']) expect(cli.run(['--format', 'emacs', 'example.rb'])).to eq(1) @@ -385,7 +385,7 @@ def and_with_args context 'and there are no other offenses' do it 'exits with error code' do - create_file('example.rb', 'a' * 10 + ' # rubocop:disable LineLength') + create_file('example.rb', "#{'a' * 10} # rubocop:disable LineLength") expect(cli.run(['example.rb'])).to eq(1) end end @@ -398,7 +398,7 @@ def and_with_args '', '#' * 130, '# rubocop:disable all', - 'a' * 10 + ' # rubocop:disable LineLength,ClassLength', + "#{'a' * 10} # rubocop:disable LineLength,ClassLength", 'y(123) # rubocop:disable all']) create_file('.rubocop.yml', config) expect(cli.run(['--format', 'emacs'])).to eq(1) diff --git a/spec/rubocop/config_loader_spec.rb b/spec/rubocop/config_loader_spec.rb index 4b35ce7bab7..40ad5e88a33 100644 --- a/spec/rubocop/config_loader_spec.rb +++ b/spec/rubocop/config_loader_spec.rb @@ -1526,7 +1526,7 @@ def cop_enabled?(cop_class) before do create_file('.rubocop.yml', ['require:', " - #{required_file_path}"]) - create_file(required_file_path + '.rb', ['class MyClass', 'end']) + create_file("#{required_file_path}.rb", ['class MyClass', 'end']) end it 'works without a starting .' do diff --git a/spec/rubocop/config_store_spec.rb b/spec/rubocop/config_store_spec.rb index c5d22010e08..f6ef4a9203f 100644 --- a/spec/rubocop/config_store_spec.rb +++ b/spec/rubocop/config_store_spec.rb @@ -10,7 +10,7 @@ # dir/.rubocop.yml # dir/file2 # dir/subdir/file3 - (/dir/.match?(arg) ? 'dir' : '.') + '/.rubocop.yml' + "#{(/dir/.match?(arg) ? 'dir' : '.')}/.rubocop.yml" end allow(RuboCop::ConfigLoader) .to receive(:configuration_from_file) { |arg| arg } diff --git a/spec/rubocop/cop/layout/line_length_spec.rb b/spec/rubocop/cop/layout/line_length_spec.rb index a6e76ba0e6e..98703158169 100644 --- a/spec/rubocop/cop/layout/line_length_spec.rb +++ b/spec/rubocop/cop/layout/line_length_spec.rb @@ -20,7 +20,7 @@ end it 'highlights excessive characters' do - inspect_source('#' * 80 + 'abc') + inspect_source("#{'#' * 80}abc") expect(cop.highlights).to eq(['abc']) end @@ -328,7 +328,7 @@ def method_definition_that_is_just_under_the_line_length_limit(foo) # rubocop:di end context 'and the source is too long' do - let(:source) { 'a' * 80 + 'bcd' + ' # rubocop:enable Style/ClassVars' } + let(:source) { "#{'a' * 80}bcd # rubocop:enable Style/ClassVars" } it 'registers an offense for the line' do inspect_source(source) @@ -378,7 +378,7 @@ def method_definition_that_is_just_under_the_line_length_limit(foo) # rubocop:di shared_examples 'with tabs indentation' do it "registers an offense for a line that's including 2 tab with size 2" \ ' and 28 other characters' do - inspect_source("\t\t" + '#' * 28) + inspect_source("\t\t#{'#' * 28}") expect(cop.offenses.size).to eq(1) expect(cop.offenses.first.message).to eq('Line is too long. [32/30]') expect(cop.config_to_allow_offenses) @@ -386,13 +386,13 @@ def method_definition_that_is_just_under_the_line_length_limit(foo) # rubocop:di end it 'highlights excessive characters' do - inspect_source("\t" + '#' * 28 + 'a') + inspect_source("\t#{'#' * 28}a") expect(cop.highlights).to eq(['a']) end it "accepts a line that's including 1 tab with size 2" \ ' and 28 other characters' do - expect_no_offenses("\t" + '#' * 28) + expect_no_offenses("\t#{'#' * 28}") end end diff --git a/spec/rubocop/cop/range_help_spec.rb b/spec/rubocop/cop/range_help_spec.rb index a90c418c139..0ff40d66298 100644 --- a/spec/rubocop/cop/range_help_spec.rb +++ b/spec/rubocop/cop/range_help_spec.rb @@ -111,7 +111,7 @@ context 'with include_final_newline' do let(:include_final_newline) { true } - it { is_expected.to eq(expected + "\n") } + it { is_expected.to eq("#{expected}\n") } end end diff --git a/spec/rubocop/cop/style/string_concatenation_spec.rb b/spec/rubocop/cop/style/string_concatenation_spec.rb new file mode 100644 index 00000000000..c942930734f --- /dev/null +++ b/spec/rubocop/cop/style/string_concatenation_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::Style::StringConcatenation do + subject(:cop) { described_class.new } + + it 'registers an offense and corrects for string concatenation' do + expect_offense(<<~RUBY) + email_with_name = user.name + ' <' + user.email + '>' + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer string interpolation instead of string concatenation. + RUBY + + expect_correction(<<~RUBY) + email_with_name = "\#{user.name} <\#{user.email}>" + RUBY + end + + it 'registers an offense and corrects for string concatenation as part of other expression' do + expect_offense(<<~RUBY) + users = (user.name + ' ' + user.email) * 5 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer string interpolation instead of string concatenation. + RUBY + + expect_correction(<<~RUBY) + users = ("\#{user.name} \#{user.email}") * 5 + RUBY + end + + it 'correctly handles strings with special characters' do + expect_offense(<<-RUBY) + email_with_name = "\\n" + user.name + ' ' + user.email + '\\n' + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer string interpolation instead of string concatenation. + RUBY + + expect_correction(<<-RUBY) + email_with_name = "\\n\#{user.name} \#{user.email}\\\\n" + RUBY + end + + it 'does not register an offense when using `+` with all non string arguments' do + expect_no_offenses(<<~RUBY) + user.name + user.email + RUBY + end +end diff --git a/spec/rubocop/cop/utils/format_string_spec.rb b/spec/rubocop/cop/utils/format_string_spec.rb index 6fbaa787642..9226b373a2e 100644 --- a/spec/rubocop/cop/utils/format_string_spec.rb +++ b/spec/rubocop/cop/utils/format_string_spec.rb @@ -70,7 +70,7 @@ def format_sequences(string) expect(described_class.new(escaped).named_interpolation?) .to be_falsey - expect(described_class.new('prefix:' + escaped).named_interpolation?) + expect(described_class.new("prefix:#{escaped}").named_interpolation?) .to be_falsey end end diff --git a/spec/support/multiline_literal_brace_helper.rb b/spec/support/multiline_literal_brace_helper.rb index 129b810ddd0..3cafcb81403 100644 --- a/spec/support/multiline_literal_brace_helper.rb +++ b/spec/support/multiline_literal_brace_helper.rb @@ -37,6 +37,6 @@ def default_args # Construct a piece of source code for brace layout testing. This farms # out most of the work to `#braces` but it also includes a prefix and suffix. def construct(*args) - (prefix + braces(*args) + "\n" + suffix) + "#{prefix}#{braces(*args)}\n#{suffix}" end end diff --git a/tasks/cops_documentation.rake b/tasks/cops_documentation.rake index 9d49f1c9493..686c698b5a7 100644 --- a/tasks/cops_documentation.rake +++ b/tasks/cops_documentation.rake @@ -60,7 +60,7 @@ task generate_cops_documentation: :yard_for_generate_documentation do cop_config.fetch('VersionAdded', '-'), cop_config.fetch('VersionChanged', '-') ]] - to_table(header, content) + "\n" + "#{to_table(header, content)}\n" end # rubocop:enable Metrics/MethodLength @@ -196,7 +196,7 @@ task generate_cops_documentation: :yard_for_generate_documentation do file_name = "#{Dir.pwd}/docs/modules/ROOT/pages/cops_#{department.downcase}.adoc" File.open(file_name, 'w') do |file| puts "* generated #{file_name}" - file.write(content.strip + "\n") + file.write("#{content.strip}\n") end end