rubocop 0.23.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +35 -1
  4. data/CONTRIBUTING.md +1 -1
  5. data/README.md +6 -6
  6. data/config/default.yml +25 -6
  7. data/config/enabled.yml +20 -0
  8. data/lib/rubocop.rb +10 -13
  9. data/lib/rubocop/cli.rb +23 -20
  10. data/lib/rubocop/cop/lint/def_end_alignment.rb +47 -0
  11. data/lib/rubocop/cop/lint/end_alignment.rb +18 -65
  12. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +1 -1
  13. data/lib/rubocop/cop/lint/syntax.rb +28 -4
  14. data/lib/rubocop/cop/lint/underscore_prefixed_variable_name.rb +1 -1
  15. data/lib/rubocop/cop/lint/unused_block_argument.rb +13 -1
  16. data/lib/rubocop/cop/lint/useless_access_modifier.rb +3 -2
  17. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -9
  18. data/lib/rubocop/cop/lint/useless_setter_call.rb +28 -20
  19. data/lib/rubocop/cop/mixin/access_modifier_node.rb +18 -0
  20. data/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb +4 -2
  21. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +42 -0
  22. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +32 -0
  23. data/lib/rubocop/cop/mixin/surrounding_space.rb +7 -8
  24. data/lib/rubocop/cop/mixin/unused_argument.rb +1 -1
  25. data/lib/rubocop/cop/offense.rb +27 -14
  26. data/lib/rubocop/cop/style/access_modifier_indentation.rb +2 -9
  27. data/lib/rubocop/cop/style/attr.rb +3 -1
  28. data/lib/rubocop/cop/style/class_check.rb +42 -0
  29. data/lib/rubocop/cop/style/each_with_object.rb +5 -1
  30. data/lib/rubocop/cop/style/empty_lines.rb +1 -4
  31. data/lib/rubocop/cop/style/empty_lines_around_access_modifier.rb +2 -8
  32. data/lib/rubocop/cop/style/empty_lines_around_body.rb +1 -4
  33. data/lib/rubocop/cop/style/encoding.rb +3 -5
  34. data/lib/rubocop/cop/style/end_of_line.rb +2 -5
  35. data/lib/rubocop/cop/style/file_name.rb +2 -4
  36. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -1
  37. data/lib/rubocop/cop/style/indentation_consistency.rb +2 -1
  38. data/lib/rubocop/cop/style/indentation_width.rb +25 -5
  39. data/lib/rubocop/cop/style/line_length.rb +59 -5
  40. data/lib/rubocop/cop/style/next.rb +18 -18
  41. data/lib/rubocop/cop/style/numeric_literals.rb +22 -9
  42. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -0
  43. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  44. data/lib/rubocop/cop/style/semicolon.rb +1 -3
  45. data/lib/rubocop/cop/style/space_after_colon.rb +7 -3
  46. data/lib/rubocop/cop/style/space_around_equals_in_parameter_default.rb +3 -1
  47. data/lib/rubocop/cop/style/space_before_comma.rb +16 -0
  48. data/lib/rubocop/cop/style/space_before_semicolon.rb +16 -0
  49. data/lib/rubocop/cop/style/tab.rb +6 -5
  50. data/lib/rubocop/cop/style/trailing_comma.rb +33 -6
  51. data/lib/rubocop/cop/style/trailing_whitespace.rb +4 -3
  52. data/lib/rubocop/cop/style/unneeded_capital_w.rb +6 -0
  53. data/lib/rubocop/cop/style/unneeded_percent_q.rb +45 -0
  54. data/lib/rubocop/cop/style/unneeded_percent_x.rb +2 -3
  55. data/lib/rubocop/cop/style/word_array.rb +1 -1
  56. data/lib/rubocop/cop/team.rb +6 -12
  57. data/lib/rubocop/cop/util.rb +26 -8
  58. data/lib/rubocop/cop/variable_force.rb +3 -6
  59. data/lib/rubocop/cop/variable_force/variable.rb +7 -3
  60. data/lib/rubocop/processed_source.rb +52 -12
  61. data/lib/rubocop/{file_inspector.rb → runner.rb} +50 -59
  62. data/lib/rubocop/version.rb +1 -1
  63. data/relnotes/v0.24.0.md +77 -0
  64. data/rubocop.gemspec +4 -4
  65. data/spec/rubocop/cli_spec.rb +38 -21
  66. data/spec/rubocop/config_loader_spec.rb +7 -6
  67. data/spec/rubocop/config_spec.rb +8 -8
  68. data/spec/rubocop/cop/cop_spec.rb +1 -1
  69. data/spec/rubocop/cop/lint/def_end_alignment_spec.rb +108 -0
  70. data/spec/rubocop/cop/lint/end_alignment_spec.rb +0 -47
  71. data/spec/rubocop/cop/lint/invalid_character_literal_spec.rb +6 -7
  72. data/spec/rubocop/cop/lint/unused_block_argument_spec.rb +19 -0
  73. data/spec/rubocop/cop/lint/useless_assignment_spec.rb +8 -18
  74. data/spec/rubocop/cop/lint/useless_setter_call_spec.rb +99 -51
  75. data/spec/rubocop/cop/offense_spec.rb +3 -3
  76. data/spec/rubocop/cop/rails/delegate_spec.rb +1 -1
  77. data/spec/rubocop/cop/style/access_modifier_indentation_spec.rb +12 -0
  78. data/spec/rubocop/cop/style/align_hash_spec.rb +4 -4
  79. data/spec/rubocop/cop/style/align_parameters_spec.rb +1 -1
  80. data/spec/rubocop/cop/style/attr_spec.rb +12 -2
  81. data/spec/rubocop/cop/style/class_check_spec.rb +41 -0
  82. data/spec/rubocop/cop/style/each_with_object_spec.rb +5 -0
  83. data/spec/rubocop/cop/style/if_with_semicolon_spec.rb +5 -0
  84. data/spec/rubocop/cop/style/indentation_width_spec.rb +95 -0
  85. data/spec/rubocop/cop/style/line_length_spec.rb +75 -0
  86. data/spec/rubocop/cop/style/next_spec.rb +28 -0
  87. data/spec/rubocop/cop/style/numeric_literals_spec.rb +10 -0
  88. data/spec/rubocop/cop/style/parentheses_around_condition_spec.rb +5 -0
  89. data/spec/rubocop/cop/style/space_after_colon_spec.rb +17 -0
  90. data/spec/rubocop/cop/style/space_around_equals_in_parameter_default_spec.rb +11 -0
  91. data/spec/rubocop/cop/style/space_around_operators_spec.rb +1 -0
  92. data/spec/rubocop/cop/style/space_before_comma_spec.rb +42 -0
  93. data/spec/rubocop/cop/style/space_before_semicolon_spec.rb +28 -0
  94. data/spec/rubocop/cop/style/trailing_comma_spec.rb +37 -15
  95. data/spec/rubocop/cop/style/unneeded_capital_w_spec.rb +8 -10
  96. data/spec/rubocop/cop/style/unneeded_percent_q_spec.rb +72 -0
  97. data/spec/rubocop/cop/style/word_array_spec.rb +6 -0
  98. data/spec/rubocop/cop/team_spec.rb +8 -8
  99. data/spec/rubocop/cop/util_spec.rb +10 -0
  100. data/spec/rubocop/cop/variable_force/assignment_spec.rb +1 -1
  101. data/spec/rubocop/cop/variable_force/locatable_spec.rb +1 -1
  102. data/spec/rubocop/cop/variable_force/scope_spec.rb +1 -1
  103. data/spec/rubocop/cop/variable_force/variable_spec.rb +4 -4
  104. data/spec/rubocop/formatter/base_formatter_spec.rb +5 -5
  105. data/spec/rubocop/formatter/colorizable_spec.rb +2 -2
  106. data/spec/rubocop/formatter/json_formatter_spec.rb +1 -1
  107. data/spec/rubocop/path_util_spec.rb +15 -15
  108. data/spec/rubocop/processed_source_spec.rb +104 -50
  109. data/spec/rubocop/runner_spec.rb +64 -0
  110. data/spec/spec_helper.rb +8 -10
  111. data/spec/support/shared_examples.rb +22 -0
  112. metadata +39 -15
  113. data/lib/rubocop/source_parser.rb +0 -47
  114. data/spec/rubocop/file_inspector_spec.rb +0 -84
  115. data/spec/rubocop/source_parser_spec.rb +0 -85
@@ -23,25 +23,25 @@ module RuboCop
23
23
  include ConfigurableEnforcedStyle
24
24
 
25
25
  MSG = 'Use `next` to skip iteration.'
26
- METHODS = [:collect, :detect, :downto, :each, :find, :find_all,
27
- :inject, :loop, :map!, :map, :reduce, :reverse_each,
28
- :select, :times, :upto]
26
+ ENUMERATORS = [:collect, :detect, :downto, :each, :find, :find_all,
27
+ :inject, :loop, :map!, :map, :reduce, :reverse_each,
28
+ :select, :times, :upto]
29
29
 
30
30
  def on_block(node)
31
- method, _, body = *node
32
- return unless method.type == :send
31
+ block_owner, _, body = *node
32
+ return unless block_owner.type == :send
33
33
  return if body.nil?
34
34
 
35
- _, method_name = *method
36
- return unless method?(method_name)
35
+ _, method_name = *block_owner
36
+ return unless enumerator?(method_name)
37
37
  return unless ends_with_condition?(body)
38
38
 
39
- add_offense(method, :selector, MSG)
39
+ add_offense(block_owner, :selector, MSG)
40
40
  end
41
41
 
42
42
  def on_while(node)
43
43
  _, body = *node
44
- return unless ends_with_condition?(body)
44
+ return unless body && ends_with_condition?(body)
45
45
 
46
46
  add_offense(node, :keyword, MSG)
47
47
  end
@@ -56,8 +56,8 @@ module RuboCop
56
56
 
57
57
  private
58
58
 
59
- def method?(method_name)
60
- METHODS.include?(method_name) || /\Aeach_/.match(method_name)
59
+ def enumerator?(method_name)
60
+ ENUMERATORS.include?(method_name) || /\Aeach_/.match(method_name)
61
61
  end
62
62
 
63
63
  def ends_with_condition?(body)
@@ -66,14 +66,14 @@ module RuboCop
66
66
  body.type == :begin && simple_if_without_break?(body.children.last)
67
67
  end
68
68
 
69
- def simple_if_without_break?(body)
70
- return false if ternary_op?(body)
71
- return false if if_else?(body)
72
- return false unless body.type == :if
73
- return false if style == :skip_modifier_ifs && modifier_if?(body)
69
+ def simple_if_without_break?(node)
70
+ return false if ternary_op?(node)
71
+ return false if if_else?(node)
72
+ return false unless node.type == :if
73
+ return false if style == :skip_modifier_ifs && modifier_if?(node)
74
74
 
75
- _, return_method, return_body = *body
76
- (return_method || return_body).type != :break
75
+ _conditional, if_body, else_body = *node
76
+ ![:break, :return].include?((if_body || else_body).type)
77
77
  end
78
78
  end
79
79
  end
@@ -47,15 +47,28 @@ module RuboCop
47
47
 
48
48
  def autocorrect(node)
49
49
  @corrections << lambda do |corrector|
50
- int = node.loc.expression.source.to_i
51
- formatted_int = int
52
- .abs
53
- .to_s
54
- .reverse
55
- .gsub(/...(?=.)/, '\&_')
56
- .reverse
57
- formatted_int.insert(0, '-') if int < 0
58
- corrector.replace(node.loc.expression, formatted_int)
50
+ corrector.replace(
51
+ node.loc.expression,
52
+ format_number(node)
53
+ )
54
+ end
55
+ end
56
+
57
+ def format_number(node)
58
+ int_part, float_part = node.loc.expression.source.split('.')
59
+ int_part = int_part.to_i
60
+ formatted_int = int_part
61
+ .abs
62
+ .to_s
63
+ .reverse
64
+ .gsub(/...(?=.)/, '\&_')
65
+ .reverse
66
+ formatted_int.insert(0, '-') if int_part < 0
67
+
68
+ if float_part
69
+ format('%s.%s', formatted_int, float_part)
70
+ else
71
+ formatted_int
59
72
  end
60
73
  end
61
74
 
@@ -39,6 +39,7 @@ module RuboCop
39
39
  end
40
40
 
41
41
  def modifier_op?(node)
42
+ return false if ternary_op?(node)
42
43
  return true if node.type == :rescue
43
44
 
44
45
  [:if, :while, :until].include?(node.type) &&
@@ -27,7 +27,7 @@ module RuboCop
27
27
  #
28
28
  # def foo2
29
29
  # bar = 1
30
- # self.bar # resolves name class with local variable
30
+ # self.bar # resolves name clash with local variable
31
31
  # end
32
32
  #
33
33
  # * Calling an attribute writer to prevent an local variable assignment
@@ -50,9 +50,7 @@ module RuboCop
50
50
  end
51
51
 
52
52
  def convention_on(line, column, last_on_line)
53
- range = source_range(@processed_source.buffer,
54
- @processed_source[0...(line - 1)], column,
55
- 1)
53
+ range = source_range(@processed_source.buffer, line, column)
56
54
  add_offense(last_on_line ? range : nil, range)
57
55
  end
58
56
 
@@ -11,20 +11,24 @@ module RuboCop
11
11
 
12
12
  def on_pair(node)
13
13
  oper = node.loc.operator
14
- return unless oper.is?(':') &&
15
- oper.source_buffer.source[oper.end_pos] =~ /\S/
14
+ return unless oper.is?(':') && followed_by_space?(oper)
16
15
 
17
16
  add_offense(oper, oper)
18
17
  end
19
18
 
20
19
  def on_if(node)
21
20
  return unless ternary_op?(node)
21
+
22
22
  colon = node.loc.colon
23
- return unless colon.source_buffer.source[colon.end_pos] =~ /\S/
23
+ return unless followed_by_space?(colon)
24
24
 
25
25
  add_offense(colon, colon)
26
26
  end
27
27
 
28
+ def followed_by_space?(colon)
29
+ colon.source_buffer.source[colon.end_pos] =~ /\S/
30
+ end
31
+
28
32
  def autocorrect(range)
29
33
  @corrections << lambda do |corrector|
30
34
  corrector.insert_after(range, ' ')
@@ -57,9 +57,11 @@ module RuboCop
57
57
  end
58
58
 
59
59
  def autocorrect(range)
60
+ m = range.source.match(/=\s*(\S+)/)
61
+ rest = m ? m.captures[0] : ''
60
62
  replacement = style == :space ? ' = ' : '='
61
63
  @corrections << lambda do |corrector|
62
- corrector.replace(range, replacement)
64
+ corrector.replace(range, replacement + rest)
63
65
  end
64
66
  end
65
67
  end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for comma (,) preceded by space.
7
+ class SpaceBeforeComma < Cop
8
+ include SpaceBeforePunctuation
9
+
10
+ def kind(token)
11
+ 'comma' if token.type == :tCOMMA
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for semicolon (;) preceded by space.
7
+ class SpaceBeforeSemicolon < Cop
8
+ include SpaceBeforePunctuation
9
+
10
+ def kind(token)
11
+ 'semicolon' if token.type == :tSEMI
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -13,11 +13,12 @@ module RuboCop
13
13
  next unless match
14
14
 
15
15
  spaces = match.captures[0]
16
- add_offense(nil,
17
- source_range(processed_source.buffer,
18
- processed_source[0...index],
19
- spaces.length, 1),
20
- MSG)
16
+
17
+ range = source_range(processed_source.buffer,
18
+ index + 1,
19
+ spaces.length)
20
+
21
+ add_offense(nil, range, MSG)
21
22
  end
22
23
  end
23
24
  end
@@ -4,11 +4,31 @@ module RuboCop
4
4
  module Cop
5
5
  module Style
6
6
  # This cop checks for trailing comma in parameter lists and literals.
7
+ #
8
+ # @example
9
+ # # always bad
10
+ # a = [1, 2,]
11
+ # b = [
12
+ # 1, 2,
13
+ # 3,
14
+ # ]
15
+ #
16
+ # # good if EnforcedStyleForMultiline is comma
17
+ # a = [
18
+ # 1,
19
+ # 2,
20
+ # ]
21
+ #
22
+ # # good if EnforcedStyleForMultiline is no_comma
23
+ # a = [
24
+ # 1,
25
+ # 2
26
+ # ]
7
27
  class TrailingComma < Cop
8
28
  include ArraySyntax
9
29
  include ConfigurableEnforcedStyle
10
30
 
11
- MSG = '%s comma after the last %s.'
31
+ MSG = '%s comma after the last %s'
12
32
 
13
33
  def on_array(node)
14
34
  check_literal(node, 'item of %s array') if square_brackets?(node)
@@ -56,7 +76,13 @@ module RuboCop
56
76
  should_have_comma = style == :comma && multiline?(node)
57
77
  if comma_offset
58
78
  unless should_have_comma
59
- avoid_comma(kind, after_last_item.begin_pos + comma_offset, sb)
79
+ extra_info = if style == :comma
80
+ ', unless each item is on its own line'
81
+ else
82
+ ''
83
+ end
84
+ avoid_comma(kind, after_last_item.begin_pos + comma_offset, sb,
85
+ extra_info)
60
86
  end
61
87
  elsif should_have_comma
62
88
  put_comma(items, kind, sb)
@@ -88,15 +114,16 @@ module RuboCop
88
114
  end
89
115
 
90
116
  def on_same_line?(a, b)
91
- [a, b].map(&:line).uniq.size == 1
117
+ a.line + a.source.count("\n") == b.line
92
118
  end
93
119
 
94
- def avoid_comma(kind, comma_begin_pos, sb)
120
+ def avoid_comma(kind, comma_begin_pos, sb, extra_info)
95
121
  range = Parser::Source::Range.new(sb, comma_begin_pos,
96
122
  comma_begin_pos + 1)
97
123
  article = kind =~ /array/ ? 'an' : 'a'
98
124
  add_offense(nil, range,
99
- format(MSG, 'Avoid', format(kind, article)))
125
+ format(MSG, 'Avoid', format(kind, article)) +
126
+ extra_info + '.')
100
127
  end
101
128
 
102
129
  def put_comma(items, kind, sb)
@@ -106,7 +133,7 @@ module RuboCop
106
133
  range = Parser::Source::Range.new(sb, last_expr.begin_pos + ix,
107
134
  last_expr.end_pos)
108
135
  add_offense(nil, range,
109
- format(MSG, 'Put a', format(kind, 'a multiline')))
136
+ format(MSG, 'Put a', format(kind, 'a multiline') + '.'))
110
137
  end
111
138
  end
112
139
  end
@@ -10,10 +10,11 @@ module RuboCop
10
10
  def investigate(processed_source)
11
11
  processed_source.lines.each_with_index do |line, index|
12
12
  next unless line =~ /.*[ \t]+$/
13
+
13
14
  range = source_range(processed_source.buffer,
14
- processed_source[0...index],
15
- line.rstrip.length,
16
- line.length - line.rstrip.length)
15
+ index + 1,
16
+ (line.rstrip.length)...(line.length))
17
+
17
18
  add_offense(range, range)
18
19
  end
19
20
  end
@@ -23,6 +23,12 @@ module RuboCop
23
23
 
24
24
  add_offense(node, :expression)
25
25
  end
26
+
27
+ def autocorrect(node)
28
+ @corrections << lambda do |corrector|
29
+ corrector.replace(node.loc.begin, '%w(')
30
+ end
31
+ end
26
32
  end
27
33
  end
28
34
  end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for usage of the %q/%Q syntax when '' or "" would do.
7
+ class UnneededPercentQ < Cop
8
+ MSG = 'Use `%s` only for strings that contain both single quotes and ' \
9
+ 'double quotes%s.'
10
+
11
+ def on_dstr(node)
12
+ # Using %Q to avoid escaping inner " is ok.
13
+ check(node) unless node.loc.expression.source =~ /"/
14
+ end
15
+
16
+ def on_str(node)
17
+ check(node)
18
+ end
19
+
20
+ private
21
+
22
+ def check(node)
23
+ src = node.loc.expression.source
24
+ return unless src =~ /^%q/i
25
+ return if src =~ /'/ && src =~ /"/
26
+
27
+ extra = if src =~ /^%Q/
28
+ ', or for dynamic strings that contain double quotes'
29
+ else
30
+ ''
31
+ end
32
+ add_offense(node, :expression, format(MSG, src[0, 2], extra))
33
+ end
34
+
35
+ def autocorrect(node)
36
+ delimiter = node.loc.expression.source =~ /^%Q[^"]+$|'/ ? '"' : "'"
37
+ @corrections << lambda do |corrector|
38
+ corrector.replace(node.loc.begin, delimiter)
39
+ corrector.replace(node.loc.end, delimiter)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -14,10 +14,9 @@ module RuboCop
14
14
  private
15
15
 
16
16
  def autocorrect(node)
17
- string, = *node
18
17
  @corrections << lambda do |corrector|
19
- corrector.replace(node.loc.expression,
20
- "`#{string.loc.expression.source}`")
18
+ corrector.replace(node.loc.begin, '`')
19
+ corrector.replace(node.loc.end, '`')
21
20
  end
22
21
  end
23
22
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
  next if source.start_with?('?') # %W(\r \n) can replace [?\r, ?\n]
46
46
 
47
47
  str_content = Util.strip_quotes(source)
48
- return true unless str_content =~ /\A[\w-]+\z/
48
+ return true unless str_content =~ /\A[\p{Word}]+\z/
49
49
  end
50
50
 
51
51
  false
@@ -25,27 +25,21 @@ module RuboCop
25
25
 
26
26
  def inspect_file(processed_source)
27
27
  # If we got any syntax errors, return only the syntax offenses.
28
- # Parser may return nil for AST even though there are no syntax errors.
29
- # e.g. sources which contain only comments
30
28
  unless processed_source.valid_syntax?
31
- diagnostics = processed_source.diagnostics
32
- return Lint::Syntax.offenses_from_diagnostics(diagnostics)
29
+ return Lint::Syntax.offenses_from_processed_source(processed_source)
33
30
  end
34
31
 
35
32
  commissioner = Commissioner.new(cops, forces)
36
33
  offenses = commissioner.investigate(processed_source)
37
- process_commissioner_errors(
38
- processed_source.file_path, commissioner.errors)
34
+ process_commissioner_errors(processed_source.path, commissioner.errors)
39
35
  autocorrect(processed_source.buffer, cops)
40
36
  offenses
41
37
  end
42
38
 
43
39
  def cops
44
- @cops ||= begin
45
- @cop_classes.each_with_object([]) do |cop_class, instances|
46
- next unless cop_enabled?(cop_class)
47
- instances << cop_class.new(@config, @options)
48
- end
40
+ @cops ||= @cop_classes.each_with_object([]) do |cop_class, instances|
41
+ next unless cop_enabled?(cop_class)
42
+ instances << cop_class.new(@config, @options)
49
43
  end
50
44
  end
51
45
 
@@ -80,7 +74,7 @@ module RuboCop
80
74
  # We raise RuntimeError ourselves if the rewritten code
81
75
  # is not parsable ruby. We don't want to write that code
82
76
  # to file.
83
- fail unless SourceParser.parse(s).valid_syntax?
77
+ fail unless ProcessedSource.new(s).valid_syntax?
84
78
  s
85
79
  rescue RangeError, RuntimeError
86
80
  # Handle all errors by limiting the changes to one