Skip to content

Commit

Permalink
Fix wrong line numbers with nested function calls
Browse files Browse the repository at this point in the history
When a nested function call was inside a gettext call, but on a separate
line, the message got extracted with the line number of that function
call. We updated the line number whenever we encountered an opening
parenthesis after a gettext function name.

This commit fixes that by only updating the line number on the first
argument inside a gettext call.

Compared with `xgettext`, some test cases were thus wrong and corrected
in this commit.

Fixes python-babel#1123
  • Loading branch information
dylankiss committed Sep 18, 2024
1 parent f91754b commit 34aa940
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 4 deletions.
10 changes: 8 additions & 2 deletions babel/messages/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from functools import lru_cache
from os.path import relpath
from textwrap import dedent
from tokenize import COMMENT, NAME, OP, STRING, generate_tokens
from tokenize import COMMENT, NAME, NL, OP, STRING, generate_tokens
from typing import TYPE_CHECKING, Any

from babel.messages._compat import find_entrypoints
Expand Down Expand Up @@ -530,7 +530,6 @@ def extract_python(
in_def = False
continue
if funcname:
message_lineno = lineno
call_stack += 1
elif in_def and tok == OP and value == ':':
# End of a class definition without parens
Expand Down Expand Up @@ -580,11 +579,15 @@ def extract_python(
elif tok == STRING:
val = _parse_python_string(value, encoding, future_flags)
if val is not None:
if not message_lineno:
message_lineno = lineno
buf.append(val)

# Python 3.12+, see https://peps.python.org/pep-0701/#new-tokens
elif tok == FSTRING_START:
current_fstring_start = value
if not message_lineno:
message_lineno = lineno
elif tok == FSTRING_MIDDLE:
if current_fstring_start is not None:
current_fstring_start += value
Expand All @@ -608,6 +611,9 @@ def extract_python(
# for the comment to still be a valid one
old_lineno, old_comment = translator_comments.pop()
translator_comments.append((old_lineno + 1, old_comment))

elif tok != NL and not message_lineno:
message_lineno = lineno
elif call_stack > 0 and tok == OP and value == ')':
call_stack -= 1
elif funcname and call_stack == -1:
Expand Down
10 changes: 8 additions & 2 deletions tests/messages/test_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def test_nested_calls(self):
msg8 = gettext('Rabbit')
msg9 = dgettext('wiki', model.addPage())
msg10 = dngettext(getDomain(), 'Page', 'Pages', 3)
msg11 = ngettext(
"bunny",
"bunnies",
len(bunnies)
)
""")
messages = list(extract.extract_python(buf,
extract.DEFAULT_KEYWORDS.keys(),
Expand All @@ -49,6 +54,7 @@ def test_nested_calls(self):
(8, 'gettext', 'Rabbit', []),
(9, 'dgettext', ('wiki', None), []),
(10, 'dngettext', (None, 'Page', 'Pages', None), []),
(12, 'ngettext', ('bunny', 'bunnies', None), []),
]

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

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

def test_declarations(self):
buf = BytesIO(b"""\
Expand Down

0 comments on commit 34aa940

Please sign in to comment.