rubocop-dev_doc 0.5.0.beta1 → 0.5.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/config/default.yml +19 -139
- data/lib/rubocop/cop/dev_doc/auth/current_user_branching.rb +47 -4
- data/lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb +53 -2
- data/lib/rubocop/cop/dev_doc/migration/avoid_vague_column_names.rb +7 -1
- data/lib/rubocop/cop/dev_doc/rails/avoid_ordering_by_id.rb +167 -0
- data/lib/rubocop/cop/dev_doc/rails/avoid_rails_callbacks.rb +14 -4
- data/lib/rubocop/cop/dev_doc/rails/avoid_raw_sql.rb +227 -0
- data/lib/rubocop/cop/dev_doc/style/prefer_public_send.rb +86 -0
- data/lib/rubocop/cop/dev_doc/style/tap_block_ignores_value.rb +123 -0
- data/lib/rubocop/cop/dev_doc/test/avoid_unit_test.rb +24 -0
- data/lib/rubocop/dev_doc/version.rb +1 -1
- metadata +13 -7
- data/lib/rubocop/cop/dev_doc/i18n/report_text.rb +0 -112
- data/lib/rubocop/cop/dev_doc/i18n/require_translation.rb +0 -116
- data/lib/rubocop/cop/dev_doc/i18n/translation_key_prefix.rb +0 -89
- data/lib/rubocop/cop/dev_doc/i18n/unverified_translation.rb +0 -106
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
module RuboCop
|
|
2
|
-
module Cop
|
|
3
|
-
module DevDoc
|
|
4
|
-
module I18n
|
|
5
|
-
# Flag hardcoded user-facing strings in glib JSON-UI component props.
|
|
6
|
-
#
|
|
7
|
-
# ## Rationale
|
|
8
|
-
# Glib components render text from props like `text:`, `label:`, and
|
|
9
|
-
# `title:`. Passing a string literal ships untranslatable copy — it can
|
|
10
|
-
# never be localized and bypasses the I18n catalog. Pass `t('...')` (or
|
|
11
|
-
# any non-literal) so the text resolves through the locale files.
|
|
12
|
-
#
|
|
13
|
-
# Only the hash form is checked (`view.p text: '...'`). Empty/whitespace
|
|
14
|
-
# strings are ignored — they carry no user-facing text. An interpolated
|
|
15
|
-
# string (`"Hi #{name}"`) is flagged because its literal portions are
|
|
16
|
-
# still hardcoded copy — unless it interpolates a `t(...)` (or
|
|
17
|
-
# `translate`/`I18n.t`) call, which is treated as positive localization
|
|
18
|
-
# and left alone.
|
|
19
|
-
#
|
|
20
|
-
# The watched method names and localizable keys are configurable via
|
|
21
|
-
# `WatchedMethods:` and `LocalizableKeys:`.
|
|
22
|
-
#
|
|
23
|
-
# ❌ Hardcoded — can't be translated
|
|
24
|
-
# view.p text: 'Welcome'
|
|
25
|
-
# view.fields_text label: 'Email'
|
|
26
|
-
#
|
|
27
|
-
# ✔️ Resolved through I18n
|
|
28
|
-
# view.p text: t('home.welcome')
|
|
29
|
-
# view.fields_text label: t('user.email')
|
|
30
|
-
# view.p text: "#{t('home.welcome')}, #{user.name}"
|
|
31
|
-
#
|
|
32
|
-
# @example
|
|
33
|
-
# # bad
|
|
34
|
-
# view.p text: 'Welcome'
|
|
35
|
-
#
|
|
36
|
-
# # bad
|
|
37
|
-
# view.fields_text label: 'Email'
|
|
38
|
-
#
|
|
39
|
-
# # bad (interpolation, but no translation call)
|
|
40
|
-
# view.p text: "Hi #{name}"
|
|
41
|
-
#
|
|
42
|
-
# # good
|
|
43
|
-
# view.p text: t('home.welcome')
|
|
44
|
-
#
|
|
45
|
-
# # good (interpolates a translation call)
|
|
46
|
-
# view.p text: "#{t('home.greeting')} #{user.name}"
|
|
47
|
-
#
|
|
48
|
-
# # good (non-literal — not flagged)
|
|
49
|
-
# view.p text: user.name
|
|
50
|
-
class RequireTranslation < Base
|
|
51
|
-
MSG = 'Localize this string: pass `t(...)` instead of a hardcoded ' \
|
|
52
|
-
'string for `%<key>s:`.'.freeze
|
|
53
|
-
|
|
54
|
-
DEFAULT_WATCHED_METHODS = %w[
|
|
55
|
-
h1 h2 h3 h4 h5 p label markdown
|
|
56
|
-
fields_text fields_number fields_select fields_password
|
|
57
|
-
fields_textarea fields_check fields_checkGroup fields_chipGroup
|
|
58
|
-
fields_timeZone fields_radioGroup fields_date fields_datetime
|
|
59
|
-
].freeze
|
|
60
|
-
|
|
61
|
-
DEFAULT_LOCALIZABLE_KEYS = %w[
|
|
62
|
-
title subtitle subsubtitle label placeholder text
|
|
63
|
-
].freeze
|
|
64
|
-
|
|
65
|
-
TRANSLATION_METHODS = %i[t translate].freeze
|
|
66
|
-
|
|
67
|
-
def on_send(node)
|
|
68
|
-
return unless watched_methods.include?(node.method_name.to_s)
|
|
69
|
-
|
|
70
|
-
node.arguments.each do |arg|
|
|
71
|
-
next unless arg.hash_type?
|
|
72
|
-
|
|
73
|
-
arg.pairs.each { |pair| check_pair(pair) }
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
alias on_csend on_send
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
def check_pair(pair)
|
|
81
|
-
key = pair.key
|
|
82
|
-
return unless key.sym_type?
|
|
83
|
-
return unless localizable_keys.include?(key.value.to_s)
|
|
84
|
-
return unless hardcoded_string?(pair.value)
|
|
85
|
-
|
|
86
|
-
add_offense(pair.value, message: format(MSG, key: key.value))
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# A plain string literal is hardcoded copy unless it's blank. A `dstr`
|
|
90
|
-
# (interpolated string) is hardcoded copy too — unless it interpolates
|
|
91
|
-
# a translation call, which counts as positive localization.
|
|
92
|
-
def hardcoded_string?(node)
|
|
93
|
-
return !localized_interpolation?(node) if node.dstr_type?
|
|
94
|
-
return false unless node.str_type?
|
|
95
|
-
|
|
96
|
-
!node.value.strip.empty?
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def localized_interpolation?(node)
|
|
100
|
-
node.each_descendant(:send, :csend).any? do |call|
|
|
101
|
-
TRANSLATION_METHODS.include?(call.method_name)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def watched_methods
|
|
106
|
-
cop_config.fetch('WatchedMethods', DEFAULT_WATCHED_METHODS)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def localizable_keys
|
|
110
|
-
cop_config.fetch('LocalizableKeys', DEFAULT_LOCALIZABLE_KEYS)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
module RuboCop
|
|
2
|
-
module Cop
|
|
3
|
-
module DevDoc
|
|
4
|
-
module I18n
|
|
5
|
-
# Require translation keys to start with an allowed namespace prefix.
|
|
6
|
-
#
|
|
7
|
-
# ## Rationale
|
|
8
|
-
# A flat translation catalog drifts into collisions and dead keys.
|
|
9
|
-
# Requiring every `t(...)` key to start with an agreed namespace (e.g.
|
|
10
|
-
# `hotel.`, `general.`) keeps the locale files organized and makes it
|
|
11
|
-
# obvious which feature owns a key.
|
|
12
|
-
#
|
|
13
|
-
# The allowed prefixes are project-specific, so `AllowedPrefixes:`
|
|
14
|
-
# defaults to empty and the cop does nothing until a project configures
|
|
15
|
-
# it. Matches `t`, `translate`, and `I18n.t` (the receiver is ignored).
|
|
16
|
-
#
|
|
17
|
-
# Only statically-literal keys are checked. A key built from
|
|
18
|
-
# interpolation (`t("#{prefix}.foo")`) or a variable (`t(key)`) can't be
|
|
19
|
-
# verified and is skipped. A key that begins with a literal segment
|
|
20
|
-
# (`t("hotel.#{id}")`) is checked against that leading segment.
|
|
21
|
-
#
|
|
22
|
-
# ❌ No recognized prefix
|
|
23
|
-
# t('welcome.title')
|
|
24
|
-
#
|
|
25
|
-
# ✔️ Namespaced
|
|
26
|
-
# t('general.welcome.title')
|
|
27
|
-
# t('hotel.rooms.heading')
|
|
28
|
-
#
|
|
29
|
-
# @example AllowedPrefixes: ['hotel.', 'general.']
|
|
30
|
-
# # bad
|
|
31
|
-
# t('welcome.title')
|
|
32
|
-
#
|
|
33
|
-
# # bad (lazy key has no namespace)
|
|
34
|
-
# t('.title')
|
|
35
|
-
#
|
|
36
|
-
# # good
|
|
37
|
-
# t('hotel.rooms.heading')
|
|
38
|
-
#
|
|
39
|
-
# # good (variable suffix, literal prefix is checked)
|
|
40
|
-
# t("general.#{key}")
|
|
41
|
-
#
|
|
42
|
-
# # not checked (fully dynamic key)
|
|
43
|
-
# t("#{prefix}.title")
|
|
44
|
-
class TranslationKeyPrefix < Base
|
|
45
|
-
MSG = 'Translation key `%<key>s` must start with an allowed ' \
|
|
46
|
-
'prefix: %<prefixes>s.'.freeze
|
|
47
|
-
|
|
48
|
-
RESTRICT_ON_SEND = %i[t translate].freeze
|
|
49
|
-
|
|
50
|
-
def on_send(node)
|
|
51
|
-
prefixes = allowed_prefixes
|
|
52
|
-
return if prefixes.empty?
|
|
53
|
-
|
|
54
|
-
key_node = node.first_argument
|
|
55
|
-
return unless key_node
|
|
56
|
-
|
|
57
|
-
leading = leading_literal(key_node)
|
|
58
|
-
return if leading.nil?
|
|
59
|
-
return if prefixes.any? { |prefix| leading.start_with?(prefix) }
|
|
60
|
-
|
|
61
|
-
add_offense(key_node, message: format(MSG, key: key_node.source, prefixes: prefixes_display(prefixes)))
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
private
|
|
65
|
-
|
|
66
|
-
# The statically-known leading portion of the key, or nil when the key
|
|
67
|
-
# is dynamic (a variable, or a string that begins with interpolation).
|
|
68
|
-
def leading_literal(node)
|
|
69
|
-
case node.type
|
|
70
|
-
when :str then node.value
|
|
71
|
-
when :sym then node.value.to_s
|
|
72
|
-
when :dstr
|
|
73
|
-
first = node.children.first
|
|
74
|
-
first&.str_type? ? first.value : nil
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def allowed_prefixes
|
|
79
|
-
Array(cop_config['AllowedPrefixes'])
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def prefixes_display(prefixes)
|
|
83
|
-
prefixes.map { |prefix| "`#{prefix}`" }.join(', ')
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
module RuboCop
|
|
2
|
-
module Cop
|
|
3
|
-
module DevDoc
|
|
4
|
-
module I18n
|
|
5
|
-
# Warn when a glib text prop is set from a non-literal value that isn't
|
|
6
|
-
# a `t(...)` call — it may be an unlocalized string.
|
|
7
|
-
#
|
|
8
|
-
# ## Rationale
|
|
9
|
-
# `DevDoc/I18n/RequireTranslation` only catches string literals written
|
|
10
|
-
# at the call site. A value passed through a variable or method
|
|
11
|
-
# (`label: label_text`) escapes it — the literal could be one hop away.
|
|
12
|
-
# This cop flags those values so a human can confirm they resolve
|
|
13
|
-
# through I18n.
|
|
14
|
-
#
|
|
15
|
-
# It can't tell a stashed literal from genuinely dynamic data
|
|
16
|
-
# (`user.name`) or a translation reached via a variable, so it's a
|
|
17
|
-
# review aid, not a clean lint: it's **disabled by default** and runs at
|
|
18
|
-
# `info` severity. Run it during a localization pass, not on every
|
|
19
|
-
# commit. Values that are obviously translated (`t(...)`, `translate`,
|
|
20
|
-
# `I18n.t`) are excluded; literals are left to `RequireTranslation`.
|
|
21
|
-
#
|
|
22
|
-
# ⚠️ Can't be verified — confirm it's localized
|
|
23
|
-
# view.fields_text label: label_text
|
|
24
|
-
# view.p text: user.name
|
|
25
|
-
#
|
|
26
|
-
# ✔️ Obviously localized — not flagged
|
|
27
|
-
# view.p text: t('home.welcome')
|
|
28
|
-
#
|
|
29
|
-
# @example
|
|
30
|
-
# # warning (non-literal — may be unlocalized)
|
|
31
|
-
# view.fields_text label: label_text
|
|
32
|
-
#
|
|
33
|
-
# # warning (attribute — may be unlocalized)
|
|
34
|
-
# view.p text: user.name
|
|
35
|
-
#
|
|
36
|
-
# # good (obviously localized — not flagged)
|
|
37
|
-
# view.p text: t('home.welcome')
|
|
38
|
-
#
|
|
39
|
-
# # ignored (literal — handled by RequireTranslation)
|
|
40
|
-
# view.p text: 'Welcome'
|
|
41
|
-
class UnverifiedTranslation < Base
|
|
42
|
-
MSG = 'Possibly unlocalized: `%<key>s:` is set from a non-literal. ' \
|
|
43
|
-
'Use `t(...)` if this is user-facing text.'.freeze
|
|
44
|
-
|
|
45
|
-
DYNAMIC_TYPES = %i[lvar ivar send csend].freeze
|
|
46
|
-
TRANSLATION_METHODS = %i[t translate].freeze
|
|
47
|
-
|
|
48
|
-
DEFAULT_WATCHED_METHODS = %w[
|
|
49
|
-
h1 h2 h3 h4 h5 p label markdown
|
|
50
|
-
fields_text fields_number fields_select fields_password
|
|
51
|
-
fields_textarea fields_check fields_checkGroup fields_chipGroup
|
|
52
|
-
fields_timeZone fields_radioGroup fields_date fields_datetime
|
|
53
|
-
].freeze
|
|
54
|
-
|
|
55
|
-
DEFAULT_LOCALIZABLE_KEYS = %w[
|
|
56
|
-
title subtitle subsubtitle label placeholder text
|
|
57
|
-
].freeze
|
|
58
|
-
|
|
59
|
-
def on_send(node)
|
|
60
|
-
return unless watched_methods.include?(node.method_name.to_s)
|
|
61
|
-
|
|
62
|
-
node.arguments.each do |arg|
|
|
63
|
-
next unless arg.hash_type?
|
|
64
|
-
|
|
65
|
-
arg.pairs.each { |pair| check_pair(pair) }
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
alias on_csend on_send
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def check_pair(pair)
|
|
73
|
-
key = pair.key
|
|
74
|
-
return unless key.sym_type?
|
|
75
|
-
return unless localizable_keys.include?(key.value.to_s)
|
|
76
|
-
return unless unverified?(pair.value)
|
|
77
|
-
|
|
78
|
-
add_offense(pair.value, message: format(MSG, key: key.value))
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# A value we can neither confirm nor deny is localized: a variable or
|
|
82
|
-
# method result (but not an obvious `t(...)`/`translate` call). String
|
|
83
|
-
# and other literals are excluded — those are RequireTranslation's job.
|
|
84
|
-
def unverified?(node)
|
|
85
|
-
return false unless DYNAMIC_TYPES.include?(node.type)
|
|
86
|
-
|
|
87
|
-
!translation_call?(node)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def translation_call?(node)
|
|
91
|
-
(node.send_type? || node.csend_type?) &&
|
|
92
|
-
TRANSLATION_METHODS.include?(node.method_name)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def watched_methods
|
|
96
|
-
cop_config.fetch('WatchedMethods', DEFAULT_WATCHED_METHODS)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def localizable_keys
|
|
100
|
-
cop_config.fetch('LocalizableKeys', DEFAULT_LOCALIZABLE_KEYS)
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|