364 lines
10 KiB
Dart
364 lines
10 KiB
Dart
import 'package:swagger_generator_flutter/core/config.dart';
|
||
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
||
import 'package:swagger_generator_flutter/core/models.dart';
|
||
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||
|
||
/// 代码生成器基类
|
||
/// 定义通用的接口和功能
|
||
abstract class BaseGenerator {
|
||
/// 生成器类型
|
||
String get generatorType;
|
||
|
||
/// 生成代码
|
||
String generate();
|
||
|
||
/// 生成文件头注释
|
||
/// [description] 文件描述
|
||
/// [fileName] 文件名(可选)
|
||
String generateFileHeader(String description, {String? fileName}) {
|
||
final header = StringUtils.generateFileHeader(
|
||
description,
|
||
SwaggerConfig.swaggerJsonUrls.isNotEmpty
|
||
? SwaggerConfig.swaggerJsonUrls.first
|
||
: '',
|
||
fileName: fileName,
|
||
fileType: description,
|
||
);
|
||
|
||
// 添加 lint 忽略注释
|
||
return '$header\n// ignore_for_file: type=lint, invalid_annotation_target\n';
|
||
}
|
||
|
||
/// 生成类型安全的代码
|
||
String generateTypeCheckedCode(String code) {
|
||
// 基础类型检查和验证
|
||
try {
|
||
if (code.trim().isEmpty) {
|
||
throw CodeGenerationException(
|
||
'生成的代码不能为空',
|
||
generatorType: generatorType,
|
||
);
|
||
}
|
||
|
||
// 确保文件以换行符结尾
|
||
if (!code.endsWith('\n')) {
|
||
return '$code\n';
|
||
}
|
||
|
||
return code;
|
||
} catch (e) {
|
||
throw CodeGenerationException(
|
||
'代码生成失败',
|
||
details: e.toString(),
|
||
generatorType: generatorType,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 验证生成的代码是否符合Dart语法规范
|
||
bool validateDartCode(String code) {
|
||
// 基础验证规则
|
||
final validationRules = [
|
||
// 检查是否有未闭合的大括号
|
||
(String code) => _countMatches(code, '{') == _countMatches(code, '}'),
|
||
// 检查是否有未闭合的小括号
|
||
(String code) => _countMatches(code, '(') == _countMatches(code, ')'),
|
||
// 检查是否有未闭合的方括号
|
||
(String code) => _countMatches(code, '[') == _countMatches(code, ']'),
|
||
// 检查是否有基本的类声明
|
||
(String code) =>
|
||
code.contains('class ') ||
|
||
code.contains('enum ') ||
|
||
code.contains('const '),
|
||
];
|
||
|
||
for (final rule in validationRules) {
|
||
if (!rule(code)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// 计算字符出现次数
|
||
int _countMatches(String text, String pattern) {
|
||
return pattern.allMatches(text).length;
|
||
}
|
||
}
|
||
|
||
/// 模型代码生成器基类
|
||
abstract class ModelGenerator extends BaseGenerator {
|
||
ModelGenerator(this.document, {this.useSimpleModels = false});
|
||
final SwaggerDocument document;
|
||
final bool useSimpleModels;
|
||
|
||
@override
|
||
String get generatorType => 'ModelGenerator';
|
||
|
||
/// 生成单个模型的代码
|
||
String generateModelCode(ApiModel model);
|
||
|
||
/// 生成枚举代码
|
||
String generateEnumCode(ApiModel model) {
|
||
if (!model.isEnum) {
|
||
throw CodeGenerationException('模型不是枚举类型', generatorType: generatorType);
|
||
}
|
||
|
||
final className = StringUtils.generateClassName(model.name);
|
||
final enumType = model.enumType?.value ?? 'string';
|
||
final valueType =
|
||
enumType == 'integer' || enumType == 'number' ? 'int' : 'String';
|
||
final buffer = StringBuffer()
|
||
// 生成文件头
|
||
..writeln(generateFileHeader('${model.name} 枚举定义'))
|
||
..writeln();
|
||
|
||
// 生成枚举类
|
||
if (model.description.isNotEmpty) {
|
||
buffer.writeln(StringUtils.generateComment(model.description));
|
||
}
|
||
|
||
buffer.writeln('enum $className {');
|
||
|
||
// 生成枚举值
|
||
for (var i = 0; i < model.enumValues.length; i++) {
|
||
final value = model.enumValues[i];
|
||
final enumName = StringUtils.generateEnumValueName(value, i);
|
||
final enumLine = enumType == 'integer' || enumType == 'number'
|
||
? ' $enumName($value),'
|
||
: " $enumName('$value'),";
|
||
|
||
buffer.writeln(enumLine);
|
||
}
|
||
|
||
// 移除最后一个逗号
|
||
final content = buffer.toString().trimRight();
|
||
buffer
|
||
..clear()
|
||
..writeAll(
|
||
[
|
||
content.substring(0, content.lastIndexOf(',')),
|
||
';',
|
||
'',
|
||
' const $className(this.value);',
|
||
' final $valueType value;',
|
||
'',
|
||
' static $className fromValue(dynamic value) {',
|
||
' for (final enumValue in $className.values) {',
|
||
' if (enumValue.value == value) {',
|
||
' return enumValue;',
|
||
' }',
|
||
' }',
|
||
r" throw ArgumentError('Unknown enum value: $value');",
|
||
' }',
|
||
'',
|
||
' factory $className.fromJson(dynamic json) {',
|
||
' return fromValue(json);',
|
||
' }',
|
||
'',
|
||
' dynamic toJson() => value;',
|
||
'',
|
||
'}',
|
||
],
|
||
'\n',
|
||
);
|
||
|
||
return generateTypeCheckedCode(buffer.toString());
|
||
}
|
||
|
||
// 已移动到 StringUtils.generateEnumValueName
|
||
|
||
/// 获取导入的类型列表
|
||
Set<String> getImportedTypes(ApiModel model) {
|
||
final importedTypes = <String>{};
|
||
|
||
model.properties.forEach((_, property) {
|
||
if (property.type == PropertyType.reference &&
|
||
property.reference != null) {
|
||
importedTypes.add(property.reference!);
|
||
} else if (property.type == PropertyType.array) {
|
||
// 处理数组类型的引用
|
||
if (property.items != null) {
|
||
final itemType = _getItemType(property.items!);
|
||
// 如果是引用类型(不是基本类型),则添加到导入列表
|
||
if (itemType != 'String' &&
|
||
itemType != 'int' &&
|
||
itemType != 'double' &&
|
||
itemType != 'bool' &&
|
||
itemType != 'dynamic') {
|
||
importedTypes.add(property.items!.name);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
return importedTypes;
|
||
}
|
||
|
||
/// 生成属性的Dart类型
|
||
String getDartPropertyType(ApiProperty property) {
|
||
switch (property.type) {
|
||
case PropertyType.string:
|
||
switch (property.format) {
|
||
case 'date':
|
||
case 'date-time':
|
||
return 'DateTime';
|
||
default:
|
||
return 'String';
|
||
}
|
||
case PropertyType.integer:
|
||
return 'int';
|
||
case PropertyType.number:
|
||
return 'double';
|
||
case PropertyType.boolean:
|
||
return 'bool';
|
||
case PropertyType.enumType:
|
||
return 'String';
|
||
case PropertyType.array:
|
||
// 根据数组元素类型推导具体类型
|
||
if (property.items != null) {
|
||
final itemType = _getItemType(property.items!);
|
||
return 'List<$itemType>';
|
||
}
|
||
return 'List<dynamic>';
|
||
case PropertyType.object:
|
||
return 'Map<String, dynamic>';
|
||
case PropertyType.reference:
|
||
return property.reference != null
|
||
? StringUtils.generateClassName(property.reference!)
|
||
: 'dynamic';
|
||
case PropertyType.file:
|
||
return 'dynamic';
|
||
case PropertyType.date:
|
||
return 'DateTime';
|
||
case PropertyType.dateTime:
|
||
return 'DateTime';
|
||
case PropertyType.unknown:
|
||
return 'dynamic';
|
||
}
|
||
}
|
||
|
||
/// 获取数组元素的类型
|
||
String _getItemType(ApiModel items) {
|
||
// 如果是引用类型,直接返回类名
|
||
if (items.name != 'string' &&
|
||
items.name != 'integer' &&
|
||
items.name != 'number' &&
|
||
items.name != 'boolean') {
|
||
return StringUtils.generateClassName(items.name);
|
||
}
|
||
|
||
// 如果是基本类型,转换为对应的Dart类型
|
||
switch (items.name) {
|
||
case 'string':
|
||
return 'String';
|
||
case 'integer':
|
||
return 'int';
|
||
case 'number':
|
||
return 'double';
|
||
case 'boolean':
|
||
return 'bool';
|
||
default:
|
||
return 'dynamic';
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 选项配置类
|
||
class GeneratorOptions {
|
||
const GeneratorOptions({
|
||
this.generateEndpoints = true,
|
||
this.generateModels = true,
|
||
this.generateDocs = true,
|
||
this.useSimpleModels = false,
|
||
this.modelsDirectory = 'models',
|
||
this.outputDirectory = 'generator',
|
||
this.endpointsFileName = 'api_paths.dart',
|
||
this.docsFileName = 'api_documentation.md',
|
||
});
|
||
|
||
/// 从命令行参数创建选项
|
||
factory GeneratorOptions.fromArgs(List<String> args) {
|
||
var generateEndpoints = false;
|
||
var generateModels = false;
|
||
var generateDocs = false;
|
||
var useSimpleModels = false;
|
||
var modelsDirectory = 'models';
|
||
var outputDirectory = 'generator';
|
||
var endpointsFileName = 'api_paths.dart';
|
||
var docsFileName = 'api_documentation.md';
|
||
|
||
var hasSpecificOption = false;
|
||
|
||
for (var i = 0; i < args.length; i++) {
|
||
final arg = args[i];
|
||
|
||
switch (arg) {
|
||
case '--endpoints':
|
||
generateEndpoints = true;
|
||
hasSpecificOption = true;
|
||
case '--models':
|
||
generateModels = true;
|
||
hasSpecificOption = true;
|
||
case '--docs':
|
||
generateDocs = true;
|
||
hasSpecificOption = true;
|
||
case '--all':
|
||
generateEndpoints = true;
|
||
generateModels = true;
|
||
generateDocs = true;
|
||
hasSpecificOption = true;
|
||
case '--simple':
|
||
useSimpleModels = true;
|
||
case '--models-dir':
|
||
if (i + 1 < args.length) {
|
||
modelsDirectory = args[i + 1];
|
||
i++; // 跳过下一个参数
|
||
}
|
||
case '--output-dir':
|
||
if (i + 1 < args.length) {
|
||
outputDirectory = args[i + 1];
|
||
i++; // 跳过下一个参数
|
||
}
|
||
case '--endpoints-file':
|
||
if (i + 1 < args.length) {
|
||
endpointsFileName = args[i + 1];
|
||
i++; // 跳过下一个参数
|
||
}
|
||
case '--docs-file':
|
||
if (i + 1 < args.length) {
|
||
docsFileName = args[i + 1];
|
||
i++; // 跳过下一个参数
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有指定特定选项,默认生成所有文件
|
||
if (!hasSpecificOption) {
|
||
generateEndpoints = true;
|
||
generateModels = true;
|
||
generateDocs = true;
|
||
}
|
||
|
||
return GeneratorOptions(
|
||
generateEndpoints: generateEndpoints,
|
||
generateModels: generateModels,
|
||
generateDocs: generateDocs,
|
||
useSimpleModels: useSimpleModels,
|
||
modelsDirectory: modelsDirectory,
|
||
outputDirectory: outputDirectory,
|
||
endpointsFileName: endpointsFileName,
|
||
docsFileName: docsFileName,
|
||
);
|
||
}
|
||
final bool generateEndpoints;
|
||
final bool generateModels;
|
||
final bool generateDocs;
|
||
final bool useSimpleModels;
|
||
final String modelsDirectory;
|
||
final String outputDirectory;
|
||
final String endpointsFileName;
|
||
final String docsFileName;
|
||
}
|