Compare commits

..

23 Commits

Author SHA1 Message Date
idok becfc2b789 0.6.3 2018-05-08 10:21:19 +03:00
idok 09983e7d47 move nyc to dev dep 2018-05-08 10:21:01 +03:00
idok a1c918c238 0.6.2 2018-05-07 18:42:11 +03:00
idok 140ede94c7 update dependencies, smaller package with package.files 2018-05-07 18:20:40 +03:00
snyk-bot c5df6cce72 fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:atob:20180429

Latest report for wix/react-templates:
https://snyk.io/test/github/wix/react-templates
2018-05-07 09:24:33 +03:00
snyk-bot b17b1989e9 fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:lodash:20180130

Latest report for wix/react-templates:
https://snyk.io/test/github/wix/react-templates
2018-03-13 11:32:27 +02:00
idok 4698e07134 fix test 2018-02-19 10:17:12 +02:00
idok aa32337b77 fix lint 2018-02-06 15:34:52 +02:00
ido 2e4d022ad0 update dependencies 2017-06-29 15:46:37 +03:00
ido c175351c39 move to mocha 2017-06-29 14:48:33 +03:00
ido 77aec486a5 fix jsdocs
ignore package-lock
2017-06-29 11:47:09 +03:00
ido e3cef47f51 fix test 2017-06-29 11:39:41 +03:00
ido ed1b3540fe add duplicate test 2017-06-29 11:15:45 +03:00
ido 78b59cef47 fix tests 2017-06-29 11:15:07 +03:00
ido f58ee987f0 fix lint 2017-06-29 11:08:52 +03:00
ido 62b7d11cfc fix tests 2017-06-29 11:04:25 +03:00
ido 5e8aeeca41 eslint fix 2017-06-29 11:01:38 +03:00
ido 0322350344 remove node 4 and 5 support 2017-06-29 10:55:34 +03:00
ido fdc000cc46 add wallaby
move to master
2017-06-29 10:54:17 +03:00
ido 87ed2032fe move back to master 2017-06-29 10:47:10 +03:00
Dany Shaanan 9a475475ca updated gitignore to include gh-pages ignores 2015-02-05 12:54:08 +02:00
Dany Shaanan c5e8121eae Update README.md 2015-02-05 11:00:48 +02:00
Dany Shaanan 5f3abe3a4d removed content from master, moved to gh-pages 2014-11-17 14:58:15 +02:00
59 changed files with 1453 additions and 1352 deletions

View File

@ -7,6 +7,7 @@ playground/**/*.rt.js
playground/dist/** playground/dist/**
playground/libs/** playground/libs/**
playground2/**
playground3/** playground3/**
playground3/dist/** playground3/dist/**
playground3/libs/** playground3/libs/**

View File

@ -1,11 +1,11 @@
{ {
"extends": ["wix-editor", "wix-editor/node", "plugin:lodash/recommended"], "extends": ["wix-editor", "wix-editor/node", "plugin:lodash/canonical"],
"plugins": ["lodash", "wix-editor"], "plugins": ["lodash", "wix-editor"],
"rules": { "rules": {
"semi": [2, "always"], "semi": [2, "never"],
"func-style": [2, "declaration", {"allowArrowFunctions": true}], "func-style": [2, "declaration", {"allowArrowFunctions": true}],
"prefer-spread": 0, "prefer-spread": 1,
"prefer-template": 0, "prefer-template": 1,
"consistent-return": 0, "consistent-return": 0,
"no-restricted-syntax": [2, "WithStatement", "ContinueStatement", "ForStatement"], "no-restricted-syntax": [2, "WithStatement", "ContinueStatement", "ForStatement"],

5
.gitignore vendored
View File

@ -23,9 +23,12 @@ npm-debug.log
/web /web
/target /target
/coverage /coverage
/.nyc_output
### Test Output ### ### Test Output ###
test/data/**/*.rt.actual.js test/data/**/*.rt.actual.js
test/data/**/*.code.js test/data/**/*.code.js
test/data/**/*.actual.html test/data/**/*.actual.html
/package-lock.json
/yarn.lock
/yarn-error.log

View File

@ -1,25 +0,0 @@
### Operating systems ###
.DS_Store
.AppleDouble
.LSOverride
._*
.Spotlight-V100
.Trashes
### Regular dev ###
node_modules
npm-debug.log
.idea
*.iml
### bower ###
/bower_components/*
### tests ###
/test
### gh-pages content ###
/index.html
/playground
/web

View File

@ -1,7 +1,5 @@
language: node_js language: node_js
node_js: node_js:
- 4
- 5
- 6 - 6
sudo: false sudo: false
@ -18,7 +16,7 @@ script:
branches: branches:
only: only:
- gh-pages - master
#after_success: #after_success:
# - npm run coveralls # - npm run coveralls

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = function (grunt) { module.exports = function (grunt) {
grunt.initConfig({ grunt.initConfig({
clean: { clean: {
@ -88,33 +88,33 @@ module.exports = function (grunt) {
options: readConfig('./playground.config.js') options: readConfig('./playground.config.js')
} }
} }
}); })
function readConfig(file) { function readConfig(file) {
return eval(require('fs').readFileSync(file).toString()); // eslint-disable-line no-eval return eval(require('fs').readFileSync(file).toString()) // eslint-disable-line no-eval
} }
grunt.loadNpmTasks('grunt-tape'); grunt.loadNpmTasks('grunt-tape')
grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-browserify')
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-watch')
grunt.loadNpmTasks('grunt-contrib-requirejs'); grunt.loadNpmTasks('grunt-contrib-requirejs')
grunt.loadNpmTasks('grunt-eslint'); grunt.loadNpmTasks('grunt-eslint')
grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-uglify')
grunt.registerTask('default', ['eslint:all']); grunt.registerTask('default', ['eslint:all'])
grunt.registerTask('lint', ['eslint:all']); grunt.registerTask('lint', ['eslint:all'])
grunt.registerTask('test', ['tape']); grunt.registerTask('test', ['tape'])
grunt.registerTask('rt', () => { grunt.registerTask('rt', () => {
const reactTemplates = require('./src/cli'); const reactTemplates = require('./src/cli')
const files = grunt.file.expand('playground/*.rt'); const files = grunt.file.expand('playground/*.rt')
const ret = reactTemplates.execute({modules: 'amd', force: true, _: files}); const ret = reactTemplates.execute({modules: 'amd', force: true, _: files})
return ret === 0; return ret === 0
}); })
grunt.registerTask('build', ['rt', 'browserify:pg']); grunt.registerTask('build', ['rt', 'browserify:pg'])
grunt.registerTask('home', ['rt', 'browserify:home']); grunt.registerTask('home', ['rt', 'browserify:home'])
grunt.registerTask('pgall', ['rt', 'browserify', 'uglify', 'requirejs']); grunt.registerTask('pgall', ['rt', 'browserify', 'uglify', 'requirejs'])
grunt.registerTask('all', ['default', 'test']); grunt.registerTask('all', ['default', 'test'])
}; }

View File

@ -49,6 +49,7 @@ http://plugins.jetbrains.com/plugin/7648
* [rt-template](#rt-template) * [rt-template](#rt-template)
* [rt-include](#rt-include) * [rt-include](#rt-include)
* [rt-pre](#rt-pre) * [rt-pre](#rt-pre)
* [rt-virtual](#rt-virtual)
* [styles](#styles) * [styles](#styles)
* [event handlers](#event-handlers) * [event handlers](#event-handlers)
@ -161,7 +162,7 @@ For instance, to repeat several nodes at once without a shared root for each ins
<rt-virtual rt-repeat="n in [1,2,3]"> <rt-virtual rt-repeat="n in [1,2,3]">
<li>{n}</li> <li>{n}</li>
<li>{n*2}</li> <li>{n*2}</li>
</virtual> </rt-virtual>
</ul> </ul>
``` ```
@ -664,9 +665,9 @@ Copyright (c) 2015 Wix. Licensed under the MIT license.
[npm-image]: https://img.shields.io/npm/v/react-templates.svg?style=flat-square [npm-image]: https://img.shields.io/npm/v/react-templates.svg?style=flat-square
[npm-url]: https://npmjs.org/package/react-templates [npm-url]: https://npmjs.org/package/react-templates
[travis-image]: https://img.shields.io/travis/wix/react-templates/gh-pages.svg?style=flat-square [travis-image]: https://img.shields.io/travis/wix/react-templates/master.svg?style=flat-square
[travis-url]: https://travis-ci.org/wix/react-templates [travis-url]: https://travis-ci.org/wix/react-templates
[coveralls-image]: https://img.shields.io/coveralls/wix/react-templates/gh-pages.svg?style=flat-square [coveralls-image]: https://img.shields.io/coveralls/wix/react-templates/master.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/wix/react-templates?branch=gh-pages [coveralls-url]: https://coveralls.io/r/wix/react-templates?branch=master
[downloads-image]: http://img.shields.io/npm/dm/react-templates.svg?style=flat-square [downloads-image]: http://img.shields.io/npm/dm/react-templates.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/react-templates [downloads-url]: https://npmjs.org/package/react-templates

View File

@ -1,6 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; 'use strict'
const cli = require('../dist/cli'); const cli = require('../dist/cli') //src
const exitCode = cli.execute(process.argv); // console.log(process.argv);
const exitCode = cli.execute(process.argv)
/*eslint no-process-exit:0*/ /*eslint no-process-exit:0*/
process.exit(exitCode); process.exit(exitCode)

View File

@ -1,6 +1,6 @@
{ {
"name": "react-templates", "name": "react-templates",
"version": "0.6.1", "version": "0.6.3",
"description": "Light weight templates for react -> write html get valid react code", "description": "Light weight templates for react -> write html get valid react code",
"main": "./dist/cli.js", "main": "./dist/cli.js",
"bin": { "bin": {
@ -8,9 +8,11 @@
}, },
"scripts": { "scripts": {
"build": "npm run lint && npm run test", "build": "npm run lint && npm run test",
"lint": "eslint .", "lint": "eslint . --cache",
"test": "babel-node test/src/test.js", "test1": "babel-node test/src/test.js",
"test-cov": "istanbul cover test/src/test.js -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/", "pretest": "npm run lint",
"test": "mocha test/src/**/*.unit.js",
"test-cov": "nyc mocha -- test/src/**/*.unit.js --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"patch": "npm version patch -m\"update version to %s\" && git push && git push --tags", "patch": "npm version patch -m\"update version to %s\" && git push && git push --tags",
"minor": "npm version minor -m\"update version to %s\" && git push && git push --tags", "minor": "npm version minor -m\"update version to %s\" && git push && git push --tags",
"major": "npm version major -m\"update version to %s\" && git push && git push --tags", "major": "npm version major -m\"update version to %s\" && git push && git push --tags",
@ -28,47 +30,51 @@
"bugs": { "bugs": {
"url": "https://github.com/wix/react-templates/issues" "url": "https://github.com/wix/react-templates/issues"
}, },
"files": [
"dist",
"bin"
],
"homepage": "https://github.com/wix/react-templates", "homepage": "https://github.com/wix/react-templates",
"dependencies": { "dependencies": {
"chalk": "1.1.3", "chalk": "^2.4.1",
"cheerio": "0.22.0", "cheerio": "^0.22.0",
"css": "2.2.1", "css": "^2.2.3",
"escodegen": "1.8.1", "escodegen": "^1.9.1",
"esprima": "3.1.3", "esprima": "^4.0.0",
"glob": "7.1.1", "glob": "^7.1.2",
"lodash": "4.17.4", "lodash": "^4.17.10",
"normalize-html-whitespace": "0.2.0", "normalize-html-whitespace": "^0.2.0",
"optionator": "0.8.2", "optionator": "^0.8.2",
"text-table": "0.2.0" "text-table": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "6.18.0", "babel-cli": "^6.26.0",
"babel-core": "6.21.0", "babel-core": "^6.26.3",
"babel-loader": "6.2.10", "babel-loader": "^7.1.4",
"babel-preset-es2015": "6.18.0", "babel-preset-es2015": "^6.24.1",
"brace": "0.9.1", "brace": "^0.11.1",
"brfs": "1.4.3", "brfs": "^1.6.1",
"coveralls": "2.11.15", "coveralls": "^3.0.1",
"eslint": "3.12.2", "eslint": "^4.19.1",
"eslint-config-wix-editor": "0.2.3", "eslint-config-wix-editor": "^2.1.0",
"eslint-plugin-lodash": "2.2.5", "eslint-plugin-lodash": "^2.7.0",
"eslint-plugin-react": "6.8.0", "eslint-plugin-react": "^7.7.0",
"eslint-plugin-wix-editor": "1.1.1", "eslint-plugin-wix-editor": "^1.1.1",
"grunt": "1.0.1", "nyc": "^11.7.1",
"grunt-babel": "6.0.0", "grunt": "^1.0.2",
"grunt-browserify": "5.0.0", "grunt-babel": "^7.0.0",
"grunt-contrib-requirejs": "1.0.0", "grunt-browserify": "5.3.0",
"grunt-contrib-uglify": "2.0.0", "grunt-contrib-requirejs": "^1.0.0",
"grunt-contrib-watch": "1.0.0", "grunt-contrib-uglify": "^3.3.0",
"grunt-eslint": "19.0.0", "grunt-contrib-watch": "1.0.1",
"grunt-tape": "0.1.0", "grunt-eslint": "^20.1.0",
"istanbul": "0.4.5", "istanbul": "^0.4.5",
"json-loader": "0.5.4", "json-loader": "^0.5.7",
"mocha": "^5.1.1",
"react": "15.3.2", "react": "15.3.2",
"react-dom": "15.3.2", "react-dom": "15.3.2",
"react-native": "0.39.2", "react-native": "0.45.1",
"tape": "4.6.3", "webpack": "^4.7.0"
"webpack": "1.14.0"
}, },
"keywords": [ "keywords": [
"templates", "templates",

View File

@ -1,18 +1,18 @@
define(['react', 'lodash', 'jquery', './libs/codemirror-4.8/lib/codemirror'], function (React, _, $, CodeMirror) { define(['react', 'lodash', 'jquery', './libs/codemirror-4.8/lib/codemirror'], function (React, _, $, CodeMirror) {
'use strict'; 'use strict'
function annotationTooltip(ann) { function annotationTooltip(ann) {
var severity = ann.severity; var severity = ann.severity
if (!severity) { if (!severity) {
severity = 'error'; severity = 'error'
} }
var tip = document.createElement('div'); var tip = document.createElement('div')
tip.className = 'CodeMirror-lint-message-' + severity; tip.className = 'CodeMirror-lint-message-' + severity
tip.appendChild(document.createTextNode(ann.message)); tip.appendChild(document.createTextNode(ann.message))
return tip; return tip
} }
var GUTTER_ID = 'rt-annotations'; var GUTTER_ID = 'rt-annotations'
function annotate(editor, annot) { function annotate(editor, annot) {
//if (annot.index) { //if (annot.index) {
@ -21,10 +21,10 @@ define(['react', 'lodash', 'jquery', './libs/codemirror-4.8/lib/codemirror'], fu
// var range = editor.findWordAt(pos); // var range = editor.findWordAt(pos);
// editor.markText(range.anchor, range.head, {className: 'editor-error'}); // editor.markText(range.anchor, range.head, {className: 'editor-error'});
//} //}
var tipLabel = document.createDocumentFragment(); /*state.hasGutter &&*/ var tipLabel = document.createDocumentFragment() /*state.hasGutter &&*/
var ann = {severity: 'error', message: annot.message}; var ann = {severity: 'error', message: annot.message}
tipLabel.appendChild(annotationTooltip(ann)); tipLabel.appendChild(annotationTooltip(ann))
editor.setGutterMarker(Math.max(annot.line, 0), GUTTER_ID, makeMarker(tipLabel, 'error', false, 'state.options.tooltips')); editor.setGutterMarker(Math.max(annot.line, 0), GUTTER_ID, makeMarker(tipLabel, 'error', false, 'state.options.tooltips'))
} }
function clearMarks(cm) { function clearMarks(cm) {
@ -33,86 +33,86 @@ define(['react', 'lodash', 'jquery', './libs/codemirror-4.8/lib/codemirror'], fu
//for (var i = 0; i < state.marked.length; ++i) //for (var i = 0; i < state.marked.length; ++i)
// state.marked[i].clear(); // state.marked[i].clear();
//state.marked.length = 0; //state.marked.length = 0;
cm.clearGutter(GUTTER_ID); cm.clearGutter(GUTTER_ID)
} }
function makeMarker(labels, severity, multiple, tooltips) { function makeMarker(labels, severity, multiple, tooltips) {
var marker = document.createElement('div'); var marker = document.createElement('div')
var inner = marker; var inner = marker
marker.className = 'CodeMirror-lint-marker-' + severity; marker.className = 'CodeMirror-lint-marker-' + severity
if (multiple) { if (multiple) {
inner = marker.appendChild(document.createElement('div')); inner = marker.appendChild(document.createElement('div'))
inner.className = 'CodeMirror-lint-marker-multiple'; inner.className = 'CodeMirror-lint-marker-multiple'
} }
if (tooltips !== false) { if (tooltips !== false) {
CodeMirror.on(inner, 'mouseover', function (e) { CodeMirror.on(inner, 'mouseover', function (e) {
showTooltipFor(e, labels, inner); showTooltipFor(e, labels, inner)
}); })
} }
return marker; return marker
} }
function showTooltip(e, content) { function showTooltip(e, content) {
var tt = document.createElement('div'); var tt = document.createElement('div')
tt.className = 'CodeMirror-lint-tooltip'; tt.className = 'CodeMirror-lint-tooltip'
tt.appendChild(content.cloneNode(true)); tt.appendChild(content.cloneNode(true))
document.body.appendChild(tt); document.body.appendChild(tt)
function position(ev) { function position(ev) {
if (!tt.parentNode) { if (!tt.parentNode) {
return CodeMirror.off(document, 'mousemove', position); return CodeMirror.off(document, 'mousemove', position)
} }
tt.style.top = Math.max(0, ev.clientY - tt.offsetHeight - 5) + 'px'; tt.style.top = Math.max(0, ev.clientY - tt.offsetHeight - 5) + 'px'
tt.style.left = (ev.clientX + 5) + 'px'; //eslint-disable-line no-extra-parens tt.style.left = (ev.clientX + 5) + 'px' //eslint-disable-line no-extra-parens
} }
CodeMirror.on(document, 'mousemove', position); CodeMirror.on(document, 'mousemove', position)
position(e); position(e)
if (tt.style.opacity !== null) { if (tt.style.opacity !== null) {
tt.style.opacity = 1; tt.style.opacity = 1
} }
return tt; return tt
} }
function rm(elt) { function rm(elt) {
if (elt.parentNode) { if (elt.parentNode) {
elt.parentNode.removeChild(elt); elt.parentNode.removeChild(elt)
} }
} }
function hideTooltip(tt) { function hideTooltip(tt) {
if (!tt.parentNode) { if (!tt.parentNode) {
return; return
} }
if (tt.style.opacity === null) { if (tt.style.opacity === null) {
rm(tt); rm(tt)
} }
tt.style.opacity = 0; tt.style.opacity = 0
setTimeout(function () { rm(tt); }, 600); setTimeout(function () { rm(tt) }, 600)
} }
function showTooltipFor(e, content, node) { function showTooltipFor(e, content, node) {
var tooltip = showTooltip(e, content); var tooltip = showTooltip(e, content)
function hide() { function hide() {
CodeMirror.off(node, 'mouseout', hide); CodeMirror.off(node, 'mouseout', hide)
if (tooltip) { hideTooltip(tooltip); tooltip = null; } if (tooltip) { hideTooltip(tooltip); tooltip = null }
} }
var poll = setInterval(function () { var poll = setInterval(function () {
if (tooltip) { if (tooltip) {
for (var n = node; ; n = n.parentNode) { //eslint-disable-line no-restricted-syntax for (var n = node; ; n = n.parentNode) { //eslint-disable-line no-restricted-syntax
if (n === document.body) { if (n === document.body) {
return undefined; return undefined
} }
if (!n) { hide(); break; } if (!n) { hide(); break }
} }
} }
if (!tooltip) { if (!tooltip) {
return clearInterval(poll); return clearInterval(poll)
} }
}, 400); }, 400)
CodeMirror.on(node, 'mouseout', hide); CodeMirror.on(node, 'mouseout', hide)
} }
return { return {
GUTTER_ID: GUTTER_ID, GUTTER_ID: GUTTER_ID,
annotate: annotate, annotate: annotate,
clearMarks: clearMarks clearMarks: clearMarks
}; }
}); })

View File

@ -12,7 +12,7 @@ define([
'./libs/codemirror-4.8/addon/runmode/runmode' './libs/codemirror-4.8/addon/runmode/runmode'
//'./libs/codemirror-4.8/addon/display/placeholder' //'./libs/codemirror-4.8/addon/display/placeholder'
], function (React, ReactDOM, _, $, CodeMirror, CMLint) { ], function (React, ReactDOM, _, $, CodeMirror, CMLint) {
'use strict'; 'use strict'
//codeMirror: 'libs/codemirror-4.8/lib/codemirror', //codeMirror: 'libs/codemirror-4.8/lib/codemirror',
//htmlmixed: 'libs/codemirror-4.8/mode/htmlmixed/htmlmixed', //htmlmixed: 'libs/codemirror-4.8/mode/htmlmixed/htmlmixed',
//javascript: 'libs/codemirror-4.8/mode/javascript/javascript' //javascript: 'libs/codemirror-4.8/mode/javascript/javascript'
@ -31,40 +31,40 @@ define([
dangerouslySetInnerHTML: null dangerouslySetInnerHTML: null
} }
} }
}; }
var tags = CodeMirror.htmlSchema; var tags = CodeMirror.htmlSchema
Object.keys(CodeMirror.htmlSchema).forEach(function (i) { Object.keys(CodeMirror.htmlSchema).forEach(function (i) {
tags[i].attrs = _.defaults(rtSchema.div.attrs, tags[i].attrs); tags[i].attrs = _.defaults(rtSchema.div.attrs, tags[i].attrs)
}); })
function completeAfter(cm, pred) { function completeAfter(cm, pred) {
//var cur = cm.getCursor(); //var cur = cm.getCursor();
if (!pred || pred()) { if (!pred || pred()) {
setTimeout(function () { setTimeout(function () {
if (!cm.state.completionActive) { if (!cm.state.completionActive) {
cm.showHint({completeSingle: false}); cm.showHint({completeSingle: false})
} }
}, 100); }, 100)
} }
return CodeMirror.Pass; return CodeMirror.Pass
} }
function completeIfAfterLt(cm) { function completeIfAfterLt(cm) {
return completeAfter(cm, function () { return completeAfter(cm, function () {
var cur = cm.getCursor(); var cur = cm.getCursor()
return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) === '<'; //eslint-disable-line new-cap return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) === '<' //eslint-disable-line new-cap
}); })
} }
function completeIfInTag(cm) { function completeIfInTag(cm) {
return completeAfter(cm, function () { return completeAfter(cm, function () {
var tok = cm.getTokenAt(cm.getCursor()); var tok = cm.getTokenAt(cm.getCursor())
if (tok.type === 'string' && (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length === 1)) { if (tok.type === 'string' && (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length === 1)) {
return false; return false
} }
var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state; var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state
return inner.tagName; return inner.tagName
}); })
} }
return React.createClass({ return React.createClass({
@ -82,29 +82,29 @@ define([
return { return {
readOnly: false, readOnly: false,
mode: 'html' mode: 'html'
}; }
}, },
getInitialState: function () { getInitialState: function () {
return { return {
editorId: _.uniqueId() editorId: _.uniqueId()
}; }
}, },
//componentWillMount: function () { //componentWillMount: function () {
//}, //},
render: function () { render: function () {
var props = _.omit(this.props, ['ref', 'key', 'value', 'valueLink', 'onChange']); var props = _.omit(this.props, ['ref', 'key', 'value', 'valueLink', 'onChange'])
props.id = this.props.id || this.state.editorId; props.id = this.props.id || this.state.editorId
props.defaultValue = this.props.valueLink ? this.props.valueLink() : this.props.value; props.defaultValue = this.props.valueLink ? this.props.valueLink() : this.props.value
return React.DOM.textarea(props); return React.DOM.textarea(props)
}, },
componentWillUpdate: function (nextProps/*, nextState*/) { componentWillUpdate: function (nextProps/*, nextState*/) {
var value = nextProps.valueLink ? nextProps.valueLink() : nextProps.value; var value = nextProps.valueLink ? nextProps.valueLink() : nextProps.value
if (this.editor && this.editor.getValue() !== value) { if (this.editor && this.editor.getValue() !== value) {
this.editor.setValue(value || ''); this.editor.setValue(value || '')
} }
}, },
componentDidMount: function () { componentDidMount: function () {
var value = this.props.valueLink ? this.props.valueLink() : this.props.value; var value = this.props.valueLink ? this.props.valueLink() : this.props.value
var options = { var options = {
readOnly: this.props.readOnly, readOnly: this.props.readOnly,
lineWrapping: true, lineWrapping: true,
@ -115,36 +115,36 @@ define([
mode: 'javascript', mode: 'javascript',
gutters: ['CodeMirror-linenumbers', 'rt-annotations'], gutters: ['CodeMirror-linenumbers', 'rt-annotations'],
theme: 'solarized' //solarized_light solarized-light theme: 'solarized' //solarized_light solarized-light
}; }
if (this.props.mode === 'html') { if (this.props.mode === 'html') {
options.mode = 'text/html'; options.mode = 'text/html'
options.extraKeys = { options.extraKeys = {
"'<'": completeAfter, "'<'": completeAfter,
"'/'": completeIfAfterLt, "'/'": completeIfAfterLt,
"' '": completeIfInTag, "' '": completeIfInTag,
"'='": completeIfInTag, "'='": completeIfInTag,
'Ctrl-Space': 'autocomplete' 'Ctrl-Space': 'autocomplete'
}; }
options.hintOptions = {schemaInfo: tags}; options.hintOptions = {schemaInfo: tags}
//options.gutters = ['CodeMirror-lint-markers']; //options.gutters = ['CodeMirror-lint-markers'];
//options.lint = true; //options.lint = true;
} else { } else {
options.mode = 'javascript'; options.mode = 'javascript'
//options.gutters = ['CodeMirror-lint-markers']; //options.gutters = ['CodeMirror-lint-markers'];
//options.lint = true; //options.lint = true;
} }
this.editor = CodeMirror.fromTextArea(ReactDOM.findDOMNode(this), options); this.editor = CodeMirror.fromTextArea(ReactDOM.findDOMNode(this), options)
if (!this.props.readOnly) { if (!this.props.readOnly) {
this.editor.on('change', function (/*e*/) { this.editor.on('change', function (/*e*/) {
if (this.props.valueLink) { if (this.props.valueLink) {
this.props.valueLink(this.editor.getValue()); this.props.valueLink(this.editor.getValue())
} else if (this.props.onChange) { } else if (this.props.onChange) {
this.props.onChange({target: {value: this.editor.getValue()}}); this.props.onChange({target: {value: this.editor.getValue()}})
} }
}.bind(this)); }.bind(this))
} }
}, },
//showMessage: function (msg) { //showMessage: function (msg) {
@ -163,13 +163,13 @@ define([
// } // }
//}, //},
annotate: function (annot) { annotate: function (annot) {
CMLint.annotate(this.editor, annot); CMLint.annotate(this.editor, annot)
}, },
clearAnnotations: function () { clearAnnotations: function () {
CMLint.clearMarks(this.editor); CMLint.clearMarks(this.editor)
}, },
componentWillUnmount: function () { componentWillUnmount: function () {
this.editor.toTextArea(); this.editor.toTextArea()
} }
}); })
}); })

View File

@ -3,7 +3,7 @@ define(['react', 'react-dom', 'lodash', 'jquery', './libs/codemirror-4.8/lib/cod
'./libs/codemirror-4.8/mode/xml/xml', './libs/codemirror-4.8/mode/xml/xml',
'./libs/codemirror-4.8/addon/runmode/runmode' './libs/codemirror-4.8/addon/runmode/runmode'
], function (React, ReactDOM, _, $, CodeMirror) { ], function (React, ReactDOM, _, $, CodeMirror) {
'use strict'; 'use strict'
return React.createClass({ return React.createClass({
displayName: 'CodeMirrorViewer', displayName: 'CodeMirrorViewer',
propTypes: { propTypes: {
@ -13,34 +13,34 @@ define(['react', 'react-dom', 'lodash', 'jquery', './libs/codemirror-4.8/lib/cod
valueLink: React.PropTypes.string valueLink: React.PropTypes.string
}, },
getDefaultProps: function () { getDefaultProps: function () {
return {mode: 'html'}; return {mode: 'html'}
}, },
getInitialState: function () { getInitialState: function () {
return {editorId: _.uniqueId()}; return {editorId: _.uniqueId()}
}, },
render: function () { render: function () {
var props = _.omit(this.props, ['ref', 'key', 'value', 'valueLink', 'onChange']); var props = _.omit(this.props, ['ref', 'key', 'value', 'valueLink', 'onChange'])
props.id = this.props.id || this.state.editorId; props.id = this.props.id || this.state.editorId
props.className = 'cm-s-default'; props.className = 'cm-s-default'
var value = this.props.valueLink ? this.props.valueLink() : this.props.value; var value = this.props.valueLink ? this.props.valueLink() : this.props.value
return React.DOM.pre(props, value); return React.DOM.pre(props, value)
}, },
componentWillUpdate: function (nextProps/*, nextState*/) { componentWillUpdate: function (nextProps/*, nextState*/) {
var value = nextProps.valueLink ? nextProps.valueLink() : nextProps.value; var value = nextProps.valueLink ? nextProps.valueLink() : nextProps.value
if (this.editor && this.editor.getValue() !== value) { if (this.editor && this.editor.getValue() !== value) {
this.editor.setValue(value || ''); this.editor.setValue(value || '')
} }
}, },
componentDidMount: function () { componentDidMount: function () {
var value = this.props.valueLink ? this.props.valueLink() : this.props.value; var value = this.props.valueLink ? this.props.valueLink() : this.props.value
var mode = this.props.mode; var mode = this.props.mode
if (this.props.mode === 'html') { if (this.props.mode === 'html') {
mode = 'text/html'; mode = 'text/html'
} }
this.editor = CodeMirror.runMode(value, mode, ReactDOM.findDOMNode(this)); this.editor = CodeMirror.runMode(value, mode, ReactDOM.findDOMNode(this))
}, },
componentWillUnmount: function () { componentWillUnmount: function () {
this.editor.toTextArea(); this.editor.toTextArea()
} }
}); })
}); })

View File

@ -3,7 +3,7 @@
*/ */
/*global ace:true*/ /*global ace:true*/
define(['react', 'lodash']/*, 'ace'*/, function (React, _/*, ace*/) { define(['react', 'lodash']/*, 'ace'*/, function (React, _/*, ace*/) {
'use strict'; 'use strict'
return React.createClass({ return React.createClass({
displayName: 'BraceEditor', displayName: 'BraceEditor',
propTypes: { propTypes: {
@ -17,49 +17,49 @@ define(['react', 'lodash']/*, 'ace'*/, function (React, _/*, ace*/) {
getInitialState: function () { getInitialState: function () {
return { return {
editorId: _.uniqueId() editorId: _.uniqueId()
}; }
}, },
componentWillMount: _.noop, componentWillMount: _.noop,
render: function () { render: function () {
var props = _.omit(this.props, ['ref', 'key', 'value', 'valueLink', 'onChange']); var props = _.omit(this.props, ['ref', 'key', 'value', 'valueLink', 'onChange'])
props.id = this.props.id || this.state.editorId; props.id = this.props.id || this.state.editorId
return React.DOM.div(props); return React.DOM.div(props)
}, },
componentWillUpdate: function (nextProps/*, nextState*/) { componentWillUpdate: function (nextProps/*, nextState*/) {
var value = nextProps.valueLink ? nextProps.valueLink() : nextProps.value; var value = nextProps.valueLink ? nextProps.valueLink() : nextProps.value
if (this.editor && this.editor.getValue() !== value) { if (this.editor && this.editor.getValue() !== value) {
this.editor.setValue(value, 0); this.editor.setValue(value, 0)
} }
}, },
componentDidMount: function () { componentDidMount: function () {
this.editor = ace.edit(this.props.id || this.state.editorId); this.editor = ace.edit(this.props.id || this.state.editorId)
// this.editor.setTheme('ace/theme/monokai'); // this.editor.setTheme('ace/theme/monokai');
this.editor.setTheme('ace/theme/solarized_light'); this.editor.setTheme('ace/theme/solarized_light')
if (this.props.mode === 'html') { if (this.props.mode === 'html') {
this.editor.getSession().setMode('ace/mode/html'); this.editor.getSession().setMode('ace/mode/html')
} else { } else {
this.editor.getSession().setMode('ace/mode/javascript'); this.editor.getSession().setMode('ace/mode/javascript')
} }
this.editor.getSession().setUseWorker(false); this.editor.getSession().setUseWorker(false)
var value = this.props.valueLink ? this.props.valueLink() : this.props.value; var value = this.props.valueLink ? this.props.valueLink() : this.props.value
this.editor.setValue(value, 0); this.editor.setValue(value, 0)
if (this.props.readOnly) { if (this.props.readOnly) {
this.editor.setReadOnly(true); this.editor.setReadOnly(true)
} else { } else {
this.editor.setReadOnly(false); this.editor.setReadOnly(false)
this.editor.on('change', function (/*e*/) { this.editor.on('change', function (/*e*/) {
if (this.props.valueLink) { if (this.props.valueLink) {
this.props.valueLink(this.editor.getValue()); this.props.valueLink(this.editor.getValue())
} else if (this.props.onChange) { } else if (this.props.onChange) {
this.props.onChange({target: {value: this.editor.getValue()}}); this.props.onChange({target: {value: this.editor.getValue()}})
} }
}.bind(this)); }.bind(this))
} }
this.editor.clearSelection(); this.editor.clearSelection()
}, },
componentWillUnmount: function () { componentWillUnmount: function () {
this.editor.destroy(); this.editor.destroy()
} }
}); })
}); })

View File

@ -7,7 +7,7 @@ define(['lodash', 'react', './examples.rt',
'text!./samples/weather.code', 'text!./samples/weather.rt', 'text!./samples/weather.code', 'text!./samples/weather.rt',
'text!./samples/rt-import.rt' 'text!./samples/rt-import.rt'
], function (_, React, examplesTemplate, helloCode, helloRT, todoCode, todoRT, rtIfCode, rtIfRT, rtPropsCode, rtPropsRT, rtRepeatCode, rtRepeatRT, weatherCode, weatherRT, rtImportRT) { ], function (_, React, examplesTemplate, helloCode, helloRT, todoCode, todoRT, rtIfCode, rtIfRT, rtPropsCode, rtPropsRT, rtRepeatCode, rtRepeatRT, weatherCode, weatherRT, rtImportRT) {
'use strict'; 'use strict'
var samples = { var samples = {
hello: [helloCode, helloRT], hello: [helloCode, helloRT],
todo: [todoCode, todoRT], todo: [todoCode, todoRT],
@ -15,24 +15,24 @@ define(['lodash', 'react', './examples.rt',
rtIf: [rtIfCode, rtIfRT], rtIf: [rtIfCode, rtIfRT],
repeat: [rtRepeatCode, rtRepeatRT], repeat: [rtRepeatCode, rtRepeatRT],
weather: [weatherCode, weatherRT] weather: [weatherCode, weatherRT]
}; }
samples = _.mapValues(samples, function (v, k) { return {name: k, templateProps: _.template(v[0])({name: k}), templateHTML: v[1]}; }); samples = _.mapValues(samples, function (v, k) { return {name: k, templateProps: _.template(v[0])({name: k}), templateHTML: v[1]} })
return React.createClass({ return React.createClass({
displayName: 'Examples', displayName: 'Examples',
mixins: [React.addons.LinkedStateMixin], mixins: [React.addons.LinkedStateMixin],
getInitialState: function () { getInitialState: function () {
var codeAmd = window.reactTemplates.convertTemplateToReact(rtImportRT, {modules: 'amd', name: 'template'}); var codeAmd = window.reactTemplates.convertTemplateToReact(rtImportRT, {modules: 'amd', name: 'template'})
var codeCJS = window.reactTemplates.convertTemplateToReact(rtImportRT, {modules: 'commonjs', name: 'template'}); var codeCJS = window.reactTemplates.convertTemplateToReact(rtImportRT, {modules: 'commonjs', name: 'template'})
var codeES6 = window.reactTemplates.convertTemplateToReact(rtImportRT, {modules: 'es6', name: 'template'}); var codeES6 = window.reactTemplates.convertTemplateToReact(rtImportRT, {modules: 'es6', name: 'template'})
return { return {
rtImport: {value: rtImportRT}, rtImport: {value: rtImportRT},
amd: {value: codeAmd}, amd: {value: codeAmd},
cjs: {value: codeCJS}, cjs: {value: codeCJS},
es6: {value: codeES6}, es6: {value: codeES6},
samples: samples samples: samples
}; }
}, },
render: examplesTemplate render: examplesTemplate
}); })
}); })

View File

@ -20,10 +20,10 @@ requirejs.config({
map: { map: {
'*': {'react/addons': 'react'} '*': {'react/addons': 'react'}
} }
}); })
requirejs(['fiddle', 'react', 'react-dom', 'jquery', 'bootstrap'], function (fiddle, React, ReactDOM) { requirejs(['fiddle', 'react', 'react-dom', 'jquery', 'bootstrap'], function (fiddle, React, ReactDOM) {
'use strict'; 'use strict'
var elem = React.createElement(fiddle); var elem = React.createElement(fiddle)
window.fiddle = ReactDOM.render(elem, document.getElementById('container')); window.fiddle = ReactDOM.render(elem, document.getElementById('container'))
}); })

View File

@ -2,44 +2,44 @@
* Created by avim on 12/2/2014. * Created by avim on 12/2/2014.
*/ */
define(['react', 'firebase', 'lodash', './fiddle.rt', 'jquery'], function (React, Firebase, _, fiddleTemplate, $) { define(['react', 'firebase', 'lodash', './fiddle.rt', 'jquery'], function (React, Firebase, _, fiddleTemplate, $) {
'use strict'; 'use strict'
function generateRandomId() { function generateRandomId() {
var uuid = 'xxxxxxxx'.replace(/[xy]/g, function (c) { var uuid = 'xxxxxxxx'.replace(/[xy]/g, function (c) {
var r = _.random(0, 15); var r = _.random(0, 15)
return (c === 'x' ? r : r & 0x3 | 0x8).toString(16); return (c === 'x' ? r : r & 0x3 | 0x8).toString(16)
}); })
return uuid; return uuid
} }
var Fiddle = React.createClass({ var Fiddle = React.createClass({
displayName: 'Fiddle', displayName: 'Fiddle',
componentDidMount: function () { componentDidMount: function () {
if (window.location.hash) { if (window.location.hash) {
var newHash = window.location.hash.replace('#', ''); var newHash = window.location.hash.replace('#', '')
var firebase = new Firebase('https://reacttemplates.firebaseio-demo.com/'); var firebase = new Firebase('https://reacttemplates.firebaseio-demo.com/')
firebase.child('fiddles').child(newHash).on('value', function (snapshot) { firebase.child('fiddles').child(newHash).on('value', function (snapshot) {
this.refs.playground.setState(snapshot.val()); this.refs.playground.setState(snapshot.val())
Firebase.goOffline(); Firebase.goOffline()
}.bind(this)); }.bind(this))
} else { } else {
Firebase.goOffline(); Firebase.goOffline()
} }
}, },
save: function () { save: function () {
var newHash = generateRandomId(); var newHash = generateRandomId()
window.location.hash = newHash; window.location.hash = newHash
Firebase.goOnline(); Firebase.goOnline()
var playgroundState = this.refs.playground.state; var playgroundState = this.refs.playground.state
var firebase = new Firebase('https://reacttemplates.firebaseio-demo.com/'); var firebase = new Firebase('https://reacttemplates.firebaseio-demo.com/')
firebase.child('fiddles').child(newHash).set(playgroundState, function () { firebase.child('fiddles').child(newHash).set(playgroundState, function () {
Firebase.goOffline(); Firebase.goOffline()
alert('Saved the fiddle, you can share your url'); //eslint-disable-line no-alert alert('Saved the fiddle, you can share your url') //eslint-disable-line no-alert
}); })
}, },
clear: function () { clear: function () {
this.refs.playground.clear(); this.refs.playground.clear()
}, },
loadSample: function (name) { loadSample: function (name) {
//require(['text!./samples/' + name + '.rt', 'text!./samples/' + name + '.code'], function (rt, code) { //require(['text!./samples/' + name + '.rt', 'text!./samples/' + name + '.code'], function (rt, code) {
@ -51,25 +51,25 @@ define(['react', 'firebase', 'lodash', './fiddle.rt', 'jquery'], function (React
// this.refs.playground.setState(currentState); // this.refs.playground.setState(currentState);
//}); //});
var playground = this.refs.playground; var playground = this.refs.playground
$.get('playground/samples/' + name + '.rt', null, function (data/*, textStatus, jqXHR*/) { $.get('playground/samples/' + name + '.rt', null, function (data/*, textStatus, jqXHR*/) {
var rt = data; var rt = data
$.get('playground/samples/' + name + '.code', null, function (data2/*, textStatus2, jqXHR2*/) { $.get('playground/samples/' + name + '.code', null, function (data2/*, textStatus2, jqXHR2*/) {
var currentState = { var currentState = {
templateHTML: rt, templateHTML: rt,
templateProps: _.template(data2)({name: 'template'}) templateProps: _.template(data2)({name: 'template'})
}; }
//this.updateSample(currentState); //this.updateSample(currentState);
playground.setState(currentState); playground.setState(currentState)
}); })
}); })
//this.refs.playground.clear(); //this.refs.playground.clear();
}, },
render: fiddleTemplate render: fiddleTemplate
}); })
return Fiddle; return Fiddle
}); })

View File

@ -21,10 +21,10 @@ requirejs.config({
map: { map: {
'*': {'react/addons': 'react'} '*': {'react/addons': 'react'}
} }
}); })
requirejs(['./examples', 'react', 'react-dom', 'jquery'], function (Examples, React, ReactDOM) { requirejs(['./examples', 'react', 'react-dom', 'jquery'], function (Examples, React, ReactDOM) {
'use strict'; 'use strict'
var elem = React.createElement(Examples); var elem = React.createElement(Examples)
ReactDOM.render(elem, document.getElementById('home-section')); ReactDOM.render(elem, document.getElementById('home-section'))
}); })

View File

@ -1,6 +1,6 @@
/*eslint-env browser*/ /*eslint-env browser*/
define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './playground.rt'], function (React, ReactDOM, $, _, pgFiddleTemplate, playgroundTemplate) { define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './playground.rt'], function (React, ReactDOM, $, _, pgFiddleTemplate, playgroundTemplate) {
'use strict'; 'use strict'
//function emptyFunc() { //function emptyFunc() {
// return null; // return null;
//} //}
@ -25,13 +25,13 @@ define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './p
function showMessage(editor, msg) { function showMessage(editor, msg) {
if (editor && editor.showMessage) { if (editor && editor.showMessage) {
editor.annotate({line: 1, message: msg}); editor.annotate({line: 1, message: msg})
} }
} }
function clearMessage(editor) { function clearMessage(editor) {
if (editor && editor.clearAnnotations) { if (editor && editor.clearAnnotations) {
editor.clearAnnotations(); editor.clearAnnotations()
} }
} }
@ -55,23 +55,23 @@ define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './p
function generateRenderFunc(renderFunc) { function generateRenderFunc(renderFunc) {
return function () { return function () {
var res = null; var res = null
try { try {
res = renderFunc.apply(this); res = renderFunc.apply(this)
} catch (e) { } catch (e) {
res = React.DOM.div.apply(this, [{style: {color: 'red'}}, 'Exception:' + e.message]); res = React.DOM.div.apply(this, [{style: {color: 'red'}}, 'Exception:' + e.message])
} }
return React.DOM.div.apply(this, _.flatten([ return React.DOM.div.apply(this, _.flatten([
{key: 'result'}, {key: 'result'},
res res
])); ]))
}; }
} }
var templateHTML = '<div></div>'; var templateHTML = '<div></div>'
var templateProps = 'var template = React.createClass({\n' + var templateProps = 'var template = React.createClass({\n' +
' render: templateRT\n' + ' render: templateRT\n' +
'});'; '});'
//var selfCleaningTimeout = { //var selfCleaningTimeout = {
// componentDidUpdate: function() { // componentDidUpdate: function() {
@ -100,18 +100,18 @@ define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './p
validProps: true, validProps: true,
setTimeout: function () { setTimeout: function () {
//console.log('setTimeout'); //console.log('setTimeout');
clearTimeout(this.timeoutID); clearTimeout(this.timeoutID)
this.timeoutID = setTimeout.apply(null, arguments); this.timeoutID = setTimeout.apply(null, arguments)
}, },
getDefaultProps: function () { getDefaultProps: function () {
return { return {
direction: 'horizontal', //vertical direction: 'horizontal', //vertical
codeVisible: true, codeVisible: true,
fiddle: false fiddle: false
}; }
}, },
getLayoutClass: function () { getLayoutClass: function () {
return (this.props.direction === 'horizontal' && 'horizontal') || 'vertical'; //eslint-disable-line no-extra-parens return (this.props.direction === 'horizontal' && 'horizontal') || 'vertical' //eslint-disable-line no-extra-parens
}, },
//executeCode: function() { //executeCode: function() {
// var mountNode = this.refs.mount.getDOMNode(); // var mountNode = this.refs.mount.getDOMNode();
@ -141,78 +141,78 @@ define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './p
//}, //},
getTabs: function () { getTabs: function () {
if (this.props.codeVisible) { if (this.props.codeVisible) {
return [['templateHTML', 'Template'], ['templateProps', 'Class'], ['templateSource', 'Generated code']]; return [['templateHTML', 'Template'], ['templateProps', 'Class'], ['templateSource', 'Generated code']]
} }
return [['templateHTML', 'Template'], ['templateSource', 'Generated code']]; return [['templateHTML', 'Template'], ['templateSource', 'Generated code']]
}, },
updateSample: function (state) { updateSample: function (state) {
//try { //try {
// React.unmountComponentAtNode(mountNode); // React.unmountComponentAtNode(mountNode);
//} catch (e) { } //} catch (e) { }
this.generateCode(state); this.generateCode(state)
//this.sampleFunc = generateTemplateFunction(this.templateSource); //this.sampleFunc = generateTemplateFunction(this.templateSource);
//this.validHTML = this.sampleFunc !== emptyFunc; //this.validHTML = this.sampleFunc !== emptyFunc;
this.validHTML = true; this.validHTML = true
this.sampleRender = generateRenderFunc(this.sampleFunc); this.sampleRender = generateRenderFunc(this.sampleFunc)
var editor; var editor
try { try {
this.validProps = true; this.validProps = true
//console.log(state.templateProps); //console.log(state.templateProps);
this.sample = eval('(function () {' + this.templateSource + '\n' + state.templateProps + '\n return React.createElement(' + state.name + ');})()'); //eslint-disable-line no-eval this.sample = eval('(function () {' + this.templateSource + '\n' + state.templateProps + '\n return React.createElement(' + state.name + ');})()') //eslint-disable-line no-eval
clearMessage(this.refs.editorCode); clearMessage(this.refs.editorCode)
} catch (e) { } catch (e) {
this.validProps = false; this.validProps = false
this.sample = null; this.sample = null
editor = this.refs.editorCode; editor = this.refs.editorCode
this.showError(e, editor); this.showError(e, editor)
} }
//classBase.render = this.sampleRender; //classBase.render = this.sampleRender;
//this.sample = React.createFactory(React.createClass(classBase)); //this.sample = React.createFactory(React.createClass(classBase));
}, },
showError: function (e, editor) { showError: function (e, editor) {
var mountNode = this.refs.mount; var mountNode = this.refs.mount
this.setTimeout(function () { this.setTimeout(function () {
showMessage(editor, e.message); showMessage(editor, e.message)
ReactDOM.render( ReactDOM.render(
React.createElement('div', {className: 'playground-error'}, e.toString()), React.createElement('div', {className: 'playground-error'}, e.toString()),
mountNode mountNode
); )
}, 500); }, 500)
}, },
showErrorAnnotation: function (annot, editor) { showErrorAnnotation: function (annot, editor) {
var mountNode = this.refs.mount; var mountNode = this.refs.mount
this.setTimeout(function () { this.setTimeout(function () {
editor.annotate(annot); editor.annotate(annot)
ReactDOM.render( ReactDOM.render(
React.createElement('div', {className: 'playground-error'}, annot.message), React.createElement('div', {className: 'playground-error'}, annot.message),
mountNode mountNode
); )
}, 500); }, 500)
}, },
clear: function () { clear: function () {
var currentState = { var currentState = {
templateHTML: templateHTML, templateHTML: templateHTML,
templateProps: templateProps templateProps: templateProps
}; }
//this.updateSample(currentState); //this.updateSample(currentState);
this.setState(currentState); this.setState(currentState)
}, },
generateCode: function (state) { generateCode: function (state) {
var html = state.templateHTML; var html = state.templateHTML
var editor = this.refs.editorRT; var editor = this.refs.editorRT
var name = window.reactTemplates.normalizeName(state.name) + 'RT'; var name = window.reactTemplates.normalizeName(state.name) + 'RT'
var code = null; var code = null
try { try {
code = window.reactTemplates.convertTemplateToReact(html.trim().replace(/\r/g, ''), {modules: 'none', name: name}); code = window.reactTemplates.convertTemplateToReact(html.trim().replace(/\r/g, ''), {modules: 'none', name: name})
clearMessage(editor); clearMessage(editor)
} catch (e) { } catch (e) {
var annot = e.name === 'RTCodeError' ? {line: e.line, message: e.message, index: e.index} : {line: 1, message: e.message}; var annot = e.name === 'RTCodeError' ? {line: e.line, message: e.message, index: e.index} : {line: 1, message: e.message}
this.showErrorAnnotation(annot, editor); this.showErrorAnnotation(annot, editor)
//showMessage(editor, msg); //showMessage(editor, msg);
console.log(e); console.log(e)
} }
this.templateSource = code; this.templateSource = code
}, },
getInitialState: function () { getInitialState: function () {
var currentState = { var currentState = {
@ -220,53 +220,53 @@ define(['react', 'react-dom', 'jquery', 'lodash', './playground-fiddle.rt', './p
templateProps: this.props.templateProps || templateProps, templateProps: this.props.templateProps || templateProps,
name: this.props.name || 'template', name: this.props.name || 'template',
currentTab: 'templateHTML' currentTab: 'templateHTML'
}; }
//this.updateSample(currentState); //this.updateSample(currentState);
return currentState; return currentState
}, },
componentDidMount: function () { componentDidMount: function () {
if (this.props.fiddle) { if (this.props.fiddle) {
window.addEventListener('resize', this.calcSize); window.addEventListener('resize', this.calcSize)
this.calcSize(); this.calcSize()
} }
this.updateSample(this.state); this.updateSample(this.state)
this.renderSample(); this.renderSample()
}, },
renderSample: function () { renderSample: function () {
var mountNode = this.refs.mount; var mountNode = this.refs.mount
if (this.sample) { if (this.sample) {
ReactDOM.render(this.sample, mountNode); ReactDOM.render(this.sample, mountNode)
} }
}, },
componentDidUpdate: function () { componentDidUpdate: function () {
this.renderSample(); this.renderSample()
}, },
componentWillUnmount: function () { componentWillUnmount: function () {
window.removeEventListener('resize', this.calcSize); window.removeEventListener('resize', this.calcSize)
}, },
calcSize: function () { calcSize: function () {
var contentHeight = $(window).height() - $('#header').height(); var contentHeight = $(window).height() - $('#header').height()
var height = contentHeight / 2 - 10; var height = contentHeight / 2 - 10
$('.code-area').each(function (/*i, k*/) { $('.code-area').each(function (/*i, k*/) {
$(this).height(height); $(this).height(height)
//console.log($(this).height()); //console.log($(this).height());
}); })
this.refs.editorCode.editor.refresh(); this.refs.editorCode.editor.refresh()
this.refs.editorRT.editor.refresh(); this.refs.editorRT.editor.refresh()
this.refs.editorGenerated.editor.refresh(); this.refs.editorGenerated.editor.refresh()
}, },
componentWillUpdate: function (nextProps, nextState) { componentWillUpdate: function (nextProps, nextState) {
if (nextState.templateHTML !== this.state.templateHTML || nextState.templateProps !== this.state.templateProps) { if (nextState.templateHTML !== this.state.templateHTML || nextState.templateProps !== this.state.templateProps) {
this.updateSample(nextState); this.updateSample(nextState)
} }
}, },
render: function () { render: function () {
this.generateCode(this.state); this.generateCode(this.state)
var template = this.props.fiddle ? pgFiddleTemplate : playgroundTemplate; var template = this.props.fiddle ? pgFiddleTemplate : playgroundTemplate
return template.apply(this); return template.apply(this)
} }
}); })
return Playground; return Playground
}); })

View File

@ -1,9 +1,9 @@
/*eslint strict:0*/ /*eslint strict:0*/
'use strict'; 'use strict'
/*eslint-env browser*/ /*eslint-env browser*/
/*var _ = */require('lodash'); /*var _ = */require('lodash')
var reactTemplates = require('../dist/reactTemplates'); var reactTemplates = require('../dist/reactTemplates')
window.reactTemplates = reactTemplates; window.reactTemplates = reactTemplates

View File

@ -4,7 +4,7 @@ define([
'react', 'react',
'ImageSearch.rt' 'ImageSearch.rt'
], function (_, $, React, template) { ], function (_, $, React, template) {
'use strict'; 'use strict'
var ImageSearch = React.createClass({ var ImageSearch = React.createClass({
@ -18,50 +18,50 @@ define([
realTerm: 'cats', realTerm: 'cats',
getInitialState: function () { getInitialState: function () {
setTimeout(this.search, 0); setTimeout(this.search, 0)
return { return {
searchTerm: this.realTerm, searchTerm: this.realTerm,
items: [[], [], []] items: [[], [], []]
}; }
}, },
search: function () { search: function () {
this.state.items = [[], [], []]; this.state.items = [[], [], []]
this.total = 0; this.total = 0
this.heights = [0, 0, 0]; this.heights = [0, 0, 0]
this.hasMore = true; this.hasMore = true
this.realTerm = this.state.searchTerm; this.realTerm = this.state.searchTerm
this.loadMore(); this.loadMore()
}, },
indexOfMin: function (array) { indexOfMin: function (array) {
var indexAndMin = _.reduce(array, function (accum, height, index) { var indexAndMin = _.reduce(array, function (accum, height, index) {
/*eslint no-extra-parens:0*/ /*eslint no-extra-parens:0*/
return (height < accum.min) ? {i: index, min: height} : accum; return (height < accum.min) ? {i: index, min: height} : accum
}, {i: -1, min: Number.MAX_VALUE}); }, {i: -1, min: Number.MAX_VALUE})
return indexAndMin.i; return indexAndMin.i
}, },
loadMore: function (done) { loadMore: function (done) {
done = done || _.noop; done = done || _.noop
if (!this.hasMore) { if (!this.hasMore) {
done(); done()
return; return
} }
var url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&start=' + this.total + '&q=' + this.realTerm + '&callback=?'; var url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&start=' + this.total + '&q=' + this.realTerm + '&callback=?'
var self = this; var self = this
$.ajax({url: url, dataType: 'jsonp'}) $.ajax({url: url, dataType: 'jsonp'})
.done(function (data) { .done(function (data) {
if (!data.responseData) { if (!data.responseData) {
self.hasMore = false; self.hasMore = false
done(); done()
return; return
} }
var results = data.responseData.results; var results = data.responseData.results
var items = _.cloneDeep(self.state.items); var items = _.cloneDeep(self.state.items)
results.forEach(function (result) { results.forEach(function (result) {
var minHeightIndex = self.indexOfMin(self.heights); var minHeightIndex = self.indexOfMin(self.heights)
items[minHeightIndex].push({ items[minHeightIndex].push({
id: self.seq + 1, id: self.seq + 1,
@ -69,26 +69,26 @@ define([
url: result.url, url: result.url,
ratio: result.width / result.height, ratio: result.width / result.height,
originalContext: result.originalContextUrl originalContext: result.originalContextUrl
}); })
self.heights[minHeightIndex] += result.height / result.width; self.heights[minHeightIndex] += result.height / result.width
self.total++; self.total++
self.seq++; self.seq++
}); })
self.setState({items: items}); self.setState({items: items})
done(); done()
}); })
}, },
shouldComponentUpdate: function (nextProps, nextState) { shouldComponentUpdate: function (nextProps, nextState) {
return !_.isEqual(this.state, nextState); return !_.isEqual(this.state, nextState)
}, },
render: function () { render: function () {
return template.apply(this); return template.apply(this)
} }
}); })
return ImageSearch; return ImageSearch
}); })

View File

@ -10,9 +10,9 @@ requirejs.config({
jquery: {exports: '$'}, jquery: {exports: '$'},
react: {exports: 'React'} react: {exports: 'React'}
} }
}); })
requirejs(['jquery', 'react', 'ImageSearch'], function ($, React, ImageSearch) { requirejs(['jquery', 'react', 'ImageSearch'], function ($, React, ImageSearch) {
'use strict'; 'use strict'
React.renderComponent(ImageSearch(), $('#main').get(0)); //eslint-disable-line new-cap React.renderComponent(ImageSearch(), $('#main').get(0)) //eslint-disable-line new-cap
}); })

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict'
const util = require('util'); const util = require('util')
const _ = require('lodash'); const _ = require('lodash')
/** /**
@ -14,43 +14,49 @@ const _ = require('lodash');
*/ */
function getLine(html, node) { function getLine(html, node) {
if (!node) { if (!node) {
return {line: 1, col: 1}; return {line: 1, col: 1}
} }
const linesUntil = html.substring(0, node.startIndex).split('\n'); const linesUntil = html.substring(0, node.startIndex).split('\n')
return {line: linesUntil.length, col: linesUntil[linesUntil.length - 1].length + 1}; return {line: linesUntil.length, col: linesUntil[linesUntil.length - 1].length + 1}
}
function norm(n) {
return n === undefined ? -1 : n;
} }
/** /**
* @param {string} message * @param {number} n
* @param {number=} startOffset * @return {number}
* @param {number=} endOffset */
* @param {number=} line function norm(n) {
* @param {number=} column return n === undefined ? -1 : n
* @constructor }
/**
*
*/ */
class RTCodeError extends Error { class RTCodeError extends Error {
/**
* @param {string} message
* @param {number=} startOffset
* @param {number=} endOffset
* @param {number=} line
* @param {number=} column
*/
constructor(message, startOffset, endOffset, line, column) { constructor(message, startOffset, endOffset, line, column) {
super(); super()
Error.captureStackTrace(this, RTCodeError); Error.captureStackTrace(this, RTCodeError)
this.name = 'RTCodeError'; this.name = 'RTCodeError'
this.message = message || ''; this.message = message || ''
this.index = norm(startOffset); this.index = norm(startOffset)
this.startOffset = norm(startOffset); this.startOffset = norm(startOffset)
this.endOffset = norm(endOffset); this.endOffset = norm(endOffset)
this.line = norm(line); this.line = norm(line)
this.column = norm(column); this.column = norm(column)
} }
} }
/** /**
* @type {buildError} * @type {buildError}
*/ */
RTCodeError.build = buildError; RTCodeError.build = buildError
RTCodeError.norm = norm; RTCodeError.norm = norm
/** /**
* @param {*} context * @param {*} context
@ -60,7 +66,7 @@ RTCodeError.norm = norm;
* @return {RTCodeError} * @return {RTCodeError}
*/ */
function buildFormat(context, node, msg, args) { function buildFormat(context, node, msg, args) {
return buildError(context, node, util.format.apply(this, [msg].concat(args))); return buildError(context, node, util.format.apply(this, [msg].concat(args)))
} }
/** /**
@ -70,7 +76,7 @@ function buildFormat(context, node, msg, args) {
* @param {Array.<string>} args * @param {Array.<string>} args
* @return {RTCodeError} * @return {RTCodeError}
*/ */
RTCodeError.buildFormat = _.rest(buildFormat, 3); RTCodeError.buildFormat = _.rest(buildFormat, 3)
/** /**
* @param {*} context * @param {*} context
@ -79,8 +85,8 @@ RTCodeError.buildFormat = _.rest(buildFormat, 3);
* @return {RTCodeError} * @return {RTCodeError}
*/ */
function buildError(context, node, msg) { function buildError(context, node, msg) {
const loc = getNodeLoc(context, node); const loc = getNodeLoc(context, node)
return new RTCodeError(msg, loc.start, loc.end, loc.pos.line, loc.pos.col); return new RTCodeError(msg, loc.start, loc.end, loc.pos.line, loc.pos.col)
} }
/** /**
@ -89,24 +95,24 @@ function buildError(context, node, msg) {
* @return {{pos:Pos, start:number, end:number}} * @return {{pos:Pos, start:number, end:number}}
*/ */
function getNodeLoc(context, node) { function getNodeLoc(context, node) {
const start = node.startIndex; const start = node.startIndex
const pos = getLine(context.html, node); const pos = getLine(context.html, node)
let end; let end
if (node.data) { if (node.data) {
end = start + node.data.length; end = start + node.data.length
} else if (node.next) { // eslint-disable-line } else if (node.next) { // eslint-disable-line
end = node.next.startIndex; end = node.next.startIndex
} else { } else {
end = context.html.length; end = context.html.length
} }
return { return {
pos, pos,
start, start,
end end
}; }
} }
module.exports = { module.exports = {
RTCodeError, RTCodeError,
getNodeLoc getNodeLoc
}; }

View File

@ -1,12 +1,12 @@
'use strict'; 'use strict'
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
const chalk = require('chalk'); const chalk = require('chalk')
const reactTemplates = require('./reactTemplates'); const reactTemplates = require('./reactTemplates')
const fsUtil = require('./fsUtil'); const fsUtil = require('./fsUtil')
const convertRT = reactTemplates.convertRT; const convertRT = reactTemplates.convertRT
const convertJSRTToJS = reactTemplates.convertJSRTToJS; const convertJSRTToJS = reactTemplates.convertJSRTToJS
/** /**
* @param {string} source * @param {string} source
@ -15,35 +15,35 @@ const convertJSRTToJS = reactTemplates.convertJSRTToJS;
* @param {CONTEXT} context * @param {CONTEXT} context
*/ */
function convertFile(source, target, options, context) { function convertFile(source, target, options, context) {
options = options || {}; options = options || {}
options.fileName = source; options.fileName = source
if (!options.force && !fsUtil.isStale(source, target)) { if (!options.force && !fsUtil.isStale(source, target)) {
context.verbose(`target file ${chalk.cyan(target)} is up to date, skipping`); context.verbose(`target file ${chalk.cyan(target)} is up to date, skipping`)
return; return
} }
const html = fs.readFileSync(source).toString(); const html = fs.readFileSync(source).toString()
if (path.extname(source) === '.rts') { if (path.extname(source) === '.rts') {
const rtStyle = require('./rtStyle'); const rtStyle = require('./rtStyle')
const out = rtStyle.convert(html); const out = rtStyle.convert(html)
if (!options.dryRun) { if (!options.dryRun) {
fs.writeFileSync(target, out); fs.writeFileSync(target, out)
} }
return; return
} }
const modules = options.modules || 'none'; const modules = options.modules || 'none'
const shouldAddName = modules === 'none' && !options.name; const shouldAddName = modules === 'none' && !options.name
if (shouldAddName) { if (shouldAddName) {
options.name = reactTemplates.normalizeName(path.basename(source, path.extname(source))) + 'RT'; options.name = reactTemplates.normalizeName(path.basename(source, path.extname(source))) + 'RT'
} }
options.readFileSync = fsUtil.createRelativeReadFileSync(source); options.readFileSync = fsUtil.createRelativeReadFileSync(source)
const js = modules === 'jsrt' ? convertJSRTToJS(html, context, options) : convertRT(html, context, options); const js = modules === 'jsrt' ? convertJSRTToJS(html, context, options) : convertRT(html, context, options)
if (!options.dryRun) { if (!options.dryRun) {
fs.writeFileSync(target, js); fs.writeFileSync(target, js)
} }
if (shouldAddName) { if (shouldAddName) {
delete options.name; delete options.name
} }
} }
@ -51,4 +51,4 @@ module.exports = {
convertFile, convertFile,
context: require('./context'), context: require('./context'),
_test: {} _test: {}
}; }

View File

@ -1,37 +1,36 @@
#!/usr/bin/env node 'use strict'
'use strict'; const _ = require('lodash')
const _ = require('lodash'); const path = require('path')
const path = require('path');
// const fs = require('fs'); // const fs = require('fs');
const api = require('./api'); const api = require('./api')
const context = require('./context'); const context = require('./context')
const shell = require('./shell'); const shell = require('./shell')
const pkg = require('../package.json'); const pkg = require('../package.json')
const options = require('./options'); const options = require('./options')
const reactDOMSupport = require('./reactDOMSupport'); const reactDOMSupport = require('./reactDOMSupport')
const reactTemplates = require('./reactTemplates'); const reactTemplates = require('./reactTemplates')
const rtStyle = require('./rtStyle'); const rtStyle = require('./rtStyle')
const glob = require('glob'); const glob = require('glob')
/** /**
* @param {Options} currentOptions * @param {Options} currentOptions
* @return {number} * @return {number}
*/ */
function executeOptions(currentOptions) { function executeOptions(currentOptions) {
let ret = 0; let ret = 0
const files = currentOptions._; const files = currentOptions._
context.options.format = currentOptions.format || 'stylish'; context.options.format = currentOptions.format || 'stylish'
if (currentOptions.version) { if (currentOptions.version) {
console.log(`v${pkg.version}`); console.log(`v${pkg.version}`)
} else if (currentOptions.help) { } else if (currentOptions.help) {
if (files.length) { if (files.length) {
console.log(options.generateHelpForOption(files[0])); console.log(options.generateHelpForOption(files[0]))
} else { } else {
console.log(options.generateHelp()); console.log(options.generateHelp())
} }
} else if (currentOptions.listTargetVersion) { } else if (currentOptions.listTargetVersion) {
printVersions(currentOptions); printVersions(currentOptions)
} else if (files.length) { } else if (files.length) {
// console.log(files); // console.log(files);
// console.log(files.length); // console.log(files.length);
@ -43,22 +42,22 @@ function executeOptions(currentOptions) {
// } // }
// return fp; // return fp;
// }); // });
const allFiles = _.flatMap(files, f => glob.sync(f, {cwd: context.cwd})); const allFiles = _.flatMap(files, f => glob.sync(f, {cwd: context.cwd}))
// console.log(allFiles.length); // console.log(allFiles.length);
_.forEach(allFiles, handleSingleFile.bind(this, currentOptions)); _.forEach(allFiles, handleSingleFile.bind(this, currentOptions))
ret = shell.printResults(context); ret = shell.printResults(context)
} else { } else {
console.log(options.generateHelp()); console.log(options.generateHelp())
} }
return ret; return ret
} }
function printVersions(currentOptions) { function printVersions(currentOptions) {
const ret = Object.keys(reactDOMSupport); const ret = Object.keys(reactDOMSupport)
if (currentOptions.format === 'json') { if (currentOptions.format === 'json') {
console.log(JSON.stringify(ret, undefined, 2)); console.log(JSON.stringify(ret, undefined, 2))
} else { } else {
console.log(ret.join(', ')); console.log(ret.join(', '))
} }
} }
@ -68,23 +67,23 @@ function printVersions(currentOptions) {
*/ */
function handleSingleFile(currentOptions, filename) { function handleSingleFile(currentOptions, filename) {
try { try {
const sourceExt = path.extname(filename); const sourceExt = path.extname(filename)
let outputFilename; let outputFilename
if (sourceExt === '.rt') { if (sourceExt === '.rt') {
outputFilename = filename + (currentOptions.modules === 'typescript' ? '.ts' : '.js'); outputFilename = filename + (currentOptions.modules === 'typescript' ? '.ts' : '.js')
} else if (sourceExt === '.jsrt') { } else if (sourceExt === '.jsrt') {
outputFilename = filename.replace(/\.jsrt$/, '.js'); outputFilename = filename.replace(/\.jsrt$/, '.js')
currentOptions = _.assign({}, currentOptions, {modules: 'jsrt'}); currentOptions = _.assign({}, currentOptions, {modules: 'jsrt'})
} else if (sourceExt === '.rts') { } else if (sourceExt === '.rts') {
outputFilename = filename + '.js'; outputFilename = `${filename}.js`
currentOptions = _.assign({}, currentOptions, {modules: 'rts'}); currentOptions = _.assign({}, currentOptions, {modules: 'rts'})
} else { } else {
context.error('invalid file, only handle rt/jsrt files', filename); context.error('invalid file, only handle rt/jsrt files', filename)
return; return
} }
api.convertFile(filename, outputFilename, currentOptions, context); api.convertFile(filename, outputFilename, currentOptions, context)
} catch (e) { } catch (e) {
context.error(e.message, filename, e.line, e.column, e.startOffset, e.endOffset); context.error(e.message, filename, e.line, e.column, e.startOffset, e.endOffset)
} }
} }
@ -95,11 +94,11 @@ function handleSingleFile(currentOptions, filename) {
*/ */
function execute(args) { function execute(args) {
try { try {
const currentOptions = options.parse(args); const currentOptions = options.parse(args)
return executeOptions(currentOptions); return executeOptions(currentOptions)
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message)
return 1; return 1
} }
} }
@ -110,4 +109,4 @@ module.exports = {
handleSingleFile, handleSingleFile,
convertTemplateToReact: reactTemplates.convertTemplateToReact, convertTemplateToReact: reactTemplates.convertTemplateToReact,
convertStyle: rtStyle.convert convertStyle: rtStyle.convert
}; }

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
/** /**
* @typedef {{color: boolean, cwd: string, report: function(string), issue: function(string, string,string,number,number,number=,number=), warn: function(string), verbose: function(string), getMessages: function():Array.<MESSAGE>, options:Options, messages: Array.<MESSAGE>}} CONTEXT * @typedef {{color: boolean, cwd: string, report: function(string), issue: function(string, string,string,number,number,number=,number=), warn: function(string), verbose: function(string), getMessages: function():Array.<MESSAGE>, options:Options, messages: Array.<MESSAGE>}} CONTEXT
*/ */
@ -14,11 +14,11 @@ const MESSAGE_LEVEL = {
ERROR: 'ERROR', ERROR: 'ERROR',
WARN: 'WARN', WARN: 'WARN',
INFO: 'INFO' INFO: 'INFO'
}; }
const _ = require('lodash'); const _ = require('lodash')
const err = require('./RTCodeError'); const err = require('./RTCodeError')
const norm = err.RTCodeError.norm; const norm = err.RTCodeError.norm
/** /**
@ -32,21 +32,21 @@ const context = {
/** @type {string} */ /** @type {string} */
cwd: process.cwd(), cwd: process.cwd(),
report(msg) { report(msg) {
console.log(msg); console.log(msg)
}, },
verbose(msg) { verbose(msg) {
if (context.options.verbose) { if (context.options.verbose) {
console.log(msg); console.log(msg)
} }
}, },
info(msg, file, line, column) { info(msg, file, line, column) {
context.issue(MESSAGE_LEVEL.INFO, msg, file, line, column); context.issue(MESSAGE_LEVEL.INFO, msg, file, line, column)
}, },
warn(msg, file, line, column, startOffset, endOffset) { warn(msg, file, line, column, startOffset, endOffset) {
context.issue(MESSAGE_LEVEL.WARN, msg, file, line, column, startOffset, endOffset); context.issue(MESSAGE_LEVEL.WARN, msg, file, line, column, startOffset, endOffset)
}, },
error(msg, file, line, column, startOffset, endOffset) { error(msg, file, line, column, startOffset, endOffset) {
context.issue(MESSAGE_LEVEL.ERROR, msg, file, line, column, startOffset, endOffset); context.issue(MESSAGE_LEVEL.ERROR, msg, file, line, column, startOffset, endOffset)
}, },
/** /**
* @param {MESSAGE_LEVEL} level * @param {MESSAGE_LEVEL} level
@ -58,16 +58,16 @@ const context = {
* @param {number=} endOffset * @param {number=} endOffset
*/ */
issue(level, msg, file, line, column, startOffset, endOffset) { issue(level, msg, file, line, column, startOffset, endOffset) {
context.messages.push({level, msg, file: file || null, line: norm(line), column: norm(column), index: norm(startOffset), startOffset: norm(startOffset), endOffset: norm(endOffset)}); context.messages.push({level, msg, file: file || null, line: norm(line), column: norm(column), index: norm(startOffset), startOffset: norm(startOffset), endOffset: norm(endOffset)})
}, },
getMessages() { getMessages() {
return context.messages; return context.messages
}, },
clear() { clear() {
context.messages = []; context.messages = []
}, },
hasErrors() { hasErrors() {
return _.some(context.messages, {level: MESSAGE_LEVEL.ERROR}); return _.some(context.messages, {level: MESSAGE_LEVEL.ERROR})
}, },
options: { options: {
verbose: false, verbose: false,
@ -75,6 +75,6 @@ const context = {
format: 'stylish' format: 'stylish'
}, },
MESSAGE_LEVEL MESSAGE_LEVEL
}; }
module.exports = context; module.exports = context

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = function (results/*, config*/) { module.exports = function (results/*, config*/) {
return JSON.stringify(results, undefined, 2); return JSON.stringify(results, undefined, 2)
}; }

View File

@ -1,10 +1,10 @@
'use strict'; 'use strict'
/** /**
* @type {Chalk.ChalkModule} * @type {Chalk.ChalkModule}
*/ */
const chalk = require('chalk'); const chalk = require('chalk')
const _ = require('lodash'); const _ = require('lodash')
const table = require('text-table'); const table = require('text-table')
/** /**
* Given a word and a count, append an s if count is not one. * Given a word and a count, append an s if count is not one.
@ -13,51 +13,51 @@ const table = require('text-table');
* @returns {string} The original word with an s on the end if count is not one. * @returns {string} The original word with an s on the end if count is not one.
*/ */
function pluralize(word, count) { function pluralize(word, count) {
return count === 1 ? word : word + 's'; return count === 1 ? word : `${word}s`
} }
/** /**
* @param {number} line * @param {number} line
* @return {string} * @return {string}
*/ */
function lineText(line) { function lineText(line) {
return line < 1 ? '' : line; return line < 1 ? '' : line
} }
module.exports = function (results) { module.exports = function (results) {
results = _.groupBy(results, 'file'); results = _.groupBy(results, 'file')
let output = '\n'; let output = '\n'
let total = 0; let total = 0
let errors = 0; let errors = 0
let warnings = 0; let warnings = 0
let infos = 0; let infos = 0
let summaryColor = 'cyan'; let summaryColor = 'cyan'
_.forEach(results, (result, k) => { _.forEach(results, (result, k) => {
const messages = result; const messages = result
if (messages.length === 0) { if (messages.length === 0) {
return; return
} }
total += messages.length; total += messages.length
output += chalk.underline(k) + '\n'; output += chalk.underline(k) + '\n'
output += table( output += table(
messages.map(message => { messages.map(message => {
let messageType; let messageType
if (message.level === 'ERROR') { if (message.level === 'ERROR') {
messageType = chalk.red('error'); messageType = chalk.red('error')
summaryColor = 'red'; summaryColor = 'red'
errors++; errors++
} else if (message.level === 'WARN') { } else if (message.level === 'WARN') {
messageType = chalk.yellow('warning'); messageType = chalk.yellow('warning')
summaryColor = 'yellow'; summaryColor = 'yellow'
warnings++; warnings++
} else { } else {
messageType = chalk.cyan('info'); messageType = chalk.cyan('info')
infos++; infos++
} }
return [ return [
@ -67,14 +67,14 @@ module.exports = function (results) {
messageType, messageType,
message.msg.replace(/\.$/, ''), message.msg.replace(/\.$/, ''),
chalk.gray(message.ruleId || '') chalk.gray(message.ruleId || '')
]; ]
}), }),
{ {
align: ['', 'r', 'l'], align: ['', 'r', 'l'],
stringLength: str => chalk.stripColor(str).length stringLength: str => chalk.stripColor(str).length
} }
).split('\n').map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.gray(p1 + ':' + p2))).join('\n') + '\n\n'; ).split('\n').map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.gray(`${p1}:${p2}`))).join('\n') + '\n\n'
}); })
if (total > 0) { if (total > 0) {
output += chalk[summaryColor].bold([ output += chalk[summaryColor].bold([
@ -82,8 +82,8 @@ module.exports = function (results) {
' (', errors, pluralize(' error', errors), ', ', ' (', errors, pluralize(' error', errors), ', ',
warnings, pluralize(' warning', warnings), ', ', warnings, pluralize(' warning', warnings), ', ',
infos, pluralize(' info', infos), ')\n' infos, pluralize(' info', infos), ')\n'
].join('')); ].join(''))
} }
return total > 0 ? output : ''; return total > 0 ? output : ''
}; }

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict'
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
/** /**
* @param {string} source * @param {string} source
@ -9,19 +9,19 @@ const path = require('path');
*/ */
function isStale(source, target) { function isStale(source, target) {
if (!fs.existsSync(target)) { if (!fs.existsSync(target)) {
return true; return true
} }
const sourceTime = fs.statSync(source).mtime; const sourceTime = fs.statSync(source).mtime
const targetTime = fs.statSync(target).mtime; const targetTime = fs.statSync(target).mtime
return sourceTime.getTime() > targetTime.getTime(); return sourceTime.getTime() > targetTime.getTime()
} }
function createRelativeReadFileSync(baseFile) { function createRelativeReadFileSync(baseFile) {
const basePath = path.dirname(baseFile); const basePath = path.dirname(baseFile)
return filename => fs.readFileSync(path.resolve(basePath, filename)); return filename => fs.readFileSync(path.resolve(basePath, filename))
} }
module.exports = { module.exports = {
isStale, isStale,
createRelativeReadFileSync createRelativeReadFileSync
}; }

View File

@ -2,16 +2,16 @@
* @fileoverview Options configuration for optionator. * @fileoverview Options configuration for optionator.
* @author idok * @author idok
*/ */
'use strict'; 'use strict'
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Requirements // Requirements
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const optionator = require('optionator'); const optionator = require('optionator')
const pkg = require('../package.json'); const pkg = require('../package.json')
const reactDOMSupport = require('./reactDOMSupport'); const reactDOMSupport = require('./reactDOMSupport')
const reactNativeSupport = require('./reactNativeSupport'); const reactNativeSupport = require('./reactNativeSupport')
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Initialization and Public Interface // Initialization and Public Interface
@ -121,5 +121,10 @@ $ rt <filename|glob> [<filename|glob> ...] [<args>]`,
type: 'Boolean', type: 'Boolean',
default: 'false', default: 'false',
description: 'Remove repeating whitespace from HTML text.' description: 'Remove repeating whitespace from HTML text.'
}, {
option: 'autobind',
type: 'Boolean',
default: 'false',
description: 'Automatically bind event handlers to components'
}] }]
}); })

View File

@ -1,12 +1,12 @@
'use strict'; 'use strict'
const ver0_12_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', const ver0_12_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data',
'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'ellipse', 'g', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan']; 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'ellipse', 'g', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan']
const ver0_11_2 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'ellipse', 'g', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan', 'injection']; const ver0_11_2 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'ellipse', 'g', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan', 'injection']
const ver0_11_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'ellipse', 'g', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan', 'injection']; const ver0_11_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'ellipse', 'g', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan', 'injection']
const ver0_10_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'g', 'line', 'linearGradient', 'path', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'injection']; const ver0_10_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr', 'circle', 'defs', 'g', 'line', 'linearGradient', 'path', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'injection']
const svg = ['a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'color-profile', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'g', 'image', 'line', const svg = ['a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'color-profile', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'g', 'image', 'line',
'linearGradient', 'marker', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tref', 'tspan', 'use']; 'linearGradient', 'marker', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tref', 'tspan', 'use']
const v12_svg = ver0_12_0.concat(svg); const v12_svg = ver0_12_0.concat(svg)
const versions = { const versions = {
'15.0.1': v12_svg, '15.0.1': v12_svg,
@ -21,5 +21,5 @@ const versions = {
'0.11.0': ver0_11_0, '0.11.0': ver0_11_0,
'0.10.0': ver0_10_0, '0.10.0': ver0_10_0,
default: '0.14.0' default: '0.14.0'
}; }
module.exports = versions; module.exports = versions

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict'
const ver0_9_0 = ['ActivityIndicatorIOS', 'DatePickerIOS', 'Image', 'ListView', 'MapView', 'Navigator', 'NavigatorIOS', 'PickerIOS', 'ScrollView', 'SliderIOS', 'SwitchIOS', 'TabBarIOS', 'Text', 'TextInput', 'TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', 'View', 'WebView']; const ver0_9_0 = ['ActivityIndicatorIOS', 'DatePickerIOS', 'Image', 'ListView', 'MapView', 'Navigator', 'NavigatorIOS', 'PickerIOS', 'ScrollView', 'SliderIOS', 'SwitchIOS', 'TabBarIOS', 'Text', 'TextInput', 'TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', 'View', 'WebView']
const versions = { const versions = {
'0.9.0': { '0.9.0': {
@ -14,6 +14,6 @@ const versions = {
components: ver0_9_0 components: ver0_9_0
}, },
default: '0.9.0' default: '0.9.0'
}; }
module.exports = versions; module.exports = versions

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
const native = { const native = {
'0.9.0': { '0.9.0': {
@ -21,9 +21,9 @@ const native = {
Separator: {prop: 'renderSeparator', arguments: ['sectionID', 'rowID', 'adjacentRowHighlighted']} Separator: {prop: 'renderSeparator', arguments: ['sectionID', 'rowID', 'adjacentRowHighlighted']}
} }
} }
}; }
module.exports = { module.exports = {
native, native,
dom: {} dom: {}
}; }

View File

@ -1,5 +1,5 @@
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @param {Context} context * @param {Context} context
@ -11,9 +11,9 @@ function shouldUseCreateElement(context) {
case '0.11.1': case '0.11.1':
case '0.11.0': case '0.11.0':
case '0.10.0': case '0.10.0':
return false; return false
default: default:
return true; return true
} }
} }
@ -22,24 +22,24 @@ const reactSupportedAttributes = ['accept', 'acceptCharset', 'accessKey', 'actio
'draggable', 'encType', 'form', 'formNoValidate', 'frameBorder', 'height', 'hidden', 'href', 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'label', 'lang', 'list', 'loop', 'manifest', 'draggable', 'encType', 'form', 'formNoValidate', 'frameBorder', 'height', 'hidden', 'href', 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'label', 'lang', 'list', 'loop', 'manifest',
'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'multiple', 'muted', 'name', 'noValidate', 'open', 'pattern', 'placeholder', 'poster', 'preload', 'radioGroup', 'readOnly', 'rel', 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'multiple', 'muted', 'name', 'noValidate', 'open', 'pattern', 'placeholder', 'poster', 'preload', 'radioGroup', 'readOnly', 'rel',
'required', 'role', 'rows', 'rowSpan', 'sandbox', 'scope', 'scrolling', 'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcSet', 'start', 'step', 'required', 'role', 'rows', 'rowSpan', 'sandbox', 'scope', 'scrolling', 'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcSet', 'start', 'step',
'style', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode']; 'style', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode']
const classNameProp = 'className'; const classNameProp = 'className'
const attributesMapping = {'class': classNameProp, 'rt-class': classNameProp, 'for': 'htmlFor'}; //eslint-disable-line quote-props const attributesMapping = {'class': classNameProp, 'rt-class': classNameProp, 'for': 'htmlFor'} //eslint-disable-line quote-props
_.forEach(reactSupportedAttributes, attributeReactName => { _.forEach(reactSupportedAttributes, attributeReactName => {
if (attributeReactName !== attributeReactName.toLowerCase()) { if (attributeReactName !== attributeReactName.toLowerCase()) {
attributesMapping[attributeReactName.toLowerCase()] = attributeReactName; attributesMapping[attributeReactName.toLowerCase()] = attributeReactName
} }
}); })
const htmlSelfClosingTags = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']; const htmlSelfClosingTags = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']
const templateAMDTemplate = _.template("define(<%= name ? '\"'+name + '\", ' : '' %>[<%= requirePaths %>], function (<%= AMDArguments %>) {\n'use strict';\n<%= AMDSubstitutions %>return <%= renderFunction %>;\n});"); const templateAMDTemplate = _.template("define(<%= name ? '\"'+name + '\", ' : '' %>[<%= requirePaths %>], function (<%= AMDArguments %>) {\n'use strict';\n<%= AMDSubstitutions %>return <%= renderFunction %>;\n});")
const templateCommonJSTemplate = _.template("'use strict';\n<%= vars %>\nmodule.exports = <%= renderFunction %>;\n"); const templateCommonJSTemplate = _.template("'use strict';\n<%= vars %>\nmodule.exports = <%= renderFunction %>;\n")
const templateES6Template = _.template('<%= vars %>\nexport default <%= renderFunction %>\n'); const templateES6Template = _.template('<%= vars %>\nexport default <%= renderFunction %>\n')
const templatePJSTemplate = _.template('var <%= name %> = <%= renderFunction %>'); const templatePJSTemplate = _.template('var <%= name %> = <%= renderFunction %>')
const templateTypescriptTemplate = _.template('<%= vars %>\nexport = <%= renderFunction %>;\n'); const templateTypescriptTemplate = _.template('<%= vars %>\nexport = <%= renderFunction %>;\n')
const templateJSRTTemplate = _.template('<%= renderFunction %>'); const templateJSRTTemplate = _.template('<%= renderFunction %>')
const templates = { const templates = {
amd: templateAMDTemplate, amd: templateAMDTemplate,
@ -48,27 +48,27 @@ const templates = {
es6: templateES6Template, es6: templateES6Template,
none: templatePJSTemplate, none: templatePJSTemplate,
jsrt: templateJSRTTemplate jsrt: templateJSRTTemplate
}; }
const isImportAsterisk = _.matches({member: '*'}); const isImportAsterisk = _.matches({member: '*'})
const defaultCase = _.constant(true); const defaultCase = _.constant(true)
const buildImportTypeScript = _.cond([ const buildImportTypeScript = _.cond([
[isImportAsterisk, d => `import ${d.alias} = require('${d.moduleName}');`], [isImportAsterisk, d => `import ${d.alias} = require('${d.moduleName}');`],
[_.matches({member: 'default'}), d => `import ${d.alias} from '${d.moduleName}';`], [_.matches({member: 'default'}), d => `import ${d.alias} from '${d.moduleName}';`],
[defaultCase, d => `import { ${d.member} as ${d.alias} } from '${d.moduleName}';`] [defaultCase, d => `import { ${d.member} as ${d.alias} } from '${d.moduleName}';`]
]); ])
const buildImportES6 = _.cond([ const buildImportES6 = _.cond([
[isImportAsterisk, d => `import * as ${d.alias} from '${d.moduleName}';`], [isImportAsterisk, d => `import * as ${d.alias} from '${d.moduleName}';`],
[_.matches({member: 'default'}), d => `import ${d.alias} from '${d.moduleName}';`], [_.matches({member: 'default'}), d => `import ${d.alias} from '${d.moduleName}';`],
[defaultCase, d => `import { ${d.member} as ${d.alias} } from '${d.moduleName}';`] [defaultCase, d => `import { ${d.member} as ${d.alias} } from '${d.moduleName}';`]
]); ])
const buildImportCommonJS = _.cond([ const buildImportCommonJS = _.cond([
[isImportAsterisk, d => `var ${d.alias} = require('${d.moduleName}');`], [isImportAsterisk, d => `var ${d.alias} = require('${d.moduleName}');`],
[defaultCase, d => `var ${d.alias} = require('${d.moduleName}').${d.member};`] [defaultCase, d => `var ${d.alias} = require('${d.moduleName}').${d.member};`]
]); ])
const buildImport = { const buildImport = {
typescript: buildImportTypeScript, typescript: buildImportTypeScript,
@ -77,7 +77,7 @@ const buildImport = {
amd: buildImportCommonJS, amd: buildImportCommonJS,
none: buildImportCommonJS, none: buildImportCommonJS,
jsrt: buildImportCommonJS jsrt: buildImportCommonJS
}; }
module.exports = { module.exports = {
htmlSelfClosingTags, htmlSelfClosingTags,
@ -86,4 +86,4 @@ module.exports = {
shouldUseCreateElement, shouldUseCreateElement,
templates, templates,
buildImport buildImport
}; }

View File

@ -1,22 +1,22 @@
'use strict'; 'use strict'
const cheerio = require('cheerio'); const cheerio = require('cheerio')
const _ = require('lodash'); const _ = require('lodash')
const esprima = require('esprima'); const esprima = require('esprima')
const escodegen = require('escodegen'); const escodegen = require('escodegen')
const reactDOMSupport = require('./reactDOMSupport'); const reactDOMSupport = require('./reactDOMSupport')
const reactNativeSupport = require('./reactNativeSupport'); const reactNativeSupport = require('./reactNativeSupport')
const reactPropTemplates = require('./reactPropTemplates'); const reactPropTemplates = require('./reactPropTemplates')
const rtError = require('./RTCodeError'); const rtError = require('./RTCodeError')
const reactSupport = require('./reactSupport'); const reactSupport = require('./reactSupport')
const templates = reactSupport.templates; const templates = reactSupport.templates
const utils = require('./utils'); const utils = require('./utils')
const validateJS = utils.validateJS; const validateJS = utils.validateJS
const RTCodeError = rtError.RTCodeError; const RTCodeError = rtError.RTCodeError
const repeatTemplate = _.template('_.map(<%= collection %>,<%= repeatFunction %>.bind(<%= repeatBinds %>))'); const repeatTemplate = _.template('_.map(<%= collection %>,<%= repeatFunction %>.bind(<%= repeatBinds %>))')
const ifTemplate = _.template('((<%= condition %>)?(<%= body %>):null)'); const ifTemplate = _.template('((<%= condition %>)?(<%= body %>):null)')
const propsTemplateSimple = _.template('_.assign({}, <%= generatedProps %>, <%= rtProps %>)'); const propsTemplateSimple = _.template('_.assign({}, <%= generatedProps %>, <%= rtProps %>)')
const propsTemplate = _.template('mergeProps( <%= generatedProps %>, <%= rtProps %>)'); const propsTemplate = _.template('mergeProps( <%= generatedProps %>, <%= rtProps %>)')
const propsMergeFunction = `function mergeProps(inline,external) { const propsMergeFunction = `function mergeProps(inline,external) {
var res = _.assign({},inline,external) var res = _.assign({},inline,external)
@ -28,43 +28,43 @@ const propsMergeFunction = `function mergeProps(inline,external) {
} }
return res; return res;
} }
`; `
const classSetTemplate = _.template('_.transform(<%= classSet %>, function(res, value, key){ if(value){ res.push(key); } }, []).join(" ")'); const classSetTemplate = _.template('_.transform(<%= classSet %>, function(res, value, key){ if(value){ res.push(key); } }, []).join(" ")')
function getTagTemplateString(simpleTagTemplate, shouldCreateElement) { function getTagTemplateString(simpleTagTemplate, shouldCreateElement) {
if (simpleTagTemplate) { if (simpleTagTemplate) {
return shouldCreateElement ? 'React.createElement(<%= name %>,<%= props %><%= children %>)' : '<%= name %>(<%= props %><%= children %>)'; return shouldCreateElement ? 'React.createElement(<%= name %>,<%= props %><%= children %>)' : '<%= name %>(<%= props %><%= children %>)'
} }
return shouldCreateElement ? 'React.createElement.apply(this, [<%= name %>,<%= props %><%= children %>])' : '<%= name %>.apply(this, [<%= props %><%= children %>])'; return shouldCreateElement ? 'React.createElement.apply(this, [<%= name %>,<%= props %><%= children %>])' : '<%= name %>.apply(this, [<%= props %><%= children %>])'
} }
const commentTemplate = _.template(' /* <%= data %> */ '); const commentTemplate = _.template(' /* <%= data %> */ ')
const repeatAttr = 'rt-repeat'; const repeatAttr = 'rt-repeat'
const ifAttr = 'rt-if'; const ifAttr = 'rt-if'
const classSetAttr = 'rt-class'; const classSetAttr = 'rt-class'
const classAttr = 'class'; const classAttr = 'class'
const scopeAttr = 'rt-scope'; const scopeAttr = 'rt-scope'
const propsAttr = 'rt-props'; const propsAttr = 'rt-props'
const templateNode = 'rt-template'; const templateNode = 'rt-template'
const virtualNode = 'rt-virtual'; const virtualNode = 'rt-virtual'
const includeNode = 'rt-include'; const includeNode = 'rt-include'
const includeSrcAttr = 'src'; const includeSrcAttr = 'src'
const requireAttr = 'rt-require'; const requireAttr = 'rt-require'
const importAttr = 'rt-import'; const importAttr = 'rt-import'
const statelessAttr = 'rt-stateless'; const statelessAttr = 'rt-stateless'
const preAttr = 'rt-pre'; const preAttr = 'rt-pre'
const reactTemplatesSelfClosingTags = [includeNode]; const reactTemplatesSelfClosingTags = [includeNode]
/** /**
* @param {Options} options * @param {Options} options
* @return {Options} * @return {Options}
*/ */
function getOptions(options) { function getOptions(options) {
options = options || {}; options = options || {}
const defaultOptions = { const defaultOptions = {
version: false, version: false,
force: false, force: false,
@ -73,29 +73,29 @@ function getOptions(options) {
lodashImportPath: 'lodash', lodashImportPath: 'lodash',
native: false, native: false,
nativeTargetVersion: reactNativeSupport.default nativeTargetVersion: reactNativeSupport.default
}; }
const finalOptions = _.defaults({}, options, defaultOptions); const finalOptions = _.defaults({}, options, defaultOptions)
finalOptions.reactImportPath = reactImport(finalOptions); finalOptions.reactImportPath = reactImport(finalOptions)
finalOptions.modules = finalOptions.modules || (finalOptions.native ? 'commonjs' : 'none'); finalOptions.modules = finalOptions.modules || (finalOptions.native ? 'commonjs' : 'none')
const defaultPropTemplates = finalOptions.native ? const defaultPropTemplates = finalOptions.native ?
reactPropTemplates.native[finalOptions.nativeTargetVersion] : reactPropTemplates.native[finalOptions.nativeTargetVersion] :
reactPropTemplates.dom[finalOptions.targetVersion]; reactPropTemplates.dom[finalOptions.targetVersion]
finalOptions.propTemplates = _.defaults({}, options.propTemplates, defaultPropTemplates); finalOptions.propTemplates = _.defaults({}, options.propTemplates, defaultPropTemplates)
return finalOptions; return finalOptions
} }
function reactImport(options) { function reactImport(options) {
if (options.native) { if (options.native) {
return reactNativeSupport[options.nativeTargetVersion].react.module; return reactNativeSupport[options.nativeTargetVersion].react.module
} }
if (!options.reactImportPath) { if (!options.reactImportPath) {
const isNewReact = _.includes(['0.14.0', '0.15.0', '15.0.0', '15.0.1'], options.targetVersion); const isNewReact = _.includes(['0.14.0', '0.15.0', '15.0.0', '15.0.1'], options.targetVersion)
return isNewReact ? 'react' : 'react/addons'; return isNewReact ? 'react' : 'react/addons'
} }
return options.reactImportPath; return options.reactImportPath
} }
/** /**
@ -106,66 +106,66 @@ function reactImport(options) {
* @return {string} * @return {string}
*/ */
function generateInjectedFunc(context, namePrefix, body, params) { function generateInjectedFunc(context, namePrefix, body, params) {
params = params || context.boundParams; params = params || context.boundParams
const funcName = namePrefix.replace(',', '') + (context.injectedFunctions.length + 1); const funcName = namePrefix.replace(',', '') + (context.injectedFunctions.length + 1)
const funcText = `function ${funcName}(${params.join(',')}) { const funcText = `function ${funcName}(${params.join(',')}) {
${body} ${body}
} }
`; `
context.injectedFunctions.push(funcText); context.injectedFunctions.push(funcText)
return funcName; return funcName
} }
function generateTemplateProps(node, context) { function generateTemplateProps(node, context) {
let templatePropCount = 0; let templatePropCount = 0
const propTemplateDefinition = context.options.propTemplates[node.name]; const propTemplateDefinition = context.options.propTemplates[node.name]
const propertiesTemplates = _(node.children) const propertiesTemplates = _(node.children)
.map((child, index) => { .map((child, index) => {
let templateProp = null; let templateProp = null
if (child.name === templateNode) { // Generic explicit template tag if (child.name === templateNode) { // Generic explicit template tag
if (!_.has(child.attribs, 'prop')) { if (!_.has(child.attribs, 'prop')) {
throw RTCodeError.build(context, child, 'rt-template must have a prop attribute'); throw RTCodeError.build(context, child, 'rt-template must have a prop attribute')
} }
if (_.filter(child.children, {type: 'tag'}).length !== 1) { if (_.filter(child.children, {type: 'tag'}).length !== 1) {
throw RTCodeError.build(context, child, "'rt-template' should have a single non-text element as direct child"); throw RTCodeError.build(context, child, "'rt-template' should have a single non-text element as direct child")
} }
const childTemplate = _.find(context.options.propTemplates, {prop: child.attribs.prop}) || {arguments: []}; const childTemplate = _.find(context.options.propTemplates, {prop: child.attribs.prop}) || {arguments: []}
templateProp = { templateProp = {
prop: child.attribs.prop, prop: child.attribs.prop,
arguments: (child.attribs.arguments ? child.attribs.arguments.split(',') : childTemplate.arguments) || [] arguments: (child.attribs.arguments ? child.attribs.arguments.split(',') : childTemplate.arguments) || []
}; }
} else if (propTemplateDefinition && propTemplateDefinition[child.name]) { // Implicit child template from configuration } else if (propTemplateDefinition && propTemplateDefinition[child.name]) { // Implicit child template from configuration
templateProp = { templateProp = {
prop: propTemplateDefinition[child.name].prop, prop: propTemplateDefinition[child.name].prop,
arguments: child.attribs.arguments ? child.attribs.arguments.split(',') : propTemplateDefinition[child.name].arguments arguments: child.attribs.arguments ? child.attribs.arguments.split(',') : propTemplateDefinition[child.name].arguments
}; }
} }
if (templateProp) { if (templateProp) {
_.assign(templateProp, {childIndex: index - templatePropCount++, content: _.find(child.children, {type: 'tag'})}); _.assign(templateProp, {childIndex: index - templatePropCount++, content: _.find(child.children, {type: 'tag'})})
} }
return templateProp; return templateProp
}) })
.compact() .compact()
.value(); .value()
return _.transform(propertiesTemplates, (props, templateProp) => { return _.transform(propertiesTemplates, (props, templateProp) => {
const functionParams = _.values(context.boundParams).concat(templateProp.arguments); const functionParams = _.values(context.boundParams).concat(templateProp.arguments)
const oldBoundParams = context.boundParams; const oldBoundParams = context.boundParams
context.boundParams = context.boundParams.concat(templateProp.arguments); context.boundParams = context.boundParams.concat(templateProp.arguments)
const functionBody = 'return ' + convertHtmlToReact(templateProp.content, context); const functionBody = 'return ' + convertHtmlToReact(templateProp.content, context)
context.boundParams = oldBoundParams; context.boundParams = oldBoundParams
const generatedFuncName = generateInjectedFunc(context, templateProp.prop, functionBody, functionParams); const generatedFuncName = generateInjectedFunc(context, templateProp.prop, functionBody, functionParams)
props[templateProp.prop] = genBind(generatedFuncName, _.values(context.boundParams)); props[templateProp.prop] = genBind(generatedFuncName, _.values(context.boundParams))
// Remove the template child from the children definition. // Remove the template child from the children definition.
node.children.splice(templateProp.childIndex, 1); node.children.splice(templateProp.childIndex, 1)
}, {}); }, {})
} }
/** /**
@ -174,60 +174,70 @@ function generateTemplateProps(node, context) {
* @return {string} * @return {string}
*/ */
function generateProps(node, context) { function generateProps(node, context) {
const props = {}; const props = {}
_.forOwn(node.attribs, (val, key) => { _.forOwn(node.attribs, (val, key) => {
const propKey = reactSupport.attributesMapping[key.toLowerCase()] || key; const propKey = reactSupport.attributesMapping[key.toLowerCase()] || key
if (props.hasOwnProperty(propKey) && propKey !== reactSupport.classNameProp) { if (props.hasOwnProperty(propKey) && propKey !== reactSupport.classNameProp) {
throw RTCodeError.build(context, node, `duplicate definition of ${propKey} ${JSON.stringify(node.attribs)}`); throw RTCodeError.build(context, node, `duplicate definition of ${propKey} ${JSON.stringify(node.attribs)}`)
} }
if (_.startsWith(key, 'on') && !utils.isStringOnlyCode(val)) { if (_.startsWith(key, 'on') && !utils.isStringOnlyCode(val)) {
props[propKey] = handleEventHandler(val, context, node, key); props[propKey] = handleEventHandler(val, context, node, key)
} else if (key === 'style' && !utils.isStringOnlyCode(val)) { } else if (key === 'style' && !utils.isStringOnlyCode(val)) {
props[propKey] = handleStyleProp(val, node, context); props[propKey] = handleStyleProp(val, node, context)
} else if (propKey === reactSupport.classNameProp) { } else if (propKey === reactSupport.classNameProp) {
// Processing for both class and rt-class conveniently return strings that // Processing for both class and rt-class conveniently return strings that
// represent JS expressions, each evaluating to a space-separated set of class names. // represent JS expressions, each evaluating to a space-separated set of class names.
// We can just join them with another space here. // We can just join them with another space here.
const existing = props[propKey] ? `${props[propKey]} + " " + ` : ''; const existing = props[propKey] ? `${props[propKey]} + " " + ` : ''
if (key === classSetAttr) { if (key === classSetAttr) {
props[propKey] = existing + classSetTemplate({classSet: val}); props[propKey] = existing + classSetTemplate({classSet: val})
} else if (key === classAttr || key === reactSupport.classNameProp) { } else if (key === classAttr || key === reactSupport.classNameProp) {
props[propKey] = existing + utils.convertText(node, context, val.trim()); props[propKey] = existing + utils.convertText(node, context, val.trim())
} }
} else if (!_.startsWith(key, 'rt-')) { } else if (!_.startsWith(key, 'rt-')) {
props[propKey] = utils.convertText(node, context, val.trim()); props[propKey] = utils.convertText(node, context, val.trim())
} }
}); })
_.assign(props, generateTemplateProps(node, context)); _.assign(props, generateTemplateProps(node, context))
// map 'className' back into 'class' for custom elements // map 'className' back into 'class' for custom elements
if (props[reactSupport.classNameProp] && isCustomElement(node.name)) { if (props[reactSupport.classNameProp] && isCustomElement(node.name)) {
props[classAttr] = props[reactSupport.classNameProp]; props[classAttr] = props[reactSupport.classNameProp]
delete props[reactSupport.classNameProp]; delete props[reactSupport.classNameProp]
} }
const propStr = _.map(props, (v, k) => `${JSON.stringify(k)} : ${v}`).join(','); const propStr = _.map(props, (v, k) => `${JSON.stringify(k)} : ${v}`).join(',')
return `{${propStr}}`; return `{${propStr}}`
} }
function handleEventHandler(val, context, node, key) { function handleEventHandler(val, context, node, key) {
const funcParts = val.split('=>'); let handlerString
if (funcParts.length !== 2) { if (_.startsWith(val, 'this.')) {
throw RTCodeError.build(context, node, `when using 'on' events, use lambda '(p1,p2)=>body' notation or use {} to return a callback function. error: [${key}='${val}']`); if (context.options.autobind) {
handlerString = `${val}.bind(this)`
} else {
throw RTCodeError.build(context, node, "'this.handler' syntax allowed only when the --autobind is on, use {} to return a callback function.")
}
} else {
const funcParts = val.split('=>')
if (funcParts.length !== 2) {
throw RTCodeError.build(context, node, `when using 'on' events, use lambda '(p1,p2)=>body' notation or 'this.handler'; otherwise use {} to return a callback function. error: [${key}='${val}']`)
}
const evtParams = funcParts[0].replace('(', '').replace(')', '').trim()
const funcBody = funcParts[1].trim()
let params = context.boundParams
if (evtParams.trim() !== '') {
params = params.concat([evtParams.trim()])
}
const generatedFuncName = generateInjectedFunc(context, key, funcBody, params)
handlerString = genBind(generatedFuncName, context.boundParams)
} }
const evtParams = funcParts[0].replace('(', '').replace(')', '').trim(); return handlerString
const funcBody = funcParts[1].trim();
let params = context.boundParams;
if (evtParams.trim() !== '') {
params = params.concat([evtParams.trim()]);
}
const generatedFuncName = generateInjectedFunc(context, key, funcBody, params);
return genBind(generatedFuncName, context.boundParams);
} }
function genBind(func, args) { function genBind(func, args) {
const bindArgs = ['this'].concat(args); const bindArgs = ['this'].concat(args)
return `${func}.bind(${bindArgs.join(',')})`; return `${func}.bind(${bindArgs.join(',')})`
} }
function handleStyleProp(val, node, context) { function handleStyleProp(val, node, context) {
@ -236,17 +246,17 @@ function handleStyleProp(val, node, context) {
.map(_.trim) .map(_.trim)
.filter(i => _.includes(i, ':')) .filter(i => _.includes(i, ':'))
.map(i => { .map(i => {
const pair = i.split(':'); const pair = i.split(':')
const key = pair[0].trim(); const key = pair[0].trim()
if (/\{|}/g.test(key)) { if (/\{|}/g.test(key)) {
throw RTCodeError.build(context, node, 'style attribute keys cannot contain { } expressions'); throw RTCodeError.build(context, node, 'style attribute keys cannot contain { } expressions')
} }
const value = pair.slice(1).join(':').trim(); const value = pair.slice(1).join(':').trim()
const parsedKey = /(^-moz-)|(^-o-)|(^-webkit-)/ig.test(key) ? _.upperFirst(_.camelCase(key)) : _.camelCase(key); const parsedKey = /(^-moz-)|(^-o-)|(^-webkit-)/ig.test(key) ? _.upperFirst(_.camelCase(key)) : _.camelCase(key)
return parsedKey + ' : ' + utils.convertText(node, context, value.trim()); return parsedKey + ' : ' + utils.convertText(node, context, value.trim())
}) })
.join(','); .join(',')
return `{${styleStr}}`; return `{${styleStr}}`
} }
/** /**
@ -256,19 +266,19 @@ function handleStyleProp(val, node, context) {
*/ */
function convertTagNameToConstructor(tagName, context) { function convertTagNameToConstructor(tagName, context) {
if (context.options.native) { if (context.options.native) {
const targetSupport = reactNativeSupport[context.options.nativeTargetVersion]; const targetSupport = reactNativeSupport[context.options.nativeTargetVersion]
return _.includes(targetSupport.components, tagName) ? `${targetSupport.reactNative.name}.${tagName}` : tagName; return _.includes(targetSupport.components, tagName) ? `${targetSupport.reactNative.name}.${tagName}` : tagName
} }
let isHtmlTag = _.includes(reactDOMSupport[context.options.targetVersion], tagName) || isCustomElement(tagName); let isHtmlTag = _.includes(reactDOMSupport[context.options.targetVersion], tagName) || isCustomElement(tagName)
if (reactSupport.shouldUseCreateElement(context)) { if (reactSupport.shouldUseCreateElement(context)) {
isHtmlTag = isHtmlTag || tagName.match(/^\w+(-\w+)+$/); isHtmlTag = isHtmlTag || tagName.match(/^\w+(-\w+)+$/)
return isHtmlTag ? `'${tagName}'` : tagName; return isHtmlTag ? `'${tagName}'` : tagName
} }
return isHtmlTag ? `React.DOM.${tagName}` : tagName; return isHtmlTag ? `React.DOM.${tagName}` : tagName
} }
function isCustomElement(tagName) { function isCustomElement(tagName) {
return tagName.match(/^\w+(-\w+)+$/); return tagName.match(/^\w+(-\w+)+$/)
} }
/** /**
@ -281,11 +291,11 @@ function defaultContext(html, options, reportContext) {
const defaultDefines = [ const defaultDefines = [
{moduleName: options.reactImportPath, alias: 'React', member: '*'}, {moduleName: options.reactImportPath, alias: 'React', member: '*'},
{moduleName: options.lodashImportPath, alias: '_', member: '*'} {moduleName: options.lodashImportPath, alias: '_', member: '*'}
]; ]
if (options.native) { if (options.native) {
const targetSupport = reactNativeSupport[options.nativeTargetVersion]; const targetSupport = reactNativeSupport[options.nativeTargetVersion]
if (targetSupport.reactNative.module !== targetSupport.react.module) { if (targetSupport.reactNative.module !== targetSupport.react.module) {
defaultDefines.splice(0, 0, {moduleName: targetSupport.reactNative.module, alias: targetSupport.reactNative.name, member: '*'}); defaultDefines.splice(0, 0, {moduleName: targetSupport.reactNative.module, alias: targetSupport.reactNative.name, member: '*'})
} }
} }
return { return {
@ -295,7 +305,7 @@ function defaultContext(html, options, reportContext) {
options, options,
defines: options.defines ? _.clone(options.defines) : defaultDefines, defines: options.defines ? _.clone(options.defines) : defaultDefines,
reportContext reportContext
}; }
} }
/** /**
@ -303,7 +313,7 @@ function defaultContext(html, options, reportContext) {
* @return {boolean} * @return {boolean}
*/ */
function hasNonSimpleChildren(node) { function hasNonSimpleChildren(node) {
return _.some(node.children, child => child.type === 'tag' && child.attribs[repeatAttr]); return _.some(node.children, child => child.type === 'tag' && child.attribs[repeatAttr])
} }
/** /**
@ -312,7 +322,7 @@ function hasNonSimpleChildren(node) {
* @return {string} * @return {string}
*/ */
function trimHtmlText(text) { function trimHtmlText(text) {
return text.replace(/^[ \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+|[ \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+$/g, ''); return text.replace(/^[ \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+|[ \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+$/g, '')
} }
/** /**
@ -324,84 +334,84 @@ function convertHtmlToReact(node, context) {
if (node.type === 'tag' || node.type === 'style') { if (node.type === 'tag' || node.type === 'style') {
context = _.defaults({ context = _.defaults({
boundParams: _.clone(context.boundParams) boundParams: _.clone(context.boundParams)
}, context); }, context)
if (node.type === 'tag' && node.name === importAttr) { if (node.type === 'tag' && node.name === importAttr) {
throw RTCodeError.build(context, node, "'rt-import' must be a toplevel node"); throw RTCodeError.build(context, node, "'rt-import' must be a toplevel node")
} }
if (node.type === 'tag' && node.name === includeNode) { if (node.type === 'tag' && node.name === includeNode) {
const srcFile = node.attribs[includeSrcAttr]; const srcFile = node.attribs[includeSrcAttr]
if (!srcFile) { if (!srcFile) {
throw RTCodeError.build(context, node, 'rt-include must supply a source attribute'); throw RTCodeError.build(context, node, 'rt-include must supply a source attribute')
} }
if (!context.options.readFileSync) { if (!context.options.readFileSync) {
throw RTCodeError.build(context, node, 'rt-include needs a readFileSync polyfill on options'); throw RTCodeError.build(context, node, 'rt-include needs a readFileSync polyfill on options')
} }
try { try {
context.html = context.options.readFileSync(srcFile); context.html = context.options.readFileSync(srcFile)
} catch (e) { } catch (e) {
console.error(e); console.error(e)
throw RTCodeError.build(context, node, `rt-include failed to read file '${srcFile}'`); throw RTCodeError.build(context, node, `rt-include failed to read file '${srcFile}'`)
} }
return parseAndConvertHtmlToReact(context.html, context); return parseAndConvertHtmlToReact(context.html, context)
} }
const data = {name: convertTagNameToConstructor(node.name, context)}; const data = {name: convertTagNameToConstructor(node.name, context)}
// Order matters. We need to add the item and itemIndex to context.boundParams before // Order matters. We need to add the item and itemIndex to context.boundParams before
// the rt-scope directive is processed, lest they are not passed to the child scopes // the rt-scope directive is processed, lest they are not passed to the child scopes
if (node.attribs[repeatAttr]) { if (node.attribs[repeatAttr]) {
const arr = node.attribs[repeatAttr].split(' in '); const arr = node.attribs[repeatAttr].split(' in ')
if (arr.length !== 2) { if (arr.length !== 2) {
throw RTCodeError.build(context, node, `rt-repeat invalid 'in' expression '${node.attribs[repeatAttr]}'`); throw RTCodeError.build(context, node, `rt-repeat invalid 'in' expression '${node.attribs[repeatAttr]}'`)
} }
const repeaterParams = arr[0].split(',').map(s => s.trim()); const repeaterParams = arr[0].split(',').map(s => s.trim())
data.item = repeaterParams[0]; data.item = repeaterParams[0]
data.index = repeaterParams[1] || `${data.item}Index`; data.index = repeaterParams[1] || `${data.item}Index`
data.collection = arr[1].trim(); data.collection = arr[1].trim()
const bindParams = [data.item, data.index]; const bindParams = [data.item, data.index]
_.forEach(bindParams, param => { _.forEach(bindParams, param => {
validateJS(param, node, context); validateJS(param, node, context)
}); })
validateJS(`(${data.collection})`, node, context); validateJS(`(${data.collection})`, node, context)
_.forEach(bindParams, param => { _.forEach(bindParams, param => {
if (!_.includes(context.boundParams, param)) { if (!_.includes(context.boundParams, param)) {
context.boundParams.push(param); context.boundParams.push(param)
} }
}); })
} }
if (node.attribs[scopeAttr]) { if (node.attribs[scopeAttr]) {
handleScopeAttribute(node, context, data); handleScopeAttribute(node, context, data)
} }
if (node.attribs[ifAttr]) { if (node.attribs[ifAttr]) {
validateIfAttribute(node, context, data); validateIfAttribute(node, context, data)
data.condition = node.attribs[ifAttr].trim(); data.condition = node.attribs[ifAttr].trim()
if (!node.attribs.key && node.name !== virtualNode) { if (!node.attribs.key && node.name !== virtualNode) {
_.set(node, ['attribs', 'key'], `${node.startIndex}`); _.set(node, ['attribs', 'key'], `${node.startIndex}`)
} }
} }
data.props = generateProps(node, context); data.props = generateProps(node, context)
if (node.attribs[propsAttr]) { if (node.attribs[propsAttr]) {
if (data.props === '{}') { if (data.props === '{}') {
data.props = node.attribs[propsAttr]; data.props = node.attribs[propsAttr]
} else if (!node.attribs.style && !node.attribs.class) { } else if (!node.attribs.style && !node.attribs.class) {
data.props = propsTemplateSimple({generatedProps: data.props, rtProps: node.attribs[propsAttr]}); data.props = propsTemplateSimple({generatedProps: data.props, rtProps: node.attribs[propsAttr]})
} else { } else {
data.props = propsTemplate({generatedProps: data.props, rtProps: node.attribs[propsAttr]}); data.props = propsTemplate({generatedProps: data.props, rtProps: node.attribs[propsAttr]})
if (!_.includes(context.injectedFunctions, propsMergeFunction)) { if (!_.includes(context.injectedFunctions, propsMergeFunction)) {
context.injectedFunctions.push(propsMergeFunction); context.injectedFunctions.push(propsMergeFunction)
} }
} }
} }
if (node.name === virtualNode) { if (node.name === virtualNode) {
const invalidAttributes = _.without(_.keys(node.attribs), scopeAttr, ifAttr, repeatAttr); const invalidAttributes = _.without(_.keys(node.attribs), scopeAttr, ifAttr, repeatAttr)
if (invalidAttributes.length > 0) { if (invalidAttributes.length > 0) {
throw RTCodeError.build(context, node, "<rt-virtual> may not contain attributes other than 'rt-scope', 'rt-if' and 'rt-repeat'"); throw RTCodeError.build(context, node, "<rt-virtual> may not contain attributes other than 'rt-scope', 'rt-if' and 'rt-repeat'")
} }
// provide a key to virtual node children if missing // provide a key to virtual node children if missing
@ -410,52 +420,52 @@ function convertHtmlToReact(node, context) {
.reject('attribs.key') .reject('attribs.key')
.forEach((child, i) => { .forEach((child, i) => {
if (child.type === 'tag' && child.name !== virtualNode) { if (child.type === 'tag' && child.name !== virtualNode) {
_.set(child, ['attribs', 'key'], `${node.startIndex}${i}`); _.set(child, ['attribs', 'key'], `${node.startIndex}${i}`)
} }
}); })
} }
} }
const children = _.map(node.children, child => { const children = _.map(node.children, child => {
const code = convertHtmlToReact(child, context); const code = convertHtmlToReact(child, context)
validateJS(code, child, context); validateJS(code, child, context)
return code; return code
}); })
data.children = utils.concatChildren(children); data.children = utils.concatChildren(children)
if (node.name === virtualNode) { //eslint-disable-line wix-editor/prefer-ternary if (node.name === virtualNode) { //eslint-disable-line wix-editor/prefer-ternary
data.body = `[${_.compact(children).join(',')}]`; data.body = `[${_.compact(children).join(',')}]`
} else { } else {
data.body = _.template(getTagTemplateString(!hasNonSimpleChildren(node), reactSupport.shouldUseCreateElement(context)))(data); data.body = _.template(getTagTemplateString(!hasNonSimpleChildren(node), reactSupport.shouldUseCreateElement(context)))(data)
} }
if (node.attribs[scopeAttr]) { if (node.attribs[scopeAttr]) {
const functionBody = _.values(data.innerScope.innerMapping).join('\n') + `return ${data.body}`; const functionBody = _.values(data.innerScope.innerMapping).join('\n') + `return ${data.body}`
const generatedFuncName = generateInjectedFunc(context, 'scope' + data.innerScope.scopeName, functionBody, _.keys(data.innerScope.outerMapping)); const generatedFuncName = generateInjectedFunc(context, 'scope' + data.innerScope.scopeName, functionBody, _.keys(data.innerScope.outerMapping))
data.body = `${generatedFuncName}.apply(this, [${_.values(data.innerScope.outerMapping).join(',')}])`; data.body = `${generatedFuncName}.apply(this, [${_.values(data.innerScope.outerMapping).join(',')}])`
} }
// Order matters here. Each rt-repeat iteration wraps over the rt-scope, so // Order matters here. Each rt-repeat iteration wraps over the rt-scope, so
// the scope variables are evaluated in context of the current iteration. // the scope variables are evaluated in context of the current iteration.
if (node.attribs[repeatAttr]) { if (node.attribs[repeatAttr]) {
data.repeatFunction = generateInjectedFunc(context, 'repeat' + _.upperFirst(data.item), 'return ' + data.body); data.repeatFunction = generateInjectedFunc(context, 'repeat' + _.upperFirst(data.item), 'return ' + data.body)
data.repeatBinds = ['this'].concat(_.reject(context.boundParams, p => p === data.item || p === data.index || data.innerScope && p in data.innerScope.innerMapping)); data.repeatBinds = ['this'].concat(_.reject(context.boundParams, p => p === data.item || p === data.index || data.innerScope && p in data.innerScope.innerMapping))
data.body = repeatTemplate(data); data.body = repeatTemplate(data)
} }
if (node.attribs[ifAttr]) { if (node.attribs[ifAttr]) {
data.body = ifTemplate(data); data.body = ifTemplate(data)
} }
return data.body; return data.body
} else if (node.type === 'comment') { } else if (node.type === 'comment') {
const sanitizedComment = node.data.split('*/').join('* /'); const sanitizedComment = node.data.split('*/').join('* /')
return commentTemplate({data: sanitizedComment}); return commentTemplate({data: sanitizedComment})
} else if (node.type === 'text') { } else if (node.type === 'text') {
const parentNode = node.parent; const parentNode = node.parent
const overrideNormalize = parentNode !== undefined && (parentNode.name === 'pre' || parentNode.name === 'textarea' || _.has(parentNode.attribs, preAttr)); const overrideNormalize = parentNode !== undefined && (parentNode.name === 'pre' || parentNode.name === 'textarea' || _.has(parentNode.attribs, preAttr))
const normalizeWhitespaces = context.options.normalizeHtmlWhitespace && !overrideNormalize; const normalizeWhitespaces = context.options.normalizeHtmlWhitespace && !overrideNormalize
const text = node.data; const text = node.data
return trimHtmlText(text) ? utils.convertText(node, context, text, normalizeWhitespaces) : ''; return trimHtmlText(text) ? utils.convertText(node, context, text, normalizeWhitespaces) : ''
} }
} }
@ -480,21 +490,21 @@ function parseScopeSyntax(text) {
// //
// regex = capture(expression) + as + capture(id) + optional_spaces + semicolon + optional_spaces // regex = capture(expression) + as + capture(id) + optional_spaces + semicolon + optional_spaces
const regex = RegExp("((?:(?:\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^\"']*?))*?) as(?: )+([$_a-zA-Z]+[$_a-zA-Z0-9]*)(?: )*(?:;|$)(?: )*", 'g'); const regex = RegExp("((?:(?:\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^\"']*?))*?) as(?: )+([$_a-zA-Z]+[$_a-zA-Z0-9]*)(?: )*(?:;|$)(?: )*", 'g')
const res = []; const res = []
do { do {
const idx = regex.lastIndex; const idx = regex.lastIndex
const match = regex.exec(text); const match = regex.exec(text)
if (regex.lastIndex === idx || match === null) { if (regex.lastIndex === idx || match === null) {
throw text.substr(idx); throw text.substr(idx)
} }
if (match.index === regex.lastIndex) { if (match.index === regex.lastIndex) {
regex.lastIndex++; regex.lastIndex++
} }
res.push({expression: match[1].trim(), identifier: match[2]}); res.push({expression: match[1].trim(), identifier: match[2]})
} while (regex.lastIndex < text.length); } while (regex.lastIndex < text.length)
return res; return res
} }
function handleScopeAttribute(node, context, data) { function handleScopeAttribute(node, context, data) {
@ -502,110 +512,110 @@ function handleScopeAttribute(node, context, data) {
scopeName: '', scopeName: '',
innerMapping: {}, innerMapping: {},
outerMapping: {} outerMapping: {}
}; }
data.innerScope.outerMapping = _.zipObject(context.boundParams, context.boundParams); data.innerScope.outerMapping = _.zipObject(context.boundParams, context.boundParams)
let scopes; let scopes
try { try {
scopes = parseScopeSyntax(node.attribs[scopeAttr]); scopes = parseScopeSyntax(node.attribs[scopeAttr])
} catch (scopePart) { } catch (scopePart) {
throw RTCodeError.build(context, node, `invalid scope part '${scopePart}'`); throw RTCodeError.build(context, node, `invalid scope part '${scopePart}'`)
} }
scopes.forEach(({expression, identifier}) => { scopes.forEach(({expression, identifier}) => {
validateJS(identifier, node, context); validateJS(identifier, node, context)
// this adds both parameters to the list of parameters passed further down // this adds both parameters to the list of parameters passed further down
// the scope chain, as well as variables that are locally bound before any // the scope chain, as well as variables that are locally bound before any
// function call, as with the ones we generate for rt-scope. // function call, as with the ones we generate for rt-scope.
if (!_.includes(context.boundParams, identifier)) { if (!_.includes(context.boundParams, identifier)) {
context.boundParams.push(identifier); context.boundParams.push(identifier)
} }
data.innerScope.scopeName += _.upperFirst(identifier); data.innerScope.scopeName += _.upperFirst(identifier)
data.innerScope.innerMapping[identifier] = `var ${identifier} = ${expression};`; data.innerScope.innerMapping[identifier] = `var ${identifier} = ${expression};`
validateJS(data.innerScope.innerMapping[identifier], node, context); validateJS(data.innerScope.innerMapping[identifier], node, context)
}); })
} }
function validateIfAttribute(node, context, data) { function validateIfAttribute(node, context, data) {
const innerMappingKeys = _.keys(data.innerScope && data.innerScope.innerMapping || {}); const innerMappingKeys = _.keys(data.innerScope && data.innerScope.innerMapping || {})
let ifAttributeTree = null; let ifAttributeTree = null
try { try {
ifAttributeTree = esprima.parse(node.attribs[ifAttr]); ifAttributeTree = esprima.parse(node.attribs[ifAttr])
} catch (e) { } catch (e) {
throw new RTCodeError(e.message, e.index, -1); throw new RTCodeError(e.message, e.index, -1)
} }
if (ifAttributeTree && ifAttributeTree.body && ifAttributeTree.body.length === 1 && ifAttributeTree.body[0].type === 'ExpressionStatement') { if (ifAttributeTree && ifAttributeTree.body && ifAttributeTree.body.length === 1 && ifAttributeTree.body[0].type === 'ExpressionStatement') {
// make sure that rt-if does not use an inner mapping // make sure that rt-if does not use an inner mapping
if (ifAttributeTree.body[0].expression && utils.usesScopeName(innerMappingKeys, ifAttributeTree.body[0].expression)) { if (ifAttributeTree.body[0].expression && utils.usesScopeName(innerMappingKeys, ifAttributeTree.body[0].expression)) {
throw RTCodeError.buildFormat(context, node, "invalid scope mapping used in if part '%s'", node.attribs[ifAttr]); throw RTCodeError.buildFormat(context, node, "invalid scope mapping used in if part '%s'", node.attribs[ifAttr])
} }
} else { } else {
throw RTCodeError.buildFormat(context, node, "invalid if part '%s'", node.attribs[ifAttr]); throw RTCodeError.buildFormat(context, node, "invalid if part '%s'", node.attribs[ifAttr])
} }
} }
function handleSelfClosingHtmlTags(nodes) { function handleSelfClosingHtmlTags(nodes) {
return _.flatMap(nodes, node => { return _.flatMap(nodes, node => {
let externalNodes = []; let externalNodes = []
node.children = handleSelfClosingHtmlTags(node.children); node.children = handleSelfClosingHtmlTags(node.children)
if (node.type === 'tag' && (_.includes(reactSupport.htmlSelfClosingTags, node.name) || if (node.type === 'tag' && (_.includes(reactSupport.htmlSelfClosingTags, node.name) ||
_.includes(reactTemplatesSelfClosingTags, node.name))) { _.includes(reactTemplatesSelfClosingTags, node.name))) {
externalNodes = _.filter(node.children, {type: 'tag'}); externalNodes = _.filter(node.children, {type: 'tag'})
_.forEach(externalNodes, i => {i.parent = node;}); _.forEach(externalNodes, i => {i.parent = node})
node.children = _.reject(node.children, {type: 'tag'}); node.children = _.reject(node.children, {type: 'tag'})
} }
return [node].concat(externalNodes); return [node].concat(externalNodes)
}); })
} }
function handleRequire(tag, context) { function handleRequire(tag, context) {
let moduleName; let moduleName
let alias; let alias
let member; let member
if (tag.children.length) { if (tag.children.length) {
throw RTCodeError.build(context, tag, `'${requireAttr}' may have no children`); throw RTCodeError.build(context, tag, `'${requireAttr}' may have no children`)
} else if (tag.attribs.dependency && tag.attribs.as) { } else if (tag.attribs.dependency && tag.attribs.as) {
moduleName = tag.attribs.dependency; moduleName = tag.attribs.dependency
member = '*'; member = '*'
alias = tag.attribs.as; alias = tag.attribs.as
} }
if (!moduleName) { if (!moduleName) {
throw RTCodeError.build(context, tag, `'${requireAttr}' needs 'dependency' and 'as' attributes`); throw RTCodeError.build(context, tag, `'${requireAttr}' needs 'dependency' and 'as' attributes`)
} }
context.defines.push({moduleName, member, alias}); context.defines.push({moduleName, member, alias})
} }
function handleImport(tag, context) { function handleImport(tag, context) {
let moduleName; let moduleName
let alias; let alias
let member; let member
if (tag.children.length) { if (tag.children.length) {
throw RTCodeError.build(context, tag, `'${importAttr}' may have no children`); throw RTCodeError.build(context, tag, `'${importAttr}' may have no children`)
} else if (tag.attribs.name && tag.attribs.from) { } else if (tag.attribs.name && tag.attribs.from) {
moduleName = tag.attribs.from; moduleName = tag.attribs.from
member = tag.attribs.name; member = tag.attribs.name
alias = tag.attribs.as; alias = tag.attribs.as
if (!alias) { if (!alias) {
if (member === '*') { if (member === '*') {
throw RTCodeError.build(context, tag, "'*' imports must have an 'as' attribute"); throw RTCodeError.build(context, tag, "'*' imports must have an 'as' attribute")
} else if (member === 'default') { } else if (member === 'default') {
throw RTCodeError.build(context, tag, "default imports must have an 'as' attribute"); throw RTCodeError.build(context, tag, "default imports must have an 'as' attribute")
} }
alias = member; alias = member
} }
} }
if (!moduleName) { if (!moduleName) {
throw RTCodeError.build(context, tag, `'${importAttr}' needs 'name' and 'from' attributes`); throw RTCodeError.build(context, tag, `'${importAttr}' needs 'name' and 'from' attributes`)
} }
context.defines.push({moduleName, member, alias}); context.defines.push({moduleName, member, alias})
} }
function convertTemplateToReact(html, options) { function convertTemplateToReact(html, options) {
const context = require('./context'); const context = require('./context')
return convertRT(html, context, options); return convertRT(html, context, options)
} }
function parseAndConvertHtmlToReact(html, context) { function parseAndConvertHtmlToReact(html, context) {
@ -614,36 +624,36 @@ function parseAndConvertHtmlToReact(html, context) {
lowerCaseAttributeNames: false, lowerCaseAttributeNames: false,
xmlMode: true, xmlMode: true,
withStartIndices: true withStartIndices: true
}); })
utils.validate(context.options, context, context.reportContext, rootNode.root()[0]); utils.validate(context.options, context, context.reportContext, rootNode.root()[0])
let rootTags = _.filter(rootNode.root()[0].children, {type: 'tag'}); let rootTags = _.filter(rootNode.root()[0].children, {type: 'tag'})
rootTags = handleSelfClosingHtmlTags(rootTags); rootTags = handleSelfClosingHtmlTags(rootTags)
if (!rootTags || rootTags.length === 0) { if (!rootTags || rootTags.length === 0) {
throw new RTCodeError('Document should have a root element'); throw new RTCodeError('Document should have a root element')
} }
let firstTag = null; let firstTag = null
_.forEach(rootTags, tag => { _.forEach(rootTags, tag => {
if (tag.name === requireAttr) { if (tag.name === requireAttr) {
handleRequire(tag, context); handleRequire(tag, context)
} else if (tag.name === importAttr) { } else if (tag.name === importAttr) {
handleImport(tag, context); handleImport(tag, context)
} else if (firstTag === null) { } else if (firstTag === null) {
firstTag = tag; firstTag = tag
if (_.hasIn(tag, ['attribs', statelessAttr])) { if (_.hasIn(tag, ['attribs', statelessAttr])) {
context.stateless = true; context.stateless = true
} }
} else { } else {
throw RTCodeError.build(context, tag, 'Document should have no more than a single root element'); throw RTCodeError.build(context, tag, 'Document should have no more than a single root element')
} }
}); })
if (firstTag === null) { if (firstTag === null) {
throw RTCodeError.build(context, rootNode.root()[0], 'Document should have a single root element'); throw RTCodeError.build(context, rootNode.root()[0], 'Document should have a single root element')
} else if (firstTag.name === virtualNode) { } else if (firstTag.name === virtualNode) {
throw RTCodeError.build(context, firstTag, `Document should not have <${virtualNode}> as root element`); throw RTCodeError.build(context, firstTag, `Document should not have <${virtualNode}> as root element`)
} else if (_.includes(_.keys(firstTag.attribs), repeatAttr)) { } else if (_.includes(_.keys(firstTag.attribs), repeatAttr)) {
throw RTCodeError.build(context, firstTag, "root element may not have a 'rt-repeat' attribute"); throw RTCodeError.build(context, firstTag, "root element may not have a 'rt-repeat' attribute")
} }
return convertHtmlToReact(firstTag, context); return convertHtmlToReact(firstTag, context)
} }
/** /**
@ -653,22 +663,22 @@ function parseAndConvertHtmlToReact(html, context) {
* @return {string} * @return {string}
*/ */
function convertRT(html, reportContext, options) { function convertRT(html, reportContext, options) {
options = getOptions(options); options = getOptions(options)
const context = defaultContext(html, options, reportContext); const context = defaultContext(html, options, reportContext)
const body = parseAndConvertHtmlToReact(html, context); const body = parseAndConvertHtmlToReact(html, context)
const injectedFunctions = context.injectedFunctions.join('\n'); const injectedFunctions = context.injectedFunctions.join('\n')
const statelessParams = context.stateless ? 'props, context' : ''; const statelessParams = context.stateless ? 'props, context' : ''
const renderFunction = `function(${statelessParams}) { ${injectedFunctions}return ${body} }`; const renderFunction = `function(${statelessParams}) { ${injectedFunctions}return ${body} }`
const requirePaths = _.map(context.defines, d => `"${d.moduleName}"`).join(','); const requirePaths = _.map(context.defines, d => `"${d.moduleName}"`).join(',')
const requireNames = _.map(context.defines, d => `${d.alias}`).join(','); const requireNames = _.map(context.defines, d => `${d.alias}`).join(',')
const AMDArguments = _.map(context.defines, (d, i) => (d.member === '*' ? `${d.alias}` : `$${i}`)).join(','); //eslint-disable-line const AMDArguments = _.map(context.defines, (d, i) => d.member === '*' ? `${d.alias}` : `$${i}`).join(',') //eslint-disable-line no-confusing-arrow
const AMDSubstitutions = _.map(context.defines, (d, i) => (d.member === '*' ? null : `var ${d.alias} = $${i}.${d.member};`)).join('\n'); //eslint-disable-line const AMDSubstitutions = _.map(context.defines, (d, i) => d.member === '*' ? null : `var ${d.alias} = $${i}.${d.member};`).join('\n') //eslint-disable-line no-confusing-arrow
const buildImport = reactSupport.buildImport[options.modules] || reactSupport.buildImport.commonjs; const buildImport = reactSupport.buildImport[options.modules] || reactSupport.buildImport.commonjs
const requires = _.map(context.defines, buildImport).join('\n'); const requires = _.map(context.defines, buildImport).join('\n')
const header = options.flow ? '/* @flow */\n' : ''; const header = options.flow ? '/* @flow */\n' : ''
const vars = header + requires; const vars = header + requires
const data = { const data = {
renderFunction, renderFunction,
requireNames, requireNames,
@ -677,37 +687,37 @@ function convertRT(html, reportContext, options) {
AMDSubstitutions, AMDSubstitutions,
vars, vars,
name: options.name name: options.name
};
let code = templates[options.modules](data);
if (options.modules !== 'typescript' && options.modules !== 'jsrt') {
code = parseJS(code, options);
} }
return code; let code = templates[options.modules](data)
if (options.modules !== 'typescript' && options.modules !== 'jsrt') {
code = parseJS(code, options)
}
return code
} }
function parseJS(code, options) { function parseJS(code, options) {
try { try {
let tree = esprima.parse(code, {range: true, tokens: true, comment: true, sourceType: 'module'}); let tree = esprima.parse(code, {range: true, tokens: true, comment: true, sourceType: 'module'})
// fix for https://github.com/wix/react-templates/issues/157 // fix for https://github.com/wix/react-templates/issues/157
// do not include comments for es6 modules due to bug in dependency "escodegen" // do not include comments for es6 modules due to bug in dependency "escodegen"
// to be removed when https://github.com/estools/escodegen/issues/263 will be fixed // to be removed when https://github.com/estools/escodegen/issues/263 will be fixed
// remove also its test case "test/data/comment.rt.es6.js" // remove also its test case "test/data/comment.rt.es6.js"
if (options.modules !== 'es6') { if (options.modules !== 'es6') {
tree = escodegen.attachComments(tree, tree.comments, tree.tokens); tree = escodegen.attachComments(tree, tree.comments, tree.tokens)
} }
return escodegen.generate(tree, {comment: true}); return escodegen.generate(tree, {comment: true})
} catch (e) { } catch (e) {
throw new RTCodeError(e.message, e.index, -1); throw new RTCodeError(e.message, e.index, -1)
} }
} }
function convertJSRTToJS(text, reportContext, options) { function convertJSRTToJS(text, reportContext, options) {
options = getOptions(options); options = getOptions(options)
options.modules = 'jsrt'; options.modules = 'jsrt'
const templateMatcherJSRT = /<template>([^]*?)<\/template>/gm; const templateMatcherJSRT = /<template>([^]*?)<\/template>/gm
const code = text.replace(templateMatcherJSRT, (template, html) => convertRT(html, reportContext, options).replace(/;$/, '')); const code = text.replace(templateMatcherJSRT, (template, html) => convertRT(html, reportContext, options).replace(/;$/, ''))
return parseJS(code, options); return parseJS(code, options)
} }
module.exports = { module.exports = {
@ -716,4 +726,4 @@ module.exports = {
convertJSRTToJS, convertJSRTToJS,
RTCodeError, RTCodeError,
normalizeName: utils.normalizeName normalizeName: utils.normalizeName
}; }

View File

@ -1,4 +1,4 @@
'use strict'; 'use strict'
const map = { const map = {
/*flex*/ /*flex*/
alignItems: 'string', // "enum('flex-start', 'flex-end', 'center', 'stretch')", alignItems: 'string', // "enum('flex-start', 'flex-end', 'center', 'stretch')",
@ -42,6 +42,6 @@ const map = {
overflow: 'string', // enum('visible', 'hidden') overflow: 'string', // enum('visible', 'hidden')
tintColor: 'string', tintColor: 'string',
opacity: 'number' opacity: 'number'
}; }
module.exports = map; module.exports = map

View File

@ -1,47 +1,47 @@
'use strict'; 'use strict'
const css = require('css'); const css = require('css')
const _ = require('lodash'); const _ = require('lodash')
const rtnData = require('./rt-style-support-data.js'); const rtnData = require('./rt-style-support-data.js')
const templateCommonJSTemplate = _.template( const templateCommonJSTemplate = _.template(
`'use strict'; `'use strict';
var style = <%= body %>; var style = <%= body %>;
module.exports = style; module.exports = style;
`); `)
function convert(text) { function convert(text) {
return templateCommonJSTemplate({body: convertBody(text)}); return templateCommonJSTemplate({body: convertBody(text)})
} }
function convertBody(text) { function convertBody(text) {
//source //source
const obj = css.parse(text, {silent: false}); const obj = css.parse(text, {silent: false})
const result = _.reduce(obj.stylesheet.rules, processRule2, {}); const result = _.reduce(obj.stylesheet.rules, processRule2, {})
return JSON.stringify(result, undefined, 2); return JSON.stringify(result, undefined, 2)
} }
function processRule2(result, rule) { function processRule2(result, rule) {
const name = rule.selectors[0].substring(1); const name = rule.selectors[0].substring(1)
result[name] = _.reduce(rule.declarations, processDeclaration, {}); result[name] = _.reduce(rule.declarations, processDeclaration, {})
return result; return result
} }
function processDeclaration(result, dec) { function processDeclaration(result, dec) {
const prop = _.camelCase(dec.property); const prop = _.camelCase(dec.property)
result[prop] = convertValue(prop, dec.value); result[prop] = convertValue(prop, dec.value)
return result; return result
} }
function convertValue(p, v) { function convertValue(p, v) {
if (rtnData[p] === 'string') { if (rtnData[p] === 'string') {
return v; return v
} }
// TODO remove units // TODO remove units
return parseInt(v.match(/(\d+)/g)[0], 10); return parseInt(v.match(/(\d+)/g)[0], 10)
} }
module.exports = { module.exports = {
convert, convert,
convertBody convertBody
}; }

View File

@ -1,16 +1,16 @@
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @param {CONTEXT} context * @param {CONTEXT} context
* @return {number} * @return {number}
*/ */
function printResults(context) { function printResults(context) {
const warnings = context.getMessages(); const warnings = context.getMessages()
const out = require(`./formatters/${context.options.format}`)(warnings); const out = require(`./formatters/${context.options.format}`)(warnings)
context.report(out); context.report(out)
const grouped = _.groupBy(warnings, 'level'); const grouped = _.groupBy(warnings, 'level')
return grouped.ERROR ? grouped.ERROR.length : 0; return grouped.ERROR ? grouped.ERROR.length : 0
} }
module.exports = {printResults}; module.exports = {printResults}

View File

@ -1,9 +1,9 @@
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const esprima = require('esprima'); const esprima = require('esprima')
const normalizeHtmlWhitespace = require('normalize-html-whitespace'); const normalizeHtmlWhitespace = require('normalize-html-whitespace')
const rtError = require('./RTCodeError'); const rtError = require('./RTCodeError')
const RTCodeError = rtError.RTCodeError; const RTCodeError = rtError.RTCodeError
/** /**
* @param {string} code * @param {string} code
@ -12,9 +12,9 @@ const RTCodeError = rtError.RTCodeError;
*/ */
function validateJS(code, node, context) { function validateJS(code, node, context) {
try { try {
esprima.parse(code); esprima.parse(code)
} catch (e) { } catch (e) {
throw RTCodeError.build(context, node, e.description); throw RTCodeError.build(context, node, e.description)
} }
} }
@ -23,7 +23,7 @@ function validateJS(code, node, context) {
* @return {string} * @return {string}
*/ */
function normalizeName(name) { function normalizeName(name) {
return name.replace(/-/g, '_'); return name.replace(/-/g, '_')
} }
/** /**
@ -31,7 +31,7 @@ function normalizeName(name) {
* @return {boolean} * @return {boolean}
*/ */
function isStringOnlyCode(txt) { function isStringOnlyCode(txt) {
return /^\s*\{.*}\s*$/g.test(txt); return /^\s*\{.*}\s*$/g.test(txt)
//txt = txt.trim(); //txt = txt.trim();
//return txt.length && txt.charAt(0) === '{' && txt.charAt(txt.length - 1) === '}'; //return txt.length && txt.charAt(0) === '{' && txt.charAt(txt.length - 1) === '}';
} }
@ -42,7 +42,7 @@ function isStringOnlyCode(txt) {
*/ */
function addIfMissing(array, obj) { function addIfMissing(array, obj) {
if (!_.includes(array, obj)) { if (!_.includes(array, obj)) {
array.push(obj); array.push(obj)
} }
} }
@ -51,14 +51,14 @@ function addIfMissing(array, obj) {
* @return {string} * @return {string}
*/ */
function concatChildren(children) { function concatChildren(children) {
let res = ''; let res = ''
_.forEach(children, child => { _.forEach(children, child => {
if (child && !_.startsWith(child, ' /*')) { if (child && !_.startsWith(child, ' /*')) {
res += ','; res += ','
} }
res += child; res += child
}); })
return res; return res
} }
/** /**
@ -70,15 +70,16 @@ function concatChildren(children) {
*/ */
function validate(options, context, reportContext, node) { function validate(options, context, reportContext, node) {
if (node.type === 'tag' && node.attribs['rt-if'] && !node.attribs.key) { if (node.type === 'tag' && node.attribs['rt-if'] && !node.attribs.key) {
const loc = rtError.getNodeLoc(context, node); const loc = rtError.getNodeLoc(context, node)
reportContext.warn('rt-if without a key', options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end); reportContext.warn('rt-if without a key', options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end)
} }
if (node.type === 'tag' && node.attribs['rt-require'] && (node.attribs.dependency || node.attribs.as)) { if (node.type === 'tag' && node.attribs['rt-require'] && (node.attribs.dependency || node.attribs.as)) {
const loc = rtError.getNodeLoc(context, node); const loc = rtError.getNodeLoc(context, node)
reportContext.warn("'rt-require' is obsolete, use 'rt-import' instead", options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end); reportContext.warn("'rt-require' is obsolete, use 'rt-import' instead", options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end)
} }
// TODO check for duplicate import/require
if (node.children) { if (node.children) {
node.children.forEach(validate.bind(this, options, context, reportContext)); node.children.forEach(validate.bind(this, options, context, reportContext))
} }
} }
@ -89,51 +90,51 @@ function validate(options, context, reportContext, node) {
*/ */
function usesScopeName(scopeNames, node) { function usesScopeName(scopeNames, node) {
function usesScope(root) { function usesScope(root) {
return usesScopeName(scopeNames, root); return usesScopeName(scopeNames, root)
} }
if (_.isEmpty(scopeNames)) { if (_.isEmpty(scopeNames)) {
return false; return false
} }
// rt-if="x" // rt-if="x"
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
return _.includes(scopeNames, node.name); return _.includes(scopeNames, node.name)
} }
// rt-if="e({key1: value1})" // rt-if="e({key1: value1})"
if (node.type === 'Property') { if (node.type === 'Property') {
return usesScope(node.value); return usesScope(node.value)
} }
// rt-if="e.x" or rt-if="e1[e2]" // rt-if="e.x" or rt-if="e1[e2]"
if (node.type === 'MemberExpression') { if (node.type === 'MemberExpression') {
return node.computed ? usesScope(node.object) || usesScope(node.property) : usesScope(node.object); return node.computed ? usesScope(node.object) || usesScope(node.property) : usesScope(node.object)
} }
// rt-if="!e" // rt-if="!e"
if (node.type === 'UnaryExpression') { if (node.type === 'UnaryExpression') {
return usesScope(node.argument); return usesScope(node.argument)
} }
// rt-if="e1 || e2" or rt-if="e1 | e2" // rt-if="e1 || e2" or rt-if="e1 | e2"
if (node.type === 'LogicalExpression' || node.type === 'BinaryExpression') { if (node.type === 'LogicalExpression' || node.type === 'BinaryExpression') {
return usesScope(node.left) || usesScope(node.right); return usesScope(node.left) || usesScope(node.right)
} }
// rt-if="e1(e2, ... eN)" // rt-if="e1(e2, ... eN)"
if (node.type === 'CallExpression') { if (node.type === 'CallExpression') {
return usesScope(node.callee) || _.some(node.arguments, usesScope); return usesScope(node.callee) || _.some(node.arguments, usesScope)
} }
// rt-if="f({e1: e2})" // rt-if="f({e1: e2})"
if (node.type === 'ObjectExpression') { if (node.type === 'ObjectExpression') {
return _.some(node.properties, usesScope); return _.some(node.properties, usesScope)
} }
// rt-if="e1[e2]" // rt-if="e1[e2]"
if (node.type === 'ArrayExpression') { if (node.type === 'ArrayExpression') {
return _.some(node.elements, usesScope); return _.some(node.elements, usesScope)
} }
return false; return false
} }
/** /**
* @const * @const
*/ */
const curlyMap = {'{': 1, '}': -1}; const curlyMap = {'{': 1, '}': -1}
/** /**
* @typedef {{boundParams: Array.<string>, injectedFunctions: Array.<string>, html: string, options: *}} Context * @typedef {{boundParams: Array.<string>, injectedFunctions: Array.<string>, html: string, options: *}} Context
@ -152,39 +153,39 @@ const curlyMap = {'{': 1, '}': -1};
*/ */
function convertText(node, context, txt, normalizeWhitespaces) { function convertText(node, context, txt, normalizeWhitespaces) {
function jsonText(text) { function jsonText(text) {
return JSON.stringify(normalizeWhitespaces ? normalizeHtmlWhitespace(text) : text); return JSON.stringify(normalizeWhitespaces ? normalizeHtmlWhitespace(text) : text)
} }
let res = ''; let res = ''
let first = true; let first = true
const concatChar = node.type === 'text' ? ',' : '+'; const concatChar = node.type === 'text' ? ',' : '+'
while (_.includes(txt, '{')) { while (_.includes(txt, '{')) {
const start = txt.indexOf('{'); const start = txt.indexOf('{')
const pre = txt.substr(0, start); const pre = txt.substr(0, start)
if (pre) { if (pre) {
res += (first ? '' : concatChar) + jsonText(pre); res += (first ? '' : concatChar) + jsonText(pre)
first = false; first = false
} }
let curlyCounter = 1; let curlyCounter = 1
let end = start; let end = start
while (++end < txt.length && curlyCounter > 0) { while (++end < txt.length && curlyCounter > 0) {
curlyCounter += curlyMap[txt.charAt(end)] || 0; curlyCounter += curlyMap[txt.charAt(end)] || 0
} }
if (curlyCounter === 0) { if (curlyCounter === 0) {
const needsParens = start !== 0 || end !== txt.length - 1; const needsParens = start !== 0 || end !== txt.length - 1
res += (first ? '' : concatChar) + (needsParens ? '(' : '') + txt.substr(start + 1, end - start - 2) + (needsParens ? ')' : ''); res += (first ? '' : concatChar) + (needsParens ? '(' : '') + txt.substr(start + 1, end - start - 2) + (needsParens ? ')' : '')
first = false; first = false
txt = txt.substr(end); txt = txt.substr(end)
} else { } else {
throw RTCodeError.build(context, node, `Failed to parse text '${txt}'`); throw RTCodeError.build(context, node, `Failed to parse text '${txt}'`)
} }
} }
if (txt) { if (txt) {
res += (first ? '' : concatChar) + jsonText(txt); res += (first ? '' : concatChar) + jsonText(txt)
} }
if (res === '') { if (res === '') {
res = 'true'; res = 'true'
} }
return res; return res
} }
@ -197,4 +198,4 @@ module.exports = {
validate, validate,
addIfMissing, addIfMissing,
convertText convertText
}; }

2
test/data/autobind.rt Normal file
View File

@ -0,0 +1,2 @@
<div onKeyDown="this.handleKeyDown"></div>

10
test/data/autobind.rt.js Normal file
View File

@ -0,0 +1,10 @@
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.createElement('div', { 'onKeyDown': this.handleKeyDown.bind(this) });
};
});

9
test/data/div.rt.15.js Normal file
View File

@ -0,0 +1,9 @@
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.createElement('div', {});
};
});

View File

@ -0,0 +1,4 @@
<div onKeyDown="this.handleKeyDown">
This should trigger an error because evaluated with no autobinding (--autobind=false)
</div>

View File

@ -0,0 +1,4 @@
<rt-require dependency="util" as="util"/>
<rt-require dependency="util" as="util"/>
<div>
</div>

5
test/src/.eslintrc Normal file
View File

@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}

View File

@ -1,31 +0,0 @@
'use strict';
const fs = require('fs');
const fsUtil = require('../../src/fsUtil');
const path = require('path');
module.exports = {
runTests(test, dataPath) {
test('test isStale', t => {
const a = path.join(dataPath, 'a.tmp');
const b = path.join(dataPath, 'b.tmp');
fs.writeFileSync(a, 'actual');
fs.writeFileSync(b, 'actual');
const mtime1 = new Date(1995, 11, 17, 3, 24, 0);
fs.utimesSync(a, mtime1, mtime1);
const mtime2 = new Date(1995, 11, 17, 3, 24, 1);
fs.utimesSync(b, mtime2, mtime2);
let actual = fsUtil.isStale(a, b);
t.equal(actual, false);
actual = fsUtil.isStale(b, a);
t.equal(actual, true);
fs.unlinkSync(a);
fs.unlinkSync(b);
t.end();
});
}
};

30
test/src/fsUtil.unit.js Normal file
View File

@ -0,0 +1,30 @@
'use strict'
const fs = require('fs')
const fsUtil = require('../../src/fsUtil')
const path = require('path')
const assert = require('assert')
describe('utils', () => {
it('test isStale', () => {
const dataPath = path.resolve(__dirname, '..', 'data')
const a = path.join(dataPath, 'a.tmp')
const b = path.join(dataPath, 'b.tmp')
fs.writeFileSync(a, 'actual')
fs.writeFileSync(b, 'actual')
const mtime1 = new Date(1995, 11, 17, 3, 24, 0)
fs.utimesSync(a, mtime1, mtime1)
const mtime2 = new Date(1995, 11, 17, 3, 24, 1)
fs.utimesSync(b, mtime2, mtime2)
let actual = fsUtil.isStale(a, b)
assert.equal(actual, false)
actual = fsUtil.isStale(b, a)
assert.equal(actual, true)
fs.unlinkSync(a)
fs.unlinkSync(b)
})
})

View File

@ -1,62 +0,0 @@
'use strict';
const reactTemplates = require('../../src/reactTemplates');
const testUtils = require('./testUtils');
const readFileNormalized = testUtils.readFileNormalized;
const path = require('path');
const fsUtil = require('../../src/fsUtil');
const fs = require('fs');
module.exports = {
runTests(test, dataPath) {
test('html tests', t => {
const files = [
'scope.rt',
'scope-trailing-semicolon.rt',
'scope-variable-references.rt',
'lambda.rt',
'eval.rt',
'props.rt',
'custom-element.rt',
'style.rt',
'concat.rt',
'js-in-attr.rt',
'props-class.rt',
'rt-class.rt',
'className.rt',
'svg.rt',
'virtual.rt',
'scope-evaluated-after-repeat.rt',
'scope-evaluated-after-repeat2.rt',
'scope-evaluated-after-if.rt',
'scope-obj.rt',
'scope-reserved-tokens.rt',
'repeat-literal-collection.rt',
'include.rt'
];
files.forEach(testFile => {
const filename = path.join(dataPath, testFile);
const options = {
readFileSync: fsUtil.createRelativeReadFileSync(filename),
modules: 'amd'
};
let actual = '';
let equal = false;
try {
const html = fs.readFileSync(filename).toString();
const expected = testUtils.normalizeHtml(readFileNormalized(filename + '.html'));
const code = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '');
actual = testUtils.normalizeHtml(testUtils.codeToHtml(code));
equal = t.equal(actual, expected, `${testFile}`);
} catch (e) {
console.log(testFile, e);
t.fail(e);
}
if (!equal) {
fs.writeFileSync(filename + '.actual.html', actual);
}
});
t.end();
});
}
};

View File

@ -0,0 +1,63 @@
'use strict'
const reactTemplates = require('../../src/reactTemplates')
const testUtils = require('./utils/testUtils')
const readFileNormalized = testUtils.readFileNormalized
const path = require('path')
const fsUtil = require('../../src/fsUtil')
const fs = require('fs')
const assert = require('assert')
const files = [
'scope.rt',
'scope-trailing-semicolon.rt',
'scope-variable-references.rt',
'lambda.rt',
'eval.rt',
'props.rt',
'custom-element.rt',
'style.rt',
'concat.rt',
'js-in-attr.rt',
'props-class.rt',
'rt-class.rt',
'className.rt',
'svg.rt',
'virtual.rt',
'scope-evaluated-after-repeat.rt',
'scope-evaluated-after-repeat2.rt',
'scope-evaluated-after-if.rt',
'scope-obj.rt',
'scope-reserved-tokens.rt',
'repeat-literal-collection.rt',
'include.rt'
]
describe('utils', () => {
describe('#convertText', () => {
it('should convert text successfully', () => {
const dataPath = path.resolve(__dirname, '..', 'data')
files.forEach(testFile => {
const filename = path.join(dataPath, testFile)
const options = {
readFileSync: fsUtil.createRelativeReadFileSync(filename),
modules: 'amd'
}
let actual = ''
let equal = false
try {
const html = fs.readFileSync(filename).toString()
const expected = testUtils.normalizeHtml(readFileNormalized(`${filename}.html`))
const code = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '')
actual = testUtils.normalizeHtml(testUtils.codeToHtml(code))
equal = assert.equal(actual, expected, `${testFile}`)
} catch (e) {
console.log(testFile, e)
assert.fail(e)
}
if (!equal) {
fs.writeFileSync(`${filename}.actual.html`, actual)
}
})
})
})
})

View File

@ -1,31 +1,79 @@
'use strict'; 'use strict'
const reactTemplates = require('../../src/reactTemplates'); const reactTemplates = require('../../src/reactTemplates')
const testUtils = require('./testUtils'); const testUtils = require('./utils/testUtils')
const _ = require('lodash'); const _ = require('lodash')
const path = require('path'); const path = require('path')
const RTCodeError = reactTemplates.RTCodeError; const RTCodeError = reactTemplates.RTCodeError
const assert = require('assert')
const cli = require('../../src/cli')
const context = require('../../src/context')
const omitStack = err => _.omit(err, 'stack', 'toIssue'); const omitStack = err => _.omit(err, 'stack', 'toIssue')
module.exports = { /**
runTests(test, basePath) { * @param {ERR} err
const dataPath = path.resolve(basePath, 'invalid'); * @return {ERR}
*/
function normalizeError(err) {
if (err) {
err.msg = err.msg.replace(/\r/g, '')
}
return err
}
/**
* @typedef {{index: number, line: number, column: number, msg: string, level: string, file: string}} ERR
*/
/**
* @param {RTCodeError} err
* @param {string} file
* @return {ERR}
*/
function errorEqualMessage(err, file) {
return {
index: err.index,
line: err.line,
column: err.column,
startOffset: err.startOffset,
endOffset: err.endOffset,
msg: err.message,
level: 'ERROR',
file
}
}
describe('utils', () => {
describe('#convertText', () => {
const dataPath = path.resolve(__dirname, '..', 'data', 'invalid')
const invalidFiles = [ const invalidFiles = [
// {file: 'sortByDragListItemRowExamples.rt', issue: new RTCodeError('x', 0, 160, 1, 1)}
{file: 'if-with-scope/invalid-if-scope-1.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar(activeUsers.length)'", 0, 160, 1, 1)}, {file: 'if-with-scope/invalid-if-scope-1.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar(activeUsers.length)'", 0, 160, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-2.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar[activeUsers || 0]'", 0, 158, 1, 1)}, {file: 'if-with-scope/invalid-if-scope-2.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar[activeUsers || 0]'", 0, 158, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-3.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.foo + activeUsers.length > this.bar'", 0, 172, 1, 1)}, {
file: 'if-with-scope/invalid-if-scope-3.rt',
issue: new RTCodeError("invalid scope mapping used in if part 'this.foo + activeUsers.length > this.bar'", 0, 172, 1, 1)
},
{file: 'if-with-scope/invalid-if-scope-4.rt', issue: new RTCodeError("invalid scope mapping used in if part 'getCurrentActiveUsers().length'", 0, 170, 1, 1)}, {file: 'if-with-scope/invalid-if-scope-4.rt', issue: new RTCodeError("invalid scope mapping used in if part 'getCurrentActiveUsers().length'", 0, 170, 1, 1)},
{file: 'if-with-scope/invalid-if-scope-5.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar({activeUsers})'", 0, 155, 1, 1)}, {file: 'if-with-scope/invalid-if-scope-5.rt', issue: new RTCodeError("invalid scope mapping used in if part 'this.bar({activeUsers})'", 0, 155, 1, 1)},
{file: 'invalid-scope.rt', issue: new RTCodeError("invalid scope part 'a in a in a'", 0, 35, 1, 1)}, {file: 'invalid-scope.rt', issue: new RTCodeError("invalid scope part 'a in a in a'", 0, 35, 1, 1)},
{file: 'invalid-html.rt', issue: new RTCodeError('Document should have a root element', -1, -1, -1, -1)}, {file: 'invalid-html.rt', issue: new RTCodeError('Document should have a root element', -1, -1, -1, -1)},
{file: 'invalid-exp.rt', issue: new RTCodeError("Failed to parse text '\n {z\n'", 5, 13, 1, 6)}, {file: 'invalid-exp.rt', issue: new RTCodeError("Failed to parse text '\n {z\n'", 5, 13, 1, 6)},
{file: 'invalid-lambda.rt', issue: new RTCodeError("when using 'on' events, use lambda '(p1,p2)=>body' notation or use {} to return a callback function. error: [onClick='']", 0, 23, 1, 1)}, {
file: 'invalid-lambda.rt',
issue: new RTCodeError("when using 'on' events, use lambda '(p1,p2)=>body' notation or 'this.handler'; otherwise use {} to return a callback function. error: [onClick='']", 0, 23, 1, 1)
},
{
file: 'invalid-autobind.rt',
issue: new RTCodeError("'this.handler' syntax allowed only when the --autobind is on, use {} to return a callback function.", 0, 132, 1, 1)
},
// {file: 'invalid-js.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 0, 32, 1, 1)}, bug interduced due to scope parsing // {file: 'invalid-js.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 0, 32, 1, 1)}, bug interduced due to scope parsing
{file: 'invalid-single-root.rt', issue: new RTCodeError('Document should have no more than a single root element', 12, 23, 2, 1)}, {file: 'invalid-single-root.rt', issue: new RTCodeError('Document should have no more than a single root element', 12, 23, 2, 1)},
{file: 'invalid-repeat-1.rt', issue: new RTCodeError("rt-repeat invalid 'in' expression 'a in b in c'", 9, 44, 2, 4)}, {file: 'invalid-repeat-1.rt', issue: new RTCodeError("rt-repeat invalid 'in' expression 'a in b in c'", 9, 44, 2, 4)},
{file: 'invalid-repeat-2.rt', issue: new RTCodeError("root element may not have a 'rt-repeat' attribute", 0, 39, 1, 1)}, {file: 'invalid-repeat-2.rt', issue: new RTCodeError("root element may not have a 'rt-repeat' attribute", 0, 39, 1, 1)},
{file: 'invalid-rt-require-1.rt', issue: new RTCodeError("'rt-require' needs 'dependency' and 'as' attributes", 0, 14, 1, 1)}, {file: 'invalid-rt-require-1.rt', issue: new RTCodeError("'rt-require' needs 'dependency' and 'as' attributes", 0, 14, 1, 1)},
{file: 'invalid-rt-require-2.rt', issue: new RTCodeError("'rt-require' may have no children", 0, 32, 1, 1)}, {file: 'invalid-rt-require-2.rt', issue: new RTCodeError("'rt-require' may have no children", 0, 32, 1, 1)},
// {file: 'invalid-rt-require-duplicate.rt', issue: new RTCodeError("Line 1: Strict mode function may not have duplicate parameter names", 64, -1, -1, -1), options: {modules: 'amd'}},
{file: 'invalid-rt-import-1.rt', issue: new RTCodeError("'*' imports must have an 'as' attribute", 0, 36, 1, 1)}, {file: 'invalid-rt-import-1.rt', issue: new RTCodeError("'*' imports must have an 'as' attribute", 0, 36, 1, 1)},
{file: 'invalid-rt-import-2.rt', issue: new RTCodeError("default imports must have an 'as' attribute", 0, 42, 1, 1)}, {file: 'invalid-rt-import-2.rt', issue: new RTCodeError("default imports must have an 'as' attribute", 0, 42, 1, 1)},
{file: 'invalid-rt-import-3.rt', issue: new RTCodeError("'rt-import' needs 'name' and 'from' attributes", 0, 13, 1, 1)}, {file: 'invalid-rt-import-3.rt', issue: new RTCodeError("'rt-import' needs 'name' and 'from' attributes", 0, 13, 1, 1)},
@ -37,72 +85,33 @@ module.exports = {
{file: 'invalid-style-2.rt', issue: new RTCodeError('style attribute keys cannot contain { } expressions', 35, 68, 2, 5)}, {file: 'invalid-style-2.rt', issue: new RTCodeError('style attribute keys cannot contain { } expressions', 35, 68, 2, 5)},
{file: 'invalid-virtual-1.rt', issue: new RTCodeError('Document should not have <rt-virtual> as root element', 0, 60, 1, 1)}, {file: 'invalid-virtual-1.rt', issue: new RTCodeError('Document should not have <rt-virtual> as root element', 0, 60, 1, 1)},
{file: 'invalid-virtual-2.rt', issue: new RTCodeError("<rt-virtual> may not contain attributes other than 'rt-scope', 'rt-if' and 'rt-repeat'", 9, 119, 2, 4)} {file: 'invalid-virtual-2.rt', issue: new RTCodeError("<rt-virtual> may not contain attributes other than 'rt-scope', 'rt-if' and 'rt-repeat'", 9, 119, 2, 4)}
]; ]
test('invalid tests', t => {
t.plan(invalidFiles.length);
it('should convert text successfully', () => {
invalidFiles.forEach(testFile => { invalidFiles.forEach(testFile => {
const filename = path.join(dataPath, testFile.file); const filename = path.join(dataPath, testFile.file)
const html = testUtils.readFileNormalized(filename); const html = testUtils.readFileNormalized(filename)
let error = null; let error = null
try { try {
reactTemplates.convertTemplateToReact(html); reactTemplates.convertTemplateToReact(html)
} catch (e) { } catch (e) {
error = e; error = e
} }
t.deepEqual(omitStack(error), omitStack(testFile.issue), 'Expect convertTemplateToReact to throw an error'); assert.deepEqual(omitStack(error), omitStack(testFile.issue), 'Expect convertTemplateToReact to throw an error')
}); })
});
/** invalidFiles.forEach(check)
* @param {ERR} err
* @return {ERR}
*/
function normalizeError(err) {
if (err) {
err.msg = err.msg.replace(/\r/g, '');
}
return err;
}
test('invalid tests json', t => {
const cli = require('../../src/cli');
const context = require('../../src/context');
t.plan(invalidFiles.length);
invalidFiles.forEach(check);
function check(testFile) { function check(testFile) {
context.clear(); context.clear()
const filename = path.join(dataPath, testFile.file); const filename = path.join(dataPath, testFile.file)
const options = {format: 'json', force: true}; const options = {format: 'json', force: true}
cli.handleSingleFile(options, filename); cli.handleSingleFile(_.assign(options, testFile.options), filename)
t.deepEqual(normalizeError(context.getMessages()[0]), errorEqualMessage(testFile.issue, filename), `Expect cli to produce valid output messages ${testFile.file}`); assert.deepEqual(normalizeError(context.getMessages()[0]), errorEqualMessage(testFile.issue, filename), `Expect cli to produce valid output messages ${testFile.file}`)
} }
}); })
})
})
/**
* @typedef {{index: number, line: number, column: number, msg: string, level: string, file: string}} ERR
*/
/**
* @param {RTCodeError} err
* @param {string} file
* @return {ERR}
*/
function errorEqualMessage(err, file) {
return {
index: err.index,
line: err.line,
column: err.column,
startOffset: err.startOffset,
endOffset: err.endOffset,
msg: err.message,
level: 'ERROR',
file
};
}
}
};

View File

@ -1,46 +1,63 @@
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const reactTemplates = require('../../src/reactTemplates'); const reactTemplates = require('../../src/reactTemplates')
const testUtils = require('./testUtils'); const testUtils = require('./utils/testUtils')
const readFileNormalized = testUtils.readFileNormalized; const readFileNormalized = testUtils.readFileNormalized
const compareAndWrite = testUtils.compareAndWrite; const compareAndWrite = testUtils.compareAndWrite
const path = require('path'); const path = require('path')
const context = require('../../src/context'); const context = require('../../src/context')
// const assert = require('assert')
const dataPath = path.resolve(__dirname, '..', 'data')
module.exports = { /**
runTests(test, dataPath) { * @param testData
function check(t, testData) { */
const filename = path.join(dataPath, testData.source); function check(testData) {
const html = readFileNormalized(filename); const filename = path.join(dataPath, testData.source)
const expected = readFileNormalized(path.join(dataPath, testData.expected)); const html = readFileNormalized(filename)
const actual = reactTemplates.convertTemplateToReact(html, testData.options).replace(/\r/g, '').trim(); const expected = readFileNormalized(path.join(dataPath, testData.expected))
compareAndWrite(t, actual, expected, filename); const actual = reactTemplates.convertTemplateToReact(html, testData.options).replace(/\r/g, '').trim()
} compareAndWrite(actual, expected, filename)
}
function testFiles(t, files, options) { /**
t.plan(files.length); * @param files
files.forEach(testFile => { * @param options
check(t, { */
source: testFile, function testFiles(files, options) {
expected: `${testFile}.js`, files.forEach(testFile => {
options check({
}); source: testFile,
}); expected: `${testFile}.js`,
} options
})
})
}
test('rt-if with rt-scope test', t => { describe('utils', () => {
const files = ['if-with-scope/valid-if-scope.rt']; describe('#convertText', () => {
const options = {modules: 'amd'}; it('rt-if with rt-scope test', () => {
testFiles(t, files, options); const files = ['if-with-scope/valid-if-scope.rt']
}); const options = {modules: 'amd'}
testFiles(files, options)
})
test('conversion test', t => { it('conversion test', () => {
const files = ['div.rt', 'test.rt', 'repeat.rt', 'repeat-with-index.rt', 'inputs.rt', 'virtual.rt', 'stateless.rt', 'style-vendor-prefix.rt', 'non-breaking-space.rt']; const files = ['div.rt', 'test.rt', 'repeat.rt', 'repeat-with-index.rt', 'inputs.rt', 'virtual.rt', 'stateless.rt', 'style-vendor-prefix.rt', 'non-breaking-space.rt']
const options = {modules: 'amd'}; const options = {modules: 'amd'}
testFiles(t, files, options); testFiles(files, options)
}); })
test('prop template conversion test', t => { it('autobinding conversion test', () => {
const options = {
modules: 'amd',
autobind: true
}
const files = ['autobind.rt']
testFiles(files, options)
})
it('prop template conversion test', () => {
const options = { const options = {
propTemplates: { propTemplates: {
List: { List: {
@ -48,18 +65,18 @@ module.exports = {
} }
}, },
modules: 'amd' modules: 'amd'
}; }
const files = [ const files = [
'simpleTemplate.rt', 'simpleTemplate.rt',
'templateInScope.rt', 'templateInScope.rt',
'implicitTemplate.rt', 'implicitTemplate.rt',
'twoTemplates.rt', 'twoTemplates.rt',
'siblingTemplates.rt' 'siblingTemplates.rt'
].map(file => path.join('propTemplates', file)); ].map(file => path.join('propTemplates', file))
testFiles(t, files, options); testFiles(files, options)
}); })
test('conversion test - native', t => { it('conversion test - native', () => {
const options = { const options = {
propTemplates: { propTemplates: {
MyComp: { MyComp: {
@ -67,8 +84,8 @@ module.exports = {
} }
}, },
native: true native: true
}; }
const optionsNew = _.assign({nativeTargetVersion: '0.29.0'}, options); const optionsNew = _.assign({nativeTargetVersion: '0.29.0'}, options)
const files = [ const files = [
{source: 'native/nativeView.rt', expected: 'native/nativeView.rt.js', options}, {source: 'native/nativeView.rt', expected: 'native/nativeView.rt.js', options},
@ -77,72 +94,67 @@ module.exports = {
{source: 'native/nativeView.rt', expected: 'native/nativeView.rt.v029.js', options: optionsNew}, {source: 'native/nativeView.rt', expected: 'native/nativeView.rt.v029.js', options: optionsNew},
{source: 'native/listViewTemplate.rt', expected: 'native/listViewTemplate.rt.v029.js', options: optionsNew}, {source: 'native/listViewTemplate.rt', expected: 'native/listViewTemplate.rt.v029.js', options: optionsNew},
{source: 'native/listViewAndCustomTemplate.rt', expected: 'native/listViewAndCustomTemplate.rt.v029.js', options: optionsNew} {source: 'native/listViewAndCustomTemplate.rt', expected: 'native/listViewAndCustomTemplate.rt.v029.js', options: optionsNew}
]; ]
t.plan(files.length); files.forEach(check)
files.forEach(file => check(t, file)); })
});
test('convert div with all module types', t => { it('convert div with all module types', () => {
const files = [ const files = [
{source: 'div.rt', expected: 'div.rt.commonjs.js', options: {modules: 'commonjs'}}, {source: 'div.rt', expected: 'div.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'div.rt', expected: 'div.rt.amd.js', options: {modules: 'amd', name: 'div'}}, {source: 'div.rt', expected: 'div.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.globals.js', options: {modules: 'none', name: 'div'}}, {source: 'div.rt', expected: 'div.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.es6.js', options: {modules: 'es6'}}, {source: 'div.rt', expected: 'div.rt.es6.js', options: {modules: 'es6'}},
{source: 'div.rt', expected: 'div.rt.typescript.ts', options: {modules: 'typescript'}} {source: 'div.rt', expected: 'div.rt.typescript.ts', options: {modules: 'typescript'}},
]; {source: 'div.rt', expected: 'div.rt.15.js', options: {targetVersion: '15.0.0', modules: 'amd'}}
t.plan(files.length); ]
files.forEach(file => check(t, file)); files.forEach(check)
}); })
test('normalize whitespace', t => { it('normalize whitespace', () => {
const files = ['whitespace.rt']; const files = ['whitespace.rt']
const options = {normalizeHtmlWhitespace: true, modules: 'amd'}; const options = {normalizeHtmlWhitespace: true, modules: 'amd'}
testFiles(t, files, options); testFiles(files, options)
}); })
test('convert comment with AMD and ES6 modules', t => { it('convert comment with AMD and ES6 modules', () => {
const files = [ const files = [
{source: 'comment.rt', expected: 'comment.rt.amd.js', options: {modules: 'amd'}}, {source: 'comment.rt', expected: 'comment.rt.amd.js', options: {modules: 'amd'}},
{source: 'comment.rt', expected: 'comment.rt.es6.js', options: {modules: 'es6'}} {source: 'comment.rt', expected: 'comment.rt.es6.js', options: {modules: 'es6'}}
]; ]
t.plan(files.length); files.forEach(check)
files.forEach(file => check(t, file)); })
});
test('rt-require with all module types', t => { it('rt-require with all module types', () => {
const files = [ const files = [
{source: 'require.rt', expected: 'require.rt.commonjs.js', options: {modules: 'commonjs'}}, {source: 'require.rt', expected: 'require.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'require.rt', expected: 'require.rt.amd.js', options: {modules: 'amd', name: 'div'}}, {source: 'require.rt', expected: 'require.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'require.rt', expected: 'require.rt.globals.js', options: {modules: 'none', name: 'div'}}, {source: 'require.rt', expected: 'require.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'require.rt', expected: 'require.rt.es6.js', options: {modules: 'es6'}}, {source: 'require.rt', expected: 'require.rt.es6.js', options: {modules: 'es6'}},
{source: 'require.rt', expected: 'require.rt.typescript.ts', options: {modules: 'typescript'}} {source: 'require.rt', expected: 'require.rt.typescript.ts', options: {modules: 'typescript'}}
]; ]
t.plan(files.length); files.forEach(check)
files.forEach(file => check(t, file)); })
});
test('rt-import with all module types', t => { it('rt-import with all module types', () => {
const files = [ const files = [
{source: 'import.rt', expected: 'import.rt.commonjs.js', options: {modules: 'commonjs'}}, {source: 'import.rt', expected: 'import.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'import.rt', expected: 'import.rt.amd.js', options: {modules: 'amd', name: 'div'}}, {source: 'import.rt', expected: 'import.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'import.rt', expected: 'import.rt.globals.js', options: {modules: 'none', name: 'div'}}, {source: 'import.rt', expected: 'import.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'import.rt', expected: 'import.rt.es6.js', options: {modules: 'es6'}}, {source: 'import.rt', expected: 'import.rt.es6.js', options: {modules: 'es6'}},
{source: 'import.rt', expected: 'import.rt.typescript.ts', options: {modules: 'typescript'}} {source: 'import.rt', expected: 'import.rt.typescript.ts', options: {modules: 'typescript'}}
]; ]
t.plan(files.length); files.forEach(check)
files.forEach(file => check(t, file)); })
});
test('convert jsrt and test source results', t => { it('convert jsrt and test source results', () => {
const files = ['simple.jsrt']; const files = ['simple.jsrt']
t.plan(files.length);
files.forEach(file => { files.forEach(file => {
const filename = path.join(dataPath, file); const filename = path.join(dataPath, file)
const js = readFileNormalized(filename); const js = readFileNormalized(filename)
const expected = readFileNormalized(path.join(dataPath, file.replace('.jsrt', '.js'))); const expected = readFileNormalized(path.join(dataPath, file.replace('.jsrt', '.js')))
const actual = reactTemplates.convertJSRTToJS(js, context).replace(/\r/g, '').trim(); const actual = reactTemplates.convertJSRTToJS(js, context).replace(/\r/g, '').trim()
compareAndWrite(t, actual, expected, filename); compareAndWrite(actual, expected, filename)
}); })
}); })
} })
}; })

View File

@ -1,14 +0,0 @@
'use strict';
const rtStyle = require('../../src/rtStyle');
module.exports = {
runTests(test) {
test('test rtStyle', t => {
const text = '.text { background-color: #00346E; padding: 3px; }';
const expected = '{\n "text": {\n "backgroundColor": "#00346E",\n "padding": 3\n }\n}';
const actual = rtStyle.convertBody(text);
t.equal(actual, expected);
t.end();
});
}
};

12
test/src/rtStyle.unit.js Normal file
View File

@ -0,0 +1,12 @@
'use strict'
const assert = require('assert')
const rtStyle = require('../../src/rtStyle')
describe('rtStyle', () => {
it('should convertBody style successfully', () => {
const text = '.text { background-color: #00346E; padding: 3px; }'
const expected = '{\n "text": {\n "backgroundColor": "#00346E",\n "padding": 3\n }\n}'
const actual = rtStyle.convertBody(text)
assert.equal(actual, expected)
})
})

View File

@ -1,53 +0,0 @@
'use strict';
const context = require('../../src/context');
const _ = require('lodash');
const path = require('path');
module.exports = {
runTests(test, dataPath) {
test('test context', t => {
context.clear();
t.equal(context.hasErrors(), false);
context.error('hi', '', 1, 1);
t.equal(context.hasErrors(), true);
context.clear();
t.equal(context.hasErrors(), false);
t.end();
});
test('test shell', t => {
const shell = require('../../src/shell');
const newContext = _.cloneDeep(context);
let outputJSON = '';
newContext.options.format = 'json';
newContext.report = function (text) { outputJSON = text; };
let r = shell.printResults(newContext);
t.equal(r, 0);
context.error('hi', '', 1, 1);
r = shell.printResults(newContext);
t.equal(r, 1);
const output = JSON.parse(outputJSON);
t.deepEqual(output, [{
column: 1,
endOffset: -1,
file: null,
index: -1,
level: 'ERROR',
line: 1,
msg: 'hi',
startOffset: -1
}]);
context.clear();
t.end();
});
test('test shell', t => {
const filename = path.join(dataPath, 'div.rt');
const cli = require('../../src/cli');
const r = cli.execute(`${filename} -r --dry-run`);
t.equal(r, 0);
t.end();
});
}
};

51
test/src/shell.unit.js Normal file
View File

@ -0,0 +1,51 @@
'use strict'
const context = require('../../src/context')
const _ = require('lodash')
const path = require('path')
const assert = require('assert')
const dataPath = path.resolve(__dirname, '..', 'data')
describe('rtStyle', () => {
it('should convertBody style successfully', () => {
it('should convertBody style successfully', () => {
context.clear()
assert.equal(context.hasErrors(), false)
context.error('hi', '', 1, 1)
assert.equal(context.hasErrors(), true)
context.clear()
assert.equal(context.hasErrors(), false)
})
it('test shell', () => {
const shell = require('../../src/shell')
const newContext = _.cloneDeep(context)
let outputJSON = ''
newContext.options.format = 'json'
newContext.report = function (text) { outputJSON = text }
let r = shell.printResults(newContext)
assert.equal(r, 0)
context.error('hi', '', 1, 1)
r = shell.printResults(newContext)
assert.equal(r, 1)
const output = JSON.parse(outputJSON)
assert.deepEqual(output, [{
column: 1,
endOffset: -1,
file: null,
index: -1,
level: 'ERROR',
line: 1,
msg: 'hi',
startOffset: -1
}])
context.clear()
})
it('test shell', () => {
const filename = path.join(dataPath, 'div.rt')
const cli = require('../../src/cli')
const r = cli.execute(`${filename} -r --dry-run`)
assert.equal(r, 0)
})
})
})

View File

@ -1,10 +0,0 @@
'use strict';
const test = require('tape');
const path = require('path');
const dataPath = path.resolve(__dirname, '..', 'data');
const specs = ['rt.invalid', 'rt.valid', 'rt-html-valid', 'utils', 'shell', 'rtStyle', 'fsUtil'];
specs
.map(file => require(`./${file}.spec`))
.forEach(spec => spec.runTests(test, dataPath));

View File

@ -1,20 +0,0 @@
'use strict';
const utils = require('../../src/utils');
module.exports = {
runTests(test) {
test('test convertText', t => {
const texts = [
{input: '{}', expected: '()'},
{input: "a {'b'}", expected: '"a "+(\'b\')'}
];
t.plan(texts.length);
texts.forEach(check);
function check(testData) {
const r = utils.convertText({}, {}, testData.input);
t.equal(r, testData.expected);
}
});
}
};

30
test/src/utils.unit.js Normal file
View File

@ -0,0 +1,30 @@
'use strict'
const assert = require('assert')
const utils = require('../../src/utils')
describe('utils', () => {
describe('#convertText', () => {
it('should convert text successfully', () => {
const texts = [
{input: '{}', expected: '()'},
{input: "a {'b'}", expected: '"a "+(\'b\')'}
]
texts.forEach(check)
function check(testData) {
const r = utils.convertText({}, {}, testData.input)
assert.equal(r, testData.expected)
}
})
it('should fail', () => {
const texts = [
{input: '() => {}', expected: '"() => "+()'}
]
texts.forEach(check)
function check(testData) {
const r = utils.convertText({}, {}, testData.input)
assert.equal(r, testData.expected)
}
})
})
})

View File

@ -1,11 +1,12 @@
'use strict'; 'use strict'
const cheerio = require('cheerio'); const cheerio = require('cheerio')
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
const reactTemplates = require('../../src/reactTemplates'); const reactTemplates = require('../../../src/reactTemplates')
const React = require('react'); const React = require('react')
const ReactDOMServer = require('react-dom/server'); const ReactDOMServer = require('react-dom/server')
const _ = require('lodash'); const _ = require('lodash')
const assert = require('assert')
/** /**
* @param {string} html * @param {string} html
@ -15,39 +16,38 @@ function normalizeHtml(html) {
return cheerio.load(html, {normalizeWhitespace: true}).html() return cheerio.load(html, {normalizeWhitespace: true}).html()
.replace(/>\s+/mg, '>') .replace(/>\s+/mg, '>')
.replace(/\s+</mg, '<') .replace(/\s+</mg, '<')
.replace(/>\s+</mg, '><'); .replace(/>\s+</mg, '><')
} }
/** /**
* @param {*} t
* @param {string} actual * @param {string} actual
* @param {string} expected * @param {string} expected
* @param {string} filename * @param {string} filename
* @return {boolean} whether actual is equal to expected * @return {boolean} whether actual is equal to expected
*/ */
function compareAndWrite(t, actual, expected, filename) { function compareAndWrite(actual, expected, filename) {
t.equal(actual, expected, filename); assert.equal(actual, expected, filename)
if (actual !== expected) { if (actual !== expected) {
fs.writeFileSync(`${filename}.actual.js`, actual); fs.writeFileSync(`${filename}.actual.js`, actual)
return false; return false
} }
return true; return true
} }
function compareNodes(t, a, b, filename) { function compareNodes(t, a, b, filename) {
_.forEach(a.attribs, (v, k) => { _.forEach(a.attribs, (v, k) => {
if (v !== b.attribs[k]) { if (v !== b.attribs[k]) {
console.log(`${v} is not ${b.attribs[k]}`); console.log(`${v} is not ${b.attribs[k]}`)
} }
t.equal(v, b.attribs[k], filename); t.equal(v, b.attribs[k], filename)
}); })
compareNodesList(t, a.children, b.children); compareNodesList(t, a.children, b.children)
} }
function compareNodesList(t, a, b, filename) { function compareNodesList(t, a, b, filename) {
_.forEach(a, (v, i) => { _.forEach(a, (v, i) => {
compareNodes(t, v, b[i], filename); compareNodes(t, v, b[i], filename)
}); })
} }
/** /**
@ -55,7 +55,7 @@ function compareNodesList(t, a, b, filename) {
* @return {string} * @return {string}
*/ */
function readFileNormalized(filename) { function readFileNormalized(filename) {
return readFile(filename).replace(/\r/g, '').trim(); return readFile(filename).replace(/\r/g, '').trim()
} }
//const dataPath = path.resolve(__dirname, '..', 'data'); //const dataPath = path.resolve(__dirname, '..', 'data');
@ -64,31 +64,31 @@ function readFileNormalized(filename) {
* @return {string} * @return {string}
*/ */
function readFile(filename) { function readFile(filename) {
return fs.readFileSync(filename).toString(); return fs.readFileSync(filename).toString()
} }
function joinDataPath(fileName) { function joinDataPath(fileName) {
const dataPath = path.resolve(__dirname, '..', 'data'); const dataPath = path.resolve(__dirname, '..', 'data')
return path.join(dataPath, fileName); return path.join(dataPath, fileName)
} }
function rtToHtml(rt) { function rtToHtml(rt) {
const code = reactTemplates.convertTemplateToReact(rt).replace(/\r/g, ''); const code = reactTemplates.convertTemplateToReact(rt).replace(/\r/g, '')
return codeToHtml(code); return codeToHtml(code)
} }
function codeToHtml(code) { function codeToHtml(code) {
const defineMap = {react: React, lodash: _}; const defineMap = {react: React, lodash: _}
//noinspection JSUnusedLocalSymbols //noinspection JSUnusedLocalSymbols
const define = function (requirementsNames, content) { //eslint-disable-line no-unused-vars,func-style const define = function (requirementsNames, content) { //eslint-disable-line no-unused-vars,func-style
const requirements = _.map(requirementsNames, reqName => defineMap[reqName]); const requirements = _.map(requirementsNames, reqName => defineMap[reqName])
return content.apply(this, requirements); return content.apply(this, requirements)
}; }
const comp = React.createFactory(React.createClass({ const comp = React.createFactory(React.createClass({
displayName: 'testClass', displayName: 'testClass',
render: eval(code) //eslint-disable-line no-eval render: eval(code) //eslint-disable-line no-eval
})); }))
return ReactDOMServer.renderToStaticMarkup(comp()); return ReactDOMServer.renderToStaticMarkup(comp())
} }
module.exports = { module.exports = {
@ -99,4 +99,4 @@ module.exports = {
joinDataPath, joinDataPath,
rtToHtml, rtToHtml,
codeToHtml codeToHtml
}; }

44
wallaby.js Normal file
View File

@ -0,0 +1,44 @@
/*eslint object-shorthand:0*/
'use strict'
// const path = require('path')
module.exports = function (/*wallaby*/) {
return {
files: [
'src/**/*.js',
'test/src/utils/**/*.js',
{pattern: 'test/data/**/*', instrument: false},
{pattern: 'package.json', instrument: false}
],
tests: [
'test/**/*.unit.js'
],
testFramework: 'mocha',
// debug: true,
// reportConsoleErrorAsError: true,
// maxConsoleMessagesPerTest: 10000,
env: {
// use 'node' type to use node.js or io.js
type: 'node',
// if runner property is not set, then wallaby.js embedded node/io.js version is used
// you can specifically set the node version by specifying 'node' (or any other command)
// that resolves your default node version or just specify the path
// your node installation, like
runner: process.env.NODE_HOME
// runner: '/Users/idok/.nvm/versions/node/v6.9.4/bin/node'
//runner: '/usr/local/bin/jasmine-node'
// or
// runner: 'path to the desired node version'
// params: {
// runner: '--harmony --harmony_arrow_functions',
// env: 'PARAM1=true;PARAM2=false'
// }
},
setup: function (w) {
// require(path.resolve(__dirname, './spec/support/unit-init.js'))
// require('jasmine-expect')
// w.testFramework.addMatchers(require('./test/src/utils/customMatchers'))
w.testFramework.DEFAULT_TIMEOUT_INTERVAL = 2500
}
}
}