Skip to content

Commit 8fee76c

Browse files
committed
JavaScript: Add JSX/E4X tag lexing
Fixes #280
1 parent d4cafa4 commit 8fee76c

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

babel/messages/extract.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ def extract_javascript(fileobj, keywords, comment_tags, options):
506506
:param comment_tags: a list of translator tags to search for and include
507507
in the results
508508
:param options: a dictionary of additional options (optional)
509+
Supported options are:
510+
* `jsx` -- set to false to disable JSX/E4X support.
509511
"""
510512
from babel.messages.jslexer import tokenize, unquote_string
511513
funcname = message_lineno = None
@@ -517,7 +519,7 @@ def extract_javascript(fileobj, keywords, comment_tags, options):
517519
last_token = None
518520
call_stack = -1
519521

520-
for token in tokenize(fileobj.read().decode(encoding)):
522+
for token in tokenize(fileobj.read().decode(encoding), jsx=options.get("jsx", True)):
521523
if token.type == 'operator' and token.value == '(':
522524
if funcname:
523525
message_lineno = token.lineno

babel/messages/jslexer.py

+22-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
Token = namedtuple('Token', 'type value lineno')
3333

34-
rules = [
34+
_rules = [
3535
(None, re.compile(r'\s+(?u)')),
3636
(None, re.compile(r'<!--.*')),
3737
('linecomment', re.compile(r'//.*')),
@@ -43,6 +43,7 @@
4343
([eE][-+]?\d+)? |
4444
(0x[a-fA-F0-9]+)
4545
)''')),
46+
('jsx_tag', re.compile(r'<(?:/?)\w+.+?>', re.I)), # May be mangled in `get_rules`
4647
('operator', re.compile(r'(%s)' % '|'.join(map(re.escape, operators)))),
4748
('string', re.compile(r'''(?xs)(
4849
'(?:[^'\\]*(?:\\.[^'\\]*)*)' |
@@ -51,6 +52,20 @@
5152
]
5253

5354

55+
def get_rules(jsx):
56+
"""
57+
Get a tokenization rule list given the passed syntax options.
58+
59+
Internal to this module.
60+
"""
61+
rules = []
62+
for token_type, rule in _rules:
63+
if not jsx and token_type and 'jsx' in token_type:
64+
continue
65+
rules.append((token_type, rule))
66+
return rules
67+
68+
5469
def indicates_division(token):
5570
"""A helper function that helps the tokenizer to decide if the current
5671
token may be followed by a division operator.
@@ -116,13 +131,17 @@ def unquote_string(string):
116131
return u''.join(result)
117132

118133

119-
def tokenize(source):
120-
"""Tokenize a JavaScript source. Returns a generator of tokens.
134+
def tokenize(source, jsx=True):
135+
"""
136+
Tokenize JavaScript/JSX source. Returns a generator of tokens.
137+
138+
:param jsx: Enable (limited) JSX parsing.
121139
"""
122140
may_divide = False
123141
pos = 0
124142
lineno = 1
125143
end = len(source)
144+
rules = get_rules(jsx=jsx)
126145

127146
while pos < end:
128147
# handle regular rules first

tests/messages/test_js_extract.py

+25
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)