Skip to content

Commit 72af41b

Browse files
committed
JavaScript extractor: Add rudimentary JSX/E4X support
Fixes #280
1 parent afc3d36 commit 72af41b

File tree

3 files changed

+36
-3
lines changed

3 files changed

+36
-3
lines changed

babel/messages/extract.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ def extract_javascript(fileobj, keywords, comment_tags, options):
461461
:param comment_tags: a list of translator tags to search for and include
462462
in the results
463463
:param options: a dictionary of additional options (optional)
464+
Supported options are:
465+
* `jsx` -- set to false to disable JSX/E4X support.
464466
"""
465467
from babel.messages.jslexer import tokenize, unquote_string
466468
funcname = message_lineno = None
@@ -472,7 +474,7 @@ def extract_javascript(fileobj, keywords, comment_tags, options):
472474
last_token = None
473475
call_stack = -1
474476

475-
for token in tokenize(fileobj.read().decode(encoding)):
477+
for token in tokenize(fileobj.read().decode(encoding), jsx=options.get("jsx", True)):
476478
if token.type == 'operator' and token.value == '(':
477479
if funcname:
478480
message_lineno = token.lineno

babel/messages/jslexer.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
([eE][-+]?\d+)? |
3737
(0x[a-fA-F0-9]+)
3838
)''')),
39+
('jsx_tag', re.compile(r'<(?:/?)\w+.+?>', re.I)),
3940
('operator', re.compile(r'(%s)' % '|'.join(map(re.escape, operators)))),
4041
('string', re.compile(r'''(?xs)(
4142
'(?:[^'\\]*(?:\\.[^'\\]*)*)' |
@@ -127,8 +128,11 @@ def unquote_string(string):
127128
return u''.join(result)
128129

129130

130-
def tokenize(source):
131-
"""Tokenize a JavaScript source. Returns a generator of tokens.
131+
def tokenize(source, jsx=True):
132+
"""
133+
Tokenize JavaScript/JSX source. Returns a generator of tokens.
134+
135+
:param jsx: Enable (limited) JSX parsing.
132136
"""
133137
may_divide = False
134138
pos = 0
@@ -138,6 +142,8 @@ def tokenize(source):
138142
while pos < end:
139143
# handle regular rules first
140144
for token_type, rule in rules:
145+
if not jsx and token_type and 'jsx' in token_type:
146+
continue
141147
match = rule.match(source, pos)
142148
if match is not None:
143149
break

tests/messages/test_js_extract.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -- encoding: UTF-8 --
2+
import pytest
23
from babel._compat import BytesIO
34
from babel.messages import extract
45

@@ -97,3 +98,27 @@ def test_misplaced_comments():
9798
assert messages[1][3] == [u'NOTE: this will show up', 'too.']
9899
assert messages[2][2] == u'no comment here'
99100
assert messages[2][3] == []
101+
102+
103+
JSX_SOURCE = b"""
104+
class Foo {
105+
render() {
106+
const value = gettext("hello");
107+
return (
108+
<option value="val1">{ i18n._('String1') }</option>
109+
<option value="val2">{ i18n._('String 2') }</option>
110+
<option value="val3">{ i18n._('String 3') }</option>
111+
);
112+
}
113+
"""
114+
EXPECTED_JSX_MESSAGES = ["hello", "String1", "String 2", "String 3"]
115+
116+
117+
@pytest.mark.parametrize("jsx_enabled", (False, True))
118+
def test_jsx_extraction(jsx_enabled):
119+
buf = BytesIO(JSX_SOURCE)
120+
messages = [m[2] for m in extract.extract_javascript(buf, ('_', 'gettext'), [], {"jsx": jsx_enabled})]
121+
if jsx_enabled:
122+
assert messages == EXPECTED_JSX_MESSAGES
123+
else:
124+
assert messages != EXPECTED_JSX_MESSAGES

0 commit comments

Comments
 (0)