diff --git a/package.json b/package.json
index 5d413ca..e70ad26 100644
--- a/package.json
+++ b/package.json
@@ -49,8 +49,10 @@
"tasks"
],
"dependencies": {
- "acorn-jsx": "^4.1.1",
- "acorn-jsx-walk": "^1.0.1",
+ "acorn": "^6.0.4",
+ "acorn-jsx": "^5.0.1",
+ "acorn-stage3": "^1.0.0",
+ "acorn-walk": "^6.1.1",
"chalk": "^2.4.1",
"clone-deep": "^4.0.0",
"commander": "^2.15.1",
diff --git a/src/acorn-jsx-walk.js b/src/acorn-jsx-walk.js
new file mode 100644
index 0000000..dec904c
--- /dev/null
+++ b/src/acorn-jsx-walk.js
@@ -0,0 +1,62 @@
+// Originally from: https://github.com/sderosiaux/acorn-jsx-walk
+
+import { Parser } from 'acorn';
+import { simple as walk, base } from 'acorn-walk';
+import jsx from 'acorn-jsx';
+import stage3 from 'acorn-stage3';
+
+//
+// Extends acorn walk with JSX elements
+//
+
+// See: https://github.com/RReverser/acorn-jsx/issues/23#issuecomment-403753801
+Object.assign(base, {
+ FieldDefinition(node, state, callback) {
+ if (node.value !== null) {
+ callback(node.value, state);
+ }
+ },
+
+ JSXAttribute(node, state, callback) {
+ if (node.value !== null) {
+ callback(node.value, state);
+ }
+ },
+
+ JSXElement(node, state, callback) {
+ node.openingElement.attributes.forEach(attribute => {
+ callback(attribute, state);
+ });
+ node.children.forEach(node => {
+ callback(node, state);
+ });
+ },
+
+ JSXEmptyExpression(node, state, callback) {
+ // Comments. Just ignore.
+ },
+
+ JSXExpressionContainer(node, state, callback) {
+ callback(node.expression, state);
+ },
+
+ JSXFragment(node, state, callback) {
+ node.children.forEach(node => {
+ callback(node, state);
+ });
+ },
+
+ JSXSpreadAttribute(node, state, callback) {
+ callback(node.argument, state);
+ },
+
+ JSXText() {}
+});
+
+export default (source, options) => {
+ const ast = Parser.extend(stage3, jsx()).parse(source, {
+ sourceType: 'module',
+ ecmaVersion: 10,
+ });
+ walk(ast, options || {});
+};
diff --git a/src/parser.js b/src/parser.js
index 4230c4d..ad0b644 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -1,7 +1,6 @@
/* eslint no-console: 0 */
/* eslint no-eval: 0 */
import fs from 'fs';
-import jsxwalk from 'acorn-jsx-walk';
import chalk from 'chalk';
import cloneDeep from 'clone-deep';
import deepMerge from 'deepmerge';
@@ -10,6 +9,7 @@ import { parse } from 'esprima';
import _ from 'lodash';
import parse5 from 'parse5';
import sortObject from 'sortobject';
+import jsxwalk from './acorn-jsx-walk';
import flattenObjectKeys from './flatten-object-keys';
import omitEmptyObject from './omit-empty-object';
import nodesToString from './nodes-to-string';
diff --git a/test/fixtures/modules/index.js b/test/fixtures/modules/index.js
index 08011ec..f47e038 100644
--- a/test/fixtures/modules/index.js
+++ b/test/fixtures/modules/index.js
@@ -10,7 +10,7 @@ var msg = [
_t('YouTube has more than {{count}} billion users.', {count: 1}),
_t('You have {{count}} messages.', {
count: 10
- });
+ }),
].join('\n');
console.log(msg);
diff --git a/test/fixtures/trans-acorn-broken.jsx b/test/fixtures/trans-acorn-broken.jsx
new file mode 100644
index 0000000..5a336e0
--- /dev/null
+++ b/test/fixtures/trans-acorn-broken.jsx
@@ -0,0 +1,11 @@
+import { Fragment } from 'react';
+
+// Provoke an acorn parsing error by inserting a `async` keyword in a wrong place. This should lead
+// to no translateions being extracted from this file.
+const async Component = () => (
+
+ Broken
+
+);
+
+export default Component;
diff --git a/test/fixtures/trans-acorn.jsx b/test/fixtures/trans-acorn.jsx
new file mode 100644
index 0000000..48e9310
--- /dev/null
+++ b/test/fixtures/trans-acorn.jsx
@@ -0,0 +1,37 @@
+import { Fragment } from 'react';
+
+class Component extends React.Component {
+ // noop just to see if acorn can parse this
+ state = {
+ };
+
+ static async onClick(
+ a,
+ b,
+ ...args
+ ) {
+ console.log(a, b, ...args);
+ // noop just to see if acorn can parse this
+ }
+
+ render() {
+ // This does not work yet.
+ const spreadProps = {
+ i18nKey: 'spread',
+ };
+
+ return (
+
+ {
+ // Empty expression should not fail
+ }
+ <>
+ Simple i18nKey
+ Spread i18nKey
+ >
+
+ );
+ }
+}
+
+export default Component;
diff --git a/test/fixtures/trans.jsx b/test/fixtures/trans.jsx
index 0859944..6a9d68d 100644
--- a/test/fixtures/trans.jsx
+++ b/test/fixtures/trans.jsx
@@ -2,6 +2,9 @@ import { Fragment } from 'react';
const Component = () => (
+ {
+ // Empty expression should not fail
+ }
Use double quotes for the i18nKey attribute
Use single quote for the i18nKey attribute
diff --git a/test/jsx-parser.js b/test/jsx-parser.js
index ac76d78..f53e3ad 100644
--- a/test/jsx-parser.js
+++ b/test/jsx-parser.js
@@ -1,14 +1,13 @@
import { test } from 'tap';
-import { parse } from 'acorn-jsx';
+import { Parser } from 'acorn';
+import jsx from 'acorn-jsx';
import ensureArray from 'ensure-array';
import _get from 'lodash/get';
import nodesToString from '../src/nodes-to-string';
const jsxToString = (code) => {
try {
- const ast = parse(`${code}`, {
- plugins: { jsx: true }
- });
+ const ast = Parser.extend(jsx()).parse(`${code}`);
const nodes = ensureArray(_get(ast, 'body[0].expression.children'));
if (nodes.length === 0) {
diff --git a/test/parser.js b/test/parser.js
index 37e1b6f..e8ec5fc 100644
--- a/test/parser.js
+++ b/test/parser.js
@@ -254,6 +254,53 @@ test('Parse wrapped Trans components', (t) => {
t.end();
});
+test('Parse Trans components with modern acorn features', (t) => {
+ const parser = new Parser({
+ lngs: ['en'],
+ trans: {
+ fallbackKey: true
+ },
+ nsSeparator: false,
+ keySeparator: '.', // Specify the keySeparator for this test to make sure the fallbackKey won't be separated
+ fallbackLng: 'en'
+ });
+
+ const content = fs.readFileSync(path.resolve(__dirname, 'fixtures/trans-acorn.jsx'), 'utf-8');
+ parser.parseTransFromString(content);
+ t.same(parser.get(), {
+ en: {
+ translation: {
+ // Passing keys to via object spread is not yet supported:
+ 'Spread i18nKey': 'Spread i18nKey',
+ // 'spread': 'Spread i18nKey', // this would be expected.
+ 'simple': 'Simple i18nKey'
+ }
+ }
+ });
+ t.end();
+});
+
+test('Parse Trans components should fail with broken syntax', (t) => {
+ const parser = new Parser({
+ lngs: ['en'],
+ trans: {
+ fallbackKey: true
+ },
+ nsSeparator: false,
+ keySeparator: '.', // Specify the keySeparator for this test to make sure the fallbackKey won't be separated
+ fallbackLng: 'en'
+ });
+
+ const content = fs.readFileSync(path.resolve(__dirname, 'fixtures/trans-acorn-broken.jsx'), 'utf-8');
+ parser.parseTransFromString(content);
+ t.same(parser.get(), {
+ en: {
+ translation: {}
+ }
+ });
+ t.end();
+});
+
test('Parse HTML attribute', (t) => {
test('parseAttrFromString(content)', (t) => {
const parser = new Parser({