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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclsyntax
     5  
     6  import (
     7  	"reflect"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/hcl/v2"
    11  )
    12  
    13  func TestBlocksAtPos(t *testing.T) {
    14  	tests := map[string]struct {
    15  		Src       string
    16  		Pos       hcl.Pos
    17  		WantTypes []string
    18  	}{
    19  		"empty": {
    20  			``,
    21  			hcl.Pos{Byte: 0},
    22  			nil,
    23  		},
    24  		"spaces": {
    25  			`    `,
    26  			hcl.Pos{Byte: 1},
    27  			nil,
    28  		},
    29  		"single in header": {
    30  			`foo {}`,
    31  			hcl.Pos{Byte: 1},
    32  			[]string{"foo"},
    33  		},
    34  		"single in body": {
    35  			`foo {    }`,
    36  			hcl.Pos{Byte: 7},
    37  			[]string{"foo"},
    38  		},
    39  		"single in body with unselected nested": {
    40  			`
    41  			foo {
    42  
    43  				bar {
    44  
    45  				}
    46  			}
    47  			`,
    48  			hcl.Pos{Byte: 10},
    49  			[]string{"foo"},
    50  		},
    51  		"single in body with unselected sibling": {
    52  			`
    53  			foo {  }
    54  			bar {  }
    55  			`,
    56  			hcl.Pos{Byte: 10},
    57  			[]string{"foo"},
    58  		},
    59  		"selected nested two levels": {
    60  			`
    61  			foo {
    62  				bar {
    63  
    64  				}
    65  			}
    66  			`,
    67  			hcl.Pos{Byte: 20},
    68  			[]string{"foo", "bar"},
    69  		},
    70  		"selected nested three levels": {
    71  			`
    72  			foo {
    73  				bar {
    74  					baz {
    75  
    76  					}
    77  				}
    78  			}
    79  			`,
    80  			hcl.Pos{Byte: 31},
    81  			[]string{"foo", "bar", "baz"},
    82  		},
    83  		"selected nested three levels with unselected sibling after": {
    84  			`
    85  			foo {
    86  				bar {
    87  					baz {
    88  
    89  					}
    90  				}
    91  				not_wanted {}
    92  			}
    93  			`,
    94  			hcl.Pos{Byte: 31},
    95  			[]string{"foo", "bar", "baz"},
    96  		},
    97  		"selected nested three levels with unselected sibling before": {
    98  			`
    99  			foo {
   100  				not_wanted {}
   101  				bar {
   102  					baz {
   103  
   104  					}
   105  				}
   106  			}
   107  			`,
   108  			hcl.Pos{Byte: 49},
   109  			[]string{"foo", "bar", "baz"},
   110  		},
   111  		"unterminated": {
   112  			`foo {    `,
   113  			hcl.Pos{Byte: 7},
   114  			[]string{"foo"},
   115  		},
   116  		"unterminated nested": {
   117  			`
   118  			foo {
   119  				bar {
   120  			}
   121  			`,
   122  			hcl.Pos{Byte: 16},
   123  			[]string{"foo", "bar"},
   124  		},
   125  	}
   126  
   127  	for name, test := range tests {
   128  		t.Run(name, func(t *testing.T) {
   129  			f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1})
   130  			for _, diag := range diags {
   131  				// We intentionally ignore diagnostics here because we should be
   132  				// able to work with the incomplete configuration that results
   133  				// when the parser does its recovery behavior. However, we do
   134  				// log them in case it's helpful to someone debugging a failing
   135  				// test.
   136  				t.Logf(diag.Error())
   137  			}
   138  
   139  			blocks := f.BlocksAtPos(test.Pos)
   140  			outermost := f.OutermostBlockAtPos(test.Pos)
   141  			innermost := f.InnermostBlockAtPos(test.Pos)
   142  
   143  			gotTypes := make([]string, len(blocks))
   144  			for i, block := range blocks {
   145  				gotTypes[i] = block.Type
   146  			}
   147  
   148  			if len(test.WantTypes) == 0 {
   149  				if len(gotTypes) != 0 {
   150  					t.Errorf("wrong block types\ngot:  %#v\nwant: (none)", gotTypes)
   151  				}
   152  				if outermost != nil {
   153  					t.Errorf("wrong outermost type\ngot:  %#v\nwant: (none)", outermost.Type)
   154  				}
   155  				if innermost != nil {
   156  					t.Errorf("wrong innermost type\ngot:  %#v\nwant: (none)", innermost.Type)
   157  				}
   158  				return
   159  			}
   160  
   161  			if !reflect.DeepEqual(gotTypes, test.WantTypes) {
   162  				if len(gotTypes) != 0 {
   163  					t.Errorf("wrong block types\ngot:  %#v\nwant: %#v", gotTypes, test.WantTypes)
   164  				}
   165  			}
   166  			if got, want := outermost.Type, test.WantTypes[0]; got != want {
   167  				t.Errorf("wrong outermost type\ngot:  %#v\nwant: %#v", got, want)
   168  			}
   169  			if got, want := innermost.Type, test.WantTypes[len(test.WantTypes)-1]; got != want {
   170  				t.Errorf("wrong innermost type\ngot:  %#v\nwant: %#v", got, want)
   171  			}
   172  		})
   173  	}
   174  }
   175  
   176  func TestAttributeAtPos(t *testing.T) {
   177  	tests := map[string]struct {
   178  		Src      string
   179  		Pos      hcl.Pos
   180  		WantName string
   181  	}{
   182  		"empty": {
   183  			``,
   184  			hcl.Pos{Byte: 0},
   185  			"",
   186  		},
   187  		"top-level": {
   188  			`foo = 1`,
   189  			hcl.Pos{Byte: 0},
   190  			"foo",
   191  		},
   192  		"top-level with ignored sibling after": {
   193  			`
   194  			foo = 1
   195  			bar = 2
   196  			`,
   197  			hcl.Pos{Byte: 6},
   198  			"foo",
   199  		},
   200  		"top-level ignored sibling before": {
   201  			`
   202  			foo = 1
   203  			bar = 2
   204  			`,
   205  			hcl.Pos{Byte: 17},
   206  			"bar",
   207  		},
   208  		"nested": {
   209  			`
   210  			foo {
   211  				bar = 2
   212  			}
   213  			`,
   214  			hcl.Pos{Byte: 17},
   215  			"bar",
   216  		},
   217  		"nested in unterminated block": {
   218  			`
   219  			foo {
   220  				bar = 2
   221  			`,
   222  			hcl.Pos{Byte: 17},
   223  			"bar",
   224  		},
   225  	}
   226  
   227  	for name, test := range tests {
   228  		t.Run(name, func(t *testing.T) {
   229  			f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1})
   230  			for _, diag := range diags {
   231  				// We intentionally ignore diagnostics here because we should be
   232  				// able to work with the incomplete configuration that results
   233  				// when the parser does its recovery behavior. However, we do
   234  				// log them in case it's helpful to someone debugging a failing
   235  				// test.
   236  				t.Logf(diag.Error())
   237  			}
   238  
   239  			got := f.AttributeAtPos(test.Pos)
   240  
   241  			if test.WantName == "" {
   242  				if got != nil {
   243  					t.Errorf("wrong attribute name\ngot:  %#v\nwant: (none)", got.Name)
   244  				}
   245  				return
   246  			}
   247  
   248  			if got == nil {
   249  				t.Fatalf("wrong attribute name\ngot:  (none)\nwant: %#v", test.WantName)
   250  			}
   251  
   252  			if got.Name != test.WantName {
   253  				t.Errorf("wrong attribute name\ngot:  %#v\nwant: %#v", got.Name, test.WantName)
   254  			}
   255  		})
   256  	}
   257  }
   258  
   259  func TestOutermostExprAtPos(t *testing.T) {
   260  	tests := map[string]struct {
   261  		Src     string
   262  		Pos     hcl.Pos
   263  		WantSrc string
   264  	}{
   265  		"empty": {
   266  			``,
   267  			hcl.Pos{Byte: 0},
   268  			``,
   269  		},
   270  		"simple bool": {
   271  			`a = true`,
   272  			hcl.Pos{Byte: 6},
   273  			`true`,
   274  		},
   275  		"simple reference": {
   276  			`a = blah`,
   277  			hcl.Pos{Byte: 6},
   278  			`blah`,
   279  		},
   280  		"attribute reference": {
   281  			`a = blah.foo`,
   282  			hcl.Pos{Byte: 6},
   283  			`blah.foo`,
   284  		},
   285  		"parens": {
   286  			`a = (1 + 1)`,
   287  			hcl.Pos{Byte: 6},
   288  			`(1 + 1)`,
   289  		},
   290  		"tuple cons": {
   291  			`a = [1, 2, 3]`,
   292  			hcl.Pos{Byte: 5},
   293  			`[1, 2, 3]`,
   294  		},
   295  		"function call": {
   296  			`a = foom("a")`,
   297  			hcl.Pos{Byte: 10},
   298  			`foom("a")`,
   299  		},
   300  	}
   301  
   302  	for name, test := range tests {
   303  		t.Run(name, func(t *testing.T) {
   304  			inputSrc := []byte(test.Src)
   305  			f, diags := ParseConfig(inputSrc, "", hcl.Pos{Line: 1, Column: 1})
   306  			for _, diag := range diags {
   307  				// We intentionally ignore diagnostics here because we should be
   308  				// able to work with the incomplete configuration that results
   309  				// when the parser does its recovery behavior. However, we do
   310  				// log them in case it's helpful to someone debugging a failing
   311  				// test.
   312  				t.Logf(diag.Error())
   313  			}
   314  
   315  			gotExpr := f.OutermostExprAtPos(test.Pos)
   316  			var gotSrc string
   317  			if gotExpr != nil {
   318  				rng := gotExpr.Range()
   319  				gotSrc = string(rng.SliceBytes(inputSrc))
   320  			}
   321  
   322  			if test.WantSrc == "" {
   323  				if gotExpr != nil {
   324  					t.Errorf("wrong expression source\ngot:  %s\nwant: (none)", gotSrc)
   325  				}
   326  				return
   327  			}
   328  
   329  			if gotExpr == nil {
   330  				t.Fatalf("wrong expression source\ngot:  (none)\nwant: %s", test.WantSrc)
   331  			}
   332  
   333  			if gotSrc != test.WantSrc {
   334  				t.Errorf("wrong expression source\ngot:  %#v\nwant: %#v", gotSrc, test.WantSrc)
   335  			}
   336  		})
   337  	}
   338  }