Skip to content

Commit 597b354

Browse files
authored
Merge pull request #25 from ericldelaney/task/master/PUP-8038/fix_decorate_string_cop_for_sentences
(PUP-8038) Update DecorateString to look for sentences
2 parents 614a49d + 0664305 commit 597b354

File tree

4 files changed

+139
-22
lines changed

4 files changed

+139
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## master (unreleased)
44

5+
* Updated DecorateString to look for sentences using a regular expression that should be decorated. This limits the number of strings that it finds to things that look like a sentence.
56
* Code restructure (no API changes)
67
* RuboCop lint fixes
78

README.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ require:
2626
- rubocop-i18n
2727
...
2828
GetText/DecorateString:
29-
Enabled: false
29+
Enabled: true
3030
GetText/DecorateFunctionMessage:
3131
Enabled: true
3232
GetText/DecorateStringFormattingUsingInterpolation
@@ -37,6 +37,36 @@ GetText/DecorateStringFormattingUsingPercent
3737

3838
## Cops
3939

40+
### GetText/DecorateString
41+
42+
This cop is looks for strings that appear to be sentences but are not decorated.
43+
Sentences are determined by the STRING_REGEXP.
44+
45+
##### Error message thrown
46+
47+
```
48+
decorator is missing around sentence
49+
```
50+
51+
##### Bad
52+
53+
``` ruby
54+
"Result is bad."
55+
```
56+
57+
##### Good
58+
59+
``` ruby
60+
_("Result is good.")
61+
```
62+
63+
##### Ignored
64+
``` ruby
65+
"string"
66+
"A string with out a punctuation at the end"
67+
"a string that doesn't start with a capital letter."
68+
```
69+
4070
### GetText/DecorateFunctionMessage
4171

4272
This cop looks for any raise or fail functions and checks that the user visible message is using gettext decoration with the _() function.
@@ -207,8 +237,9 @@ raise(_("Warning is %{value}") % { value: 'bad' })
207237

208238
It may be necessary to ignore a cop for a particular piece of code. We follow standard rubocop idioms.
209239
``` ruby
210-
raise("We don't want this translated") # rubocop:disable GetText/DecorateFunctionMessage
211-
raise(_("We don't want this translated #{crazy}") # rubocop:disable GetText/DecorateStringFormattingUsingInterpolation)
240+
raise("We don't want this translated.") # rubocop:disable GetText/DecorateString
241+
raise("We don't want this translated") # rubocop:disable GetText/DecorateFunctionMessage
242+
raise(_("We don't want this translated #{crazy}") # rubocop:disable GetText/DecorateStringFormattingUsingInterpolation)
212243
raise(_("We don't want this translated %s") % ['crazy'] # rubocop:disable GetText/DecorateStringFormattingUsingPercent)
213244
```
214245

lib/rubocop/cop/i18n/gettext/decorate_string.rb

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,69 @@ module RuboCop
44
module Cop
55
module I18n
66
module GetText
7-
# This cop checks for butt or ass,
8-
# which is redundant.
7+
# This cop is looks for strings that appear to be sentences but are not decorated.
8+
# Sentences are determined by the STRING_REGEXP. (Upper case character, at least one space,
9+
# and sentence punctuation at the end)
910
#
1011
# @example
1112
#
1213
# # bad
1314
#
14-
# "result is #{something.to_s}"
15+
# "Result is bad."
1516
#
1617
# @example
1718
#
1819
# # good
1920
#
20-
# "result is #{something}"
21+
# _("Result is good.")
2122
class DecorateString < Cop
23+
STRING_REGEXP = /^\s*[[:upper:]][[:alpha:]]*[[:blank:]]+.*[.!?]$/
24+
25+
def on_dstr(node)
26+
check_for_parent_decorator(node) if dstr_contains_sentence?(node)
27+
end
28+
2229
def on_str(node)
23-
str = node.children[0]
24-
# ignore strings with no whitespace - are typically keywords or interpolation statements and cover the above commented-out statements
25-
if str !~ /^\S*$/
26-
add_offense(node, :expression, 'decorator is missing around sentence') if node.loc.respond_to?(:begin)
30+
return unless sentence?(node)
31+
32+
parent = node.parent
33+
if parent.respond_to?(:type)
34+
return if parent.type == :regexp || parent.type == :dstr
2735
end
36+
37+
check_for_parent_decorator(node)
2838
end
2939

3040
private
3141

32-
def message(node)
33-
node.receiver ? MSG_DEFAULT : MSG_SELF
42+
def sentence?(node)
43+
child = node.children[0]
44+
if child.is_a?(String)
45+
if child.valid_encoding?
46+
child.encode(Encoding::UTF_8).chomp =~ STRING_REGEXP
47+
else
48+
false
49+
end
50+
elsif child.respond_to?(:type) && child.type == :str
51+
sentence?(child)
52+
else
53+
false
54+
end
55+
end
56+
57+
def dstr_contains_sentence?(node)
58+
node.children.any? { |child| sentence?(child) }
59+
end
60+
61+
def check_for_parent_decorator(node)
62+
parent = node.parent
63+
if parent.respond_to?(:type) && parent.type == :send
64+
method_name = parent.loc.selector.source
65+
return if GetText.supported_decorator?(method_name)
66+
elsif parent.respond_to?(:method_name) && parent.method_name == :[]
67+
return
68+
end
69+
add_offense(node, :expression, 'decorator is missing around sentence')
3470
end
3571
end
3672
end

spec/rubocop/cop/i18n/gettext/decorate_string_spec.rb

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,67 @@
55
describe RuboCop::Cop::I18n::GetText::DecorateString do
66
let(:config) { RuboCop::Config.new }
77
subject(:cop) { described_class.new(config) }
8+
before(:each) do
9+
investigate(cop, source)
10+
end
11+
12+
context 'decoration needed for string' do
13+
it_behaves_like 'a_detecting_cop', 'a = "A sentence that is not decorated."', '_', 'decorator is missing around sentence'
14+
it_behaves_like 'a_detecting_cop', 'thing("A sentence that is not decorated.")', '_', 'decorator is missing around sentence'
15+
end
16+
17+
context 'decoration not needed for string' do
18+
it_behaves_like 'a_no_cop_required', "'keyword'"
19+
# a regexp is a string
20+
it_behaves_like 'a_no_cop_required', '@regexp = /[ =]/'
21+
it_behaves_like 'a_no_cop_required', 'A sentence with out punctuation at the end'
22+
it_behaves_like 'a_no_cop_required', 'Dir[File.dirname(__FILE__) + "/parser/compiler/catalog_validator/*.rb"].each { |f| require f }'
23+
it_behaves_like 'a_no_cop_required', 'stream.puts "#@version\n" if @version'
24+
it_behaves_like 'a_no_cop_required', '
25+
f.puts(<<-YAML)
26+
---
27+
:tag: yaml
28+
:yaml:
29+
:something: #{variable}
30+
YAML'
31+
end
32+
33+
context 'decoration not needed for a hash key' do
34+
# a string as an hash key is ok
35+
it_behaves_like 'a_no_cop_required', 'memo["#{class_path}##{method}-#{source_line}"] += 1'
36+
end
37+
38+
context 'string with invalid UTF-8' do
39+
it_behaves_like 'a_no_cop_required', '
40+
STRING_MAP = {
41+
Encoding::UTF_8 => "\uFFFD",
42+
Encoding::UTF_16LE => "\xFD\xFF".force_encoding(Encoding::UTF_16LE),
43+
}'
44+
end
45+
46+
context 'decoration missing for dstr' do
47+
it_behaves_like 'a_detecting_cop', "b = \"A sentence line one.
48+
line two\"", '_', 'decorator is missing around sentence'
49+
it_behaves_like 'a_detecting_cop', "b = \"line one.
50+
A sentence line two.\"", '_', 'decorator is missing around sentence'
51+
end
852

9-
context 'undecorated string' do
10-
let(:source) do
11-
<<-RUBY
12-
"a string"
13-
RUBY
53+
RuboCop::Cop::I18n::GetText.supported_decorators.each do |decorator|
54+
context "#{decorator} already present" do
55+
it_behaves_like 'a_no_cop_required', "#{decorator}('a string')"
56+
it_behaves_like 'a_no_cop_required', "#{decorator} \"a string\""
57+
it_behaves_like 'a_no_cop_required', "a = #{decorator}('a string')"
58+
it_behaves_like 'a_no_cop_required', "#{decorator}(\"a %-5.2.s thing s string\")"
59+
it_behaves_like 'a_no_cop_required', "Log.warning #{decorator}(\"could not change to group %{group}: %{detail}\") % { group: group, detail: detail }"
60+
it_behaves_like 'a_no_cop_required', "Log.warning #{decorator}(\"could not change to group %{group}: %{detail}\") %
61+
{ group: #{decorator}(\"group\"), detail: #{decorator}(\"detail\") }"
1462
end
1563

16-
it 'rejects' do
17-
investigate(cop, source)
18-
expect(cop.offenses.size).to eq(1)
19-
expect(cop.offenses[0].message).to match(/decorator is missing around sentence/)
64+
context "#{decorator} around dstr" do
65+
it_behaves_like 'a_no_cop_required', "a = #{decorator}(\"A sentence line one.
66+
line two\")"
67+
it_behaves_like 'a_no_cop_required', "a = #{decorator}(\"line one.
68+
A sentence line two.\")"
2069
end
2170
end
2271
end

0 commit comments

Comments
 (0)