github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/build_langserver/langserver/analyzer.go (about)

     1  package langserver
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path"
     8  	"path/filepath"
     9  	"query"
    10  	"regexp"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/thought-machine/please/src/cli"
    16  	"github.com/thought-machine/please/src/core"
    17  	"github.com/thought-machine/please/src/fs"
    18  	"github.com/thought-machine/please/src/parse/asp"
    19  	"github.com/thought-machine/please/src/parse/rules"
    20  	"github.com/thought-machine/please/src/plz"
    21  	"github.com/thought-machine/please/tools/build_langserver/lsp"
    22  )
    23  
    24  // Analyzer is a wrapper around asp.parser
    25  // This is being loaded into a handler on initialization
    26  type Analyzer struct {
    27  	parser *asp.Parser
    28  	State  *core.BuildState
    29  
    30  	BuiltIns   map[string]*RuleDef
    31  	Attributes map[string][]*RuleDef
    32  }
    33  
    34  // RuleDef is a wrapper around asp.FuncDef,
    35  // it also includes a Header(function definition)
    36  // And Argument map stores the name and the information of the arguments this rule has
    37  type RuleDef struct {
    38  	*asp.FuncDef
    39  	Header string
    40  	ArgMap map[string]*Argument
    41  
    42  	// This applies when the FuncDef is a attribute of an object
    43  	Object string
    44  }
    45  
    46  // Argument is a wrapper around asp.Argument,
    47  // this is used to store the argument information for specific rules,
    48  // and it also tells you if the argument is required
    49  type Argument struct {
    50  	*asp.Argument
    51  	// the definition string when hover over the argument, e.g. src type:list, required:false
    52  	Definition string
    53  	// string representation of the original argument definition
    54  	Repr     string
    55  	Required bool
    56  }
    57  
    58  // Call represent a function call
    59  type Call struct {
    60  	Arguments []asp.CallArgument
    61  	Name      string
    62  }
    63  
    64  // Identifier is a wrapper around asp.Identifier
    65  // Including the starting line and the ending line number
    66  type Identifier struct {
    67  	*asp.IdentStatement
    68  	Type   string
    69  	Pos    lsp.Position
    70  	EndPos lsp.Position
    71  }
    72  
    73  // Variable is a representation of a variable assignment in
    74  // ***More fields can be added in later if needed
    75  type Variable struct {
    76  	Name string
    77  	Type string
    78  }
    79  
    80  // BuildDef is the definition for a build target.
    81  // often a function call using a specific build rule
    82  type BuildDef struct {
    83  	*Identifier
    84  	BuildDefName string
    85  	// The content of the build definition
    86  	Content    string
    87  	Visibility []string
    88  }
    89  
    90  // Statement is a simplified version of asp.Statement
    91  // Here we only care about Idents and Expressions
    92  type Statement struct {
    93  	Ident      *Identifier
    94  	Expression *asp.Expression
    95  }
    96  
    97  // BuildLabel is a wrapper around core.BuildLabel
    98  // Including the path of the buildFile
    99  type BuildLabel struct {
   100  	*core.BuildLabel
   101  	// Path of the build file
   102  	Path string
   103  	// IdentStatement for the build definition,
   104  	// usually the call to the specific buildrule, such as "go_library()"
   105  	BuildDef *BuildDef
   106  	// The definition of the buildlabel, e.g: BUILD Label: //src/core
   107  	Definition string
   108  	// Reverse Dependency of this build label
   109  	RevDeps core.BuildLabels
   110  }
   111  
   112  func newAnalyzer() (*Analyzer, error) {
   113  	// Saving the state to Analyzer,
   114  	// so we will be able to get the CONFIG properties by calling state.config.GetTags()
   115  	config, err := core.ReadDefaultConfigFiles("")
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	state := core.NewBuildState(1, nil, 4, config)
   120  	parser := asp.NewParser(state)
   121  
   122  	a := &Analyzer{
   123  		parser: parser,
   124  		State:  state,
   125  	}
   126  	a.builtInsRules()
   127  
   128  	return a, nil
   129  }
   130  
   131  // BuiltInsRules gets all the builtin functions and rules as a map, and store it in Analyzer.BuiltIns
   132  // This is typically called when instantiate a new Analyzer
   133  func (a *Analyzer) builtInsRules() error {
   134  	a.BuiltIns = make(map[string]*RuleDef)
   135  	a.Attributes = make(map[string][]*RuleDef)
   136  
   137  	dir, _ := rules.AssetDir("")
   138  	sort.Strings(dir)
   139  	// Iterate through the directory and get the builtin statements
   140  	for _, filename := range dir {
   141  		if !strings.HasSuffix(filename, ".gob") {
   142  			asset := rules.MustAsset(filename)
   143  			stmts, err := a.parser.ParseData(asset, filename)
   144  			if err != nil {
   145  				log.Warning("parsing failure: %s ", err)
   146  			}
   147  
   148  			log.Info("Loading built-in build rules...")
   149  			a.loadBuiltinRules(stmts, string(asset))
   150  		}
   151  	}
   152  
   153  	for _, buildDef := range a.State.Config.Parse.PreloadBuildDefs {
   154  		filePath := path.Join(core.RepoRoot, buildDef)
   155  		bytecontent, err := ioutil.ReadFile(filePath)
   156  		if err != nil {
   157  			log.Warning("parsing failure for preload build defs: %s ", err)
   158  		}
   159  		stmts, err := a.parser.ParseData(bytecontent, filePath)
   160  
   161  		log.Debug("Preloading build defs from %s...", buildDef)
   162  		a.loadBuiltinRules(stmts, string(bytecontent))
   163  
   164  	}
   165  	return nil
   166  }
   167  
   168  func (a *Analyzer) loadBuiltinRules(stmts []*asp.Statement, fileContent string) {
   169  	for _, statement := range stmts {
   170  		if statement.FuncDef != nil && !statement.FuncDef.IsPrivate {
   171  
   172  			ruleDef := newRuleDef(fileContent, statement)
   173  			a.BuiltIns[statement.FuncDef.Name] = ruleDef
   174  
   175  			// Fill in attribute map if certain ruleDef is a attribute
   176  			if ruleDef.Object != "" {
   177  				if _, ok := a.Attributes[ruleDef.Object]; ok {
   178  					a.Attributes[ruleDef.Object] = append(a.Attributes[ruleDef.Object], ruleDef)
   179  				} else {
   180  					a.Attributes[ruleDef.Object] = []*RuleDef{ruleDef}
   181  				}
   182  			}
   183  		}
   184  	}
   185  }
   186  
   187  func newRuleDef(content string, stmt *asp.Statement) *RuleDef {
   188  	ruleDef := &RuleDef{
   189  		FuncDef: stmt.FuncDef,
   190  		ArgMap:  make(map[string]*Argument),
   191  	}
   192  
   193  	// Fill in the header property of ruleDef
   194  	contentStrSlice := strings.Split(content, "\n")
   195  	headerSlice := contentStrSlice[stmt.Pos.Line-1 : stmt.FuncDef.EoDef.Line]
   196  	argReprs := getArgReprs(headerSlice)
   197  
   198  	if len(stmt.FuncDef.Arguments) > 0 {
   199  		for i, arg := range stmt.FuncDef.Arguments {
   200  			// Check if it a builtin type method, and reconstruct header if it is
   201  			if i == 0 && arg.Name == "self" {
   202  				originalDef := fmt.Sprintf("def %s(self:%s", stmt.FuncDef.Name, arg.Type[0])
   203  				if len(stmt.FuncDef.Arguments) > 1 {
   204  					originalDef += ", "
   205  				}
   206  				newDef := fmt.Sprintf("%s.%s(", arg.Type[0], stmt.FuncDef.Name)
   207  				headerSlice[0] = strings.Replace(headerSlice[0], originalDef, newDef, 1)
   208  				ruleDef.Object = arg.Type[0]
   209  			} else {
   210  				// Fill in the ArgMap
   211  				var repr string
   212  				if len(argReprs)-1 >= i {
   213  					repr = strings.TrimSpace(argReprs[i])
   214  				}
   215  				ruleDef.ArgMap[arg.Name] = &Argument{
   216  					Argument:   &stmt.FuncDef.Arguments[i],
   217  					Repr:       repr,
   218  					Definition: getArgString(arg),
   219  					Required:   arg.Value == nil,
   220  				}
   221  			}
   222  		}
   223  	}
   224  
   225  	header := strings.TrimSuffix(strings.Join(headerSlice, "\n"), ":")
   226  	if typeAnnotation := strings.Index(header, "->"); typeAnnotation != -1 {
   227  		header = header[:strings.Index(header, "->")-1]
   228  	}
   229  	ruleDef.Header = removePrivateArgFromHeader(header)
   230  	return ruleDef
   231  }
   232  
   233  // AspStatementFromFile gets all the Asp.Statement from a given BUILD file
   234  // *reads complete files only*
   235  func (a *Analyzer) AspStatementFromFile(uri lsp.DocumentURI) ([]*asp.Statement, error) {
   236  	filepath, err := GetPathFromURL(uri, "file")
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	bytecontent, err := ioutil.ReadFile(filepath)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	stmts, err := a.parser.ParseData(bytecontent, filepath)
   246  	if err != nil {
   247  		log.Warning("reading only partial of the file due to parsing failure: %s ", err)
   248  	}
   249  
   250  	return stmts, nil
   251  }
   252  
   253  // AspStatementFromContent returns a slice of asp.Statement given content string(usually workSpaceStore.doc.TextInEdit)
   254  func (a *Analyzer) AspStatementFromContent(content string) []*asp.Statement {
   255  	byteContent := []byte(content)
   256  
   257  	stmts, err := a.parser.ParseData(byteContent, "")
   258  	if err != nil {
   259  		log.Warning("reading only partial of the file due to parsing failure: %s ", err)
   260  	}
   261  
   262  	return stmts
   263  }
   264  
   265  // StatementFromPos returns a Statement struct with either an Identifier or asp.Expression
   266  func (a *Analyzer) StatementFromPos(stmts []*asp.Statement, position lsp.Position) *Statement {
   267  	statement, expr := asp.StatementOrExpressionFromAst(stmts,
   268  		asp.Position{Line: position.Line + 1, Column: position.Character + 1})
   269  
   270  	if statement != nil {
   271  		return &Statement{
   272  			Ident: a.identFromStatement(statement),
   273  		}
   274  	} else if expr != nil {
   275  		return &Statement{
   276  			Expression: expr,
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  // IdentsFromContent returns a channel of Identifier object
   283  func (a *Analyzer) IdentsFromContent(content string, pos *lsp.Position) chan *Identifier {
   284  	stmts := a.AspStatementFromContent(content)
   285  
   286  	return a.IdentsFromStatement(stmts, pos)
   287  }
   288  
   289  // IdentsFromStatement returns a channel of Identifier object given the slice of statement and position
   290  func (a *Analyzer) IdentsFromStatement(stmts []*asp.Statement, pos *lsp.Position) chan *Identifier {
   291  	ch := make(chan *Identifier)
   292  	go func() {
   293  		for _, stmt := range stmts {
   294  			// get global level variables
   295  			if stmt.Ident != nil {
   296  				ident := a.identFromStatement(stmt)
   297  				ch <- ident
   298  			}
   299  			// Get local variables if it's within scope
   300  			if pos != nil && !withInRange(stmt.Pos, stmt.EndPos, *pos) {
   301  				continue
   302  			}
   303  
   304  			callback := func(astStruct interface{}) interface{} {
   305  				if stmt, ok := astStruct.(asp.Statement); ok {
   306  					if stmt.Ident != nil {
   307  						ident := a.identFromStatement(&stmt)
   308  						return ident
   309  					}
   310  				}
   311  				return nil
   312  			}
   313  
   314  			if item := asp.WalkAST(stmt, callback); item != nil {
   315  				ident := item.(*Identifier)
   316  				ch <- ident
   317  			}
   318  
   319  		}
   320  		close(ch)
   321  	}()
   322  
   323  	return ch
   324  }
   325  
   326  // CallFromContentAndPos returns a Identifier object represents function call,
   327  // Only returns the not nil object when the Identifier is within the range specified by the position
   328  func (a *Analyzer) CallFromContentAndPos(content string, pos lsp.Position) *Call {
   329  	stmts := a.AspStatementFromContent(content)
   330  	return a.CallFromAST(stmts, pos)
   331  }
   332  
   333  // CallFromAST returns the Call object from the AST if it's within the range of the position
   334  func (a *Analyzer) CallFromAST(val interface{}, pos lsp.Position) *Call {
   335  	var callback func(astStruct interface{}) interface{}
   336  
   337  	callback = func(astStruct interface{}) interface{} {
   338  		if expr, ok := astStruct.(asp.IdentExpr); ok {
   339  			for _, action := range expr.Action {
   340  				if action.Call != nil &&
   341  					withInRange(expr.Pos, expr.EndPos, pos) {
   342  					return &Call{
   343  						Name:      expr.Name,
   344  						Arguments: action.Call.Arguments,
   345  					}
   346  				}
   347  				if action.Property != nil {
   348  					return asp.WalkAST(action.Property, callback)
   349  				}
   350  			}
   351  		} else if stmt, ok := astStruct.(asp.Statement); ok {
   352  			if stmt.Ident != nil && withInRange(stmt.Pos, stmt.EndPos, pos) {
   353  
   354  				// Walk through the ident first to see any the pos yields to any argument calls
   355  				if item := asp.WalkAST(stmt.Ident, callback); item != nil {
   356  					return item
   357  				}
   358  
   359  				if stmt.Ident.Action != nil && stmt.Ident.Action.Call != nil {
   360  					return &Call{
   361  						Arguments: stmt.Ident.Action.Call.Arguments,
   362  						Name:      stmt.Ident.Name,
   363  					}
   364  				}
   365  			}
   366  
   367  		}
   368  		return nil
   369  	}
   370  
   371  	if item := asp.WalkAST(val, callback); item != nil {
   372  		return item.(*Call)
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  // BuildLabelFromContentAndPos returns the BuildLabel object from the AST if it's within the range of the position
   379  // Given the content
   380  func (a *Analyzer) BuildLabelFromContentAndPos(ctx context.Context,
   381  	content string, uri lsp.DocumentURI, pos lsp.Position) *BuildLabel {
   382  
   383  	stmts := a.AspStatementFromContent(content)
   384  	return a.BuildLabelFromAST(ctx, stmts, uri, pos)
   385  }
   386  
   387  // BuildLabelFromAST returns the BuildLabel object from the AST if it's within the range of the position
   388  func (a *Analyzer) BuildLabelFromAST(ctx context.Context,
   389  	val interface{}, uri lsp.DocumentURI, pos lsp.Position) *BuildLabel {
   390  
   391  	var callback func(astStruct interface{}) interface{}
   392  
   393  	callback = func(astStruct interface{}) interface{} {
   394  		if expr, ok := astStruct.(asp.Expression); ok {
   395  			if withInRange(expr.Pos, expr.EndPos, pos) && expr.Val != nil {
   396  				if expr.Val.String != "" {
   397  
   398  					trimmed := TrimQuotes(expr.Val.String)
   399  					if core.LooksLikeABuildLabel(trimmed) {
   400  						buildLabel, err := a.BuildLabelFromString(ctx, uri, trimmed)
   401  						if err != nil {
   402  							log.Info("error occurred trying to get buildlabel: %s", err)
   403  							return nil
   404  						}
   405  						if buildLabel != nil {
   406  							return buildLabel
   407  						}
   408  					}
   409  				}
   410  				return asp.WalkAST(expr.Val, callback)
   411  			}
   412  		}
   413  		return nil
   414  	}
   415  
   416  	if item := asp.WalkAST(val, callback); item != nil {
   417  		return item.(*BuildLabel)
   418  	}
   419  
   420  	return nil
   421  }
   422  
   423  // GetSubinclude returns a Subinclude object based on the statement and uri passed in.
   424  func (a *Analyzer) GetSubinclude(ctx context.Context, stmts []*asp.Statement, uri lsp.DocumentURI) map[string]*RuleDef {
   425  
   426  	ruleDefs := make(map[string]*RuleDef)
   427  
   428  	currentPkg, err := PackageLabelFromURI(uri)
   429  	if err != nil {
   430  		log.Warning("fail to load package from uri %s: %s", uri, err)
   431  	}
   432  	for _, stmt := range stmts {
   433  		if stmt.Ident != nil {
   434  			ident := a.identFromStatement(stmt)
   435  			if ident.Type == "call" && ident.Name == "subinclude" && len(ident.Action.Call.Arguments) > 0 {
   436  				if ident.Action.Call.Arguments[0].Value.Val == nil {
   437  					log.Warning("Subinclude is nil, skipping...")
   438  					continue
   439  				}
   440  				includeLabel := ident.Action.Call.Arguments[0].Value.Val.String
   441  
   442  				label, err := a.BuildLabelFromString(ctx, uri, TrimQuotes(includeLabel))
   443  				if err != nil {
   444  					log.Warning("error occured when trying to get subinclude %s: %s", includeLabel, err)
   445  					continue
   446  				}
   447  
   448  				if label.BuildDef != nil &&
   449  					label.BuildDef.Name == "filegroup" && isVisible(label.BuildDef, currentPkg) {
   450  					// TODO(bnm): support genrule as well!
   451  					srcs := getSourcesFromBuildDef(label.BuildDef, label.Path)
   452  					a.loadRuleDefsFromSource(ruleDefs, srcs)
   453  
   454  				}
   455  			}
   456  		}
   457  	}
   458  
   459  	return ruleDefs
   460  }
   461  
   462  // GetBuildRuleByName takes the name and subincludes ruleDefs, and return the appropriate ruleDef
   463  func (a *Analyzer) GetBuildRuleByName(name string, subincludes map[string]*RuleDef) *RuleDef {
   464  	if rule, ok := a.BuiltIns[name]; ok {
   465  		return rule
   466  	}
   467  
   468  	if rule, ok := subincludes[name]; ok {
   469  		return rule
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  func (a *Analyzer) loadRuleDefsFromSource(rulesMap map[string]*RuleDef, srcs []string) {
   476  	for _, src := range srcs {
   477  		bytecontent, err := ioutil.ReadFile(src)
   478  		if err != nil {
   479  			log.Warning("parsing failure for build defs %s: %s ", src, err)
   480  		}
   481  
   482  		stmts, err := a.parser.ParseData(bytecontent, src)
   483  
   484  		for _, statement := range stmts {
   485  			if statement.FuncDef != nil && !statement.FuncDef.IsPrivate {
   486  
   487  				ruleDef := newRuleDef(string(bytecontent), statement)
   488  				rulesMap[statement.FuncDef.Name] = ruleDef
   489  			}
   490  		}
   491  	}
   492  }
   493  
   494  func getSourcesFromBuildDef(def *BuildDef, buildFilePath string) []string {
   495  	var srcs []string
   496  
   497  	pkgDir := path.Dir(buildFilePath)
   498  	for _, arg := range def.Action.Call.Arguments {
   499  		if arg.Value.Val == nil {
   500  			continue
   501  		}
   502  		if arg.Name == "src" && arg.Value.Val.String != "" {
   503  			srcPath := path.Join(pkgDir, arg.Value.Val.String)
   504  			srcs = append(srcs, srcPath)
   505  		} else if arg.Name == "srcs" && arg.Value.Val.List != nil {
   506  			srcList := aspListToStrSlice(arg.Value.Val.List)
   507  			for _, src := range srcList {
   508  				srcPath := path.Join(pkgDir, src)
   509  				srcs = append(srcs, srcPath)
   510  			}
   511  		}
   512  	}
   513  
   514  	return srcs
   515  }
   516  
   517  // VariablesFromContent returns a map of variable name to Variable objects given string content
   518  func (a *Analyzer) VariablesFromContent(content string, pos *lsp.Position) map[string]Variable {
   519  	idents := a.IdentsFromContent(content, pos)
   520  
   521  	return a.variablesFromIdents(idents)
   522  }
   523  
   524  // VariablesFromURI returns a map of variable name to Variable objects given an URI
   525  func (a *Analyzer) VariablesFromURI(uri lsp.DocumentURI, pos *lsp.Position) (map[string]Variable, error) {
   526  	stmts, err := a.AspStatementFromFile(uri)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  
   531  	return a.VariablesFromStatements(stmts, pos), nil
   532  }
   533  
   534  // VariablesFromStatements returns a map of variable name to Variable objects given an slice of asp.Statements
   535  func (a *Analyzer) VariablesFromStatements(stmts []*asp.Statement, pos *lsp.Position) map[string]Variable {
   536  	idents := a.IdentsFromStatement(stmts, pos)
   537  
   538  	return a.variablesFromIdents(idents)
   539  }
   540  
   541  func (a *Analyzer) variablesFromIdents(idents chan *Identifier) map[string]Variable {
   542  	vars := make(map[string]Variable)
   543  	for i := range idents {
   544  		if variable := a.VariableFromIdent(i); variable != nil {
   545  			vars[variable.Name] = *variable
   546  		}
   547  	}
   548  
   549  	return vars
   550  }
   551  
   552  // VariableFromIdent returns Variable object passing in an single Identifier
   553  func (a *Analyzer) VariableFromIdent(ident *Identifier) *Variable {
   554  	var varType string
   555  	if ident.Type == "assign" {
   556  		varType = GetValType(ident.Action.Assign.Val)
   557  	} else if ident.Type == "augAssign" {
   558  		varType = GetValType(ident.Action.AugAssign.Val)
   559  	}
   560  
   561  	if varType != "" {
   562  		variable := &Variable{
   563  			Name: ident.Name,
   564  			Type: varType,
   565  		}
   566  		return variable
   567  	}
   568  
   569  	return nil
   570  }
   571  
   572  // GetValType returns a string representation of the type a asp.ValueExpression struct
   573  func GetValType(valExpr *asp.ValueExpression) string {
   574  	if valExpr.String != "" || valExpr.FString != nil {
   575  		return "str"
   576  	} else if valExpr.Int != nil {
   577  		return "int"
   578  	} else if valExpr.Bool != "" {
   579  		return "bool"
   580  	} else if valExpr.Dict != nil {
   581  		return "dict"
   582  	} else if valExpr.List != nil {
   583  		return "list"
   584  	}
   585  
   586  	return ""
   587  }
   588  
   589  func (a *Analyzer) identFromStatement(stmt *asp.Statement) *Identifier {
   590  	// get the identifier type
   591  	var identType string
   592  	if stmt.Ident.Action != nil {
   593  		if stmt.Ident.Action.Property != nil {
   594  			identType = "property"
   595  		} else if stmt.Ident.Action.Call != nil {
   596  			identType = "call"
   597  		} else if stmt.Ident.Action.Assign != nil {
   598  			identType = "assign"
   599  		} else if stmt.Ident.Action.AugAssign != nil {
   600  			identType = "augAssign"
   601  		}
   602  	}
   603  
   604  	ident := &Identifier{
   605  		IdentStatement: stmt.Ident,
   606  		Type:           identType,
   607  		// -1 from asp.Statement.Pos.Line, as lsp position requires zero index
   608  		Pos:    lsp.Position{Line: stmt.Pos.Line - 1, Character: stmt.Pos.Column - 1},
   609  		EndPos: lsp.Position{Line: stmt.EndPos.Line - 1, Character: stmt.EndPos.Column - 1},
   610  	}
   611  
   612  	return ident
   613  }
   614  
   615  // BuildLabelFromString returns a BuildLabel object given a label string
   616  func (a *Analyzer) BuildLabelFromString(ctx context.Context,
   617  	currentURI lsp.DocumentURI, labelStr string) (*BuildLabel, error) {
   618  
   619  	filePath, err := GetPathFromURL(currentURI, "file")
   620  	if err != nil {
   621  		return nil, err
   622  	}
   623  
   624  	label, err := core.TryParseBuildLabel(labelStr, path.Dir(filePath))
   625  	if err != nil {
   626  		return nil, err
   627  	}
   628  
   629  	return a.BuildLabelFromCoreBuildLabel(ctx, label)
   630  }
   631  
   632  // BuildLabelFromCoreBuildLabel returns a BuildLabel object given a core.BuildLabel
   633  func (a *Analyzer) BuildLabelFromCoreBuildLabel(ctx context.Context, label core.BuildLabel) (buildLabel *BuildLabel, err error) {
   634  	if label.IsEmpty() {
   635  		return nil, fmt.Errorf("empty build label %s", label.String())
   636  	}
   637  
   638  	// Get the BUILD file path for the build label
   639  	// Handling subrepo
   640  	if label.Subrepo != "" {
   641  		return &BuildLabel{
   642  			BuildLabel: &label,
   643  			Path:       label.PackageDir(),
   644  			BuildDef:   nil,
   645  			Definition: "Subrepo label: " + label.String(),
   646  		}, nil
   647  	}
   648  
   649  	labelPath := string(a.BuildFileURIFromPackage(label.PackageDir()))
   650  	if labelPath == "" {
   651  		return nil, fmt.Errorf("cannot find the path for build label %s", label.String())
   652  	}
   653  
   654  	// Get the BuildDef and BuildDefContent for the BuildLabel
   655  	var buildDef *BuildDef
   656  	var definition string
   657  
   658  	if label.IsAllSubpackages() {
   659  		// Check for cases such as "//tools/build_langserver/..."
   660  		definition = "BuildLabel includes all subpackages in path: " + path.Join(path.Dir(labelPath))
   661  	} else if label.IsAllTargets() {
   662  		// Check for cases such as "//tools/build_langserver/all"
   663  		definition = "BuildLabel includes all BuildTargets in BUILD file: " + labelPath
   664  	} else {
   665  		buildDef, err = a.BuildDefFromLabel(ctx, &label, labelPath)
   666  		if err != nil {
   667  			return nil, err
   668  		}
   669  		definition = "BUILD Label: " + label.String()
   670  	}
   671  
   672  	return &BuildLabel{
   673  		BuildLabel: &label,
   674  		Path:       labelPath,
   675  		BuildDef:   buildDef,
   676  		Definition: definition,
   677  	}, nil
   678  }
   679  
   680  // BuildDefFromLabel returns a BuildDef struct given an *core.BuildLabel and the path of the label
   681  func (a *Analyzer) BuildDefFromLabel(ctx context.Context, label *core.BuildLabel, path string) (*BuildDef, error) {
   682  	if label.IsAllSubpackages() || label.IsAllTargets() {
   683  		return nil, nil
   684  	}
   685  
   686  	// Get the BuildDef IdentStatement from the build file
   687  	buildDef, err := a.getBuildDefByName(ctx, label.Name, path)
   688  	if err != nil {
   689  		return nil, err
   690  	}
   691  
   692  	// Get the content for the BuildDef
   693  	labelfileContent, err := ReadFile(ctx, lsp.DocumentURI(path))
   694  	if err != nil {
   695  		return nil, err
   696  	}
   697  	buildDef.Content = strings.Join(labelfileContent[buildDef.Pos.Line:buildDef.EndPos.Line+1], "\n")
   698  
   699  	return buildDef, nil
   700  }
   701  
   702  // getBuildDefByName returns an Identifier object of a BuildDef(call of a Build rule)
   703  // based on the name and the buildfile path
   704  func (a *Analyzer) getBuildDefByName(ctx context.Context, name string, path string) (*BuildDef, error) {
   705  	buildDefs, err := a.BuildDefsFromURI(ctx, lsp.DocumentURI(path))
   706  	if err != nil {
   707  		return nil, err
   708  	}
   709  
   710  	if buildDef, ok := buildDefs[name]; ok {
   711  		return buildDef, nil
   712  	}
   713  
   714  	return nil, fmt.Errorf("cannot find BuildDef for the name '%s' in '%s'", name, path)
   715  }
   716  
   717  // RevDepsFromBuildDef returns a a slice of core.BuildLabel object represent the reverse dependency
   718  // of the BuildDef object passed in
   719  func (a *Analyzer) RevDepsFromBuildDef(def *BuildDef, uri lsp.DocumentURI) (core.BuildLabels, error) {
   720  	label, err := getCoreBuildLabel(def, uri)
   721  	if err != nil {
   722  		return nil, err
   723  	}
   724  
   725  	return a.RevDepsFromCoreBuildLabel(label, uri)
   726  }
   727  
   728  // RevDepsFromCoreBuildLabel returns a slice of core.BuildLabel object represent the reverse dependency
   729  // of the core.BuildLabel object passed in
   730  func (a *Analyzer) RevDepsFromCoreBuildLabel(label core.BuildLabel, uri lsp.DocumentURI) (core.BuildLabels, error) {
   731  
   732  	//Ensure we do not get locked out
   733  	state := core.NewBuildState(1, nil, 4, a.State.Config)
   734  	state.NeedBuild = false
   735  	state.NeedTests = false
   736  
   737  	plz.Run([]core.BuildLabel{label}, nil, state, a.State.Config, cli.Arch{})
   738  
   739  	if !state.Success {
   740  		log.Warning("building %s not successful, skipping..", label)
   741  		return nil, nil
   742  	}
   743  	revDeps := query.GetRevDepsLabels(state, []core.BuildLabel{label})
   744  
   745  	return revDeps, nil
   746  }
   747  
   748  // BuildDefsFromPos returns the BuildDef object from the position given if it exists
   749  func (a *Analyzer) BuildDefsFromPos(ctx context.Context, uri lsp.DocumentURI, pos lsp.Position) (*BuildDef, error) {
   750  	defs, err := a.BuildDefsFromURI(ctx, uri)
   751  	if err != nil {
   752  		return nil, err
   753  	}
   754  
   755  	for _, def := range defs {
   756  		if withInRangeLSP(def.Pos, def.EndPos, pos) {
   757  			return def, nil
   758  		}
   759  	}
   760  
   761  	log.Info("BuildDef not found in %s at position:%s", uri, pos)
   762  	return nil, nil
   763  }
   764  
   765  // BuildDefsFromURI returns a map of buildDefname : *BuildDef
   766  func (a *Analyzer) BuildDefsFromURI(ctx context.Context, uri lsp.DocumentURI) (map[string]*BuildDef, error) {
   767  	// Get all the statements from the build file
   768  	stmts, err := a.AspStatementFromFile(uri)
   769  	if err != nil {
   770  		return nil, err
   771  	}
   772  
   773  	return a.BuildDefsFromStatements(ctx, uri, stmts)
   774  }
   775  
   776  // BuildDefsFromStatements takes in the uri of the label, stmts of the build file
   777  // returns a map of buildDefname : *BuildDef
   778  func (a *Analyzer) BuildDefsFromStatements(ctx context.Context, labelURI lsp.DocumentURI,
   779  	stmts []*asp.Statement) (map[string]*BuildDef, error) {
   780  
   781  	buildDefs := make(map[string]*BuildDef)
   782  
   783  	var defaultVisibility []string
   784  	for _, stmt := range stmts {
   785  		if stmt.Ident == nil {
   786  			continue
   787  		}
   788  		ident := a.identFromStatement(stmt)
   789  		if ident.Type != "call" {
   790  			continue
   791  		}
   792  
   793  		// Filling in buildDef struct based on arg
   794  		var buildDef *BuildDef
   795  		for _, arg := range ident.Action.Call.Arguments {
   796  			switch arg.Name {
   797  			case "default_visibility":
   798  				defaultVisibility = aspListToStrSlice(arg.Value.Val.List)
   799  			case "name":
   800  				buildDef = &BuildDef{
   801  					Identifier:   ident,
   802  					BuildDefName: TrimQuotes(arg.Value.Val.String),
   803  					Visibility:   []string{},
   804  				}
   805  			case "visibility":
   806  				if buildDef != nil {
   807  					buildDef.Visibility = append(buildDef.Visibility, aspListToStrSlice(arg.Value.Val.List)...)
   808  				}
   809  			}
   810  		}
   811  
   812  		// Set visibility
   813  		if buildDef != nil {
   814  			if len(buildDef.Visibility) == 0 && len(defaultVisibility) > 0 {
   815  				buildDef.Visibility = defaultVisibility
   816  			}
   817  
   818  			currentPkg, err := PackageLabelFromURI(labelURI)
   819  			if err != nil {
   820  				return nil, err
   821  			}
   822  			buildDef.Visibility = append(buildDef.Visibility, currentPkg)
   823  
   824  			// Get the content for the BuildDef
   825  			labelfileContent, err := ReadFile(ctx, labelURI)
   826  			if err != nil {
   827  				return nil, err
   828  			}
   829  			buildDef.Content = strings.Join(labelfileContent[buildDef.Pos.Line:buildDef.EndPos.Line+1], "\n")
   830  
   831  			buildDefs[buildDef.BuildDefName] = buildDef
   832  		}
   833  	}
   834  	return buildDefs, nil
   835  }
   836  
   837  // BuildFileURIFromPackage takes a relative(to the reporoot) package directory, and returns a build file path
   838  func (a *Analyzer) BuildFileURIFromPackage(packageDir string) lsp.DocumentURI {
   839  	for _, i := range a.State.Config.Parse.BuildFileName {
   840  		buildFilePath := path.Join(packageDir, i)
   841  		if !strings.HasPrefix(packageDir, core.RepoRoot) {
   842  			buildFilePath = path.Join(core.RepoRoot, buildFilePath)
   843  		}
   844  		if fs.FileExists(buildFilePath) {
   845  			return lsp.DocumentURI(buildFilePath)
   846  		}
   847  	}
   848  	return lsp.DocumentURI("")
   849  }
   850  
   851  // IsBuildFile takes a uri path and check if it's a valid build file
   852  func (a *Analyzer) IsBuildFile(uri lsp.DocumentURI) bool {
   853  	filePath, err := GetPathFromURL(uri, "file")
   854  	if err != nil {
   855  		return false
   856  	}
   857  
   858  	base := path.Base(filePath)
   859  	return a.State.Config.IsABuildFile(base)
   860  }
   861  
   862  // GetConfigNames returns a slice of strings config variable names
   863  func (a *Analyzer) GetConfigNames() []string {
   864  	var configs []string
   865  
   866  	for tag := range a.State.Config.TagsToFields() {
   867  		configs = append(configs, tag)
   868  	}
   869  
   870  	return configs
   871  }
   872  
   873  /************************
   874   * Helper functions
   875   ************************/
   876  
   877  // e.g. src type:list, required:false
   878  func getArgString(argument asp.Argument) string {
   879  	argType := strings.Join(argument.Type, "|")
   880  	required := strconv.FormatBool(argument.Value == nil)
   881  
   882  	argString := argument.Name + " required:" + required
   883  	if argType != "" {
   884  		argString += ", type:" + argType
   885  	}
   886  	return argString
   887  }
   888  
   889  func getArgReprs(headerSlice []string) []string {
   890  	re := regexp.MustCompile(`(\(.*\))`)
   891  	allArgs := re.FindString(strings.Join(headerSlice, ""))
   892  
   893  	var args string
   894  	if allArgs != "" {
   895  		args = allArgs[1 : len(allArgs)-1]
   896  	}
   897  
   898  	return strings.Split(args, ",")
   899  }
   900  
   901  func removePrivateArgFromHeader(headerstring string) string {
   902  	newHeader := ""
   903  	argsSplit := strings.Split(headerstring, ",")
   904  	for _, arg := range argsSplit {
   905  		if strings.HasPrefix(strings.TrimSpace(arg), "_") {
   906  			continue
   907  		}
   908  		newHeader += arg + ","
   909  	}
   910  
   911  	newHeader = strings.TrimSuffix(strings.TrimSpace(newHeader), ",")
   912  	if strings.HasSuffix(newHeader, ")") {
   913  		return newHeader
   914  	}
   915  	return newHeader + ")"
   916  }
   917  
   918  func aspListToStrSlice(listVal *asp.List) []string {
   919  	var retSlice []string
   920  
   921  	for _, i := range listVal.Values {
   922  		if i.Val.String != "" {
   923  			retSlice = append(retSlice, TrimQuotes(i.Val.String))
   924  		}
   925  	}
   926  	return retSlice
   927  }
   928  
   929  // getCoreBuildLabel returns a core.BuildLabel object providing a BuildDef and its URI
   930  func getCoreBuildLabel(def *BuildDef, uri lsp.DocumentURI) (buildLabel core.BuildLabel, err error) {
   931  	fp, err := GetPathFromURL(uri, "file")
   932  	if err != nil {
   933  		return core.BuildLabel{}, err
   934  	}
   935  
   936  	rel, err := filepath.Rel(core.RepoRoot, filepath.Dir(fp))
   937  	if err != nil {
   938  		return core.BuildLabel{}, err
   939  	}
   940  
   941  	return core.TryNewBuildLabel(rel, def.BuildDefName)
   942  }