package constants import ( "fmt" "path/filepath" "slices" "strings" "github.com/bazelbuild/bazel-gazelle/language" "github.com/bazelbuild/buildtools/build" bzlFile "github.com/bazelbuild/buildtools/file" ) type BazelConstMap map[string]string type constantSource struct { Module string Filepath string Constants []string } func (cs constantSource) LoadBazelConstMap(cm BazelConstMap) (BazelConstMap, error) { moduleFileBytes, _, err := bzlFile.ReadFile(cs.Filepath) if err != nil { return cm, fmt.Errorf("error reading loaded file %s: err: %w", cs.Filepath, err) } moduleBzlFile, err := build.ParseBzl(cs.Filepath, moduleFileBytes) if err != nil { return cm, fmt.Errorf("error parsing file %s: %w", cs.Filepath, err) } for _, stmt := range moduleBzlFile.Stmt { if assignStmt, isAssign := stmt.(*build.AssignExpr); isAssign { var constName string var constValue string if ident, ok := assignStmt.LHS.(*build.Ident); ok { if !slices.Contains(cs.Constants, ident.Name) { continue } constName = ident.Name } // Resolve values if val, ok := assignStmt.RHS.(*build.StringExpr); ok { constValue = val.Value } // ignore all other attr types besides StringExpr for now cm[constName] = constValue } } return cm, nil } // ResolveConstants resolves values from the current file as well as from load statements // in the file func ResolveConstants(args language.GenerateArgs, f *build.File, constMap BazelConstMap) (BazelConstMap, error) { constantSources := []constantSource{} for _, s := range f.Stmt { switch stmt := s.(type) { case *build.LoadStmt: cs := constantSource{ Module: stmt.Module.Value, Filepath: labelToPath(args, stmt.Module.Value), } for _, from := range stmt.From { if from.Name != strings.ToUpper(from.Name) { // Not a constant continue } cs.Constants = append(cs.Constants, from.Name) } if len(cs.Constants) == 0 { // No constants to resolve, don't add to resolve list continue } constantSources = append(constantSources, cs) case *build.AssignExpr: // Only handle string constants for now constName, constValue, canAssign := assignExprVals(stmt) if canAssign { constMap[constName] = constValue } } } for _, constSource := range constantSources { constMap, err := constSource.LoadBazelConstMap(constMap) if err != nil { return constMap, err } } return constMap, nil } func ResolveAttr(rule *build.Rule, key string, constMap BazelConstMap) string { attr := rule.Attr(key) if reg, ok := attr.(*build.Ident); ok { return constMap[reg.Name] } if reg, ok := attr.(*build.StringExpr); ok { return reg.Value } return "" } func labelToPath(args language.GenerateArgs, label string) string { if strings.HasPrefix(label, ":") { label = strings.TrimPrefix(label, ":") return filepath.Join(args.Dir, label) } label = strings.TrimPrefix(label, "//") label = strings.ReplaceAll(label, ":", "/") return filepath.Join(args.Config.RepoRoot, label) } // assignExprVals extracts the key and value from an assignment // func assignExprVals(e build.Expr) (string, string) { func assignExprVals(assign *build.AssignExpr) (string, string, bool) { var constName string var constValue string switch ident := assign.LHS.(type) { case *build.Ident: constName = ident.Name default: // only try ident for now return "", "", false } // Resolve values switch val := assign.RHS.(type) { case *build.StringExpr: constValue = val.Value return constName, constValue, true default: // only load strings for now return "", "", false } }