UNPKG

@lcap/nasl-parser

Version:

Take Nasl text to Nasl AST with the help of generalized parsing.

511 lines (459 loc) 18.2 kB
// @ts-nocheck import * as nasl from './decorated-nasl-ast'; import { isNewList, isStructure } from '@lcap/nasl-concepts/asserts'; import { head, last, isEmpty, projectIdentifier, CODEWAVE, Visitor, NASL_CORE_NL, } from './common-util'; import { CompilerEnvInfo } from './resolve-local-bindings'; import { forceToString, forceToBoolean, forceToNumber } from './process-annotations'; import { isMap } from 'util/types'; /** 已支持: * @Entity(...) 注解支持以下参数 * source = "defaultDS", * name = "person", * table = "sql_School", // 数据库表名,或者日后用 @table("sql_School") * description = "学校", // 实体描述 * origin = "ide", // 实体来源, * @createdTime, @createdTime(true), @createdTime() 前两种等价 * @updatedTime(false), 没实现 updatedTime=updatedTime(false) 这种语法 * @createdBy, * @updatedBy, 目前以上四个字段,有注解才会生成,不会默认生成 * @id, 自动生成 Id * 用 @Entity(...) 标记一个 struct 后,struct 的 @Column(...) 属性的注解只能是以下注解的组合 * @required * @primaryKey * @name * @label * @description * @type * @generationRule * @display * @rules * @sequence */ /** * 没实现的 column 注解 TODO: * relationNamespace * relationEntity * relationProperty * deleteRule */ const SOURCE = 'source' const ENTITY = 'Entity'; const COLUMN = 'Column'; const supportedEntityAnnArgs = [SOURCE, 'name', 'table', 'description', 'origin', 'createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'id']; // 后五个去掉也行,因为类似 id = @id 的语法还没实现 const supportedColumnAnnArgs = ['name', 'label', 'description', 'type', 'generationRule', 'display', 'rules', 'sequence', 'required', 'primaryKey']; // 后两个去掉也行,因为类似 required = @required 的语法还没实现 const isSpecificAnnArg = (name: string) => (arg: nasl.CstAnnotationArg) => arg.name === name; const isSpecificAnnArgVal = (name: string) => (arg: nasl.CstAnnotationArg) => projectIdentifier(arg.value.name) === name; // 几个特殊注解,不用 key = val(...) 而用 @val(...) 语法 const isCreatedTime = isSpecificAnnArgVal('createdTime'); const isUpdatedTime = isSpecificAnnArgVal('updatedTime'); const isCreatedBy = isSpecificAnnArgVal('createdBy'); const isUpdatedBy = isSpecificAnnArgVal('updatedBy'); const isId = isSpecificAnnArgVal('id'); const isRequired = isSpecificAnnArgVal('required'); const isPrimaryKey = isSpecificAnnArgVal('primaryKey'); const isColumnName = isSpecificAnnArg('name'); const isLabel = isSpecificAnnArg('label'); const isDescription = isSpecificAnnArg('description'); const isDatabaseTypeAnnotation = isSpecificAnnArg('type'); const isGenerationRule = isSpecificAnnArg('generationRule'); const isDisplay = isSpecificAnnArg('display'); const isRules = isSpecificAnnArg('rules'); const isSequence = isSpecificAnnArg('sequence'); export const tryAddToNaslDataSource = (dss: Array<nasl.DataSource>) => (ns: nasl.Structure): nasl.Structure => { if (!isStructure(ns)) { return ns; } const anns: Array<Annotation> = ns.annotations; if (anns) { anns.forEach((ann: nasl.CstAnnotation) => { // const nameSpace: string = isEmpty(ann.name.namespace) ? null : ann.name.namespace.join(NAMESPACE_SEP); if (ann.type === 'Builtin') { if (!isValidEntityAnn(ann)) { throw new Error('invalid entity annotation'); } const dsArg = ann.args.find(arg => arg.name === SOURCE); let targetDs = dss.find(ds => ds.name === forceToString(dsArg.val)); // DANGEROUS if (!targetDs) { targetDs = new nasl.DataSource({ name: forceToString(dsArg.val), // DANGEROUS description: forceToString(ann.args.find(arg => arg.name === 'description').val), // DANGEROUS entities: [], }) dss.push(targetDs) } addToSpecificNaslDataSource(ns, targetDs); } // if (ann.name.name === ENTITY) { // } }); } return ns; } function addToSpecificNaslDataSource(ns: nasl.Structure, ds: nasl.DataSource) { const anns: Array<Annotation> = ns.annotations; const dsAnn = anns.find(ann => ann.args.find(arg => arg.name === SOURCE)); const ne = new nasl.Entity({ name: forceToString(dsAnn.args.find(arg => arg.name === 'name').val), // DANGEROUS description: forceToString(dsAnn.args.find(arg => arg.name === 'description').val), // DANGEROUS tableName: forceToString(dsAnn.args.find(arg => arg.name === 'table').val), // DANGEROUS // @ts-ignore origin: dsAnn.args.find(arg => arg.name === 'origin') ? forceToString(dsAnn.args.find(arg => arg.name === 'origin').val) : 'ide', // DANGEROUS properties: ns.properties.map(toNaslDataEntity), }); // 几个特殊属性(注解) const maybeCtp = maybeCreatedTimeProp(dsAnn.args.find(isCreatedTime)) if (maybeCtp) { ne.properties.push(maybeCtp); } const maybeUtp = maybeUpdatedTimeProp(dsAnn.args.find(isUpdatedTime)) if (maybeUtp) { ne.properties.push(maybeUtp); } const maybeCbp = maybeCreatedByProp(dsAnn.args.find(isCreatedBy)) if (maybeCbp) { ne.properties.push(maybeCbp); } const maybeUbp = maybeUpdatedByProp(dsAnn.args.find(isUpdatedBy)) if (maybeUbp) { ne.properties.push(maybeUbp); } const maybeId = maybeIdProp(dsAnn.args.find(isId)) if (maybeId) { ne.properties.push(maybeId); } if (!ds.entities) { ds.entities = new Array(); } ds.entities.push(ne); } function processColumnAnnotation(nep: nasl.EntityProperty, ann: Annotation) { ann.args.forEach(arg => { if (isColumnName(arg)) { nep.columnName = forceToString(arg.val); } if (isLabel(arg)) { nep.label = forceToString(arg.val); } if (isDescription(arg)) { nep.description = forceToString(arg.val); } if (isGenerationRule(arg)) { if (arg.val) { if (['auto', 'autoIncrement', 'manual'].includes(forceToString(arg.val))) { // @ts-ignore nep.generationRule = forceToString(arg.val); } else { throw new Error('invalid generationRule annotation argument'); } } else { nep.generationRule = 'auto'; } } if (isSequence(arg)) { nep.sequence = forceToString(arg.val); } if (isRequired(arg)) { nep.required = true; } if (isPrimaryKey(arg)) { nep.primaryKey = true; } if (isDatabaseTypeAnnotation(arg)) { nep.databaseTypeAnnotation = processAnnArgValForDatabaseTypeAnnotation(arg.val); if (isCustomAnnotation(arg.val) && projectIdentifier(arg.val.name) === 'decimal') { nep.rules.push(`scale(${arg.val.args[1].val})`); // decimal(31, 2), scale = 2 } } if (isDisplay(arg) && isMapLit(arg.val)) { nep.display = processAnnArgValForDisplay(arg.val) } if (isRules(arg)) { if (isMapLit(arg.val)) { nep.rules = processAnnArgValForRules(arg.val); } } }); } const toNaslDataEntity = (nsp: nasl.StructureProperty): nasl.EntityProperty => { const normalizeColName = (input: string) => input.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, ''); const nep = new nasl.EntityProperty({ name: nsp.name, typeAnnotation: nsp.typeAnnotation, defaultValue: nsp.defaultValue, columnName: normalizeColName(nsp.name), label: "", description: "", display: genDisplay(true, true, true, true), rules: [], generationRule: "manual", sequence: undefined, required: undefined, primaryKey: undefined, databaseTypeAnnotation: undefined }); const anns: Array<Annotation> = nsp.annotations; anns.forEach(ann => { if (!isCustomAnnotation(ann)) { return; } if (projectIdentifier(ann.name) === COLUMN) { if (!isValidColumnAnn(ann)) { throw new Error('invalid column annotation'); } processColumnAnnotation(nep, ann); } }); return nep; } // 如返回 "max(100)" function processAnnArgValForRules(aav: AnnArgVal): Array<string> { if (isAnnotation(aav)) { return isCustomAnnotation(aav) ? [`${projectIdentifier(aav.name)}(${processAnnArgValForRules(aav.args[0].val)})`] : null; } else if (isMapLit(aav)) { return aav.entries.map(e => `${e.key}=${e.val}`); } else if (isArrayLit(aav)) { return aav.elems.flatMap(toString); } else { return [aav.val.toString()]; } } /** * 支持语法:@A@A(true) 或 @A(false) * 支持注解:@detail, @filter, @form, @table, * @detail 返回 "detail": true, * @filter(true) 返回 "detail": true, * @form(false) 返回 "form": false, */ function processAnnArgValForDisplay(aav: AnnArgVal): { [name: string]: boolean } { if (isCustomAnnotation(aav)) { if (aav.args.length > 0) { return { [aav.name.name]: forceToBoolean(aav.args[0].val) } } else { return { [aav.name.name]: true } } } else if (isMapLit(aav)) { const res = {}; aav.entries.forEach(e => { if (isUnaryExpr(e.key.expr) && isStringLit(e.key.expr.expr) && isUnaryExpr(e.val.expr) && isStringLit(e.val.expr.expr)) { res[e.key.expr.expr.val] = e.key.expr.expr.val; } }) return res; } else { throw new Error('should not reach here in processAnnArgValForDisplay'); } } function processAnnArgValForDatabaseTypeAnnotation(aav: AnnArgVal): nasl.DatabaseTypeAnnotation { if (isCustomAnnotation(aav)) { const nlc = projectIdentifier(aav.name).toLowerCase(); const numFirstArg = forceToNumber(aav.args[0].val); if (['varchar', 'char'].includes(nlc)) { // console.log('🐔', nlc, numFirstArg) const b = new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), // 这里使用 new Map() 会导致 toJSON 时丢失数据,所以改用普通对象 //@ts-ignore arguments: { 'length': numFirstArg.toString() }, }); // b.arguments.set() console.log(b.arguments) return b } if (['int', 'tinyint', 'smallint', 'mediumint', 'bigint'].includes(nlc)) { return new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), //@ts-ignore arguments: { 'displayWidth': numFirstArg.toString() }, }) } if (['decimal'].includes(nlc)) { return new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), //@ts-ignore arguments: { 'precision': numFirstArg.toString() }, }) } if (['date'].includes(nlc)) { return new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), }) } if (['time'].includes(nlc)) { return new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), }) } if (['datetime', 'timestamp'].includes(nlc)) { return new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), }) } if (['text', 'tinytext', 'mediumtext', 'longtext'].includes(nlc)) { return new nasl.DatabaseTypeAnnotation({ typeName: projectIdentifier(aav.name), }) } } throw new Error('Unsupported database type annotations. in processAnnArgValForDatabaseTypeAnnotation'); } // CreatedTime function maybeCreatedTimeProp(ct: AnnArg | undefined): nasl.EntityProperty | undefined { if (ct && isAnnotation(ct.val)) { let aav = ct.val; const shouldGenerate = aav?.args?.length === 0 || forceToBoolean(aav?.args?.[0]?.val) === true // @createdTime(true) if (shouldGenerate) { return new nasl.EntityProperty({ name: 'createdTime', columnName: 'created_time', label: '创建时间', description: '创建时间', typeAnnotation: naslCoreTypeAnn('DateTime'), display: genDisplay(true, false, false, false), generationRule: 'auto', }) } } return undefined; } // UpdatedTime function maybeUpdatedTimeProp(ut: AnnArg | undefined): nasl.EntityProperty | undefined { if (ut && isAnnotation(ut.val)) { let aav = ut.val; const shouldGenerate = aav?.args?.length === 0 || forceToBoolean(aav?.args?.[0]?.val) === true // @updatedTime(true) if (shouldGenerate) { return new nasl.EntityProperty({ name: 'updatedTime', columnName: 'updated_time', label: '更新时间', description: '更新时间', typeAnnotation: naslCoreTypeAnn('DateTime'), display: genDisplay(true, false, false, false), generationRule: 'auto', }) } } return undefined; } // CreatedBy function maybeCreatedByProp(cb: AnnArg | undefined): nasl.EntityProperty | undefined { if (cb && isAnnotation(cb.val)) { let aav = cb.val; const shouldGenerate = aav?.args?.length === 0 || forceToBoolean(aav?.args?.[0]?.val) === true // @createdBy(true) if (shouldGenerate) { return new nasl.EntityProperty({ name: 'createdBy', columnName: 'created_by', label: '创建者', description: '创建者', typeAnnotation: naslCoreTypeAnn('String'), display: genDisplay(false, false, false, false), generationRule: 'auto', }) } } return undefined; } // UpdatedBy function maybeUpdatedByProp(ub: AnnArg | undefined): nasl.EntityProperty | undefined { if (ub && isAnnotation(ub.val)) { let aav = ub.val; const shouldGenerate = aav?.args?.length === 0 || forceToBoolean(aav?.args?.[0]?.val) === true // @updatedBy(true) if (shouldGenerate) { return new nasl.EntityProperty({ name: 'updatedBy', columnName: 'updated_by', label: '更新者', description: '更新者', typeAnnotation: naslCoreTypeAnn('String'), display: genDisplay(false, false, false, false), generationRule: 'auto', }) } } return undefined; } // Id function maybeIdProp(id: AnnArg | undefined): nasl.EntityProperty | undefined { if (id && !id.name && isAnnotation(id.val)) { let aav = id.val; const shouldGenerate = aav?.args?.length === 0 || forceToBoolean(aav?.args?.[0]?.val) === true // @id(true) if (shouldGenerate) { return new nasl.EntityProperty({ name: 'id', columnName: "id", label: "主键", description: "主键", primaryKey: true, // 主键默认在哪都不展示 display: genDisplay(false, false, false, false), generationRule: 'auto' }) } } return undefined; } // const extractBooleanArg0FromAnnArg(aa : AnnArg): boolean => forceToBoolean((aa.val as Annotation).args[0].val) const isValidEntityAnnArg = (aa: AnnArg): boolean => { let isNameValid = aa && supportedEntityAnnArgs.includes(aa.name); // (@SomeAnnotation) 也是合法的,这里省略了 name = 部分,所以 aa.name 为空,aa.val 又是一个 annotation if (!aa.name && isCustomAnnotation(aa.val)) { let aav = aa.val; if (['createdTime', 'updatedTime', 'createdBy', 'updatedBy', 'id'].includes(aav?.name?.name)) { // @SomeAnnotation 和 @SomeAnnotation(true) 和 @SomeAnnotation(false) return !(aav.args?.[0]) || isBooleanLit(aav.args?.[0].val) } } return isNameValid; } const isValidColumnAnnArg = (aa: AnnArg): boolean => { let isNameValid = aa && supportedColumnAnnArgs.includes(aa.name); if (!aa.name && isCustomAnnotation(aa.val)) { let aav = aa.val; if (['required', 'primaryKey'].includes(aav?.name?.name)) { return !(aav.args?.[0]) || isBooleanLit(aav.args?.[0].val); } } return isNameValid; } const isValidEntityAnn = (ann : Annotation): boolean => isCustomAnnotation(ann) ? ann.name.name === ENTITY && ann.args.every(isValidEntityAnnArg) : ann.args.every(isValidEntityAnnArg); const isValidColumnAnn = (ann : Annotation): boolean => isCustomAnnotation(ann) ? ann.name.name === COLUMN && ann.args.every(isValidColumnAnnArg) : ann.args.every(isValidColumnAnnArg); const genDisplay = (inDetail: boolean, inFilter: boolean, inForm: boolean, inTable: boolean) => { return { 'inDetail': inDetail, 'inFilter': inFilter, 'inForm': inForm, 'inTable': inTable, } } const naslCoreTypeAnn = (n: string) => new nasl.TypeAnnotation({ "concept": "TypeAnnotation", "typeKind": "primitive", "typeNamespace": NASL_CORE_NL, "typeName": n, "typeArguments": null, "returnType": null, "properties": null });