diff --git a/changelog/new_add_autocorrect_for_hash_in_litelal_interpolation.md b/changelog/new_add_autocorrect_for_hash_in_litelal_interpolation.md new file mode 100644 index 000000000000..eb060f48ddbe --- /dev/null +++ b/changelog/new_add_autocorrect_for_hash_in_litelal_interpolation.md @@ -0,0 +1 @@ +* [#11475](https://github.com/rubocop/rubocop/pull/11475): Add autocorrect for hash in `Lint/LiteralInInterpolation`. ([@KessaPassa][]) diff --git a/lib/rubocop/cop/lint/literal_in_interpolation.rb b/lib/rubocop/cop/lint/literal_in_interpolation.rb index fb6684ba737b..ab2e2539d066 100644 --- a/lib/rubocop/cop/lint/literal_in_interpolation.rb +++ b/lib/rubocop/cop/lint/literal_in_interpolation.rb @@ -58,7 +58,7 @@ def special_keyword?(node) (node.str_type? && !node.loc.respond_to?(:begin)) || node.source_range.is?('__LINE__') end - # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity def autocorrected_value(node) case node.type when :int @@ -71,13 +71,15 @@ def autocorrected_value(node) autocorrected_value_for_symbol(node) when :array autocorrected_value_for_array(node) + when :hash + autocorrected_value_for_hash(node) when :nil '' else node.source.gsub('"', '\"') end end - # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity def autocorrected_value_for_string(node) if node.source.start_with?("'", '%q') @@ -91,7 +93,16 @@ def autocorrected_value_for_symbol(node) end_pos = node.loc.end ? node.loc.end.begin_pos : node.source_range.end_pos - range_between(node.loc.begin.end_pos, end_pos).source + range_between(node.loc.begin.end_pos, end_pos).source.gsub('"', '\"') + end + + def autocorrected_value_in_hash_for_symbol(node) + # TODO: We need to detect symbol unacceptable names more correctly + if / |"|'/.match?(node.value.to_s) + ":\\\"#{node.value.to_s.gsub('"') { '\\\\\"' }}\\\"" + else + ":#{node.value}" + end end def autocorrected_value_for_array(node) @@ -100,6 +111,37 @@ def autocorrected_value_for_array(node) contents_range(node).source.split.to_s.gsub('"', '\"') end + def autocorrected_value_for_hash(node) + hash_string = node.children.map do |child| + key = autocorrected_value_in_hash(child.key) + value = autocorrected_value_in_hash(child.value) + "#{key}=>#{value}" + end.join(', ') + + "{#{hash_string}}" + end + + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + def autocorrected_value_in_hash(node) + case node.type + when :int + node.children.last.to_i.to_s + when :float + node.children.last.to_f.to_s + when :str + "\\\"#{node.value.to_s.gsub('"') { '\\\\\"' }}\\\"" + when :sym + autocorrected_value_in_hash_for_symbol(node) + when :array + autocorrected_value_for_array(node) + when :hash + autocorrected_value_for_hash(node) + else + node.source.gsub('"', '\"') + end + end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + # Does node print its own source when converted to a string? def prints_as_self?(node) node.basic_literal? || diff --git a/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb b/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb index b9933937578f..bd490a51e4c7 100644 --- a/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb +++ b/spec/rubocop/cop/lint/literal_in_interpolation_spec.rb @@ -89,31 +89,113 @@ end end - it_behaves_like('literal interpolation', 1) - it_behaves_like('literal interpolation', -1) - it_behaves_like('literal interpolation', '1_123', '1123') - it_behaves_like('literal interpolation', '123_456_789_123_456_789', '123456789123456789') - it_behaves_like('literal interpolation', '1.2e-3', '0.0012') - it_behaves_like('literal interpolation', '0xaabb', '43707') - it_behaves_like('literal interpolation', '0o377', '255') - it_behaves_like('literal interpolation', 2.0) - it_behaves_like('literal interpolation', '[]', '[]') - it_behaves_like('literal interpolation', '["a", "b"]', '[\"a\", \"b\"]') - it_behaves_like('literal interpolation', '{"a" => "b"}', '{\"a\" => \"b\"}') - it_behaves_like('literal interpolation', true) - it_behaves_like('literal interpolation', false) - it_behaves_like('literal interpolation', 'nil', '') - it_behaves_like('literal interpolation', ':symbol', 'symbol') - it_behaves_like('literal interpolation', ':"symbol"', 'symbol') - it_behaves_like('literal interpolation', 1..2) - it_behaves_like('literal interpolation', 1...2) - it_behaves_like('literal interpolation', '%w[]', '[]') - it_behaves_like('literal interpolation', '%w[v1]', '[\"v1\"]') - it_behaves_like('literal interpolation', '%w[v1 v2]', '[\"v1\", \"v2\"]') - it_behaves_like('literal interpolation', '%i[s1 s2]', '[\"s1\", \"s2\"]') - it_behaves_like('literal interpolation', '%I[s1 s2]', '[\"s1\", \"s2\"]') - it_behaves_like('literal interpolation', '%i[s1 s2]', '[\"s1\", \"s2\"]') - it_behaves_like('literal interpolation', '%i[ s1 s2 ]', '[\"s1\", \"s2\"]') + describe 'type int' do + it_behaves_like('literal interpolation', 1) + it_behaves_like('literal interpolation', -1) + it_behaves_like('literal interpolation', '1_123', '1123') + it_behaves_like('literal interpolation', '123_456_789_123_456_789', '123456789123456789') + it_behaves_like('literal interpolation', '0xaabb', '43707') + it_behaves_like('literal interpolation', '0o377', '255') + end + + describe 'type float' do + it_behaves_like('literal interpolation', '1.2e-3', '0.0012') + it_behaves_like('literal interpolation', 2.0) + end + + describe 'type str' do + it_behaves_like('literal interpolation', '"double_quot_string"', 'double_quot_string') + it_behaves_like('literal interpolation', "'single_quot_string'", 'single_quot_string') + it_behaves_like('literal interpolation', '"double_quot_string: \'"', "double_quot_string: '") + it_behaves_like('literal interpolation', "'single_quot_string: \"'", 'single_quot_string: \"') + end + + describe 'type sym' do + it_behaves_like('literal interpolation', ':symbol', 'symbol') + it_behaves_like('literal interpolation', ':"symbol"', 'symbol') + it_behaves_like('literal interpolation', + ':"single quot in symbol: \'"', "single quot in symbol: '") + it_behaves_like('literal interpolation', + ":'double quot in symbol: \"'", 'double quot in symbol: \"') + end + + describe 'type array' do + it_behaves_like('literal interpolation', '[]', '[]') + it_behaves_like('literal interpolation', '["a", "b"]', '[\"a\", \"b\"]') + it_behaves_like('literal interpolation', '%w[]', '[]') + it_behaves_like('literal interpolation', '%w[v1]', '[\"v1\"]') + it_behaves_like('literal interpolation', '%w[v1 v2]', '[\"v1\", \"v2\"]') + it_behaves_like('literal interpolation', '%i[s1 s2]', '[\"s1\", \"s2\"]') + it_behaves_like('literal interpolation', '%I[s1 s2]', '[\"s1\", \"s2\"]') + it_behaves_like('literal interpolation', '%i[s1 s2]', '[\"s1\", \"s2\"]') + it_behaves_like('literal interpolation', '%i[ s1 s2 ]', '[\"s1\", \"s2\"]') + end + + describe 'type hash' do + it_behaves_like('literal interpolation', '{"a" => "b"}', '{\"a\"=>\"b\"}') + it_behaves_like('literal interpolation', "{ foo: 'bar', :fiz => \"buzz\" }", + '{:foo=>\"bar\", :fiz=>\"buzz\"}') + it_behaves_like('literal interpolation', "{ foo: { fiz: 'buzz' } }", '{:foo=>{:fiz=>\"buzz\"}}') + it_behaves_like( + 'literal interpolation', + '{ num: { separate: 1_123, long_separate: 123_456_789_123_456_789, exponent: 1.2e-3 } }', + '{:num=>{:separate=>1123, :long_separate=>123456789123456789, :exponent=>0.0012}}' + ) + it_behaves_like('literal interpolation', '{ n_adic_num: { hex: 0xaabb, oct: 0o377 } }', + '{:n_adic_num=>{:hex=>43707, :oct=>255}}') + it_behaves_like( + 'literal interpolation', + '{ double_quot: { simple: "double_quot", single_in_double: "double_quot: \'" } }', + '{:double_quot=>{:simple=>\"double_quot\", :single_in_double=>\"double_quot: \'\"}}' + ) + it_behaves_like( + 'literal interpolation', + "{ single_quot: { simple: 'single_quot', double_in_single: 'single_quot: \"' } }", + '{:single_quot=>{:simple=>\"single_quot\", :double_in_single=>\"single_quot: \\\\\\"\"}}' + ) + it_behaves_like('literal interpolation', '{ bool: { key: true } }', '{:bool=>{:key=>true}}') + it_behaves_like('literal interpolation', '{ bool: { key: false } }', '{:bool=>{:key=>false}}') + it_behaves_like('literal interpolation', '{ nil: { key: nil } }', '{:nil=>{:key=>nil}}') + it_behaves_like('literal interpolation', '{ symbol: { key: :symbol } }', + '{:symbol=>{:key=>:symbol}}') + it_behaves_like('literal interpolation', '{ symbol: { key: :"symbol" } }', + '{:symbol=>{:key=>:symbol}}') + it_behaves_like('literal interpolation', + '{ single_quot_symbol: { key: :"single_quot_in_symbol: \'" } }', + '{:single_quot_symbol=>{:key=>:\"single_quot_in_symbol: \'\"}}') + it_behaves_like('literal interpolation', + "{ double_quot_symbol: { key: :'double_quot_in_symbol: \"' } }", + '{:double_quot_symbol=>{:key=>:\"double_quot_in_symbol: \\\\\"\"}}') + it_behaves_like('literal interpolation', + '{ single_quot_symbol_not_in_space: { key: :"single_quot_in_symbol:\'" } }', + '{:single_quot_symbol_not_in_space=>{:key=>:\"single_quot_in_symbol:\'\"}}') + it_behaves_like('literal interpolation', + '{ single_quot_symbol_in_space: { key: :"single_quot_in_symbol: " } }', + '{:single_quot_symbol_in_space=>{:key=>:\"single_quot_in_symbol: \"}}') + it_behaves_like('literal interpolation', '{ range: { key: 1..2 } }', '{:range=>{:key=>1..2}}') + it_behaves_like('literal interpolation', '{ range: { key: 1...2 } }', '{:range=>{:key=>1...2}}') + it_behaves_like('literal interpolation', '{ array: { key: %w[] } }', '{:array=>{:key=>[]}}') + it_behaves_like('literal interpolation', '{ array: { key: %w[v1] } }', + '{:array=>{:key=>[\"v1\"]}}') + it_behaves_like('literal interpolation', '{ array: { key: %w[v1 v2] } }', + '{:array=>{:key=>[\"v1\", \"v2\"]}}') + it_behaves_like('literal interpolation', '{ array: { key: %i[s1 s2] } }', + '{:array=>{:key=>[\"s1\", \"s2\"]}}') + it_behaves_like('literal interpolation', '{ array: { key: %I[s1 s2] } }', + '{:array=>{:key=>[\"s1\", \"s2\"]}}') + it_behaves_like('literal interpolation', '{ array: { key: %i[s1 s2] } }', + '{:array=>{:key=>[\"s1\", \"s2\"]}}') + it_behaves_like('literal interpolation', '{ array: { key: %i[ s1 s2 ] } }', + '{:array=>{:key=>[\"s1\", \"s2\"]}}') + end + + describe 'type else' do + it_behaves_like('literal interpolation', 'nil', '') + it_behaves_like('literal interpolation', 1..2) + it_behaves_like('literal interpolation', 1...2) + it_behaves_like('literal interpolation', true) + it_behaves_like('literal interpolation', false) + end shared_examples 'literal interpolation in words literal' do |prefix| let(:word) { 'interpolation' }