github.com/tiagovtristao/plz@v13.4.0+incompatible/src/parse/asp/util.go (about) 1 package asp 2 3 import ( 4 "reflect" 5 "strings" 6 ) 7 8 // FindTarget returns the top-level call in a BUILD file that corresponds to a target 9 // of the given name (or nil if one does not exist). 10 func FindTarget(statements []*Statement, name string) *Statement { 11 for _, statement := range statements { 12 if ident := statement.Ident; ident != nil && ident.Action != nil && ident.Action.Call != nil { 13 for _, arg := range ident.Action.Call.Arguments { 14 if arg.Name == "name" { 15 if arg.Value.Val != nil && arg.Value.Val.String != "" && strings.Trim(arg.Value.Val.String, `"`) == name { 16 return statement 17 } 18 } 19 } 20 } 21 } 22 return nil 23 } 24 25 // NextStatement finds the statement that follows the given one. 26 // This is often useful to find the extent of a statement in source code. 27 // It will return nil if there is not one following it. 28 func NextStatement(statements []*Statement, statement *Statement) *Statement { 29 for i, s := range statements { 30 if s == statement && i < len(statements)-1 { 31 return statements[i+1] 32 } 33 } 34 return nil 35 } 36 37 // GetExtents returns the "extents" of a statement, i.e. the lines that it covers in source. 38 // The caller must pass a value for the maximum extent of the file; we can't detect it here 39 // because the AST only contains positions for the beginning of the statements. 40 func GetExtents(statements []*Statement, statement *Statement, max int) (int, int) { 41 next := NextStatement(statements, statement) 42 if next == nil { 43 // Assume it reaches to the end of the file 44 return statement.Pos.Line, max 45 } 46 return statement.Pos.Line, next.Pos.Line - 1 47 } 48 49 // FindArgument finds an argument of any one of the given names, or nil if there isn't one. 50 // The statement must be a function call (e.g. as returned by FindTarget). 51 func FindArgument(statement *Statement, args ...string) *CallArgument { 52 for i, a := range statement.Ident.Action.Call.Arguments { 53 for _, arg := range args { 54 if a.Name == arg { 55 return &statement.Ident.Action.Call.Arguments[i] 56 } 57 } 58 } 59 return nil 60 } 61 62 // StatementOrExpressionFromAst recursively finds asp.IdentStatement and asp.Expression in the ast 63 // and returns a valid statement pointer if within range 64 func StatementOrExpressionFromAst(stmts []*Statement, position Position) (statement *Statement, expression *Expression) { 65 callback := func(astStruct interface{}) interface{} { 66 if expr, ok := astStruct.(Expression); ok { 67 if withInRange(expr.Pos, expr.EndPos, position) { 68 return expr 69 } 70 } else if stmt, ok := astStruct.(Statement); ok { 71 if withInRange(stmt.Pos, stmt.EndPos, position) { 72 // get function call, assignment, and property access 73 if stmt.Ident != nil { 74 return stmt 75 } 76 } 77 } 78 79 return nil 80 } 81 82 item := WalkAST(stmts, callback) 83 if item != nil { 84 if expr, ok := item.(Expression); ok { 85 return nil, &expr 86 } else if stmt, ok := item.(Statement); ok { 87 return &stmt, nil 88 } 89 } 90 91 return nil, nil 92 } 93 94 // WalkAST is a generic function that walks through the ast recursively, 95 // astStruct can be anything inside of the AST, such as asp.Statement, asp.Expression 96 // it accepts a callback for any operations 97 func WalkAST(astStruct interface{}, callback func(astStruct interface{}) interface{}) interface{} { 98 if astStruct == nil { 99 return nil 100 } 101 102 item := callback(astStruct) 103 if item != nil { 104 return item 105 } 106 107 v, ok := astStruct.(reflect.Value) 108 if !ok { 109 v = reflect.ValueOf(astStruct) 110 } 111 112 if v.Kind() == reflect.Ptr && !v.IsNil() { 113 return WalkAST(v.Elem().Interface(), callback) 114 } else if v.Kind() == reflect.Slice { 115 for i := 0; i < v.Len(); i++ { 116 item = WalkAST(v.Index(i).Interface(), callback) 117 if item != nil { 118 return item 119 } 120 } 121 } else if v.Kind() == reflect.Struct { 122 for i := 0; i < v.NumField(); i++ { 123 item = WalkAST(v.Field(i).Interface(), callback) 124 if item != nil { 125 return item 126 } 127 } 128 } 129 return nil 130 131 } 132 133 // withInRange checks if the input position is within the range of the Expression 134 func withInRange(exprPos Position, exprEndPos Position, pos Position) bool { 135 withInLineRange := pos.Line >= exprPos.Line && 136 pos.Line <= exprEndPos.Line 137 138 withInColRange := pos.Column >= exprPos.Column && 139 pos.Column <= exprEndPos.Column 140 141 onTheSameLine := pos.Line == exprEndPos.Line && 142 pos.Line == exprPos.Line 143 144 if !withInLineRange || (onTheSameLine && !withInColRange) { 145 return false 146 } 147 148 if pos.Line == exprPos.Line { 149 return pos.Column >= exprPos.Column 150 } 151 152 if pos.Line == exprEndPos.Line { 153 return pos.Column <= exprEndPos.Column 154 } 155 156 return true 157 }