@lcap/nasl-parser
Version:
Take Nasl text to Nasl AST with the help of generalized parsing.
511 lines (459 loc) • 18.2 kB
text/typescript
// @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
});