├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── gen.js ├── index.js ├── lib ├── babel-jsx.js └── helpers.js ├── package.json └── tests ├── man ├── test-out.js ├── test.js └── test.jsx ├── missing_scope.js ├── syntax.js └── syntax ├── children ├── _header.js ├── custom_first.jsx ├── js_values.jsx ├── member_first.jsx └── namespaced_first.jsx ├── props ├── _header.js ├── basic.jsx ├── namespaced.jsx ├── spread.jsx ├── spread_alone.jsx └── spread_first.jsx └── tags ├── _header.js ├── basic.jsx ├── member_expression.jsx └── namespaced.jsx /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Arthur Stolyar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jsx-ir/babel-plugin-jsx.svg?branch=master)](https://travis-ci.org/jsx-ir/babel-plugin-jsx) 2 | 3 | ## Babel Plugin for generating JSX-IR 4 | 5 | ### Overview 6 | 7 | This plugin produces [JSX-IR](https://github.com/jsx-ir/spec) output of given JSX source. Main purpose of this plugin is to be used with [```jsx-runtime```](https://github.com/jsx-ir/jsx-runtime) and one or more its [renderers](https://github.com/jsx-ir?utf8=%E2%9C%93&query=jsx-to). But, of course, if could be used separately. 8 | 9 | ### Installation 10 | 11 | ```npm install babel-plugin-jsx``` 12 | 13 | ### Usage 14 | 15 | Basic usage look like this: 16 | ```js 17 | babel.transform(code, { 18 | plugins: ['babel-plugin-jsx'], 19 | blacklist: ['react'] 20 | }); 21 | ``` 22 | or any other way described [here](http://babeljs.io/docs/advanced/plugins/#usage). 23 | 24 | ### Advanced usage (options) 25 | 26 | Because Babel does not supports direct providing options for plugins (yet), here are some tricks: 27 | 28 | First of all, you need to require "plugin-generator" which will generate for you plugin instance with specified options: 29 | ```js 30 | var jsx = require('babel-plugin-jsx/gen'); 31 | ``` 32 | 33 | Next you can generate plugin on the fly if you are using ``babel-core`` directly: 34 | ```js 35 | var jsx = require('babel-plugin-jsx/gen'); 36 | var babel = require('babel-core'); 37 | 38 | babel.transform(code, { 39 | plugins: [jsx({ 40 | ... // options goes here 41 | })], 42 | blacklist: ['react'] 43 | }); 44 | ``` 45 | Or create special file in your package and use it as a module instead: 46 | ```js 47 | // my-local-plugin.js 48 | module.exports = require('babel-plugin-jsx/gen')({ 49 | ... // options goes here 50 | }); 51 | ``` 52 | then use it in other place like ``index.js``: 53 | ```js 54 | babel.transform(code, { 55 | plugins: ['./my-local-plugin'], 56 | blacklist: ['react'] 57 | }); 58 | ``` 59 | 60 | #### Options and combinations 61 | 62 | There is some number of options, first and main option is ```captureScope```: 63 | * ```captureScope``` [boolean] - when enabled plugin looks for current scope to find same variables as JSX source tags. For example, this code ``
`` will produce ``{ tag: 'div', ... }`` when capture is disabled and ``{ tag: ['div', div], ...}`` when capture is enabled -- plugin captures variable for feature use by runtime. 64 | * ```builtins``` [Array] - only has effect when ``captureScope`` is ``true``. This options allows number of built-ins tags so plugin won't need to look for when in the scope. Usage of this options assumes that _renderer_ knows how to handle listed built-in tags. If this option is provided and used tag is not a _built-in_ and it's not in the current _scope_ when compilation error will be thrown. 65 | * ```throwOnMissing``` [boolean] - only has effect when ``captureScope`` and ``builtins`` options are used simultaneously. By default this is ``true``, setting it to ``false`` means that plugin won't throw compilation error for missed tags, instead it will produce normal _scope output_ and if variable is missing you will get an runtime error. 66 | 67 | ### Examples 68 | 69 | #### Example of input 70 | 71 | ```xml 72 |
73 | 74 |
75 | 76 |
77 |
78 |
79 | ``` 80 | 81 | ### Example of output 82 | 83 | ```js 84 | ({ 85 | tag: "div", 86 | props: { 87 | className: "box" 88 | }, 89 | children: [{ 90 | tag: "List", 91 | props: null, 92 | children: [{ 93 | tag: "div", 94 | props: { 95 | className: "list-wrap" 96 | }, 97 | children: [{ 98 | tag: "ListItem", 99 | props: _extends({ 100 | index: index 101 | }, val), 102 | children: null 103 | }] 104 | }] 105 | }] 106 | }) 107 | ``` 108 | 109 | ### License 110 | 111 | [MIT](LICENSE.md) 112 | -------------------------------------------------------------------------------- /gen.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var jsx = require('./lib/babel-jsx'); 4 | 5 | module.exports = function gen(options) { 6 | var plugin = function(babel) { 7 | return jsx.call(this, babel, options); 8 | }; 9 | 10 | plugin.gen = gen; 11 | return plugin; 12 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var gen = require('./gen'); 4 | 5 | module.exports = gen({ 6 | captureScope: true 7 | }); -------------------------------------------------------------------------------- /lib/babel-jsx.js: -------------------------------------------------------------------------------- 1 | // Based on: 2 | // https://github.com/babel/babel/blob/8b096ac7057379fd698eea3a33a2f77a8311a363/src/babel/transformation/helpers/build-react-transformer.js 3 | "use strict"; 4 | 5 | var esutils = require('esutils'); 6 | var isIdentifierNameES6 = esutils.keyword.isIdentifierNameES6; 7 | var helpers = require('./helpers'); 8 | 9 | module.exports = function(babel, options) { 10 | var t = babel.types; 11 | 12 | var captureScope = options && options.captureScope; 13 | var builtins = captureScope && options.builtins; 14 | var throwOnMissing = options.throwOnMissing !== false; 15 | var hasBuiltins = Array.isArray(builtins); 16 | 17 | return new babel.Transformer('jsx-ir', { 18 | JSXIdentifier: function (node) { 19 | if (node.name === 'this' && this.isReferenced()) { 20 | return t.thisExpression(); 21 | } else if (isIdentifierNameES6(node.name)) { 22 | node.type = 'Identifier'; 23 | } else { 24 | return t.literal(node.name); 25 | } 26 | }, 27 | JSXNamespacedName: function(node) { 28 | return t.literal(node.namespace.name + ':' + node.name.name); 29 | }, 30 | JSXMemberExpression: { 31 | exit: function(node) { 32 | node.computed = t.isLiteral(node.property); 33 | node.type = 'MemberExpression'; 34 | } 35 | }, 36 | JSXEmptyExpression: function (node) { 37 | node.type = 'Literal'; 38 | node.value = null; 39 | }, 40 | JSXExpressionContainer: function(node) { 41 | return node.expression; 42 | }, 43 | JSXAttribute: { 44 | enter: function(node) { 45 | var value = node.value; 46 | 47 | if (t.isLiteral(value) && typeof value.value === 'string') { 48 | value.value = value.value.replace(/\n\s+/g, ' '); 49 | } 50 | }, 51 | 52 | exit: function(node) { 53 | var name = node.name; 54 | var value = node.value || t.literal(true); 55 | 56 | return t.inherits(t.property('init', name, value), node); 57 | } 58 | }, 59 | JSXOpeningElement: { 60 | exit: function(node, parent, scope, file) { 61 | var props = node.attributes; 62 | 63 | if (props.length) { 64 | // props = helpers.handleProps(props, scope, file, t); 65 | props = helpers.handleSpreadProps(props, scope, file, t); 66 | } else { 67 | props = t.literal(null); 68 | } 69 | 70 | var item; 71 | var tag; 72 | var getElementObject = function(tag, fn) { 73 | var tagVal; 74 | 75 | if (fn) { 76 | tagVal = tagVal = t.arrayExpression([t.literal(tag), fn]); 77 | } else { 78 | tagVal = t.literal(tag); 79 | } 80 | 81 | return t.objectExpression([ 82 | t.property('init', t.identifier('tag'), tagVal), 83 | t.property('init', t.identifier('props'), props) 84 | ]) 85 | }; 86 | 87 | var isCapturable = captureScope && 88 | (t.isMemberExpression(node.name) || node.name.name); 89 | 90 | if (isCapturable) { 91 | var tagString; 92 | var inScope; 93 | var fnIntentifier; 94 | 95 | if (t.isMemberExpression(node.name)) { 96 | tag = helpers.readMemberExpression(node.name, t); 97 | tagString = tag.join('.'); 98 | inScope = this.scope.hasBinding(tag[0]); 99 | fnIntentifier = node.name; 100 | } else { 101 | tag = node.name; 102 | tagString = 'name' in tag ? tag.name : tag.value; 103 | inScope = 'name' in tag && this.scope.hasBinding(tagString); 104 | fnIntentifier = t.identifier(tagString); 105 | } 106 | 107 | if ((!hasBuiltins && !inScope) || helpers.checkBuiltins(builtins, tagString)) { 108 | item = getElementObject(tagString); 109 | } else if (!throwOnMissing || inScope) { 110 | item = getElementObject(tagString, fnIntentifier); 111 | } else { 112 | throw this.errorWithNode('Tag <' + tagString + '> is not a built-in and is missed from the scope'); 113 | } 114 | } else { 115 | if (t.isMemberExpression(node.name)) { 116 | tag = helpers.readMemberExpression(node.name, t); 117 | tag = tag.join('.'); 118 | } else { 119 | tag = node.name; 120 | tag = tag.name || tag.value; 121 | } 122 | 123 | item = getElementObject(tag); 124 | } 125 | 126 | return item; 127 | } 128 | }, 129 | JSXElement: { 130 | exit: function(node) { 131 | var item = node.openingElement; 132 | var children = helpers.buildChildren(node.children, t); 133 | var object; 134 | 135 | children = children.length ? t.arrayExpression(children) : t.literal(null); 136 | 137 | if (t.isCallExpression(item)) { 138 | object = item.arguments[0]; 139 | } else { 140 | object = item; 141 | } 142 | 143 | object.properties.push( 144 | t.property('init', t.identifier('children'), children) 145 | ); 146 | 147 | return t.inherits(item, node); 148 | } 149 | } 150 | }); 151 | }; -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var isIdentifierNameES6 = require('esutils').keyword.isIdentifierNameES6; 4 | 5 | function cleanChildren(child) { 6 | var lines = child.value.split(/\r\n|\n|\r/); 7 | var lastNonEmptyLine = 0; 8 | 9 | for (var i = 0; i < lines.length; i++) { 10 | if (lines[i].match(/[^ \t]/)) { 11 | lastNonEmptyLine = i; 12 | } 13 | } 14 | 15 | var str = ''; 16 | 17 | for (var i = 0; i < lines.length; i++) { 18 | var line = lines[i]; 19 | 20 | var isFirstLine = i === 0; 21 | var isLastLine = i === lines.length - 1; 22 | var isLastNonEmptyLine = i === lastNonEmptyLine; 23 | 24 | // replace rendered whitespace tabs with spaces 25 | var trimmedLine = line.replace(/\t/g, ' '); 26 | 27 | // trim whitespace touching a newline 28 | if (!isFirstLine) { 29 | trimmedLine = trimmedLine.replace(/^[ ]+/, ''); 30 | } 31 | 32 | // trim whitespace touching an endline 33 | if (!isLastLine) { 34 | trimmedLine = trimmedLine.replace(/[ ]+$/, ''); 35 | } 36 | 37 | if (trimmedLine) { 38 | if (!isLastNonEmptyLine) { 39 | trimmedLine += ' '; 40 | } 41 | 42 | str += trimmedLine; 43 | } 44 | } 45 | 46 | return str; 47 | } 48 | 49 | function buildChildren(children, t) { 50 | var elems = []; 51 | 52 | for (var i = 0; i < children.length; i++) { 53 | var child = children[i]; 54 | 55 | if (t.isLiteral(child) && typeof child.value === 'string') { 56 | var str = cleanChildren(child, t); 57 | if (str) elems.push(t.literal(str)); 58 | 59 | continue; 60 | } 61 | 62 | elems.push(child); 63 | } 64 | 65 | return elems; 66 | } 67 | 68 | function handleProps(props, scope, file, t) { 69 | // scope.push({ id: t.identifier("id"), init: t.identifier("init") }); 70 | props = props.map(function(prop) { 71 | var propName = prop.key.name || prop.key.value; 72 | 73 | if (isIdentifierNameES6(propName)) { 74 | propName = t.identifier(propName); 75 | } else { 76 | propName = t.literal(propName); 77 | } 78 | 79 | return t.property('init', propName, prop.value); 80 | }); 81 | 82 | return t.objectExpression(props); 83 | } 84 | 85 | function handleSpreadProps(props, scope, file, t) { 86 | var propsStack = []; 87 | var objectsStack = []; 88 | 89 | var pushProps = function () { 90 | if (!propsStack.length) return; 91 | 92 | objectsStack.push(t.objectExpression(propsStack)); 93 | propsStack = []; 94 | }; 95 | 96 | var makeProp = function(prop) { 97 | var propName = prop.key.name || prop.key.value; 98 | 99 | if (isIdentifierNameES6(propName)) { 100 | propName = t.identifier(propName); 101 | } else { 102 | propName = t.literal(propName); 103 | } 104 | 105 | return t.property('init', propName, prop.value); 106 | }; 107 | 108 | for (var i = 0, len = props.length; i < len; i++) { 109 | var prop = props[i]; 110 | 111 | if (t.isJSXSpreadAttribute(prop)) { 112 | pushProps(); 113 | objectsStack.push(prop.argument); 114 | } else { 115 | propsStack.push(makeProp(prop)); 116 | } 117 | } 118 | 119 | pushProps(); 120 | 121 | if (objectsStack.length === 1) { 122 | // only one object 123 | props = objectsStack[0]; 124 | } else { 125 | // looks like we have multiple objects 126 | if (!t.isObjectExpression(objectsStack[0])) { 127 | objectsStack.unshift(t.objectExpression([])); 128 | } 129 | 130 | // spread it 131 | props = t.arrayExpression( 132 | objectsStack 133 | ); 134 | } 135 | 136 | return props; 137 | } 138 | 139 | function checkBuiltins(builtins, tag) { 140 | if (!builtins) return false; 141 | 142 | if (builtins instanceof RegExp) { 143 | return builtins.test(tag); 144 | } 145 | 146 | if (Array.isArray(builtins)) { 147 | var match = false; 148 | 149 | for (var i = 0, len = builtins.length; i < len; i++) { 150 | var check = builtins[i]; 151 | 152 | if (typeof check === 'string') { 153 | match = check === tag; 154 | } else if (check instanceof RegExp) { 155 | match = check.test(tag); 156 | } 157 | 158 | if (match) return true; 159 | } 160 | } 161 | 162 | return false; 163 | } 164 | 165 | function readMemberExpression(node, t) { 166 | var tag = []; 167 | 168 | do { 169 | tag.push(node.property.name); 170 | } while (t.isMemberExpression(node = node.object)); 171 | 172 | tag.push(node.name); 173 | 174 | return tag.reverse(); 175 | } 176 | 177 | module.exports = { 178 | cleanChildren: cleanChildren, 179 | buildChildren: buildChildren, 180 | handleProps: handleProps, 181 | handleSpreadProps: handleSpreadProps, 182 | checkBuiltins: checkBuiltins, 183 | readMemberExpression: readMemberExpression 184 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-jsx", 3 | "version": "1.2.0", 4 | "description": "Plugin for Babel which generates JSX intermediate representation", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/mocha tests/" 8 | }, 9 | "author": "Arthur Stolyar ", 10 | "license": "MIT", 11 | "dependencies": { 12 | "esutils": "^2.0.2" 13 | }, 14 | "devDependencies": { 15 | "babel-core": "^5.5.5", 16 | "mocha": "^2.2.5" 17 | }, 18 | "keywords": [ 19 | "babel", 20 | "babel-plugin", 21 | "plugin", 22 | "jsx", 23 | "jsx-ir", 24 | "babel-jsx", 25 | "jsx-plugin" 26 | ], 27 | "repository": "jsx-ir/babel-plugin-jsx" 28 | } 29 | -------------------------------------------------------------------------------- /tests/man/test-out.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | var a = { 7 | zzz: 134, 8 | foo: 'baz' 9 | }; 10 | 11 | var Test = function Test() { 12 | return {}; 13 | }; 14 | 15 | var blah = { 16 | Test: { 17 | zzz: function zzz() {} 18 | } 19 | }; 20 | 21 | var meta = function meta() { 22 | console.log('out', arguments); 23 | 24 | return function () { 25 | console.log('in', arguments); 26 | }; 27 | }; 28 | 29 | exports['default'] = function (data) { 30 | ({ 31 | tag: ['Test', Test], 32 | props: [{ 33 | 'foo-bar': 'baz', 34 | b: undefined, 35 | a: undefined.test, 36 | 'data-test': '123' 37 | }, a, { 38 | blah: true, 39 | 'ns:prop': true, 40 | 'aria-role': 'button' 41 | }], 42 | children: [{ 43 | tag: 'ZZZ', 44 | props: { 45 | a: 'b' 46 | }, 47 | children: null 48 | }, 'zzZzzzZ -- ', data.text, ' 123', { 49 | tag: 'blah:Test', 50 | props: null, 51 | children: null 52 | }, { 53 | tag: ['blah.Test.zzz', blah.Test.zzz], 54 | props: null, 55 | children: null 56 | }, { 57 | tag: 'blah-blah', 58 | props: null, 59 | children: null 60 | }] 61 | }); 62 | }; 63 | 64 | module.exports = exports['default']; -------------------------------------------------------------------------------- /tests/man/test.js: -------------------------------------------------------------------------------- 1 | var babel = require('babel-core'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | 6 | var jsx = require('../../gen'); 7 | var code = fs.readFileSync(path.join(__dirname, 'test.jsx')); 8 | 9 | var result = babel.transform(code, { 10 | plugins: [jsx({ 11 | captureScope: true 12 | })], 13 | blacklist: ['react'] 14 | }); 15 | 16 | fs.writeFileSync(path.join(__dirname, 'test-out.js'), result.code); 17 | console.log(result.code); -------------------------------------------------------------------------------- /tests/man/test.jsx: -------------------------------------------------------------------------------- 1 | var a = { 2 | zzz: 134, 3 | foo: 'baz' 4 | }; 5 | 6 | var Test = function() { 7 | return {}; 8 | }; 9 | 10 | var blah = { 11 | Test: { 12 | zzz: function() {} 13 | } 14 | }; 15 | 16 | var meta = function() { 17 | console.log('out', arguments); 18 | 19 | return function() { 20 | console.log('in', arguments); 21 | }; 22 | }; 23 | 24 | export default (data) => { 25 | 26 | 27 | zzZzzzZ -- {data.text} 123 28 | 29 | 30 | 31 | 32 | } -------------------------------------------------------------------------------- /tests/missing_scope.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var babel = require('babel-core'); 4 | var jsx = require('../gen'); 5 | var assert = require('assert'); 6 | 7 | var pluginEmptyBuiltins = jsx({ 8 | captureScope: true, 9 | builtins: [], 10 | throwOnMissing: true 11 | }); 12 | 13 | describe('missing_scope', function() { 14 | it('should throw', function() { 15 | assert.throws(function() { 16 | var result = babel.transform(code, { 17 | plugins: [pluginEmptyBuiltins], 18 | blacklist: ['react'] 19 | }); 20 | }, function(err) { 21 | if ( 22 | err.message.indexOf('') !== -1 23 | ) { 24 | return true; 25 | } 26 | }, 'Not scope error'); 27 | }); 28 | }); 29 | 30 | var code = 'export default () => \n' + 31 | ''; -------------------------------------------------------------------------------- /tests/syntax.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var assert = require('assert'); 6 | var babel = require('babel-core'); 7 | var jsx = require('../gen'); 8 | var Module = require('module'); 9 | 10 | var testsFolder = path.join(__dirname, 'syntax'); 11 | var dir = fs.readdirSync(testsFolder); 12 | 13 | var pluginSimple = jsx({ 14 | captureScope: false, 15 | throwOnMissing: false 16 | }); 17 | 18 | var pluginScoped = jsx({ 19 | captureScope: true, 20 | throwOnMissing: false 21 | }); 22 | 23 | describe('syntax', function() { 24 | dir.forEach(function(dirName) { 25 | var header = require(path.join(testsFolder, dirName, '_header.js')); 26 | 27 | if (header.builtins) { 28 | var pluginBuiltins = jsx({ 29 | captureScope: true, 30 | builtins: header.builtins, 31 | throwOnMissing: false 32 | }) 33 | } 34 | 35 | describe(dirName + ': ' + header.title, function() { 36 | header.files.forEach(function(fileName) { 37 | describe(fileName, function() { 38 | var p = path.join(testsFolder, dirName, fileName); 39 | var file = fs.readFileSync(p, 'utf-8'); 40 | testFile(file, p); 41 | }); 42 | }); 43 | }); 44 | 45 | function testFile(file, filePath) { 46 | var map = { 47 | simple: pluginSimple, 48 | scoped: pluginScoped, 49 | builtins: pluginBuiltins 50 | }; 51 | 52 | Object.keys(map).forEach(function(key) { 53 | var plugin = map[key]; 54 | if (!plugin) return; 55 | 56 | var result = babel.transform(file, { 57 | plugins: [plugin], 58 | blacklist: ['react'] 59 | }); 60 | 61 | var mod = requireString(result.code, filePath); 62 | 63 | if (mod[key]) { 64 | it(key, mod[key]); 65 | } 66 | }); 67 | } 68 | }); 69 | }); 70 | 71 | function requireString(src, file) { 72 | var newModule = new Module(); 73 | 74 | newModule._compile(src, file); 75 | return newModule.exports; 76 | } 77 | -------------------------------------------------------------------------------- /tests/syntax/children/_header.js: -------------------------------------------------------------------------------- 1 | exports.title = '' 2 | exports.builtins = ['div', 'span']; 3 | exports.files = ['custom_first.jsx', 'member_first.jsx', 'namespaced_first.jsx', 'js_values.jsx']; 4 | -------------------------------------------------------------------------------- /tests/syntax/children/custom_first.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | function div() { 4 | return 5 | } 6 | 7 | export var input = () => { 8 | return
9 | 10 | 11 | 12 | 13 |
; 14 | }; 15 | 16 | export var simple = () => { 17 | return assert.deepEqual(input(), { 18 | tag: 'div', 19 | props: null, 20 | children: [{ 21 | tag: 'span', 22 | children: null, 23 | props: null 24 | }, { 25 | tag: 'Custom', 26 | children: null, 27 | props: null 28 | }, { 29 | tag: 'Member.Tag', 30 | children: null, 31 | props: null 32 | }, { 33 | tag: 'ns:Tag', 34 | children: null, 35 | props: null 36 | }] 37 | }); 38 | }; 39 | 40 | export var scoped = () => { 41 | assert.deepEqual(input(), { 42 | tag: ['div', div], 43 | props: null, 44 | children: [{ 45 | tag: 'span', 46 | children: null, 47 | props: null 48 | }, { 49 | tag: 'Custom', 50 | children: null, 51 | props: null 52 | }, { 53 | tag: 'Member.Tag', 54 | children: null, 55 | props: null 56 | }, { 57 | tag: 'ns:Tag', 58 | children: null, 59 | props: null 60 | }] 61 | }); 62 | }; 63 | 64 | export var builtins = () => { 65 | assert.throws(function() { 66 | input(); 67 | }, /Custom is not defined/); 68 | }; -------------------------------------------------------------------------------- /tests/syntax/children/js_values.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | function div() { 4 | return 5 | } 6 | 7 | let fnVal = () => {}; 8 | 9 | export var input = () => { 10 | return
11 | { true } 12 | { 1 } 13 | { 'string' } 14 | { { key: 'value' } } 15 | { [1, 2, 3] } 16 | { fnVal } 17 |
; 18 | }; 19 | 20 | export var simple = () => { 21 | return assert.deepEqual(input(), { 22 | tag: 'div', 23 | props: null, 24 | children: [ 25 | true, 26 | 1, 27 | 'string', 28 | { key: 'value' }, 29 | [1, 2, 3], 30 | fnVal 31 | ] 32 | }); 33 | }; -------------------------------------------------------------------------------- /tests/syntax/children/member_first.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | function div() { 4 | return 5 | } 6 | 7 | export var input = () => { 8 | return
9 | 10 | 11 | 12 | 13 |
; 14 | }; 15 | 16 | export var simple = () => { 17 | return assert.deepEqual(input(), { 18 | tag: 'div', 19 | props: null, 20 | children: [{ 21 | tag: 'span', 22 | children: null, 23 | props: null 24 | }, { 25 | tag: 'Member.Tag', 26 | children: null, 27 | props: null 28 | }, { 29 | tag: 'ns:Tag', 30 | children: null, 31 | props: null 32 | }, { 33 | tag: 'Custom', 34 | children: null, 35 | props: null 36 | }] 37 | }); 38 | }; 39 | 40 | export var scoped = () => { 41 | assert.deepEqual(input(), { 42 | tag: ['div', div], 43 | props: null, 44 | children: [{ 45 | tag: 'span', 46 | children: null, 47 | props: null 48 | }, { 49 | tag: 'Member.Tag', 50 | children: null, 51 | props: null 52 | }, { 53 | tag: 'ns:Tag', 54 | children: null, 55 | props: null 56 | }, { 57 | tag: 'Custom', 58 | children: null, 59 | props: null 60 | }] 61 | }); 62 | }; 63 | 64 | export var builtins = () => { 65 | assert.throws(function() { 66 | input(); 67 | }, /Member is not defined/); 68 | }; -------------------------------------------------------------------------------- /tests/syntax/children/namespaced_first.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | function div() { 4 | return 5 | } 6 | 7 | export var input = () => { 8 | return
9 | 10 | 11 | 12 | 13 |
; 14 | }; 15 | 16 | export var simple = () => { 17 | return assert.deepEqual(input(), { 18 | tag: 'div', 19 | props: null, 20 | children: [{ 21 | tag: 'span', 22 | children: null, 23 | props: null 24 | }, { 25 | tag: 'ns:Tag', 26 | children: null, 27 | props: null 28 | }, { 29 | tag: 'Member.Tag', 30 | children: null, 31 | props: null 32 | }, { 33 | tag: 'Custom', 34 | children: null, 35 | props: null 36 | }] 37 | }); 38 | }; 39 | 40 | export var scoped = () => { 41 | assert.deepEqual(input(), { 42 | tag: ['div', div], 43 | props: null, 44 | children: [{ 45 | tag: 'span', 46 | children: null, 47 | props: null 48 | }, { 49 | tag: 'ns:Tag', 50 | children: null, 51 | props: null 52 | }, { 53 | tag: 'Member.Tag', 54 | children: null, 55 | props: null 56 | }, { 57 | tag: 'Custom', 58 | children: null, 59 | props: null 60 | }] 61 | }); 62 | }; 63 | 64 | export var builtins = () => { 65 | assert.throws(function() { 66 | input(); 67 | }, /Member is not defined/); 68 | }; -------------------------------------------------------------------------------- /tests/syntax/props/_header.js: -------------------------------------------------------------------------------- 1 | exports.title = '' 2 | exports.builtins = ['div', 'span']; 3 | exports.files = [ 4 | 'basic.jsx', 5 | 'namespaced.jsx', 6 | 'spread.jsx', 7 | 'spread_first.jsx', 8 | 'spread_alone.jsx' 9 | ]; -------------------------------------------------------------------------------- /tests/syntax/props/basic.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | export var input = () => { 4 | return
; 12 | }; 13 | 14 | export var simple = () => { 15 | assert.deepEqual(input(), { 16 | tag: 'div', 17 | props: { 18 | string: 'string', 19 | boolean: true, 20 | 'dashed-prop': 'dashed', 21 | bool_val: true, 22 | str_val: 'string', 23 | num_val: 1 24 | }, 25 | children: null 26 | }); 27 | }; -------------------------------------------------------------------------------- /tests/syntax/props/namespaced.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | export var input = () => { 4 | return
; 12 | }; 13 | 14 | export var simple = () => { 15 | assert.deepEqual(input(), { 16 | tag: 'div', 17 | props: { 18 | 'xml:string': 'string', 19 | 'xml:boolean': true, 20 | 'xml:dashed-prop': 'dashed', 21 | 'xml:bool_val': true, 22 | 'xml:str_val': 'string', 23 | 'xml:num_val': 1 24 | }, 25 | children: null 26 | }); 27 | }; -------------------------------------------------------------------------------- /tests/syntax/props/spread.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | let override = { 4 | string: false, 5 | boolean: false, 6 | 'dashed-prop': false, 7 | bool_val: false, 8 | str_val: false, 9 | num_val: false 10 | }; 11 | 12 | export var input = () => { 13 | return
; 23 | }; 24 | 25 | export var simple = () => { 26 | return assert.deepEqual(input(), { 27 | tag: 'div', 28 | props: [{ 29 | string: 'string', 30 | boolean: true, 31 | 'dashed-prop': 'dashed', 32 | bool_val: true, 33 | str_val: 'string', 34 | num_val: 1 35 | }, override], 36 | children: null 37 | }); 38 | }; -------------------------------------------------------------------------------- /tests/syntax/props/spread_alone.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | let override = { 4 | string: false, 5 | boolean: false, 6 | 'dashed-prop': false, 7 | bool_val: false, 8 | str_val: false, 9 | num_val: false 10 | }; 11 | 12 | export var input = () => { 13 | return
; 14 | }; 15 | 16 | export var simple = () => { 17 | return assert.deepEqual(input(), { 18 | tag: 'div', 19 | props: override, 20 | children: null 21 | }); 22 | }; -------------------------------------------------------------------------------- /tests/syntax/props/spread_first.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | let override = { 4 | string: false, 5 | boolean: false, 6 | 'dashed-prop': false, 7 | bool_val: false, 8 | str_val: false, 9 | num_val: false 10 | }; 11 | 12 | export var input = () => { 13 | return
; 21 | }; 22 | 23 | export var simple = () => { 24 | return assert.deepEqual(input(), { 25 | tag: 'div', 26 | props: [{}, override, { 27 | string: 'string', 28 | boolean: true, 29 | 'dashed-prop': 'dashed', 30 | bool_val: true, 31 | str_val: 'string', 32 | num_val: 1 33 | }], 34 | children: null 35 | }); 36 | }; -------------------------------------------------------------------------------- /tests/syntax/tags/_header.js: -------------------------------------------------------------------------------- 1 | exports.title = '' 2 | exports.builtins = ['div', 'span']; 3 | exports.files = ['basic.jsx', 'member_expression.jsx', 'namespaced.jsx']; 4 | -------------------------------------------------------------------------------- /tests/syntax/tags/basic.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | function div() { 4 | return 5 | } 6 | 7 | export var input = () => { 8 | return
; 9 | }; 10 | 11 | export var simple = () => { 12 | assert.deepEqual(input(), { 13 | tag: 'div', 14 | props: null, 15 | children: null 16 | }); 17 | }; 18 | 19 | export var scoped = () => { 20 | assert.deepEqual(input(), { 21 | tag: ['div', div], 22 | props: null, 23 | children: null 24 | }); 25 | }; 26 | 27 | export var builtins = () => { 28 | assert.deepEqual(input(), { 29 | tag: 'div', 30 | props: null, 31 | children: null 32 | }); 33 | }; -------------------------------------------------------------------------------- /tests/syntax/tags/member_expression.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | let UI = { 4 | Views: { 5 | Custom: function CustomView() { 6 | return
CustomView
7 | } 8 | } 9 | }; 10 | 11 | export var input = () => { 12 | return 13 |
DIV
14 |
; 15 | }; 16 | 17 | export var simple = () => { 18 | assert.deepEqual(input(), { 19 | tag: 'UI.Views.Custom', 20 | props: null, 21 | children: [{ 22 | tag: 'div', 23 | props: null, 24 | children: ['DIV'] 25 | }] 26 | }); 27 | }; 28 | 29 | export var scoped = () => { 30 | assert.deepEqual(input(), { 31 | tag: ['UI.Views.Custom', UI.Views.Custom], 32 | props: null, 33 | children: [{ 34 | tag: 'div', 35 | children: ['DIV'], 36 | props: null 37 | }] 38 | }); 39 | }; 40 | 41 | export var builtins = () => { 42 | assert.deepEqual(input(), { 43 | tag: ['UI.Views.Custom', UI.Views.Custom], 44 | props: null, 45 | children: [{ 46 | tag: 'div', 47 | children: ['DIV'], 48 | props: null 49 | }] 50 | }); 51 | }; -------------------------------------------------------------------------------- /tests/syntax/tags/namespaced.jsx: -------------------------------------------------------------------------------- 1 | let assert = require('assert'); 2 | 3 | function div() { 4 | return 5 | } 6 | 7 | export var input = () => { 8 | return
9 | 10 |
; 11 | }; 12 | 13 | export var simple = () => { 14 | assert.deepEqual(input(), { 15 | tag: 'div', 16 | props: null, 17 | children: [{ 18 | tag: 'html:div', 19 | children: null, 20 | props: null 21 | }] 22 | }); 23 | }; 24 | 25 | export var scoped = () => { 26 | assert.deepEqual(input(), { 27 | tag: ['div', div], 28 | props: null, 29 | children: [{ 30 | tag: 'html:div', 31 | children: null, 32 | props: null 33 | }] 34 | }); 35 | }; 36 | 37 | export var builtins = () => { 38 | assert.deepEqual(input(), { 39 | tag: 'div', 40 | props: null, 41 | children: [{ 42 | tag: 'html:div', 43 | children: null, 44 | props: null 45 | }] 46 | }); 47 | }; --------------------------------------------------------------------------------