From 10830a4896094d85aa2993a5c746b7120c8de40e Mon Sep 17 00:00:00 2001 From: David Castro <117443013+Dcastroro@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:58:05 +0100 Subject: [PATCH 1/2] Fix JavaScript vertical-tab escape decoding --- babel/messages/jslexer.py | 2 +- tests/messages/test_js_extract.py | 9 +++++++++ tests/messages/test_jslexer.py | 13 +++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/babel/messages/jslexer.py b/babel/messages/jslexer.py index 4d9e66fca..5a9c34597 100644 --- a/babel/messages/jslexer.py +++ b/babel/messages/jslexer.py @@ -22,7 +22,7 @@ '[', ']', '{', '}', '!', '--', '++', '~', ',', ';', '.', ':', ], key=len, reverse=True) # fmt: skip -escapes: dict[str, str] = {'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t'} +escapes: dict[str, str] = {'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t', 'v': '\v'} name_re = re.compile(r'[\w$_][\w\d$_]*', re.UNICODE) dotted_name_re = re.compile(r'[\w$_][\w\d$_.]*[\w\d$_.]', re.UNICODE) diff --git a/tests/messages/test_js_extract.py b/tests/messages/test_js_extract.py index af6dfb144..ec046ecc4 100644 --- a/tests/messages/test_js_extract.py +++ b/tests/messages/test_js_extract.py @@ -5,6 +5,15 @@ from babel.messages import extract +@pytest.mark.parametrize('quote', ["'", '"', '`']) +def test_extract_vertical_tab_escape(quote): + source = 'gettext(' + quote + r'before\vafter' + quote + ');' + buf = BytesIO(source.encode('utf-8')) + assert list(extract.extract('javascript', buf)) == [ + (1, 'before\vafter', [], None), + ] + + def test_simple_extract(): buf = BytesIO(b"""\ msg1 = _('simple') diff --git a/tests/messages/test_jslexer.py b/tests/messages/test_jslexer.py index 00afc78ae..4c7c2a856 100644 --- a/tests/messages/test_jslexer.py +++ b/tests/messages/test_jslexer.py @@ -1,3 +1,5 @@ +import pytest + from babel.messages import jslexer @@ -8,6 +10,17 @@ def test_unquote(): assert jslexer.unquote_string(r'"\xebb"') == "ëb" +@pytest.mark.parametrize('quote', ["'", '"', '`']) +@pytest.mark.parametrize('text', [r'\vtext', r'text\vtext', r'text\v']) +def test_unquote_vertical_tab(quote, text): + assert jslexer.unquote_string(quote + text + quote) == text.replace(r'\v', '\v') + + +@pytest.mark.parametrize('quote', ["'", '"', '`']) +def test_unquote_literal_vertical_tab_escape(quote): + assert jslexer.unquote_string(quote + r'\\v' + quote) == r'\v' + + def test_dollar_in_identifier(): assert list(jslexer.tokenize('dollar$dollar')) == [('name', 'dollar$dollar', 1)] From 4de1dd24cacafd00286ca0cf16c77a2b22894ea2 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 30 Oct 2026 12:00:59 +0200 Subject: [PATCH 2/2] Simplify tests --- tests/messages/test_js_extract.py | 13 +++---------- tests/messages/test_jslexer.py | 15 ++------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/tests/messages/test_js_extract.py b/tests/messages/test_js_extract.py index ec046ecc4..2bad02041 100644 --- a/tests/messages/test_js_extract.py +++ b/tests/messages/test_js_extract.py @@ -5,20 +5,12 @@ from babel.messages import extract -@pytest.mark.parametrize('quote', ["'", '"', '`']) -def test_extract_vertical_tab_escape(quote): - source = 'gettext(' + quote + r'before\vafter' + quote + ');' - buf = BytesIO(source.encode('utf-8')) - assert list(extract.extract('javascript', buf)) == [ - (1, 'before\vafter', [], None), - ] - - def test_simple_extract(): buf = BytesIO(b"""\ msg1 = _('simple') msg2 = gettext('simple') msg3 = ngettext('s', 'p', 42) +msg4 = gettext('before\\vafter') """) messages = \ list(extract.extract('javascript', buf, extract.DEFAULT_KEYWORDS, @@ -26,7 +18,8 @@ def test_simple_extract(): assert messages == [(1, 'simple', [], None), (2, 'simple', [], None), - (3, ('s', 'p'), [], None)] + (3, ('s', 'p'), [], None), + (4, 'before\vafter', [], None)] def test_various_calls(): diff --git a/tests/messages/test_jslexer.py b/tests/messages/test_jslexer.py index 4c7c2a856..788e29a41 100644 --- a/tests/messages/test_jslexer.py +++ b/tests/messages/test_jslexer.py @@ -1,5 +1,3 @@ -import pytest - from babel.messages import jslexer @@ -8,17 +6,8 @@ def test_unquote(): assert jslexer.unquote_string(r'"h\u00ebllo"') == "hëllo" assert jslexer.unquote_string(r'"h\xebllo"') == "hëllo" assert jslexer.unquote_string(r'"\xebb"') == "ëb" - - -@pytest.mark.parametrize('quote', ["'", '"', '`']) -@pytest.mark.parametrize('text', [r'\vtext', r'text\vtext', r'text\v']) -def test_unquote_vertical_tab(quote, text): - assert jslexer.unquote_string(quote + text + quote) == text.replace(r'\v', '\v') - - -@pytest.mark.parametrize('quote', ["'", '"', '`']) -def test_unquote_literal_vertical_tab_escape(quote): - assert jslexer.unquote_string(quote + r'\\v' + quote) == r'\v' + assert jslexer.unquote_string(r'"a\vb"') == "a\vb" + assert jslexer.unquote_string(r'"\\v"') == r"\v" def test_dollar_in_identifier():