internal/parser/parser_utils.go (105 lines of code) (raw):

package parser import ( "fmt" "strings" "github.com/Azure/azurerm-lsp/internal/lsp" "github.com/Azure/azurerm-lsp/internal/protocol" "github.com/Azure/azurerm-lsp/provider-schema/azurerm/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" ) type HCLContext struct { File *hcl.File Body *hclsyntax.Body Block *hclsyntax.Block SubBlock *hclsyntax.Block Attribute *hclsyntax.Attribute Resource string ParsedPath string } func BuildHCLContext(docContent, fileName string, position protocol.Position) (*HCLContext, hcl.Diagnostics, error) { file, diags := hclsyntax.ParseConfig([]byte(docContent), fileName, hcl.InitialPos) if diags != nil && diags.HasErrors() { return nil, diags, fmt.Errorf("failed to parse HCL: %v", diags) } body, ok := file.Body.(*hclsyntax.Body) if !ok { return nil, nil, fmt.Errorf("file is not valid HCL body") } hclPos := lsp.LSPPosToHCL(position) block := BlockAtPos(body, hclPos) if block == nil || len(block.Labels) == 0 { return nil, nil, fmt.Errorf("no valid block found") } resource := block.Labels[0] if !strings.HasPrefix(resource, schema.AzureRMPrefix) { return nil, nil, fmt.Errorf("not an AzureRM resource") } subBlock := BlockAtPos(block.Body, hclPos) attr := AttributeAtPos(block, hclPos) if attr == nil && subBlock != nil { attr = AttributeAtPos(subBlock, hclPos) } var path string if attr != nil { path = buildNestedPath(block, attr) } else if subBlock != nil { path = buildNestedPath(block, subBlock) } return &HCLContext{ File: file, Body: body, Block: block, SubBlock: subBlock, Attribute: attr, Resource: resource, ParsedPath: path, }, nil, nil } func buildNestedPath(topLevelBlock *hclsyntax.Block, targetNode hclsyntax.Node) string { var pathParts []string var traverse func(block *hclsyntax.Block, target hclsyntax.Node) bool traverse = func(block *hclsyntax.Block, target hclsyntax.Node) bool { for _, attr := range block.Body.Attributes { if attr == target { pathParts = append(pathParts, attr.Name) return true } } for _, nested := range block.Body.Blocks { if nested == target { pathParts = append(pathParts, nested.Type) return true } if traverse(nested, target) { pathParts = append([]string{nested.Type}, pathParts...) return true } } return false } if traverse(topLevelBlock, targetNode) { return strings.Join(pathParts, ".") } return "" } func AttemptReparse(content string, lineNum uint32) (updatedContent, fieldName string, isNewBlock bool, err error) { lineContents := strings.Split(content, "\n") if lineNum < 0 || int(lineNum) >= len(lineContents) { return "", "", false, fmt.Errorf("invalid line number") } lineContent := strings.TrimSpace(lineContents[lineNum]) if lineContent == "" { return "", "", true, fmt.Errorf("empty line content") } fieldParts := strings.Split(lineContent, "=") if len(fieldParts) == 0 { return "", "", true, fmt.Errorf("invalid line content") } updatedContent = strings.Join(lineContents[:lineNum], "\n") + "\n\n" + strings.Join(lineContents[lineNum+1:], "\n") fieldName = strings.TrimSpace(fieldParts[0]) if fieldName == "" { return "", "", true, fmt.Errorf("invalid field name") } return updatedContent, fieldName, false, nil }