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 +4 -4
- data/config/default.yml +74 -43
- data/lib/rubocop/cop/dev_doc/auth/current_user_branching.rb +47 -4
- data/lib/rubocop/cop/dev_doc/i18n/avoid_titleize_humanize.rb +59 -0
- data/lib/rubocop/cop/dev_doc/i18n/localizable_props.rb +109 -0
- data/lib/rubocop/cop/dev_doc/i18n/report_text.rb +8 -44
- data/lib/rubocop/cop/dev_doc/i18n/require_translation.rb +21 -42
- 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 +7 -2
- data/lib/rubocop/cop/dev_doc/i18n/unverified_translation.rb +0 -106
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dfa7b6778294801c95bb37bb3c15a2c4b30f81e9b915ba3ee548475c4adc9cd4
|
|
4
|
+
data.tar.gz: 6606b6c2b6745b2c54f63a89a7b3ae1b4ce3bfb60dda96a471dc51e33232e802
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 `
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
78
|
-
|
|
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(
|
|
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
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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 ignored — they 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
|
|
81
|
-
|
|
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(
|
|
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
|