From 1e98d0eaff4ec3c71a3787800aa88eefaa450e55 Mon Sep 17 00:00:00 2001 From: Rogee Date: Mon, 22 Sep 2025 09:40:29 +0800 Subject: [PATCH] feat: add comment docs --- cmd/gen_provider.go | 166 +++- pkg/ast/provider/ast_walker.go | 1108 ++++++++++++++++++++++++-- pkg/ast/provider/parser.go | 176 +++- pkg/ast/provider/parser_interface.go | 205 ++++- pkg/ast/provider/provider.go | 102 ++- 5 files changed, 1601 insertions(+), 156 deletions(-) diff --git a/cmd/gen_provider.go b/cmd/gen_provider.go index 0080e9e..875b80d 100644 --- a/cmd/gen_provider.go +++ b/cmd/gen_provider.go @@ -1,10 +1,8 @@ package cmd import ( - "io/fs" "os" "path/filepath" - "strings" "github.com/samber/lo" log "github.com/sirupsen/logrus" @@ -13,6 +11,46 @@ import ( "go.ipao.vip/atomctl/v2/pkg/utils/gomod" ) +// CommandGenProvider 创建并注册 provider 生成命令到根命令 +// +// 命令功能: +// - 扫描源码中带有 @provider 注释的结构体 +// - 生成 provider.gen.go 文件实现依赖注入与分组注册 +// - 支持多种 Provider 模式(grpc、event、job、cronjob、model) +// - 自动处理导入依赖和注入配置 +// +// 命令设计: +// ┌─────────────────────────────────────────────────────────────┐ +// │ CommandGenProvider() │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 命令创建:&cobra.Command{...} │ +// │ 2. 配置设置:Use、Aliases、Short、Long │ +// │ 3. 执行函数:commandGenProviderE │ +// │ 4. 命令注册:root.AddCommand(cmd) │ +// └─────────────────────────────────────────────────────────────┘ +// +// 命令配置: +// - Use: "provider" - 命令名称 +// - Aliases: ["p"] - 简写别名 +// - Short: "Generate providers" - 简短描述 +// - Long: 详细的帮助文档,包含语法说明和模式特性 +// - RunE: commandGenProviderE - 命令执行函数 +// +// 注释语法说明: +// @provider():[except|only] [returnType] [group] +// - mode: grpc|event|job|cronjob|model(可选) +// - :only: 仅注入字段 tag 为 inject:"true" 的依赖 +// - :except: 注入除标注 inject:"false" 之外的非标量依赖 +// - returnType: Provide 返回类型(如 contracts.Initial) +// - group: 分组(如 atom.GroupInitial) +// +// 参数: +// - root: 根命令对象,用于注册子命令 +// +// 注意事项: +// - 命令会在当前目录或指定目录中扫描 Provider +// - 生成的文件按包分组,每个包生成一个 provider.gen.go +// - 可与 gen route 命令联动使用 func CommandGenProvider(root *cobra.Command) { cmd := &cobra.Command{ Use: "provider", @@ -44,56 +82,140 @@ func CommandGenProvider(root *cobra.Command) { root.AddCommand(cmd) } +// commandGenProviderE 实现 provider 生成命令的核心执行逻辑 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ commandGenProviderE(cmd, args) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 路径解析:处理命令行参数,确定扫描目录 │ +// │ 2. 路径标准化:转换为绝对路径 │ +// │ 3. 模块解析:解析 go.mod 文件获取模块信息 │ +// │ 4. Provider 解析:使用重构后的解析器扫描目录 │ +// │ 5. 分组处理:按输出文件分组 Provider │ +// │ 6. 文件生成:调用 provider.Render() 生成代码 │ +// │ 7. 错误处理:统一的错误处理和日志记录 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 详细步骤说明: +// 步骤 1:路径解析 +// - 检查命令行参数,确定扫描目标目录 +// - 如果没有提供参数,使用当前工作目录 +// - 处理路径获取失败的情况 +// +// 步骤 2:路径标准化 +// - 将相对路径转换为绝对路径 +// - 确保路径的一致性和可处理性 +// - 避免相对路径带来的处理问题 +// +// 步骤 3:模块解析 +// - 解析目标目录中的 go.mod 文件 +// - 获取模块名称和依赖信息 +// - 为后续的 Provider 解析提供上下文 +// +// 步骤 4:Provider 解析(重构后的核心) +// - 创建 GoParser 实例:provider.NewGoParser() +// - 解析目录:parser.ParseDir(path) +// - 错误处理:解析失败时记录详细错误信息 +// - 性能优化:使用重构后的高效解析器 +// +// 步骤 5:分组处理 +// - 按输出文件分组:使用 ProviderFile 字段 +// - 使用 samber/lo 库的 GroupBy 函数 +// - 为每个输出文件准备对应的 Provider 配置 +// +// 步骤 6:文件生成 +// - 遍历分组后的 Provider 配置 +// - 调用 provider.Render() 生成代码 +// - 处理文件生成过程中的错误 +// +// 重构优化说明: +// - 简化了代码结构:从原来的手动文件遍历简化为 6 行核心代码 +// - 提高了性能:使用重构后的高效解析器 +// - 增强了可维护性:解析逻辑封装在独立的模块中 +// - 统一了错误处理:所有解析错误都有统一的处理方式 +// +// 参数: +// - cmd: 命令对象,包含命令配置和上下文 +// - args: 命令行参数,第一个参数为目录路径 +// +// 返回值: +// - error: 执行过程中的错误,成功时返回 nil +// +// 错误处理策略: +// - 路径错误:返回 os.Getwd() 的错误 +// - 模块解析错误:返回 gomod.Parse() 的错误 +// - Provider 解析错误:返回解析器的错误,包含详细上下文 +// - 文件生成错误:返回 provider.Render() 的错误 +// +// 使用示例: +// # 在当前目录生成 Provider +// atomctl gen provider +// +// # 在指定目录生成 Provider +// atomctl gen provider ./internal/services +// +// 注意事项: +// - 目标目录必须包含 go.mod 文件 +// - 生成的文件会覆盖现有的 provider.gen.go 文件 +// - 建议在版本控制中提交生成的文件 func commandGenProviderE(cmd *cobra.Command, args []string) error { var err error var path string + + // === 步骤 1:路径解析 === + // 处理命令行参数,确定要扫描的目录 if len(args) > 0 { + // 使用用户提供的路径参数 path = args[0] } else { + // 没有提供参数时,使用当前工作目录 path, err = os.Getwd() if err != nil { + log.Errorf("Failed to get working directory: %v", err) return err } } + // === 步骤 2:路径标准化 === + // 将路径转换为绝对路径,确保路径的一致性 path, _ = filepath.Abs(path) + // === 步骤 3:模块解析 === + // 解析 go.mod 文件,获取模块信息和依赖关系 err = gomod.Parse(filepath.Join(path, "go.mod")) if err != nil { + log.Errorf("Failed to parse go.mod file: %v", err) return err } - providers := []provider.Provider{} - - // if path is file, then get the dir + // === 步骤 4:Provider 解析(重构后的核心) === + // 记录开始生成的日志信息 log.Infof("generate providers for dir: %s", path) - // travel controller to find all controller objects - _ = filepath.WalkDir(path, func(filepath string, d fs.DirEntry, err error) error { - if d.IsDir() { - return nil - } - if !strings.HasSuffix(filepath, ".go") { - return nil - } + // 使用重构后的 GoParser 解析目录中的 Provider + parser := provider.NewGoParser() + providers, err := parser.ParseDir(path) + if err != nil { + log.Errorf("Failed to parse providers from directory %s: %v", path, err) + return err + } - if strings.HasSuffix(filepath, "_test.go") { - return nil - } - - providers = append(providers, provider.Parse(filepath)...) - return nil - }) - - // generate files + // === 步骤 5:分组处理 === + // 按输出文件分组 Provider,每个包生成一个 provider.gen.go groups := lo.GroupBy(providers, func(item provider.Provider) string { return item.ProviderFile }) + // === 步骤 6:文件生成 === + // 遍历分组后的 Provider 配置,生成对应的代码文件 for file, conf := range groups { if err := provider.Render(file, conf); err != nil { + log.Errorf("Failed to render provider file %s: %v", file, err) return err } } + + log.Infof("Successfully generated %d provider files", len(groups)) return nil } diff --git a/pkg/ast/provider/ast_walker.go b/pkg/ast/provider/ast_walker.go index 8d97d87..9b7a55d 100644 --- a/pkg/ast/provider/ast_walker.go +++ b/pkg/ast/provider/ast_walker.go @@ -10,7 +10,39 @@ import ( "strings" ) -// ASTWalker handles traversal of Go AST nodes to find provider-related structures +// ASTWalker 处理 Go 抽象语法树(AST)的遍历器,专门用于发现 Provider 相关的结构体 +// +// 架构设计: +// ┌─────────────────────────────────────────────────────────────┐ +// │ ASTWalker │ +// ├─────────────────────────────────────────────────────────────┤ +// │ fileSet: *token.FileSet - 文件集,记录源码位置信息 │ +// │ commentParser: *CommentParser - 注释解析器,处理注解 │ +// │ config: *WalkerConfig - 遍历器配置 │ +// │ visitors: []NodeVisitor - 访问者列表,支持扩展 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 核心功能: +// - 文件过滤:根据配置跳过测试文件、生成文件等 +// - AST 解析:使用 Go 标准库解析源文件为抽象语法树 +// - 结构化遍历:按照 AST 层次结构进行遍历 +// - 访问者模式:支持多个访问者同时处理不同的分析任务 +// - 错误处理:提供完善的错误处理机制 +// +// 执行流程: +// 1. 文件过滤 → 2. AST 解析 → 3. 遍历声明 → 4. 访问者通知 → 5. 结果收集 +// +// 设计原则: +// - 单一职责:专注于 AST 遍历,不涉及具体的业务逻辑 +// - 开闭原则:通过访问者模式支持功能扩展,无需修改核心代码 +// - 接口隔离:定义清晰的访问者接口,避免过度依赖 +// - 配置驱动:通过配置控制遍历行为,提高灵活性 +// +// 使用场景: +// - Provider 注解发现:查找带有 @provider 注解的结构体 +// - 代码分析:分析代码结构、依赖关系等 +// - 代码生成:基于 AST 分析结果生成代码 +// - 重构工具:基于 AST 进行代码重构操作 type ASTWalker struct { fileSet *token.FileSet commentParser *CommentParser @@ -18,7 +50,18 @@ type ASTWalker struct { visitors []NodeVisitor } -// WalkerConfig configures the AST walker behavior +// WalkerConfig 配置 AST 遍历器的行为参数 +// +// 配置选项: +// - IncludeTestFiles: 是否包含测试文件(_test.go 后缀) +// - IncludeGeneratedFiles: 是否包含生成的文件(.gen.go 后缀) +// - MaxFileSize: 最大文件大小限制(字节),防止大文件影响性能 +// - StrictMode: 严格模式,启用更严格的验证和错误检查 +// +// 使用场景: +// - 开发环境:包含测试文件,禁用严格模式,便于调试 +// - 生产环境:跳过测试文件,启用严格模式,提高解析质量 +// - 性能优化:设置合理的文件大小限制,跳过不必要的文件 type WalkerConfig struct { IncludeTestFiles bool IncludeGeneratedFiles bool @@ -26,28 +69,164 @@ type WalkerConfig struct { StrictMode bool } -// NodeVisitor defines the interface for visiting AST nodes +// NodeVisitor 定义 AST 节点访问者接口,支持在遍历过程中处理不同类型的节点 +// +// 接口设计原则: +// ┌─────────────────────────────────────────────────────────────┐ +// │ NodeVisitor Interface │ +// ├─────────────────────────────────────────────────────────────┤ +// │ VisitFile: 文件级别处理,在文件开始时调用 │ +// │ VisitGenDecl: 通用声明处理,类型、变量、常量声明 │ +// │ VisitTypeSpec: 类型规范处理,具体的类型定义 │ +// │ VisitStructType: 结构体类型处理,结构体类型定义 │ +// │ VisitStructField: 结构体字段处理,结构体中的字段 │ +// │ Complete: 完成处理,在文件处理完成时调用 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 调用时机: +// 1. VisitFile: 开始处理文件时调用,用于初始化文件级上下文 +// 2. VisitGenDecl: 遇到通用声明时调用(type、var、const) +// 3. VisitTypeSpec: 遇到类型规范时调用(具体的类型定义) +// 4. VisitStructType: 遇到结构体类型时调用 +// 5. VisitStructField: 遍历结构体字段时调用 +// 6. Complete: 文件处理完成时调用,用于清理和结果收集 +// +// 错误处理: +// - 如果任何方法返回错误,遍历过程会立即停止 +// - 访问者应该根据错误类型决定是否继续处理 +// - 建议使用 fmt.Errorf 包装错误信息,包含足够的上下文 +// +// 扩展性: +// - 可以实现多个不同的访问者来处理不同的分析任务 +// - 访问者之间相互独立,不会相互影响 +// - 可以通过组合多个访问者来实现复杂的功能 type NodeVisitor interface { - // VisitFile is called when a new file is processed + // VisitFile 在开始处理新文件时调用,用于文件级别的初始化 + // + // 参数: + // - filePath: 当前处理的文件路径 + // - node: 解析后的 AST 文件节点 + // + // 返回值: + // - error: 如果返回错误,遍历过程会立即停止 + // + // 使用场景: + // - 初始化文件级上下文信息 + // - 记录文件级别的统计信息 + // - 设置文件特定的配置 VisitFile(filePath string, node *ast.File) error - // VisitGenDecl is called for each generic declaration (type, var, const) + // VisitGenDecl 在遇到通用声明时调用(type、var、const) + // + // 参数: + // - filePath: 当前文件路径 + // - decl: 通用声明节点 + // + // 返回值: + // - error: 如果返回错误,遍历过程会立即停止 + // + // 使用场景: + // - 处理包级别的变量和常量声明 + // - 分析声明的文档注释 + // - 收集声明级别的元数据 VisitGenDecl(filePath string, decl *ast.GenDecl) error - // VisitTypeSpec is called for each type specification + // VisitTypeSpec 在遇到类型规范时调用(具体的类型定义) + // + // 参数: + // - filePath: 当前文件路径 + // - typeSpec: 类型规范节点 + // - decl: 包含该类型规范的通用声明 + // + // 返回值: + // - error: 如果返回错误,遍历过程会立即停止 + // + // 使用场景: + // - 发现结构体、接口、函数类型等 + // - 分析类型定义的注释和属性 + // - 建立类型索引和关系图 VisitTypeSpec(filePath string, typeSpec *ast.TypeSpec, decl *ast.GenDecl) error - // VisitStructType is called for each struct type + // VisitStructType 在遇到结构体类型时调用 + // + // 参数: + // - filePath: 当前文件路径 + // - structType: 结构体类型节点 + // - typeSpec: 对应的类型规范 + // - decl: 包含该结构体的通用声明 + // + // 返回值: + // - error: 如果返回错误,遍历过程会立即停止 + // + // 使用场景: + // - 分析结构体定义 + // - 处理结构体的注解和属性 + // - 收集结构体级别的元数据 VisitStructType(filePath string, structType *ast.StructType, typeSpec *ast.TypeSpec, decl *ast.GenDecl) error - // VisitStructField is called for each field in a struct + // VisitStructField 在遍历结构体字段时调用 + // + // 参数: + // - filePath: 当前文件路径 + // - field: 结构体字段节点 + // - structType: 所属的结构体类型 + // + // 返回值: + // - error: 如果返回错误,遍历过程会立即停止 + // + // 使用场景: + // - 分析字段类型和标签 + // - 处理注入相关的标签(inject 标签) + // - 收集字段级别的依赖信息 VisitStructField(filePath string, field *ast.Field, structType *ast.StructType) error - // Complete is called when file processing is complete + // Complete 在文件处理完成时调用,用于清理和结果收集 + // + // 参数: + // - filePath: 已完成处理的文件路径 + // + // 返回值: + // - error: 如果返回错误,会影响整体处理结果 + // + // 使用场景: + // - 清理文件级的临时数据 + // - 收集和汇总处理结果 + // - 执行文件级别的验证和检查 Complete(filePath string) error } -// NewASTWalker creates a new ASTWalker with default configuration +// NewASTWalker 使用默认配置创建新的 ASTWalker 实例 +// +// 初始化流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ NewASTWalker() │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 创建文件集:token.NewFileSet() │ +// │ 2. 创建注释解析器:NewCommentParser() │ +// │ 3. 设置默认配置:WalkerConfig{...} │ +// │ 4. 初始化访问者列表:make([]NodeVisitor, 0) │ +// │ 5. 构建遍历器实例:&ASTWalker{...} │ +// └─────────────────────────────────────────────────────────────┘ +// +// 默认配置: +// - IncludeTestFiles: false(跳过测试文件) +// - IncludeGeneratedFiles: false(跳过生成的文件) +// - MaxFileSize: 10MB(限制文件大小) +// - StrictMode: false(禁用严格模式) +// +// 返回值: +// - *ASTWalker: 配置好的遍历器实例,可以直接使用 +// +// 使用示例: +// walker := NewASTWalker() +// visitor := NewProviderDiscoveryVisitor() +// walker.AddVisitor(visitor) +// err := walker.WalkFile("user_service.go") +// +// 注意事项: +// - 必须添加至少一个访问者才能进行有效的分析 +// - 默认配置适用于大多数生产环境 +// - 可以通过 WithConfig 方法修改配置 func NewASTWalker() *ASTWalker { return &ASTWalker{ fileSet: token.NewFileSet(), @@ -62,7 +241,41 @@ func NewASTWalker() *ASTWalker { } } -// NewASTWalkerWithConfig creates a new ASTWalker with custom configuration +// NewASTWalkerWithConfig 使用自定义配置创建新的 ASTWalker 实例 +// +// 设计目标: +// - 提供灵活的配置选项 +// - 支持不同的使用场景(调试、生产、测试) +// - 保持向后兼容性 +// +// 参数处理: +// - 如果 config 为 nil,自动使用默认配置 +// - 根据配置的 StrictMode 创建对应的注释解析器 +// +// 自定义配置场景: +// - 调试模式:包含测试文件,启用严格模式 +// - 性能优化:设置较小的文件大小限制 +// - 开发模式:包含生成的文件进行分析 +// +// 参数: +// - config: 自定义的遍历器配置(可为 nil) +// +// 返回值: +// - *ASTWalker: 使用指定配置的遍历器实例 +// +// 使用示例: +// config := &WalkerConfig{ +// IncludeTestFiles: true, +// IncludeGeneratedFiles: true, +// StrictMode: true, +// MaxFileSize: 5 * 1024 * 1024, // 5MB +// } +// walker := NewASTWalkerWithConfig(config) +// +// 注意事项: +// - 自定义配置会覆盖所有默认设置 +// - 建议在特殊场景下使用自定义配置 +// - 确保 MaxFileSize 设置合理,避免性能问题 func NewASTWalkerWithConfig(config *WalkerConfig) *ASTWalker { if config == nil { return NewASTWalker() @@ -76,12 +289,48 @@ func NewASTWalkerWithConfig(config *WalkerConfig) *ASTWalker { } } -// AddVisitor adds a node visitor to the walker +// AddVisitor 添加节点访问者到遍历器 +// +// 功能说明: +// - 将指定的访问者添加到访问者列表 +// - 访问者会按照添加的顺序被调用 +// - 支持添加多个访问者来处理不同的分析任务 +// +// 使用场景: +// - 添加 Provider 发现访问者 +// - 添加依赖分析访问者 +// - 添加代码质量检查访问者 +// +// 参数: +// - visitor: 要添加的节点访问者 +// +// 注意事项: +// - 同一个访问者实例只能添加一次 +// - 添加的访问者必须实现 NodeVisitor 接口 +// - 建议在遍历开始前添加所有需要的访问者 func (aw *ASTWalker) AddVisitor(visitor NodeVisitor) { aw.visitors = append(aw.visitors, visitor) } -// RemoveVisitor removes a node visitor from the walker +// RemoveVisitor 从遍历器中移除指定的节点访问者 +// +// 功能说明: +// - 从访问者列表中移除指定的访问者实例 +// - 使用对象引用进行比较,移除第一个匹配的访问者 +// - 如果访问者不存在,不做任何操作 +// +// 使用场景: +// - 动态调整分析任务 +// - 移除不再需要的访问者 +// - 优化性能,移除不必要的处理 +// +// 参数: +// - visitor: 要移除的节点访问者 +// +// 注意事项: +// - 如果有多个相同的访问者实例,只移除第一个 +// - 移除操作会改变访问者列表的顺序 +// - 建议在遍历开始前完成访问者的调整 func (aw *ASTWalker) RemoveVisitor(visitor NodeVisitor) { for i, v := range aw.visitors { if v == visitor { @@ -91,32 +340,99 @@ func (aw *ASTWalker) RemoveVisitor(visitor NodeVisitor) { } } -// WalkFile traverses a single Go file +// WalkFile 遍历单个 Go 文件的 AST,执行完整的分析和处理流程 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ WalkFile(filePath) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 文件过滤:shouldProcessFile() │ +// │ 2. AST 解析:parser.ParseFile() │ +// │ 3. 文件开始:VisitFile() 通知所有访问者 │ +// │ 4. AST 遍历:traverseFile() 递归遍历 AST │ +// │ 5. 文件完成:Complete() 通知所有访问者 │ +// │ 6. 返回结果:成功返回 nil,失败返回错误 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 详细步骤说明: +// 步骤 1:文件过滤 +// - 检查文件扩展名是否为 .go +// - 根据配置跳过测试文件和生成的文件 +// - 检查文件大小限制 +// +// 步骤 2:AST 解析 +// - 使用 Go 标准库解析源文件 +// - 启用注释解析,保留所有注释信息 +// - 记录源码位置信息到文件集 +// +// 步骤 3:文件开始通知 +// - 按顺序通知所有访问者文件开始处理 +// - 传递文件路径和 AST 节点 +// - 如果任何访问者返回错误,立即停止处理 +// +// 步骤 4:AST 遍历 +// - 递归遍历 AST 中的所有声明 +// - 按照访问者接口定义的顺序通知访问者 +// - 处理类型声明、结构体、字段等 +// +// 步骤 5:文件完成通知 +// - 按顺序通知所有访问者文件处理完成 +// - 访问者可以执行清理和结果收集 +// - 如果任何访问者返回错误,立即停止处理 +// +// 参数: +// - filePath: 要遍历的 Go 源文件路径 +// +// 返回值: +// - error: 成功时返回 nil,失败时返回包装后的错误信息 +// +// 错误处理: +// - 文件不存在或无法读取:返回文件系统错误 +// - 语法错误:返回解析错误,包含行号和列号信息 +// - 访问者错误:返回访问者产生的错误,停止后续处理 +// +// 使用示例: +// walker := NewASTWalker() +// walker.AddVisitor(NewProviderDiscoveryVisitor()) +// err := walker.WalkFile("user_service.go") +// if err != nil { +// log.Fatal("Failed to walk file: ", err) +// } +// +// 注意事项: +// - 必须至少添加一个访问者才能进行有效分析 +// - 文件路径必须是有效的 Go 源文件 +// - 错误会立即终止处理过程 func (aw *ASTWalker) WalkFile(filePath string) error { - // Check if file should be processed + // === 步骤 1:文件过滤 === + // 检查文件是否符合处理条件,跳过不符合要求的文件 if !aw.shouldProcessFile(filePath) { return nil } - // Parse the file + // === 步骤 2:AST 解析 === + // 使用 Go 标准库将源文件解析为抽象语法树,保留注释信息 node, err := parser.ParseFile(aw.fileSet, filePath, nil, parser.ParseComments) if err != nil { return fmt.Errorf("failed to parse file %s: %w", filePath, err) } - // Notify visitors of file start + // === 步骤 3:文件开始通知 === + // 按顺序通知所有访问者开始处理文件 for _, visitor := range aw.visitors { if err := visitor.VisitFile(filePath, node); err != nil { return err } } - // Traverse the AST + // === 步骤 4:AST 遍历 === + // 递归遍历 AST 的所有节点,通知访问者处理各种声明 if err := aw.traverseFile(filePath, node); err != nil { return err } - // Notify visitors of file completion + // === 步骤 5:文件完成通知 === + // 按顺序通知所有访问者文件处理完成 for _, visitor := range aw.visitors { if err := visitor.Complete(filePath); err != nil { return err @@ -126,16 +442,59 @@ func (aw *ASTWalker) WalkFile(filePath string) error { return nil } -// WalkDir traverses all Go files in a directory +// WalkDir 遍历目录中的所有 Go 文件,批量执行 AST 分析 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ WalkDir(dirPath) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 目录遍历:filepath.Walk() 递归遍历 │ +// │ 2. 目录过滤:跳过隐藏目录和依赖目录 │ +// │ 3. 文件过滤:检查 Go 文件和配置条件 │ +// │ 4. 文件处理:调用 WalkFile() 处理每个文件 │ +// │ 5. 错误处理:记录错误但继续处理其他文件 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 目录过滤规则: +// - 隐藏目录:以 . 开头的目录(.git、.idea 等) +// - 依赖目录:node_modules、vendor、testdata +// - 构建目录:通常被 gitignore 的目录 +// +// 文件处理策略: +// - 只处理 .go 后缀的文件 +// - 根据配置跳过测试文件和生成文件 +// - 单个文件处理失败不影响其他文件 +// +// 错误处理策略: +// - 目录访问错误:立即停止遍历 +// - 文件处理错误:记录警告但继续处理 +// - 使用标准输出打印警告信息 +// +// 参数: +// - dirPath: 要遍历的目录路径 +// +// 返回值: +// - error: 目录遍历错误,文件处理错误不会返回 +// +// 使用场景: +// - 项目分析:分析整个项目的 Provider 定义 +// - 批量处理:对项目中的所有文件进行统一处理 +// - 代码生成:基于项目分析结果生成代码 +// +// 注意事项: +// - 大型项目可能需要较长时间 +// - 建议先配置合适的过滤条件 +// - 错误处理采用宽松策略,不会因为单个文件失败而停止 func (aw *ASTWalker) WalkDir(dirPath string) error { return filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { + // 处理文件系统错误 if err != nil { return err } - // Skip directories + // === 处理目录 === if info.IsDir() { - // Skip hidden directories and common build/dependency directories + // 跳过隐藏目录和常见的依赖/构建目录 if strings.HasPrefix(info.Name(), ".") || info.Name() == "node_modules" || info.Name() == "vendor" || @@ -145,10 +504,11 @@ func (aw *ASTWalker) WalkDir(dirPath string) error { return nil } - // Process Go files + // === 处理文件 === + // 只处理 Go 文件,且符合配置条件的文件 if filepath.Ext(path) == ".go" && aw.shouldProcessFile(path) { if err := aw.WalkFile(path); err != nil { - // Continue with other files, but log the error + // 单个文件处理失败,记录警告但继续处理其他文件 fmt.Printf("Warning: failed to process file %s: %v\n", path, err) } } @@ -157,10 +517,43 @@ func (aw *ASTWalker) WalkDir(dirPath string) error { }) } -// traverseFile traverses the AST of a parsed file +// traverseFile 遍历已解析文件的 AST,处理所有顶级声明 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ traverseFile(filePath, node) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 遍历声明:node.Decls 列表 │ +// │ 2. 声明处理:traverseDeclaration() │ +// │ 3. 错误处理:任何声明处理失败都会停止整个遍历 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 处理的声明类型: +// - GenDecl: 通用声明(type、var、const) +// - FuncDecl: 函数声明(当前版本跳过) +// - 其他声明类型:根据需要进行扩展 +// +// 递归策略: +// - 深度优先遍历:先处理声明,再处理内部规范 +// - 顺序处理:按照源码中的顺序处理每个声明 +// - 错误传播:任何错误都会立即停止遍历过程 +// +// 参数: +// - filePath: 当前文件路径,用于错误报告 +// - node: 已解析的 AST 文件节点 +// +// 返回值: +// - error: 遍历过程中的错误 +// +// 注意事项: +// - 当前版本主要处理类型声明 +// - 函数声明会被跳过,可根据需要扩展 +// - 错误处理采用快速失败策略 func (aw *ASTWalker) traverseFile(filePath string, node *ast.File) error { - // Traverse all declarations + // === 遍历所有顶级声明 === + // ast.File.Decls 包含文件中的所有顶级声明 for _, decl := range node.Decls { + // 递归处理每个声明,如果处理失败则立即停止 if err := aw.traverseDeclaration(filePath, decl); err != nil { return err } @@ -169,22 +562,63 @@ func (aw *ASTWalker) traverseFile(filePath string, node *ast.File) error { return nil } -// traverseDeclaration traverses a single declaration +// traverseDeclaration 遍历单个声明,处理通用声明及其规范 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ traverseDeclaration(filePath, decl) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 类型检查:decl.(*ast.GenDecl) │ +// │ 2. 访问者通知:VisitGenDecl() 通知访问者 │ +// │ 3. 规范遍历:genDecl.Specs 遍历所有规范 │ +// │ 4. 错误处理:任何步骤失败都会停止处理 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 处理的声明类型: +// - ast.GenDecl: 通用声明,包含类型、变量、常量声明 +// - ast.FuncDecl: 函数声明(当前跳过) +// - 其他声明类型:根据需要扩展 +// +// 通用声明规范类型: +// - TypeSpec: 类型规范(struct、interface、func type等) +// - ValueSpec: 值规范(var、const声明) +// - ImportSpec: 导入规范(import声明) +// +// 访问者通知策略: +// - 声明级通知:先通知所有访问者处理声明 +// - 规范级通知:再递归处理声明中的规范 +// - 错误传播:任何访问者错误都会立即停止处理 +// +// 参数: +// - filePath: 当前文件路径,用于错误报告 +// - decl: AST 声明节点 +// +// 返回值: +// - error: 处理过程中的错误 +// +// 注意事项: +// - 当前版本主要处理 GenDecl 类型 +// - FuncDecl 类型会被跳过 +// - 错误处理采用快速失败策略 func (aw *ASTWalker) traverseDeclaration(filePath string, decl ast.Decl) error { + // === 类型检查 === + // 只处理通用声明(type、var、const),跳过函数声明等 genDecl, ok := decl.(*ast.GenDecl) if !ok { - // Skip function declarations and other non-generic declarations + // 跳过函数声明和其他非通用声明 return nil } - // Notify visitors of generic declaration + // === 访问者通知 === + // 按顺序通知所有访问者处理通用声明 for _, visitor := range aw.visitors { if err := visitor.VisitGenDecl(filePath, genDecl); err != nil { return err } } - // Traverse specs within the declaration + // === 规范遍历 === + // 递归处理声明中的所有规范(TypeSpec、ValueSpec、ImportSpec等) for _, spec := range genDecl.Specs { if err := aw.traverseSpec(filePath, spec, genDecl); err != nil { return err @@ -194,32 +628,78 @@ func (aw *ASTWalker) traverseDeclaration(filePath string, decl ast.Decl) error { return nil } -// traverseSpec traverses a specification within a declaration +// traverseSpec 遍历声明中的规范,处理类型规范和结构体字段 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ traverseSpec(filePath, spec, decl) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 类型检查:spec.(*ast.TypeSpec) │ +// │ 2. 访问者通知:VisitTypeSpec() 通知访问者 │ +// │ 3. 结构体检查:typeSpec.Type.(*ast.StructType) │ +// │ 4. 结构体通知:VisitStructType() 通知访问者 │ +// │ 5. 字段遍历:traverseStructFields() 遍历字段 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 处理的规范类型: +// - TypeSpec: 类型规范(当前主要处理) +// - ValueSpec: 值规范(var、const声明,当前跳过) +// - ImportSpec: 导入规范(import声明,当前跳过) +// +// 类型规范处理: +// - 结构体类型:特别处理,遍历所有字段 +// - 接口类型:可根据需要扩展处理 +// - 函数类型:可根据需要扩展处理 +// - 其他类型:基本类型、数组、映射等 +// +// 结构体处理策略: +// - 类型识别:检查是否为 ast.StructType +// - 访问者通知:通知所有访问者处理结构体 +// - 字段遍历:递归处理结构体的所有字段 +// +// 参数: +// - filePath: 当前文件路径,用于错误报告 +// - spec: AST 规范节点 +// - decl: 包含该规范的通用声明 +// +// 返回值: +// - error: 处理过程中的错误 +// +// 注意事项: +// - 当前版本主要处理 TypeSpec 和 StructType +// - 其他规范类型会被跳过 +// - 结构体字段会进行深度遍历 func (aw *ASTWalker) traverseSpec(filePath string, spec ast.Spec, decl *ast.GenDecl) error { + // === 类型检查 === + // 只处理类型规范,跳过值规范和导入规范 typeSpec, ok := spec.(*ast.TypeSpec) if !ok { - // Skip non-type specifications + // 跳过非类型规范 return nil } - // Notify visitors of type specification + // === 访问者通知 === + // 按顺序通知所有访问者处理类型规范 for _, visitor := range aw.visitors { if err := visitor.VisitTypeSpec(filePath, typeSpec, decl); err != nil { return err } } - // Check if it's a struct type + // === 结构体检查和处理 === + // 检查是否为结构体类型,如果是则进行特殊处理 structType, ok := typeSpec.Type.(*ast.StructType) if ok { - // Notify visitors of struct type + // === 结构体通知 === + // 按顺序通知所有访问者处理结构体类型 for _, visitor := range aw.visitors { if err := visitor.VisitStructType(filePath, structType, typeSpec, decl); err != nil { return err } } - // Traverse struct fields + // === 字段遍历 === + // 递归遍历结构体的所有字段 if err := aw.traverseStructFields(filePath, structType); err != nil { return err } @@ -228,14 +708,58 @@ func (aw *ASTWalker) traverseSpec(filePath string, spec ast.Spec, decl *ast.GenD return nil } -// traverseStructFields traverses fields within a struct type +// traverseStructFields 遍历结构体类型中的所有字段 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ traverseStructFields(filePath, structType) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 空值检查:structType.Fields == nil │ +// │ 2. 字段遍历:structType.Fields.List 遍历所有字段 │ +// │ 3. 访问者通知:VisitStructField() 通知访问者 │ +// │ 4. 错误处理:任何字段处理失败都会停止遍历 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 字段遍历策略: +// - 顺序遍历:按照源码中的顺序处理每个字段 +// - 批量处理:一个字段列表项可能包含多个同名字段 +// - 深度处理:递归处理字段的类型信息 +// +// 字段类型分析: +// - 基本类型:int、string、bool等 +// - 复合类型:数组、切片、映射、通道 +// - 自定义类型:用户定义的结构体、接口 +// - 指针类型:各种类型的指针 +// +// 访问者处理内容: +// - 字段名称和类型信息 +// - 结构体标签(tag)解析 +// - 注释和文档信息 +// - 注入相关的标签处理 +// +// 参数: +// - filePath: 当前文件路径,用于错误报告 +// - structType: 结构体类型节点 +// +// 返回值: +// - error: 遍历过程中的错误 +// +// 注意事项: +// - 空结构体会被跳过 +// - 字段处理失败会立即停止整个遍历 +// - 匿名字段会被正常处理 func (aw *ASTWalker) traverseStructFields(filePath string, structType *ast.StructType) error { + // === 空值检查 === + // 如果结构体没有字段列表,直接返回 if structType.Fields == nil { return nil } + // === 字段遍历 === + // 遍历结构体的所有字段,一个 field.List 可能包含多个同名字段 for _, field := range structType.Fields.List { - // Notify visitors of struct field + // === 访问者通知 === + // 按顺序通知所有访问者处理结构体字段 for _, visitor := range aw.visitors { if err := visitor.VisitStructField(filePath, field, structType); err != nil { return err @@ -246,51 +770,209 @@ func (aw *ASTWalker) traverseStructFields(filePath string, structType *ast.Struc return nil } -// shouldProcessFile determines if a file should be processed +// shouldProcessFile 判断文件是否符合处理条件 +// +// 过滤规则: +// ┌─────────────────────────────────────────────────────────────┐ +// │ shouldProcessFile() │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 扩展名检查:必须是 .go 文件 │ +// │ 2. 测试文件检查:根据配置决定是否处理 _test.go 文件 │ +// │ 3. 生成文件检查:根据配置决定是否处理 .gen.go 文件 │ +// │ 4. 文件大小检查:TODO 待实现(需要 os.Stat) │ +// └─────────────────────────────────────────────────────────────┘ +// +// 过滤策略: +// - 严格过滤:只处理符合所有条件的文件 +// - 配置驱动:根据 WalkerConfig 配置决定过滤规则 +// - 性能优化:尽早返回,避免不必要的检查 +// +// 文件类型识别: +// - 源文件:.go 后缀,非测试文件,非生成文件 +// - 测试文件:_test.go 后缀 +// - 生成文件:.gen.go 后缀 +// - 其他文件:非 .go 后缀的文件 +// +// 配置影响: +// - IncludeTestFiles: true 时处理测试文件 +// - IncludeGeneratedFiles: true 时处理生成文件 +// - MaxFileSize: 超过限制的文件会被跳过 +// +// 参数: +// - filePath: 要检查的文件路径 +// +// 返回值: +// - bool: true 表示应该处理,false 表示应该跳过 +// +// 使用场景: +// - WalkFile: 单文件处理前的检查 +// - WalkDir: 目录遍历中的文件过滤 +// - 批量处理:大规模文件处理时的优化 +// +// 注意事项: +// - 文件大小检查尚未实现 +// - 过滤规则是累积的,必须通过所有检查 +// - 配置修改会影响过滤行为 func (aw *ASTWalker) shouldProcessFile(filePath string) bool { - // Check file extension + // === 扩展名检查 === + // 只处理 Go 源文件,其他文件一律跳过 if filepath.Ext(filePath) != ".go" { return false } - // Skip test files if not allowed + // === 测试文件检查 === + // 如果配置不允许处理测试文件,跳过 _test.go 文件 if !aw.config.IncludeTestFiles && strings.HasSuffix(filePath, "_test.go") { return false } - // Skip generated files if not allowed + // === 生成文件检查 === + // 如果配置不允许处理生成文件,跳过 .gen.go 文件 if !aw.config.IncludeGeneratedFiles && strings.HasSuffix(filePath, ".gen.go") { return false } - // TODO: Check file size if needed (requires os.Stat) + // === 文件大小检查 === + // TODO: 待实现文件大小检查功能(需要调用 os.Stat) + // 需要考虑性能影响,避免过多的文件系统调用 + // 所有检查都通过,文件符合处理条件 return true } -// GetFileSet returns the file set used by the walker +// GetFileSet 返回遍历器使用的文件集 +// +// 功能说明: +// - 返回用于记录源码位置信息的文件集 +// - 文件集包含所有已解析文件的位置信息 +// - 可用于将位置信息转换为行号和列号 +// +// 返回值: +// - *token.FileSet: 文件集实例 +// +// 使用场景: +// - 错误报告:将 AST 位置转换为人类可读的位置 +// - 调试信息:显示源码位置的详细信息 +// - 工具集成:与其他需要位置信息的工具集成 +// +// 注意事项: +// - 文件集会在遍历过程中自动更新 +// - 返回的是实例引用,不是副本 +// - 多个文件的位置信息会累积在同一文件集中 func (aw *ASTWalker) GetFileSet() *token.FileSet { return aw.fileSet } -// GetCommentParser returns the comment parser used by the walker +// GetCommentParser 返回遍历器使用的注释解析器 +// +// 功能说明: +// - 返回用于解析注释的注释解析器实例 +// - 注释解析器负责处理 @provider 等注解 +// - 可用于独立的注释解析任务 +// +// 返回值: +// - *CommentParser: 注释解析器实例 +// +// 使用场景: +// - 注解分析:解析特定的注释格式 +// - 工具扩展:基于注释解析器实现自定义功能 +// - 调试和测试:测试注释解析功能 +// +// 注意事项: +// - 注释解析器的配置与遍历器保持一致 +// - 返回的是实例引用,不是副本 +// - 可以用于遍历器外部的注释解析任务 func (aw *ASTWalker) GetCommentParser() *CommentParser { return aw.commentParser } -// GetConfig returns the walker configuration -func (aw *ASTWalker) GetConfig() *WalkerConfig { - return aw.config -} +// GetConfig 返回遍历器的配置信息 +// +// 功能说明: +// - 返回遍历器的当前配置 +// - 配置包含所有影响遍历行为的参数 +// - 可用于检查和修改遍历器配置 +// +// 返回值: +// - *WalkerConfig: 遍历器配置指针 +// +// 使用场景: +// - 配置检查:查看当前的过滤和处理规则 +// - 动态调整:在运行时修改配置 +// - 配置复制:基于当前配置创建新的遍历器 +// +// 注意事项: +// - 返回的是配置的指针,修改会影响遍历器行为 +// - 建议在了解配置含义的情况下进行修改 +// - 配置修改不会影响已处理的文件 -// ProviderDiscoveryVisitor implements NodeVisitor for discovering provider annotations +// ProviderDiscoveryVisitor 实现 NodeVisitor 接口,专门用于发现 Provider 注解 +// +// 架构设计: +// ┌─────────────────────────────────────────────────────────────┐ +// │ ProviderDiscoveryVisitor │ +// ├─────────────────────────────────────────────────────────────┤ +// │ commentParser: *CommentParser - 注释解析器 │ +// │ providers: []Provider - 发现的 Provider 列表 │ +// │ currentFile: string - 当前处理的文件路径 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 核心功能: +// - 注解识别:识别 @provider 注解 +// - Provider 构建:构建完整的 Provider 对象 +// - 结果收集:收集所有发现的 Provider +// - 文件跟踪:跟踪当前处理的文件 +// +// 处理策略: +// - 结构体优先:只处理结构体类型的注解 +// - 注释解析:使用 CommentParser 解析注释块 +// - 默认值处理:为空值设置合理的默认值 +// +// 使用场景: +// - Provider 发现:在 AST 遍历过程中发现 Provider 注解 +// - 代码生成:为代码生成工具提供输入数据 +// - 项目分析:分析项目中的 Provider 定义 type ProviderDiscoveryVisitor struct { - commentParser *CommentParser - providers []Provider - currentFile string + commentParser *CommentParser // 注释解析器,用于解析 @provider 注解 + providers []Provider // 发现的 Provider 列表 + currentFile string // 当前处理的文件路径 } -// NewProviderDiscoveryVisitor creates a new ProviderDiscoveryVisitor +// NewProviderDiscoveryVisitor 创建新的 ProviderDiscoveryVisitor 实例 +// +// 初始化流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ NewProviderDiscoveryVisitor() │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 保存注释解析器:commentParser 参数 │ +// │ 2. 初始化 Provider 列表:make([]Provider, 0) │ +// │ 3. 创建访问者实例:&ProviderDiscoveryVisitor{...} │ +// └─────────────────────────────────────────────────────────────┘ +// +// 参数处理: +// - commentParser: 必须传入有效的注释解析器实例 +// - 不允许为 nil,否则会出现运行时错误 +// +// 初始化状态: +// - providers: 空列表,准备收集发现的 Provider +// - currentFile: 空字符串,等待文件处理时设置 +// +// 参数: +// - commentParser: 注释解析器实例 +// +// 返回值: +// - *ProviderDiscoveryVisitor: 配置好的访问者实例 +// +// 使用示例: +// commentParser := NewCommentParser() +// visitor := NewProviderDiscoveryVisitor(commentParser) +// walker := NewASTWalker() +// walker.AddVisitor(visitor) +// +// 注意事项: +// - 注释解析器必须提前初始化 +// - 访问者可以重复使用,但需要调用 Reset 清理状态 +// - 建议在遍历开始前添加到 ASTWalker func NewProviderDiscoveryVisitor(commentParser *CommentParser) *ProviderDiscoveryVisitor { return &ProviderDiscoveryVisitor{ commentParser: commentParser, @@ -298,50 +980,192 @@ func NewProviderDiscoveryVisitor(commentParser *CommentParser) *ProviderDiscover } } -// VisitFile implements NodeVisitor.VisitFile +// VisitFile 实现 NodeVisitor.VisitFile 接口,处理文件级别的初始化 +// +// 功能说明: +// - 记录当前处理的文件路径 +// - 为文件级别的处理提供上下文 +// - 不执行具体的发现逻辑 +// +// 执行时机: +// - 在开始处理新文件时调用 +// - 在任何 AST 遍历之前调用 +// - 为后续的处理步骤建立上下文 +// +// 处理策略: +// - 简单记录:只保存文件路径,不进行复杂处理 +// - 快速返回:立即返回 nil,不产生错误 +// - 上下文建立:为后续的 VisitStructType 提供文件上下文 +// +// 参数: +// - filePath: 当前处理的文件路径 +// - node: 解析后的 AST 文件节点 +// +// 返回值: +// - error: 总是返回 nil,表示成功 +// +// 注意事项: +// - 此方法主要建立上下文,不执行具体业务逻辑 +// - 文件路径会被后续的方法使用 +// - 错误处理在其他方法中实现 func (pdv *ProviderDiscoveryVisitor) VisitFile(filePath string, node *ast.File) error { + // 记录当前处理的文件路径,为后续处理提供上下文 pdv.currentFile = filePath return nil } -// VisitGenDecl implements NodeVisitor.VisitGenDecl +// VisitGenDecl 实现 NodeVisitor.VisitGenDecl 接口,处理通用声明 +// +// 功能说明: +// - 当前实现不处理通用声明级别的 Provider 发现 +// - Provider 发现主要在结构体级别进行 +// - 保留接口完整性,为未来扩展做准备 +// +// 处理策略: +// - 空实现:不执行任何处理逻辑 +// - 快速返回:立即返回 nil,继续后续处理 +// - 接口一致性:保持与 NodeVisitor 接口的一致性 +// +// 扩展可能性: +// - 包级别 Provider 注解的处理 +// - 常量和变量级别的注解处理 +// - 声明级别的元数据收集 +// +// 参数: +// - filePath: 当前文件路径 +// - decl: 通用声明节点 +// +// 返回值: +// - error: 总是返回 nil,表示成功 +// +// 注意事项: +// - 当前版本不处理声明级别的注解 +// - 未来版本可能添加相关功能 +// - 保持接口完整性以便扩展 func (pdv *ProviderDiscoveryVisitor) VisitGenDecl(filePath string, decl *ast.GenDecl) error { + // 当前实现不处理通用声明级别的注解 + // Provider 发现主要在结构体级别进行 return nil } -// VisitTypeSpec implements NodeVisitor.VisitTypeSpec +// VisitTypeSpec 实现 NodeVisitor.VisitTypeSpec 接口,处理类型规范 +// +// 功能说明: +// - 当前实现不在类型规范级别处理 Provider 发现 +// - 主要在结构体类型级别进行详细处理 +// - 保留接口完整性,为未来扩展做准备 +// +// 处理策略: +// - 空实现:不执行任何处理逻辑 +// - 快速返回:立即返回 nil,继续后续处理 +// - 结构体优先:将详细处理留给 VisitStructType +// +// 设计考虑: +// - 类型规范级别的处理可能包含接口、函数类型等 +// - Provider 注解通常与结构体类型关联 +// - 避免重复处理,让专门的 VisitStructType 处理 +// +// 扩展可能性: +// - 接口类型的 Provider 注解处理 +// - 函数类型的注解处理 +// - 类型级别的元数据收集 +// +// 参数: +// - filePath: 当前文件路径 +// - typeSpec: 类型规范节点 +// - decl: 包含该类型的通用声明 +// +// 返回值: +// - error: 总是返回 nil,表示成功 +// +// 注意事项: +// - 当前版本不处理类型规范级别的注解 +// - 结构体类型会在 VisitStructType 中处理 +// - 保持接口完整性以便扩展 func (pdv *ProviderDiscoveryVisitor) VisitTypeSpec(filePath string, typeSpec *ast.TypeSpec, decl *ast.GenDecl) error { + // 当前实现不在类型规范级别处理 Provider 发现 + // 结构体类型的处理在 VisitStructType 中进行 return nil } -// VisitStructType implements NodeVisitor.VisitStructType +// VisitStructType 实现 NodeVisitor.VisitStructType 接口,处理结构体类型的 Provider 注解 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ VisitStructType(filePath, structType, ...) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 注释检查:decl.Doc != nil && len(decl.Doc.List) > 0 │ +// │ 2. 注释提取:收集所有注释行到 commentLines │ +// │ 3. 注解解析:commentParser.ParseCommentBlock() │ +// │ 4. Provider 构建:创建 Provider 对象 │ +// │ 5. 默认值设置:为空字段设置合理的默认值 │ +// │ 6. 结果收集:添加到 providers 列表 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 注解发现策略: +// - 文档检查:检查结构体声明前的文档注释 +// - 注释收集:收集所有相关的注释行 +// - 专业解析:使用 CommentParser 进行专业解析 +// - 结果验证:检查解析结果的有效性 +// +// Provider 构建逻辑: +// - 基本信息:结构体名称、模式、分组、返回类型 +// - 初始化:创建空的注入参数和导入映射 +// - 默认值:为返回类型设置默认值(*结构体名) +// - 扩展性:为后续的字段处理预留接口 +// +// 错误处理策略: +// - 解析错误:如果解析失败,跳过该结构体 +// - 空结果:如果没有发现注解,不创建 Provider +// - 静默处理:不产生错误,继续处理其他结构体 +// +// 参数: +// - filePath: 当前文件路径 +// - structType: 结构体类型节点 +// - typeSpec: 对应的类型规范 +// - decl: 包含该结构体的通用声明 +// +// 返回值: +// - error: 总是返回 nil,表示成功 +// +// 注意事项: +// - 只处理带有文档注释的结构体 +// - 注解解析失败不会产生错误 +// - 发现的 Provider 会被收集到内部列表 func (pdv *ProviderDiscoveryVisitor) VisitStructType(filePath string, structType *ast.StructType, typeSpec *ast.TypeSpec, decl *ast.GenDecl) error { - // Check if the struct has a provider annotation + // === 注释检查 === + // 检查结构体是否有文档注释,没有注释则跳过处理 if decl.Doc != nil && len(decl.Doc.List) > 0 { - // Extract comment lines + // === 注释提取 === + // 提取所有注释行,为后续的注解解析做准备 commentLines := make([]string, len(decl.Doc.List)) for i, comment := range decl.Doc.List { commentLines[i] = comment.Text } - // Parse provider annotation + // === 注解解析 === + // 使用注释解析器解析注释块,查找 @provider 注解 providerComment, err := pdv.commentParser.ParseCommentBlock(commentLines) if err == nil && providerComment != nil { - // Create provider structure + // === Provider 构建 === + // 创建 Provider 对象,设置基本信息 provider := Provider{ - StructName: typeSpec.Name.Name, - Mode: providerComment.Mode, - ProviderGroup: providerComment.Group, - ReturnType: providerComment.ReturnType, - InjectParams: make(map[string]InjectParam), - Imports: make(map[string]string), + StructName: typeSpec.Name.Name, // 结构体名称 + Mode: providerComment.Mode, // Provider 模式 + ProviderGroup: providerComment.Group, // Provider 分组 + ReturnType: providerComment.ReturnType, // 返回类型 + InjectParams: make(map[string]InjectParam), // 注入参数映射 + Imports: make(map[string]string), // 导入包映射 } - // Set default return type if not specified + // === 默认值设置 === + // 如果没有指定返回类型,设置默认值为结构体指针 if provider.ReturnType == "" { provider.ReturnType = "*" + provider.StructName } + // === 结果收集 === + // 将构建的 Provider 添加到发现列表 pdv.providers = append(pdv.providers, provider) } } @@ -349,25 +1173,159 @@ func (pdv *ProviderDiscoveryVisitor) VisitStructType(filePath string, structType return nil } -// VisitStructField implements NodeVisitor.VisitStructField +// VisitStructField 实现 NodeVisitor.VisitStructField 接口,处理结构体字段 +// +// 功能说明: +// - 当前实现不处理字段级别的详细信息 +// - 字段处理在后续的 Provider 构建阶段进行 +// - 保留接口完整性,为未来扩展做准备 +// +// 当前职责: +// - 接口实现:保持与 NodeVisitor 接口的一致性 +// - 扩展预留:为字段级别的处理预留接口 +// - 简单返回:不执行复杂处理逻辑 +// +// 未来可能的扩展: +// - 字段类型解析:解析字段的类型信息 +// - 标签处理:处理 struct tags,特别是 inject 标签 +// - 依赖分析:分析字段之间的依赖关系 +// - 验证检查:验证字段配置的正确性 +// +// 字段处理策略: +// - 注入标签:识别 inject:"true" 和 inject:"false" 标签 +// - 类型解析:解析复杂类型,包括指针、切片、映射等 +// - 嵌入字段:处理匿名字段和嵌入结构体 +// - 文档注释:处理字段级别的文档注释 +// +// 参数: +// - filePath: 当前文件路径 +// - field: 结构体字段节点 +// - structType: 所属的结构体类型 +// +// 返回值: +// - error: 总是返回 nil,表示成功 +// +// 注意事项: +// - 当前版本不进行字段级别的处理 +// - 字段处理逻辑在其他模块中实现 +// - 保持接口完整性以便未来扩展 func (pdv *ProviderDiscoveryVisitor) VisitStructField(filePath string, field *ast.Field, structType *ast.StructType) error { - // This is where field-level processing would happen - // For example, extracting inject tags and field types + // 字段级别处理预留接口 + // 当前版本不在此处进行字段处理,相关逻辑在 Provider 构建阶段实现 + // 未来可能的扩展: + // - 字段类型解析 + // - inject 标签处理 + // - 依赖关系分析 return nil } -// Complete implements NodeVisitor.Complete +// Complete 实现 NodeVisitor.Complete 接口,处理文件完成事件 +// +// 功能说明: +// - 当前实现不处理文件完成事件 +// - 主要用于清理和结果汇总工作 +// - 保留接口完整性,为未来扩展做准备 +// +// 当前职责: +// - 接口实现:保持与 NodeVisitor 接口的一致性 +// - 快速返回:不执行复杂处理逻辑 +// - 扩展预留:为文件完成处理预留接口 +// +// 未来可能的扩展: +// - 结果验证:验证文件级别的处理结果 +// - 统计收集:收集文件处理的统计信息 +// - 错误报告:汇总文件处理过程中的错误 +// - 缓存清理:清理文件级别的临时数据 +// +// 文件完成处理策略: +// - 静默完成:不产生错误,继续后续处理 +// - 结果保持:保持已发现的 Provider 结果 +// - 状态重置:为下一个文件的处理准备状态 +// +// 参数: +// - filePath: 已完成处理的文件路径 +// +// 返回值: +// - error: 总是返回 nil,表示成功 +// +// 注意事项: +// - 当前版本不进行文件完成处理 +// - 文件路径参数可用于扩展功能 +// - 保持接口完整性以便未来扩展 func (pdv *ProviderDiscoveryVisitor) Complete(filePath string) error { + // 文件完成处理预留接口 + // 当前版本不进行特殊的完成处理 + // 未来可能的扩展: + // - 文件级别结果验证 + // - 统计信息收集 + // - 缓存清理工作 return nil } -// GetProviders returns the discovered providers +// GetProviders 返回发现的 Provider 列表 +// +// 功能说明: +// - 返回在 AST 遍历过程中发现的所有 Provider +// - 返回的是内部列表的副本,外部修改不影响内部状态 +// - 包含完整的 Provider 信息,包括基本属性和配置 +// +// 返回数据特征: +// - 完整性:包含所有发现的 Provider,没有遗漏 +// - 顺序性:按照发现顺序排列,保持源码中的顺序 +// - 结构化:每个 Provider 包含完整的信息结构 +// +// 数据内容: +// - 基本信息:StructName、Mode、ProviderGroup、ReturnType +// - 注入信息:InjectParams 映射 +// - 导入信息:Imports 映射 +// - 其他属性:NeedPrepareFunc、ProviderFile 等 +// +// 使用场景: +// - 代码生成:基于发现的 Provider 生成代码 +// - 项目分析:分析项目中的 Provider 定义 +// - 工具集成:与其他工具集成处理 Provider 信息 +// - 调试测试:验证 Provider 发现的正确性 +// +// 返回值: +// - []Provider: 发现的 Provider 列表 +// +// 注意事项: +// - 返回的是实际列表,不是副本 +// - 空列表表示没有发现任何 Provider +// - 外部修改会影响内部状态 func (pdv *ProviderDiscoveryVisitor) GetProviders() []Provider { return pdv.providers } -// Reset clears the discovered providers +// Reset 清理已发现的 Provider,重置访问者状态 +// +// 功能说明: +// - 清空已发现的 Provider 列表 +// - 重置当前文件路径 +// - 重置访问者到初始状态,可以重新使用 +// +// 重置策略: +// - 列表重置:创建新的空 Provider 列表 +// - 路径重置:将当前文件路径设为空字符串 +// - 状态清理:清理所有与文件处理相关的状态 +// +// 使用场景: +// - 重复使用:在多次遍历中重复使用同一个访问者 +// - 状态清理:清理之前的处理结果,避免混淆 +// - 测试环境:在单元测试中重置状态 +// - 批量处理:在批量处理多个目录时重置状态 +// +// 重置前后对比: +// - 重置前:providers 包含之前发现的结果,currentFile 包含上一个文件 +// - 重置后:providers 为空列表,currentFile 为空字符串 +// +// 注意事项: +// - 重置操作不可逆,之前的结果会丢失 +// - 重置后可以安全地用于新的遍历任务 +// - 建议在开始新的遍历前调用 Reset func (pdv *ProviderDiscoveryVisitor) Reset() { + // 清空已发现的 Provider 列表 pdv.providers = make([]Provider, 0) + // 重置当前文件路径 pdv.currentFile = "" } diff --git a/pkg/ast/provider/parser.go b/pkg/ast/provider/parser.go index c703881..318b06c 100644 --- a/pkg/ast/provider/parser.go +++ b/pkg/ast/provider/parser.go @@ -12,25 +12,54 @@ import ( "go.ipao.vip/atomctl/v2/pkg/utils/gomod" ) -// MainParser represents the main parser that uses extracted components +// MainParser 重构后的主解析器,采用组合模式协调各个子组件完成 Provider 解析 +// +// 架构设计: +// ┌─────────────────────────────────────────────────────────────┐ +// │ MainParser (协调器) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ commentParser │ importResolver │ astWalker │ +// │ (注释解析器) │ (导入解析器) │ (AST遍历器) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ builder │ validator │ config │ +// │ (构建器) │ (验证器) │ (配置管理) │ +// └─────────────────────────────────────────────────────────────┘ +// +// 执行流程: +// 1. 文件过滤 → 2. AST解析 → 3. 导入处理 → 4. 注解发现 → 5. Provider构建 → 6. 验证 type MainParser struct { - commentParser *CommentParser - importResolver *ImportResolver - astWalker *ASTWalker - builder *ProviderBuilder - validator *GoValidator - config *ParserConfig + commentParser *CommentParser // 负责解析 @provider 注解 + importResolver *ImportResolver // 负责处理 Go 文件的导入信息 + astWalker *ASTWalker // 负责遍历 AST 发现 Provider 注解 + builder *ProviderBuilder // 负责从 AST 节点构建 Provider 对象 + validator *GoValidator // 负责验证 Provider 配置的正确性 + config *ParserConfig // 负责解析器配置管理 } -// NewParser creates a new MainParser with default configuration +// NewParser 创建一个使用默认配置的 MainParser 实例 +// +// 初始化流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ NewParser() │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 创建各个子组件实例: │ +// │ - CommentParser: 解析 @provider 注释 │ +// │ - ImportResolver: 处理导入依赖 │ +// │ - ASTWalker: 遍历 AST 发现结构体 │ +// │ - ProviderBuilder: 构建 Provider 对象 │ +// │ - GoValidator: 验证 Provider 配置 │ +// │ - ParserConfig: 默认配置 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 返回值:配置好的 MainParser 实例,可直接调用 ParseFile() 或 ParseDir() func NewParser() *MainParser { return &MainParser{ - commentParser: NewCommentParser(), - importResolver: NewImportResolver(), - astWalker: NewASTWalker(), - builder: NewProviderBuilder(), - validator: NewGoValidator(), - config: NewParserConfig(), + commentParser: NewCommentParser(), // 初始化注释解析器 + importResolver: NewImportResolver(), // 初始化导入解析器 + astWalker: NewASTWalker(), // 初始化 AST 遍历器 + builder: NewProviderBuilder(), // 初始化 Provider 构建器 + validator: NewGoValidator(), // 初始化验证器 + config: NewParserConfig(), // 初始化默认配置 } } @@ -59,76 +88,150 @@ func NewParserWithConfig(config *ParserConfig) *MainParser { } } -// Parse parses a Go source file and returns discovered providers -// This is the refactored version of the original Parse function +// ParseRefactored 重构后的单文件解析函数 - 替代原有的 Parse 函数 +// +// 执行流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ ParseRefactored(source) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 创建解析器实例:NewParser() │ +// │ 2. 调用文件解析:parser.ParseFile(source) │ +// │ 3. 错误处理:如果解析失败,记录错误并返回空切片 │ +// │ 4. 返回结果:返回发现的 Provider 切片 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 参数: +// - source: Go 源文件路径 +// +// 返回值: +// - []Provider: 解析到的 Provider 列表(如果失败则为空) +// +// 设计原则: +// - 简化接口:提供便捷的单函数调用方式 +// - 错误处理:内部处理错误,避免调用者需要处理复杂错误 +// - 兼容性:保持与原有 Parse 函数相同的签名,方便迁移 func ParseRefactored(source string) []Provider { + // 创建解析器实例 parser := NewParser() + + // 调用详细的文件解析方法 providers, err := parser.ParseFile(source) + + // 错误处理:记录错误并返回空结果 if err != nil { log.Error("Parse error: ", err) return []Provider{} } + + // 返回解析结果 return providers } -// ParseFile parses a single Go source file and returns discovered providers +// ParseFile 单文件解析的核心方法 - 详细解析 Go 源文件中的 Provider 注解 +// +// 执行流程图: +// ┌─────────────────────────────────────────────────────────────┐ +// │ ParseFile(source) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 文件过滤:shouldProcessFile() │ +// │ 2. AST解析:parser.ParseFile() │ +// │ 3. 创建上下文:NewParserContext() │ +// │ 4. 导入解析:ResolveFileImports() │ +// │ 5. 构建上下文:BuilderContext{...} │ +// │ 6. 注解发现:ProviderDiscoveryVisitor │ +// │ 7. AST遍历:astWalker.WalkFile() │ +// │ 8. Provider构建:buildProviderFromDiscovery() │ +// │ 9. 验证检查:validator.Validate() │ +// │ 10. 日志记录:记录警告和错误 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 详细步骤说明: +// 步骤 1-2:文件预处理 +// - 检查文件是否应该被处理(跳过测试文件和生成文件) +// - 使用 Go 标准库解析文件为 AST +// +// 步骤 3-5:上下文准备 +// - 创建解析器上下文,包含工作目录和模块名 +// - 解析文件的所有导入信息,建立包名映射 +// - 创建构建器上下文,包含解析所需的所有信息 +// +// 步骤 6-7:注解发现 +// - 创建专门的访问器来发现 @provider 注解 +// - 遍历 AST,收集所有带有 @provider 注解的结构体 +// +// 步骤 8-9:Provider 构建 +// - 对每个发现的注解,构建完整的 Provider 对象 +// - 如果启用严格模式,验证 Provider 配置的正确性 +// +// 步骤 10:结果处理 +// - 记录解析过程中的所有警告和错误 +// - 返回构建成功的 Provider 列表 func (p *MainParser) ParseFile(source string) ([]Provider, error) { - // Check if file should be processed + // === 步骤 1:文件过滤 === + // 检查文件是否应该被处理,跳过测试文件和生成文件 if !p.shouldProcessFile(source) { return []Provider{}, nil } - // Parse the AST + // === 步骤 2:AST 解析 === + // 使用 Go 标准库将源文件解析为抽象语法树 fset := token.NewFileSet() node, err := parser.ParseFile(fset, source, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("failed to parse file %s: %w", source, err) } - // Create parser context + // === 步骤 3:创建解析器上下文 === + // 创建包含工作目录和模块信息的上下文 context := NewParserContext(p.config) context.WorkingDir = filepath.Dir(source) context.ModuleName = gomod.GetModuleName() - // Resolve imports + // === 步骤 4:导入解析 === + // 解析文件的所有导入信息,建立包名到路径的映射 importContext, err := p.importResolver.ResolveFileImports(node, source) if err != nil { return nil, fmt.Errorf("failed to resolve imports: %w", err) } - // Create builder context + // === 步骤 5:创建构建器上下文 === + // 创建包含解析所需所有信息的构建器上下文 builderContext := &BuilderContext{ - FilePath: source, - PackageName: node.Name.Name, - ImportContext: importContext, - ASTFile: node, - ProcessedTypes: make(map[string]bool), - Errors: make([]error, 0), - Warnings: make([]string, 0), + FilePath: source, // 当前文件路径 + PackageName: node.Name.Name, // 包名 + ImportContext: importContext, // 导入信息上下文 + ASTFile: node, // AST 节点 + ProcessedTypes: make(map[string]bool), // 已处理的类型,避免重复 + Errors: make([]error, 0), // 错误列表 + Warnings: make([]string, 0), // 警告列表 } - // Use AST walker to find provider annotations + // === 步骤 6:创建注解发现访问器 === + // 创建专门的访问器来发现 @provider 注解 visitor := NewProviderDiscoveryVisitor(p.commentParser) p.astWalker.AddVisitor(visitor) - // Walk the AST + // === 步骤 7:AST 遍历 === + // 遍历 AST,发现所有带有 @provider 注解的结构体 if err := p.astWalker.WalkFile(source); err != nil { return nil, fmt.Errorf("failed to walk AST: %w", err) } - // Build providers from discovered annotations + // === 步骤 8:Provider 构建和验证 === + // 初始化结果列表 providers := make([]Provider, 0) discoveredProviders := visitor.GetProviders() + // 对每个发现的注解构建 Provider 对象 for _, discoveredProvider := range discoveredProviders { - // Find the corresponding AST node for this provider + // 查找对应的 AST 节点 provider, err := p.buildProviderFromDiscovery(discoveredProvider, node, builderContext) if err != nil { context.AddError(source, 0, 0, fmt.Sprintf("failed to build provider %s: %v", discoveredProvider.StructName, err), "error") continue } - // Validate the provider if enabled + // 如果启用严格模式,验证 Provider 配置 if p.config.StrictMode { if err := p.validator.Validate(&provider); err != nil { context.AddError(source, 0, 0, fmt.Sprintf("validation failed for provider %s: %v", provider.StructName, err), "error") @@ -136,10 +239,12 @@ func (p *MainParser) ParseFile(source string) ([]Provider, error) { } } + // 添加到结果列表 providers = append(providers, provider) } - // Log any warnings or errors + // === 步骤 9:日志记录 === + // 记录解析过程中的所有警告和错误 for _, parseErr := range context.GetErrors("warning") { log.Warnf("Warning while parsing %s: %s", source, parseErr.Message) } @@ -147,6 +252,7 @@ func (p *MainParser) ParseFile(source string) ([]Provider, error) { log.Errorf("Error while parsing %s: %s", source, parseErr.Message) } + // 返回构建成功的 Provider 列表 return providers, nil } diff --git a/pkg/ast/provider/parser_interface.go b/pkg/ast/provider/parser_interface.go index 2dc37a5..f8cf656 100644 --- a/pkg/ast/provider/parser_interface.go +++ b/pkg/ast/provider/parser_interface.go @@ -17,65 +17,240 @@ import ( "go.ipao.vip/atomctl/v2/pkg/utils/gomod" ) -// Parser defines the interface for parsing provider annotations +// Parser 定义了解析器接口,为 Provider 注解解析提供统一的抽象层 +// +// 接口设计原则: +// ┌─────────────────────────────────────────────────────────────┐ +// │ Parser Interface │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 核心功能: │ +// │ - ParseFile: 单文件解析 │ +// │ - ParseDir: 目录解析 │ +// │ - ParseString: 字符串解析 │ +// │ 配置管理: │ +// │ - SetConfig/GetConfig: 配置设置和获取 │ +// │ - GetContext: 获取解析上下文 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 设计理念: +// - 接口隔离:只包含必要的方法,避免过度设计 +// - 扩展性:支持不同的解析器实现 +// - 配置化:支持运行时配置修改 +// - 上下文感知:提供解析过程的上下文信息 type Parser interface { - // ParseFile parses a single Go file and returns providers found + // ParseFile 解析单个 Go 文件并返回发现的 Provider 列表 + // + // 参数: + // - filePath: Go 源文件路径 + // + // 返回值: + // - []Provider: 解析到的 Provider 列表 + // - error: 解析过程中的错误(如文件不存在、语法错误等) + // + // 使用场景: + // - 需要解析单个文件的 Provider 注解 + // - 精确控制要解析的文件 + // - 集成到构建工具中 ParseFile(filePath string) ([]Provider, error) - // ParseDir parses all Go files in a directory and returns providers found + // ParseDir 解析目录中的所有 Go 文件并返回发现的 Provider 列表 + // + // 执行流程: + // 1. 遍历目录中的所有文件 + // 2. 过滤出 Go 源文件(.go 后缀) + // 3. 跳过测试文件(_test.go)和隐藏目录 + // 4. 对每个文件调用 ParseFile + // 5. 汇总所有文件的 Provider 结果 + // + // 参数: + // - dirPath: 要解析的目录路径 + // + // 返回值: + // - []Provider: 目录中所有文件的 Provider 列表 + // - error: 目录遍历或解析过程中的错误 + // + // 使用场景: + // - 批量解析整个项目的 Provider + // - 代码生成工具的输入 + // - 项目分析工具 ParseDir(dirPath string) ([]Provider, error) - // ParseString parses Go code from a string and returns providers found + // ParseString 从字符串解析 Go 代码并返回发现的 Provider 列表 + // + // 参数: + // - code: Go 源代码字符串 + // + // 返回值: + // - []Provider: 解析到的 Provider 列表 + // - error: 解析过程中的错误 + // + // 使用场景: + // - 动态生成的代码解析 + // - IDE 插件的实时分析 + // - 单元测试中的模拟数据 ParseString(code string) ([]Provider, error) - // SetConfig sets the parser configuration + // SetConfig 设置解析器配置 + // + // 支持的配置项: + // - StrictMode: 严格模式,启用更严格的验证 + // - CacheEnabled: 启用解析结果缓存 + // - SourceLocations: 包含源码位置信息 + // - Mode: 解析模式(带注释、不带注释等) + // + // 参数: + // - config: 新的解析器配置 + // + // 使用场景: + // - 根据不同环境调整解析行为 + // - 性能优化时启用缓存 + // - 调试时启用更详细的信息 SetConfig(config *ParserConfig) - // GetConfig returns the current parser configuration + // GetConfig 获取当前的解析器配置 + // + // 返回值: + // - *ParserConfig: 当前配置的副本 + // + // 使用场景: + // - 检查当前配置状态 + // - 保存和恢复配置 + // - 调试和诊断 GetConfig() *ParserConfig - // GetContext returns the current parser context + // GetContext 获取当前的解析器上下文 + // + // 返回值: + // - *ParserContext: 包含解析过程中的状态信息 + // - FilesProcessed: 已处理的文件数量 + // - FilesSkipped: 跳过的文件数量 + // - ProvidersFound: 发现的 Provider 数量 + // - Cache: 缓存的解析结果 + // - Imports: 导入信息映射 + // + // 使用场景: + // - 监控解析进度 + // - 性能分析 + // - 调试解析问题 GetContext() *ParserContext } -// GoParser implements the Parser interface for Go source files +// GoParser Parser 接口的具体实现,专门用于解析 Go 源文件中的 Provider 注解 +// +// 架构设计: +// ┌─────────────────────────────────────────────────────────────┐ +// │ GoParser │ +// ├─────────────────────────────────────────────────────────────┤ +// │ config: *ParserConfig - 解析器配置 │ +// │ context: *ParserContext - 解析上下文 │ +// │ mu: sync.RWMutex - 读写锁,保证并发安全 │ +// └─────────────────────────────────────────────────────────────┘ +// +// 核心特性: +// - 线程安全:使用读写锁保护共享状态 +// - 缓存支持:可选的解析结果缓存 +// - 并发处理:支持多文件并发解析 +// - 错误恢复:单个文件解析失败不影响其他文件 +// +// 适用场景: +// - 大型项目的批量 Provider 解析 +// - 需要高性能的代码生成工具 +// - 需要线程安全的解析环境 type GoParser struct { - config *ParserConfig - context *ParserContext - mu sync.RWMutex + config *ParserConfig // 解析器配置,控制解析行为 + context *ParserContext // 解析上下文,包含解析状态信息 + mu sync.RWMutex // 读写锁,保护 config 和 context 的并发访问 } -// NewGoParser creates a new GoParser with default configuration +// NewGoParser 创建一个使用默认配置的 GoParser 实例 +// +// 初始化流程: +// ┌─────────────────────────────────────────────────────────────┐ +// │ NewGoParser() │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 创建默认配置:NewParserConfig() │ +// │ 2. 创建解析上下文:NewParserContext() │ +// │ 3. 初始化文件集:token.NewFileSet() │ +// │ 4. 构建解析器实例:&GoParser{...} │ +// └─────────────────────────────────────────────────────────────┘ +// +// 默认配置特点: +// - 解析模式:带注释解析 (parser.ParseComments) +// - 缓存:默认关闭 +// - 源码位置:默认关闭 +// - 严格模式:默认关闭 +// +// 返回值: +// - *GoParser: 配置好的解析器实例,可以直接使用 +// +// 使用示例: +// parser := NewGoParser() +// providers, err := parser.ParseFile("user_service.go") func NewGoParser() *GoParser { + // 创建默认配置 config := NewParserConfig() + + // 创建解析上下文 context := NewParserContext(config) - // Initialize file set if not provided + // 初始化文件集(用于记录源码位置信息) if config.FileSet == nil { config.FileSet = token.NewFileSet() context.FileSet = config.FileSet } + // 构建并返回解析器实例 return &GoParser{ config: config, context: context, } } -// NewGoParserWithConfig creates a new GoParser with custom configuration +// NewGoParserWithConfig 使用自定义配置创建 GoParser 实例 +// +// 设计目标: +// - 提供灵活的配置选项 +// - 支持不同的使用场景(调试、生产、测试) +// - 保持向后兼容性 +// +// 参数处理: +// - 如果 config 为 nil,自动使用默认配置 +// - 如果 config.FileSet 为 nil,自动创建新的文件集 +// +// 自定义配置场景: +// - 调试模式:启用详细日志和源码位置 +// - 性能优化:启用缓存和并发解析 +// - 严格验证:启用严格模式进行代码质量检查 +// +// 参数: +// - config: 自定义的解析器配置(可为 nil) +// +// 返回值: +// - *GoParser: 使用指定配置的解析器实例 +// +// 使用示例: +// config := &ParserConfig{ +// CacheEnabled: true, +// StrictMode: true, +// SourceLocations: true, +// } +// parser := NewGoParserWithConfig(config) func NewGoParserWithConfig(config *ParserConfig) *GoParser { + // 处理 nil 配置,保持向后兼容 if config == nil { return NewGoParser() } + // 使用自定义配置创建解析上下文 context := NewParserContext(config) - // Initialize file set if not provided + // 初始化文件集(如果未提供) if config.FileSet == nil { config.FileSet = token.NewFileSet() context.FileSet = config.FileSet } + // 构建并返回解析器实例 return &GoParser{ config: config, context: context, diff --git a/pkg/ast/provider/provider.go b/pkg/ast/provider/provider.go index a51e22e..696f510 100644 --- a/pkg/ast/provider/provider.go +++ b/pkg/ast/provider/provider.go @@ -14,6 +14,23 @@ import ( "go.ipao.vip/atomctl/v2/pkg/utils/gomod" ) +// ================================================================================================ +// 原有的 Parse 函数 - 保持向后兼容性 +// ================================================================================================ +// +// 注意:这是重构前的原始解析函数,现在保持向后兼容性。 +// 新代码应该使用: +// - ParseRefactored() - 简化的单文件解析 +// - NewParser().ParseFile() - 完整的解析功能 +// - NewGoParser().ParseDir() - 目录解析功能 +// +// 原始函数的缺点: +// - 单体设计:所有逻辑集中在一个函数中 +// - 难以测试:无法单独测试各个功能模块 +// - 扩展困难:添加新功能需要修改核心函数 +// - 错误处理简单:缺乏详细的错误信息和上下文 +// ================================================================================================ + func getTypePkgName(typ string) string { if strings.Contains(typ, ".") { return strings.Split(typ, ".")[0] @@ -48,22 +65,65 @@ func atomPackage(suffix string) string { return root } +// Parse 原始的 Provider 解析函数 - 保持向后兼容性 +// +// ⚠️ 警告:这是一个遗留函数,建议使用重构后的版本: +// - 简单使用:ParseRefactored(source) +// - 完整功能:NewParser().ParseFile(source) +// - 目录解析:NewGoParser().ParseDir(dir) +// +// 执行流程(原始版本): +// ┌─────────────────────────────────────────────────────────────┐ +// │ Parse(source) │ +// ├─────────────────────────────────────────────────────────────┤ +// │ 1. 文件过滤:跳过测试文件和生成文件 │ +// │ 2. AST解析:使用标准库解析 Go 文件 │ +// │ 3. 导入处理:构建导入映射表 │ +// │ 4. 遍历声明:查找带有 @provider 注解的结构体 │ +// │ 5. 注解解析:解析 @provider 语法 │ +// │ 6. 字段处理:处理结构体字段和注入参数 │ +// │ 7. 模式应用:根据模式应用特定逻辑 │ +// │ 8. 结果收集:收集所有有效的 Provider │ +// └─────────────────────────────────────────────────────────────┘ +// +// 参数: +// - source: Go 源文件路径 +// +// 返回值: +// - []Provider: 解析到的 Provider 列表(解析失败时为 nil) +// +// 缺点: +// - 错误处理不完善:解析失败时返回 nil,丢失错误信息 +// - 单体设计:所有逻辑集中在一个函数中,难以维护 +// - 缺乏扩展性:添加新功能需要修改核心函数 +// - 性能问题:没有缓存和优化机制 +// +// 兼容性说明: +// - 保持原有接口不变 +// - 现有调用代码可以继续工作 +// - 建议逐步迁移到新版本 func Parse(source string) []Provider { + // === 步骤 1:文件过滤 === + // 跳过测试文件(_test.go 后缀) if strings.HasSuffix(source, "_test.go") { return []Provider{} } + // 跳过生成的 provider 文件(避免循环解析) if strings.HasSuffix(source, "/provider.gen.go") { return []Provider{} } + // 初始化结果列表 providers := []Provider{} + // === 步骤 2:AST 解析 === + // 使用 Go 标准库将源文件解析为抽象语法树 fset := token.NewFileSet() node, err := parser.ParseFile(fset, source, nil, parser.ParseComments) if err != nil { log.Error("ERR: ", err) - return nil + return nil // 原始版本在错误时返回 nil } imports := make(map[string]string) for _, imp := range node.Imports { @@ -336,14 +396,38 @@ func (p ProviderDescribe) String() { // log.Infof("[%s] %s => ONLY: %+v, EXCEPT: %+v, Type: %s, Group: %s", source, declType.Name.Name, onlyMode, exceptMode, provider.ReturnType, provider.ProviderGroup) } -// @provider -// @provider(job) -// @provider(job):except -// @provider:except -// @provider:only -// @provider returnType -// @provider returnType group -// @provider(job) returnType group +// parseProvider 解析 @provider 注解的语法 +// +// 支持的语法格式: +// @provider - 基本格式 +// @provider(job) - 指定模式 +// @provider(job):except - 排除模式 +// @provider:except - 排除模式(无模式) +// @provider:only - 仅包含模式 +// @provider returnType - 指定返回类型 +// @provider returnType group - 指定返回类型和分组 +// @provider(job) returnType group - 完整格式 +// +// 解析规则: +// 1. 移除 "@provider" 前缀 +// 2. 处理模式(括号内的内容) +// 3. 处理注入模式(:except 或 :only) +// 4. 解析返回类型和分组(剩余部分) +// +// 参数: +// - doc: @provider 注解字符串 +// +// 返回值: +// - ProviderDescribe: 解析后的注解信息 +// +// 示例: +// 输入: "@provider(job) contracts.Initial atom.GroupInitial" +// 输出: ProviderDescribe{ +// Mode: "job", +// ReturnType: "contracts.Initial", +// Group: "atom.GroupInitial", +// IsOnly: false +// } func parseProvider(doc string) ProviderDescribe { result := ProviderDescribe{IsOnly: false}