feat(translator-fluurt): extract some translate logic to analyze stage (#63)

This commit is contained in:
Dylan Piercey 2020-12-17 08:46:52 -07:00 committed by GitHub
parent aeceabc97d
commit 4b71f45928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 416 additions and 376 deletions

27
package-lock.json generated
View File

@ -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",

View File

@ -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",

View 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;

View 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;
}

View 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;
}

View File

@ -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)

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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(

View File

@ -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;

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -63,10 +63,10 @@ const _renderer = _register("packages/translator/test/fixtures/at-tags-dynamic/t
});
_hello({
col: _col,
list: {
item: _item
}
},
col: _col
});
});