support react 13,

rename string utils method,
warn on rt-if without key
This commit is contained in:
ido 2015-04-21 17:15:03 +03:00
parent e4bbd93844
commit 307084e11d
8 changed files with 219 additions and 90 deletions

View File

@ -1,13 +1,17 @@
'use strict';
/**
* @typedef {{line: number, col: number}} Pos
*/
/**
* @param {string} html
* @param node
* @return {{line: number, col: number}}}
* @return {Pos}
*/
function getLine(html, node) {
if (!node) {
return {col: 1, line: 1};
return {line: 1, col: 1};
}
var linesUntil = html.substring(0, node.startIndex).split('\n');
return {line: linesUntil.length, col: linesUntil[linesUntil.length - 1].length + 1};
@ -80,6 +84,16 @@ RTCodeError.prototype.toIssue = function () {
* @return {RTCodeError}
*/
function buildError(msg, context, node) {
var loc = getNodeLoc(context, node);
return new RTCodeError(msg, loc.start, loc.end, loc.pos.line, loc.pos.col);
}
/**
* @param context
* @param node
* @return {{pos:Pos, start:number, end:number}}
*/
function getNodeLoc(context, node) {
var pos = getLine(context.html, node);
var end;
if (node.data) {
@ -89,9 +103,14 @@ function buildError(msg, context, node) {
} else {
end = context.html.length;
}
return new RTCodeError(msg, node.startIndex, end, pos.line, pos.col);
return {
pos: pos,
start: node.startIndex,
end: end
};
}
module.exports = {
RTCodeError: RTCodeError
RTCodeError: RTCodeError,
getNodeLoc: getNodeLoc
};

View File

@ -9,8 +9,8 @@ var convertJSRTToJS = reactTemplates.convertJSRTToJS;
/**
* @param {string} source
* @param {{modules:string, dryRun:boolean}?} options
* @param {string} target
* @param {{modules:string, dryRun:boolean}?} options
* @param {CONTEXT} context
*/
function convertFile(source, target, options, context) {
@ -19,6 +19,7 @@ function convertFile(source, target, options, context) {
// return;// only handle html files
// }
options = options || {};
options.fileName = source;
var fsUtil = require('./fsUtil');
if (!options.force && !fsUtil.isStale(source, target)) {
@ -35,7 +36,7 @@ function convertFile(source, target, options, context) {
if (options.modules === 'jsrt') {
js = convertJSRTToJS(html, options);
} else {
js = convertTemplateToReact(html, options);
js = convertTemplateToReact(html, context, options);
}
if (!options.dryRun) {
fs.writeFileSync(target, js);

View File

@ -37,8 +37,8 @@ var context = {
info: function (msg, file, line, column) {
context.issue(MESSAGE_LEVEL.INFO, msg, file, line, column);
},
warn: function (msg, file, line, column) {
context.issue(MESSAGE_LEVEL.WARN, msg, file, line, column);
warn: function (msg, file, line, column, startOffset, endOffset) {
context.issue(MESSAGE_LEVEL.WARN, msg, file, line, column, startOffset, endOffset);
},
error: function (msg, file, line, column, startOffset, endOffset) {
context.issue(MESSAGE_LEVEL.ERROR, msg, file, line, column, startOffset, endOffset);

View File

@ -1,9 +1,10 @@
'use strict';
/**
* @type {Chalk.ChalkModule}
*/
var chalk = require('chalk');
var _ = require('lodash');
var table = require('text-table');
/**
* @param {MESSAGE} message
@ -19,78 +20,153 @@ function getMessageType(message) {
return chalk.cyan('info');
}
module.exports = function (warnings/*, config*/) {
var _ = require('lodash');
var table = require('text-table');
//var verbosity = false;
var UNICODE_HEAVY_MULTIPLICATION_X = '\u2716';
/**
* Given a word and a count, append an s if count is not one.
* @param {string} word A word in its singular form.
* @param {int} count A number controlling whether word should be pluralized.
* @returns {string} The original word with an s on the end if count is not one.
*/
function pluralize(word, count) {
return (count === 1 ? word : word + 's');
}
function pluralize(n, single, plural) {
return n === 1 ? single : plural;
}
//function pluralize(n, single, plural) {
// return n === 1 ? single : plural;
//}
function lineText(line) {
return line < 1 ? '' : line;
}
function lineText(line) {
return line < 1 ? '' : line;
}
// context.report(JSON.stringify(warnings, undefined, 2));
var output = table(
warnings.map(function (message) {
return [
'',
message.file || '',
lineText(message.line || 0),
lineText(message.column || 0),
getMessageType(message),
// message.message.replace(/\.$/, ""),
message.msg || ''
// chalk.gray(message.ruleId)
];
}),
{
align: ['', 'r', 'l'],
stringLength: function (str) {
return chalk.stripColor(str).length;
}
}
//}
);
var buf = [];
buf.push(output);
var grouped = _.groupBy(warnings, 'level');
var errCount = grouped.ERROR ? grouped.ERROR.length : 0;
var warnCount = grouped.WARN ? grouped.WARN.length : 0;
//var infoCount = grouped.INFO ? grouped.INFO.length : 0;
// buf.push(errCount + ' ' + warnCount + ' ' + infoCount + '\n');
if (errCount === 0 && warnCount === 0) {
buf.push(chalk.green('React templates done'));
} else {
var msg = [];
if (errCount > 0) {
msg.push(errCount + ' ' + pluralize(errCount, 'error', 'errors'));
} else {
msg.push(warnCount + ' ' + pluralize(warnCount, 'warning', 'warnings'));
}
buf.push(chalk.red.bold(UNICODE_HEAVY_MULTIPLICATION_X + ' ' + msg.join(', ')));
if (errCount > 0) {
buf.push(chalk.red('React templates failed due to errors'));
} else {
buf.push(chalk.yellow('React templates done with warnings'));
}
}
// context.report(JSON.stringify(grouped, undefined, 2));
// if (grouped.ERROR && grouped.ERROR.length > 0) {
//// throw new Error(errorMessages.VERIFY_FAILED.format(grouped.ERROR.length, pluralize(grouped.ERROR.length, 'error', 'errors')));
//module.exports = function (warnings/*, config*/) {
// var _ = require('lodash');
// var table = require('text-table');
// //var verbosity = false;
// var UNICODE_HEAVY_MULTIPLICATION_X = '\u2716';
//
// // context.report(JSON.stringify(warnings, undefined, 2));
// var output = table(
// warnings.map(function (message) {
// return [
// '',
// message.file || '',
// lineText(message.line || 0),
// lineText(message.column || 0),
// getMessageType(message),
// // message.message.replace(/\.$/, ''),
// message.msg || ''
// // chalk.gray(message.ruleId)
// ];
// }),
// {
// align: ['', 'r', 'l'],
// stringLength: function (str) {
// return chalk.stripColor(str).length;
// }
// }
// //}
// );
//
// var buf = [];
//
// buf.push(output);
//
// var grouped = _.groupBy(warnings, 'level');
//
// var errCount = grouped.ERROR ? grouped.ERROR.length : 0;
// var warnCount = grouped.WARN ? grouped.WARN.length : 0;
// //var infoCount = grouped.INFO ? grouped.INFO.length : 0;
//
//// buf.push(errCount + ' ' + warnCount + ' ' + infoCount + '\n');
//
// if (errCount === 0 && warnCount === 0) {
// buf.push(chalk.green('React templates done'));
// } else {
// buf.push(chalk.red.bold(UNICODE_HEAVY_MULTIPLICATION_X + ' ' + warnings.length + ' ' + pluralize(warnings.length, 'problem', 'problems')) + '\n');
// buf.push('React templates done with warnings\n');
// var msg = [];
// if (errCount > 0) {
// msg.push(errCount + ' ' + pluralize(errCount, 'error', 'errors'));
// } else {
// msg.push(warnCount + ' ' + pluralize(warnCount, 'warning', 'warnings'));
// }
// buf.push(chalk.red.bold(UNICODE_HEAVY_MULTIPLICATION_X + ' ' + msg.join(', ')));
// if (errCount > 0) {
// buf.push(chalk.red('React templates failed due to errors'));
// } else {
// buf.push(chalk.yellow('React templates done with warnings'));
// }
// }
return buf.join('\n');
//
//// context.report(JSON.stringify(grouped, undefined, 2));
//// if (grouped.ERROR && grouped.ERROR.length > 0) {
////// throw new Error(errorMessages.VERIFY_FAILED.format(grouped.ERROR.length, pluralize(grouped.ERROR.length, 'error', 'errors')));
//// } else {
//// buf.push(chalk.red.bold(UNICODE_HEAVY_MULTIPLICATION_X + ' ' + warnings.length + ' ' + pluralize(warnings.length, 'problem', 'problems')) + '\n');
//// buf.push('React templates done with warnings\n');
//// }
// return buf.join('\n');
//};
module.exports = function (results) {
results = _.groupBy(results, 'file');
var output = '\n',
total = 0,
errors = 0,
warnings = 0,
summaryColor = 'yellow';
_.each(results, function (result, k) {
var messages = result;
if (messages.length === 0) {
return;
}
total += messages.length;
output += chalk.underline(k) + '\n';
output += table(
messages.map(function (message) {
var messageType;
if (message.fatal || message.severity === 2) {
messageType = chalk.red('error');
summaryColor = 'red';
errors++;
} else {
messageType = chalk.yellow('warning');
warnings++;
}
return [
'',
message.line || 0,
message.column || 0,
messageType,
message.msg.replace(/\.$/, ''),
chalk.gray(message.ruleId || '')
];
}),
{
align: ['', 'r', 'l'],
stringLength: function (str) {
return chalk.stripColor(str).length;
}
}
).split('\n').map(function (el) {
return el.replace(/(\d+)\s+(\d+)/, function (m, p1, p2) {
return chalk.gray(p1 + ':' + p2);
});
}).join('\n') + '\n\n';
});
if (total > 0) {
output += chalk[summaryColor].bold([
'\u2716 ', total, pluralize(' problem', total),
' (', errors, pluralize(' error', errors), ', ',
warnings, pluralize(' warning', warnings), ')\n'
].join(''));
}
return total > 0 ? output : '';
};

View File

@ -74,7 +74,7 @@ module.exports = optionator({
option: 'target-version',
alias: 't',
type: 'String',
default: '0.12.2',
default: '0.13.1',
description: 'React version to generate code for (' + Object.keys(reactDOMSupport).join(', ') + ')'
}, {
option: 'list-target-version',

View File

@ -2,6 +2,7 @@
* Created by avim on 12/4/2014.
*/
'use strict';
var 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'];
var 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'];
var 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'];
@ -9,6 +10,7 @@ var ver0_10_0 = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b
var versions = {
'0.13.1': ver0_12_0,
'0.12.2': ver0_12_0,
'0.12.1': ver0_12_0,
'0.12.0': ver0_12_0,

View File

@ -15,8 +15,11 @@ var repeatTemplate = _.template('_.map(<%= collection %>,<%= repeatFunction %>.b
var ifTemplate = _.template('((<%= condition %>)?(<%= body %>):null)');
var propsTemplateSimple = _.template('_.assign({}, <%= generatedProps %>, <%= rtProps %>)');
var propsTemplate = _.template('mergeProps( <%= generatedProps %>, <%= rtProps %>)');
var propsMergeFunction = 'function mergeProps(inline,external) {\n var res = _.assign({},inline,external)\nif (inline.hasOwnProperty(\'style\')) {\n res.style = _.defaults(res.style, inline.style);\n}\n if (inline.hasOwnProperty(\'className\') && external.hasOwnProperty(\'className\')) {\n res.className = external.className + \' \' + inline.className;\n} return res;\n}\n';
var classSetTemplate = _.template('React.addons.classSet(<%= classSet %>)');
var propsMergeFunction = 'function mergeProps(inline,external) {\n var res = _.assign({},inline,external)\nif (inline.hasOwnProperty(\'style\')) {\n res.style = _.defaults(res.style, inline.style);\n}\n' +
' if (inline.hasOwnProperty(\'className\') && external.hasOwnProperty(\'className\')) {\n' +
' res.className = external.className + \' \' + inline.className;\n} return res;\n}\n';
//var classSetTemplate = _.template('React.addons.classSet(<%= classSet %>)');
var classSetTemplate = _.template('_.keys(_.pick(<%= classSet %>, _.identity)).join(" ")');
var simpleTagTemplate = _.template('<%= name %>(<%= props %><%= children %>)');
var tagTemplate = _.template('<%= name %>.apply(this, [<%= props %><%= children %>])');
var simpleTagTemplateCreateElement = _.template('React.createElement(<%= name %>,<%= props %><%= children %>)');
@ -51,7 +54,7 @@ var classSetProp = 'rt-class';
var scopeProp = 'rt-scope';
var propsProp = 'rt-props';
var defaultOptions = {modules: 'amd', version: false, force: false, format: 'stylish', targetVersion: '0.12.2'};
var defaultOptions = {modules: 'amd', version: false, force: false, format: 'stylish', targetVersion: '0.13.1'};
/**
* @param {Context} context
@ -69,7 +72,12 @@ function shouldUseCreateElement(context) {
}
}
var reactSupportedAttributes = ['accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', 'async', 'autoComplete', 'autoPlay', 'cellPadding', 'cellSpacing', 'charSet', 'checked', 'classID', 'className', 'cols', 'colSpan', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'defer', 'dir', 'disabled', 'download', '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', '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'];
var reactSupportedAttributes = ['accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', 'async', 'autoComplete', 'autoPlay', 'cellPadding', 'cellSpacing', 'charSet', 'checked',
'classID', 'className', 'cols', 'colSpan', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'defer', 'dir', 'disabled', 'download',
'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',
'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'];
var attributesMapping = {'class': 'className', 'rt-class': 'className'};
_.forEach(reactSupportedAttributes, function (attributeReactName) {
if (attributeReactName !== attributeReactName.toLowerCase()) {
@ -291,7 +299,7 @@ function convertHtmlToReact(node, context) {
}
var scopeName = scopeSubParts[1].trim();
validateJS(scopeName, node, context);
stringUtils.addIfNotThere(context.boundParams, scopeName);
stringUtils.addIfMissing(context.boundParams, scopeName);
data.scopeName += stringUtils.capitalize(scopeName);
data.scopeMapping[scopeName] = scopeSubParts[0].trim();
validateJS(data.scopeMapping[scopeName], node, context);
@ -307,8 +315,8 @@ function convertHtmlToReact(node, context) {
data.collection = arr[1].trim();
validateJS(data.item, node, context);
validateJS(data.collection, node, context);
stringUtils.addIfNotThere(context.boundParams, data.item);
stringUtils.addIfNotThere(context.boundParams, data.item + 'Index');
stringUtils.addIfMissing(context.boundParams, data.item);
stringUtils.addIfMissing(context.boundParams, data.item + 'Index');
}
data.props = generateProps(node, context);
if (node.attribs[propsProp]) {
@ -377,10 +385,15 @@ function isTag(node) {
return node.type === 'tag';
}
//function isEmptyText(node) {
// return node.type === 'text' && /^\s*$/g.test(node.data);
//}
function handleSelfClosingHtmlTags(nodes) {
return _(nodes)
.map(function (node) {
var externalNodes = [];
//node.children = _.reject(node.children, isEmptyText);
node.children = handleSelfClosingHtmlTags(node.children);
if (node.type === 'tag' && _.contains(htmlSelfClosingTags, node.name)) {
externalNodes = _.filter(node.children, isTag);
@ -395,16 +408,34 @@ function handleSelfClosingHtmlTags(nodes) {
.value();
}
/**
* @param options
* @param {*} context
* @param {CONTEXT} reportContext
* @param node
*/
function validate(options, context, reportContext, node) {
if (node.type === 'tag' && node.attribs['rt-if'] && !node.attribs.key) {
var loc = rtError.getNodeLoc(context, node);
reportContext.warn('rt-if without a key', options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end);
}
if (node.children) {
node.children.forEach(validate.bind(this, options, context, reportContext));
}
}
/**
* @param {string} html
* @param {CONTEXT} reportContext
* @param {{modules:string,defines:*}?} options
* @return {string}
*/
function convertTemplateToReact(html, options) {
function convertTemplateToReact(html, reportContext, options) {
var rootNode = cheerio.load(html, {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true, withStartIndices: true});
options = _.defaults({}, options, defaultOptions);
var defines = options.defines ? options.defines : {'react/addons': 'React', lodash: '_'};
var context = defaultContext(html, options);
validate(options, context, reportContext, rootNode.root()[0]);
var rootTags = _.filter(rootNode.root()[0].children, {type: 'tag'});
rootTags = handleSelfClosingHtmlTags(rootTags);
if (!rootTags || rootTags.length === 0) {
@ -452,7 +483,7 @@ function convertTemplateToReact(html, options) {
tree = escodegen.attachComments(tree, tree.comments, tree.tokens);
code = escodegen.generate(tree, {comment: true});
} catch (e) {
console.log(code);
//console.log(code);
throw new RTCodeError(e.message, e.index, -1);
}
}

View File

@ -21,7 +21,7 @@ function capitalize(str) {
* @param {Array.<*>} array
* @param {*} obj
*/
function addIfNotThere(array, obj) {
function addIfMissing(array, obj) {
if (!_.contains(array, obj)) {
array.push(obj);
}
@ -30,5 +30,5 @@ function addIfNotThere(array, obj) {
module.exports = {
convertToCamelCase: convertToCamelCase,
capitalize: capitalize,
addIfNotThere: addIfNotThere
addIfMissing: addIfMissing
};