mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
feat(translator-fluurt): extract some translate logic to analyze stage (#63)
This commit is contained in:
parent
aeceabc97d
commit
4b71f45928
27
package-lock.json
generated
27
package-lock.json
generated
@ -2980,9 +2980,9 @@
|
||||
}
|
||||
},
|
||||
"@marko/compiler": {
|
||||
"version": "5.0.0-next.69",
|
||||
"resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.0.0-next.69.tgz",
|
||||
"integrity": "sha512-vhAtqWHpz3GDgxP4ug3tIhbpGri17mbxOL/0v+FFFL5P4KR1AT/h3EbVsgLKILEmDmXA2V3q1kxRsQ5UrgPdKg==",
|
||||
"version": "5.0.0-next.70",
|
||||
"resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.0.0-next.70.tgz",
|
||||
"integrity": "sha512-ATeT/fWT+QBArIaQrE+4uVxi7JoYMvhuFmdMODeLxlp3ABXoYzj8wCNA6DGhoJynmAgTWohUR/T59l1sklCTRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
@ -2995,7 +2995,7 @@
|
||||
"@babel/types": "^7.7.4",
|
||||
"@marko/babel-types": "^5.0.0-next.67",
|
||||
"@marko/babel-utils": "^5.0.0-next.68",
|
||||
"@marko/translator-default": "^5.0.0-next.69",
|
||||
"@marko/translator-default": "^5.0.0-next.70",
|
||||
"complain": "^1.6.0",
|
||||
"enhanced-resolve": "5.0.0",
|
||||
"he": "^1.1.0",
|
||||
@ -3007,12 +3007,31 @@
|
||||
"strip-ansi": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@marko/translator-default": {
|
||||
"version": "5.0.0-next.70",
|
||||
"resolved": "https://registry.npmjs.org/@marko/translator-default/-/translator-default-5.0.0-next.70.tgz",
|
||||
"integrity": "sha512-biu+5sE8f1+xO1m6x1dUU8mmiqHu+N2tcaL2/sKNzSI0V9+naYoUPbvD7XZIcQ5Brit4oRnq6m6v3yqDMqkwBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@marko/babel-types": "^5.0.0-next.67",
|
||||
"@marko/babel-utils": "^5.0.0-next.68",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"self-closing-tags": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@commitlint/config-lerna-scopes": "^11.0.0",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||
"@marko/compiler": "^5.0.0-next.69",
|
||||
"@marko/compiler": "^5.0.0-next.70",
|
||||
"@rollup/plugin-replace": "2.3.4",
|
||||
"@types/jsdom": "^16.2.5",
|
||||
"@types/mocha": "^8.0.4",
|
||||
|
||||
24
packages/translator/src/analyze/index.ts
Normal file
24
packages/translator/src/analyze/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Visitor } from "@marko/babel-types";
|
||||
import analyzeTagNameType, { TagNameTypes } from "./tag-name-type";
|
||||
import analyzeNestedAttributeTags from "./nested-attribute-tags";
|
||||
|
||||
declare module "@marko/babel-types" {
|
||||
// This is extended by individual helpers.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface MarkoTagExtra {}
|
||||
|
||||
export interface MarkoTag {
|
||||
extra: MarkoTagExtra & Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
MarkoTag(tag) {
|
||||
const extra = (tag.node.extra ??= {} as typeof tag.node.extra);
|
||||
analyzeTagNameType(tag);
|
||||
|
||||
if (extra.tagNameType !== TagNameTypes.NativeTag) {
|
||||
analyzeNestedAttributeTags(tag);
|
||||
}
|
||||
}
|
||||
} as Visitor;
|
||||
88
packages/translator/src/analyze/nested-attribute-tags.ts
Normal file
88
packages/translator/src/analyze/nested-attribute-tags.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import {
|
||||
isAttributeTag,
|
||||
isTransparentTag,
|
||||
isLoopTag
|
||||
} from "@marko/babel-utils";
|
||||
|
||||
type Lookup = Record<
|
||||
string,
|
||||
{
|
||||
identifier?: t.Identifier;
|
||||
dynamic: boolean;
|
||||
repeated: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
declare module "@marko/babel-types" {
|
||||
export interface MarkoTagExtra {
|
||||
hoistedControlFlows: number;
|
||||
nestedAttributeTags: Lookup;
|
||||
}
|
||||
}
|
||||
|
||||
export default function analyzeAttributeTags(tag: NodePath<t.MarkoTag>) {
|
||||
const { extra } = tag.node;
|
||||
extra.nestedAttributeTags = {};
|
||||
extra.hoistedControlFlows = 0;
|
||||
analyzeChildren(extra, false, false, tag);
|
||||
}
|
||||
|
||||
function analyzeChildren(
|
||||
rootExtra: t.MarkoTag["extra"],
|
||||
repeated: boolean,
|
||||
dynamic: boolean,
|
||||
tag: NodePath<t.MarkoTag>
|
||||
) {
|
||||
let hasAttributeTags = false;
|
||||
for (const child of tag.get("body").get("body")) {
|
||||
if (child.isMarkoTag()) {
|
||||
if (
|
||||
analyzeChild(
|
||||
rootExtra,
|
||||
repeated,
|
||||
dynamic,
|
||||
child as NodePath<t.MarkoTag>
|
||||
)
|
||||
) {
|
||||
hasAttributeTags = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasAttributeTags;
|
||||
}
|
||||
|
||||
function analyzeChild(
|
||||
rootExtra: t.MarkoTag["extra"],
|
||||
repeated: boolean,
|
||||
dynamic: boolean,
|
||||
tag: NodePath<t.MarkoTag>
|
||||
) {
|
||||
if (isTransparentTag(tag)) {
|
||||
if (analyzeChildren(rootExtra, repeated || isLoopTag(tag), true, tag)) {
|
||||
if (
|
||||
!isTransparentTag(tag.parentPath.parentPath as NodePath<t.MarkoTag>)
|
||||
) {
|
||||
rootExtra.hoistedControlFlows++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (isAttributeTag(tag)) {
|
||||
const attrName = (tag.node.name as t.StringLiteral).value.slice(1);
|
||||
const lookup = rootExtra.nestedAttributeTags;
|
||||
const existing = lookup[attrName];
|
||||
const info =
|
||||
existing ||
|
||||
(lookup[attrName] = {
|
||||
dynamic: false,
|
||||
repeated: false
|
||||
});
|
||||
|
||||
info.dynamic ||= dynamic;
|
||||
info.repeated ||= repeated || existing !== undefined;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
193
packages/translator/src/analyze/tag-name-type.ts
Normal file
193
packages/translator/src/analyze/tag-name-type.ts
Normal file
@ -0,0 +1,193 @@
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import { isNativeTag } from "@marko/babel-utils";
|
||||
|
||||
declare module "@marko/babel-types" {
|
||||
export interface MarkoTagExtra {
|
||||
tagNameType: TagNameTypes;
|
||||
tagNameNullable: boolean;
|
||||
tagNameDynamic: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export const enum TagNameTypes {
|
||||
NativeTag,
|
||||
CustomTag,
|
||||
DynamicTag,
|
||||
AttributeTag
|
||||
}
|
||||
|
||||
const MARKO_FILE_REG = /^<.*>$|\.marko$/;
|
||||
|
||||
export default function analyzeTagNameType(tag: NodePath<t.MarkoTag>) {
|
||||
const name = tag.get("name");
|
||||
const { extra } = tag.node;
|
||||
|
||||
if (name.isStringLiteral()) {
|
||||
extra.tagNameType =
|
||||
name.node.value[0] === "@"
|
||||
? TagNameTypes.AttributeTag
|
||||
: isNativeTag(tag)
|
||||
? TagNameTypes.NativeTag
|
||||
: TagNameTypes.CustomTag;
|
||||
|
||||
extra.tagNameNullable = extra.tagNameNullable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const pending = [name] as NodePath<any>[];
|
||||
let path: typeof pending[0] | undefined;
|
||||
let type: TagNameTypes | undefined = undefined;
|
||||
let nullable = false;
|
||||
|
||||
while ((path = pending.pop()) && type !== TagNameTypes.DynamicTag) {
|
||||
switch (path.type) {
|
||||
case "ConditionalExpression": {
|
||||
const curPath = path as NodePath<t.ConditionalExpression>;
|
||||
pending.push(curPath.get("consequent"));
|
||||
|
||||
if (curPath.node.alternate) {
|
||||
pending.push(curPath.get("alternate"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "LogicalExpression": {
|
||||
const curPath = path as NodePath<t.LogicalExpression>;
|
||||
if (curPath.node.operator === "||") {
|
||||
pending.push(curPath.get("left"));
|
||||
} else {
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
pending.push(curPath.get("right"));
|
||||
break;
|
||||
}
|
||||
|
||||
case "AssignmentExpression":
|
||||
pending.push((path as NodePath<t.AssignmentExpression>).get("right"));
|
||||
break;
|
||||
|
||||
case "BinaryExpression":
|
||||
type =
|
||||
(path as NodePath<t.BinaryExpression>).node.operator !== "+" ||
|
||||
(type !== undefined && type !== TagNameTypes.NativeTag)
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.NativeTag;
|
||||
|
||||
break;
|
||||
|
||||
case "StringLiteral":
|
||||
case "TemplateLiteral":
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.NativeTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.NativeTag;
|
||||
break;
|
||||
|
||||
case "NullLiteral":
|
||||
nullable = true;
|
||||
break;
|
||||
|
||||
case "Identifier": {
|
||||
const curPath = path as NodePath<t.Identifier>;
|
||||
if (curPath.node.name === "undefined") {
|
||||
nullable = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const binding = curPath.scope.getBinding(curPath.node.name);
|
||||
|
||||
if (!binding) {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
break;
|
||||
}
|
||||
|
||||
if (binding.kind === "module") {
|
||||
const decl = binding.path.parent as t.ImportDeclaration;
|
||||
if (
|
||||
MARKO_FILE_REG.test(decl.source.value) &&
|
||||
decl.specifiers.some(it => t.isImportDefaultSpecifier(it))
|
||||
) {
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.CustomTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.CustomTag;
|
||||
} else {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const bindingPath = binding.path;
|
||||
const bindingTag = bindingPath.parentPath as NodePath<t.MarkoTag>;
|
||||
|
||||
if (bindingTag.isMarkoTag() && bindingPath.key === "var") {
|
||||
const bindingTagName = (bindingTag.get("name")
|
||||
.node as t.StringLiteral).value;
|
||||
if (bindingTagName === "tag") {
|
||||
// treat <tag/name> as a custom tag.
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.CustomTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.CustomTag;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bindingTagName === "const") {
|
||||
pending.push(
|
||||
(bindingTag.get(
|
||||
"attributes"
|
||||
)[0] as NodePath<t.MarkoAttribute>).get("value")
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bindingTagName === "let") {
|
||||
const defaultAttr = bindingTag.get("attributes")[0];
|
||||
|
||||
if (defaultAttr.node) {
|
||||
pending.push(
|
||||
(defaultAttr as NodePath<t.MarkoAttribute>).get("value")
|
||||
);
|
||||
} else {
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
const assignments = binding.constantViolations;
|
||||
for (let i = assignments.length; i--; ) {
|
||||
const assignment = assignments[
|
||||
i
|
||||
] as NodePath<t.AssignmentExpression>;
|
||||
const { operator } = assignment.node;
|
||||
if (operator === "=") {
|
||||
pending.push(assignment.get("right"));
|
||||
} else if (operator === "+=") {
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.NativeTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.NativeTag;
|
||||
} else {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
type = TagNameTypes.DynamicTag;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
type = TagNameTypes.DynamicTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extra.tagNameType = type!;
|
||||
extra.tagNameNullable = nullable;
|
||||
extra.tagNameDynamic = true;
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import { markIdentifierAsComponent } from "../util/analyze-tag-name";
|
||||
import { flushBefore, flushInto } from "../util/html-flush";
|
||||
import toFirstExpressionOrBlock from "../util/to-first-expression-or-block";
|
||||
|
||||
@ -28,7 +27,7 @@ export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
tag.replaceWith(
|
||||
t.variableDeclaration("const", [
|
||||
t.variableDeclarator(
|
||||
markIdentifierAsComponent(tag.node.var! as t.Identifier),
|
||||
tag.node.var!,
|
||||
t.arrowFunctionExpression(
|
||||
tag.node.params || [],
|
||||
toFirstExpressionOrBlock(tag.node.body)
|
||||
|
||||
@ -13,7 +13,9 @@ export const taglibs = [
|
||||
[require.resolve("./core/marko.json"), require("./core/marko.json")]
|
||||
];
|
||||
|
||||
export const visitor: Visitor = {
|
||||
export { default as analyze } from "./analyze";
|
||||
|
||||
export const translate: Visitor = {
|
||||
Program,
|
||||
ImportDeclaration,
|
||||
MarkoDocumentType,
|
||||
|
||||
@ -1,37 +1,21 @@
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import {
|
||||
findParentTag,
|
||||
isTransparentTag,
|
||||
isLoopTag,
|
||||
isAttributeTag,
|
||||
assertNoVar
|
||||
} from "@marko/babel-utils";
|
||||
import analyzeTagName, { TagNameTypes } from "../util/analyze-tag-name";
|
||||
import { findParentTag, assertNoVar } from "@marko/babel-utils";
|
||||
import { TagNameTypes } from "../analyze/tag-name-type";
|
||||
import attrsToObject from "../util/attrs-to-object";
|
||||
import { flushInto, hasPendingHTML } from "../util/html-flush";
|
||||
|
||||
const HOISTED_NODES = new WeakSet<t.Node>();
|
||||
const HOISTED_CHILDREN = new WeakSet<NodePath<t.MarkoTag>>();
|
||||
const PARENT_TAGS = new WeakMap<NodePath<t.MarkoTag>, NodePath<t.MarkoTag>>();
|
||||
const LOOKUPS = new WeakMap<NodePath<t.MarkoTag>, Lookup>();
|
||||
type Lookup = Record<
|
||||
string,
|
||||
{
|
||||
identifier?: t.Identifier;
|
||||
dynamic: boolean;
|
||||
repeated: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export function hasHoistedChildren(tag: NodePath<t.MarkoTag>) {
|
||||
return HOISTED_CHILDREN.has(tag);
|
||||
}
|
||||
|
||||
export function isHoistedNode(node: t.Node) {
|
||||
return HOISTED_NODES.has(node);
|
||||
}
|
||||
|
||||
export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
if (hasPendingHTML(tag)) {
|
||||
throw tag
|
||||
.get("name")
|
||||
.buildCodeFrameError("Dynamic @tags cannot be mixed with body content.");
|
||||
}
|
||||
}
|
||||
|
||||
export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
assertNoVar(tag);
|
||||
flushInto(tag);
|
||||
|
||||
const parentTag = findParentTag(tag);
|
||||
|
||||
if (!parentTag) {
|
||||
@ -40,84 +24,63 @@ export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
.buildCodeFrameError("@tags must be nested within another tag.");
|
||||
}
|
||||
|
||||
if (analyzeTagName(parentTag).type === TagNameTypes.NativeTag) {
|
||||
const parentExtra = parentTag.node.extra;
|
||||
|
||||
if (parentExtra.tagNameType === TagNameTypes.NativeTag) {
|
||||
throw tag
|
||||
.get("name")
|
||||
.buildCodeFrameError("@tags cannot be nested under native tags.");
|
||||
}
|
||||
|
||||
if (hasPendingHTML(tag)) {
|
||||
throw tag
|
||||
.get("name")
|
||||
.buildCodeFrameError("Dynamic @tags cannot be mixed with body content.");
|
||||
}
|
||||
|
||||
PARENT_TAGS.set(tag, parentTag);
|
||||
|
||||
if (!LOOKUPS.has(parentTag)) {
|
||||
const lookup = analyzeRoot(parentTag);
|
||||
LOOKUPS.set(parentTag, lookup);
|
||||
(parentTag.node as any).exampleAttributeTag = tag.node; // Used by @marko/babel-utils assertNoAttributeTags.
|
||||
|
||||
for (const attrName in lookup) {
|
||||
const info = lookup[attrName];
|
||||
if (info.dynamic) {
|
||||
info.identifier = parentTag.scope.generateUidIdentifier(attrName);
|
||||
parentTag.insertBefore(
|
||||
info.repeated
|
||||
? t.variableDeclaration("const", [
|
||||
t.variableDeclarator(info.identifier, t.arrayExpression([]))
|
||||
])
|
||||
: t.variableDeclaration("let", [
|
||||
t.variableDeclarator(info.identifier)
|
||||
])
|
||||
);
|
||||
|
||||
parentTag.pushContainer(
|
||||
"attributes",
|
||||
t.markoAttribute(attrName, info.identifier)
|
||||
);
|
||||
|
||||
HOISTED_CHILDREN.add(parentTag);
|
||||
} else if (info.repeated) {
|
||||
parentTag.pushContainer(
|
||||
"attributes",
|
||||
t.markoAttribute(attrName, t.arrayExpression([]))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
assertNoVar(tag);
|
||||
flushInto(tag);
|
||||
|
||||
const attrName = (tag.node.name as t.StringLiteral).value.slice(1);
|
||||
const parentTag = PARENT_TAGS.get(tag)!;
|
||||
const info = LOOKUPS.get(parentTag)![attrName];
|
||||
const info = parentExtra.nestedAttributeTags[attrName];
|
||||
const attrsObject = attrsToObject(tag, true) || t.objectExpression([]);
|
||||
|
||||
if (info.identifier) {
|
||||
const replacement = t.expressionStatement(
|
||||
info.repeated
|
||||
? t.callExpression(
|
||||
t.memberExpression(info.identifier, t.identifier("push")),
|
||||
[attrsObject]
|
||||
)
|
||||
: t.assignmentExpression("=", info.identifier, attrsObject)
|
||||
);
|
||||
if (info.dynamic) {
|
||||
if (!info.identifier) {
|
||||
info.identifier = parentTag.scope.generateUidIdentifier(attrName);
|
||||
parentTag.insertBefore(
|
||||
info.repeated
|
||||
? t.variableDeclaration("const", [
|
||||
t.variableDeclarator(info.identifier, t.arrayExpression([]))
|
||||
])
|
||||
: t.variableDeclaration("let", [
|
||||
t.variableDeclarator(info.identifier)
|
||||
])
|
||||
);
|
||||
|
||||
HOISTED_NODES.add(replacement);
|
||||
tag.replaceWith(replacement);
|
||||
} else if (info.repeated) {
|
||||
(parentTag
|
||||
.get("attributes")
|
||||
.find(attr => (attr.node as t.MarkoAttribute).name === attrName)!
|
||||
.get("value") as NodePath<t.ArrayExpression>).pushContainer(
|
||||
"elements",
|
||||
attrsObject
|
||||
parentTag.pushContainer(
|
||||
"attributes",
|
||||
t.markoAttribute(attrName, info.identifier)
|
||||
);
|
||||
}
|
||||
|
||||
tag.replaceWith(
|
||||
t.expressionStatement(
|
||||
info.repeated
|
||||
? t.callExpression(
|
||||
t.memberExpression(info.identifier, t.identifier("push")),
|
||||
[attrsObject]
|
||||
)
|
||||
: t.assignmentExpression("=", info.identifier, attrsObject)
|
||||
)
|
||||
);
|
||||
} else if (info.repeated) {
|
||||
const existingAttr = parentTag
|
||||
.get("attributes")
|
||||
.find(attr => (attr.node as t.MarkoAttribute).name === attrName) as
|
||||
| NodePath<t.ArrayExpression>
|
||||
| undefined;
|
||||
|
||||
if (existingAttr) {
|
||||
existingAttr.pushContainer("elements", attrsObject);
|
||||
} else {
|
||||
parentTag.pushContainer(
|
||||
"attributes",
|
||||
t.markoAttribute(attrName, t.arrayExpression([attrsObject]))
|
||||
);
|
||||
}
|
||||
|
||||
tag.remove();
|
||||
} else {
|
||||
parentTag.pushContainer(
|
||||
@ -127,45 +90,3 @@ export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
tag.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function analyzeRoot(tag: NodePath<t.MarkoTag>) {
|
||||
const lookup = {} as Lookup;
|
||||
analyzeChildren(lookup, false, false, tag);
|
||||
return lookup;
|
||||
}
|
||||
|
||||
function analyzeChildren(
|
||||
lookup: Lookup,
|
||||
repeated: boolean,
|
||||
dynamic: boolean,
|
||||
tag: NodePath<t.MarkoTag>
|
||||
) {
|
||||
for (const child of tag.get("body").get("body")) {
|
||||
if (child.isMarkoTag()) {
|
||||
analyzeChild(lookup, repeated, dynamic, child as NodePath<t.MarkoTag>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function analyzeChild(
|
||||
lookup: Lookup,
|
||||
repeated: boolean,
|
||||
dynamic: boolean,
|
||||
tag: NodePath<t.MarkoTag>
|
||||
) {
|
||||
if (isTransparentTag(tag)) {
|
||||
analyzeChildren(lookup, repeated || isLoopTag(tag), true, tag);
|
||||
} else if (isAttributeTag(tag)) {
|
||||
const attrName = (tag.node.name as t.StringLiteral).value.slice(1);
|
||||
const existing = lookup[attrName];
|
||||
const info =
|
||||
existing ||
|
||||
(lookup[attrName] = {
|
||||
dynamic: false,
|
||||
repeated: false
|
||||
});
|
||||
|
||||
info.dynamic ||= dynamic;
|
||||
info.repeated ||= repeated || existing !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
} from "@marko/babel-utils";
|
||||
import attrsToObject, { getRenderBodyProp } from "../util/attrs-to-object";
|
||||
import { flushBefore, flushInto } from "../util/html-flush";
|
||||
import analyzeTagName from "../util/analyze-tag-name";
|
||||
import translateVar from "../util/translate-var";
|
||||
|
||||
export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
@ -46,7 +45,7 @@ export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
|
||||
const attrsObject = attrsToObject(tag, true);
|
||||
|
||||
if (analyzeTagName(tag).nullable) {
|
||||
if (node.extra.tagNameNullable) {
|
||||
const renderBodyProp = getRenderBodyProp(attrsObject);
|
||||
let renderBodyId: t.Identifier | undefined = undefined;
|
||||
let renderTagExpr: t.Expression = callExpression(
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
Plugin
|
||||
} from "@marko/babel-utils";
|
||||
import { require as markoRequire } from "@marko/compiler/modules";
|
||||
import analyzeTagName, { TagNameTypes } from "../util/analyze-tag-name";
|
||||
import { TagNameTypes } from "../analyze/tag-name-type";
|
||||
import * as hooks from "../util/plugin-hooks";
|
||||
import * as NativeTag from "./native-tag";
|
||||
import * as CustomTag from "./custom-tag";
|
||||
@ -21,6 +21,7 @@ declare module "@marko/babel-utils" {
|
||||
|
||||
export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
const tagDef = getTagDef(tag);
|
||||
const extra = tag.node.extra;
|
||||
|
||||
assertNoArgs(tag);
|
||||
|
||||
@ -57,9 +58,7 @@ export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
}
|
||||
}
|
||||
|
||||
const analyzed = analyzeTagName(tag);
|
||||
|
||||
if (analyzed.dynamic && analyzed.nullable) {
|
||||
if (extra.tagNameDynamic && extra.tagNameNullable) {
|
||||
if (!tag.get("name").isIdentifier()) {
|
||||
const tagNameId = tag.scope.generateUidIdentifier("tagName");
|
||||
const [tagNameVarPath] = tag.insertBefore(
|
||||
@ -73,7 +72,7 @@ export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
}
|
||||
}
|
||||
|
||||
switch (analyzed.type) {
|
||||
switch (extra.tagNameType) {
|
||||
case TagNameTypes.NativeTag:
|
||||
NativeTag.enter(tag);
|
||||
break;
|
||||
@ -97,7 +96,7 @@ export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (analyzeTagName(tag).type) {
|
||||
switch (tag.node.extra.tagNameType) {
|
||||
case TagNameTypes.NativeTag:
|
||||
NativeTag.exit(tag);
|
||||
break;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import { getTagDef } from "@marko/babel-utils";
|
||||
import analyzeTagName from "../../util/analyze-tag-name";
|
||||
import attrsToObject from "../../util/attrs-to-object";
|
||||
import { consumeHTML, flushBefore, flushInto } from "../../util/html-flush";
|
||||
import { writeHTML } from "../../util/html-write";
|
||||
@ -8,13 +7,13 @@ import { callRuntime, getHTMLRuntime } from "../../util/runtime";
|
||||
import translateVar from "../../util/translate-var";
|
||||
|
||||
export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
const { extra } = tag.node;
|
||||
const name = tag.get("name");
|
||||
const attrs = tag.get("attributes");
|
||||
const tagDef = getTagDef(tag);
|
||||
const hasSpread = attrs.some(attr => attr.isMarkoSpreadAttribute());
|
||||
const { nullable } = analyzeTagName(tag);
|
||||
|
||||
if (nullable) {
|
||||
if (extra.tagNameNullable) {
|
||||
flushBefore(tag);
|
||||
}
|
||||
|
||||
@ -87,7 +86,7 @@ export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
emptyBody = true;
|
||||
}
|
||||
|
||||
if (nullable) {
|
||||
if (extra.tagNameNullable) {
|
||||
tag.insertBefore(t.ifStatement(name.node, consumeHTML(tag)!))[0].skip();
|
||||
}
|
||||
|
||||
@ -97,9 +96,9 @@ export function enter(tag: NodePath<t.MarkoTag>) {
|
||||
}
|
||||
|
||||
export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
const { nullable } = analyzeTagName(tag);
|
||||
const { extra } = tag.node;
|
||||
|
||||
if (nullable) {
|
||||
if (extra.tagNameNullable) {
|
||||
flushInto(tag);
|
||||
}
|
||||
|
||||
@ -107,7 +106,7 @@ export function exit(tag: NodePath<t.MarkoTag>) {
|
||||
|
||||
writeHTML(tag)`</${tag.node.name}>`;
|
||||
|
||||
if (nullable) {
|
||||
if (extra.tagNameNullable) {
|
||||
tag.insertBefore(t.ifStatement(tag.node.name, consumeHTML(tag)!))[0].skip();
|
||||
}
|
||||
|
||||
|
||||
@ -1,175 +0,0 @@
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import { isNativeTag } from "@marko/babel-utils";
|
||||
|
||||
export const enum TagNameTypes {
|
||||
NativeTag,
|
||||
CustomTag,
|
||||
DynamicTag,
|
||||
AttributeTag
|
||||
}
|
||||
type TagNameInfo = { type: TagNameTypes; nullable: boolean; dynamic: boolean };
|
||||
const HANDLE_BINDINGS = ["module", "var", "let", "const"];
|
||||
const MARKO_FILE_REG = /^<.*>$|\.marko$/;
|
||||
const CACHE = new WeakMap<NodePath<t.MarkoTag>, TagNameInfo>();
|
||||
const KNOWN_COMPONENTS = new WeakSet<t.Identifier>();
|
||||
|
||||
export function markIdentifierAsComponent(node: t.Identifier) {
|
||||
KNOWN_COMPONENTS.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
export default function analyzeTagName(tag: NodePath<t.MarkoTag>) {
|
||||
let cached = CACHE.get(tag);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const name = tag.get("name");
|
||||
|
||||
if (name.isStringLiteral()) {
|
||||
cached = {
|
||||
type:
|
||||
name.node.value[0] === "@"
|
||||
? TagNameTypes.AttributeTag
|
||||
: isNativeTag(tag)
|
||||
? TagNameTypes.NativeTag
|
||||
: TagNameTypes.CustomTag,
|
||||
nullable: false,
|
||||
dynamic: false
|
||||
};
|
||||
} else {
|
||||
const pending = [name] as NodePath<any>[];
|
||||
let path: typeof pending[0] | undefined;
|
||||
let type: TagNameTypes | undefined = undefined;
|
||||
let nullable = false;
|
||||
|
||||
while ((path = pending.pop()) && type !== TagNameTypes.DynamicTag) {
|
||||
switch (path.type) {
|
||||
case "ConditionalExpression": {
|
||||
const curPath = path as NodePath<t.ConditionalExpression>;
|
||||
pending.push(curPath.get("consequent"));
|
||||
|
||||
if (curPath.node.alternate) {
|
||||
pending.push(curPath.get("alternate"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "LogicalExpression": {
|
||||
const curPath = path as NodePath<t.LogicalExpression>;
|
||||
if (curPath.node.operator === "||") {
|
||||
pending.push(curPath.get("left"));
|
||||
} else {
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
pending.push(curPath.get("right"));
|
||||
break;
|
||||
}
|
||||
|
||||
case "AssignmentExpression":
|
||||
pending.push((path as NodePath<t.AssignmentExpression>).get("right"));
|
||||
break;
|
||||
|
||||
case "BinaryExpression":
|
||||
type =
|
||||
(path as NodePath<t.BinaryExpression>).node.operator !== "+" ||
|
||||
(type !== undefined && type !== TagNameTypes.NativeTag)
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.NativeTag;
|
||||
|
||||
break;
|
||||
|
||||
case "StringLiteral":
|
||||
case "TemplateLiteral":
|
||||
if (type === undefined || type === TagNameTypes.NativeTag) {
|
||||
type = TagNameTypes.NativeTag;
|
||||
} else {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
}
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.NativeTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.NativeTag;
|
||||
break;
|
||||
|
||||
case "NullLiteral":
|
||||
nullable = true;
|
||||
break;
|
||||
|
||||
case "Identifier": {
|
||||
const curPath = path as NodePath<t.Identifier>;
|
||||
if (curPath.node.name === "undefined") {
|
||||
nullable = true;
|
||||
} else {
|
||||
const binding = curPath.scope.getBinding(curPath.node.name);
|
||||
|
||||
if (!binding) {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
} else if (
|
||||
KNOWN_COMPONENTS.has(binding.path.node as t.Identifier)
|
||||
) {
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.CustomTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.CustomTag;
|
||||
} else if (!HANDLE_BINDINGS.includes(binding.kind)) {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
} else if (binding.kind === "module") {
|
||||
const decl = binding.path.parent as t.ImportDeclaration;
|
||||
if (
|
||||
MARKO_FILE_REG.test(decl.source.value) &&
|
||||
decl.specifiers.some(it => t.isImportDefaultSpecifier(it))
|
||||
) {
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.CustomTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.CustomTag;
|
||||
} else {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
}
|
||||
} else {
|
||||
const initialValue = (binding.path as NodePath<t.VariableDeclarator>).get(
|
||||
"init"
|
||||
);
|
||||
if (initialValue.node) {
|
||||
pending.push(initialValue);
|
||||
} else {
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
const assignments = binding.constantViolations;
|
||||
for (let i = assignments.length; i--; ) {
|
||||
const assignment = assignments[
|
||||
i
|
||||
] as NodePath<t.AssignmentExpression>;
|
||||
const { operator } = assignment.node;
|
||||
if (operator === "=") {
|
||||
pending.push(assignment.get("right"));
|
||||
} else if (operator === "+=") {
|
||||
type =
|
||||
type !== undefined && type !== TagNameTypes.NativeTag
|
||||
? TagNameTypes.DynamicTag
|
||||
: TagNameTypes.NativeTag;
|
||||
} else {
|
||||
type = TagNameTypes.DynamicTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
type = TagNameTypes.DynamicTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cached = { type: type!, nullable, dynamic: true };
|
||||
}
|
||||
|
||||
CACHE.set(tag, cached);
|
||||
return cached;
|
||||
}
|
||||
@ -1,20 +1,6 @@
|
||||
import { types as t, NodePath, Visitor } from "@marko/babel-types";
|
||||
import { hasHoistedChildren, isHoistedNode } from "../tag/attribute-tag";
|
||||
import { types as t, NodePath } from "@marko/babel-types";
|
||||
import toPropertyName from "./to-property-name";
|
||||
|
||||
type HoistedVisitorState = { isHoisted: boolean };
|
||||
const HOISTED_CHILDREN_VISITOR: Visitor = {
|
||||
ExpressionStatement(
|
||||
path: NodePath<t.ExpressionStatement>,
|
||||
state: HoistedVisitorState
|
||||
) {
|
||||
if (isHoistedNode(path.node)) {
|
||||
state.isHoisted = true;
|
||||
path.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default function attrsToObject(
|
||||
tag: NodePath<t.MarkoTag>,
|
||||
withRenderBody = false
|
||||
@ -35,31 +21,17 @@ export default function attrsToObject(
|
||||
}
|
||||
|
||||
if (withRenderBody) {
|
||||
if (hasHoistedChildren(tag)) {
|
||||
const state: HoistedVisitorState = { isHoisted: false };
|
||||
const children = tag.get("body").get("body");
|
||||
const len = children.length;
|
||||
let hoistedControlFlows = node.extra.hoistedControlFlows;
|
||||
|
||||
for (let i = len; i--; ) {
|
||||
const child = children[i];
|
||||
if (hoistedControlFlows) {
|
||||
for (const child of tag.get("body").get("body")) {
|
||||
tag.insertBefore(child.node);
|
||||
child.remove();
|
||||
|
||||
if (isHoistedNode(child.node)) {
|
||||
state.isHoisted = true;
|
||||
} else {
|
||||
child.traverse(HOISTED_CHILDREN_VISITOR, state);
|
||||
}
|
||||
|
||||
if (state.isHoisted) {
|
||||
const renderBodyStartIndex = i + 1;
|
||||
|
||||
if (renderBodyStartIndex === len) {
|
||||
tag.insertBefore(tag.node.body.body);
|
||||
tag.node.body.body = [];
|
||||
} else {
|
||||
tag.insertBefore(tag.node.body.body.slice(0, renderBodyStartIndex));
|
||||
tag.node.body.body = tag.node.body.body.slice(renderBodyStartIndex);
|
||||
if (child.isConditional() || child.isLoop()) {
|
||||
if (!--hoistedControlFlows) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,10 +63,10 @@ const _renderer = _register("packages/translator/test/fixtures/at-tags-dynamic/t
|
||||
});
|
||||
|
||||
_hello({
|
||||
col: _col,
|
||||
list: {
|
||||
item: _item
|
||||
}
|
||||
},
|
||||
col: _col
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user