diff --git a/src/compiler/util/parseJavaScript.js b/src/compiler/util/parseJavaScript.js
index 73b830d8e5..ec13d56158 100644
--- a/src/compiler/util/parseJavaScript.js
+++ b/src/compiler/util/parseJavaScript.js
@@ -192,7 +192,15 @@ function parseExpression(src, builder, isExpression) {
if (node.body && node.body.length === 1) {
return convert(node.body[0]);
}
- return null;
+
+ let container = builder.containerNode();
+ for (let child of node.body) {
+ let convertedChild = convert(child);
+ if (convertedChild) {
+ container.appendChild(convertedChild);
+ }
+ }
+ return container;
}
case "ObjectExpression": {
let properties = convert(node.properties);
@@ -209,6 +217,10 @@ function parseExpression(src, builder, isExpression) {
return null;
}
+ if (node.kind === "get" || node.kind === "set") {
+ return null;
+ }
+
if (!computed && key.type === "Identifier") {
// Favor using a Literal AST node to represent
// the key instead of an Identifier
@@ -288,6 +300,49 @@ function parseExpression(src, builder, isExpression) {
}
return builder.vars(declarations, kind);
}
+ case "IfStatement": {
+ const ifNode = builder.ifStatement(
+ convert(node.test),
+ convert(node.consequent)
+ );
+
+ let alternate = node.alternate;
+
+ if (!alternate) {
+ return ifNode;
+ }
+
+ const container = builder.containerNode();
+ container.appendChild(ifNode);
+
+ do {
+ container.appendChild(
+ alternate.consequent
+ ? builder.elseIfStatement(
+ convert(alternate.test),
+ convert(alternate.consequent)
+ )
+ : builder.elseStatement(convert(alternate))
+ );
+ alternate = alternate.alternate;
+ } while (alternate);
+
+ return container;
+ }
+ case "ForStatement": {
+ return builder.forStatement(
+ convert(node.init),
+ convert(node.test),
+ convert(node.update),
+ convert(node.body)
+ );
+ }
+ case "WhileStatement": {
+ return builder.whileStatement(
+ convert(node.test),
+ convert(node.body)
+ );
+ }
default:
return null;
}
diff --git a/src/taglibs/core/invoke-tag.js b/src/taglibs/core/invoke-tag.js
index 566f058d4c..dce4219f0e 100644
--- a/src/taglibs/core/invoke-tag.js
+++ b/src/taglibs/core/invoke-tag.js
@@ -1,70 +1,9 @@
+const renderCallToDynamicTag = require("./util/renderCallToDynamicTag");
+
module.exports = function codeGenerator(elNode, context) {
+ const builder = context.builder;
const functionAttr = elNode.attributes[0];
- const attrs = elNode.attributes;
- const args = context.builder.parseJavaScriptArgs(functionAttr.argument);
- const argsLength = args.length;
- const functionName = functionAttr.name;
-
- let outIsFirstIndex = false;
- let argsContainsOut = false;
- let functionCallExpression = null;
- let newNode = null;
-
- args.map((arg, i) => {
- if (arg.name === "out") {
- if (i === 0) {
- outIsFirstIndex = true;
- }
-
- argsContainsOut = true;
- }
- });
-
- // Removes HtmlAtrribute
- attrs.splice(0, 1);
-
- if (
- (functionName === "data.renderBody" ||
- functionName === "input.renderBody") &&
- argsContainsOut &&
- outIsFirstIndex
- ) {
- // Handles cases for the following:
- // 1. --> <${data.renderBody} w-id="barTest"/>
-
- attrs.unshift({
- value: args[0],
- spread: true
- });
-
- newNode = context.createNodeForEl(
- context.builder.parseExpression(functionName),
- attrs
- );
- } else if (argsLength > 1 && argsContainsOut && !outIsFirstIndex) {
- // Handles cases for the following:
- // 1. --> <${{ render:data.barRenderer }} ...{} w-id="barTest"/>
- // 2. --> <${{ render: data.template.render }} ...{} w-id="barTest"/>
- // 3. --> <${{ render: data.template.renderer }} ...{} w-id="barTest"/>
-
- attrs.unshift({
- value: args[0],
- spread: true
- });
-
- newNode = context.createNodeForEl(
- context.builder.parseExpression("{renderer:" + functionName + "}"),
- attrs
- );
- } else {
- // Handles all other cases:
- // 1. e.g. --> console.log(arguement/s)
-
- functionCallExpression = functionName + "(" + args + ");";
- newNode = context.builder.scriptlet({
- value: functionCallExpression
- });
- }
+ const functionArgs = functionAttr.argument;
context.deprecate(
'The "" tag is deprecated. Please use "$ " for JavaScript in the template. See: https://github.com/marko-js/marko/wiki/Deprecation:-var-assign-invoke-tags'
@@ -77,13 +16,28 @@ module.exports = function codeGenerator(elNode, context) {
return;
}
- if (args === undefined) {
+ if (functionArgs === undefined) {
context.addError(
'Invalid tag. Missing function arguments. Expected: {
+ if (attr !== functionAttr) {
+ replacement.addAttribute(attr);
+ }
+ });
+ } else {
+ replacement = builder.scriptlet({
+ value: functionCallExpression
+ });
+ }
+
+ elNode.replaceWith(replacement);
};
diff --git a/src/taglibs/core/marko.json b/src/taglibs/core/marko.json
index 62653dd2c8..66c9513eb9 100644
--- a/src/taglibs/core/marko.json
+++ b/src/taglibs/core/marko.json
@@ -1,4 +1,5 @@
{
+ "transformer": "./root-transformer",
"": {
"transformer": "./assign-tag",
"open-tag-only": true,
diff --git a/src/taglibs/core/root-transformer.js b/src/taglibs/core/root-transformer.js
new file mode 100644
index 0000000000..0de87c124b
--- /dev/null
+++ b/src/taglibs/core/root-transformer.js
@@ -0,0 +1,76 @@
+"use strict";
+
+const OUT_IDENTIFIER_REG = /[(,] *out *[,)]/;
+const renderCallToDynamicTag = require("./util/renderCallToDynamicTag");
+
+module.exports = function transform(el, context) {
+ const walker = context.createWalker({
+ enter(node) {
+ if (
+ node.type !== "Scriptlet" ||
+ !OUT_IDENTIFIER_REG.test(node.code)
+ ) {
+ return;
+ }
+
+ const replacement = replaceScriptlets(
+ context.builder.parseStatement(node.code),
+ context
+ );
+
+ node.replaceWith(replacement);
+ }
+ });
+ walker.walk(el);
+};
+
+function replaceScriptlets(node, context) {
+ const builder = context.builder;
+ if (!node.type) {
+ if (node.replaceChild) {
+ node.forEach(child => {
+ const replacement = replaceScriptlets(child, context);
+ if (child !== replacement) {
+ node.replaceChild(replacement, child);
+ }
+ });
+ } else if (node.body) {
+ node.body.forEach(child => {
+ const replacement = replaceScriptlets(child, context);
+ if (child !== replacement) {
+ node.body.replaceChild(replacement, child);
+ }
+ });
+ }
+
+ return node;
+ }
+
+ switch (node.type) {
+ case "LogicalExpression":
+ node = builder.ifStatement(
+ node.operator === "&&" ? node.left : builder.negate(node.left),
+ [replaceScriptlets(node.right, context)]
+ );
+ break;
+ case "FunctionCall":
+ node = renderCallToDynamicTag(node, context) || node;
+ break;
+ case "If":
+ case "ElseIf":
+ node.body = replaceScriptlets(node.body, context);
+ if (node.else) {
+ replaceScriptlets(node.else, context);
+ }
+ break;
+ case "Else":
+ case "ForStatement":
+ case "WhileStatement":
+ node.body = replaceScriptlets(node.body, context);
+ break;
+ default:
+ break;
+ }
+
+ return node;
+}
diff --git a/src/taglibs/core/util/renderCallToDynamicTag.js b/src/taglibs/core/util/renderCallToDynamicTag.js
new file mode 100644
index 0000000000..61eb6f45ad
--- /dev/null
+++ b/src/taglibs/core/util/renderCallToDynamicTag.js
@@ -0,0 +1,89 @@
+module.exports = function renderCallToDynamicTag(ast, context) {
+ const builder = context.builder;
+ const args = ast.args;
+ const callee = ast.callee;
+ const argsLength = args.length;
+ const outIndex = args.findIndex(arg => arg.name === "out");
+ const calleeProperty = callee.property && callee.property.name;
+
+ if (outIndex === -1) {
+ return false;
+ }
+
+ let tagName;
+ let tagAttrs;
+
+ if (argsLength <= 2) {
+ if (outIndex === 0) {
+ // Handles cases for the following:
+ // 1. input.renderBody(out) --> <${input}/>
+ // 2. input.renderThing(out) --> <${input.renderThing}/>
+ // 3. input.renderBody(out, attrs) --> <${input} ...attrs/>
+ // 4. renderBody(out) --> <${renderBody}/>
+ if (argsLength === 2) {
+ tagName = callee;
+ tagAttrs = toAttributesOrSpread(args[1]);
+ }
+
+ // Removes `.renderBody` which is optional.
+ if (calleeProperty === "renderBody") {
+ tagName = callee.object;
+ } else {
+ tagName = callee;
+ }
+ } else if (outIndex === 1) {
+ // Handles cases for the following:
+ // 1. input.template.render({}, out) --> <${input.template} ...{}/>
+ // 2. input.template.renderer({}, out) --> <${input.template} ...{}/>
+ // 3. input.barRenderer({}, out) --> <${{ render:input.barRenderer }} ...{}/>
+
+ tagAttrs = toAttributesOrSpread(args[0]);
+
+ // Removes `.render` or `.renderer` which are optional.
+ if (calleeProperty === "render" || calleeProperty === "renderer") {
+ tagName = callee.object;
+ } else {
+ tagName = builder.objectExpression({
+ render: callee
+ });
+ }
+ }
+ } else {
+ // Handles worst case scenario:
+ // 1. input.barRenderer({}, true, out) --> <${(out) => input.barRenderer({}, true, out)}/>
+ tagName = builder.functionDeclaration(
+ null,
+ [builder.identifier("out")],
+ [ast]
+ );
+ }
+
+ return context.createNodeForEl(tagName, tagAttrs, null, true, true);
+};
+
+function toAttributesOrSpread(val) {
+ if (
+ !val ||
+ (val.type === "Literal" && val.value === null) ||
+ (val.type === "Identifier" && val.name === "undefined")
+ ) {
+ return [];
+ }
+
+ if (
+ val.type === "ObjectExpression" &&
+ val.properties.every(prop => !prop.computed)
+ ) {
+ return val.properties.map(prop => ({
+ name: prop.literalKeyValue,
+ value: prop.value
+ }));
+ }
+
+ return [
+ {
+ value: val,
+ spread: true
+ }
+ ];
+}
diff --git a/test/compiler/fixtures-html/invoke-if/expected.js b/test/compiler/fixtures-html/invoke-if/expected.js
index cf66196309..2c5510854e 100644
--- a/test/compiler/fixtures-html/invoke-if/expected.js
+++ b/test/compiler/fixtures-html/invoke-if/expected.js
@@ -10,7 +10,7 @@ function render(input, out, __component, component, state) {
var data = input;
if (true) {
- console.log("hello");
+ console.log('hello')
}
}
diff --git a/test/compiler/fixtures-html/invoke/expected.js b/test/compiler/fixtures-html/invoke/expected.js
index 5a3635c268..1eb7065896 100644
--- a/test/compiler/fixtures-html/invoke/expected.js
+++ b/test/compiler/fixtures-html/invoke/expected.js
@@ -11,7 +11,9 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
function render(input, out, __component, component, state) {
var data = input;
- marko_dynamicTag(input.renderBody, out, out, __component, "0");
+ marko_dynamicTag(input, {
+ x: 1
+ }, out, __component, "hi");
}
marko_template._ = marko_renderer(render, {
diff --git a/test/compiler/fixtures-html/invoke/template.marko b/test/compiler/fixtures-html/invoke/template.marko
index 292a434273..58e986a643 100644
--- a/test/compiler/fixtures-html/invoke/template.marko
+++ b/test/compiler/fixtures-html/invoke/template.marko
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test/compiler/fixtures-html/render-body-call/expected.js b/test/compiler/fixtures-html/render-body-call/expected.js
new file mode 100644
index 0000000000..e19c3e0225
--- /dev/null
+++ b/test/compiler/fixtures-html/render-body-call/expected.js
@@ -0,0 +1,76 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/html").t(__filename),
+ marko_componentType = "/marko-test$1.0.0/compiler/fixtures-html/render-body-call/template.marko",
+ components_helpers = require("marko/src/components/helpers"),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/html/helpers"),
+ marko_dynamicTag = marko_helpers.d;
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ marko_dynamicTag(input, {}, out, __component, "0");
+
+ marko_dynamicTag(input.renderThing, {}, out, __component, "1");
+
+ marko_dynamicTag(input, attrs, out, __component, "2");
+
+ marko_dynamicTag(renderBody, {}, out, __component, "3");
+
+ marko_dynamicTag(input.template, {
+ x: 1
+ }, out, __component, "4");
+
+ marko_dynamicTag(input.template, {
+ y: function() {}
+ }, out, __component, "5");
+
+ marko_dynamicTag({
+ render: input.barRenderer
+ }, {}, out, __component, "6");
+
+ marko_dynamicTag(function(out) {
+ input.barRenderer({}, true, out);
+ }, {}, out, __component, "7");
+
+ if (x) {
+ marko_dynamicTag(renderA, {}, out, __component, "8");
+ } else if (y) {
+ marko_dynamicTag(renderB, {}, out, __component, "9");
+ } else {
+ marko_dynamicTag(renderC, {}, out, __component, "10");
+ }
+
+ if (x) {
+ marko_dynamicTag(render, {}, out, __component, "11");
+ }
+
+ if (!x) {
+ marko_dynamicTag(render, {}, out, __component, "12");
+ }
+
+ var for__13 = 0;
+
+ for (let i = 0; i < 10; i++) {
+ var keyscope__14 = "[" + ((for__13++) + "]");
+
+ marko_dynamicTag(input.items[i], {}, out, __component, "15" + keyscope__14);
+ }
+
+ let i = 10;
+
+ while (i--) marko_dynamicTag(input, {}, out, __component, "16")
+}
+
+marko_template._ = marko_renderer(render, {
+ ___implicit: true,
+ ___type: marko_componentType
+ });
+
+marko_template.Component = marko_defineComponent({}, marko_template._);
+
+marko_template.meta = {
+ id: "/marko-test$1.0.0/compiler/fixtures-html/render-body-call/template.marko"
+ };
diff --git a/test/compiler/fixtures-html/render-body-call/template.marko b/test/compiler/fixtures-html/render-body-call/template.marko
new file mode 100644
index 0000000000..5c18c3045f
--- /dev/null
+++ b/test/compiler/fixtures-html/render-body-call/template.marko
@@ -0,0 +1,32 @@
+$ {
+ input.renderBody(out);
+ input.renderThing(out);
+ input.renderBody(out, attrs);
+ renderBody(out);
+}
+
+$ input.template.render({ x: 1 }, out);
+$ input.template.renderer({ y() {} }, out);
+$ input.barRenderer(null, out);
+
+$ input.barRenderer({}, true, out);
+
+$ if (x) {
+ renderA(out);
+} else if (y) {
+ renderB(out);
+} else {
+ renderC(out);
+}
+
+$ x && render(out);
+$ x || render(out);
+
+$ for (let i = 0; i < 10; i++) {
+ input.items[i].renderBody(out);
+}
+
+$ let i = 10;
+$ while (i--) {
+ input.renderBody(out);
+}
\ No newline at end of file