mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Migration stage (#1180)
* Add migration stage to taglib * integrate migrations into migration stage
This commit is contained in:
parent
3e68893388
commit
7d37fe3634
@ -460,10 +460,6 @@ class CompileContext extends EventEmitter {
|
||||
elDef = { tagName, argument, attributes, openTagOnly, selfClosed };
|
||||
}
|
||||
|
||||
if (elDef.tagName === "") {
|
||||
elDef.tagName = tagName = "assign";
|
||||
}
|
||||
|
||||
if (!attributes) {
|
||||
attributes = elDef.attributes = [];
|
||||
} else if (typeof attributes === "object") {
|
||||
|
||||
@ -154,7 +154,7 @@ class Compiler {
|
||||
var codeGenerator = new CodeGenerator(context);
|
||||
|
||||
// STAGE 1: Parse the template to produce the initial AST
|
||||
var ast = this.parser.parse(src, context);
|
||||
var ast = this.parser.parse(src, context, { migrate: true });
|
||||
context._parsingFinished = true;
|
||||
|
||||
if (!context.ignoreUnrecognizedTags && context.unrecognizedTags) {
|
||||
|
||||
87
src/compiler/Migrator.js
Normal file
87
src/compiler/Migrator.js
Normal file
@ -0,0 +1,87 @@
|
||||
"use strict";
|
||||
|
||||
var createError = require("raptor-util/createError");
|
||||
|
||||
const FLAG_MIGRATOR_APPLIED = "migratorApply";
|
||||
|
||||
function migrateNode(node, context) {
|
||||
if (node.isDetached()) {
|
||||
return; //The node might have been removed from the tree
|
||||
}
|
||||
|
||||
if (!(node.tagName || node.tagNameExpression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.taglibLookup.forEachTagMigrator(node, migration => {
|
||||
// Check to make sure a migration of a certain type is only applied once to a node
|
||||
if (node.isTransformerApplied(migration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the node as have been transformed by the current migrator
|
||||
node.setTransformerApplied(migration);
|
||||
// Set the flag to indicate that a node was transformed
|
||||
context.setFlag(FLAG_MIGRATOR_APPLIED);
|
||||
context._currentNode = node;
|
||||
|
||||
try {
|
||||
migration(node, context);
|
||||
} catch (e) {
|
||||
throw createError(
|
||||
new Error(
|
||||
'Unable to migrate template at path "' +
|
||||
context.filename +
|
||||
'". Error: ' +
|
||||
e.message
|
||||
),
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function migrateTreeHelper(node, context) {
|
||||
migrateNode(node, context);
|
||||
|
||||
/*
|
||||
* Now process the child nodes by looping over the child nodes
|
||||
* and migrating the subtree recursively
|
||||
*
|
||||
* NOTE: The length of the childNodes array might change as the tree is being performed.
|
||||
* The checks to prevent migrators from being applied multiple times makes
|
||||
* sure that this is not a problem.
|
||||
*/
|
||||
|
||||
node.forEachChild(function(childNode) {
|
||||
migrateTreeHelper(childNode, context);
|
||||
});
|
||||
}
|
||||
|
||||
function migrateTree(ast, context) {
|
||||
// TODO: Consider moving this into the loop below so that root level migrations
|
||||
// are also run on new nodes.
|
||||
context.taglibLookup.forEachTemplateMigrator(migration => {
|
||||
migration(ast, context);
|
||||
});
|
||||
|
||||
/*
|
||||
* The tree is continuously migrated until we go through an entire pass where
|
||||
* there were no new nodes that needed to be migrated. This loop makes sure that
|
||||
* nodes added by migrators are also migrated.
|
||||
*/
|
||||
do {
|
||||
context.clearFlag(FLAG_MIGRATOR_APPLIED);
|
||||
//Reset the flag to indicate that no migrations were yet applied to any of the nodes for this pass
|
||||
migrateTreeHelper(ast, context); //Run the migrations on the tree
|
||||
} while (context.isFlagSet(FLAG_MIGRATOR_APPLIED));
|
||||
}
|
||||
|
||||
class Migrator {
|
||||
migrate(ast, context) {
|
||||
migrateTree(ast, context);
|
||||
return ast;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Migrator;
|
||||
@ -2,6 +2,7 @@
|
||||
var ok = require("assert").ok;
|
||||
var extend = require("raptor-util/extend");
|
||||
var Normalizer = require("./Normalizer");
|
||||
var Migrator = require("./Migrator");
|
||||
|
||||
var COMPILER_ATTRIBUTE_HANDLERS = {
|
||||
"preserve-whitespace": function(attr, context) {
|
||||
@ -96,6 +97,7 @@ class Parser {
|
||||
var rootNode = builder.templateRoot();
|
||||
var mergedOptions = Object.assign({}, this.defaultOptions, options);
|
||||
var raw = mergedOptions.raw === true;
|
||||
var migrate = mergedOptions.migrate === true;
|
||||
|
||||
this.stack.push({
|
||||
node: rootNode
|
||||
@ -103,6 +105,11 @@ class Parser {
|
||||
|
||||
this.parserImpl.parse(src, this, context.filename, mergedOptions);
|
||||
|
||||
if (migrate) {
|
||||
var migrator = new Migrator();
|
||||
rootNode = migrator.migrate(rootNode, context);
|
||||
}
|
||||
|
||||
if (!raw) {
|
||||
var normalizer = new Normalizer();
|
||||
rootNode = normalizer.normalize(rootNode, context);
|
||||
@ -143,6 +150,10 @@ class Parser {
|
||||
argument = argument.value;
|
||||
}
|
||||
|
||||
if (!el.tagNameExpression && !tagName) {
|
||||
tagName = el.tagName = "assign";
|
||||
}
|
||||
|
||||
if (tagName === "marko-compiler-options") {
|
||||
this.parentNode.setTrimStartEnd(true);
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ class Node {
|
||||
this.tagDef = null; // The tag definition associated with this Node
|
||||
this._codeGeneratorFuncs = null;
|
||||
this._flags = {};
|
||||
this._transformersApplied = {};
|
||||
this._transformersApplied = new Set();
|
||||
this._preserveWhitespace = null;
|
||||
this._events = null;
|
||||
this._childTextNormalized = undefined;
|
||||
@ -191,11 +191,11 @@ class Node {
|
||||
}
|
||||
|
||||
isTransformerApplied(transformer) {
|
||||
return this._transformersApplied[transformer.id] === true;
|
||||
return this._transformersApplied.has(transformer);
|
||||
}
|
||||
|
||||
setTransformerApplied(transformer) {
|
||||
this._transformersApplied[transformer.id] = true;
|
||||
this._transformersApplied.add(transformer);
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
||||
@ -273,6 +273,10 @@ var coreTaglibsRegistered = false;
|
||||
function registerCoreTaglibs() {
|
||||
if (!coreTaglibsRegistered) {
|
||||
coreTaglibsRegistered = true;
|
||||
registerTaglib(
|
||||
require("../taglibs/migrate/marko.json"),
|
||||
require.resolve("../taglibs/migrate/marko.json")
|
||||
);
|
||||
registerTaglib(
|
||||
require("../taglibs/core/marko.json"),
|
||||
require.resolve("../taglibs/core/marko.json")
|
||||
|
||||
@ -23,6 +23,8 @@ class Tag {
|
||||
this.dir = path.dirname(filePath);
|
||||
}
|
||||
|
||||
this.migrators = {};
|
||||
this.migratorPaths = [];
|
||||
this.attributes = {};
|
||||
this.transformers = {};
|
||||
this.patternAttributes = [];
|
||||
@ -198,6 +200,16 @@ class Tag {
|
||||
hasNestedTags() {
|
||||
return this.nestedTags != null;
|
||||
}
|
||||
|
||||
forEachMigrator(callback, thisObj) {
|
||||
this.migratorPaths
|
||||
.map(function(path) {
|
||||
return (this.migrators[path] =
|
||||
this.migrators[path] || markoModules.require(path));
|
||||
}, this)
|
||||
.forEach(callback, thisObj);
|
||||
}
|
||||
|
||||
getNodeFactory() {
|
||||
var nodeFactory = this._nodeFactory;
|
||||
if (nodeFactory !== undefined) {
|
||||
|
||||
@ -3,6 +3,7 @@ var forEachEntry = require("raptor-util/forEachEntry");
|
||||
var ok = require("assert").ok;
|
||||
var path = require("path");
|
||||
var loaders = require("./loaders");
|
||||
var markoModules = require("../modules");
|
||||
|
||||
function handleImport(taglib, importedTaglib) {
|
||||
var importsLookup = taglib.importsLookup || (taglib.importsLookup = {});
|
||||
@ -65,6 +66,14 @@ class Taglib {
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
getMigrator() {
|
||||
var path = this.migratorPath;
|
||||
|
||||
if (path) {
|
||||
return (this._migrator =
|
||||
this._migrator || markoModules.require(path));
|
||||
}
|
||||
}
|
||||
addTag(tag) {
|
||||
ok(arguments.length === 1, "Invalid args");
|
||||
if (!tag.name) {
|
||||
|
||||
@ -389,6 +389,16 @@ class TagLoader {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom tag can be mapped to module that is used
|
||||
* migrate deprecated features to modern features.
|
||||
*/
|
||||
migrator(value) {
|
||||
var tag = this.tag;
|
||||
var dirname = this.dirname;
|
||||
tag.migratorPaths.push(markoModules.resolveFrom(dirname, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom tag can be mapped to module that is is used
|
||||
* to generate compile-time code for the custom tag. A
|
||||
|
||||
@ -322,6 +322,18 @@ class TaglibLoader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taglib can be mapped to module that is used
|
||||
* migrate deprecated features to modern features across the entire template.
|
||||
*/
|
||||
migrator(value) {
|
||||
var taglib = this.taglib;
|
||||
var dirname = this.dirname;
|
||||
|
||||
var path = markoModules.resolveFrom(dirname, value);
|
||||
taglib.migratorPath = path;
|
||||
}
|
||||
|
||||
textTransformer(value) {
|
||||
// Marko allows a "text-transformer" to be registered. The provided
|
||||
// text transformer will be called for any static text found in a template.
|
||||
|
||||
@ -326,6 +326,59 @@ class TaglibLookup {
|
||||
return attrDef;
|
||||
}
|
||||
|
||||
forEachTemplateMigrator(callback, thisObj) {
|
||||
for (var key in this.taglibsById) {
|
||||
var migration = this.taglibsById[key].getMigrator();
|
||||
if (migration) {
|
||||
callback.call(thisObj, migration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEachTagMigrator(element, callback, thisObj) {
|
||||
if (typeof element === "string") {
|
||||
element = {
|
||||
tagName: element
|
||||
};
|
||||
}
|
||||
|
||||
var tagName = element.tagName;
|
||||
/*
|
||||
* If the node is an element node then we need to find all matching
|
||||
* migrators based on the URI and the local name of the element.
|
||||
*/
|
||||
|
||||
var migrators = [];
|
||||
|
||||
function addMigrator(migrator) {
|
||||
if (typeof migrator !== "function") {
|
||||
throw new Error("Invalid transformer");
|
||||
}
|
||||
|
||||
migrators.push(migrator);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle all of the migrators for all possible matching migrators.
|
||||
*
|
||||
* Start with the least specific and end with the most specific.
|
||||
*/
|
||||
|
||||
if (this.merged.tags) {
|
||||
if (tagName) {
|
||||
if (this.merged.tags[tagName]) {
|
||||
this.merged.tags[tagName].forEachMigrator(addMigrator);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.merged.tags["*"]) {
|
||||
this.merged.tags["*"].forEachMigrator(addMigrator);
|
||||
}
|
||||
}
|
||||
|
||||
migrators.forEach(callback, thisObj);
|
||||
}
|
||||
|
||||
forEachTemplateTransformer(callback, thisObj) {
|
||||
var transformers = this.merged.transformers;
|
||||
if (transformers && transformers.length) {
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
module.exports = function codeGenerator(elNode, context) {
|
||||
const attributes = elNode.attributes;
|
||||
const builder = context.builder;
|
||||
|
||||
context.deprecate(
|
||||
'The "<assign>" tag is deprecated. Please use "$ <js_code>" for JavaScript in the template. See: https://github.com/marko-js/marko/wiki/Deprecation:-var-assign-invoke-tags'
|
||||
);
|
||||
|
||||
if (!attributes) {
|
||||
context.addError(
|
||||
"Invalid <assign> tag. Argument is missing. Example; <assign x=123 />"
|
||||
);
|
||||
return elNode;
|
||||
}
|
||||
|
||||
elNode.replaceWith(
|
||||
builder.scriptlet({
|
||||
value: builder.parseExpression(
|
||||
elNode.attributes
|
||||
.map(
|
||||
attr =>
|
||||
attr.value == null
|
||||
? attr.name
|
||||
: `${attr.name} = ${attr.rawValue}`
|
||||
)
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
);
|
||||
};
|
||||
@ -1,125 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
var createLoopNode = require("./util/createLoopNode");
|
||||
|
||||
var coreAttrHandlers = [
|
||||
[
|
||||
"while",
|
||||
function(attr, node) {
|
||||
var whileArgument = attr.argument;
|
||||
if (!whileArgument) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var whileNode = this.builder.whileStatement(whileArgument);
|
||||
node.wrapWith(whileNode);
|
||||
}
|
||||
],
|
||||
[
|
||||
"for",
|
||||
function(attr, node) {
|
||||
var forArgument = attr.argument;
|
||||
if (!forArgument) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var loopNode;
|
||||
|
||||
try {
|
||||
loopNode = createLoopNode(forArgument, null, this.builder);
|
||||
} catch (e) {
|
||||
if (e.code === "INVALID_FOR") {
|
||||
this.addError(e.message);
|
||||
return;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
//Surround the existing node with the newly created loop node
|
||||
// NOTE: The loop node will be one of the following:
|
||||
// ForEach, ForRange, ForEachProp or ForStatement
|
||||
node.wrapWith(loopNode);
|
||||
}
|
||||
],
|
||||
[
|
||||
"if",
|
||||
function(attr, node) {
|
||||
var ifArgument = attr.argument;
|
||||
if (!ifArgument) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var test;
|
||||
try {
|
||||
test = this.builder.parseExpression(ifArgument);
|
||||
} catch (e) {
|
||||
test = this.builder.literalFalse();
|
||||
this.addError(
|
||||
"Invalid expression for if statement:\n" + e.message
|
||||
);
|
||||
}
|
||||
|
||||
var ifNode = this.builder.ifStatement(test);
|
||||
//Surround the existing node with an "If" node
|
||||
node.wrapWith(ifNode);
|
||||
}
|
||||
],
|
||||
[
|
||||
"unless",
|
||||
function(attr, node) {
|
||||
var ifArgument = attr.argument;
|
||||
if (!ifArgument) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var test;
|
||||
try {
|
||||
test = this.builder.parseExpression(ifArgument);
|
||||
} catch (e) {
|
||||
test = this.builder.literalFalse();
|
||||
this.addError(
|
||||
"Invalid expression for unless statement:\n" + e.message
|
||||
);
|
||||
}
|
||||
|
||||
test = this.builder.negate(test);
|
||||
var ifNode = this.builder.ifStatement(test);
|
||||
//Surround the existing node with an "if" node
|
||||
node.wrapWith(ifNode);
|
||||
}
|
||||
],
|
||||
[
|
||||
"else-if",
|
||||
function(attr, node) {
|
||||
var elseIfArgument = attr.argument;
|
||||
if (!elseIfArgument) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var test;
|
||||
try {
|
||||
test = this.builder.parseExpression(elseIfArgument);
|
||||
} catch (e) {
|
||||
test = this.builder.literalFalse();
|
||||
this.addError(
|
||||
"Invalid expression for else-if statement:\n" + e.message
|
||||
);
|
||||
}
|
||||
|
||||
var elseIfNode = this.builder.elseIfStatement(test);
|
||||
//Surround the existing node with an "ElseIf" node
|
||||
node.wrapWith(elseIfNode);
|
||||
}
|
||||
],
|
||||
[
|
||||
"else",
|
||||
function(attr, node) {
|
||||
var elseNode = this.builder.elseStatement();
|
||||
//Surround the existing node with an "Else" node
|
||||
node.wrapWith(elseNode);
|
||||
}
|
||||
],
|
||||
[
|
||||
"body-only-if",
|
||||
function(attr, node, el) {
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
{
|
||||
"transformer": "./root-transformer",
|
||||
"<assign>": {
|
||||
"transformer": "./assign-tag",
|
||||
"open-tag-only": true,
|
||||
"deprecated": true
|
||||
},
|
||||
"<class>": {
|
||||
"code-generator": "./class-tag",
|
||||
"open-tag-only": true
|
||||
@ -139,10 +133,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"<invoke>": {
|
||||
"transformer": "./invoke-tag",
|
||||
"deprecated": true
|
||||
},
|
||||
"<macro>": {
|
||||
"node-factory": "./macro-tag",
|
||||
"autocomplete": [
|
||||
@ -213,10 +203,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"<var>": {
|
||||
"transformer": "./var-tag",
|
||||
"deprecated": true
|
||||
},
|
||||
"<while>": {
|
||||
"code-generator": "./while-tag",
|
||||
"autocomplete": [
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
"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;
|
||||
}
|
||||
33
src/taglibs/migrate/assign-tag.js
Normal file
33
src/taglibs/migrate/assign-tag.js
Normal file
@ -0,0 +1,33 @@
|
||||
const printJS = require("./util/printJS");
|
||||
const migrateControlFlowDirectives = require("./control-flow-directives");
|
||||
|
||||
module.exports = function migrator(elNode, context) {
|
||||
const attributes = elNode.attributes;
|
||||
const builder = context.builder;
|
||||
migrateControlFlowDirectives(elNode, context);
|
||||
elNode.setTransformerApplied(migrateControlFlowDirectives);
|
||||
|
||||
context.deprecate(
|
||||
'The "<assign>" tag is deprecated. Please use "$ <js_code>" for JavaScript in the template. See: https://github.com/marko-js/marko/wiki/Deprecation:-var-assign-invoke-tags'
|
||||
);
|
||||
|
||||
if (!attributes) {
|
||||
context.addError(
|
||||
"Invalid <assign> tag. Argument is missing. Example; <assign x=123 />"
|
||||
);
|
||||
return elNode;
|
||||
}
|
||||
|
||||
elNode.attributes.forEach(attr => {
|
||||
elNode.insertSiblingBefore(
|
||||
builder.scriptlet({
|
||||
value:
|
||||
attr.value == null
|
||||
? attr.name
|
||||
: `${attr.name} = ${printJS(attr.value, context)}`
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
elNode.detach();
|
||||
};
|
||||
39
src/taglibs/migrate/control-flow-directives.js
Normal file
39
src/taglibs/migrate/control-flow-directives.js
Normal file
@ -0,0 +1,39 @@
|
||||
const CONTROL_FLOW_ATTRIBUTES = [
|
||||
"while",
|
||||
"for",
|
||||
"if",
|
||||
"unless",
|
||||
"else-if",
|
||||
"else"
|
||||
];
|
||||
|
||||
module.exports = function migrate(el, context) {
|
||||
const builder = context.builder;
|
||||
|
||||
if (CONTROL_FLOW_ATTRIBUTES.includes(el.tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.forEachAttribute(attr => {
|
||||
const name = attr.name;
|
||||
if (
|
||||
CONTROL_FLOW_ATTRIBUTES.includes(name) &&
|
||||
(name === "else" || attr.argument)
|
||||
) {
|
||||
context.deprecate(
|
||||
`The "${name}" attribute is deprecated. Please use the <${name}> tag instead. See: https://github.com/marko-js/marko/wiki/Deprecation:-control-flow-directive`
|
||||
);
|
||||
el.removeAttribute(name);
|
||||
el.wrapWith(
|
||||
builder.htmlElement(
|
||||
name,
|
||||
undefined,
|
||||
[],
|
||||
attr.argument,
|
||||
false,
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -1,9 +1,12 @@
|
||||
const renderCallToDynamicTag = require("./util/renderCallToDynamicTag");
|
||||
const migrateControlFlowDirectives = require("./control-flow-directives");
|
||||
|
||||
module.exports = function codeGenerator(elNode, context) {
|
||||
module.exports = function migrator(elNode, context) {
|
||||
const builder = context.builder;
|
||||
const functionAttr = elNode.attributes[0];
|
||||
const functionArgs = functionAttr.argument;
|
||||
migrateControlFlowDirectives(elNode, context);
|
||||
elNode.setTransformerApplied(migrateControlFlowDirectives);
|
||||
|
||||
context.deprecate(
|
||||
'The "<invoke>" tag is deprecated. Please use "$ <js_code>" for JavaScript in the template. See: https://github.com/marko-js/marko/wiki/Deprecation:-var-assign-invoke-tags'
|
||||
19
src/taglibs/migrate/marko.json
Normal file
19
src/taglibs/migrate/marko.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"migrator": "./root-migrator",
|
||||
"<*>": {
|
||||
"migrator": "./control-flow-directives"
|
||||
},
|
||||
"<invoke>": {
|
||||
"migrator": "./invoke-tag",
|
||||
"deprecated": true
|
||||
},
|
||||
"<assign>": {
|
||||
"migrator": "./assign-tag",
|
||||
"open-tag-only": true,
|
||||
"deprecated": true
|
||||
},
|
||||
"<var>": {
|
||||
"migrator": "./var-tag",
|
||||
"deprecated": true
|
||||
}
|
||||
}
|
||||
162
src/taglibs/migrate/root-migrator.js
Normal file
162
src/taglibs/migrate/root-migrator.js
Normal file
@ -0,0 +1,162 @@
|
||||
"use strict";
|
||||
|
||||
const printJS = require("./util/printJS");
|
||||
const OUT_IDENTIFIER_REG = /[(,] *out *[,)]/;
|
||||
const renderCallToDynamicTag = require("./util/renderCallToDynamicTag");
|
||||
|
||||
module.exports = function migrator(el, context) {
|
||||
const walker = context.createWalker({
|
||||
enter(node) {
|
||||
if (
|
||||
node.type !== "Scriptlet" ||
|
||||
!OUT_IDENTIFIER_REG.test(node.code)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasErrors;
|
||||
let foundRenderCall;
|
||||
const replacement = replaceScriptlets(
|
||||
context.builder.parseStatement(node.code),
|
||||
context
|
||||
);
|
||||
|
||||
if (!foundRenderCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.deprecate(
|
||||
"Directly rendering by passing `out` to a function is deprecated. Please use the dynamic tag instead. See: https://github.com/marko-js/marko/wiki/Deprecation:-imperative-render-calls"
|
||||
);
|
||||
|
||||
if (hasErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!replacement.type) {
|
||||
replacement.forEachChild(child =>
|
||||
node.insertSiblingBefore(child)
|
||||
);
|
||||
node.detach();
|
||||
} else {
|
||||
node.replaceWith(replacement);
|
||||
}
|
||||
|
||||
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.htmlElement(
|
||||
"if",
|
||||
undefined,
|
||||
[replaceScriptlets(node.right, context)],
|
||||
printJS(
|
||||
node.operator === "&&"
|
||||
? node.left
|
||||
: builder.negate(node.left),
|
||||
context
|
||||
),
|
||||
false,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "FunctionCall":
|
||||
if (
|
||||
node !==
|
||||
(node =
|
||||
renderCallToDynamicTag(node, context) || node)
|
||||
) {
|
||||
foundRenderCall = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case "If":
|
||||
node = builder.htmlElement(
|
||||
"if",
|
||||
undefined,
|
||||
replaceScriptlets(node.body, context),
|
||||
printJS(node.test, context),
|
||||
false,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "ElseIf":
|
||||
node = builder.htmlElement(
|
||||
"else-if",
|
||||
undefined,
|
||||
replaceScriptlets(node.body, context),
|
||||
printJS(node.test, context),
|
||||
false,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "Else":
|
||||
node = builder.htmlElement(
|
||||
"else",
|
||||
undefined,
|
||||
replaceScriptlets(node.body, context),
|
||||
undefined,
|
||||
false,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "ForStatement":
|
||||
node = builder.htmlElement(
|
||||
"for",
|
||||
undefined,
|
||||
replaceScriptlets(node.body, context),
|
||||
`${printJS(node.init, context)}; ${printJS(
|
||||
node.test,
|
||||
context
|
||||
)}; ${printJS(node.update, context)}`,
|
||||
false,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "WhileStatement":
|
||||
node = builder.htmlElement(
|
||||
"while",
|
||||
undefined,
|
||||
replaceScriptlets(node.body, context),
|
||||
printJS(node.test, context),
|
||||
false,
|
||||
false
|
||||
);
|
||||
break;
|
||||
default:
|
||||
hasErrors = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
});
|
||||
walker.walk(el);
|
||||
};
|
||||
10
src/taglibs/migrate/util/printJS.js
Normal file
10
src/taglibs/migrate/util/printJS.js
Normal file
@ -0,0 +1,10 @@
|
||||
const CodeWriter = require("../../../compiler/CodeWriter");
|
||||
|
||||
module.exports = function(node, context, options) {
|
||||
const writer = new CodeWriter(
|
||||
Object.assign({}, context.options, options),
|
||||
context.builder
|
||||
);
|
||||
writer.write(node);
|
||||
return writer.getCode();
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
const printJS = require("./printJS");
|
||||
|
||||
module.exports = function renderCallToDynamicTag(ast, context) {
|
||||
const builder = context.builder;
|
||||
const args = ast.args;
|
||||
@ -58,7 +60,17 @@ module.exports = function renderCallToDynamicTag(ast, context) {
|
||||
);
|
||||
}
|
||||
|
||||
return context.createNodeForEl(tagName, tagAttrs, null, true, true);
|
||||
const el = builder.htmlElement(
|
||||
undefined,
|
||||
tagAttrs,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
el.rawTagNameExpression = printJS(tagName, context);
|
||||
return el;
|
||||
};
|
||||
|
||||
function toAttributesOrSpread(val) {
|
||||
@ -1,4 +1,5 @@
|
||||
const isValidJavaScriptVarName = require("../../compiler/util/isValidJavaScriptVarName");
|
||||
const printJS = require("./util/printJS");
|
||||
|
||||
module.exports = function nodeFactory(elNode, context) {
|
||||
const attributes = elNode.attributes;
|
||||
@ -34,34 +35,31 @@ module.exports = function nodeFactory(elNode, context) {
|
||||
lastChild.argument.value = lastChild.argument.value.trimRight();
|
||||
}
|
||||
|
||||
const vars = elNode.attributes.map(attr => {
|
||||
const scriptlets = elNode.attributes.map(attr => {
|
||||
const name = attr.name;
|
||||
const val = attr.rawValue;
|
||||
|
||||
if (!isValidJavaScriptVarName(attr.name)) {
|
||||
if (!isValidJavaScriptVarName(name)) {
|
||||
hasError = true;
|
||||
context.addError(
|
||||
"Invalid JavaScript variable name: " + attr.name,
|
||||
"Invalid JavaScript variable name: " + name,
|
||||
"INVALID_VAR_NAME"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedExpression = val;
|
||||
if (val != null) {
|
||||
parsedExpression = builder.parseExpression(val);
|
||||
}
|
||||
|
||||
return builder.variableDeclarator(name, parsedExpression);
|
||||
return builder.scriptlet({
|
||||
value: `var ${
|
||||
val == null ? name : `${name} = ${printJS(attr.value, context)}`
|
||||
}`
|
||||
});
|
||||
});
|
||||
|
||||
if (hasError) {
|
||||
return;
|
||||
}
|
||||
|
||||
elNode.insertSiblingBefore(
|
||||
builder.scriptlet({ value: builder.vars(vars) })
|
||||
);
|
||||
scriptlets.forEach(scriptlet => elNode.insertSiblingBefore(scriptlet));
|
||||
elNode.forEachChild(node => elNode.insertSiblingBefore(node));
|
||||
elNode.detach();
|
||||
};
|
||||
@ -99,4 +99,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
"name",
|
||||
"age",
|
||||
"foo-on-*",
|
||||
"*",
|
||||
"body-only-if",
|
||||
"if",
|
||||
"else-if",
|
||||
@ -32,4 +33,4 @@
|
||||
"on*",
|
||||
"once*",
|
||||
"w-on*"
|
||||
]
|
||||
]
|
||||
@ -3,7 +3,9 @@
|
||||
"foo",
|
||||
"bar",
|
||||
"init-widgets",
|
||||
"invoke",
|
||||
"assign",
|
||||
"var",
|
||||
"class",
|
||||
"else",
|
||||
"else-if",
|
||||
@ -13,7 +15,6 @@
|
||||
"include",
|
||||
"include-html",
|
||||
"include-text",
|
||||
"invoke",
|
||||
"macro",
|
||||
"macro-body",
|
||||
"marko",
|
||||
@ -21,7 +22,6 @@
|
||||
"module-code",
|
||||
"static",
|
||||
"unless",
|
||||
"var",
|
||||
"while",
|
||||
"layout-use",
|
||||
"layout-put",
|
||||
@ -239,4 +239,4 @@
|
||||
"_preserve",
|
||||
"no-update",
|
||||
"widget-types"
|
||||
]
|
||||
]
|
||||
@ -13,9 +13,9 @@ function render(input, out, __component, component, state) {
|
||||
var data = input;
|
||||
|
||||
var attrs = {
|
||||
foo: "bar",
|
||||
hello: "world"
|
||||
}
|
||||
foo: "bar",
|
||||
hello: "world"
|
||||
}
|
||||
|
||||
out.e("DIV", attrs, null, null, 3)
|
||||
.t("Hello ")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user