1package main23import (4 "io"5 "mvdan.cc/sh/syntax"6)78const (9 // Shell variant to use. Even though APKBUILDs are mostly10 // written in POSIX shell we still use LangBash here to check11 // for the `local` variable declaration keyword and other12 // bashims permitted in APKBUILDs.13 lang = syntax.LangBash14)1516// APKBUILD represents an Alpine Linux APKBUILD.17type APKBUILD struct {18 // Root node of the AST.19 prog *syntax.File2021 // Globally declared comments.22 Comments []syntax.Comment2324 // Global variable assignments excluding environment variables.25 Assignments []syntax.Assign2627 // Declared functions.28 Functions map[string]syntax.FuncDecl29}3031// Parse reads and parses an Alpine Linux APKBUILD. The name will be32// used in error messages emitted for this APKBUILD.33func Parse(r io.Reader, name string) (*APKBUILD, error) {34 parser := syntax.NewParser(syntax.KeepComments,35 syntax.Variant(lang))3637 prog, err := parser.Parse(r, name)38 if err != nil {39 return nil, err40 }4142 apkbuild := APKBUILD{prog: prog}43 apkbuild.Functions = make(map[string]syntax.FuncDecl)44 apkbuild.Walk(apkbuild.visit)4546 return &apkbuild, nil47}4849// Name returns the name supplied to the parse function.50func (a *APKBUILD) Name() string {51 return a.prog.Name52}5354// Walk traverses the underlying AST of the APKBUILD in depth-first55// order. It's just a wrapper function around syntax.Walk.56func (a *APKBUILD) Walk(f func(syntax.Node) bool) {57 syntax.Walk(a.prog, f)58}5960func (a *APKBUILD) visit(node syntax.Node) bool {61 switch x := node.(type) {62 case *syntax.DeclClause:63 return x.Variant.Value != "export"64 case *syntax.FuncDecl:65 a.Functions[x.Name.Value] = *x66 return false // All nodes after this have local scope67 case *syntax.Assign:68 a.Assignments = append(a.Assignments, *x)69 return true70 case *syntax.Comment:71 a.Comments = append(a.Comments, *x)72 return true73 default:74 return true75 }76}7778// IsGlobalVar checks if the supplied name responds to a global79// variable declaration.80func (a *APKBUILD) IsGlobalVar(varname string) bool {81 for _, assignment := range a.Assignments {82 if assignment.Name.Value == varname {83 return true84 }85 }8687 return false88}8990// IsUnusedVar checks if the variable with the supplied name is unused91// in the APKBUILD. It also returns true if the given variable is an92// environment variable.93func (a *APKBUILD) IsUnusedVar(varname string) bool {94 ret := true95 a.Walk(func(node syntax.Node) bool {96 switch x := node.(type) {97 case *syntax.DeclClause:98 if x.Variant.Value != "export" {99 return true100 }101102 for _, a := range x.Assigns {103 if a.Name.Value == varname {104 ret = false105 return false106 }107 }108 case *syntax.SglQuoted:109 if x.Dollar && x.Value == varname {110 ret = false111 return false112 }113 case *syntax.ParamExp:114 if x.Param.Value == varname {115 ret = false116 return false117 }118 }119120 return true121 })122123 return ret124}