diff --git a/package-lock.json b/package-lock.json index 4fd3b46c..290f10a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,22 @@ { "name": "i18next-parser", - "version": "1.0.0-beta4", + "version": "1.0.0-beta8", "lockfileVersion": 1, "requires": true, "dependencies": { + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==" + }, + "acorn-jsx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "requires": { + "acorn": "5.5.3" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -19,7 +32,7 @@ "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "dev": true, "optional": true, "requires": { @@ -38,7 +51,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "requires": { "sprintf-js": "1.0.3" } @@ -56,7 +69,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true, "optional": true }, @@ -774,7 +787,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -795,13 +808,13 @@ "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", "dev": true }, "browserslist": { "version": "2.11.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "integrity": "sha1-/jYWeu0bvN5IJ+v+cTR6LMcLmbI=", "dev": true, "requires": { "caniuse-lite": "1.0.30000814", @@ -925,7 +938,7 @@ "colors": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.1.tgz", - "integrity": "sha512-s8+wktIuDSLffCywiwSxQOMqtPxML11a/dtHE17tMn4B1MSWw/C22EKf7M2KGUBcDaVFEGT+S8N02geDXeuNKg==" + "integrity": "sha1-9KPTApdqrwQjVroa3jsaLGLZ15Q=" }, "commander": { "version": "2.9.0", @@ -978,7 +991,7 @@ "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", "dev": true, "requires": { "type-detect": "4.0.8" @@ -1005,13 +1018,13 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", "dev": true }, "duplexify": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", - "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", + "integrity": "sha1-S7RsF5bqvr7sTKmi5muAjLej2LQ=", "requires": { "end-of-stream": "1.4.1", "inherits": "2.0.3", @@ -1036,7 +1049,7 @@ "eol": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", - "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==" + "integrity": "sha1-9wGRL1BAdL41xhF6XEreSc1Ues0=" }, "escape-string-regexp": { "version": "1.0.5", @@ -1158,7 +1171,7 @@ "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", "dev": true }, "fs.realpath": { @@ -2084,7 +2097,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -2177,7 +2190,7 @@ "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=", "dev": true }, "gulp-sort": { @@ -2241,7 +2254,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "dev": true, "requires": { "loose-envify": "1.3.1" @@ -2380,7 +2393,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=" }, "isarray": { "version": "1.0.0", @@ -2510,7 +2523,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "1.1.11" } @@ -2744,12 +2757,12 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", "requires": { "end-of-stream": "1.4.1", "once": "1.4.0" @@ -2758,7 +2771,7 @@ "pumpify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz", - "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", + "integrity": "sha1-gLfF334kFT0D8OesigWl0Gi9B/s=", "requires": { "duplexify": "3.5.4", "inherits": "2.0.3", @@ -2768,7 +2781,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "dev": true, "optional": true, "requires": { @@ -2813,7 +2826,7 @@ "readable-stream": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", - "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "integrity": "sha1-tPhQA6k4y7bsvOKhJPsQEr0ag40=", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -2863,7 +2876,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "optional": true, "requires": { @@ -2907,7 +2920,7 @@ "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "integrity": "sha1-wr8eN3Ug0yT2I4kuM8EMrCwlK1M=", "requires": { "is-buffer": "1.1.6", "is-utf8": "0.2.1" @@ -2966,12 +2979,12 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=", "dev": true }, "set-immediate-shim": { @@ -3015,7 +3028,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "requires": { "safe-buffer": "5.1.1" } @@ -3149,7 +3162,7 @@ "vinyl-fs": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.2.tgz", - "integrity": "sha512-AUSFda1OukBwuLPBTbyuO4IRWgfXmqC4UTW0f8xrCa8Hkv9oyIU+NSqBlgfOLZRoUt7cHdo75hKQghCywpIyIw==", + "integrity": "sha1-G4YliEQ4P1dYH8qsCB/gnvbW11I=", "requires": { "fs-mkdirp-stream": "1.0.0", "glob-stream": "6.1.0", @@ -3197,7 +3210,7 @@ "yamljs": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "integrity": "sha1-3AYL8mdEezn3ME6bK/votafdsDs=", "requires": { "argparse": "1.0.10", "glob": "7.1.2" diff --git a/package.json b/package.json index b3dc27b2..0128e48c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "url": "https://github.com/i18next/i18next-parser" }, "dependencies": { + "acorn-jsx": "^4.1.1", "colors": "~1.2.0-rc0", "commander": "~2.9.0", "concat-stream": "~1.6.0", diff --git a/src/helpers.js b/src/helpers.js index bc957132..a9ab40b7 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -105,9 +105,16 @@ function populateHash(source, target = {}) { return target } +class ParsingError extends Error { + constructor(message) { + super(message); + this.name = "ParsingError"; + } +} export { dotPathToHash, mergeHashes, - populateHash + populateHash, + ParsingError } diff --git a/src/lexers/jsx-lexer.js b/src/lexers/jsx-lexer.js index a342966b..add003fe 100644 --- a/src/lexers/jsx-lexer.js +++ b/src/lexers/jsx-lexer.js @@ -1,4 +1,8 @@ +import * as acorn from 'acorn-jsx'; +import assert from 'assert' import HTMLLexer from './html-lexer' +import BaseLexer from './base-lexer'; +import { ParsingError } from '../helpers'; export default class JsxLexer extends HTMLLexer { constructor(options = {}) { @@ -44,7 +48,7 @@ export default class JsxLexer extends HTMLLexer { const key = attrs.keys if (matches[3] && !attrs.options.defaultValue) { - attrs.options.defaultValue = matches[3].trim() + attrs.options.defaultValue = this.eraseTags(matches[0]).replace(/\s+/g, ' ') } if (key) { @@ -54,4 +58,54 @@ export default class JsxLexer extends HTMLLexer { return this.keys } + + /** + * Recursively convert html tags and js injections to tags with the child index in it + * + * @param {string} string + * + * @returns string + */ + eraseTags(string) { + const acornAst = acorn.parse(string, {plugins: {jsx: true}}); + const acornTransAst = acornAst.body[0].expression; + const children = this.parseAcornPayload(acornTransAst.children, string); + + const elemsToString = children => children.map((child, index) => { + switch(child.type) { + case 'text': return child.content; + case 'js': return `<${index}>${child.content}`; + case 'tag': return `<${index}>${elemsToString(child.children)}`; + default: throw new ParsingError('Unknown parsed content: ' + child.type); + } + }).join(''); + + return elemsToString(children); + } + + /** + * Simplify the bulky AST given by Acorn + * @param {*} children An array of elements contained inside an html tag + * @param {string} originalString The original string being parsed + */ + parseAcornPayload(children, originalString) { + return children.map(child => { + switch (child.type) { + case 'JSXText': return { + type: 'text', + content: child.value.replace(/^(?:\s*(\n|\r)\s*)?(.*)(?:\s*(\n|\r)\s*)?$/, '$2') + }; + case 'JSXElement': return { + type: 'tag', + children: this.parseAcornPayload(child.children, originalString) + }; + case 'JSXExpressionContainer': return { + type: 'js', + content: originalString.slice(child.start, child.end) + }; + default: throw new ParsingError("Unknown ast element when parsing jsx: " + child.type) + } + // Remove empty text elements + }).filter(child => child.type !== 'text' || child.content); + } } diff --git a/test/lexers/jsx-lexer.test.js b/test/lexers/jsx-lexer.test.js index 8db9257c..a1c147f3 100644 --- a/test/lexers/jsx-lexer.test.js +++ b/test/lexers/jsx-lexer.test.js @@ -31,5 +31,12 @@ describe('JsxLexer', () => { ]) done() }) + + it('erases tags from content', done => { + const Lexer = new JsxLexer() + const content = 'a"}>cz{d}
'; + assert.equal(Lexer.eraseTags(content), 'a<1>c<1>z<2>{d}<3>') + done() + }) }) }) diff --git a/test/parser.test.js b/test/parser.test.js index 917c18d6..357868d4 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -183,7 +183,9 @@ describe('parser', () => { first: '', second: '', third: { - first: 'Hello {{name}}, you have {{count}} unread message. Go to messages.' + first: 'Hello <1><0>{{name}}, you have <3>{{count}} unread message. <5>Go to messages.', + second: ' <1>Hello, this shouldn\'t be trimmed.', + third: '<0>Hello,this should be trimmed.<2> and this shoudln\'t' }, fourth: '', fifth: '', @@ -572,7 +574,9 @@ describe('parser', () => { first: '', second: '', third: { - first: 'Hello {{name}}, you have {{count}} unread message. Go to messages.' + first: 'Hello <1><0>{{name}}, you have <3>{{count}} unread message. <5>Go to messages.', + second: ' <1>Hello, this shouldn\'t be trimmed.', + third: '<0>Hello,this should be trimmed.<2> and this shoudln\'t' }, fourth: '', fifth: '', diff --git a/test/templating/react.jsx b/test/templating/react.jsx index d37c4dc4..ac8fe65e 100644 --- a/test/templating/react.jsx +++ b/test/templating/react.jsx @@ -21,9 +21,15 @@ class Test extends React.Component {

{t('first')}

- Hello {{name}}, you have {{count}} unread message. Go to messages. + Hello {{name}}, you have {{count}} unread message. Go to messages. + Hello, this shouldn't be trimmed. + + Hello, + this should be trimmed. + and this shoudln't + ) }