copy_tuner_client 2.1.0 → 2.1.1

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: a3c816e1f12e5715861194284864c00ab5d18428d3c56b6a3140cfe9db9cd360
4
- data.tar.gz: d9a8cd76caf12860c0a981a840655254a5aab64665745d16f4524ded77821771
3
+ metadata.gz: 447684c86dea9d93b959e3adaf7e4dcc7c555461a22caa153e6a04e3b6fae211
4
+ data.tar.gz: fd0976c79ae1199c496f55025556aa1d2879615b87137b618ab8e15221c665ad
5
5
  SHA512:
6
- metadata.gz: 1538da853220ee8f4bfc4942023e1afcfd55dc4f719a89b3c965d8719acb57ec5bd418d8821e72eee8adbb5b59ca2f23b462d8894b9239734ff68a30b8a0a074
7
- data.tar.gz: '091eee7aae91c30f88fb294716bd9a139d55d7ac90d982602e14305d2c43203360dc81b98092e17dd931dcce1885940af27a47f0031e374e9724eaf4898934d5'
6
+ metadata.gz: 78e1334502deb656827a36911e877fe6f336d4e5b36b41c59665c52b04eba1a69482a8b5dd83a79416445c471002c3218fcc18c4b086fba5038dae67cc98302d
7
+ data.tar.gz: 49edcbbab88a562edcc05dda73ded2ac248c5323a890787d274261adbe9864ea1451fca04668fe68dba03910116315e16ba2f539eef9dd446c2c0e39638e2d3a
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '2.1.0'.freeze
3
+ VERSION = '2.1.1'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
@@ -3,8 +3,9 @@ name: copy-tuner-to-t-migrate
3
3
  description: >-
4
4
  copy_tuner_client v2.0.0 への移行で、削除された独自ヘルパー tt の呼び出しを Rails 標準の t(translate)へ
5
5
  置換するスキル。tt は PR #122 で存在理由を失い gem から削除されたため、残った tt(...) は NoMethodError に
6
- なる。大半は機械的に tt( → t( で済むが、訳文を文字列加工している箇所だけは t ではなく I18n.t へ移す必要があり
7
- (開発環境のマーカートークン混入バグ対策)、そこは 1 件ずつ確認する。対象は app 配下の .rb / .haml / .erb。
6
+ なる。大半は機械的に tt( → t( で済むが、訳文を文字列加工している箇所(→ I18n.t へ)と label 系ヘルパーの
7
+ 第一引数に渡している箇所(→ 引数構造を分離)だけは別扱いで、開発環境のマーカートークン残留バグを避けるため
8
+ 1 件ずつ確認する。対象は app 配下の .rb / .haml / .erb。
8
9
  disable-model-invocation: true
9
10
  license: MIT
10
11
  ---
@@ -36,13 +37,46 @@ middleware 自体が無いので再現しない=開発環境だけ壊れる)
36
37
  `t` にすると、`tt` がもともと潰していたバグをそのまま復活させてしまう。そこは middleware のラッパーを通らない
37
38
  **`I18n.t`(絶対キー)** へ移すのが正解で、機械的には決められないため 1 件ずつ確認する。
38
39
 
40
+ ## もう一つの罠: label の第一引数に渡す
41
+
42
+ 文字列加工していなくても危険な経路がもう一つある。`t(...)` の戻り値を **`label` 系ヘルパーの第一引数**
43
+ (form builder の `f.label` / `label_tag` / 素の `label`)に渡しているケース:
44
+
45
+ ```haml
46
+ = f.label t('activerecord.attributes.field.keywords'), class: 'form-label'
47
+ ```
48
+
49
+ `f.label` の**第一引数は「ラベルテキスト」ではなく method 名**(`for` 属性・`id`・ラベル文字列の元)として
50
+ 扱われる。そのため development では次の連鎖でマーカーが消せない位置に残る:
51
+
52
+ 1. `t(...)` が `⟦CT:activerecord.attributes.field.keywords⟧分類` を返す。
53
+ 2. `f.label` がこれを method 名と解釈し、**ラベルテキスト**と **`for` 属性**の 2 か所に展開する。
54
+ 3. ラベルテキスト側は Rails の `humanize` で**小文字化**され `⟦ct:…⟧` になる。
55
+ 4. Rewriter のマーカー検出プレフィックスは **大文字 `⟦CT:` 固定**(`lib/copy_tuner_client/copyray/marker.rb`
56
+ の `PREFIX`)。小文字化された `⟦ct:` は一致せず、除去されないまま画面に残る。
57
+
58
+ これも v1.x では `tt`(マーカー無し版)だったため表面化せず、**`tt → t` 一括置換で初めて顕在化する回帰**。
59
+
60
+ **重要: このケースは `I18n.t` 化では直らない。** マーカーを消しても、`t`/`I18n.t` の戻り値(訳文文字列)を
61
+ `label` の第一引数に渡す構造自体が `humanize` / `for` 属性の挙動として不適切だからだ。正しい修正は
62
+ **method 名を第一引数・表示テキストを第二引数に分離**すること:
63
+
64
+ ```haml
65
+ = f.label :keywords_cont, t('activerecord.attributes.field.keywords'), class: 'form-label'
66
+ ```
67
+
68
+ `for` 属性が変わるので、ラベルクリックでフィールドにフォーカスが当たることを実機で確認する。
69
+ スクリプトはこのパターンを suspicious のサブ種別 `label_arg` として抽出する(`label:` オプションに渡す
70
+ simple_form の `f.input :x, label: tt('k')` も同様に拾う)。
71
+
39
72
  ## 変換の 3 分類
40
73
 
41
- | 分類 | 例 | 扱い |
42
- |---|---|---|
43
- | **safe** | `tt('views.foo')` / `= tt '.title'` / `tt(key, default: x)` | `tt(` → `t(` に決定論的一括変換 |
44
- | **suspicious** | `truncate(tt('k'))` / `tt('k').length` / `tt('k')[0..n]` / `tt('k') =~ /re/` / `tt('k').gsub(...)` 等 | `I18n.t('絶対キー')` へ。**1 件ずつ確認**。スクリプトは触らない |
45
- | **other** | `def tt` / `alias tt`(定義側)/ app 外(`lib/` 等)/ `tt` を含む別識別子の誤検出 | 最後にまとめて提示。自動変換しない |
74
+ | 分類 | サブ種別 | 例 | 扱い |
75
+ |---|---|---|---|
76
+ | **safe** | — | `tt('views.foo')` / `= tt '.title'` / `tt(key, default: x)` / `f.label :method, tt('k')`(第二引数) | `tt(` → `t(` に決定論的一括変換 |
77
+ | **suspicious** | `string_manipulation` | `truncate(tt('k'))` / `tt('k').length` / `tt('k')[0..n]` / `tt('k') =~ /re/` / `tt('k').gsub(...)` 等 | `I18n.t('絶対キー')` へ。**1 件ずつ確認**。スクリプトは触らない |
78
+ | **suspicious** | `label_arg` | `= f.label tt('k')` / `label_tag tt('k')` / `f.input :x, label: tt('k')`(label 系の**第一引数 / label: オプション**) | **`f.label :method, t('k')` へ構造を直す**(method 名を第一引数・表示テキストを第二引数に)。**`I18n.t` 化では直らない**。1 件ずつ確認 |
79
+ | **other** | — | `def tt` / `alias tt`(定義側)/ app 外(`lib/` 等)/ `tt` を含む別識別子の誤検出 | 最後にまとめて提示。自動変換しない |
46
80
 
47
81
  **重要(定義側の罠):** アプリが v2.0.0 を待つ間の後方互換として `ApplicationHelper` に `def tt(key, **) = t(key, **)`
48
82
  のような **`tt` の定義**を生やしていることがある。これは「呼び出し」ではないので `t(` へ機械置換すると Rails の `t`
@@ -80,11 +114,21 @@ suspicious / other の行は**一切触らない**(行番号で限定してい
80
114
 
81
115
  ### 3. suspicious な箇所を 1 件ずつ確認
82
116
 
83
- ここが人間の判断が要る核心。手順 1 の suspicious 各件について、次を 1 件ずつ提示して確認を取る:
117
+ ここが人間の判断が要る核心。手順 1 の suspicious 各件について、次を 1 件ずつ提示して確認を取る。
118
+ **suspicious はサブ種別(`[label_arg]` / `[string_manipulation]`)で修正方法が異なる**ので、種別で分岐する:
84
119
 
120
+ **`[string_manipulation]`(戻り値を文字列加工している):**
85
121
  - 該当コード(`ファイル:行`)と、なぜ怪しいか(戻り値を文字列加工している=マーカー混入の危険)
86
122
  - 提案する置換: `t(...)` ではなく **`I18n.t('絶対キー')`**(`I18n` モジュール直呼びはラッパーを通らずマーカーが付かない)
87
123
 
124
+ **`[label_arg]`(label 系の第一引数 / label: オプションに渡している):**
125
+ - なぜ怪しいか(`humanize` で小文字化された `⟦ct:…⟧` が大文字 `⟦CT:` 固定の Rewriter をすり抜け残る。「もう一つの罠」参照)
126
+ - 提案する置換: **`I18n.t` 化ではなく引数構造を直す。** `= f.label t('views.foo.bar'), class: …` →
127
+ `= f.label :method_name, t('views.foo.bar'), class: …`。第一引数の method 名(`for` 属性に使うシンボル)は
128
+ ユーザに確認するか、対応する入力フィールド(`f.text_field :keywords_cont` 等)から推定する。
129
+ `label:` オプション(`f.input :x, label: tt('k')`)の場合は引数構造はそのままで `t('k')` でよいことが多いが、
130
+ そのフィールドのラベルが `humanize` を経るかは入力次第なので 1 件ずつ確認する。
131
+
88
132
  注意点:
89
133
  - **たとえユーザが「全部まとめて I18n.t にして」と言っても、suspicious は必ず 1 件ずつ提示する。** 相対キーを含む件は絶対キーをユーザと確認しないと置換できないためで、省略すると誤ったキーを書き込む危険がある。
90
134
  - **相対キー(先頭ドット `tt('.foo')`)は `I18n.t` では解決できない。** 絶対キー(`I18n.t('views.foo.bar')`)へ
@@ -94,6 +138,8 @@ suspicious / other の行は**一切触らない**(行番号で限定してい
94
138
  調べて基点キーを特定するか、ユーザに直接絶対キーを確認する。絶対キーが不明なときは
95
139
  `grep -r "キー末尾部分" config/locales/` でロケールファイルを検索して候補を絞り込む。
96
140
  - 本当に文字列加工していない(誤検出)なら、その場合は `t(...)` でよい。ユーザの判断に従う。
141
+ - **行をまたぐ変数経由(`txt = t('k')` → 別行で `f.label txt` や `truncate(txt)`)はスクリプトの行単位
142
+ スキャンでは拾えない**(構造的限界)。最終防波堤は手順 5 の「実画面に小文字マーカー `⟦ct:…⟧` が出ないか目視」。
97
143
 
98
144
  確認が取れた箇所だけ Edit で個別に置換する(スクリプトでは変換しない)。
99
145
 
@@ -118,6 +164,9 @@ git grep -nwI tt -- app # -I でバイナリ(画像等)の誤マッチを
118
164
  (safe は 0 件、`I18n.t` 化した suspicious も `tt` としては消える)。
119
165
  - 対象アプリで `bundle exec rspec`(または該当アプリのテスト)を回し、`NoMethodError` が出ないこと。
120
166
  - development で実画面を開き、文字列加工していた箇所にマーカートークン(`⟦CT:...⟧`)が混入していないこと。
167
+ - **label を含む画面で、小文字マーカー `⟦ct:...⟧`(小文字 ct)が残っていないこと。** Rewriter は大文字
168
+ `⟦CT:` しか除去しないため、`humanize` で小文字化された取りこぼしは**この目視が最終防波堤**になる
169
+ (label 第一引数の罠・行をまたぐ変数経由はここで初めて顕在化することがある)。
121
170
 
122
171
  ## スクリプトの責務(決定論的な範囲のみ)
123
172
 
@@ -81,6 +81,18 @@ STRING_METHOD_RE = /\btt\b\s*(?:\([^\n]*?\)|['":@][^\n,]*)\s*(?:\.\s*(?:#{STRING
81
81
  # 先頭ドットの相対キー tt('.foo') は I18n.t では解決できず絶対キー化が要る → 強調表示用。
82
82
  RELATIVE_KEY_RE = /\btt\b\s*(?:\(\s*)?['":]\s*\./
83
83
 
84
+ # label 系ヘルパーの第一引数 / label: オプションに tt(t) を渡すパターン。
85
+ # t 化すると戻り値(⟦CT:..⟧) が method 名扱いされ、humanize で小文字化された ⟦ct:..⟧ が
86
+ # Rewriter(大文字 ⟦CT: 固定)の除去網をすり抜け画面に残る。I18n.t 化では直らない。
87
+ # 正しい修正は method 名を第一引数・表示テキストを第二引数へ分離すること。
88
+ LABEL_FIRST_ARG_RE = /
89
+ (?: (?<![\w.])\w+\.label\b | \blabel_tag\b | (?<![\w.])label(?![\w]) )
90
+ \s*\(?\s*
91
+ tt\b (?=\s*[("':@])
92
+ /x
93
+ # simple_form 等の label: オプションに渡すケース(f.input :x, label: tt('k'))
94
+ LABEL_OPTION_RE = /\blabel:\s*tt\b/
95
+
84
96
  def relative(path)
85
97
  path.sub("#{ROOT}/", '')
86
98
  end
@@ -117,8 +129,19 @@ def classify_line(line, entry, in_app:)
117
129
  return [:other, entry]
118
130
  end
119
131
 
132
+ # label の第一引数 / label: オプションに渡すケースは文字列加工が無いので、safe 確定より前に拾う。
133
+ # 修正方法が文字列加工系(I18n.t 化)と異なるためサブ種別 :label_arg で区別する。
134
+ if line.match?(LABEL_FIRST_ARG_RE) || line.match?(LABEL_OPTION_RE)
135
+ entry[:kind] = :label_arg
136
+ entry[:hint] = 'label の引数に渡している。t 化すると humanize で小文字化された ⟦ct:..⟧ が ' \
137
+ 'Rewriter(大文字 ⟦CT: のみ除去)をすり抜け画面に残る。I18n.t 化では直らない。' \
138
+ 'method 名を第一引数・表示テキストを第二引数に分離: f.label :method, t(...)'
139
+ return [:suspicious, entry]
140
+ end
141
+
120
142
  return [:safe, entry] unless line.match?(STRING_HELPER_RE) || line.match?(STRING_METHOD_RE)
121
143
 
144
+ entry[:kind] = :string_manipulation
122
145
  entry[:relative_key] = line.match?(RELATIVE_KEY_RE)
123
146
  entry[:hint] = if entry[:relative_key]
124
147
  'I18n.t へ。相対キー(.foo)は絶対キーへ書き換えが必要'
@@ -147,10 +170,14 @@ end
147
170
  def print_section(title, entries)
148
171
  puts "\n== #{title} (#{entries.size}) =="
149
172
  entries.each do |e|
173
+ # hint があれば必ず kind も付く(classify_line で対で設定)ので素直に出す。
174
+ # suspicious 内のサブ種別(label_arg / string_manipulation)を可視化する。
150
175
  suffix = if e[:hint]
151
- " # #{e[:hint]}"
176
+ " # [#{e[:kind]}] #{e[:hint]}"
177
+ elsif e[:reason]
178
+ " # #{e[:reason]}"
152
179
  else
153
- (e[:reason] ? " # #{e[:reason]}" : '')
180
+ ''
154
181
  end
155
182
  puts "#{e[:file]}:#{e[:lineno]}: #{e[:code].strip}#{suffix}"
156
183
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copy_tuner_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - SonicGarden
@@ -182,7 +182,6 @@ executables: []
182
182
  extensions: []
183
183
  extra_rdoc_files: []
184
184
  files:
185
- - ".babelrc"
186
185
  - ".gitattributes"
187
186
  - ".github/dependabot.yml"
188
187
  - ".github/workflows/rspec.yml"
data/.babelrc DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "presets": [
3
- [
4
- "env",
5
- {
6
- "modules": false,
7
- "targets": {
8
- "browsers": "> 1%",
9
- "uglify": true,
10
- },
11
- "useBuiltIns": true,
12
- },
13
- ],
14
- ],
15
- "plugins": [
16
- "external-helpers"
17
- ]
18
- }