Skip to content

Commit fc076a3

Browse files
authored
feat: Support multiple t functions in the same scope (#46)
1 parent 19c7d6d commit fc076a3

File tree

8 files changed

+264
-81
lines changed

8 files changed

+264
-81
lines changed

lua/js-i18n/analyzer.lua

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ function M.get_key_at_cursor(bufnr, position)
164164
end
165165

166166
--- @class GetTDetail
167+
--- @field t_func_name string
167168
--- @field namespace string
168169
--- @field key_prefix string
169170
--- @field scope_node TSNode|nil
@@ -174,13 +175,16 @@ end
174175
--- @param query vim.treesitter.Query クエリ
175176
--- @return GetTDetail|nil
176177
local function parse_get_t(target_node, source, query)
178+
local t_func_name = nil
177179
local namespace = ""
178180
local key_prefix = ""
179181

180182
for id, node, _ in query:iter_captures(target_node, source, 0, -1) do
181183
local name = query.captures[id]
182184

183-
if name == "i18n.namespace" then
185+
if name == "i18n.t_func_name" then
186+
t_func_name = t_func_name or vim.treesitter.get_node_text(node, source)
187+
elseif name == "i18n.namespace" then
184188
namespace = vim.treesitter.get_node_text(node, source)
185189
elseif name == "i18n.key_prefix" then
186190
key_prefix = vim.treesitter.get_node_text(node, source)
@@ -190,13 +194,15 @@ local function parse_get_t(target_node, source, query)
190194
local scope_node = M.find_closest_node(target_node, { "statement_block", "jsx_element" })
191195

192196
return {
197+
t_func_name = t_func_name,
193198
namespace = namespace,
194199
key_prefix = key_prefix,
195200
scope_node = scope_node,
196201
}
197202
end
198203

199204
--- @class CallTDetail
205+
--- @field t_func_name string
200206
--- @field key string
201207
--- @field key_node TSNode
202208
--- @field key_arg_node TSNode
@@ -209,6 +215,7 @@ end
209215
--- @param query vim.treesitter.Query クエリ
210216
--- @return CallTDetail|nil
211217
local function parse_call_t(target_node, source, query)
218+
local t_func_name = nil
212219
local key = nil
213220
local key_node = nil
214221
local key_arg_node = nil
@@ -220,7 +227,9 @@ local function parse_call_t(target_node, source, query)
220227

221228
-- t関数の呼び出しがネストしている場合があるため、最初に見つかったものを採用する
222229
-- そのため key = ke or ... のような形にしている
223-
if name == "i18n.key" then
230+
if name == "i18n.t_func_name" then
231+
t_func_name = t_func_name or vim.treesitter.get_node_text(node, source)
232+
elseif name == "i18n.key" then
224233
key = key or vim.treesitter.get_node_text(node, source)
225234
key_node = key_node or node
226235
elseif name == "i18n.key_arg" then
@@ -237,6 +246,7 @@ local function parse_call_t(target_node, source, query)
237246
end
238247

239248
return {
249+
t_func_name = t_func_name,
240250
key = key,
241251
key_node = key_node,
242252
key_arg_node = key_arg_node,
@@ -308,56 +318,103 @@ function M.find_call_t_expressions(source, lib, lang)
308318

309319
local query = vim.treesitter.query.parse(language, query_str)
310320

311-
--- @type GetTDetail[]
321+
--- @type table<string, GetTDetail[]>
312322
local scope_stack = {}
313323

324+
local function preprocess_t_func_name_for_scope(t_func_name)
325+
if lib == utils.Library.I18Next then
326+
return t_func_name
327+
elseif lib == utils.Library.NextIntl then
328+
return vim.split(t_func_name, ".", { plain = true })[1]
329+
end
330+
331+
return t_func_name
332+
end
333+
314334
--- @param value GetTDetail
315335
local function enter_scope(value)
316-
table.insert(scope_stack, value)
336+
local t_func_name = value.t_func_name or "t"
337+
scope_stack[t_func_name] = scope_stack[t_func_name] or {}
338+
table.insert(scope_stack[t_func_name], value)
317339
end
318340

319-
local function leave_scope()
320-
table.remove(scope_stack)
341+
--- @param t_func_name string
342+
local function leave_scope(t_func_name)
343+
table.remove(scope_stack[t_func_name or "t"])
321344
end
322345

323-
local function current_scope()
324-
return scope_stack[#scope_stack]
346+
--- @param t_func_name string
347+
local function current_scope(t_func_name)
348+
local t_func_name = preprocess_t_func_name_for_scope(t_func_name)
349+
local stack = scope_stack[t_func_name or "t"] or {}
350+
return stack[#stack]
325351
or {
326352
namespace = "",
327353
key_prefix = "",
328354
scope_node = root_node,
329355
}
330356
end
331357

358+
local function is_t_func(t_func_name)
359+
if t_func_name == "t" or scope_stack[t_func_name] ~= nil then
360+
return true
361+
end
362+
363+
if lib == utils.Library.I18Next then
364+
if t_func_name == "i18next.t" then
365+
return true
366+
end
367+
elseif lib == utils.Library.NextIntl then
368+
-- {t_func_name}.rich や {t_func_name}.markup などの形式も考慮する
369+
local split = vim.split(t_func_name, ".", { plain = true })
370+
local name = split[1]
371+
local member = split[2]
372+
373+
local allow_members = {
374+
["rich"] = true,
375+
["markup"] = true,
376+
["raw"] = true,
377+
}
378+
if (name == "t" or scope_stack[name] ~= nil) and allow_members[member] then
379+
return true
380+
end
381+
end
382+
383+
return false
384+
end
385+
332386
--- @type FindTExpressionResultItem[]
333387
local result = {}
334388

335389
for id, node, _ in query:iter_captures(root_node, source) do
336390
local name = query.captures[id]
337391

338392
-- 現在のスコープから抜けたかどうかを判定する
339-
local current_scope_node = current_scope().scope_node or root_node
340-
if node:start() > current_scope_node:end_() or node:end_() < current_scope_node:start() then
341-
leave_scope()
393+
for t_func_name in pairs(scope_stack) do
394+
local current_scope_node = current_scope(t_func_name).scope_node or root_node
395+
if node:start() > current_scope_node:end_() or node:end_() < current_scope_node:start() then
396+
leave_scope(t_func_name)
397+
end
342398
end
343399

344400
if name == "i18n.get_t" then
345401
local get_t_detail = parse_get_t(node, source, query)
346402
if get_t_detail then
347403
-- 同一のスコープ内で get_t が呼ばれた場合はスコープを上書きする形になるように、一度 leave_scope してから enter_scope する
348-
if get_t_detail.scope_node == current_scope().scope_node then
349-
leave_scope()
404+
if get_t_detail.scope_node == current_scope(get_t_detail.t_func_name).scope_node then
405+
leave_scope(get_t_detail.t_func_name)
350406
end
351407
enter_scope(get_t_detail)
352408
end
353409
elseif name == "i18n.call_t" then
354-
local scope = current_scope()
355410
local call_t_detail = parse_call_t(node, source, query)
356411

357-
if call_t_detail == nil then
412+
if call_t_detail == nil or not is_t_func(call_t_detail.t_func_name) then
358413
goto continue
359414
end
360415

416+
local scope = current_scope(call_t_detail.t_func_name)
417+
361418
local key_prefix = call_t_detail.key_prefix or scope.key_prefix
362419
local key = call_t_detail.key
363420
if key_prefix ~= "" then

queries/i18next.scm

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,48 @@
11
;; getFixedT 関数呼び出し
2-
(call_expression
3-
function: [
4-
(identifier)
5-
(member_expression)
6-
] @get_fixed_t_func (#match? @get_fixed_t_func "getFixedT$")
7-
;; 1: lang, 2: ns, 3: keyPrefix
8-
arguments: (arguments
9-
(
10-
[
11-
(string (string_fragment))
12-
(undefined)
13-
(null)
14-
]
15-
)?
16-
(
17-
[
18-
(string (string_fragment) @i18n.namespace)
19-
(undefined)
20-
(null)
21-
]
22-
)?
23-
(
24-
[
25-
(string (string_fragment) @i18n.key_prefix)
26-
(undefined)
27-
(null)
28-
]
29-
)?
30-
)
2+
(variable_declarator
3+
name: (identifier) @i18n.t_func_name
4+
value:
5+
(call_expression
6+
function: [
7+
(identifier)
8+
(member_expression)
9+
] @get_fixed_t_func (#match? @get_fixed_t_func "getFixedT$")
10+
;; 1: lang, 2: ns, 3: keyPrefix
11+
arguments: (arguments
12+
(
13+
[
14+
(string (string_fragment))
15+
(undefined)
16+
(null)
17+
]
18+
)?
19+
(
20+
[
21+
(string (string_fragment) @i18n.namespace)
22+
(undefined)
23+
(null)
24+
]
25+
)?
26+
(
27+
[
28+
(string (string_fragment) @i18n.key_prefix)
29+
(undefined)
30+
(null)
31+
]
32+
)?
33+
)
34+
)
3135
) @i18n.get_t
3236

33-
3437
;; t 関数呼び出し
3538
(call_expression
3639
function: [
3740
(identifier)
3841
(member_expression)
39-
] @t_func (#match? @t_func "^(i18next\.)?t$")
40-
arguments: (arguments
41-
(string
42-
(string_fragment) @i18n.key
43-
) @i18n.key_arg
44-
)
42+
] @i18n.t_func_name
43+
arguments: (arguments
44+
(string
45+
(string_fragment) @i18n.key
46+
) @i18n.key_arg
47+
)
4548
) @i18n.call_t

queries/next-intl.scm

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
;; useTranslations 関数呼び出し
2-
(call_expression
3-
function: (identifier) @use_translations (#eq? @use_translations "useTranslations")
4-
arguments: (arguments
5-
[
6-
(string (string_fragment) @i18n.key_prefix)
7-
(undefined)
8-
]?
9-
)
2+
(variable_declarator
3+
name: (identifier) @i18n.t_func_name
4+
value:
5+
(call_expression
6+
function: (identifier) @use_translations (#eq? @use_translations "useTranslations")
7+
arguments: (arguments
8+
[
9+
(string (string_fragment) @i18n.key_prefix)
10+
(undefined)
11+
]?
12+
)
13+
)
1014
) @i18n.get_t
1115

1216
;; t 関数呼び出し
1317
(call_expression
1418
function: [
1519
(identifier)
1620
(member_expression)
17-
] @t_func (#match? @t_func "^t(\.rich|\.markup|\.raw)?$")
21+
] @i18n.t_func_name
1822
arguments: (arguments
1923
(string
2024
(string_fragment) @i18n.key

queries/react-i18next.scm

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
;; useTranslation 関数呼び出し
2-
(call_expression
3-
function: (identifier) @use_translation (#eq? @use_translation "useTranslation")
4-
arguments: (arguments
2+
(variable_declarator
3+
name: (object_pattern
54
[
6-
(string (string_fragment) @i18n.namespace)
7-
(undefined)
8-
]?
9-
(object
10-
(pair
11-
key: (property_identifier) @key_prefix_key (#eq? @key_prefix_key "keyPrefix")
12-
value: (string (string_fragment) @i18n.key_prefix)
13-
)?
14-
)?
15-
)
5+
(pair_pattern
6+
key: (property_identifier) @use_translation_t (#eq? @use_translation_t "t")
7+
value: (identifier) @i18n.t_func_name
8+
)
9+
(shorthand_property_identifier_pattern) @i18n.t_func_name
10+
]
11+
)
12+
value:
13+
(call_expression
14+
function: (identifier) @use_translation (#eq? @use_translation "useTranslation")
15+
arguments: (arguments
16+
[
17+
(string (string_fragment) @i18n.namespace)
18+
(undefined)
19+
]?
20+
(object
21+
(pair
22+
key: (property_identifier) @key_prefix_key (#eq? @key_prefix_key "keyPrefix")
23+
value: (string (string_fragment) @i18n.key_prefix)
24+
)?
25+
)?
26+
)
27+
)
1628
) @i18n.get_t
1729

1830
;; Translation コンポーネント
19-
(
20-
jsx_opening_element
31+
(jsx_element
32+
open_tag: (jsx_opening_element
2133
name: (identifier) @translation (#eq? @translation "Translation")
2234
attribute: (jsx_attribute
2335
(property_identifier) @key_prefix_attr (#eq? @key_prefix_attr "keyPrefix")
@@ -28,6 +40,17 @@
2840
)
2941
]
3042
)?
43+
)
44+
(jsx_expression
45+
[
46+
(arrow_function
47+
parameters: (formal_parameters (_) @i18n.t_func_name)
48+
)
49+
(function_expression
50+
parameters: (formal_parameters (_) @i18n.t_func_name)
51+
)
52+
]
53+
)
3154
) @i18n.get_t
3255

3356
;; Trans コンポーネント
@@ -45,7 +68,9 @@
4568
)
4669
attribute: (jsx_attribute
4770
(property_identifier) @attr_t (#eq? @attr_t "t")
48-
(_)
71+
(jsx_expression
72+
(identifier) @i18n.t_func_name
73+
)
4974
)
5075
) @i18n.call_t
5176
(
@@ -62,6 +87,8 @@
6287
)
6388
attribute: (jsx_attribute
6489
(property_identifier) @attr_t (#eq? @attr_t "t")
65-
(_)
90+
(jsx_expression
91+
(identifier) @i18n.t_func_name
92+
)
6693
)
6794
) @i18n.call_t

0 commit comments

Comments
 (0)