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  }