github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/structure.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclsyntax
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/hcl/v2"
    11  )
    12  
    13  // AsHCLBlock returns the block data expressed as a *hcl.Block.
    14  func (b *Block) AsHCLBlock() *hcl.Block {
    15  	if b == nil {
    16  		return nil
    17  	}
    18  
    19  	return &hcl.Block{
    20  		Type:   b.Type,
    21  		Labels: b.Labels,
    22  		Body:   b.Body,
    23  
    24  		DefRange:    b.DefRange(),
    25  		TypeRange:   b.TypeRange,
    26  		LabelRanges: b.LabelRanges,
    27  	}
    28  }
    29  
    30  // Body is the implementation of hcl.Body for the HCL native syntax.
    31  type Body struct {
    32  	Attributes Attributes
    33  	Blocks     Blocks
    34  
    35  	// These are used with PartialContent to produce a "remaining items"
    36  	// body to return. They are nil on all bodies fresh out of the parser.
    37  	hiddenAttrs  map[string]struct{}
    38  	hiddenBlocks map[string]struct{}
    39  
    40  	SrcRange hcl.Range
    41  	EndRange hcl.Range // Final token of the body (zero-length range)
    42  }
    43  
    44  // Assert that *Body implements hcl.Body
    45  var assertBodyImplBody hcl.Body = &Body{}
    46  
    47  func (b *Body) walkChildNodes(w internalWalkFunc) {
    48  	w(b.Attributes)
    49  	w(b.Blocks)
    50  }
    51  
    52  func (b *Body) Range() hcl.Range {
    53  	return b.SrcRange
    54  }
    55  
    56  func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
    57  	content, remainHCL, diags := b.PartialContent(schema)
    58  
    59  	// No we'll see if anything actually remains, to produce errors about
    60  	// extraneous items.
    61  	remain := remainHCL.(*Body)
    62  
    63  	for name, attr := range b.Attributes {
    64  		if _, hidden := remain.hiddenAttrs[name]; !hidden {
    65  			var suggestions []string
    66  			for _, attrS := range schema.Attributes {
    67  				if _, defined := content.Attributes[attrS.Name]; defined {
    68  					continue
    69  				}
    70  				suggestions = append(suggestions, attrS.Name)
    71  			}
    72  			suggestion := nameSuggestion(name, suggestions)
    73  			if suggestion != "" {
    74  				suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
    75  			} else {
    76  				// Is there a block of the same name?
    77  				for _, blockS := range schema.Blocks {
    78  					if blockS.Type == name {
    79  						suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name)
    80  						break
    81  					}
    82  				}
    83  			}
    84  
    85  			diags = append(diags, &hcl.Diagnostic{
    86  				Severity: hcl.DiagError,
    87  				Summary:  "Unsupported argument",
    88  				Detail:   fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
    89  				Subject:  &attr.NameRange,
    90  			})
    91  		}
    92  	}
    93  
    94  	for _, block := range b.Blocks {
    95  		blockTy := block.Type
    96  		if _, hidden := remain.hiddenBlocks[blockTy]; !hidden {
    97  			var suggestions []string
    98  			for _, blockS := range schema.Blocks {
    99  				suggestions = append(suggestions, blockS.Type)
   100  			}
   101  			suggestion := nameSuggestion(blockTy, suggestions)
   102  			if suggestion != "" {
   103  				suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   104  			} else {
   105  				// Is there an attribute of the same name?
   106  				for _, attrS := range schema.Attributes {
   107  					if attrS.Name == blockTy {
   108  						suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy)
   109  						break
   110  					}
   111  				}
   112  			}
   113  
   114  			diags = append(diags, &hcl.Diagnostic{
   115  				Severity: hcl.DiagError,
   116  				Summary:  "Unsupported block type",
   117  				Detail:   fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
   118  				Subject:  &block.TypeRange,
   119  			})
   120  		}
   121  	}
   122  
   123  	return content, diags
   124  }
   125  
   126  func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
   127  	attrs := make(hcl.Attributes)
   128  	var blocks hcl.Blocks
   129  	var diags hcl.Diagnostics
   130  	hiddenAttrs := make(map[string]struct{})
   131  	hiddenBlocks := make(map[string]struct{})
   132  
   133  	if b.hiddenAttrs != nil {
   134  		for k, v := range b.hiddenAttrs {
   135  			hiddenAttrs[k] = v
   136  		}
   137  	}
   138  	if b.hiddenBlocks != nil {
   139  		for k, v := range b.hiddenBlocks {
   140  			hiddenBlocks[k] = v
   141  		}
   142  	}
   143  
   144  	for _, attrS := range schema.Attributes {
   145  		name := attrS.Name
   146  		attr, exists := b.Attributes[name]
   147  		_, hidden := hiddenAttrs[name]
   148  		if hidden || !exists {
   149  			if attrS.Required {
   150  				diags = append(diags, &hcl.Diagnostic{
   151  					Severity: hcl.DiagError,
   152  					Summary:  "Missing required argument",
   153  					Detail:   fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
   154  					Subject:  b.MissingItemRange().Ptr(),
   155  				})
   156  			}
   157  			continue
   158  		}
   159  
   160  		hiddenAttrs[name] = struct{}{}
   161  		attrs[name] = attr.AsHCLAttribute()
   162  	}
   163  
   164  	blocksWanted := make(map[string]hcl.BlockHeaderSchema)
   165  	for _, blockS := range schema.Blocks {
   166  		blocksWanted[blockS.Type] = blockS
   167  	}
   168  
   169  	for _, block := range b.Blocks {
   170  		if _, hidden := hiddenBlocks[block.Type]; hidden {
   171  			continue
   172  		}
   173  		blockS, wanted := blocksWanted[block.Type]
   174  		if !wanted {
   175  			continue
   176  		}
   177  
   178  		if len(block.Labels) > len(blockS.LabelNames) {
   179  			name := block.Type
   180  			if len(blockS.LabelNames) == 0 {
   181  				diags = append(diags, &hcl.Diagnostic{
   182  					Severity: hcl.DiagError,
   183  					Summary:  fmt.Sprintf("Extraneous label for %s", name),
   184  					Detail: fmt.Sprintf(
   185  						"No labels are expected for %s blocks.", name,
   186  					),
   187  					Subject: block.LabelRanges[0].Ptr(),
   188  					Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
   189  				})
   190  			} else {
   191  				diags = append(diags, &hcl.Diagnostic{
   192  					Severity: hcl.DiagError,
   193  					Summary:  fmt.Sprintf("Extraneous label for %s", name),
   194  					Detail: fmt.Sprintf(
   195  						"Only %d labels (%s) are expected for %s blocks.",
   196  						len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), name,
   197  					),
   198  					Subject: block.LabelRanges[len(blockS.LabelNames)].Ptr(),
   199  					Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
   200  				})
   201  			}
   202  			continue
   203  		}
   204  
   205  		if len(block.Labels) < len(blockS.LabelNames) {
   206  			name := block.Type
   207  			diags = append(diags, &hcl.Diagnostic{
   208  				Severity: hcl.DiagError,
   209  				Summary:  fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(block.Labels)], name),
   210  				Detail: fmt.Sprintf(
   211  					"All %s blocks must have %d labels (%s).",
   212  					name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "),
   213  				),
   214  				Subject: &block.OpenBraceRange,
   215  				Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
   216  			})
   217  			continue
   218  		}
   219  
   220  		blocks = append(blocks, block.AsHCLBlock())
   221  	}
   222  
   223  	// We hide blocks only after we've processed all of them, since otherwise
   224  	// we can't process more than one of the same type.
   225  	for _, blockS := range schema.Blocks {
   226  		hiddenBlocks[blockS.Type] = struct{}{}
   227  	}
   228  
   229  	remain := &Body{
   230  		Attributes: b.Attributes,
   231  		Blocks:     b.Blocks,
   232  
   233  		hiddenAttrs:  hiddenAttrs,
   234  		hiddenBlocks: hiddenBlocks,
   235  
   236  		SrcRange: b.SrcRange,
   237  		EndRange: b.EndRange,
   238  	}
   239  
   240  	return &hcl.BodyContent{
   241  		Attributes: attrs,
   242  		Blocks:     blocks,
   243  
   244  		MissingItemRange: b.MissingItemRange(),
   245  	}, remain, diags
   246  }
   247  
   248  func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
   249  	attrs := make(hcl.Attributes)
   250  	var diags hcl.Diagnostics
   251  
   252  	if len(b.Blocks) > 0 {
   253  		example := b.Blocks[0]
   254  		diags = append(diags, &hcl.Diagnostic{
   255  			Severity: hcl.DiagError,
   256  			Summary:  fmt.Sprintf("Unexpected %q block", example.Type),
   257  			Detail:   "Blocks are not allowed here.",
   258  			Subject:  &example.TypeRange,
   259  		})
   260  		// we will continue processing anyway, and return the attributes
   261  		// we are able to find so that certain analyses can still be done
   262  		// in the face of errors.
   263  	}
   264  
   265  	if b.Attributes == nil {
   266  		return attrs, diags
   267  	}
   268  
   269  	for name, attr := range b.Attributes {
   270  		if _, hidden := b.hiddenAttrs[name]; hidden {
   271  			continue
   272  		}
   273  		attrs[name] = attr.AsHCLAttribute()
   274  	}
   275  
   276  	return attrs, diags
   277  }
   278  
   279  func (b *Body) MissingItemRange() hcl.Range {
   280  	return hcl.Range{
   281  		Filename: b.SrcRange.Filename,
   282  		Start:    b.SrcRange.Start,
   283  		End:      b.SrcRange.Start,
   284  	}
   285  }
   286  
   287  // Attributes is the collection of attribute definitions within a body.
   288  type Attributes map[string]*Attribute
   289  
   290  func (a Attributes) walkChildNodes(w internalWalkFunc) {
   291  	for _, attr := range a {
   292  		w(attr)
   293  	}
   294  }
   295  
   296  // Range returns the range of some arbitrary point within the set of
   297  // attributes, or an invalid range if there are no attributes.
   298  //
   299  // This is provided only to complete the Node interface, but has no practical
   300  // use.
   301  func (a Attributes) Range() hcl.Range {
   302  	// An attributes doesn't really have a useful range to report, since
   303  	// it's just a grouping construct. So we'll arbitrarily take the
   304  	// range of one of the attributes, or produce an invalid range if we have
   305  	// none. In practice, there's little reason to ask for the range of
   306  	// an Attributes.
   307  	for _, attr := range a {
   308  		return attr.Range()
   309  	}
   310  	return hcl.Range{
   311  		Filename: "<unknown>",
   312  	}
   313  }
   314  
   315  // Attribute represents a single attribute definition within a body.
   316  type Attribute struct {
   317  	Name string
   318  	Expr Expression
   319  
   320  	SrcRange    hcl.Range
   321  	NameRange   hcl.Range
   322  	EqualsRange hcl.Range
   323  }
   324  
   325  func (a *Attribute) walkChildNodes(w internalWalkFunc) {
   326  	w(a.Expr)
   327  }
   328  
   329  func (a *Attribute) Range() hcl.Range {
   330  	return a.SrcRange
   331  }
   332  
   333  // AsHCLAttribute returns the block data expressed as a *hcl.Attribute.
   334  func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
   335  	if a == nil {
   336  		return nil
   337  	}
   338  	return &hcl.Attribute{
   339  		Name: a.Name,
   340  		Expr: a.Expr,
   341  
   342  		Range:     a.SrcRange,
   343  		NameRange: a.NameRange,
   344  	}
   345  }
   346  
   347  // Blocks is the list of nested blocks within a body.
   348  type Blocks []*Block
   349  
   350  func (bs Blocks) walkChildNodes(w internalWalkFunc) {
   351  	for _, block := range bs {
   352  		w(block)
   353  	}
   354  }
   355  
   356  // Range returns the range of some arbitrary point within the list of
   357  // blocks, or an invalid range if there are no blocks.
   358  //
   359  // This is provided only to complete the Node interface, but has no practical
   360  // use.
   361  func (bs Blocks) Range() hcl.Range {
   362  	if len(bs) > 0 {
   363  		return bs[0].Range()
   364  	}
   365  	return hcl.Range{
   366  		Filename: "<unknown>",
   367  	}
   368  }
   369  
   370  // Block represents a nested block structure
   371  type Block struct {
   372  	Type   string
   373  	Labels []string
   374  	Body   *Body
   375  
   376  	TypeRange       hcl.Range
   377  	LabelRanges     []hcl.Range
   378  	OpenBraceRange  hcl.Range
   379  	CloseBraceRange hcl.Range
   380  }
   381  
   382  func (b *Block) walkChildNodes(w internalWalkFunc) {
   383  	w(b.Body)
   384  }
   385  
   386  func (b *Block) Range() hcl.Range {
   387  	return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
   388  }
   389  
   390  func (b *Block) DefRange() hcl.Range {
   391  	lastHeaderRange := b.TypeRange
   392  	if len(b.LabelRanges) > 0 {
   393  		lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
   394  	}
   395  	return hcl.RangeBetween(b.TypeRange, lastHeaderRange)
   396  }