@lcap/builder
Version:
lcap builder utils
545 lines (542 loc) • 23.2 kB
JavaScript
;
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;