mirror of https://github.com/jetkvm/kvm.git
335 lines
8.7 KiB
Go
335 lines
8.7 KiB
Go
package main
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// getStringAliasInfoWithReflection uses reflection to automatically detect constants
|
|
// This approach tries to find constants by examining the actual values
|
|
func getStringAliasInfoWithReflection(searchPath string) []StringAliasInfo {
|
|
log.Debug().Str("searchPath", searchPath).Msg("Detecting string aliases and constants in single pass")
|
|
|
|
// Detect both string aliases and their constants in a single file system walk
|
|
result := detectStringAliasesWithConstants(searchPath)
|
|
|
|
// If reflection didn't work, throw an error
|
|
if len(result) == 0 {
|
|
log.Fatal().Msg("No string aliases with constants could be detected. Make sure the types are defined with constants in Go files.")
|
|
}
|
|
|
|
log.Debug().Int("detected", len(result)).Msg("String alias detection completed")
|
|
return result
|
|
}
|
|
|
|
// detectStringAliasesWithConstants detects both string aliases and their constants in a single file system walk
|
|
func detectStringAliasesWithConstants(searchPath string) []StringAliasInfo {
|
|
var result []StringAliasInfo
|
|
|
|
// Walk the specified directory to find Go files
|
|
err := filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Skip directories and non-Go files
|
|
if info.IsDir() || !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
|
|
// Skip test files and our own tool files
|
|
if strings.Contains(path, "_test.go") || strings.Contains(path, "scripts/jsonrpc_typings") {
|
|
return nil
|
|
}
|
|
|
|
// Parse the file to find both string aliases and their constants
|
|
aliases := findStringAliasesWithConstantsInFile(path)
|
|
result = append(result, aliases...)
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Error walking directory for string alias detection")
|
|
}
|
|
|
|
// Remove duplicates based on type name
|
|
uniqueAliases := make([]StringAliasInfo, 0)
|
|
seen := make(map[string]bool)
|
|
for _, alias := range result {
|
|
if !seen[alias.Name] {
|
|
seen[alias.Name] = true
|
|
uniqueAliases = append(uniqueAliases, alias)
|
|
}
|
|
}
|
|
|
|
return uniqueAliases
|
|
}
|
|
|
|
// findStringAliasesWithConstantsInFile finds both string aliases and their constants in a single Go file
|
|
func findStringAliasesWithConstantsInFile(filePath string) []StringAliasInfo {
|
|
var result []StringAliasInfo
|
|
|
|
// Parse the Go file
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
log.Debug().Err(err).Str("file", filePath).Msg("Failed to parse file")
|
|
return result
|
|
}
|
|
|
|
// First pass: collect all string alias type names
|
|
stringAliases := make(map[string]bool)
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
genDecl, ok := n.(*ast.GenDecl)
|
|
if !ok || genDecl.Tok != token.TYPE {
|
|
return true
|
|
}
|
|
|
|
for _, spec := range genDecl.Specs {
|
|
typeSpec, ok := spec.(*ast.TypeSpec)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if this is a string alias (type Name string)
|
|
if ident, ok := typeSpec.Type.(*ast.Ident); ok && ident.Name == "string" {
|
|
stringAliases[typeSpec.Name.Name] = true
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
// Second pass: find constants for the string aliases we found
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
genDecl, ok := n.(*ast.GenDecl)
|
|
if !ok || genDecl.Tok != token.CONST {
|
|
return true
|
|
}
|
|
|
|
// Process each constant specification in the declaration
|
|
for _, spec := range genDecl.Specs {
|
|
valueSpec, ok := spec.(*ast.ValueSpec)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if this constant is typed with one of our string aliases
|
|
if valueSpec.Type == nil {
|
|
continue
|
|
}
|
|
|
|
ident, ok := valueSpec.Type.(*ast.Ident)
|
|
if !ok {
|
|
continue
|
|
}
|
|
typeName := ident.Name
|
|
|
|
// Check if this type is one of our string aliases
|
|
if _, ok := stringAliases[typeName]; !ok {
|
|
continue
|
|
}
|
|
|
|
// Extract string literal values
|
|
for _, value := range valueSpec.Values {
|
|
basicLit, ok := value.(*ast.BasicLit)
|
|
if !ok || basicLit.Kind != token.STRING {
|
|
continue
|
|
}
|
|
|
|
// Remove quotes from string literal
|
|
constantValue := strings.Trim(basicLit.Value, "\"")
|
|
|
|
// Find or create the StringAliasInfo for this type
|
|
var aliasInfo *StringAliasInfo
|
|
for i := range result {
|
|
if result[i].Name == typeName {
|
|
aliasInfo = &result[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if aliasInfo == nil {
|
|
result = append(result, StringAliasInfo{
|
|
Name: typeName,
|
|
Constants: []string{},
|
|
})
|
|
aliasInfo = &result[len(result)-1]
|
|
}
|
|
|
|
aliasInfo.Constants = append(aliasInfo.Constants, constantValue)
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
// batchDetectConstantsForTypes efficiently detects constants for multiple types in a single file system walk
|
|
func batchDetectConstantsForTypes(typeNames []string, searchPath string) map[string][]string {
|
|
result := make(map[string][]string)
|
|
|
|
// Initialize result map
|
|
for _, typeName := range typeNames {
|
|
result[typeName] = []string{}
|
|
}
|
|
|
|
// Walk the specified directory to find Go files
|
|
err := filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Skip directories and non-Go files
|
|
if info.IsDir() || !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
|
|
// Skip test files and our own tool files
|
|
if strings.Contains(path, "_test.go") || strings.Contains(path, "scripts/jsonrpc_typings") {
|
|
return nil
|
|
}
|
|
|
|
// Check if this file contains any of the types we're looking for
|
|
fileContainsAnyType := false
|
|
for _, typeName := range typeNames {
|
|
if fileContainsType(path, typeName) {
|
|
fileContainsAnyType = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !fileContainsAnyType {
|
|
return nil
|
|
}
|
|
|
|
log.Debug().Str("file", path).Strs("types", typeNames).Msg("Parsing file for constants")
|
|
|
|
// Parse constants for all types from this file
|
|
fileConstants := batchParseConstantsFromFile(path, typeNames)
|
|
|
|
// Merge results
|
|
for typeName, constants := range fileConstants {
|
|
if len(constants) > 0 {
|
|
result[typeName] = constants
|
|
log.Debug().Str("type", typeName).Strs("constants", constants).Str("file", path).Msg("Found constants")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Error searching for constants")
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// batchParseConstantsFromFile parses constants for multiple types from a single Go file
|
|
func batchParseConstantsFromFile(filePath string, typeNames []string) map[string][]string {
|
|
result := make(map[string][]string)
|
|
|
|
// Initialize result map
|
|
for _, typeName := range typeNames {
|
|
result[typeName] = []string{}
|
|
}
|
|
|
|
// Parse the Go file
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
log.Debug().Err(err).Str("file", filePath).Msg("Failed to parse file")
|
|
return result
|
|
}
|
|
|
|
// Walk the AST to find constant declarations
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
// Look for GenDecl nodes (const declarations)
|
|
genDecl, ok := n.(*ast.GenDecl)
|
|
if !ok || genDecl.Tok != token.CONST {
|
|
return true
|
|
}
|
|
|
|
// Process each constant specification in the declaration
|
|
for _, spec := range genDecl.Specs {
|
|
valueSpec, ok := spec.(*ast.ValueSpec)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if this constant is typed with one of our target types
|
|
if valueSpec.Type != nil {
|
|
if ident, ok := valueSpec.Type.(*ast.Ident); ok {
|
|
typeName := ident.Name
|
|
|
|
// Check if this type is one we're looking for
|
|
if contains(typeNames, typeName) {
|
|
// Extract string literal values
|
|
for _, value := range valueSpec.Values {
|
|
if basicLit, ok := value.(*ast.BasicLit); ok && basicLit.Kind == token.STRING {
|
|
// Remove quotes from string literal
|
|
constantValue := strings.Trim(basicLit.Value, "\"")
|
|
result[typeName] = append(result[typeName], constantValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
// contains checks if a slice contains a string
|
|
func contains(slice []string, item string) bool {
|
|
for _, s := range slice {
|
|
if s == item {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// fileContainsType checks if a Go file contains a type definition for the given type name
|
|
func fileContainsType(filePath, typeName string) bool {
|
|
fset := token.NewFileSet()
|
|
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Walk the AST to find type definitions
|
|
found := false
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.GenDecl:
|
|
if x.Tok == token.TYPE {
|
|
for _, spec := range x.Specs {
|
|
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
if typeSpec.Name.Name == typeName {
|
|
found = true
|
|
return false // Stop searching
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return !found // Continue searching if not found yet
|
|
})
|
|
|
|
return found
|
|
}
|