rubocop-dev_doc 0.5.0.beta1 → 0.6.0.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e13da0de0c2a17314d72cbe4bbeb9882a0ad1f93e4976d2e0548210af1684073
4
- data.tar.gz: 57bf1fb471573aaa8417f3a86f9c4eba84ebc1d71d38d397c7cac2d05607d058
3
+ metadata.gz: dfa7b6778294801c95bb37bb3c15a2c4b30f81e9b915ba3ee548475c4adc9cd4
4
+ data.tar.gz: 6606b6c2b6745b2c54f63a89a7b3ae1b4ce3bfb60dda96a471dc51e33232e802
5
5
  SHA512:
6
- metadata.gz: c302ad93dcb52860f23365fa361df3c1f593d618c6948b7b113eac124e640687345c2e953eca523777377eb27d7f75b4c177c2029dc5ceb71e7c30e0d52d5fab
7
- data.tar.gz: bf88a2042d7dce6169ec7b274c1249b4c291518076f1d26c5d5b9f8c5a95699bcd1e343d27970e8809f7ad91fafe67d1206a553b1e5e162f14c59458cf3b0416
6
+ metadata.gz: adc4743bfba263e139f6ca4cc5abbc213c427a46081e0aa5bc12be84ae96cc26f5b37eb218707449ca4566e5fca6958534f9581104c0d92c97f83e2ef17003b2
7
+ data.tar.gz: f8aa2b1f19d0f1a628f86ae58b19ed71ba6c5f508fd3247203909f459fb22deeef7777fc5de17509af04a0e4618aebd95c1b19a6fdf78fe53c72f97db063e261
data/config/default.yml CHANGED
@@ -83,11 +83,12 @@ DevDoc/Migration/DateColumnNaming:
83
83
  - "db/migrate/**/*.rb"
84
84
 
85
85
  DevDoc/Migration/AvoidVagueColumnNames:
86
- Description: "Avoid vague column names like `status` or `group`. Use more specific names."
86
+ Description: "Avoid vague column names like `status`, `group`, or `kind`. Use more specific names."
87
87
  Enabled: true
88
88
  VagueNames:
89
89
  - status
90
90
  - group
91
+ - kind
91
92
  Include:
92
93
  - "db/migrate/*.rb"
93
94
  - "db/migrate/**/*.rb"
@@ -150,10 +151,10 @@ DevDoc/Rails/EnumColumnNotNull:
150
151
  DevDoc/Rails/NoBlockPredicateOnRelation:
151
152
  Description: "Avoid block-form `count`/`reject`/`select`/`find`/`any?` on AR relations; push the predicate into SQL with `.where(...)` or a scope."
152
153
  Enabled: true
154
+ AdditionalNonRelationMethods: []
153
155
  Exclude:
154
156
  - "spec/**/*"
155
157
  - "test/**/*"
156
- - "db/seeds.rb"
157
158
  - "db/seeds/**/*.rb"
158
159
  - "lib/tasks/**/*.rb"
159
160
 
@@ -169,6 +170,10 @@ DevDoc/Style/AvoidSend:
169
170
  Description: "Avoid `send`/`public_send` with an explicit receiver; prefer direct calls or safer alternatives."
170
171
  Enabled: true
171
172
 
173
+ DevDoc/Style/PreferPublicSend:
174
+ Description: "Prefer `public_send` over `send` — visibility-respecting dispatch surfaces typos/misconfig and documents intent."
175
+ Enabled: true
176
+
172
177
  DevDoc/Style/RepeatedSafeNavigationReceiver:
173
178
  Description: "Avoid using `&.` on the same receiver more than once in a method body — assign once and use `.` after."
174
179
  Enabled: true
@@ -181,6 +186,10 @@ DevDoc/Style/MinimizeVariableScope:
181
186
  Description: "Assign a variable inside the `if` condition that guards it, to keep its scope local to the branch."
182
187
  Enabled: true
183
188
 
189
+ DevDoc/Style/TapBlockIgnoresValue:
190
+ Description: "Flag `tap` whose block ignores the yielded value — `tap` is for operating on the receiver, not eliding a temp var."
191
+ Enabled: true
192
+
184
193
  DevDoc/Style/AvoidHeadResponse:
185
194
  Description: "Avoid `head()` with error statuses; delegate error handling to Rails exceptions or model validations."
186
195
  Enabled: true
@@ -311,6 +320,14 @@ DevDoc/Rails/StrongParametersExpect:
311
320
  Include:
312
321
  - "app/controllers/**/*.rb"
313
322
 
323
+ DevDoc/Rails/AvoidRawSql:
324
+ Description: "Avoid raw SQL — prefer the hash/array form, parameterized fragments, or Arel; disable with a reason when raw SQL is genuinely required."
325
+ Enabled: true
326
+
327
+ DevDoc/Rails/AvoidOrderingById:
328
+ Description: "Avoid `order`/`reorder` by `id` as the primary (sole or leftmost) sort — order by a business column, or use `id` only as a secondary tiebreaker."
329
+ Enabled: true
330
+
314
331
  # `save!` / `update!` raise on persistence failure, which is what
315
332
  # we want in jobs/services/rake tasks — silent `false` returns
316
333
  # hide bugs. Excluded for controllers, which lean on `save`
@@ -383,7 +400,21 @@ DevDoc/Auth/CurrentUserBranching:
383
400
  - "app/helpers/**/*.rb"
384
401
  - "app/controllers/concerns/**/*.rb"
385
402
  - "app/views/layouts/**/*"
386
- - "app/controllers/application_controller.rb"
403
+
404
+ DevDoc/I18n/AvoidTitleizeHumanize:
405
+ Description: "Avoid `.titleize`/`.humanize` for display text; it's English-only and bypasses I18n. Use `t(...)`."
406
+ # A review aid, not a clean lint: it can't tell a display string from one
407
+ # used to build a key or a log line. Off by default and `warning` severity;
408
+ # run it during a localization pass. The flagged methods are configurable
409
+ # via `Methods:`.
410
+ Enabled: false
411
+ Severity: warning
412
+ Methods:
413
+ - titleize
414
+ - humanize
415
+ Include:
416
+ - "app/views/**/*.jbuilder"
417
+ - "app/views/**/*.rb"
387
418
 
388
419
  DevDoc/I18n/RequireTranslation:
389
420
  Description: "Localize user-facing strings in glib JSON-UI props; pass `t(...)` instead of a hardcoded string."
@@ -397,9 +428,16 @@ DevDoc/I18n/RequireTranslation:
397
428
  - h3
398
429
  - h4
399
430
  - h5
431
+ - h6
400
432
  - p
401
433
  - label
402
434
  - markdown
435
+ - html
436
+ - button
437
+ - switch
438
+ - chip
439
+ - progressCircle
440
+ - shareButton
403
441
  - fields_text
404
442
  - fields_number
405
443
  - fields_select
@@ -412,6 +450,13 @@ DevDoc/I18n/RequireTranslation:
412
450
  - fields_radioGroup
413
451
  - fields_date
414
452
  - fields_datetime
453
+ - fields_upload
454
+ - dialogs_alert
455
+ - dialogs_notification
456
+ - snackbars_alert
457
+ - snackbars_select
458
+ - banners_alert
459
+ - banners_select
415
460
  LocalizableKeys:
416
461
  - title
417
462
  - subtitle
@@ -419,6 +464,12 @@ DevDoc/I18n/RequireTranslation:
419
464
  - label
420
465
  - placeholder
421
466
  - text
467
+ - message
468
+ - description
469
+ - uploadText
470
+ - leftText
471
+ - rightText
472
+ - onLabel
422
473
  Include:
423
474
  - "app/views/**/*.jbuilder"
424
475
  - "app/views/**/*.rb"
@@ -437,46 +488,6 @@ DevDoc/I18n/TranslationKeyPrefix:
437
488
  - "app/mailers/**/*.rb"
438
489
  - "app/helpers/**/*.rb"
439
490
 
440
- DevDoc/I18n/UnverifiedTranslation:
441
- Description: "Warn when a glib text prop is set from a non-literal value that isn't a `t(...)` call — it may be an unlocalized string."
442
- # A review aid, not a clean lint: it can't tell a stashed literal from
443
- # dynamic data (`user.name`) or a translation reached via a variable. Off by
444
- # default and `info` severity — run it during a localization pass. Mirrors
445
- # RequireTranslation's WatchedMethods/LocalizableKeys.
446
- Enabled: false
447
- Severity: info
448
- WatchedMethods:
449
- - h1
450
- - h2
451
- - h3
452
- - h4
453
- - h5
454
- - p
455
- - label
456
- - markdown
457
- - fields_text
458
- - fields_number
459
- - fields_select
460
- - fields_password
461
- - fields_textarea
462
- - fields_check
463
- - fields_checkGroup
464
- - fields_chipGroup
465
- - fields_timeZone
466
- - fields_radioGroup
467
- - fields_date
468
- - fields_datetime
469
- LocalizableKeys:
470
- - title
471
- - subtitle
472
- - subsubtitle
473
- - label
474
- - placeholder
475
- - text
476
- Include:
477
- - "app/views/**/*.jbuilder"
478
- - "app/views/**/*.rb"
479
-
480
491
  DevDoc/I18n/ReportText:
481
492
  Description: "Report every user-facing glib text prop — hardcoded and already-localized — to collect all possible texts."
482
493
  # A tooling aid, not a lint: unlike RequireTranslation it fires on *every*
@@ -491,9 +502,16 @@ DevDoc/I18n/ReportText:
491
502
  - h3
492
503
  - h4
493
504
  - h5
505
+ - h6
494
506
  - p
495
507
  - label
496
508
  - markdown
509
+ - html
510
+ - button
511
+ - switch
512
+ - chip
513
+ - progressCircle
514
+ - shareButton
497
515
  - fields_text
498
516
  - fields_number
499
517
  - fields_select
@@ -506,6 +524,13 @@ DevDoc/I18n/ReportText:
506
524
  - fields_radioGroup
507
525
  - fields_date
508
526
  - fields_datetime
527
+ - fields_upload
528
+ - dialogs_alert
529
+ - dialogs_notification
530
+ - snackbars_alert
531
+ - snackbars_select
532
+ - banners_alert
533
+ - banners_select
509
534
  LocalizableKeys:
510
535
  - title
511
536
  - subtitle
@@ -513,6 +538,12 @@ DevDoc/I18n/ReportText:
513
538
  - label
514
539
  - placeholder
515
540
  - text
541
+ - message
542
+ - description
543
+ - uploadText
544
+ - leftText
545
+ - rightText
546
+ - onLabel
516
547
  Include:
517
548
  - "app/views/**/*.jbuilder"
518
549
  - "app/views/**/*.rb"
@@ -56,13 +56,56 @@ module RuboCop
56
56
  #
57
57
  # ## Allowed paths (Exclude:)
58
58
  # By default the cop is silent in:
59
- # app/policies/**/*.rb
59
+ # app/policies/**/*.rb ← auth-dependent checks belong here
60
60
  # app/helpers/**/*.rb
61
61
  # app/controllers/concerns/**/*.rb
62
- # app/views/layouts/**/*
63
- # app/controllers/application_controller.rb
62
+ # app/views/layouts/**/* ← display branching (nav bars, etc.)
64
63
  #
65
- # Override via `Exclude:` in your `.rubocop.yml`.
64
+ # Override via `Exclude:` in your `.rubocop.yml`. Note: literal file
65
+ # paths (e.g. `app/controllers/application_controller.rb`) in
66
+ # `.rubocop.yml` `Exclude:` lists are flagged by the
67
+ # `DevDoc::Test::Lints::NoFileExcludes` lint — they hide the
68
+ # suppression from readers of that file. If `ApplicationController`
69
+ # needs an exception, use an inline `# rubocop:disable` at the
70
+ # specific line with a reason.
71
+ #
72
+ # ## Before disabling inline, consider the Policy
73
+ # The Policy exclusion is not accidental — it's the canonical
74
+ # Rails home for auth-dependent branching. If the flagged line
75
+ # is an authorization check (`return if @record.accessible_by?(current_user)`,
76
+ # `if current_user.admin?`, etc.), the right move is almost
77
+ # always to push that check into a Pundit policy method, not
78
+ # disable inline. The controller becomes
79
+ # `return unless policy(@record).access?` — same behaviour,
80
+ # no `current_user` reference in the controller, and the auth
81
+ # logic is reusable + testable in isolation.
82
+ #
83
+ # Even better: declare `glib_authorize_resource` at the
84
+ # controller level. glib-web runs the appropriate policy
85
+ # method before each action automatically, so the per-action
86
+ # `verify_access` / `authorize @record` line is usually not
87
+ # needed at all — the controller body no longer references
88
+ # `current_user` because the auth check has moved out of the
89
+ # action entirely. See `best_practices/backend/en/05_controller.md`
90
+ # item #7 for the canonical pattern.
91
+ #
92
+ # ❌ Auth check in the controller — flagged
93
+ # def verify_access
94
+ # return if @support_question.accessible_by?(current_user)
95
+ # # ... token fallback ...
96
+ # end
97
+ #
98
+ # ✔️ Same check in the Policy — silently allowed
99
+ # # app/policies/support_question_policy.rb
100
+ # def access?
101
+ # user.present? && record.accessible_by?(user)
102
+ # end
103
+ #
104
+ # # in the controller:
105
+ # def verify_access
106
+ # return if policy(@support_question).access?
107
+ # # ... token fallback ...
108
+ # end
66
109
  #
67
110
  # NOTE: The cop does not autocorrect — there is no mechanical fix. The
68
111
  # right response depends on developer intent: drop the branch (if auth
@@ -0,0 +1,59 @@
1
+ module RuboCop
2
+ module Cop
3
+ module DevDoc
4
+ module I18n
5
+ # Warn when `.titleize` or `.humanize` is used to derive display text.
6
+ #
7
+ # ## Rationale
8
+ # `titleize`/`humanize` turn an identifier into an English phrase
9
+ # (`'sign_up'.titleize # => 'Sign Up'`). When that result is shown to a
10
+ # user it bypasses the I18n catalog and can only ever read as English —
11
+ # it can't be translated. Localize the text with a `t(...)` lookup
12
+ # instead of transforming a symbol/column name at render time.
13
+ #
14
+ # This can't tell a display string from one used to build a key or a
15
+ # log line, so it is a **review aid**: disabled by default, `warning`
16
+ # severity, and scoped to view files. Run it during a localization pass.
17
+ #
18
+ # ⚠️ English-only — can't be translated
19
+ # view.h1 text: status.titleize
20
+ # view.p text: user.role.humanize
21
+ #
22
+ # ✔️ Resolved through I18n
23
+ # view.h1 text: t("status.#{status}")
24
+ #
25
+ # @example
26
+ # # warning
27
+ # view.h1 text: status.titleize
28
+ #
29
+ # # warning
30
+ # view.p text: model_name.humanize
31
+ #
32
+ # # good
33
+ # view.h1 text: t("status.#{status}")
34
+ class AvoidTitleizeHumanize < Base
35
+ MSG = 'Avoid `.%<method>s` for display text: the result is ' \
36
+ 'English-only and bypasses I18n. Use `t(...)` instead.'.freeze
37
+
38
+ DEFAULT_METHODS = %w[titleize humanize].freeze
39
+
40
+ def on_send(node)
41
+ return unless forbidden_methods.include?(node.method_name.to_s)
42
+
43
+ add_offense(
44
+ node.loc.selector,
45
+ message: format(MSG, method: node.method_name)
46
+ )
47
+ end
48
+ alias on_csend on_send
49
+
50
+ private
51
+
52
+ def forbidden_methods
53
+ cop_config.fetch('Methods', DEFAULT_METHODS)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,109 @@
1
+ module RuboCop
2
+ module Cop
3
+ module DevDoc
4
+ module I18n
5
+ # Shared traversal for the glib JSON-UI localization cops
6
+ # (`RequireTranslation`, `ReportText`).
7
+ #
8
+ # It locates user-facing text values on watched glib component calls and
9
+ # hands each `(key, value_node)` to the including cop's
10
+ # `#inspect_localizable`, which decides whether to report. Three call
11
+ # shapes are covered:
12
+ #
13
+ # * hash prop: `view.p text: '...'`
14
+ # * nested hash prop: `view.icon tooltip: { text: '...' }`
15
+ # * jbuilder positional: `json.title '...'`
16
+ #
17
+ # Nested hashes (and hashes inside arrays, e.g. select options) are
18
+ # walked to any depth, so a localizable key is found wherever it sits.
19
+ #
20
+ # The watched method names and localizable keys are configurable via
21
+ # `WatchedMethods:` and `LocalizableKeys:`.
22
+ module LocalizableProps
23
+ DEFAULT_WATCHED_METHODS = %w[
24
+ h1 h2 h3 h4 h5 h6 p label markdown html
25
+ button switch chip progressCircle shareButton
26
+ fields_text fields_number fields_select fields_password
27
+ fields_textarea fields_check fields_checkGroup fields_chipGroup
28
+ fields_timeZone fields_radioGroup fields_date fields_datetime
29
+ fields_upload
30
+ dialogs_alert dialogs_notification
31
+ snackbars_alert snackbars_select
32
+ banners_alert banners_select
33
+ ].freeze
34
+
35
+ DEFAULT_LOCALIZABLE_KEYS = %w[
36
+ title subtitle subsubtitle label placeholder text
37
+ message description uploadText leftText rightText onLabel
38
+ ].freeze
39
+
40
+ TRANSLATION_METHODS = %i[t translate].freeze
41
+
42
+ def on_send(node)
43
+ if watched_methods.include?(node.method_name.to_s)
44
+ node.arguments.each { |arg| each_localizable(arg) }
45
+ end
46
+
47
+ check_jbuilder_positional(node)
48
+ end
49
+ alias on_csend on_send
50
+
51
+ private
52
+
53
+ # Walk a value, handing every localizable `(key, value)` pair found at
54
+ # any depth to the cop: top-level hash pairs, nested hashes, and
55
+ # hashes nested inside arrays (e.g. select options / menu buttons).
56
+ def each_localizable(node)
57
+ case node.type
58
+ when :hash
59
+ node.pairs.each do |pair|
60
+ key = pair.key
61
+ if key.sym_type? && localizable_keys.include?(key.value.to_s)
62
+ inspect_localizable(key.value, pair.value)
63
+ end
64
+
65
+ each_localizable(pair.value)
66
+ end
67
+ when :array
68
+ node.children.each { |child| each_localizable(child) }
69
+ end
70
+ end
71
+
72
+ # jbuilder positional form: `json.title 'Forms'`. The method name is
73
+ # the key and the first argument is the value. Restricted to the
74
+ # jbuilder root `json` so ordinary `obj.text('...')` calls aren't
75
+ # mistaken for localizable props.
76
+ def check_jbuilder_positional(node)
77
+ return unless localizable_keys.include?(node.method_name.to_s)
78
+ return unless json_receiver?(node.receiver)
79
+
80
+ value = node.first_argument
81
+ return unless value
82
+
83
+ inspect_localizable(node.method_name, value)
84
+ end
85
+
86
+ def json_receiver?(node)
87
+ return false unless node
88
+
89
+ (node.send_type? && node.method_name == :json && node.receiver.nil?) ||
90
+ (node.lvar_type? && node.children.first == :json)
91
+ end
92
+
93
+ def translation_call?(node)
94
+ (node.send_type? || node.csend_type?) &&
95
+ TRANSLATION_METHODS.include?(node.method_name)
96
+ end
97
+
98
+ def watched_methods
99
+ cop_config.fetch('WatchedMethods', DEFAULT_WATCHED_METHODS)
100
+ end
101
+
102
+ def localizable_keys
103
+ cop_config.fetch('LocalizableKeys', DEFAULT_LOCALIZABLE_KEYS)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,3 +1,5 @@
1
+ require_relative 'localizable_props'
2
+
1
3
  module RuboCop
2
4
  module Cop
3
5
  module DevDoc
@@ -19,7 +21,7 @@ module RuboCop
19
21
  # Both the hardcoded form (`view.p text: 'Welcome'`) and the localized
20
22
  # form (`view.p text: t('home.welcome')`) are reported. Blank/whitespace
21
23
  # strings and pure dynamic values (`user.name`) carry no static text and
22
- # are skipped — see `DevDoc/I18n/UnverifiedTranslation` for those.
24
+ # are skipped.
23
25
  #
24
26
  # The watched method names and localizable keys are configurable via
25
27
  # `WatchedMethods:` and `LocalizableKeys:`.
@@ -46,41 +48,16 @@ module RuboCop
46
48
  # # ignored (pure dynamic — no static text)
47
49
  # view.p text: user.name
48
50
  class ReportText < Base
49
- MSG = 'Text for `%<key>s:`: review/collect this for localization.'.freeze
50
-
51
- DEFAULT_WATCHED_METHODS = %w[
52
- h1 h2 h3 h4 h5 p label markdown
53
- fields_text fields_number fields_select fields_password
54
- fields_textarea fields_check fields_checkGroup fields_chipGroup
55
- fields_timeZone fields_radioGroup fields_date fields_datetime
56
- ].freeze
57
-
58
- DEFAULT_LOCALIZABLE_KEYS = %w[
59
- title subtitle subsubtitle label placeholder text
60
- ].freeze
51
+ include LocalizableProps
61
52
 
62
- TRANSLATION_METHODS = %i[t translate].freeze
63
-
64
- def on_send(node)
65
- return unless watched_methods.include?(node.method_name.to_s)
66
-
67
- node.arguments.each do |arg|
68
- next unless arg.hash_type?
69
-
70
- arg.pairs.each { |pair| check_pair(pair) }
71
- end
72
- end
73
- alias on_csend on_send
53
+ MSG = 'Text for `%<key>s:`: review/collect this for localization.'.freeze
74
54
 
75
55
  private
76
56
 
77
- def check_pair(pair)
78
- key = pair.key
79
- return unless key.sym_type?
80
- return unless localizable_keys.include?(key.value.to_s)
81
- return unless text?(pair.value)
57
+ def inspect_localizable(key, value)
58
+ return unless text?(value)
82
59
 
83
- add_offense(pair.value, message: format(MSG, key: key.value))
60
+ add_offense(value, message: format(MSG, key: key))
84
61
  end
85
62
 
86
63
  # Any value that carries static user-facing text: a non-blank string
@@ -92,19 +69,6 @@ module RuboCop
92
69
 
93
70
  translation_call?(node)
94
71
  end
95
-
96
- def translation_call?(node)
97
- (node.send_type? || node.csend_type?) &&
98
- TRANSLATION_METHODS.include?(node.method_name)
99
- end
100
-
101
- def watched_methods
102
- cop_config.fetch('WatchedMethods', DEFAULT_WATCHED_METHODS)
103
- end
104
-
105
- def localizable_keys
106
- cop_config.fetch('LocalizableKeys', DEFAULT_LOCALIZABLE_KEYS)
107
- end
108
72
  end
109
73
  end
110
74
  end
@@ -1,3 +1,5 @@
1
+ require_relative 'localizable_props'
2
+
1
3
  module RuboCop
2
4
  module Cop
3
5
  module DevDoc
@@ -10,10 +12,12 @@ module RuboCop
10
12
  # never be localized and bypasses the I18n catalog. Pass `t('...')` (or
11
13
  # any non-literal) so the text resolves through the locale files.
12
14
  #
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
15
+ # Hash props (`view.p text: '...'`), nested hash props
16
+ # (`view.icon tooltip: { text: '...' }`) and the jbuilder positional
17
+ # form (`json.title '...'`) are all checked. Empty/whitespace strings
18
+ # are ignoredthey carry no user-facing text. An interpolated string
19
+ # (`"Hi #{name}"`) is flagged because its literal portions are still
20
+ # hardcoded copy — unless it interpolates a `t(...)` (or
17
21
  # `translate`/`I18n.t`) call, which is treated as positive localization
18
22
  # and left alone.
19
23
  #
@@ -23,6 +27,8 @@ module RuboCop
23
27
  # ❌ Hardcoded — can't be translated
24
28
  # view.p text: 'Welcome'
25
29
  # view.fields_text label: 'Email'
30
+ # view.icon tooltip: { text: 'Share' }
31
+ # json.title 'Forms'
26
32
  #
27
33
  # ✔️ Resolved through I18n
28
34
  # view.p text: t('home.welcome')
@@ -36,6 +42,12 @@ module RuboCop
36
42
  # # bad
37
43
  # view.fields_text label: 'Email'
38
44
  #
45
+ # # bad (nested hash prop)
46
+ # view.icon tooltip: { text: 'Share' }
47
+ #
48
+ # # bad (jbuilder positional)
49
+ # json.title 'Forms'
50
+ #
39
51
  # # bad (interpolation, but no translation call)
40
52
  # view.p text: "Hi #{name}"
41
53
  #
@@ -48,42 +60,17 @@ module RuboCop
48
60
  # # good (non-literal — not flagged)
49
61
  # view.p text: user.name
50
62
  class RequireTranslation < Base
63
+ include LocalizableProps
64
+
51
65
  MSG = 'Localize this string: pass `t(...)` instead of a hardcoded ' \
52
66
  'string for `%<key>s:`.'.freeze
53
67
 
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
68
  private
79
69
 
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)
70
+ def inspect_localizable(key, value)
71
+ return unless hardcoded_string?(value)
85
72
 
86
- add_offense(pair.value, message: format(MSG, key: key.value))
73
+ add_offense(value, message: format(MSG, key: key))
87
74
  end
88
75
 
89
76
  # A plain string literal is hardcoded copy unless it's blank. A `dstr`
@@ -101,14 +88,6 @@ module RuboCop
101
88
  TRANSLATION_METHODS.include?(call.method_name)
102
89
  end
103
90
  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
91
  end
113
92
  end
114
93
  end