feat: 增加格式化代码

This commit is contained in:
Max 2025-07-19 08:13:59 +08:00
parent 77cf3a4a11
commit dc7c17b212
12 changed files with 132 additions and 45 deletions

76
analysis_options.yaml Normal file
View File

@ -0,0 +1,76 @@
# 1. 继承 Lint 规则集 (必选其一)
# --------------------------------------------------------------------------
# 强烈推荐!根据你的项目类型选择一个 Lint 规则集作为起点。
# 这能大大减少手动配置的工作量,并与社区最佳实践保持一致。
# Linter 规则
# https://dart.ac.cn/tools/linter-rules
# 如果是 Flutter 项目,推荐使用:
include: package:flutter_lints/flutter.yaml
# 如果是纯 Dart 项目,推荐使用:
# include: package:lints/recommended.yaml
# 2. 配置分析器 (必选)
# --------------------------------------------------------------------------
# 分析器用于检查代码的语法和潜在问题。
# 强烈建议启用所有规则,以确保代码质量和一致性。
analyzer:
errors:
require_trailing_commas: ignore
# 排除不想被分析的文件或目录。
# 对于由代码生成工具(如 json_serializable, freezed生成的 *.g.dart 文件,
# 强烈建议排除,因为你通常不需要对它们进行 Lint 检查。
exclude:
- '**/*.g.dart' # 排除所有以 .g.dart 结尾的文件
- 'lib/generated/**' # 排除 lib/generated/ 目录下的所有文件 (如果你的生成文件都在这里)
- 'build/**' # 排除 Flutter/Dart 构建输出目录
# 3. 配置 Lint 规则
# --------------------------------------------------------------------------
linter:
# 在此处启用或禁用特定的 Lint 规则。
# `include` 中的规则集已经包含了大部分常用规则,这里可以进行微调。
rules:
# 常用且推荐启用的规则 (即使默认集没有包含,也建议手动添加)
- avoid_empty_else # 避免空的 else 块
- avoid_print # 在生产代码中避免使用 print (可根据项目需求启用/禁用)
- avoid_relative_lib_imports # 避免从 'lib/' 相对导入
# - avoid_return_and_type_annotation # 避免冗余的返回类型注解
- curly_braces_in_flow_control_structures # 控制流语句强制使用大括号
- empty_catches # 避免空的 catch 块
- empty_constructor_bodies # 避免空的构造函数体
- empty_statements # 避免空的语句
- file_names # 文件名使用小写下划线命名 (my_file.dart)
- prefer_const_constructors # 尽可能使用 const 构造函数
- prefer_const_declarations # 尽可能使用 const 声明
- prefer_const_literals_to_create_immutables # 尽可能使用 const 创建不可变集合
# - prefer_single_quotes # 优先使用单引号 (或 prefer_double_quotes)
- prefer_final_fields # 类中的私有字段尽可能使用 final
- prefer_final_locals # 局部变量尽可能使用 final
# - prefer_for_elements_to_map_fromIterable # 优先使用 for 元素创建 Map
# - prefer_is_empty # 优先使用 .isEmpty
# - prefer_is_not_empty # 优先使用 .isNotEmpty
- unnecessary_new # Dart 2.0 后 new 关键字是可选的,推荐省略
- unnecessary_this # 避免不必要的 this 关键字
# - use_key_in_widget_constructors # Flutter Widget 构造函数中推荐使用 Key
# 根据项目特性考虑启用的规则 (可能需要团队讨论)
# - annotate_overrides # 推荐:覆写方法添加 @override 注解 (如果 flutter_lints 已包含则无需重复)
# - lines_longer_than_80_chars # 强制行长 80 字符 (默认是警告,但通常较严格)
# - public_member_api_docs # 推荐:为公共 API 编写文档注释 (对库项目非常重要,应用项目可酌情)
- require_trailing_commas # 强制多行参数列表使用尾随逗号 (有助于格式化)
# - sort_constructors_first # 构造函数在类中声明在前
# - sort_declarations_as_members # 类成员按字母顺序排序
# - sort_pub_dependencies # pubspec.yaml 依赖按字母排序
# 4. 格式化器配置
# --------------------------------------------------------------------------
formatter:
# 设置 `dart format` 工具的行宽。
# Dart 官方推荐 80但许多团队会使用 100 或 120 以适应现代宽屏显示器。
# 最重要的是整个团队**保持一致**。
page_width: 80

View File

@ -2,7 +2,7 @@
import 'dart:io'; import 'dart:io';
import '../lib/swagger_cli_new.dart'; import 'package:swagger_generator_flutter/swagger_cli_new.dart';
/// Swagger CLI /// Swagger CLI
/// ///

View File

@ -42,9 +42,8 @@ abstract class BaseCommand {
print('选项:'); print('选项:');
for (final option in options) { for (final option in options) {
final short = option.shortName != null ? '-${option.shortName}, ' : ''; final short = option.shortName != null ? '-${option.shortName}, ' : '';
final defaultValue = option.defaultValue != null final defaultValue =
? ' (默认: ${option.defaultValue})' option.defaultValue != null ? ' (默认: ${option.defaultValue})' : '';
: '';
print(' $short--${option.name} ${option.description}$defaultValue'); print(' $short--${option.name} ${option.description}$defaultValue');
} }
} }

View File

@ -320,7 +320,6 @@ class GenerateCommand extends BaseCommand {
// //
buffer.writeln('// API 模型导出文件'); buffer.writeln('// API 模型导出文件');
buffer.writeln('// 基于 Swagger API 文档: '); buffer.writeln('// 基于 Swagger API 文档: ');
buffer.writeln('// 自动生成于: ${DateTime.now().toString().substring(0, 10)} ');
buffer.writeln('// 由 xy_swagger_generator by max 生成'); buffer.writeln('// 由 xy_swagger_generator by max 生成');
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.'); buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
buffer.writeln(''); buffer.writeln('');

View File

@ -279,7 +279,7 @@ class GeneratorOptions {
bool generateModels = false; bool generateModels = false;
bool generateDocs = false; bool generateDocs = false;
bool useSimpleModels = false; bool useSimpleModels = false;
bool separateModelFiles = true; const bool separateModelFiles = true;
String modelsDirectory = 'models'; String modelsDirectory = 'models';
String outputDirectory = 'generator'; String outputDirectory = 'generator';
String endpointsFileName = 'api_paths.dart'; String endpointsFileName = 'api_paths.dart';

View File

@ -863,7 +863,7 @@ class RetrofitApiGenerator extends BaseGenerator {
param.name.isNotEmpty ? param.name : 'request'), param.name.isNotEmpty ? param.name : 'request'),
type: bodyType, type: bodyType,
annotation: useRetrofit ? '@Body()' : '', annotation: useRetrofit ? '@Body()' : '',
required: true, required: false,
)); ));
} }
@ -877,7 +877,7 @@ class RetrofitApiGenerator extends BaseGenerator {
name: 'request', name: 'request',
type: bodyType, type: bodyType,
annotation: useRetrofit ? '@Body()' : '', annotation: useRetrofit ? '@Body()' : '',
required: true, required: false,
)); ));
} }
@ -1010,7 +1010,7 @@ class RetrofitApiGenerator extends BaseGenerator {
buffer.writeln(' ) async {'); buffer.writeln(' ) async {');
// //
var requestPath = cleanPath; final requestPath = cleanPath;
final pathParams = final pathParams =
parameters.where((p) => p.annotation.contains('@Path')).toList(); parameters.where((p) => p.annotation.contains('@Path')).toList();
@ -1525,7 +1525,7 @@ class RetrofitApiGenerator extends BaseGenerator {
buffer.writeln(''); buffer.writeln('');
// //
buffer.writeln('@JsonSerializable(checked: true includeIfNull:false)'); buffer.writeln('@JsonSerializable(checked: true, includeIfNull: false)');
buffer.writeln('class $className {'); buffer.writeln('class $className {');
// //

View File

@ -265,8 +265,9 @@ class StringUtils {
static String formatBytes(int bytes) { static String formatBytes(int bytes) {
if (bytes < 1024) return '${bytes}B'; if (bytes < 1024) return '${bytes}B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB';
if (bytes < 1024 * 1024 * 1024) if (bytes < 1024 * 1024 * 1024) {
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB'; return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
}
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB'; return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
} }
@ -344,10 +345,9 @@ class StringUtils {
/// ///
static String generateFileHeader(String description, String source) { static String generateFileHeader(String description, String source) {
final timestamp = DateTime.now().toLocal().toString().split(" ").first; // final timestamp = DateTime.now().toLocal().toString().split(" ").first;
return '''// $description return '''// $description
// Swagger API : // Swagger API :
// : $timestamp
// xy_swagger_generator by max // xy_swagger_generator by max
// Copyright (C) 2025 YuanXuan. All rights reserved. // Copyright (C) 2025 YuanXuan. All rights reserved.
'''; ''';

View File

@ -72,7 +72,7 @@ class TypeValidator {
if (property.type == PropertyType.reference) { if (property.type == PropertyType.reference) {
if (property.reference == null || property.reference!.isEmpty) { if (property.reference == null || property.reference!.isEmpty) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'reference', field: 'reference',
message: '引用类型缺少引用目标', message: '引用类型缺少引用目标',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,
@ -217,7 +217,7 @@ class TypeValidator {
// //
if (code.trim().isEmpty) { if (code.trim().isEmpty) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'code', field: 'code',
message: '生成的代码为空', message: '生成的代码为空',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,
@ -228,7 +228,7 @@ class TypeValidator {
// //
if (!_isBalancedBrackets(code)) { if (!_isBalancedBrackets(code)) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'syntax', field: 'syntax',
message: '代码中存在不匹配的括号', message: '代码中存在不匹配的括号',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,
@ -241,7 +241,7 @@ class TypeValidator {
case CodeType.model: case CodeType.model:
if (!code.contains('class ') && !code.contains('enum ')) { if (!code.contains('class ') && !code.contains('enum ')) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'content', field: 'content',
message: '模型代码必须包含class或enum声明', message: '模型代码必须包含class或enum声明',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,
@ -252,7 +252,7 @@ class TypeValidator {
case CodeType.endpoints: case CodeType.endpoints:
if (!code.contains('class ')) { if (!code.contains('class ')) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'content', field: 'content',
message: '端点代码必须包含class声明', message: '端点代码必须包含class声明',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,
@ -288,7 +288,7 @@ class TypeValidator {
if (model.enumValues.isEmpty) { if (model.enumValues.isEmpty) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'enumValues', field: 'enumValues',
message: '枚举类型必须包含至少一个值', message: '枚举类型必须包含至少一个值',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,
@ -311,7 +311,7 @@ class TypeValidator {
final uniqueValues = model.enumValues.toSet(); final uniqueValues = model.enumValues.toSet();
if (uniqueValues.length != model.enumValues.length) { if (uniqueValues.length != model.enumValues.length) {
errors.add( errors.add(
ValidationError( const ValidationError(
field: 'enumValues', field: 'enumValues',
message: '枚举值存在重复', message: '枚举值存在重复',
severity: ErrorSeverity.error, severity: ErrorSeverity.error,

View File

@ -14,6 +14,8 @@ dependencies:
json_annotation: ^4.8.1 json_annotation: ^4.8.1
http: ^1.1.0 http: ^1.1.0
path: any
logging: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@ -23,5 +25,8 @@ dev_dependencies:
json_serializable: ^6.7.1 json_serializable: ^6.7.1
test: ^1.24.0 test: ^1.24.0
dio: any
retrofit: any
learning_officer_oa: any
flutter: flutter:
uses-material-design: true uses-material-design: true

View File

@ -40,15 +40,23 @@ main() {
case "$1" in case "$1" in
all) all)
dart run "$CLI_DART_FILE" generate --models --api --split-by-tags dart run "$CLI_DART_FILE" generate --models --api --split-by-tags
dart format .
dart fix --apply
;; ;;
models) models)
dart run "$CLI_DART_FILE" generate --models dart run "$CLI_DART_FILE" generate --models
dart format .
dart fix --apply
;; ;;
docs) docs)
dart run "$CLI_DART_FILE" generate --docs dart run "$CLI_DART_FILE" generate --docs
dart format .
dart fix --apply
;; ;;
api) api)
dart run "$CLI_DART_FILE" generate --api dart run "$CLI_DART_FILE" generate --api
dart format .
dart fix --apply
;; ;;
*) *)
echo -e "${YELLOW}未知命令: $1${NC}" echo -e "${YELLOW}未知命令: $1${NC}"

View File

@ -1,11 +1,11 @@
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../lib/core/models.dart'; import 'package:swagger_generator_flutter/core/models.dart';
void main() { void main() {
group('ApiPath', () { group('ApiPath', () {
test('creates ApiPath with required fields', () { test('creates ApiPath with required fields', () {
final path = ApiPath( const path = ApiPath(
path: '/api/users', path: '/api/users',
method: HttpMethod.get, method: HttpMethod.get,
summary: 'Get users', summary: 'Get users',
@ -29,7 +29,7 @@ void main() {
test('creates ApiPath with all fields', () { test('creates ApiPath with all fields', () {
final parameters = [ final parameters = [
ApiParameter( const ApiParameter(
name: 'id', name: 'id',
location: ParameterLocation.path, location: ParameterLocation.path,
required: true, required: true,
@ -39,7 +39,7 @@ void main() {
]; ];
final responses = { final responses = {
'200': ApiResponse( '200': const ApiResponse(
code: '200', code: '200',
description: 'Success', description: 'Success',
schema: {'type': 'object'}, schema: {'type': 'object'},
@ -47,7 +47,7 @@ void main() {
), ),
}; };
final requestBody = ApiRequestBody( const requestBody = ApiRequestBody(
description: 'User data', description: 'User data',
required: true, required: true,
content: { content: {
@ -77,7 +77,7 @@ void main() {
group('ApiParameter', () { group('ApiParameter', () {
test('creates ApiParameter with required fields', () { test('creates ApiParameter with required fields', () {
final param = ApiParameter( const param = ApiParameter(
name: 'id', name: 'id',
location: ParameterLocation.path, location: ParameterLocation.path,
required: true, required: true,
@ -93,7 +93,7 @@ void main() {
}); });
test('creates ApiParameter with optional fields', () { test('creates ApiParameter with optional fields', () {
final param = ApiParameter( const param = ApiParameter(
name: 'page', name: 'page',
location: ParameterLocation.query, location: ParameterLocation.query,
required: false, required: false,
@ -136,7 +136,7 @@ void main() {
group('ApiResponse', () { group('ApiResponse', () {
test('creates ApiResponse with required fields', () { test('creates ApiResponse with required fields', () {
final response = ApiResponse( const response = ApiResponse(
code: '200', code: '200',
description: 'Success', description: 'Success',
schema: null, schema: null,
@ -190,7 +190,7 @@ void main() {
group('ApiRequestBody', () { group('ApiRequestBody', () {
test('creates ApiRequestBody with required fields', () { test('creates ApiRequestBody with required fields', () {
final requestBody = ApiRequestBody( const requestBody = ApiRequestBody(
description: 'User data', description: 'User data',
required: true, required: true,
content: null, content: null,
@ -244,7 +244,7 @@ void main() {
group('ApiModel', () { group('ApiModel', () {
test('creates ApiModel with required fields', () { test('creates ApiModel with required fields', () {
final model = ApiModel( const model = ApiModel(
name: 'User', name: 'User',
description: 'User model', description: 'User model',
properties: {}, properties: {},
@ -262,13 +262,13 @@ void main() {
test('creates ApiModel with properties', () { test('creates ApiModel with properties', () {
final properties = { final properties = {
'id': ApiProperty( 'id': const ApiProperty(
name: 'id', name: 'id',
type: PropertyType.integer, type: PropertyType.integer,
description: 'User ID', description: 'User ID',
required: true, required: true,
), ),
'name': ApiProperty( 'name': const ApiProperty(
name: 'name', name: 'name',
type: PropertyType.string, type: PropertyType.string,
description: 'User name', description: 'User name',
@ -288,7 +288,7 @@ void main() {
}); });
test('creates enum ApiModel', () { test('creates enum ApiModel', () {
final model = ApiModel( const model = ApiModel(
name: 'UserStatus', name: 'UserStatus',
description: 'User status enum', description: 'User status enum',
properties: {}, properties: {},
@ -344,7 +344,7 @@ void main() {
group('ApiProperty', () { group('ApiProperty', () {
test('creates ApiProperty with required fields', () { test('creates ApiProperty with required fields', () {
final property = ApiProperty( const property = ApiProperty(
name: 'id', name: 'id',
type: PropertyType.integer, type: PropertyType.integer,
description: 'User ID', description: 'User ID',
@ -359,7 +359,7 @@ void main() {
}); });
test('creates ApiProperty with optional fields', () { test('creates ApiProperty with optional fields', () {
final property = ApiProperty( const property = ApiProperty(
name: 'name', name: 'name',
type: PropertyType.string, type: PropertyType.string,
description: 'User name', description: 'User name',
@ -497,7 +497,7 @@ void main() {
group('SwaggerDocument', () { group('SwaggerDocument', () {
test('creates SwaggerDocument with required fields', () { test('creates SwaggerDocument with required fields', () {
final document = SwaggerDocument( const document = SwaggerDocument(
title: 'Test API', title: 'Test API',
version: '1.0.0', version: '1.0.0',
description: 'Test API description', description: 'Test API description',
@ -578,7 +578,7 @@ void main() {
group('ApiController', () { group('ApiController', () {
test('creates ApiController with required fields', () { test('creates ApiController with required fields', () {
final controller = ApiController( const controller = ApiController(
name: 'UserController', name: 'UserController',
description: 'User management controller', description: 'User management controller',
paths: [], paths: [],
@ -591,7 +591,7 @@ void main() {
test('creates ApiController with paths', () { test('creates ApiController with paths', () {
final paths = [ final paths = [
ApiPath( const ApiPath(
path: '/api/users', path: '/api/users',
method: HttpMethod.get, method: HttpMethod.get,
summary: 'Get users', summary: 'Get users',
@ -602,7 +602,7 @@ void main() {
responses: {}, responses: {},
requestBody: null, requestBody: null,
), ),
ApiPath( const ApiPath(
path: '/api/users/{id}', path: '/api/users/{id}',
method: HttpMethod.post, method: HttpMethod.post,
summary: 'Create user', summary: 'Create user',
@ -628,7 +628,7 @@ void main() {
test('creates ApiController from paths', () { test('creates ApiController from paths', () {
final paths = [ final paths = [
ApiPath( const ApiPath(
path: '/api/users', path: '/api/users',
method: HttpMethod.get, method: HttpMethod.get,
summary: 'Get users', summary: 'Get users',

View File

@ -1,6 +1,6 @@
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../lib/utils/string_utils.dart'; import 'package:swagger_generator_flutter/utils/string_utils.dart';
void main() { void main() {
group('StringUtils', () { group('StringUtils', () {
@ -211,10 +211,10 @@ void main() {
group('formatDuration', () { group('formatDuration', () {
test('formats duration correctly', () { test('formats duration correctly', () {
expect( expect(StringUtils.formatDuration(const Duration(milliseconds: 500)),
StringUtils.formatDuration(Duration(milliseconds: 500)), '500毫秒'); '500毫秒');
expect(StringUtils.formatDuration(Duration(seconds: 1)), '1.00秒'); expect(StringUtils.formatDuration(const Duration(seconds: 1)), '1.00秒');
expect(StringUtils.formatDuration(Duration(seconds: 2)), '2.00秒'); expect(StringUtils.formatDuration(const Duration(seconds: 2)), '2.00秒');
}); });
}); });
}); });