Files
seclusion/plop/utils/field-parser.ts
charilezhou 473c2c1510 feat: 添加 plop 代码生成器模板
添加组件和模块的代码生成器模板,提高开发效率。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 14:08:56 +08:00

382 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 字段 DSL 解析器
*
* DSL 语法:
* 字段名:类型[修饰符] 标签 "示例值" [验证规则...]
*
* 示例:
* title:string 标题 "示例标题" min:2 max:100
* email:string! 邮箱 "test@example.com" email
* description:string? 描述 "描述内容" max:500
* price:number 价格 "99.99" min:0
* status:enum(draft,published) 状态 "draft"
*/
export type FieldType =
| 'string'
| 'number'
| 'boolean'
| 'date'
| 'datetime'
| 'enum';
export interface Validation {
type: 'min' | 'max' | 'email' | 'url' | 'pattern';
value?: string | number;
}
export interface FieldDefinition {
name: string; // 字段名
type: FieldType; // 字段类型
label: string; // 中文标签
example: string; // 示例值
nullable: boolean; // 是否可空
unique: boolean; // 是否唯一
validations: Validation[]; // 验证规则
options?: string[]; // 枚举选项
flags: {
noCreate: boolean; // 不在创建时使用
noUpdate: boolean; // 不在更新时使用
noTable: boolean; // 不在表格中显示
};
}
// 匹配: 字段名:类型[修饰符] 标签 "示例值" [验证规则...]
const FIELD_REGEX =
/^(\w+):(\w+(?:\([^)]+\))?)([\?!]*)\s+(.+?)\s+"([^"]+)"(?:\s+(.+))?$/;
/**
* 解析字段 DSL 字符串
*/
export function parseFields(dsl: string): FieldDefinition[] {
const lines = dsl
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
return lines.map((line) => parseFieldLine(line));
}
/**
* 解析单行字段定义
*/
function parseFieldLine(line: string): FieldDefinition {
const match = line.match(FIELD_REGEX);
if (!match) {
throw new Error(`无效的字段定义: ${line}`);
}
const [, name, typeStr, modifiers, label, example, validationsStr] = match;
// 解析类型
let type: FieldType;
let options: string[] | undefined;
if (typeStr.startsWith('enum(')) {
type = 'enum';
options = typeStr
.slice(5, -1)
.split(',')
.map((s) => s.trim());
} else {
type = typeStr as FieldType;
}
// 解析修饰符
const nullable = modifiers.includes('?');
const unique = modifiers.includes('!');
// 解析验证规则和标志
const validations: Validation[] = [];
const flags = { noCreate: false, noUpdate: false, noTable: false };
if (validationsStr) {
const parts = validationsStr.split(/\s+/);
for (const part of parts) {
if (part === 'noCreate') {
flags.noCreate = true;
} else if (part === 'noUpdate') {
flags.noUpdate = true;
} else if (part === 'noTable') {
flags.noTable = true;
} else if (part.startsWith('min:')) {
validations.push({ type: 'min', value: parseFloat(part.slice(4)) });
} else if (part.startsWith('max:')) {
validations.push({ type: 'max', value: parseFloat(part.slice(4)) });
} else if (part === 'email') {
validations.push({ type: 'email' });
} else if (part === 'url') {
validations.push({ type: 'url' });
} else if (part.startsWith('pattern:')) {
validations.push({ type: 'pattern', value: part.slice(8) });
}
}
}
return {
name,
type,
label,
example,
nullable,
unique,
validations,
options,
flags,
};
}
/**
* 获取 TypeScript 类型
*/
export function getTsType(field: FieldDefinition): string {
const typeMap: Record<FieldType, string> = {
string: 'string',
number: 'number',
boolean: 'boolean',
date: 'Date',
datetime: 'Date',
enum: field.options?.map((o) => `'${o}'`).join(' | ') || 'string',
};
return typeMap[field.type] || 'string';
}
/**
* 获取 TypeScript 响应类型Date 转 string
*/
export function getTsResponseType(field: FieldDefinition): string {
if (field.type === 'date' || field.type === 'datetime') {
return 'string';
}
return getTsType(field);
}
/**
* 获取 Prisma 类型
*/
export function getPrismaType(field: FieldDefinition): string {
const typeMap: Record<FieldType, string> = {
string: 'String',
number: 'Float',
boolean: 'Boolean',
date: 'DateTime',
datetime: 'DateTime',
enum: 'String',
};
const baseType = typeMap[field.type] || 'String';
return field.nullable ? `${baseType}?` : baseType;
}
/**
* 获取验证装饰器列表
*/
export function getValidationDecorators(field: FieldDefinition): string[] {
const decorators: string[] = [];
// 类型验证
switch (field.type) {
case 'string':
decorators.push('@IsString()');
break;
case 'number':
decorators.push('@IsNumber()');
break;
case 'boolean':
decorators.push('@IsBoolean()');
break;
case 'date':
case 'datetime':
decorators.push('@IsDate()');
decorators.push('@Type(() => Date)');
break;
case 'enum':
decorators.push(
`@IsIn([${field.options?.map((o) => `'${o}'`).join(', ')}])`,
);
break;
}
// 验证规则
for (const v of field.validations) {
switch (v.type) {
case 'min':
if (field.type === 'string') {
decorators.push(`@MinLength(${v.value})`);
} else {
decorators.push(`@Min(${v.value})`);
}
break;
case 'max':
if (field.type === 'string') {
decorators.push(`@MaxLength(${v.value})`);
} else {
decorators.push(`@Max(${v.value})`);
}
break;
case 'email':
decorators.push('@IsEmail()');
break;
case 'url':
decorators.push('@IsUrl()');
break;
case 'pattern':
decorators.push(`@Matches(${v.value})`);
break;
}
}
return decorators;
}
/**
* 获取验证器导入列表
*/
export function getValidationImports(fields: FieldDefinition[]): string[] {
const imports = new Set<string>();
for (const field of fields) {
switch (field.type) {
case 'string':
imports.add('IsString');
break;
case 'number':
imports.add('IsNumber');
break;
case 'boolean':
imports.add('IsBoolean');
break;
case 'date':
case 'datetime':
imports.add('IsDate');
break;
case 'enum':
imports.add('IsIn');
break;
}
for (const v of field.validations) {
switch (v.type) {
case 'min':
imports.add(field.type === 'string' ? 'MinLength' : 'Min');
break;
case 'max':
imports.add(field.type === 'string' ? 'MaxLength' : 'Max');
break;
case 'email':
imports.add('IsEmail');
break;
case 'url':
imports.add('IsUrl');
break;
case 'pattern':
imports.add('Matches');
break;
}
}
if (field.nullable) {
imports.add('IsOptional');
}
}
return Array.from(imports);
}
/**
* 获取 Zod 验证字符串
*/
export function getZodValidation(field: FieldDefinition): string {
let zod = '';
switch (field.type) {
case 'string':
zod = 'z.string()';
for (const v of field.validations) {
if (v.type === 'min')
zod += `.min(${v.value}, '最少 ${v.value} 个字符')`;
if (v.type === 'max')
zod += `.max(${v.value}, '最多 ${v.value} 个字符')`;
if (v.type === 'email') zod += `.email('请输入有效的邮箱地址')`;
if (v.type === 'url') zod += `.url('请输入有效的 URL')`;
}
break;
case 'number':
zod = 'z.coerce.number()';
for (const v of field.validations) {
if (v.type === 'min') zod += `.min(${v.value}, '最小值为 ${v.value}')`;
if (v.type === 'max') zod += `.max(${v.value}, '最大值为 ${v.value}')`;
}
break;
case 'boolean':
zod = 'z.boolean()';
break;
case 'enum':
zod = `z.enum([${field.options?.map((o) => `'${o}'`).join(', ')}])`;
break;
default:
zod = 'z.string()';
}
if (field.nullable) {
zod += '.optional()';
}
return zod;
}
/**
* 生成表单控件代码
*/
export function getFormControl(field: FieldDefinition): string {
switch (field.type) {
case 'string': {
const maxLen = field.validations.find((v) => v.type === 'max')?.value;
if (maxLen && Number(maxLen) > 100) {
return `<Textarea placeholder="请输入${field.label}" {...field} />`;
}
return `<Input placeholder="请输入${field.label}" {...field} />`;
}
case 'number':
return `<Input type="number" placeholder="请输入${field.label}" {...field} onChange={e => field.onChange(e.target.valueAsNumber)} />`;
case 'boolean':
return `<Switch checked={field.value} onCheckedChange={field.onChange} />`;
case 'enum':
return `<Select onValueChange={field.onChange} defaultValue={field.value}>
<SelectTrigger>
<SelectValue placeholder="请选择${field.label}" />
</SelectTrigger>
<SelectContent>
${field.options?.map((o) => ` <SelectItem value="${o}">${o}</SelectItem>`).join('\n')}
</SelectContent>
</Select>`;
default:
return `<Input placeholder="请输入${field.label}" {...field} />`;
}
}
/**
* 生成表格单元格渲染器
*/
export function getCellRenderer(field: FieldDefinition): string | null {
switch (field.type) {
case 'boolean':
return `row.original.${field.name} ? '是' : '否'`;
case 'date':
return `formatDate(new Date(row.original.${field.name}), 'YYYY-MM-DD')`;
case 'datetime':
return `formatDate(new Date(row.original.${field.name}), 'YYYY-MM-DD HH:mm')`;
default:
return field.nullable ? `row.original.${field.name} || '-'` : null;
}
}
/**
* 生成 where 条件
*/
export function getWhereCondition(field: FieldDefinition): string {
if (field.type === 'string') {
return `{ contains: query.${field.name}, mode: 'insensitive' }`;
}
return `query.${field.name}`;
}