package provider import ( "fmt" "go/ast" "go/parser" "go/token" "path/filepath" "strings" log "github.com/sirupsen/logrus" "go.ipao.vip/atomctl/v2/pkg/utils/gomod" ) // MainParser 重构后的主解析器,采用组合模式协调各个子组件完成 Provider 解析 // // 架构设计: // ┌─────────────────────────────────────────────────────────────┐ // │ MainParser (协调器) │ // ├─────────────────────────────────────────────────────────────┤ // │ commentParser │ importResolver │ astWalker │ // │ (注释解析器) │ (导入解析器) │ (AST遍历器) │ // ├─────────────────────────────────────────────────────────────┤ // │ builder │ validator │ config │ // │ (构建器) │ (验证器) │ (配置管理) │ // └─────────────────────────────────────────────────────────────┘ // // 执行流程: // 1. 文件过滤 → 2. AST解析 → 3. 导入处理 → 4. 注解发现 → 5. Provider构建 → 6. 验证 type MainParser struct { commentParser *CommentParser // 负责解析 @provider 注解 importResolver *ImportResolver // 负责处理 Go 文件的导入信息 astWalker *ASTWalker // 负责遍历 AST 发现 Provider 注解 builder *ProviderBuilder // 负责从 AST 节点构建 Provider 对象 validator *GoValidator // 负责验证 Provider 配置的正确性 config *ParserConfig // 负责解析器配置管理 } // 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(), // 初始化 AST 遍历器 builder: NewProviderBuilder(), // 初始化 Provider 构建器 validator: NewGoValidator(), // 初始化验证器 config: NewParserConfig(), // 初始化默认配置 } } // NewParserWithConfig creates a new MainParser with custom configuration func NewParserWithConfig(config *ParserConfig) *MainParser { if config == nil { return NewParser() } return &MainParser{ commentParser: NewCommentParser(), importResolver: NewImportResolver(), astWalker: NewASTWalkerWithConfig(&WalkerConfig{ StrictMode: config.StrictMode, }), builder: NewProviderBuilderWithConfig(&BuilderConfig{ EnableValidation: config.StrictMode, StrictMode: config.StrictMode, DefaultProviderMode: ProviderModeBasic, DefaultInjectionMode: InjectionModeAuto, AutoGenerateReturnTypes: true, ResolveImportDependencies: true, }), validator: NewGoValidator(), config: config, } } // 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 单文件解析的核心方法 - 详细解析 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) { // === 步骤 1:文件过滤 === // 检查文件是否应该被处理,跳过测试文件和生成文件 if !p.shouldProcessFile(source) { return []Provider{}, nil } // === 步骤 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) } // === 步骤 3:创建解析器上下文 === // 创建包含工作目录和模块信息的上下文 context := NewParserContext(p.config) context.WorkingDir = filepath.Dir(source) context.ModuleName = gomod.GetModuleName() // === 步骤 4:导入解析 === // 解析文件的所有导入信息,建立包名到路径的映射 importContext, err := p.importResolver.ResolveFileImports(node, source) if err != nil { return nil, fmt.Errorf("failed to resolve imports: %w", err) } // === 步骤 5:创建构建器上下文 === // 创建包含解析所需所有信息的构建器上下文 builderContext := &BuilderContext{ FilePath: source, // 当前文件路径 PackageName: node.Name.Name, // 包名 ImportContext: importContext, // 导入信息上下文 ASTFile: node, // AST 节点 ProcessedTypes: make(map[string]bool), // 已处理的类型,避免重复 Errors: make([]error, 0), // 错误列表 Warnings: make([]string, 0), // 警告列表 } // === 步骤 6:创建注解发现访问器 === // 创建专门的访问器来发现 @provider 注解 visitor := NewProviderDiscoveryVisitor(p.commentParser) p.astWalker.AddVisitor(visitor) // === 步骤 7:AST 遍历 === // 遍历 AST,发现所有带有 @provider 注解的结构体 if err := p.astWalker.WalkFile(source); err != nil { return nil, fmt.Errorf("failed to walk AST: %w", err) } // === 步骤 8:Provider 构建和验证 === // 初始化结果列表 providers := make([]Provider, 0) discoveredProviders := visitor.GetProviders() // 对每个发现的注解构建 Provider 对象 for _, discoveredProvider := range discoveredProviders { // 查找对应的 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 } // 如果启用严格模式,验证 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") continue } } // 添加到结果列表 providers = append(providers, provider) } // === 步骤 9:日志记录 === // 记录解析过程中的所有警告和错误 for _, parseErr := range context.GetErrors("warning") { log.Warnf("Warning while parsing %s: %s", source, parseErr.Message) } for _, parseErr := range context.GetErrors("error") { log.Errorf("Error while parsing %s: %s", source, parseErr.Message) } // 返回构建成功的 Provider 列表 return providers, nil } // ParseDir parses all Go files in a directory and returns discovered providers func (p *MainParser) ParseDir(dir string) ([]Provider, error) { var allProviders []Provider // Use AST walker to traverse the directory if err := p.astWalker.WalkDir(dir); err != nil { return nil, fmt.Errorf("failed to walk directory: %w", err) } // Note: This would need to be enhanced to collect providers from all files // For now, we'll return an empty slice and log a warning log.Warn("ParseDir not fully implemented yet") return allProviders, nil } // shouldProcessFile determines if a file should be processed func (p *MainParser) shouldProcessFile(source string) bool { // Skip test files if strings.HasSuffix(source, "_test.go") { return false } // Skip generated provider files if strings.HasSuffix(source, "provider.gen.go") { return false } return true } // buildProviderFromDiscovery builds a complete Provider from a discovered provider annotation func (p *MainParser) buildProviderFromDiscovery(discoveredProvider Provider, node *ast.File, context *BuilderContext) (Provider, error) { // Find the corresponding type specification in the AST var typeSpec *ast.TypeSpec var genDecl *ast.GenDecl for _, decl := range node.Decls { gd, ok := decl.(*ast.GenDecl) if !ok { continue } if len(gd.Specs) == 0 { continue } ts, ok := gd.Specs[0].(*ast.TypeSpec) if !ok { continue } if ts.Name.Name == discoveredProvider.StructName { typeSpec = ts genDecl = gd break } } if typeSpec == nil { return Provider{}, fmt.Errorf("type specification not found for %s", discoveredProvider.StructName) } // Use the builder to construct the complete provider provider, err := p.builder.BuildFromTypeSpec(typeSpec, genDecl, context) if err != nil { return Provider{}, fmt.Errorf("failed to build provider: %w", err) } // Apply legacy compatibility transformations result, err := p.applyLegacyCompatibility(&provider) if err != nil { return Provider{}, fmt.Errorf("failed to apply legacy compatibility: %w", err) } return result, nil } // applyLegacyCompatibility applies transformations to maintain backward compatibility func (p *MainParser) applyLegacyCompatibility(provider *Provider) (Provider, error) { // Set provider file path provider.ProviderFile = filepath.Join(filepath.Dir(provider.ProviderFile), "provider.gen.go") // Apply mode-specific transformations based on the original logic switch provider.Mode { case ProviderModeGrpc: p.applyGrpcCompatibility(provider) case ProviderModeEvent: p.applyEventCompatibility(provider) case ProviderModeJob, ProviderModeCronJob: p.applyJobCompatibility(provider) case ProviderModeModel: p.applyModelCompatibility(provider) } return *provider, nil } // applyGrpcCompatibility applies gRPC-specific compatibility transformations func (p *MainParser) applyGrpcCompatibility(provider *Provider) { modePkg := gomod.GetModuleName() + "/providers/grpc" // Add required imports provider.Imports[createAtomPackage("")] = "" provider.Imports[createAtomPackage("contracts")] = "" provider.Imports[modePkg] = "" // Set provider group if provider.ProviderGroup == "" { provider.ProviderGroup = "atom.GroupInitial" } // Set return type and register function // Important: Save the original return type before setting it to contracts.Initial originalReturnType := provider.ReturnType provider.ReturnType = "contracts.Initial" // Set gRPC register function name if not already set if provider.GrpcRegisterFunc == "" { if originalReturnType != "" && strings.Contains(originalReturnType, ".") { // User specified a complete register function name, like userv1.RegisterUserServiceServer provider.GrpcRegisterFunc = originalReturnType } else { // Generate default gRPC register function name // Example: UserService -> RegisterUserServiceServer provider.GrpcRegisterFunc = "Register" + strings.TrimPrefix(originalReturnType, "*") + "Server" } } // Note: Package import handling for gRPC register functions is now done // in resolveImportDependencies to ensure access to original file imports // Add gRPC injection parameter provider.InjectParams["__grpc"] = InjectParam{ Star: "*", Type: "Grpc", Package: modePkg, PackageAlias: "grpc", } } // applyEventCompatibility applies event-specific compatibility transformations func (p *MainParser) applyEventCompatibility(provider *Provider) { modePkg := gomod.GetModuleName() + "/providers/event" // Add required imports provider.Imports[createAtomPackage("")] = "" provider.Imports[createAtomPackage("contracts")] = "" provider.Imports[modePkg] = "" // Set provider group if provider.ProviderGroup == "" { provider.ProviderGroup = "atom.GroupInitial" } // Set return type provider.ReturnType = "contracts.Initial" // Add event injection parameter provider.InjectParams["__event"] = InjectParam{ Star: "*", Type: "PubSub", Package: modePkg, PackageAlias: "event", } } // applyJobCompatibility applies job-specific compatibility transformations func (p *MainParser) applyJobCompatibility(provider *Provider) { modePkg := gomod.GetModuleName() + "/providers/job" // Add required imports provider.Imports[createAtomPackage("")] = "" provider.Imports[createAtomPackage("contracts")] = "" provider.Imports["github.com/riverqueue/river"] = "" provider.Imports[modePkg] = "" // Set provider group if provider.ProviderGroup == "" { provider.ProviderGroup = "atom.GroupInitial" } // Set return type provider.ReturnType = "contracts.Initial" // Add job injection parameter provider.InjectParams["__job"] = InjectParam{ Star: "*", Type: "Job", Package: modePkg, PackageAlias: "job", } } // applyModelCompatibility applies model-specific compatibility transformations func (p *MainParser) applyModelCompatibility(provider *Provider) { // Set provider group if provider.ProviderGroup == "" { provider.ProviderGroup = "atom.GroupInitial" } // Set return type provider.ReturnType = "contracts.Initial" // Ensure prepare function is needed provider.NeedPrepareFunc = true } // GetCommentParser returns the comment parser used by this parser func (p *MainParser) GetCommentParser() *CommentParser { return p.commentParser } // GetImportResolver returns the import resolver used by this parser func (p *MainParser) GetImportResolver() *ImportResolver { return p.importResolver } // GetASTWalker returns the AST walker used by this parser func (p *MainParser) GetASTWalker() *ASTWalker { return p.astWalker } // GetBuilder returns the provider builder used by this parser func (p *MainParser) GetBuilder() *ProviderBuilder { return p.builder } // GetValidator returns the validator used by this parser func (p *MainParser) GetValidator() *GoValidator { return p.validator } // GetConfig returns the parser configuration func (p *MainParser) GetConfig() *ParserConfig { return p.config } // SetConfig updates the parser configuration func (p *MainParser) SetConfig(config *ParserConfig) { p.config = config } // Helper function to create atom package paths func createAtomPackage(suffix string) string { root := "go.ipao.vip/atom" if suffix != "" { return fmt.Sprintf("%s/%s", root, suffix) } return root }