UNPKG

@lcap/builder

Version:
336 lines (335 loc) 13.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformJSXElement2Nasl = exports.parseJSXAttr = exports.parseJSXExpression = exports.parseJSXElement2Slot = exports.transformJSXChildNode = exports.getJSXName = exports.EVENT_REGEX = void 0; const react_style_object_to_css_1 = __importDefault(require("react-style-object-to-css")); const lodash_1 = require("lodash"); const utils_1 = require("./utils"); const transform_expression2nasl_1 = __importDefault(require("./transform-expression2nasl")); const transform_func2nasl_1 = __importDefault(require("./transform-func2nasl")); exports.EVENT_REGEX = /^on[A-Z].*/; function getJSXName(ast) { if (ast.type !== 'JSXIdentifier') { throw new Error(`解析 JSXElement 异常, TagName 只运行命名标识, ${(0, utils_1.getNodeCode)(ast)}`); } return ast.name; } exports.getJSXName = getJSXName; const parseJSXStyle = (node) => { if (node.type === 'StringLiteral') { return node.value; } if (node.type === 'JSXExpressionContainer' && node.expression.type === 'ObjectExpression') { // eslint-disable-next-line no-eval const styleObj = eval(`(${(0, utils_1.getNodeCode)(node.expression)})`); return (0, react_style_object_to_css_1.default)(styleObj); } return null; }; const getSlotName = (attrName) => { const slotRegex = /^slot[A-Z].*/; if (slotRegex.test(attrName)) { return (0, lodash_1.lowerFirst)(attrName.substring(4)); } const slotPrefix = 'slot-'; if (attrName.startsWith(slotPrefix)) { return attrName.substring(slotPrefix.length); } return attrName; }; const isJSXEleArray = (ast) => ast.elements.findIndex((n) => !n || (n.type !== 'JSXElement' && n.type !== 'JSXFragment')) === -1; const createTextNode = (str = '') => { if (!str || !str.trim()) { return null; } return { concept: 'ViewElement', tag: 'Text', name: '', bindAttrs: [{ concept: 'BindAttribute', type: 'string', name: 'children', value: str, }], bindEvents: [], bindDirectives: [], bindRoles: [], bindStyles: [], children: [], }; }; const SYNC_KEY = '$sync'; const DIRECTIVE_PREFIX = '_'; function transformJSXChildNode(node) { if (node.type === 'JSXText') { return createTextNode(node.value); } if (node.type === 'JSXElement') { return transformJSXElement2Nasl(node); } if (node.type === 'JSXExpressionContainer' && ['NumericLiteral', 'StringLiteral', 'MemberExpression'].indexOf(node.expression.type) !== -1) { if (node.expression.type === 'MemberExpression') { return { concept: 'ViewElement', tag: 'Text', name: '', bindAttrs: [{ concept: 'BindAttribute', type: 'dynamic', name: 'children', expression: (0, transform_expression2nasl_1.default)(node.expression), }], bindEvents: [], bindDirectives: [], bindRoles: [], bindStyles: [], children: [], }; } return createTextNode((0, utils_1.getNodeCode)(node.expression)); } throw new Error(`JSX 解析异常,子节点仅允许是,JSXText 或 JSXElement, code: ${(0, utils_1.getNodeCode)(node)}`); } exports.transformJSXChildNode = transformJSXChildNode; function parseJSXElement2Slot(ele, attrName, argName) { const templateAST = { concept: 'ViewElement', tag: 'template', name: '', bindAttrs: [], bindEvents: [], bindDirectives: [], bindRoles: [], bindStyles: [], children: [], slotTarget: getSlotName(attrName), }; const eles = Array.isArray(ele) ? ele : [ele]; eles.forEach((node) => { if (node.type === 'JSXElement') { templateAST.children.push(transformJSXElement2Nasl(node)); return; } node.children.forEach((n) => { const naslEle = transformJSXChildNode(n); if (naslEle) { templateAST.children.push(naslEle); } }); }); if (argName) { templateAST.slotScope = argName; } return templateAST; } exports.parseJSXElement2Slot = parseJSXElement2Slot; function parseJSXExpression(ast, attrName, element) { if (ast.type === 'JSXElement') { element.children.push(parseJSXElement2Slot(ast, attrName, '')); return; } if (ast.type === 'JSXFragment') { element.children.push(parseJSXElement2Slot(ast, attrName, '')); return; } if (ast.expression.type === 'JSXEmptyExpression') { throw new Error('解析JSX Expression 异常, 不支持空的Expression'); } if (ast.expression.type === 'JSXElement') { element.children.push(parseJSXElement2Slot(ast.expression, attrName, '')); return; } if (ast.expression.type === 'JSXFragment') { element.children.push(parseJSXElement2Slot(ast.expression, attrName, '')); return; } if (ast.expression.type === 'ArrayExpression' && isJSXEleArray(ast.expression)) { element.children.push(parseJSXElement2Slot(ast.expression.elements, attrName, '')); return; } if (ast.expression.type === 'ArrowFunctionExpression' && (ast.expression.body.type === 'JSXElement' || ast.expression.body.type === 'JSXFragment')) { let argName = ''; if (ast.expression.params && ast.expression.params[0] && ast.expression.params[0].type === 'Identifier') { argName = ast.expression.params[0].name; } element.children.push(parseJSXElement2Slot(ast.expression.body, attrName, argName)); return; } if (ast.expression.type === 'CallExpression' && (0, utils_1.getNodeCode)(ast.expression.callee) === SYNC_KEY) { if (ast.expression.arguments.length !== 1) { throw new Error(`解析 JSX Expression 异常,$sync 仅允许传一个参数,${(0, utils_1.getNodeCode)(ast)}`); } const arg = ast.expression.arguments[0]; if (['JSXNamespacedName', 'SpreadElement', 'ArgumentPlaceholder'].indexOf(arg.type) !== -1) { throw new Error(`解析 JSX Expression 异常,不支持该表达式,${(0, utils_1.getNodeCode)(ast)}`); } element.bindAttrs.push({ concept: 'BindAttribute', name: attrName, type: 'dynamic', sync: true, expression: (0, transform_expression2nasl_1.default)(arg), }); return; } if (exports.EVENT_REGEX.test(attrName)) { const eventName = (0, lodash_1.kebabCase)(attrName.substring(2)); const bindEvent = { concept: 'BindEvent', name: eventName, logics: [], }; if (ast.expression.type === 'FunctionExpression') { const logicNode = (0, transform_func2nasl_1.default)(ast.expression); logicNode.params = []; bindEvent.logics.push(logicNode); } else if (ast.expression.type === 'ArrayExpression' && ast.expression.elements.findIndex((eleNode) => !eleNode || eleNode.type !== 'FunctionExpression') === -1) { bindEvent.logics.push(...ast.expression.elements.map((eleNode) => { const logicNode = (0, transform_func2nasl_1.default)(eleNode); logicNode.params = []; return logicNode; })); } else { throw new Error(`JSX 解析失败,事件绑定仅支持函数或函数数组, ${(0, utils_1.getNodeCode)(ast)}`); } element.bindEvents.push(bindEvent); return; } if (['NumericLiteral', 'BooleanLiteral', 'ArrayExpression', 'ObjectExpression'].indexOf(ast.expression.type) !== -1) { element.bindAttrs.push({ concept: 'BindAttribute', name: attrName, type: 'static', value: (0, utils_1.getNodeCode)(ast.expression), }); return; } element.bindAttrs.push({ concept: 'BindAttribute', name: attrName, type: 'dynamic', expression: (0, transform_expression2nasl_1.default)(ast.expression), }); } exports.parseJSXExpression = parseJSXExpression; function parseJSXAttr(attr, element) { if (attr.type === 'JSXSpreadAttribute') { throw new Error(`解析 JSXElement 异常, 不支持 ...rest 属性, ${(0, utils_1.getNodeCode)(attr)}`); } const attrName = getJSXName(attr.name); if (attrName === 'ref') { if (!attr.value || attr.value.type !== 'StringLiteral') { throw new Error(`解析 JSXElement 异常, ref 仅允许设置静态字符串, 例如 ref="button", ${(0, utils_1.getNodeCode)(attr)}`); } element.name = attr.value.value; return; } if (attrName === 'style') { const value = parseJSXStyle(attr.value); if (value) { element.staticStyle = value; } return; } if ((attrName === 'className' || attrName === 'class') && attr.value) { if (attr.value.type === 'StringLiteral' && attr.value.value) { element.staticClass = attr.value.value; } return; } if (attrName === 'rules') { if (!attr.value || attr.value.type !== 'JSXExpressionContainer' || attr.value.expression.type !== 'ArrayExpression') { throw new Error(`解析 JSXElement 异常, rules 仅允许设置内置验证规则, 例如 rules=[nasl.validation.required()], ${(0, utils_1.getNodeCode)(attr)}`); } element.bindAttrs.push({ concept: 'BindAttribute', name: attrName, rules: attr.value.expression.elements.map((exp) => { if (!exp || exp.type !== 'CallExpression') { throw new Error(`解析 JSXElement 异常, rules 仅允许设置内置验证规则, 例如 rules=[nasl.validation.required()], ${(0, utils_1.getNodeCode)(attr)}`); } const callLogic = (0, transform_expression2nasl_1.default)(exp); return { concept: 'ValidationRule', calleeNamespace: callLogic.calleeNamespace, calleeName: callLogic.calleeName, arguments: callLogic.arguments, }; }), }); return; } if (!attr.value) { element.bindAttrs.push({ concept: 'BindAttribute', name: attrName, type: 'static', value: 'true', }); return; } if (attr.value.type === 'JSXExpressionContainer' || attr.value.type === 'JSXElement' || attr.value.type === 'JSXFragment') { parseJSXExpression(attr.value, attrName, element); return; } element.bindAttrs.push({ concept: 'BindAttribute', name: attrName, type: 'string', value: attr.value ? attr.value.value : '', }); } exports.parseJSXAttr = parseJSXAttr; function transformJSXElement2Nasl(element) { const node = element.openingElement; const elementAST = { concept: 'ViewElement', tag: '', name: '', bindAttrs: [], bindEvents: [], bindDirectives: [], bindRoles: [], bindStyles: [], children: [], }; if (node.name.type !== 'JSXIdentifier') { throw new Error(`解析 JSXElement 异常, TagName 只运行命名标识, ${(0, utils_1.getNodeCode)(node)}`); } elementAST.tag = node.name.name; node.attributes.forEach((attr) => parseJSXAttr(attr, elementAST)); elementAST.bindAttrs = elementAST.bindAttrs.filter((attr) => { if (attr.name.startsWith(DIRECTIVE_PREFIX)) { elementAST.bindDirectives.push({ concept: 'BindDirective', name: attr.name.substring(DIRECTIVE_PREFIX.length), type: attr.type, expression: attr.expression, value: attr.value, playground: [], }); return false; } return true; }); const childNodes = []; element.children.forEach((childNode) => { const result = transformJSXChildNode(childNode); if (result) { childNodes.push(result); } }); if (elementAST.tag === 'Text' && childNodes.length > 0) { throw new Error('JSX解析异常, Text 组件不允许有子节点,请写children属性'); } elementAST.children.push(...childNodes); return elementAST; } exports.transformJSXElement2Nasl = transformJSXElement2Nasl; exports.default = transformJSXElement2Nasl;