Skip to content

Commit bef3a73

Browse files
authored
Fix extracted lineno with nested calls (#1126)
When a gettext call had a nested function call on a new line, the extract function would use that nested call's line number when extracting the terms for the gettext call. The reason is that we set the line number on any encounter of an opening parenthesis after a gettext keyword. This does not work if either we have a nested call, or our first term starts on a new line. This commit fixes that by only setting the line number when we encounter the first argument inside a gettext call. Existing tests were adapted to work according to `xgettext` with regards to the line numbers. Fixes #1123
1 parent ea84d9d commit bef3a73

File tree

2 files changed

+16
-4
lines changed

2 files changed

+16
-4
lines changed

babel/messages/extract.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from functools import lru_cache
3434
from os.path import relpath
3535
from textwrap import dedent
36-
from tokenize import COMMENT, NAME, OP, STRING, generate_tokens
36+
from tokenize import COMMENT, NAME, NL, OP, STRING, generate_tokens
3737
from typing import TYPE_CHECKING, Any
3838

3939
from babel.messages._compat import find_entrypoints
@@ -530,7 +530,6 @@ def extract_python(
530530
in_def = False
531531
continue
532532
if funcname:
533-
message_lineno = lineno
534533
call_stack += 1
535534
elif in_def and tok == OP and value == ':':
536535
# End of a class definition without parens
@@ -580,11 +579,15 @@ def extract_python(
580579
elif tok == STRING:
581580
val = _parse_python_string(value, encoding, future_flags)
582581
if val is not None:
582+
if not message_lineno:
583+
message_lineno = lineno
583584
buf.append(val)
584585

585586
# Python 3.12+, see https://peps.python.org/pep-0701/#new-tokens
586587
elif tok == FSTRING_START:
587588
current_fstring_start = value
589+
if not message_lineno:
590+
message_lineno = lineno
588591
elif tok == FSTRING_MIDDLE:
589592
if current_fstring_start is not None:
590593
current_fstring_start += value
@@ -608,6 +611,9 @@ def extract_python(
608611
# for the comment to still be a valid one
609612
old_lineno, old_comment = translator_comments.pop()
610613
translator_comments.append((old_lineno + 1, old_comment))
614+
615+
elif tok != NL and not message_lineno:
616+
message_lineno = lineno
611617
elif call_stack > 0 and tok == OP and value == ')':
612618
call_stack -= 1
613619
elif funcname and call_stack == -1:

tests/messages/test_extract.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ def test_nested_calls(self):
3434
msg8 = gettext('Rabbit')
3535
msg9 = dgettext('wiki', model.addPage())
3636
msg10 = dngettext(getDomain(), 'Page', 'Pages', 3)
37+
msg11 = ngettext(
38+
"bunny",
39+
"bunnies",
40+
len(bunnies)
41+
)
3742
""")
3843
messages = list(extract.extract_python(buf,
3944
extract.DEFAULT_KEYWORDS.keys(),
@@ -49,6 +54,7 @@ def test_nested_calls(self):
4954
(8, 'gettext', 'Rabbit', []),
5055
(9, 'dgettext', ('wiki', None), []),
5156
(10, 'dngettext', (None, 'Page', 'Pages', None), []),
57+
(12, 'ngettext', ('bunny', 'bunnies', None), []),
5258
]
5359

5460
def test_extract_default_encoding_ascii(self):
@@ -97,10 +103,10 @@ def test_comments_with_calls_that_spawn_multiple_lines(self):
97103
messages = list(extract.extract_python(buf, ('ngettext', '_'), ['NOTE:'],
98104

99105
{'strip_comment_tags': False}))
100-
assert messages[0] == (3, 'ngettext', ('Catalog deleted.', 'Catalogs deleted.', None), ['NOTE: This Comment SHOULD Be Extracted'])
106+
assert messages[0] == (2, 'ngettext', ('Catalog deleted.', 'Catalogs deleted.', None), ['NOTE: This Comment SHOULD Be Extracted'])
101107
assert messages[1] == (6, '_', 'Locale deleted.', ['NOTE: This Comment SHOULD Be Extracted'])
102108
assert messages[2] == (10, 'ngettext', ('Foo deleted.', 'Foos deleted.', None), ['NOTE: This Comment SHOULD Be Extracted'])
103-
assert messages[3] == (15, 'ngettext', ('Bar deleted.', 'Bars deleted.', None), ['NOTE: This Comment SHOULD Be Extracted', 'NOTE: And This One Too'])
109+
assert messages[3] == (14, 'ngettext', ('Bar deleted.', 'Bars deleted.', None), ['NOTE: This Comment SHOULD Be Extracted', 'NOTE: And This One Too'])
104110

105111
def test_declarations(self):
106112
buf = BytesIO(b"""\

0 commit comments

Comments
 (0)