rubocop 1.4.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +54 -11
  4. data/config/obsoletion.yml +196 -0
  5. data/lib/rubocop.rb +14 -0
  6. data/lib/rubocop/cli.rb +5 -1
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +80 -0
  8. data/lib/rubocop/config_loader.rb +1 -1
  9. data/lib/rubocop/config_loader_resolver.rb +5 -1
  10. data/lib/rubocop/config_obsoletion.rb +65 -247
  11. data/lib/rubocop/config_obsoletion/changed_enforced_styles.rb +33 -0
  12. data/lib/rubocop/config_obsoletion/changed_parameter.rb +21 -0
  13. data/lib/rubocop/config_obsoletion/cop_rule.rb +34 -0
  14. data/lib/rubocop/config_obsoletion/extracted_cop.rb +44 -0
  15. data/lib/rubocop/config_obsoletion/parameter_rule.rb +44 -0
  16. data/lib/rubocop/config_obsoletion/removed_cop.rb +41 -0
  17. data/lib/rubocop/config_obsoletion/renamed_cop.rb +34 -0
  18. data/lib/rubocop/config_obsoletion/rule.rb +41 -0
  19. data/lib/rubocop/config_obsoletion/split_cop.rb +27 -0
  20. data/lib/rubocop/config_validator.rb +18 -4
  21. data/lib/rubocop/cop/autocorrect_logic.rb +21 -6
  22. data/lib/rubocop/cop/base.rb +17 -15
  23. data/lib/rubocop/cop/cop.rb +2 -2
  24. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +6 -8
  25. data/lib/rubocop/cop/generator.rb +1 -1
  26. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +3 -3
  27. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +6 -1
  28. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -1
  29. data/lib/rubocop/cop/layout/end_of_line.rb +5 -5
  30. data/lib/rubocop/cop/layout/first_argument_indentation.rb +7 -2
  31. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +12 -0
  32. data/lib/rubocop/cop/layout/line_length.rb +6 -16
  33. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +7 -3
  34. data/lib/rubocop/cop/lint/interpolation_check.rb +7 -2
  35. data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +1 -1
  36. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +13 -0
  37. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +85 -0
  38. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +7 -2
  39. data/lib/rubocop/cop/metrics/abc_size.rb +25 -1
  40. data/lib/rubocop/cop/metrics/block_length.rb +13 -7
  41. data/lib/rubocop/cop/metrics/method_length.rb +7 -2
  42. data/lib/rubocop/cop/metrics/parameter_lists.rb +64 -1
  43. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +20 -10
  44. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +146 -0
  45. data/lib/rubocop/cop/metrics/utils/repeated_csend_discount.rb +6 -1
  46. data/lib/rubocop/cop/migration/department_name.rb +1 -1
  47. data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -2
  48. data/lib/rubocop/cop/mixin/enforce_superclass.rb +9 -1
  49. data/lib/rubocop/cop/mixin/ignored_methods.rb +36 -3
  50. data/lib/rubocop/cop/mixin/method_complexity.rb +6 -0
  51. data/lib/rubocop/cop/mixin/string_help.rb +4 -1
  52. data/lib/rubocop/cop/naming/accessor_method_name.rb +15 -1
  53. data/lib/rubocop/cop/naming/variable_number.rb +3 -1
  54. data/lib/rubocop/cop/style/and_or.rb +10 -0
  55. data/lib/rubocop/cop/style/character_literal.rb +10 -11
  56. data/lib/rubocop/cop/style/class_and_module_children.rb +8 -3
  57. data/lib/rubocop/cop/style/float_division.rb +44 -1
  58. data/lib/rubocop/cop/style/format_string.rb +8 -3
  59. data/lib/rubocop/cop/style/if_unless_modifier.rb +4 -0
  60. data/lib/rubocop/cop/style/if_with_semicolon.rb +39 -4
  61. data/lib/rubocop/cop/style/ip_addresses.rb +1 -1
  62. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +11 -2
  63. data/lib/rubocop/cop/style/numeric_literals.rb +14 -11
  64. data/lib/rubocop/cop/style/perl_backrefs.rb +86 -9
  65. data/lib/rubocop/cop/style/redundant_argument.rb +17 -2
  66. data/lib/rubocop/cop/style/redundant_condition.rb +2 -1
  67. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +24 -8
  68. data/lib/rubocop/cop/style/single_line_block_params.rb +30 -7
  69. data/lib/rubocop/cop/style/sole_nested_conditional.rb +65 -3
  70. data/lib/rubocop/cop/style/special_global_vars.rb +1 -13
  71. data/lib/rubocop/cop/style/string_concatenation.rb +26 -1
  72. data/lib/rubocop/cop/style/string_literals.rb +14 -8
  73. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -3
  74. data/lib/rubocop/cop/style/symbol_proc.rb +5 -3
  75. data/lib/rubocop/core_ext/hash.rb +20 -0
  76. data/lib/rubocop/ext/regexp_node.rb +29 -12
  77. data/lib/rubocop/ext/regexp_parser.rb +20 -9
  78. data/lib/rubocop/formatter/emacs_style_formatter.rb +2 -0
  79. data/lib/rubocop/formatter/simple_text_formatter.rb +2 -0
  80. data/lib/rubocop/formatter/tap_formatter.rb +2 -0
  81. data/lib/rubocop/lockfile.rb +40 -0
  82. data/lib/rubocop/version.rb +1 -1
  83. metadata +32 -5
@@ -36,13 +36,20 @@ module RuboCop
36
36
  # string.split
37
37
  # "first second".split
38
38
  # A.foo
39
- class RedundantArgument < Cop
39
+ class RedundantArgument < Base
40
+ include RangeHelp
41
+ extend AutoCorrector
42
+
40
43
  MSG = 'Argument %<arg>s is redundant because it is implied by default.'
41
44
 
42
45
  def on_send(node)
46
+ return if node.receiver.nil?
47
+ return if node.arguments.count != 1
43
48
  return unless redundant_argument?(node)
44
49
 
45
- add_offense(node, message: format(MSG, arg: node.arguments.first.source))
50
+ add_offense(node, message: format(MSG, arg: node.arguments.first.source)) do |corrector|
51
+ corrector.remove(argument_range(node))
52
+ end
46
53
  end
47
54
 
48
55
  private
@@ -67,6 +74,14 @@ module RuboCop
67
74
  Parser::CurrentRuby.new(builder).parse(buffer)
68
75
  end
69
76
  end
77
+
78
+ def argument_range(node)
79
+ if node.parenthesized?
80
+ range_between(node.loc.begin.begin_pos, node.loc.end.end_pos)
81
+ else
82
+ range_with_surrounding_space(range: node.first_argument.source_range, newlines: false)
83
+ end
84
+ end
70
85
  end
71
86
  end
72
87
  end
@@ -131,7 +131,8 @@ module RuboCop
131
131
  end
132
132
 
133
133
  def without_argument_parentheses_method?(node)
134
- node.send_type? && !node.arguments.empty? && !node.parenthesized?
134
+ node.send_type? &&
135
+ !node.arguments.empty? && !node.parenthesized? && !node.operator_method?
135
136
  end
136
137
  end
137
138
  end
@@ -80,14 +80,30 @@ module RuboCop
80
80
  delimiters.include?(char)
81
81
  end
82
82
 
83
- def each_escape(node)
84
- node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
85
- yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
86
-
87
- if expr.type == :set
88
- char_class_depth + (event == :enter ? 1 : -1)
89
- else
90
- char_class_depth
83
+ if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0')
84
+ def each_escape(node)
85
+ node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
86
+ yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape
87
+
88
+ if expr.type == :set
89
+ char_class_depth + (event == :enter ? 1 : -1)
90
+ else
91
+ char_class_depth
92
+ end
93
+ end
94
+ end
95
+ # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped.
96
+ # It's for compatibility with regexp_arser 1.8 and will never be maintained.
97
+ else
98
+ def each_escape(node)
99
+ node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)|
100
+ yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape
101
+
102
+ if expr.type == :set
103
+ char_class_depth + (event == :enter ? 1 : -1)
104
+ else
105
+ char_class_depth
106
+ end
91
107
  end
92
108
  end
93
109
  end
@@ -29,6 +29,8 @@ module RuboCop
29
29
  # c + d
30
30
  # end
31
31
  class SingleLineBlockParams < Base
32
+ extend AutoCorrector
33
+
32
34
  MSG = 'Name `%<method>s` block params `|%<params>s|`.'
33
35
 
34
36
  def on_block(node)
@@ -37,20 +39,41 @@ module RuboCop
37
39
  return unless eligible_method?(node)
38
40
  return unless eligible_arguments?(node)
39
41
 
40
- return if args_match?(node.send_node.method_name, node.arguments)
42
+ method_name = node.send_node.method_name
43
+ return if args_match?(method_name, node.arguments)
44
+
45
+ preferred_block_arguments = build_preferred_arguments_map(node, target_args(method_name))
46
+ joined_block_arguments = preferred_block_arguments.values.join(', ')
41
47
 
42
- message = message(node.arguments)
48
+ message = format(MSG, method: method_name, params: joined_block_arguments)
43
49
 
44
- add_offense(node.arguments, message: message)
50
+ add_offense(node.arguments, message: message) do |corrector|
51
+ autocorrect(corrector, node, preferred_block_arguments, joined_block_arguments)
52
+ end
45
53
  end
46
54
 
47
55
  private
48
56
 
49
- def message(node)
50
- method_name = node.parent.send_node.method_name
51
- arguments = target_args(method_name).join(', ')
57
+ def build_preferred_arguments_map(node, preferred_arguments)
58
+ preferred_arguments_map = {}
59
+ node.arguments.each_with_index do |current_lvar, index|
60
+ preferred_argument = preferred_arguments[index]
61
+ current_argument = current_lvar.source
62
+ preferred_argument = "_#{preferred_argument}" if current_argument.start_with?('_')
63
+ preferred_arguments_map[current_argument] = preferred_argument
64
+ end
65
+
66
+ preferred_arguments_map
67
+ end
68
+
69
+ def autocorrect(corrector, node, preferred_block_arguments, joined_block_arguments)
70
+ corrector.replace(node.arguments, "|#{joined_block_arguments}|")
52
71
 
53
- format(MSG, method: method_name, params: arguments)
72
+ node.each_descendant(:lvar) do |lvar|
73
+ if (preferred_lvar = preferred_block_arguments[lvar.source])
74
+ corrector.replace(lvar, preferred_lvar)
75
+ end
76
+ end
54
77
  end
55
78
 
56
79
  def eligible_arguments?(node)
@@ -33,17 +33,22 @@ module RuboCop
33
33
  # end
34
34
  #
35
35
  class SoleNestedConditional < Base
36
+ include RangeHelp
37
+ extend AutoCorrector
38
+
36
39
  MSG = 'Consider merging nested conditions into '\
37
40
  'outer `%<conditional_type>s` conditions.'
38
41
 
39
42
  def on_if(node)
40
43
  return if node.ternary? || node.else? || node.elsif?
41
44
 
42
- branch = node.if_branch
43
- return unless offending_branch?(branch)
45
+ if_branch = node.if_branch
46
+ return unless offending_branch?(if_branch)
44
47
 
45
48
  message = format(MSG, conditional_type: node.keyword)
46
- add_offense(branch.loc.keyword, message: message)
49
+ add_offense(if_branch.loc.keyword, message: message) do |corrector|
50
+ autocorrect(corrector, node, if_branch)
51
+ end
47
52
  end
48
53
 
49
54
  private
@@ -57,6 +62,63 @@ module RuboCop
57
62
  !(branch.modifier_form? && allow_modifier?)
58
63
  end
59
64
 
65
+ def autocorrect(corrector, node, if_branch)
66
+ if node.unless?
67
+ corrector.replace(node.loc.keyword, 'if')
68
+ corrector.insert_before(node.condition, '!')
69
+ end
70
+
71
+ corrector.wrap(node.condition, '(', ')') if node.condition.or_type?
72
+
73
+ and_operator = if_branch.unless? ? ' && !' : ' && '
74
+ if if_branch.modifier_form?
75
+ correct_for_guard_condition_style(corrector, node, if_branch, and_operator)
76
+ else
77
+ correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
78
+ correct_for_comment(corrector, node, if_branch)
79
+ end
80
+ end
81
+
82
+ def correct_for_guard_condition_style(corrector, node, if_branch, and_operator)
83
+ condition = if_branch.condition
84
+ corrector.insert_after(node.condition, replacement_condition(and_operator, condition))
85
+
86
+ range = range_between(if_branch.loc.keyword.begin_pos, condition.source_range.end_pos)
87
+ corrector.remove(range_with_surrounding_space(range: range, newlines: false))
88
+ corrector.remove(if_branch.loc.keyword)
89
+ end
90
+
91
+ def correct_for_basic_condition_style(corrector, node, if_branch, and_operator)
92
+ range = range_between(
93
+ node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
94
+ )
95
+ corrector.replace(range, and_operator)
96
+ corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
97
+ corrector.wrap(if_branch.condition, '(', ')') if wrap_condition?(if_branch.condition)
98
+ end
99
+
100
+ def correct_for_comment(corrector, node, if_branch)
101
+ return if config.for_cop('Style/IfUnlessModifier')['Enabled']
102
+
103
+ comments = processed_source.comments_before_line(if_branch.source_range.line)
104
+ comment_text = comments.map(&:text).join("\n") << "\n"
105
+
106
+ corrector.insert_before(node.loc.keyword, comment_text) unless comments.empty?
107
+ end
108
+
109
+ def wrap_condition?(node)
110
+ node.or_type? ||
111
+ (node.send_type? && node.arguments.any? && !node.parenthesized?)
112
+ end
113
+
114
+ def replacement_condition(and_operator, condition)
115
+ if wrap_condition?(condition)
116
+ "#{and_operator}(#{condition.source})"
117
+ else
118
+ "#{and_operator}#{condition.source}"
119
+ end
120
+ end
121
+
60
122
  def allow_modifier?
61
123
  cop_config['AllowModifier']
62
124
  end
@@ -26,10 +26,6 @@ module RuboCop
26
26
  # puts $LAST_MATCH_INFO
27
27
  # puts $IGNORECASE
28
28
  # puts $ARGV # or ARGV
29
- # puts $MATCH
30
- # puts $PREMATCH
31
- # puts $POSTMATCH
32
- # puts $LAST_PAREN_MATCH
33
29
  #
34
30
  # @example EnforcedStyle: use_perl_names
35
31
  # # good
@@ -51,10 +47,6 @@ module RuboCop
51
47
  # puts $~
52
48
  # puts $=
53
49
  # puts $*
54
- # puts $&
55
- # puts $`
56
- # puts $'
57
- # puts $+
58
50
  #
59
51
  class SpecialGlobalVars < Base
60
52
  include ConfigurableEnforcedStyle
@@ -85,11 +77,7 @@ module RuboCop
85
77
  :$? => [:$CHILD_STATUS],
86
78
  :$~ => [:$LAST_MATCH_INFO],
87
79
  :$= => [:$IGNORECASE],
88
- :$* => %i[$ARGV ARGV],
89
- :$& => [:$MATCH],
90
- :$` => [:$PREMATCH],
91
- :$' => [:$POSTMATCH],
92
- :$+ => [:$LAST_PAREN_MATCH]
80
+ :$* => %i[$ARGV ARGV]
93
81
  }
94
82
 
95
83
  PERL_VARS =
@@ -11,6 +11,10 @@ module RuboCop
11
11
  # In those cases, it might be useful to extract statements to local
12
12
  # variables or methods which you can then interpolate in a string.
13
13
  #
14
+ # NOTE: When concatenation between two strings is broken over multiple
15
+ # lines, this cop does not register an offense; instead,
16
+ # `Style/LineEndConcatenation` will pick up the offense if enabled.
17
+ #
14
18
  # @example
15
19
  # # bad
16
20
  # email_with_name = user.name + ' <' + user.email + '>'
@@ -19,6 +23,10 @@ module RuboCop
19
23
  # email_with_name = "#{user.name} <#{user.email}>"
20
24
  # email_with_name = format('%s <%s>', user.name, user.email)
21
25
  #
26
+ # # accepted, line-end concatenation
27
+ # name = 'First' +
28
+ # 'Last'
29
+ #
22
30
  class StringConcatenation < Base
23
31
  include Util
24
32
  extend AutoCorrector
@@ -39,6 +47,7 @@ module RuboCop
39
47
 
40
48
  def on_send(node)
41
49
  return unless string_concatenation?(node)
50
+ return if line_end_concatenation?(node)
42
51
 
43
52
  topmost_plus_node = find_topmost_plus_node(node)
44
53
 
@@ -58,6 +67,16 @@ module RuboCop
58
67
 
59
68
  private
60
69
 
70
+ def line_end_concatenation?(node)
71
+ # If the concatenation happens at the end of the line,
72
+ # and both the receiver and argument are strings, allow
73
+ # `Style/LineEndConcatenation` to handle it instead.
74
+ node.receiver.str_type? &&
75
+ node.first_argument.str_type? &&
76
+ node.multiline? &&
77
+ node.source =~ /\+\s*\n/
78
+ end
79
+
61
80
  def find_topmost_plus_node(node)
62
81
  current = node
63
82
  while (parent = current.parent) && plus_node?(parent)
@@ -106,7 +125,13 @@ module RuboCop
106
125
  end
107
126
  end
108
127
 
109
- "\"#{interpolated_parts.join}\""
128
+ "\"#{handle_quotes(interpolated_parts).join}\""
129
+ end
130
+
131
+ def handle_quotes(parts)
132
+ parts.map do |part|
133
+ part == '"' ? '\"' : part
134
+ end
110
135
  end
111
136
 
112
137
  def single_quoted?(str_node)
@@ -26,9 +26,10 @@ module RuboCop
26
26
  # "Just some text"
27
27
  # "No special chars or interpolation"
28
28
  # "Every string in #{project} uses double_quotes"
29
- class StringLiterals < Cop
29
+ class StringLiterals < Base
30
30
  include ConfigurableEnforcedStyle
31
31
  include StringLiteralsHelp
32
+ extend AutoCorrector
32
33
 
33
34
  MSG_INCONSISTENT = 'Inconsistent quote style.'
34
35
 
@@ -46,7 +47,7 @@ module RuboCop
46
47
  quote_styles = detect_quote_styles(node)
47
48
 
48
49
  if quote_styles.size > 1
49
- add_offense(node, message: MSG_INCONSISTENT)
50
+ register_offense(node, message: MSG_INCONSISTENT)
50
51
  else
51
52
  check_multiline_quote_style(node, quote_styles[0])
52
53
  end
@@ -54,11 +55,17 @@ module RuboCop
54
55
  ignore_node(node)
55
56
  end
56
57
 
57
- def autocorrect(node)
58
- StringLiteralCorrector.correct(node, style)
58
+ private
59
+
60
+ def autocorrect(corrector, node)
61
+ StringLiteralCorrector.correct(corrector, node, style)
59
62
  end
60
63
 
61
- private
64
+ def register_offense(node, message: nil)
65
+ add_offense(node, message: message || message(node)) do |corrector|
66
+ autocorrect(corrector, node)
67
+ end
68
+ end
62
69
 
63
70
  def all_string_literals?(nodes)
64
71
  nodes.all? { |n| n.str_type? || n.dstr_type? }
@@ -99,14 +106,13 @@ module RuboCop
99
106
  end
100
107
 
101
108
  def check_multiline_quote_style(node, quote)
102
- range = node.source_range
103
109
  children = node.children
104
110
  if unexpected_single_quotes?(quote)
105
111
  all_children_with_quotes = children.all? { |c| wrong_quotes?(c) }
106
- add_offense(node, location: range) if all_children_with_quotes
112
+ register_offense(node) if all_children_with_quotes
107
113
  elsif unexpected_double_quotes?(quote) &&
108
114
  !accept_child_double_quotes?(children)
109
- add_offense(node, location: range)
115
+ register_offense(node)
110
116
  end
111
117
  end
112
118
 
@@ -19,12 +19,13 @@ module RuboCop
19
19
  #
20
20
  # # good
21
21
  # result = "Tests #{success ? "PASS" : "FAIL"}"
22
- class StringLiteralsInInterpolation < Cop
22
+ class StringLiteralsInInterpolation < Base
23
23
  include ConfigurableEnforcedStyle
24
24
  include StringLiteralsHelp
25
+ extend AutoCorrector
25
26
 
26
- def autocorrect(node)
27
- StringLiteralCorrector.correct(node, style)
27
+ def autocorrect(corrector, node)
28
+ StringLiteralCorrector.correct(corrector, node, style)
28
29
  end
29
30
 
30
31
  private
@@ -8,6 +8,7 @@ module RuboCop
8
8
  # @example
9
9
  # # bad
10
10
  # something.map { |s| s.upcase }
11
+ # something.map { _1.upcase }
11
12
  #
12
13
  # # good
13
14
  # something.map(&:upcase)
@@ -22,9 +23,9 @@ module RuboCop
22
23
 
23
24
  def_node_matcher :proc_node?, '(send (const {nil? cbase} :Proc) :new)'
24
25
  def_node_matcher :symbol_proc?, <<~PATTERN
25
- (block
26
+ ({block numblock}
26
27
  ${(send ...) (super ...) zsuper}
27
- $(args (arg _var))
28
+ ${(args (arg _)) %Integer}
28
29
  (send (lvar _var) $_))
29
30
  PATTERN
30
31
 
@@ -40,11 +41,12 @@ module RuboCop
40
41
  return if proc_node?(dispatch_node)
41
42
  return if %i[lambda proc].include?(dispatch_node.method_name)
42
43
  return if ignored_method?(dispatch_node.method_name)
43
- return if destructuring_block_argument?(arguments_node)
44
+ return if node.block_type? && destructuring_block_argument?(arguments_node)
44
45
 
45
46
  register_offense(node, method_name, dispatch_node.method_name)
46
47
  end
47
48
  end
49
+ alias on_numblock on_block
48
50
 
49
51
  def destructuring_block_argument?(argument_node)
50
52
  argument_node.one? && argument_node.source.include?(',')
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extensions to the core Hash class
4
+ class Hash
5
+ unless method_defined?(:slice)
6
+ # Adds `Hash#slice` for Ruby 2.4.
7
+ # Returns a hash containing a subset of keys. If a given key is not
8
+ # in the hash, it will not be returned.
9
+ #
10
+ # @return [Hash] hash containing only the keys given.
11
+ #
12
+ # @example
13
+ # { one: 1, two: 2 }.slice(:two, :three) #=> { two: 2 }
14
+ def slice(*keys)
15
+ h = {}
16
+ keys.each { |k| h[k] = self[k] if key?(k) }
17
+ h
18
+ end
19
+ end
20
+ end