rubocop-neeto 0.1.20 → 0.1.21
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/README.md +1 -1
- data/config/default.yml +9 -6
- data/lib/rubocop/cop/neeto/html_in_string_literal.rb +5 -1
- data/lib/rubocop/cop/neeto/html_safe_on_dynamic_string.rb +5 -1
- data/lib/rubocop/cop/neeto/html_translation_interpolation.rb +45 -8
- data/lib/rubocop/neeto/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f79480fcc020ba036f101838a8970627d0d6cee99fdf7db819c0eaf55430e8f
|
|
4
|
+
data.tar.gz: 510ea55903e4e085be783f0bfc618e5e28f725ac399c83d992ea03473b030dff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e93ef6aac8079e2a95f8994e63d34f826a3f5f56041d7285695323bcb3a1910c16027c6a1278c2a81c15d52454366f7a65e89619a14ef7c2592afcdbb499344
|
|
7
|
+
data.tar.gz: 266c32b005d5d7bfb3933f936dc27895e0d388db5e74dfbc4bcf52cfe3683ef58158c8a1f6240d5a7dbf680bd2631eea6fdac5ea949690de839c68a342295ef0
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
8. [Neeto/DirectLiquidRender](https://rubocop-neeto.neetodeployapp.com/docs/RuboCop/Cop/Neeto/DirectLiquidRender)
|
|
13
13
|
9. [Neeto/HtmlTranslationInterpolation](https://rubocop-neeto.neetodeployapp.com/docs/RuboCop/Cop/Neeto/HtmlTranslationInterpolation)
|
|
14
14
|
|
|
15
|
-
The HTML-safety cops (6 to 9) report at `Severity:
|
|
15
|
+
The HTML-safety cops (6 to 9) report at `Severity: refactor`, like the other Neeto cops, so their offenses fail RuboCop and erb_lint at the default fail level.
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
data/config/default.yml
CHANGED
|
@@ -74,7 +74,7 @@ Neeto/HtmlSafeOnDynamicString:
|
|
|
74
74
|
or `safe_join` instead, or annotate a reviewed call with
|
|
75
75
|
`# neeto:html-safe-reviewed <reason>`.
|
|
76
76
|
Enabled: true
|
|
77
|
-
Severity:
|
|
77
|
+
Severity: refactor
|
|
78
78
|
VersionAdded: '0.1'
|
|
79
79
|
Include:
|
|
80
80
|
- app/**/*.rb
|
|
@@ -88,7 +88,7 @@ Neeto/HtmlInStringLiteral:
|
|
|
88
88
|
markup outside views with `tag`, `content_tag`, `link_to`, `mail_to`,
|
|
89
89
|
`safe_join` or a partial instead of string literals and heredocs.
|
|
90
90
|
Enabled: true
|
|
91
|
-
Severity:
|
|
91
|
+
Severity: refactor
|
|
92
92
|
VersionAdded: '0.1'
|
|
93
93
|
Include:
|
|
94
94
|
- app/**/*.rb
|
|
@@ -104,11 +104,12 @@ Neeto/DirectLiquidRender:
|
|
|
104
104
|
escapes every output and requires an explicit format (`:html` or `:text`).
|
|
105
105
|
Check a template's syntax with `NeetoCommonsBackend::LiquidRenderer.validate!`.
|
|
106
106
|
Enabled: true
|
|
107
|
-
Severity:
|
|
107
|
+
Severity: refactor
|
|
108
108
|
VersionAdded: '0.1'
|
|
109
109
|
Include:
|
|
110
110
|
- app/**/*.rb
|
|
111
111
|
- lib/**/*.rb
|
|
112
|
+
- app/**/*.jbuilder
|
|
112
113
|
|
|
113
114
|
Neeto/HtmlTranslationInterpolation:
|
|
114
115
|
Description: >-
|
|
@@ -117,13 +118,15 @@ Neeto/HtmlTranslationInterpolation:
|
|
|
117
118
|
Escape each value with `ERB::Util.html_escape`, or keep the markup out of
|
|
118
119
|
the translation and build it with `tag` or `content_tag`.
|
|
119
120
|
Enabled: true
|
|
120
|
-
Severity:
|
|
121
|
+
Severity: refactor
|
|
121
122
|
VersionAdded: '0.1'
|
|
122
123
|
LocaleFiles:
|
|
123
|
-
- config/locales
|
|
124
|
+
- config/locales/**/en.yml
|
|
125
|
+
- config/locales/**/*.en.yml
|
|
124
126
|
Include:
|
|
125
127
|
- app/**/*.rb
|
|
126
128
|
- lib/**/*.rb
|
|
129
|
+
- app/views/**/*.erb
|
|
130
|
+
- app/views/**/*.inky
|
|
127
131
|
Exclude:
|
|
128
|
-
- app/views/**/*
|
|
129
132
|
- lib/tasks/**/*
|
|
@@ -49,7 +49,7 @@ module RuboCop
|
|
|
49
49
|
def on_dstr(node)
|
|
50
50
|
return if part_of_another_literal?(node)
|
|
51
51
|
|
|
52
|
-
check(node, node
|
|
52
|
+
check(node, literal_text(node))
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
private
|
|
@@ -61,6 +61,10 @@ module RuboCop
|
|
|
61
61
|
add_offense(node)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def literal_text(node)
|
|
65
|
+
node.each_child_node(:str, :dstr).map { |child| child.str_type? ? child.value : literal_text(child) }.join
|
|
66
|
+
end
|
|
67
|
+
|
|
64
68
|
def part_of_another_literal?(node)
|
|
65
69
|
ENCLOSING_LITERAL_TYPES.include?(node.parent&.type)
|
|
66
70
|
end
|
|
@@ -37,6 +37,10 @@ module RuboCop
|
|
|
37
37
|
|
|
38
38
|
RESTRICT_ON_SEND = %i[html_safe raw safe_concat].freeze
|
|
39
39
|
|
|
40
|
+
def_node_matcher :view_helpers?, <<~PATTERN
|
|
41
|
+
(send _ {:helpers :view_context})
|
|
42
|
+
PATTERN
|
|
43
|
+
|
|
40
44
|
def on_send(node)
|
|
41
45
|
value = trusted_value(node)
|
|
42
46
|
return if value.nil? || static_string?(value) || reviewed?(node)
|
|
@@ -52,7 +56,7 @@ module RuboCop
|
|
|
52
56
|
when :html_safe
|
|
53
57
|
node.receiver if node.arguments.none?
|
|
54
58
|
when :raw
|
|
55
|
-
node.first_argument if node.receiver.nil? && node.arguments.one?
|
|
59
|
+
node.first_argument if (node.receiver.nil? || view_helpers?(node.receiver)) && node.arguments.one?
|
|
56
60
|
when :safe_concat
|
|
57
61
|
node.first_argument if node.arguments.one?
|
|
58
62
|
end
|
|
@@ -7,8 +7,9 @@ module RuboCop
|
|
|
7
7
|
module Neeto
|
|
8
8
|
# `I18n.t` does not escape interpolated values, even for `_html` keys
|
|
9
9
|
# (only the view helper `t` does). A translation that contains HTML and
|
|
10
|
-
# interpolates an unescaped value
|
|
11
|
-
#
|
|
10
|
+
# interpolates an unescaped value lets that value inject markup. In views,
|
|
11
|
+
# the `t` helper escapes the values of `_html` keys, so only those are
|
|
12
|
+
# allowed there. The cop reads the translations from `LocaleFiles`.
|
|
12
13
|
#
|
|
13
14
|
# @example HtmlTranslationInterpolation: true (default)
|
|
14
15
|
# # en.yml
|
|
@@ -24,6 +25,12 @@ module RuboCop
|
|
|
24
25
|
# # good
|
|
25
26
|
# tag.p(I18n.t("seed.description_text", name: user.name))
|
|
26
27
|
#
|
|
28
|
+
# # bad (in a view)
|
|
29
|
+
# <%= sanitize t("seed.description", name: @name) %>
|
|
30
|
+
#
|
|
31
|
+
# # good (in a view)
|
|
32
|
+
# <%= t("seed.description_html", name: @name) %>
|
|
33
|
+
#
|
|
27
34
|
class HtmlTranslationInterpolation < Base
|
|
28
35
|
MSG = "Translation `%<key>s` contains HTML and interpolates %<variables>s, which `I18n.t` does not " \
|
|
29
36
|
"escape. Escape each value with `ERB::Util.html_escape`, or keep the markup out of the translation " \
|
|
@@ -31,13 +38,19 @@ module RuboCop
|
|
|
31
38
|
|
|
32
39
|
RESTRICT_ON_SEND = %i[t translate].freeze
|
|
33
40
|
OPTIONS = %i[locale scope default raise throw count].freeze
|
|
41
|
+
VIEW_FILE = %r{(?:\A|/)app/views/}
|
|
34
42
|
|
|
35
43
|
def_node_matcher :translation_call, <<~PATTERN
|
|
36
|
-
(send {nil? (const {nil? cbase} :I18n)} {:t :translate}
|
|
44
|
+
(send {nil? (const {nil? cbase} :I18n)} {:t :translate} ${str sym array} $...)
|
|
37
45
|
PATTERN
|
|
38
46
|
|
|
39
47
|
def_node_matcher :escaped_value?, <<~PATTERN
|
|
40
|
-
|
|
48
|
+
{
|
|
49
|
+
(send {nil? (const (const {nil? cbase} :ERB) :Util)} {:html_escape :h} _)
|
|
50
|
+
(send (const {nil? cbase} :CGI) :escapeHTML _)
|
|
51
|
+
(send _ {:link_to :content_tag :mail_to :safe_join} ...)
|
|
52
|
+
(send (send _ :tag) _ ...)
|
|
53
|
+
}
|
|
41
54
|
PATTERN
|
|
42
55
|
|
|
43
56
|
def self.translations_cache
|
|
@@ -45,10 +58,13 @@ module RuboCop
|
|
|
45
58
|
end
|
|
46
59
|
|
|
47
60
|
def on_send(node)
|
|
48
|
-
translation_call(node) do |
|
|
61
|
+
translation_call(node) do |key_node, arguments|
|
|
49
62
|
options = arguments.last
|
|
50
63
|
next unless options&.hash_type?
|
|
51
64
|
|
|
65
|
+
key = translation_key(key_node, options)
|
|
66
|
+
next if key.nil? || escaped_by_view_helper?(node, key)
|
|
67
|
+
|
|
52
68
|
translation = translations[key]
|
|
53
69
|
next unless translation.is_a?(String) && HtmlInStringLiteral::HTML_TAG.match?(translation)
|
|
54
70
|
|
|
@@ -61,6 +77,27 @@ module RuboCop
|
|
|
61
77
|
|
|
62
78
|
private
|
|
63
79
|
|
|
80
|
+
def escaped_by_view_helper?(node, key)
|
|
81
|
+
node.receiver.nil? && VIEW_FILE.match?(processed_source.file_path.to_s) &&
|
|
82
|
+
(key.end_with?("_html") || key.split(".").last == "html")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def translation_key(key_node, options)
|
|
86
|
+
key = literal_path(key_node)
|
|
87
|
+
scope = options.pairs.find { |pair| pair.key.sym_type? && pair.key.value == :scope }&.value
|
|
88
|
+
return key if scope.nil? || key.nil?
|
|
89
|
+
|
|
90
|
+
scope_path = literal_path(scope)
|
|
91
|
+
"#{scope_path}.#{key}" if scope_path
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def literal_path(node)
|
|
95
|
+
parts = node.array_type? ? node.values : [node]
|
|
96
|
+
return unless parts.all? { |part| %i[str sym].include?(part.type) }
|
|
97
|
+
|
|
98
|
+
parts.map { |part| part.value.to_s }.join(".")
|
|
99
|
+
end
|
|
100
|
+
|
|
64
101
|
def unescaped_variables(options)
|
|
65
102
|
options.pairs.filter_map do |pair|
|
|
66
103
|
next unless pair.key.sym_type?
|
|
@@ -72,14 +109,14 @@ module RuboCop
|
|
|
72
109
|
|
|
73
110
|
def translations
|
|
74
111
|
self.class.translations_cache[locale_files] ||= locale_files.each_with_object({}) do |path, result|
|
|
75
|
-
next unless File.exist?(path)
|
|
76
|
-
|
|
77
112
|
YAML.safe_load_file(path, aliases: true)&.each_value { |tree| flatten(tree, nil, result) }
|
|
78
113
|
end
|
|
79
114
|
end
|
|
80
115
|
|
|
81
116
|
def locale_files
|
|
82
|
-
Array(cop_config["LocaleFiles"]).
|
|
117
|
+
Array(cop_config["LocaleFiles"]).flat_map do |pattern|
|
|
118
|
+
Dir.glob(File.expand_path(pattern, config.base_dir_for_path_parameters))
|
|
119
|
+
end.sort
|
|
83
120
|
end
|
|
84
121
|
|
|
85
122
|
def flatten(tree, prefix, result)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-neeto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neeraj Singh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rubocop
|