gl_rubocop 0.5.3 → 0.5.4
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/default.yml +18 -0
- data/lib/gl_rubocop/gl_cops/no_hardcoded_string_assignment_to_text_or_content_variable.rb +56 -0
- data/lib/gl_rubocop/gl_cops/no_hardcoded_strings_in_erb_text_elements.rb +78 -0
- data/lib/gl_rubocop/gl_cops/text_and_content_variable_naming.rb +88 -0
- data/lib/gl_rubocop/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbecf0a138a66001d115607b6e635bda71eb4a2637c7eaa86b164807313efc8c
|
|
4
|
+
data.tar.gz: a40a64ea1547488a822ed7f021c688da5187ec57dde140357d05b3f624c9bbfb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25d837867c4e28a5e2e7134db085a93b94392ceb783bd533240c04a9f582adbfc60033edd5fadce6b5bfa5ebf569728fd258b2394a8f4dff6c9128df44fca953
|
|
7
|
+
data.tar.gz: 6af51558e77b72d5f791f51a5cb87f4a4fa51c953d08b69f9a4c1d87b6724cf5f9601a94e9b8f47f0ee6d06618a53e6c8bb50774cdd5796ddaebc59f98aafe4e
|
data/default.yml
CHANGED
|
@@ -25,6 +25,9 @@ require:
|
|
|
25
25
|
- ./lib/gl_rubocop/gl_cops/view_component_class_naming.rb
|
|
26
26
|
- ./lib/gl_rubocop/gl_cops/view_component_inheritance.rb
|
|
27
27
|
- ./lib/gl_rubocop/gl_cops/view_component_directory_structure.rb
|
|
28
|
+
- ./lib/gl_rubocop/gl_cops/no_hardcoded_strings_in_erb_text_elements.rb
|
|
29
|
+
- ./lib/gl_rubocop/gl_cops/text_and_content_variable_naming.rb
|
|
30
|
+
- ./lib/gl_rubocop/gl_cops/no_hardcoded_string_assignment_to_text_or_content_variable.rb
|
|
28
31
|
|
|
29
32
|
AllCops:
|
|
30
33
|
SuggestExtensions: false
|
|
@@ -63,6 +66,16 @@ GLCops/InteractorInheritsFromInteractorBase:
|
|
|
63
66
|
GLCops/LimitFlashOptions:
|
|
64
67
|
Enabled: true
|
|
65
68
|
|
|
69
|
+
GLCops/NoHardcodedStringAssignmentToTextOrContentVariable:
|
|
70
|
+
Enabled: true
|
|
71
|
+
Include:
|
|
72
|
+
- "app/components/**/*.rb"
|
|
73
|
+
|
|
74
|
+
GLCops/NoHardcodedStringsInErbTextElements:
|
|
75
|
+
Enabled: true
|
|
76
|
+
Include:
|
|
77
|
+
- "app/components/**/*.erb"
|
|
78
|
+
|
|
66
79
|
GLCops/NoStubbingEnv:
|
|
67
80
|
Enabled: true
|
|
68
81
|
Include:
|
|
@@ -88,6 +101,11 @@ GLCops/TailwindNoContradictingClassName:
|
|
|
88
101
|
# Disabling the tailwind no contradicting class name for some fixes
|
|
89
102
|
Enabled: false
|
|
90
103
|
|
|
104
|
+
GLCops/TextAndContentVariableNaming:
|
|
105
|
+
Enabled: true
|
|
106
|
+
Include:
|
|
107
|
+
- "app/components/**/*.erb"
|
|
108
|
+
|
|
91
109
|
GLCops/UniqueIdentifier:
|
|
92
110
|
Enabled: true
|
|
93
111
|
Include:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GLRubocop
|
|
4
|
+
module GLCops
|
|
5
|
+
# Ensures that variables and parameters named with _text, text, _content,
|
|
6
|
+
# or content suffixes are never assigned a hardcoded string literal. Create an
|
|
7
|
+
# i18n entry and use t() or I18n.t() instead.
|
|
8
|
+
#
|
|
9
|
+
# Good:
|
|
10
|
+
# title_text = t('components.title')
|
|
11
|
+
# @label_text = I18n.t('components.label')
|
|
12
|
+
# def initialize(button_text: t('components.button.default'))
|
|
13
|
+
# title_text = content_tag(:span, t('components.title'))
|
|
14
|
+
# @label_content = tag.p(t('components.label'), class: 'label')
|
|
15
|
+
#
|
|
16
|
+
# Bad:
|
|
17
|
+
# title_text = 'Hello'
|
|
18
|
+
# @label_text = 'Click here'
|
|
19
|
+
# def initialize(button_text: 'Submit')
|
|
20
|
+
class NoHardcodedStringAssignmentToTextOrContentVariable < RuboCop::Cop::Cop
|
|
21
|
+
MSG = '`%<name>s` must not be assigned a hardcoded string. ' \
|
|
22
|
+
'Create an i18n entry and use t() instead.'
|
|
23
|
+
|
|
24
|
+
def on_lvasgn(node)
|
|
25
|
+
name, value = node.children
|
|
26
|
+
return unless text_or_content_name?(name)
|
|
27
|
+
return unless value&.str_type?
|
|
28
|
+
|
|
29
|
+
add_offense(value, message: format(MSG, name:))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def on_ivasgn(node)
|
|
33
|
+
name, value = node.children
|
|
34
|
+
return unless text_or_content_name?(name.to_s.delete_prefix('@'))
|
|
35
|
+
return unless value&.str_type?
|
|
36
|
+
|
|
37
|
+
add_offense(value, message: format(MSG, name:))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_kwoptarg(node)
|
|
41
|
+
name, default = node.children
|
|
42
|
+
return unless text_or_content_name?(name)
|
|
43
|
+
return unless default&.str_type?
|
|
44
|
+
|
|
45
|
+
add_offense(default, message: format(MSG, name:))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def text_or_content_name?(name)
|
|
51
|
+
n = name.to_s
|
|
52
|
+
n == 'text' || n == 'content' || n.end_with?('_text', '_content')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/erb_content_helper'
|
|
4
|
+
|
|
5
|
+
module GLRubocop
|
|
6
|
+
module GLCops
|
|
7
|
+
# Ensures that text-containing HTML elements in ERB files do not have
|
|
8
|
+
# literal text outside of ERB expressions. All visible text must be
|
|
9
|
+
# wrapped in <%= t() %> or a variable expression.
|
|
10
|
+
#
|
|
11
|
+
# Good:
|
|
12
|
+
# <p><%= t('components.message') %></p>
|
|
13
|
+
# <span><%= @label_text %></span>
|
|
14
|
+
# <h1><%= @title_text %></h1>
|
|
15
|
+
# <%= content_tag(:p, t('components.message')) %>
|
|
16
|
+
#
|
|
17
|
+
# Bad:
|
|
18
|
+
# <p>Hello World</p>
|
|
19
|
+
# <span>Click here</span>
|
|
20
|
+
# <h1>Page Title</h1>
|
|
21
|
+
class NoHardcodedStringsInErbTextElements < RuboCop::Cop::Cop
|
|
22
|
+
include GLRubocop::ErbContentHelper
|
|
23
|
+
|
|
24
|
+
MSG = 'Hardcoded string in <%<tag>s> on line %<line>d. ' \
|
|
25
|
+
'Use an i18n expression (t()) or a variable.'
|
|
26
|
+
|
|
27
|
+
TEXT_TAGS = %w[
|
|
28
|
+
a span strong em b i p
|
|
29
|
+
h1 h2 h3 h4 h5 h6
|
|
30
|
+
blockquote li td th
|
|
31
|
+
label button dt dd caption
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
def investigate(processed_source)
|
|
35
|
+
return unless erb_file?
|
|
36
|
+
|
|
37
|
+
content = read_erb_file
|
|
38
|
+
return unless content
|
|
39
|
+
|
|
40
|
+
find_and_report_hardcoded_strings(content, processed_source)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def find_and_report_hardcoded_strings(content, processed_source)
|
|
46
|
+
TEXT_TAGS.each do |tag|
|
|
47
|
+
pattern = element_pattern(tag)
|
|
48
|
+
content.scan(pattern) do |groups|
|
|
49
|
+
inner = groups[0]
|
|
50
|
+
next unless hardcoded_text?(inner)
|
|
51
|
+
|
|
52
|
+
match_start = Regexp.last_match.begin(0)
|
|
53
|
+
line_number = content[0...match_start].count("\n") + 1
|
|
54
|
+
range = processed_source.buffer.source_range
|
|
55
|
+
add_offense(nil, location: range, message: format(MSG, tag:, line: line_number))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Matches <tag ...attrs...>body</tag> where attrs may include ERB expressions.
|
|
61
|
+
def element_pattern(tag)
|
|
62
|
+
Regexp.new(
|
|
63
|
+
"<#{Regexp.escape(tag)}\\b(?:[^>]|<%.*?%>)*>(.*?)<\\/#{Regexp.escape(tag)}>",
|
|
64
|
+
Regexp::MULTILINE | Regexp::IGNORECASE
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def hardcoded_text?(inner_content)
|
|
69
|
+
# Skip elements that contain child HTML tags — only flag leaf-level text
|
|
70
|
+
# to avoid duplicate offenses on nested elements.
|
|
71
|
+
return false if inner_content.match?(/<[a-z]/i)
|
|
72
|
+
|
|
73
|
+
stripped = inner_content.gsub(/<%.*?%>/m, '')
|
|
74
|
+
stripped.match?(/[[:alpha:]]/)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/erb_content_helper'
|
|
4
|
+
|
|
5
|
+
module GLRubocop
|
|
6
|
+
module GLCops
|
|
7
|
+
# Ensures that simple variable references inside text-containing HTML
|
|
8
|
+
# elements in ERB files are named with a _text or _content suffix (or
|
|
9
|
+
# are exactly `text` or `content`). This makes the intent explicit:
|
|
10
|
+
# _text for plain strings, _content for text-or-HTML values.
|
|
11
|
+
#
|
|
12
|
+
# Configuration parameters that are not rendered as user-visible text
|
|
13
|
+
# (e.g. variant, size, href) belong in non-text elements and are exempt.
|
|
14
|
+
#
|
|
15
|
+
# Good:
|
|
16
|
+
# <p><%= @message_text %></p>
|
|
17
|
+
# <span><%= @banner_content %></span>
|
|
18
|
+
# <h1><%= text %></h1>
|
|
19
|
+
# <p class="<%= @variant %>"><%= @label_text %></p> (variant is in attribute, not body)
|
|
20
|
+
#
|
|
21
|
+
# Bad:
|
|
22
|
+
# <p><%= @message %></p>
|
|
23
|
+
# <span><%= @title %></span>
|
|
24
|
+
class TextAndContentVariableNaming < RuboCop::Cop::Cop
|
|
25
|
+
include GLRubocop::ErbContentHelper
|
|
26
|
+
|
|
27
|
+
MSG = '`%<name>s` (line %<line>d) is rendered inside a text element. ' \
|
|
28
|
+
'Rename it with a `_text` suffix (plain text) or `_content` suffix (HTML content).'
|
|
29
|
+
|
|
30
|
+
TEXT_TAGS = %w[
|
|
31
|
+
a span strong em b i p
|
|
32
|
+
h1 h2 h3 h4 h5 h6
|
|
33
|
+
blockquote li td th
|
|
34
|
+
label button dt dd caption
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
# Matches only bare variable/ivar references: <%= @name %> or <%= name %>
|
|
38
|
+
# Does NOT match method calls with arguments (t('key'), helper.method, etc.)
|
|
39
|
+
BARE_VAR_PATTERN = /<%=\s*@?(\w+)\s*%>/
|
|
40
|
+
|
|
41
|
+
def investigate(processed_source)
|
|
42
|
+
return unless erb_file?
|
|
43
|
+
|
|
44
|
+
content = read_erb_file
|
|
45
|
+
return unless content
|
|
46
|
+
|
|
47
|
+
find_and_report_improperly_named_variables(content, processed_source)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def find_and_report_improperly_named_variables(content, processed_source)
|
|
53
|
+
TEXT_TAGS.each do |tag|
|
|
54
|
+
pattern = element_pattern(tag)
|
|
55
|
+
content.scan(pattern) do |groups|
|
|
56
|
+
body_start = Regexp.last_match.begin(1)
|
|
57
|
+
check_body_variables(groups[0], body_start, content, processed_source)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def check_body_variables(body, body_start, content, processed_source)
|
|
63
|
+
body.scan(BARE_VAR_PATTERN) do |var_match|
|
|
64
|
+
var_name = var_match[0]
|
|
65
|
+
next if properly_named?(var_name)
|
|
66
|
+
|
|
67
|
+
absolute_offset = body_start + Regexp.last_match.begin(0)
|
|
68
|
+
line_number = content[0...absolute_offset].count("\n") + 1
|
|
69
|
+
msg = format(MSG, name: var_name, line: line_number)
|
|
70
|
+
add_offense(nil, location: processed_source.buffer.source_range, message: msg)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Matches <tag ...attrs...>body</tag> where attrs may contain ERB expressions.
|
|
75
|
+
def element_pattern(tag)
|
|
76
|
+
Regexp.new(
|
|
77
|
+
"<#{Regexp.escape(tag)}\\b(?:[^>]|<%.*?%>)*>(.*?)<\\/#{Regexp.escape(tag)}>",
|
|
78
|
+
Regexp::MULTILINE | Regexp::IGNORECASE
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def properly_named?(name)
|
|
83
|
+
name == 'text' || name == 'content' ||
|
|
84
|
+
name.end_with?('_text', '_content')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/gl_rubocop/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gl_rubocop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Give Lively
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rubocop
|
|
@@ -166,12 +166,15 @@ files:
|
|
|
166
166
|
- lib/gl_rubocop/gl_cops/consolidate_request_system_specs.rb
|
|
167
167
|
- lib/gl_rubocop/gl_cops/interactor_inherits_from_interactor_base.rb
|
|
168
168
|
- lib/gl_rubocop/gl_cops/limit_flash_options.rb
|
|
169
|
+
- lib/gl_rubocop/gl_cops/no_hardcoded_string_assignment_to_text_or_content_variable.rb
|
|
170
|
+
- lib/gl_rubocop/gl_cops/no_hardcoded_strings_in_erb_text_elements.rb
|
|
169
171
|
- lib/gl_rubocop/gl_cops/no_stubbing_env.rb
|
|
170
172
|
- lib/gl_rubocop/gl_cops/no_stubbing_perform_async.rb
|
|
171
173
|
- lib/gl_rubocop/gl_cops/prevent_haml_files.rb
|
|
172
174
|
- lib/gl_rubocop/gl_cops/rails_cache.rb
|
|
173
175
|
- lib/gl_rubocop/gl_cops/sidekiq_inherits_from_sidekiq_job.rb
|
|
174
176
|
- lib/gl_rubocop/gl_cops/tailwind_no_contradicting_class_name.rb
|
|
177
|
+
- lib/gl_rubocop/gl_cops/text_and_content_variable_naming.rb
|
|
175
178
|
- lib/gl_rubocop/gl_cops/unique_identifier.rb
|
|
176
179
|
- lib/gl_rubocop/gl_cops/valid_data_test_id.rb
|
|
177
180
|
- lib/gl_rubocop/gl_cops/vcr_cassette_names.rb
|