UNPKG

@lcap/builder

Version:
545 lines (542 loc) 23.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.orderProp = exports.removeProp = exports.updateProp = exports.addMethod = exports.addReadableProp = exports.addSlot = exports.addEvent = exports.addProp = exports.removeSubComponent = exports.addSubComponent = exports.updateInfo = void 0; /* eslint-disable no-continue */ /* eslint-disable global-require */ const babel = __importStar(require("@babel/core")); const bt = __importStar(require("@babel/types")); const traverse_1 = __importDefault(require("@babel/traverse")); const generator_1 = __importDefault(require("@babel/generator")); const lodash_1 = require("lodash"); const fs_extra_1 = __importDefault(require("fs-extra")); const babel_utils_1 = require("./babel-utils"); const schema_utils_1 = require("./schema-utils"); const lcap_1 = require("./lcap"); function updateInfo(ast, options) { const { name, data } = options; const componentKeys = ['title', 'group', 'icon', 'description']; (0, traverse_1.default)(ast, { Decorator(path) { const p = path.get('expression'); if (p.isCallExpression() && bt.isIdentifier(p.node.callee) && ['ExtensionComponent', 'Component', 'IDEExtraInfo'].includes(p.node.callee.name) && p.node.arguments[0] && p.node.arguments[0].type === 'ObjectExpression' && bt.isClassDeclaration(path.parent) && bt.isIdentifier(path.parent.id) && path.parent.id.name === name) { const obj = (0, babel_utils_1.evalOptions)(p.node.arguments[0]) || {}; if (p.node.callee.name === 'Component') { p.node.arguments[0] = (0, babel_utils_1.getAST)(Object.assign(obj, (0, lodash_1.pick)(data, componentKeys))); } else { p.node.arguments[0] = (0, babel_utils_1.getAST)(Object.assign(obj, (0, lodash_1.omit)(data, componentKeys))); } } }, }); return ast; } exports.updateInfo = updateInfo; function addSubComponent(ast, options) { const { sourceName } = options.data; let { name, title = '', description = '', type = 'pc', } = options.data; if (sourceName) { const schema = (0, lcap_1.getProjectSourceSchema)(); const sourceComponent = schema.components.find((n) => n.name === sourceName); if (!sourceComponent) { throw new Error(`未找到组件 ${sourceName} 对应的解析结果`); } title = (0, lodash_1.kebabCase)(sourceName).split('-').map((s) => (0, lodash_1.upperFirst)(s)).join(' '); description = sourceComponent.description; name = (0, lodash_1.upperFirst)((schema.write && schema.write.prefix) || '') + sourceName; if (schema.write && schema.write.type) { type = schema.write.type; } } const codeTemplate = `namespace extensions.de.viewComponents { const { Component, Prop, ViewComponent, Slot, Method, Event, ViewComponentOptions } = nasl.ui; @ExtensionComponent({ type: '${type}', ${sourceName ? `sourceName: '${sourceName}'` : ''} ideusage: { idetype: 'element', } }) @Component({ title: '${title}', description: '${description || title}', }) export class ${name} extends ViewComponent { constructor(options?: Partial<${name}Options>) { super(); } } export class ${name}Options extends ViewComponentOptions { } }`; const tplAst = babel.parse(codeTemplate, { filename: 'result.ts', presets: [require('@babel/preset-typescript')], plugins: [ [require('@babel/plugin-proposal-decorators'), { legacy: true }], ], rootMode: 'root', root: __dirname, }); const exportClassASTs = []; (0, traverse_1.default)(tplAst, { ExportNamedDeclaration(path) { if (bt.isClassDeclaration(path.node.declaration)) { exportClassASTs.push(path.node); } }, }); (0, traverse_1.default)(ast, { TSModuleBlock(path) { path.node.body.push(...exportClassASTs); }, }); } exports.addSubComponent = addSubComponent; function removeSubComponent(ast, options) { const { name } = options.data; (0, traverse_1.default)(ast, { ExportNamedDeclaration(path) { if (bt.isClassDeclaration(path.node.declaration) && bt.isIdentifier(path.node.declaration.id) && [name, `${name}Options`].includes(path.node.declaration.id.name)) { path.remove(); } }, }); } exports.removeSubComponent = removeSubComponent; function getPropertyMeta(componentName, type) { const isStateProp = ['method', 'readableProp'].includes(type); const className = isStateProp ? componentName : `${componentName}Options`; const decoratorName = type === 'readableProp' ? 'Prop' : (0, lodash_1.upperFirst)(type); return { className, decoratorName, }; } function insertProperty(ast, property, componentName, type) { const { className, decoratorName } = getPropertyMeta(componentName, type); (0, traverse_1.default)(ast, { ClassDeclaration(path) { if (bt.isIdentifier(path.node.id) && path.node.id.name === className) { const hasProp = path.node.body.body.some((n) => ((bt.isClassProperty(n) || bt.isClassMethod(n)) && ((bt.isIdentifier(n.key) && bt.isIdentifier(property.key) && n.key.name === property.key.name) || (bt.isStringLiteral(n.key) && bt.isStringLiteral(property.key) && n.key.value === property.key.value)))); if (hasProp) { throw new Error(`${componentName} 组件中已存在属性 ${bt.isIdentifier(property.key) ? property.key.name : property.key.value}`); } const lastPropIndex = path.node.body.body.findLastIndex((n) => (bt.isClassProperty(n) && n.decorators && n.decorators.some((d) => (bt.isCallExpression(d.expression) && bt.isIdentifier(d.expression.callee) && d.expression.callee.name === decoratorName)))); path.node.body.body.splice(lastPropIndex + 1, 0, property); } }, }); } function removeProperty(ast, componentName, propName, type) { const { className, decoratorName } = getPropertyMeta(componentName, type); if (type === 'event') { propName = (0, schema_utils_1.normalizeEventName)(propName); } else if (type === 'slot') { propName = (0, schema_utils_1.normalizeSlotName)(propName); } (0, traverse_1.default)(ast, { ClassDeclaration(path) { if (bt.isIdentifier(path.node.id) && path.node.id.name === className) { path.traverse({ ClassProperty(p) { if (bt.isIdentifier(p.node.key) && p.node.key.name === propName && (p.node.decorators || []).some((d) => (bt.isCallExpression(d.expression) && bt.isIdentifier(d.expression.callee) && d.expression.callee.name === decoratorName))) { p.remove(); } }, ClassMethod(p) { if (bt.isIdentifier(p.node.key) && p.node.key.name === propName && type === 'method') { p.remove(); } }, }); } }, }); // 移除驼峰形式的插槽 if (type === 'slot' && propName.includes('-')) { const n = (0, lodash_1.camelCase)(propName); removeProperty(ast, componentName, n, type); } } function findPropertyAST(ast, componentName, propName, type) { const isStateProp = ['method', 'readableProp'].includes(type); const className = isStateProp ? componentName : `${componentName}Options`; const decoratorName = type === 'readableProp' ? 'Prop' : (0, lodash_1.upperFirst)(type); let propAST; let propOptionsAST; (0, traverse_1.default)(ast, { ClassDeclaration(path) { if (bt.isIdentifier(path.node.id) && path.node.id.name === className) { propAST = path.node.body.body.find((n) => ((bt.isClassProperty(n) || bt.isClassMethod(n)) && bt.isIdentifier(n.key) && n.key.name === propName && n.decorators && n.decorators.some((d) => bt.isCallExpression(d.expression) && bt.isIdentifier(d.expression.callee) && d.expression.callee.name === decoratorName))); if (propAST && propAST.decorators) { const propDecorator = propAST.decorators.find((d) => bt.isCallExpression(d.expression) && bt.isIdentifier(d.expression.callee) && d.expression.callee.name === decoratorName); propOptionsAST = (propDecorator === null || propDecorator === void 0 ? void 0 : propDecorator.expression).arguments[0]; } } }, }); return { propAST, propOptionsAST, }; } function updatePropertyOptions(propOptionsAST, rest) { const keys = Object.keys(rest); const stringifyKeys = ['onChange', 'if', 'disabledIf', 'setter']; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = rest[key]; const propIndex = propOptionsAST.properties.findIndex((p) => ((bt.isObjectProperty(p) || bt.isObjectMethod(p)) && p.key.type === 'Identifier' && p.key.name === key)); if (value === null) { propOptionsAST.properties.splice(propIndex, 1); continue; } const prop = propOptionsAST.properties[propIndex]; const requireStringify = !stringifyKeys.includes(key); if (!prop) { propOptionsAST.properties.push({ type: 'ObjectProperty', key: { type: 'Identifier', name: key, }, value: (0, babel_utils_1.getAST)(value, requireStringify), computed: false, shorthand: false, }); } else if (bt.isClassMethod(prop)) { propOptionsAST.properties[propIndex] = { type: 'ObjectProperty', key: { type: 'Identifier', name: key, }, value: (0, babel_utils_1.getAST)(value, requireStringify), computed: false, shorthand: false, }; } else { prop.value = (0, babel_utils_1.getAST)(value, requireStringify); } } } function addProp(ast, options) { const { name: componentName } = options; const { name, schema, group = '主要属性' } = options.data; let code = ''; if (schema) { code = (0, schema_utils_1.genAttrCode)(schema, group); } else { code = `@Prop({ group: '${group}', title: '${(0, schema_utils_1.genTitle)(name)}', description: '${(0, schema_utils_1.genTitle)(name)}', setter: { concept: 'InputSetter', }, }) ${name}: any; `; } const propAST = (0, babel_utils_1.getAPIPropAST)(code, name); insertProperty(ast, propAST, componentName, 'prop'); } exports.addProp = addProp; function addEvent(ast, options) { const { name: componentName } = options; const { schema } = options.data; let code = ''; const name = (0, schema_utils_1.normalizeEventName)(options.data.name); if (schema) { code = (0, schema_utils_1.genEventCode)(schema); } else { code = `@Event({ title: '${(0, schema_utils_1.genTitle)(name)}', description: '${(0, schema_utils_1.genTitle)(name)}', }) ${name}: (event: {}) => any; `; } const propAST = (0, babel_utils_1.getAPIPropAST)(code, name); insertProperty(ast, propAST, componentName, 'event'); } exports.addEvent = addEvent; function addSlot(ast, options) { const { name: componentName } = options; const { schema } = options.data; let code = ''; const name = (0, schema_utils_1.normalizeSlotName)(options.data.name); if (schema) { code = (0, schema_utils_1.genSlotCode)(schema); } else { code = `@Slot({ title: '${(0, schema_utils_1.genTitle)(name)}', description: '${(0, schema_utils_1.genTitle)(name)}', }) ${name.includes('-') ? `'${name}'` : name}: () => Array<nasl.ui.ViewComponent>; `; } const propAST = (0, babel_utils_1.getAPIPropAST)(code, name); insertProperty(ast, propAST, componentName, options.module); if (name.includes('-')) { const n = (0, lodash_1.camelCase)(name); const camelCaseAst = (0, babel_utils_1.getAPIPropAST)(`${n}: (current: {}) => Array<nasl.ui.ViewComponent>;`, n); insertProperty(ast, camelCaseAst, componentName, options.module); } } exports.addSlot = addSlot; function addReadableProp(ast, options) { const { name: componentName } = options; const { name } = options.data; const code = `@Prop({ title: '${(0, schema_utils_1.genTitle)(name)}', description: '${(0, schema_utils_1.genTitle)(name)}', }) ${name}: any; `; const propAST = (0, babel_utils_1.getAPIPropAST)(code, name); insertProperty(ast, propAST, componentName, options.module); } exports.addReadableProp = addReadableProp; function addMethod(ast, options) { const { name: componentName } = options; const { name, schema } = options.data; let code = ''; if (schema) { code = (0, schema_utils_1.genMethodCode)(schema); } else { code = `@Method({ title: '${(0, schema_utils_1.genTitle)(name)}', description: '${(0, schema_utils_1.genTitle)(name)}', }) ${name}(): void { }`; } const propAST = (0, babel_utils_1.getAPIPropAST)(code, name); insertProperty(ast, propAST, componentName, options.module); } exports.addMethod = addMethod; function updateProp(ast, options) { const { name: componentName } = options; const _a = options.data, { name, tsType, defaultValue } = _a, rest = __rest(_a, ["name", "tsType", "defaultValue"]); let { propName } = options; if (options.module === 'event') { propName = (0, schema_utils_1.normalizeEventName)(propName); } else if (options.module === 'slot') { propName = (0, schema_utils_1.normalizeSlotName)(propName); } const { propAST, propOptionsAST } = findPropertyAST(ast, componentName, propName, options.module); if (!propAST || !propOptionsAST) { throw new Error(`${componentName} 组件中未找到属性 ${propName} 的属性声明`); } if (name) { propAST.key.name = name; } if (tsType) { const typeAST = options.module === 'method' ? (0, babel_utils_1.getAST)(tsType, false) : (0, babel_utils_1.getTypeAST)(tsType); if (options.module === 'method' && bt.isClassMethod(propAST) && typeAST.type === 'FunctionExpression') { propAST.params = typeAST.params; if (typeAST.returnType) { propAST.returnType = typeAST.returnType; propAST.body.body = propAST.returnType.type === 'TSTypeAnnotation' && propAST.returnType.typeAnnotation.type === 'TSVoidKeyword' ? [] : [ { type: 'ReturnStatement', argument: { type: 'TSAsExpression', expression: { type: 'NullLiteral', }, typeAnnotation: { type: 'TSAnyKeyword', }, }, }, ]; } else { propAST.returnType = { type: 'TSTypeAnnotation', typeAnnotation: { type: 'TSVoidKeyword', }, }; propAST.body.body = []; } } else if (bt.isClassProperty(propAST)) { if (!propAST.typeAnnotation || propAST.typeAnnotation.type !== 'TSTypeAnnotation') { propAST.typeAnnotation = { type: 'TSTypeAnnotation', typeAnnotation: { type: 'TSAnyKeyword', }, }; } propAST.typeAnnotation.typeAnnotation = (0, babel_utils_1.getTypeAST)(tsType); } } if (!(0, lodash_1.isNil)(defaultValue) && bt.isClassProperty(propAST)) { propAST.value = defaultValue ? (0, babel_utils_1.getAST)(defaultValue, false) : null; } updatePropertyOptions(propOptionsAST, rest); } exports.updateProp = updateProp; function removeProp(ast, options) { const { name: componentName, propName } = options; removeProperty(ast, componentName, propName, options.module); } exports.removeProp = removeProp; function orderProp(ast, options) { const { name: componentName } = options; const { names, isOptions } = options.data; const className = isOptions ? `${componentName}Options` : componentName; (0, traverse_1.default)(ast, { ClassDeclaration(path) { if (bt.isIdentifier(path.node.id) && path.node.id.name === className) { path.node.body.body = [...path.node.body.body].sort((ast1, ast2) => { let index1 = ast1.key && ast1.key.name ? names.indexOf(ast1.key.name) : 1000; let index2 = ast2.key && ast2.key.name ? names.indexOf(ast2.key.name) : 1000; if (index1 === -1) { index1 = 1000; } if (index2 === -1) { index2 = 1000; } return index1 - index2; }); } }, }); } exports.orderProp = orderProp; function updateAPIFile(tsPath, actions) { return __awaiter(this, void 0, void 0, function* () { if (!tsPath || !fs_extra_1.default.existsSync(tsPath)) { throw new Error(`未找到 api.ts 文件,${tsPath}`); } const tsCode = fs_extra_1.default.readFileSync(tsPath, 'utf-8').toString(); const ast = babel.parse(tsCode, { filename: 'result.ts', presets: [require('@babel/preset-typescript')], plugins: [ [require('@babel/plugin-proposal-decorators'), { legacy: true }], ], rootMode: 'root', root: __dirname, }); actions.forEach((options) => { switch (true) { case options.module === 'info' && options.type === 'update': return updateInfo(ast, options); case options.module === 'subComponent' && options.type === 'add': return addSubComponent(ast, options); case options.module === 'subComponent' && options.type === 'remove': return removeSubComponent(ast, options); case options.module === 'prop' && options.type === 'add': return addProp(ast, options); case options.module === 'event' && options.type === 'add': return addEvent(ast, options); case options.module === 'slot' && options.type === 'add': return addSlot(ast, options); case options.module === 'readableProp' && options.type === 'add': return addReadableProp(ast, options); case options.module === 'method' && options.type === 'add': return addMethod(ast, options); case options.type === 'update': return updateProp(ast, options); case options.type === 'remove': return removeProp(ast, options); case options.type === 'order': return orderProp(ast, options); default: throw new Error(`未找到匹配的更新操作,${JSON.stringify(options)}`); } }); let { code } = (0, generator_1.default)(ast); code = yield (0, babel_utils_1.formatCode)(code, 'typescript'); fs_extra_1.default.writeFileSync(tsPath, code, 'utf-8'); }); } exports.default = updateAPIFile;