@lcap/builder
Version:
lcap builder utils
336 lines (335 loc) • 13.1 kB
JavaScript
;
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;