UNPKG

25.7 kBJavaScriptView Raw
1'use strict';
2
3var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
4
5var cheerio = require('cheerio');
6var _ = require('lodash');
7var esprima = require('esprima');
8var escodegen = require('escodegen');
9var reactDOMSupport = require('./reactDOMSupport');
10var reactNativeSupport = require('./reactNativeSupport');
11var reactPropTemplates = require('./reactPropTemplates');
12var rtError = require('./RTCodeError');
13var reactSupport = require('./reactSupport');
14var templates = reactSupport.templates;
15var utils = require('./utils');
16var validateJS = utils.validateJS;
17var RTCodeError = rtError.RTCodeError;
18
19var repeatTemplate = _.template('_.map(<%= collection %>,<%= repeatFunction %>.bind(<%= repeatBinds %>))');
20var ifTemplate = _.template('((<%= condition %>)?(<%= body %>):null)');
21var propsTemplateSimple = _.template('_.assign({}, <%= generatedProps %>, <%= rtProps %>)');
22var propsTemplate = _.template('mergeProps( <%= generatedProps %>, <%= rtProps %>)');
23
24var propsMergeFunction = 'function mergeProps(inline,external) {\n var res = _.assign({},inline,external)\n if (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 }\n return res;\n}\n';
25
26var classSetTemplate = _.template('_(<%= classSet %>).transform(function(res, value, key){ if(value){ res.push(key); } }, []).join(" ")');
27
28function getTagTemplateString(simpleTagTemplate, shouldCreateElement) {
29 if (simpleTagTemplate) {
30 return shouldCreateElement ? 'React.createElement(<%= name %>,<%= props %><%= children %>)' : '<%= name %>(<%= props %><%= children %>)';
31 }
32 return shouldCreateElement ? 'React.createElement.apply(this, [<%= name %>,<%= props %><%= children %>])' : '<%= name %>.apply(this, [<%= props %><%= children %>])';
33}
34
35var commentTemplate = _.template(' /* <%= data %> */ ');
36
37var repeatAttr = 'rt-repeat';
38var ifAttr = 'rt-if';
39var classSetAttr = 'rt-class';
40var classAttr = 'class';
41var scopeAttr = 'rt-scope';
42var propsAttr = 'rt-props';
43var templateNode = 'rt-template';
44var virtualNode = 'rt-virtual';
45var includeNode = 'rt-include';
46var includeSrcAttr = 'src';
47var requireAttr = 'rt-require';
48var importAttr = 'rt-import';
49var statelessAttr = 'rt-stateless';
50
51var reactTemplatesSelfClosingTags = [includeNode];
52
53/**
54 * @param {Options} options
55 * @return {Options}
56 */
57function getOptions(options) {
58 options = options || {};
59 var defaultOptions = {
60 version: false,
61 force: false,
62 format: 'stylish',
63 targetVersion: reactDOMSupport.default,
64 lodashImportPath: 'lodash',
65 native: false,
66 nativeTargetVersion: reactNativeSupport.default
67 };
68
69 var finalOptions = _.defaults({}, options, defaultOptions);
70 finalOptions.reactImportPath = finalOptions.reactImportPath || reactImport(finalOptions);
71 finalOptions.modules = finalOptions.modules || (finalOptions.native ? 'commonjs' : 'amd');
72
73 var defaultPropTemplates = finalOptions.native ? reactPropTemplates.native[finalOptions.nativeTargetVersion] : reactPropTemplates.dom[finalOptions.targetVersion];
74
75 finalOptions.propTemplates = _.defaults({}, options.propTemplates, defaultPropTemplates);
76 return finalOptions;
77}
78
79function reactImport(options) {
80 if (options.native) {
81 return 'react-native';
82 }
83 if (options.targetVersion === '0.14.0' || options.targetVersion === '0.15.0' || options.targetVersion === '15.0.0' || options.targetVersion === '15.0.1') {
84 return 'react';
85 }
86 return 'react/addons';
87}
88
89/**
90 * @param {Context} context
91 * @param {string} namePrefix
92 * @param {string} body
93 * @param {*?} params
94 * @return {string}
95 */
96function generateInjectedFunc(context, namePrefix, body, params) {
97 params = params || context.boundParams;
98 var funcName = namePrefix.replace(',', '') + (context.injectedFunctions.length + 1);
99 var funcText = 'function ' + funcName + '(' + params.join(',') + ') {\n ' + body + '\n }\n ';
100 context.injectedFunctions.push(funcText);
101 return funcName;
102}
103
104function generateTemplateProps(node, context) {
105 var propTemplateDefinition = context.options.propTemplates[node.name];
106 var propertiesTemplates = _(node.children).map(function (child, index) {
107 var templateProp = null;
108 if (child.name === templateNode) {
109 // Generic explicit template tag
110 if (!_.has(child.attribs, 'prop')) {
111 throw RTCodeError.build(context, child, 'rt-template must have a prop attribute');
112 }
113
114 var childTemplate = _.find(context.options.propTemplates, { prop: child.attribs.prop }) || { arguments: [] };
115 templateProp = {
116 prop: child.attribs.prop,
117 arguments: (child.attribs.arguments ? child.attribs.arguments.split(',') : childTemplate.arguments) || []
118 };
119 } else if (propTemplateDefinition && propTemplateDefinition[child.name]) {
120 // Implicit child template from configuration
121 templateProp = {
122 prop: propTemplateDefinition[child.name].prop,
123 arguments: child.attribs.arguments ? child.attribs.arguments.split(',') : propTemplateDefinition[child.name].arguments
124 };
125 }
126
127 if (templateProp) {
128 _.assign(templateProp, { childIndex: index, content: _.find(child.children, { type: 'tag' }) });
129 }
130
131 return templateProp;
132 }).compact().value();
133
134 return _.transform(propertiesTemplates, function (props, templateProp) {
135 var functionParams = _.values(context.boundParams).concat(templateProp.arguments);
136
137 var oldBoundParams = context.boundParams;
138 context.boundParams = context.boundParams.concat(templateProp.arguments);
139
140 var functionBody = 'return ' + convertHtmlToReact(templateProp.content, context);
141 context.boundParams = oldBoundParams;
142
143 var generatedFuncName = generateInjectedFunc(context, templateProp.prop, functionBody, functionParams);
144 props[templateProp.prop] = genBind(generatedFuncName, _.values(context.boundParams));
145
146 // Remove the template child from the children definition.
147 node.children.splice(templateProp.childIndex, 1);
148 }, {});
149}
150
151/**
152 * @param node
153 * @param {Context} context
154 * @return {string}
155 */
156function generateProps(node, context) {
157 var props = {};
158 _.forOwn(node.attribs, function (val, key) {
159 var propKey = reactSupport.attributesMapping[key.toLowerCase()] || key;
160 if (props.hasOwnProperty(propKey) && propKey !== reactSupport.classNameProp) {
161 throw RTCodeError.build(context, node, 'duplicate definition of ' + propKey + ' ' + JSON.stringify(node.attribs));
162 }
163 if (_.startsWith(key, 'on') && !utils.isStringOnlyCode(val)) {
164 props[propKey] = handleEventHandler(val, context, node, key);
165 } else if (key === 'style' && !utils.isStringOnlyCode(val)) {
166 props[propKey] = handleStyleProp(val, node, context);
167 } else if (propKey === reactSupport.classNameProp) {
168 // Processing for both class and rt-class conveniently return strings that
169 // represent JS expressions, each evaluating to a space-separated set of class names.
170 // We can just join them with another space here.
171 var existing = props[propKey] ? props[propKey] + ' + " " + ' : '';
172 if (key === classSetAttr) {
173 props[propKey] = existing + classSetTemplate({ classSet: val });
174 } else if (key === classAttr || key === reactSupport.classNameProp) {
175 props[propKey] = existing + utils.convertText(node, context, val.trim());
176 }
177 } else if (!_.startsWith(key, 'rt-')) {
178 props[propKey] = utils.convertText(node, context, val.trim());
179 }
180 });
181 _.assign(props, generateTemplateProps(node, context));
182
183 var propStr = _.map(props, function (v, k) {
184 return JSON.stringify(k) + ' : ' + v;
185 }).join(',');
186 return '{' + propStr + '}';
187}
188
189function handleEventHandler(val, context, node, key) {
190 var funcParts = val.split('=>');
191 if (funcParts.length !== 2) {
192 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 + '\']');
193 }
194 var evtParams = funcParts[0].replace('(', '').replace(')', '').trim();
195 var funcBody = funcParts[1].trim();
196 var params = context.boundParams;
197 if (evtParams.trim() !== '') {
198 params = params.concat([evtParams.trim()]);
199 }
200 var generatedFuncName = generateInjectedFunc(context, key, funcBody, params);
201 return genBind(generatedFuncName, context.boundParams);
202}
203
204function genBind(func, args) {
205 var bindArgs = ['this'].concat(args);
206 return func + '.bind(' + bindArgs.join(',') + ')';
207}
208
209function handleStyleProp(val, node, context) {
210 var styleStr = _(val).split(';').map(_.trim).filter(function (i) {
211 return _.includes(i, ':');
212 }).map(function (i) {
213 var pair = i.split(':');
214
215 var value = pair.slice(1).join(':').trim();
216 return _.camelCase(pair[0].trim()) + ' : ' + utils.convertText(node, context, value.trim());
217 }).join(',');
218 return '{' + styleStr + '}';
219}
220
221/**
222 * @param {string} tagName
223 * @param context
224 * @return {string}
225 */
226function convertTagNameToConstructor(tagName, context) {
227 if (context.options.native) {
228 return _.includes(reactNativeSupport[context.options.nativeTargetVersion], tagName) ? 'React.' + tagName : tagName;
229 }
230 var isHtmlTag = _.includes(reactDOMSupport[context.options.targetVersion], tagName);
231 if (reactSupport.shouldUseCreateElement(context)) {
232 isHtmlTag = isHtmlTag || tagName.match(/^\w+(-\w+)$/);
233 return isHtmlTag ? '\'' + tagName + '\'' : tagName;
234 }
235 return isHtmlTag ? 'React.DOM.' + tagName : tagName;
236}
237
238/**
239 * @param {string} html
240 * @param options
241 * @param reportContext
242 * @return {Context}
243 */
244function defaultContext(html, options, reportContext) {
245 var defaultDefines = [{ moduleName: options.reactImportPath, alias: 'React', member: '*' }, { moduleName: options.lodashImportPath, alias: '_', member: '*' }];
246 return {
247 boundParams: [],
248 injectedFunctions: [],
249 html: html,
250 options: options,
251 defines: options.defines ? _.clone(options.defines) : defaultDefines,
252 reportContext: reportContext
253 };
254}
255
256/**
257 * @param node
258 * @return {boolean}
259 */
260function hasNonSimpleChildren(node) {
261 return _.some(node.children, function (child) {
262 return child.type === 'tag' && child.attribs[repeatAttr];
263 });
264}
265
266/**
267 * @param node
268 * @param {Context} context
269 * @return {string}
270 */
271function convertHtmlToReact(node, context) {
272 if (node.type === 'tag' || node.type === 'style') {
273 var _ret = function () {
274 context = _.defaults({
275 boundParams: _.clone(context.boundParams)
276 }, context);
277
278 if (node.type === 'tag' && node.name === includeNode) {
279 var srcFile = node.attribs[includeSrcAttr];
280 if (!srcFile) {
281 throw RTCodeError.build(context, node, 'rt-include must supply a source attribute');
282 }
283 if (!context.options.readFileSync) {
284 throw RTCodeError.build(context, node, 'rt-include needs a readFileSync polyfill on options');
285 }
286 try {
287 context.html = context.options.readFileSync(srcFile);
288 } catch (e) {
289 console.error(e);
290 throw RTCodeError.build(context, node, 'rt-include failed to read file \'' + srcFile + '\'');
291 }
292 return {
293 v: parseAndConvertHtmlToReact(context.html, context)
294 };
295 }
296
297 var data = { name: convertTagNameToConstructor(node.name, context) };
298
299 // Order matters. We need to add the item and itemIndex to context.boundParams before
300 // the rt-scope directive is processed, lest they are not passed to the child scopes
301 if (node.attribs[repeatAttr]) {
302 var arr = node.attribs[repeatAttr].split(' in ');
303 if (arr.length !== 2) {
304 throw RTCodeError.build(context, node, 'rt-repeat invalid \'in\' expression \'' + node.attribs[repeatAttr] + '\'');
305 }
306 var repeaterParams = arr[0].split(',').map(function (s) {
307 return s.trim();
308 });
309 data.item = repeaterParams[0];
310 data.index = repeaterParams[1] || data.item + 'Index';
311 data.collection = arr[1].trim();
312 var bindParams = [data.item, data.index];
313 _.forEach(bindParams, function (param) {
314 validateJS(param, node, context);
315 });
316 validateJS('(' + data.collection + ')', node, context);
317 _.forEach(bindParams, function (param) {
318 if (!_.includes(context.boundParams, param)) {
319 context.boundParams.push(param);
320 }
321 });
322 }
323
324 if (node.attribs[scopeAttr]) {
325 handleScopeAttribute(node, context, data);
326 }
327
328 if (node.attribs[ifAttr]) {
329 validateIfAttribute(node, context, data);
330 data.condition = node.attribs[ifAttr].trim();
331 if (!node.attribs.key) {
332 _.set(node, ['attribs', 'key'], '' + node.startIndex);
333 }
334 }
335
336 data.props = generateProps(node, context);
337 if (node.attribs[propsAttr]) {
338 if (data.props === '{}') {
339 data.props = node.attribs[propsAttr];
340 } else if (!node.attribs.style && !node.attribs.class) {
341 data.props = propsTemplateSimple({ generatedProps: data.props, rtProps: node.attribs[propsAttr] });
342 } else {
343 data.props = propsTemplate({ generatedProps: data.props, rtProps: node.attribs[propsAttr] });
344 if (!_.includes(context.injectedFunctions, propsMergeFunction)) {
345 context.injectedFunctions.push(propsMergeFunction);
346 }
347 }
348 }
349
350 // provide a key to virtual node children if missing
351 if (node.name === virtualNode && node.children.length > 1) {
352 _(node.children).reject('attribs.key').forEach(function (child, i) {
353 _.set(child, ['attribs', 'key'], '' + node.startIndex + i);
354 });
355 }
356
357 var children = _.map(node.children, function (child) {
358 var code = convertHtmlToReact(child, context);
359 validateJS(code, child, context);
360 return code;
361 });
362
363 data.children = utils.concatChildren(children);
364
365 if (node.name === virtualNode) {
366 //eslint-disable-line wix-editor/prefer-ternary
367 data.body = '[' + _.compact(children).join(',') + ']';
368 } else {
369 data.body = _.template(getTagTemplateString(!hasNonSimpleChildren(node), reactSupport.shouldUseCreateElement(context)))(data);
370 }
371
372 if (node.attribs[scopeAttr]) {
373 var functionBody = _.values(data.innerScope.innerMapping).join('\n') + ('return ' + data.body);
374 var generatedFuncName = generateInjectedFunc(context, 'scope' + data.innerScope.scopeName, functionBody, _.keys(data.innerScope.outerMapping));
375 data.body = generatedFuncName + '.apply(this, [' + _.values(data.innerScope.outerMapping).join(',') + '])';
376 }
377
378 // Order matters here. Each rt-repeat iteration wraps over the rt-scope, so
379 // the scope variables are evaluated in context of the current iteration.
380 if (node.attribs[repeatAttr]) {
381 data.repeatFunction = generateInjectedFunc(context, 'repeat' + _.upperFirst(data.item), 'return ' + data.body);
382 data.repeatBinds = ['this'].concat(_.reject(context.boundParams, function (p) {
383 return p === data.item || p === data.item + 'Index' || data.innerScope && p in data.innerScope.innerMapping;
384 }));
385 data.body = repeatTemplate(data);
386 }
387 if (node.attribs[ifAttr]) {
388 data.body = ifTemplate(data);
389 }
390 return {
391 v: data.body
392 };
393 }();
394
395 if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
396 } else if (node.type === 'comment') {
397 return commentTemplate(node);
398 } else if (node.type === 'text') {
399 return node.data.trim() ? utils.convertText(node, context, node.data) : '';
400 }
401}
402
403function handleScopeAttribute(node, context, data) {
404 data.innerScope = {
405 scopeName: '',
406 innerMapping: {},
407 outerMapping: {}
408 };
409
410 data.innerScope.outerMapping = _.zipObject(context.boundParams, context.boundParams);
411
412 _(node.attribs[scopeAttr]).split(';').invokeMap('trim').compact().forEach(function (scopePart) {
413 var scopeSubParts = _(scopePart).split(' as ').invokeMap('trim').value();
414 if (scopeSubParts.length < 2) {
415 throw RTCodeError.build(context, node, 'invalid scope part \'' + scopePart + '\'');
416 }
417 var alias = scopeSubParts[1];
418 var value = scopeSubParts[0];
419 validateJS(alias, node, context);
420
421 // this adds both parameters to the list of parameters passed further down
422 // the scope chain, as well as variables that are locally bound before any
423 // function call, as with the ones we generate for rt-scope.
424 if (!_.includes(context.boundParams, alias)) {
425 context.boundParams.push(alias);
426 }
427
428 data.innerScope.scopeName += _.upperFirst(alias);
429 data.innerScope.innerMapping[alias] = 'var ' + alias + ' = ' + value + ';';
430 validateJS(data.innerScope.innerMapping[alias], node, context);
431 });
432}
433
434function validateIfAttribute(node, context, data) {
435 var innerMappingKeys = _.keys(data.innerScope && data.innerScope.innerMapping || {});
436 var ifAttributeTree = null;
437 try {
438 ifAttributeTree = esprima.parse(node.attribs[ifAttr]);
439 } catch (e) {
440 throw new RTCodeError(e.message, e.index, -1);
441 }
442 if (ifAttributeTree && ifAttributeTree.body && ifAttributeTree.body.length === 1 && ifAttributeTree.body[0].type === 'ExpressionStatement') {
443 // make sure that rt-if does not use an inner mapping
444 if (ifAttributeTree.body[0].expression && utils.usesScopeName(innerMappingKeys, ifAttributeTree.body[0].expression)) {
445 throw RTCodeError.buildFormat(context, node, "invalid scope mapping used in if part '%s'", node.attribs[ifAttr]);
446 }
447 } else {
448 throw RTCodeError.buildFormat(context, node, "invalid if part '%s'", node.attribs[ifAttr]);
449 }
450}
451
452function handleSelfClosingHtmlTags(nodes) {
453 return _.flatMap(nodes, function (node) {
454 var externalNodes = [];
455 node.children = handleSelfClosingHtmlTags(node.children);
456 if (node.type === 'tag' && (_.includes(reactSupport.htmlSelfClosingTags, node.name) || _.includes(reactTemplatesSelfClosingTags, node.name))) {
457 externalNodes = _.filter(node.children, { type: 'tag' });
458 _.forEach(externalNodes, function (i) {
459 i.parent = node;
460 });
461 node.children = _.reject(node.children, { type: 'tag' });
462 }
463 return [node].concat(externalNodes);
464 });
465}
466
467function handleRequire(tag, context) {
468 var moduleName = void 0;
469 var alias = void 0;
470 var member = void 0;
471 if (tag.children.length) {
472 throw RTCodeError.build(context, tag, '\'' + requireAttr + '\' may have no children');
473 } else if (tag.attribs.dependency && tag.attribs.as) {
474 moduleName = tag.attribs.dependency;
475 member = '*';
476 alias = tag.attribs.as;
477 }
478 if (!moduleName) {
479 throw RTCodeError.build(context, tag, '\'' + requireAttr + '\' needs \'dependency\' and \'as\' attributes');
480 }
481 context.defines.push({ moduleName: moduleName, member: member, alias: alias });
482}
483
484function handleImport(tag, context) {
485 var moduleName = void 0;
486 var alias = void 0;
487 var member = void 0;
488 if (tag.children.length) {
489 throw RTCodeError.build(context, tag, '\'' + importAttr + '\' may have no children');
490 } else if (tag.attribs.name && tag.attribs.from) {
491 moduleName = tag.attribs.from;
492 member = tag.attribs.name;
493 alias = tag.attribs.as;
494 if (!alias) {
495 if (member === '*') {
496 throw RTCodeError.build(context, tag, "'*' imports must have an 'as' attribute");
497 } else if (member === 'default') {
498 throw RTCodeError.build(context, tag, "default imports must have an 'as' attribute");
499 }
500 alias = member;
501 }
502 }
503 if (!moduleName) {
504 throw RTCodeError.build(context, tag, '\'' + importAttr + '\' needs \'name\' and \'from\' attributes');
505 }
506 context.defines.push({ moduleName: moduleName, member: member, alias: alias });
507}
508
509function convertTemplateToReact(html, options) {
510 var context = require('./context');
511 return convertRT(html, context, options);
512}
513
514function parseAndConvertHtmlToReact(html, context) {
515 var rootNode = cheerio.load(html, {
516 lowerCaseTags: false,
517 lowerCaseAttributeNames: false,
518 xmlMode: true,
519 withStartIndices: true
520 });
521 utils.validate(context.options, context, context.reportContext, rootNode.root()[0]);
522 var rootTags = _.filter(rootNode.root()[0].children, { type: 'tag' });
523 rootTags = handleSelfClosingHtmlTags(rootTags);
524 if (!rootTags || rootTags.length === 0) {
525 throw new RTCodeError('Document should have a root element');
526 }
527 var firstTag = null;
528 _.forEach(rootTags, function (tag) {
529 if (tag.name === requireAttr) {
530 handleRequire(tag, context);
531 } else if (tag.name === importAttr) {
532 handleImport(tag, context);
533 } else if (firstTag === null) {
534 firstTag = tag;
535 if (_.hasIn(tag, ['attribs', statelessAttr])) {
536 context.stateless = true;
537 }
538 } else {
539 throw RTCodeError.build(context, tag, 'Document should have no more than a single root element');
540 }
541 });
542 if (firstTag === null) {
543 throw RTCodeError.build(context, rootNode.root()[0], 'Document should have a single root element');
544 } else if (firstTag.name === virtualNode) {
545 throw RTCodeError.build(context, firstTag, 'Document should not have <' + virtualNode + '> as root element');
546 }
547 return convertHtmlToReact(firstTag, context);
548}
549
550/**
551 * @param {string} html
552 * @param {CONTEXT} reportContext
553 * @param {Options?} options
554 * @return {string}
555 */
556function convertRT(html, reportContext, options) {
557 options = getOptions(options);
558
559 var context = defaultContext(html, options, reportContext);
560 var body = parseAndConvertHtmlToReact(html, context);
561
562 var requirePaths = _.map(context.defines, function (d) {
563 return '"' + d.moduleName + '"';
564 }).join(',');
565 var requireNames = _.map(context.defines, function (d) {
566 return '' + d.alias;
567 }).join(',');
568 var buildImport = reactSupport.buildImport[options.modules] || reactSupport.buildImport.commonjs;
569 var requires = _.map(context.defines, buildImport).join('\n');
570 var header = options.flow ? '/* @flow */\n' : '';
571 var vars = header + requires;
572 var data = {
573 body: body,
574 injectedFunctions: context.injectedFunctions.join('\n'),
575 requireNames: requireNames,
576 requirePaths: requirePaths,
577 vars: vars,
578 name: options.name,
579 statelessProps: context.stateless ? 'props' : ''
580 };
581 var code = templates[options.modules](data);
582 if (options.modules !== 'typescript' && options.modules !== 'jsrt') {
583 code = parseJS(code);
584 }
585 return code;
586}
587
588function parseJS(code) {
589 try {
590 var tree = esprima.parse(code, { range: true, tokens: true, comment: true, sourceType: 'module' });
591 tree = escodegen.attachComments(tree, tree.comments, tree.tokens);
592 return escodegen.generate(tree, { comment: true });
593 } catch (e) {
594 throw new RTCodeError(e.message, e.index, -1);
595 }
596}
597
598function convertJSRTToJS(text, reportContext, options) {
599 options = getOptions(options);
600 options.modules = 'jsrt';
601 var templateMatcherJSRT = /<template>([^]*?)<\/template>/gm;
602 var code = text.replace(templateMatcherJSRT, function (template, html) {
603 return convertRT(html, reportContext, options).replace(/;$/, '');
604 });
605
606 return parseJS(code);
607}
608
609module.exports = {
610 convertTemplateToReact: convertTemplateToReact,
611 convertRT: convertRT,
612 convertJSRTToJS: convertJSRTToJS,
613 RTCodeError: RTCodeError,
614 normalizeName: utils.normalizeName
615};
\No newline at end of file