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 }