rubocop-exception_messages 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -2
- data/README.md +97 -6
- data/RELEASING.md +0 -6
- data/config/default.yml +33 -2
- data/lib/rubocop/cop/exception_messages/casing.rb +33 -12
- data/lib/rubocop/cop/exception_messages/punctuation.rb +45 -11
- data/lib/rubocop/cop/exception_messages/quote_style.rb +99 -0
- data/lib/rubocop/cop/exception_messages/redundant_exception_name.rb +65 -0
- data/lib/rubocop/cop/exception_messages_cops.rb +2 -0
- data/lib/rubocop/exception_messages/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30c6a33ca2d34e81294dc56ebfbc93487038a551befad0f019569e9622606635
|
|
4
|
+
data.tar.gz: 2302c462589b5aa9981c6c75bdb0cd25b3a76ed4e4c1e97e34a9bc6db9b051e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 656845e5a38eeff400ab2ad31c1ae62537ec57d577978a648f50c192f14a29ef58564bf8630701e83cda3f44738d502c4dd99f916b942e6c55e467fa19ae8fec
|
|
7
|
+
data.tar.gz: d1dcbe5ed3a6c9ac92548f3ed2892ace8797cb5354b1477635a009ef93f04de6b1b9161710b64750993495576a6a4dbba01a950f76a18272b3841b8d32c1768f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
### 0.
|
|
1
|
+
### 0.2.0 (2026/09/05)
|
|
2
|
+
|
|
3
|
+
* [#6](https://github.com/dblock/rubocop-exception_messages/pull/6): Add `EnforcedStyle` support to `ExceptionMessages/Casing` (`lowercase`/`uppercase`) and `ExceptionMessages/Punctuation` (`no_period`/`period`) to allow the opposite convention - [@dblock](https://github.com/dblock).
|
|
4
|
+
* [#7](https://github.com/dblock/rubocop-exception_messages/pull/7): Add `ExceptionMessages/RedundantExceptionName` cop that flags exception messages redundantly repeating the exception class name - [@dblock](https://github.com/dblock).
|
|
5
|
+
* [#8](https://github.com/dblock/rubocop-exception_messages/pull/8): Add `ExceptionMessages/QuoteStyle` cop that checks consistent quoting of interpolated values in exception messages - [@dblock](https://github.com/dblock).
|
|
6
|
+
* [#16](https://github.com/dblock/rubocop-exception_messages/pull/16): Fix `ExceptionMessages/Punctuation` not detecting a trailing period in heredoc messages, and an infinite autocorrection loop when it did; fix `ExceptionMessages/QuoteStyle` raising an error when autocorrecting a heredoc message - [@dblock](https://github.com/dblock).
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
### 0.1.0 (2026/09/05)
|
|
4
9
|
|
|
5
10
|
* Initial release with `ExceptionMessages/Casing` and `ExceptionMessages/Punctuation` cops - [@dblock](https://github.com/dblock).
|
|
6
11
|
* Add Danger CI check that enforces a CHANGELOG entry on every pull request - [@dblock](https://github.com/dblock).
|
data/README.md
CHANGED
|
@@ -30,34 +30,125 @@ plugins:
|
|
|
30
30
|
|
|
31
31
|
### ExceptionMessages/Casing
|
|
32
32
|
|
|
33
|
-
Checks
|
|
33
|
+
Checks the capitalization of raised exception messages. Defaults to `EnforcedStyle: lowercase`.
|
|
34
34
|
|
|
35
35
|
```ruby
|
|
36
36
|
# bad
|
|
37
37
|
raise ArgumentError, "Block is required"
|
|
38
|
-
raise ArgumentError.new("Block is required")
|
|
39
38
|
|
|
40
39
|
# good
|
|
41
40
|
raise ArgumentError, "block is required"
|
|
42
|
-
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Configure `EnforcedStyle: uppercase` to require the opposite convention instead.
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
ExceptionMessages/Casing:
|
|
47
|
+
EnforcedStyle: uppercase
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# bad
|
|
52
|
+
raise ArgumentError, "block is required"
|
|
53
|
+
|
|
54
|
+
# good
|
|
55
|
+
raise ArgumentError, "Block is required"
|
|
43
56
|
```
|
|
44
57
|
|
|
45
58
|
### ExceptionMessages/Punctuation
|
|
46
59
|
|
|
47
|
-
Checks
|
|
60
|
+
Checks the trailing punctuation of raised exception messages. Defaults to `EnforcedStyle: no_period`. A literal ellipsis (`"..."`) is never considered an offense.
|
|
48
61
|
|
|
49
62
|
```ruby
|
|
50
63
|
# bad
|
|
51
64
|
raise ArgumentError, "block is required."
|
|
52
|
-
raise ArgumentError.new("block is required.")
|
|
53
65
|
|
|
54
66
|
# good
|
|
55
67
|
raise ArgumentError, "block is required"
|
|
56
|
-
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Configure `EnforcedStyle: period` to require a trailing period instead.
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
ExceptionMessages/Punctuation:
|
|
74
|
+
EnforcedStyle: period
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# bad
|
|
79
|
+
raise ArgumentError, "block is required"
|
|
80
|
+
|
|
81
|
+
# good
|
|
82
|
+
raise ArgumentError, "block is required."
|
|
57
83
|
```
|
|
58
84
|
|
|
59
85
|
Both cops support autocorrection (`rubocop -A`).
|
|
60
86
|
|
|
87
|
+
### ExceptionMessages/RedundantExceptionName
|
|
88
|
+
|
|
89
|
+
Checks that raised exception messages do not redundantly repeat the exception class name, since Ruby already prints the class name ahead of the message in a backtrace.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# bad
|
|
93
|
+
raise ArgumentError, "ArgumentError: block is required"
|
|
94
|
+
|
|
95
|
+
# good
|
|
96
|
+
raise ArgumentError, "block is required"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### ExceptionMessages/QuoteStyle
|
|
100
|
+
|
|
101
|
+
Checks that interpolated values in raised exception messages are consistently quoted, making it easier to spot where a dynamic value begins and ends in a rendered message. Defaults to `EnforcedStyle: backticks`.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# bad
|
|
105
|
+
raise ArgumentError, "unknown type: #{type}"
|
|
106
|
+
|
|
107
|
+
# good
|
|
108
|
+
raise ArgumentError, "unknown type: `#{type}`"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Configure `EnforcedStyle: single_quotes`, `double_quotes`, `square_brackets`, `parentheses`, or `curly_braces` to require a different wrapping instead.
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
ExceptionMessages/QuoteStyle:
|
|
115
|
+
Enabled: true
|
|
116
|
+
EnforcedStyle: single_quotes
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# good
|
|
121
|
+
raise ArgumentError, "unknown type: '#{type}'"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Configure `EnforcedStyle: custom` with `Prefix`/`Suffix` for anything else, including a single-sided marker with no closing character.
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
ExceptionMessages/QuoteStyle:
|
|
128
|
+
Enabled: true
|
|
129
|
+
EnforcedStyle: custom
|
|
130
|
+
Prefix: '?'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# good
|
|
135
|
+
raise ArgumentError, "unknown type: ?#{type}"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`Prefix`/`Suffix` aren't limited to a single character.
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
ExceptionMessages/QuoteStyle:
|
|
142
|
+
Enabled: true
|
|
143
|
+
EnforcedStyle: custom
|
|
144
|
+
Prefix: '--'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# good
|
|
149
|
+
raise ArgumentError, "unknown type: --#{type}"
|
|
150
|
+
```
|
|
151
|
+
|
|
61
152
|
## Contributing
|
|
62
153
|
|
|
63
154
|
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
data/RELEASING.md
CHANGED
data/config/default.yml
CHANGED
|
@@ -2,11 +2,42 @@ ExceptionMessages:
|
|
|
2
2
|
Enabled: true
|
|
3
3
|
|
|
4
4
|
ExceptionMessages/Casing:
|
|
5
|
-
Description: 'Checks
|
|
5
|
+
Description: 'Checks the capitalization of raised exception messages.'
|
|
6
6
|
Enabled: true
|
|
7
|
+
EnforcedStyle: lowercase
|
|
8
|
+
SupportedStyles:
|
|
9
|
+
- lowercase
|
|
10
|
+
- uppercase
|
|
7
11
|
VersionAdded: '0.1'
|
|
12
|
+
VersionChanged: '0.2'
|
|
8
13
|
|
|
9
14
|
ExceptionMessages/Punctuation:
|
|
10
|
-
Description: 'Checks
|
|
15
|
+
Description: 'Checks the trailing punctuation of raised exception messages.'
|
|
11
16
|
Enabled: true
|
|
17
|
+
EnforcedStyle: no_period
|
|
18
|
+
SupportedStyles:
|
|
19
|
+
- no_period
|
|
20
|
+
- period
|
|
12
21
|
VersionAdded: '0.1'
|
|
22
|
+
VersionChanged: '0.2'
|
|
23
|
+
|
|
24
|
+
ExceptionMessages/RedundantExceptionName:
|
|
25
|
+
Description: 'Checks that raised exception messages do not repeat the exception class name.'
|
|
26
|
+
Enabled: true
|
|
27
|
+
VersionAdded: '0.2'
|
|
28
|
+
|
|
29
|
+
ExceptionMessages/QuoteStyle:
|
|
30
|
+
Description: 'Checks that interpolated values in raised exception messages are consistently quoted.'
|
|
31
|
+
Enabled: true
|
|
32
|
+
EnforcedStyle: backticks
|
|
33
|
+
SupportedStyles:
|
|
34
|
+
- backticks
|
|
35
|
+
- single_quotes
|
|
36
|
+
- double_quotes
|
|
37
|
+
- square_brackets
|
|
38
|
+
- parentheses
|
|
39
|
+
- curly_braces
|
|
40
|
+
- custom
|
|
41
|
+
Prefix: ~
|
|
42
|
+
Suffix: ~
|
|
43
|
+
VersionAdded: '0.2'
|
|
@@ -3,23 +3,31 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module ExceptionMessages
|
|
6
|
-
# Checks
|
|
7
|
-
# consistent with Ruby's own core
|
|
8
|
-
# (e.g. `TypeError: no implicit
|
|
6
|
+
# Checks the capitalization of raised exception messages. By default,
|
|
7
|
+
# requires a lowercase first letter, consistent with Ruby's own core
|
|
8
|
+
# and standard library exceptions (e.g. `TypeError: no implicit
|
|
9
|
+
# conversion from nil to integer`). Configure `EnforcedStyle: uppercase`
|
|
10
|
+
# to require the opposite convention instead.
|
|
9
11
|
#
|
|
10
|
-
# @example
|
|
12
|
+
# @example EnforcedStyle: lowercase (default)
|
|
11
13
|
# # bad
|
|
12
14
|
# raise ArgumentError, "Block is required"
|
|
13
|
-
# raise ArgumentError.new("Block is required")
|
|
14
15
|
#
|
|
15
16
|
# # good
|
|
16
17
|
# raise ArgumentError, "block is required"
|
|
17
|
-
#
|
|
18
|
+
#
|
|
19
|
+
# @example EnforcedStyle: uppercase
|
|
20
|
+
# # bad
|
|
21
|
+
# raise ArgumentError, "block is required"
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# raise ArgumentError, "Block is required"
|
|
18
25
|
class Casing < Base
|
|
26
|
+
include ConfigurableEnforcedStyle
|
|
19
27
|
include MessageNode
|
|
20
28
|
extend AutoCorrector
|
|
21
29
|
|
|
22
|
-
MSG = 'Exception messages should start with a
|
|
30
|
+
MSG = 'Exception messages should start with a %<style>s letter.'
|
|
23
31
|
|
|
24
32
|
def on_send(node)
|
|
25
33
|
message_node = raise_message_node(node)
|
|
@@ -29,16 +37,29 @@ module RuboCop
|
|
|
29
37
|
return unless first_segment
|
|
30
38
|
|
|
31
39
|
content = first_segment.value
|
|
32
|
-
return if content.empty? || content
|
|
40
|
+
return if content.empty? || !offense?(content)
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
corrected = content.sub(/\A./, &:downcase)
|
|
36
|
-
corrector.replace(first_segment.loc.expression, first_segment.source.sub(content, corrected))
|
|
37
|
-
end
|
|
42
|
+
register_offense(message_node, first_segment, content)
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
private
|
|
41
46
|
|
|
47
|
+
def register_offense(message_node, segment, content)
|
|
48
|
+
add_offense(message_node, message: format(MSG, style: style)) do |corrector|
|
|
49
|
+
corrected = content.sub(/\A./, &correction_method)
|
|
50
|
+
corrector.replace(segment.loc.expression, segment.source.sub(content, corrected))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def offense?(content)
|
|
55
|
+
first_char = content[0]
|
|
56
|
+
first_char =~ (style == :lowercase ? /[A-Z]/ : /[a-z]/)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def correction_method
|
|
60
|
+
style == :lowercase ? :downcase : :upcase
|
|
61
|
+
end
|
|
62
|
+
|
|
42
63
|
def first_string_segment(message_node)
|
|
43
64
|
segment = message_node.str_type? ? message_node : message_node.child_nodes.first
|
|
44
65
|
segment if segment&.str_type?
|
|
@@ -3,23 +3,34 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module ExceptionMessages
|
|
6
|
-
# Checks
|
|
7
|
-
# consistent with Ruby's own
|
|
8
|
-
# (e.g. `ArgumentError: wrong
|
|
6
|
+
# Checks the trailing punctuation of raised exception messages. By
|
|
7
|
+
# default, disallows a trailing period, consistent with Ruby's own
|
|
8
|
+
# core and standard library exceptions (e.g. `ArgumentError: wrong
|
|
9
|
+
# number of arguments`). Configure `EnforcedStyle: period` to require
|
|
10
|
+
# the opposite convention instead. A literal ellipsis (`"..."`) is
|
|
11
|
+
# never considered an offense, since it's stylistic rather than a
|
|
12
|
+
# sentence-ending period.
|
|
9
13
|
#
|
|
10
|
-
# @example
|
|
14
|
+
# @example EnforcedStyle: no_period (default)
|
|
11
15
|
# # bad
|
|
12
16
|
# raise ArgumentError, "block is required."
|
|
13
|
-
# raise ArgumentError.new("block is required.")
|
|
14
17
|
#
|
|
15
18
|
# # good
|
|
16
19
|
# raise ArgumentError, "block is required"
|
|
17
|
-
#
|
|
20
|
+
#
|
|
21
|
+
# @example EnforcedStyle: period
|
|
22
|
+
# # bad
|
|
23
|
+
# raise ArgumentError, "block is required"
|
|
24
|
+
#
|
|
25
|
+
# # good
|
|
26
|
+
# raise ArgumentError, "block is required."
|
|
18
27
|
class Punctuation < Base
|
|
28
|
+
include ConfigurableEnforcedStyle
|
|
19
29
|
include MessageNode
|
|
20
30
|
extend AutoCorrector
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
MSG_NO_PERIOD = 'Exception messages should not end with a period.'
|
|
33
|
+
MSG_PERIOD = 'Exception messages should end with a period.'
|
|
23
34
|
|
|
24
35
|
def on_send(node)
|
|
25
36
|
message_node = raise_message_node(node)
|
|
@@ -34,16 +45,39 @@ module RuboCop
|
|
|
34
45
|
private
|
|
35
46
|
|
|
36
47
|
def check_segment(message_node, segment)
|
|
37
|
-
content = segment.value
|
|
38
|
-
return unless content.end_with?('.')
|
|
48
|
+
content = segment.value.chomp("\n")
|
|
39
49
|
return if content.end_with?('..') # ellipsis-style, not a sentence-ending period
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
if style == :no_period
|
|
52
|
+
check_no_period(message_node, segment, content)
|
|
53
|
+
else
|
|
54
|
+
check_period(message_node, segment, content)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def check_no_period(message_node, segment, content)
|
|
59
|
+
return unless content.end_with?('.')
|
|
60
|
+
|
|
61
|
+
add_offense(message_node, message: MSG_NO_PERIOD) do |corrector|
|
|
42
62
|
corrected = content.sub(/\.\z/, '')
|
|
43
|
-
corrector
|
|
63
|
+
replace_content(corrector, segment, content, corrected)
|
|
44
64
|
end
|
|
45
65
|
end
|
|
46
66
|
|
|
67
|
+
def check_period(message_node, segment, content)
|
|
68
|
+
return if content.empty? || content.end_with?('.')
|
|
69
|
+
|
|
70
|
+
add_offense(message_node, message: MSG_PERIOD) do |corrector|
|
|
71
|
+
corrected = "#{content}."
|
|
72
|
+
replace_content(corrector, segment, content, corrected)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def replace_content(corrector, segment, content, corrected)
|
|
77
|
+
range = segment.loc.respond_to?(:heredoc_body) ? segment.loc.heredoc_body : segment.loc.expression
|
|
78
|
+
corrector.replace(range, range.source.sub(content, corrected))
|
|
79
|
+
end
|
|
80
|
+
|
|
47
81
|
def last_string_segment(message_node)
|
|
48
82
|
segment = message_node.str_type? ? message_node : message_node.child_nodes.last
|
|
49
83
|
segment if segment&.str_type?
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ExceptionMessages
|
|
6
|
+
# Checks that interpolated values in raised exception messages are
|
|
7
|
+
# consistently quoted, making it easier to spot where a dynamic value
|
|
8
|
+
# begins and ends in a rendered message.
|
|
9
|
+
#
|
|
10
|
+
# @example EnforcedStyle: backticks (default)
|
|
11
|
+
# # bad
|
|
12
|
+
# raise ArgumentError, "unknown type: #{type}"
|
|
13
|
+
#
|
|
14
|
+
# # good
|
|
15
|
+
# raise ArgumentError, "unknown type: `#{type}`"
|
|
16
|
+
#
|
|
17
|
+
# @example EnforcedStyle: custom (with Prefix: '?', Suffix: '')
|
|
18
|
+
# # bad
|
|
19
|
+
# raise ArgumentError, "unknown type: #{type}"
|
|
20
|
+
#
|
|
21
|
+
# # good
|
|
22
|
+
# raise ArgumentError, "unknown type: ?#{type}"
|
|
23
|
+
class QuoteStyle < Base
|
|
24
|
+
include ConfigurableEnforcedStyle
|
|
25
|
+
include MessageNode
|
|
26
|
+
extend AutoCorrector
|
|
27
|
+
|
|
28
|
+
MSG = 'Interpolated values in exception messages should be wrapped in %<style>s.'
|
|
29
|
+
|
|
30
|
+
QUOTES = {
|
|
31
|
+
backticks: ['`', '`'],
|
|
32
|
+
single_quotes: ["'", "'"],
|
|
33
|
+
double_quotes: ['"', '"'],
|
|
34
|
+
square_brackets: ['[', ']'],
|
|
35
|
+
parentheses: ['(', ')'],
|
|
36
|
+
curly_braces: ['{', '}']
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
def on_send(node)
|
|
40
|
+
message_node = raise_message_node(node)
|
|
41
|
+
return unless message_node&.dstr_type?
|
|
42
|
+
|
|
43
|
+
message_node.child_nodes.each do |segment|
|
|
44
|
+
next unless segment.begin_type?
|
|
45
|
+
|
|
46
|
+
check_interpolation(message_node, segment)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def check_interpolation(message_node, segment)
|
|
53
|
+
open_quote, close_quote = quotes_for_style
|
|
54
|
+
return if quoted?(segment, open_quote, close_quote)
|
|
55
|
+
|
|
56
|
+
add_offense(segment, message: format(MSG, style: style_description)) do |corrector|
|
|
57
|
+
open_quote, close_quote = escape_for_literal(message_node, open_quote, close_quote)
|
|
58
|
+
corrector.insert_before(segment.loc.expression, open_quote)
|
|
59
|
+
corrector.insert_after(segment.loc.expression, close_quote) unless close_quote.empty?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def quotes_for_style
|
|
64
|
+
return QUOTES.fetch(style) unless style == :custom
|
|
65
|
+
|
|
66
|
+
[cop_config['Prefix'] || '', cop_config['Suffix'] || '']
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def style_description
|
|
70
|
+
return "`#{cop_config['Prefix']}` / `#{cop_config['Suffix']}`" if style == :custom
|
|
71
|
+
|
|
72
|
+
style.to_s.tr('_', ' ')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def escape_for_literal(message_node, open_quote, close_quote)
|
|
76
|
+
return [open_quote, close_quote] unless message_node.loc.respond_to?(:begin) && message_node.loc.begin
|
|
77
|
+
|
|
78
|
+
outer_quote = message_node.loc.begin.source
|
|
79
|
+
return [open_quote, close_quote] unless outer_quote == open_quote || outer_quote == close_quote
|
|
80
|
+
|
|
81
|
+
[open_quote == outer_quote ? "\\#{open_quote}" : open_quote,
|
|
82
|
+
close_quote == outer_quote ? "\\#{close_quote}" : close_quote]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def quoted?(segment, open_quote, close_quote)
|
|
86
|
+
preceding = segment.left_sibling
|
|
87
|
+
following = segment.right_sibling
|
|
88
|
+
|
|
89
|
+
preceding_ok = open_quote.empty? ||
|
|
90
|
+
(preceding.is_a?(RuboCop::AST::StrNode) && preceding.value.end_with?(open_quote))
|
|
91
|
+
following_ok = close_quote.empty? ||
|
|
92
|
+
(following.is_a?(RuboCop::AST::StrNode) && following.value.start_with?(close_quote))
|
|
93
|
+
|
|
94
|
+
preceding_ok && following_ok
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ExceptionMessages
|
|
6
|
+
# Checks that raised exception messages do not redundantly repeat the
|
|
7
|
+
# exception class name, since Ruby already prints the class name
|
|
8
|
+
# ahead of the message in a backtrace (`ArgumentError: block is
|
|
9
|
+
# required`, not `ArgumentError: ArgumentError: block is required`).
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# # bad
|
|
13
|
+
# raise ArgumentError, "ArgumentError: block is required"
|
|
14
|
+
#
|
|
15
|
+
# # good
|
|
16
|
+
# raise ArgumentError, "block is required"
|
|
17
|
+
class RedundantExceptionName < Base
|
|
18
|
+
include MessageNode
|
|
19
|
+
|
|
20
|
+
MSG = 'Exception messages should not repeat the exception class name.'
|
|
21
|
+
|
|
22
|
+
def on_send(node)
|
|
23
|
+
message_node = raise_message_node(node)
|
|
24
|
+
return unless message_node
|
|
25
|
+
|
|
26
|
+
first_segment = first_string_segment(message_node)
|
|
27
|
+
return unless first_segment
|
|
28
|
+
|
|
29
|
+
exception_name = exception_class_name(node)
|
|
30
|
+
return unless exception_name
|
|
31
|
+
|
|
32
|
+
return unless redundant?(first_segment.value, exception_name)
|
|
33
|
+
|
|
34
|
+
add_offense(message_node)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def redundant?(content, exception_name)
|
|
40
|
+
content.match?(/\A#{Regexp.escape(exception_name)}\b/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def exception_class_name(node)
|
|
44
|
+
klass = raised_class_node(node)
|
|
45
|
+
return unless klass&.const_type?
|
|
46
|
+
|
|
47
|
+
klass.const_name&.split('::')&.last
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def raised_class_node(node)
|
|
51
|
+
first_arg = node.arguments.first
|
|
52
|
+
return unless first_arg
|
|
53
|
+
return first_arg.receiver if first_arg.send_type? && first_arg.method?(:new)
|
|
54
|
+
|
|
55
|
+
first_arg
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def first_string_segment(message_node)
|
|
59
|
+
segment = message_node.str_type? ? message_node : message_node.child_nodes.first
|
|
60
|
+
segment if segment&.str_type?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-exception_messages
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel (dB.) Doubrovkine
|
|
@@ -59,6 +59,8 @@ files:
|
|
|
59
59
|
- lib/rubocop/cop/exception_messages/casing.rb
|
|
60
60
|
- lib/rubocop/cop/exception_messages/message_node.rb
|
|
61
61
|
- lib/rubocop/cop/exception_messages/punctuation.rb
|
|
62
|
+
- lib/rubocop/cop/exception_messages/quote_style.rb
|
|
63
|
+
- lib/rubocop/cop/exception_messages/redundant_exception_name.rb
|
|
62
64
|
- lib/rubocop/cop/exception_messages_cops.rb
|
|
63
65
|
- lib/rubocop/exception_messages.rb
|
|
64
66
|
- lib/rubocop/exception_messages/plugin.rb
|