github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/internal/token_scanner_test.go (about)

     1  package internal
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/google/go-cmp/cmp"
     7  	"github.com/google/go-cmp/cmp/cmpopts"
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hclsyntax"
    10  )
    11  
    12  func TestSeek(t *testing.T) {
    13  	tests := []struct {
    14  		name      string
    15  		source    string
    16  		seek      func(*tokenScanner) error
    17  		wantPos   hcl.Pos
    18  		wantToken hclsyntax.Token
    19  	}{
    20  		{
    21  			name:   "seek to initial position",
    22  			source: `foo = 1`,
    23  			seek: func(s *tokenScanner) error {
    24  				return s.seek(hcl.InitialPos, tokenStart)
    25  			},
    26  			wantPos: hcl.InitialPos,
    27  			wantToken: hclsyntax.Token{
    28  				Type:  hclsyntax.TokenIdent,
    29  				Bytes: []byte("foo"),
    30  			},
    31  		},
    32  		{
    33  			name:   "seek to forward with tokenStart",
    34  			source: `foo = 1`,
    35  			seek: func(s *tokenScanner) error {
    36  				return s.seek(hcl.Pos{Line: 1, Column: 7, Byte: 6}, tokenStart)
    37  			},
    38  			wantPos: hcl.Pos{Line: 1, Column: 7, Byte: 6},
    39  			wantToken: hclsyntax.Token{
    40  				Type:  hclsyntax.TokenNumberLit,
    41  				Bytes: []byte("1"),
    42  			},
    43  		},
    44  		{
    45  			name:   "seek to forward with tokenEnd",
    46  			source: `foo = 1`,
    47  			seek: func(s *tokenScanner) error {
    48  				return s.seek(hcl.Pos{Line: 1, Column: 8, Byte: 7}, tokenEnd)
    49  			},
    50  			wantPos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
    51  			wantToken: hclsyntax.Token{
    52  				Type:  hclsyntax.TokenNumberLit,
    53  				Bytes: []byte("1"),
    54  			},
    55  		},
    56  		{
    57  			name:   "seek to backward with tokenStart",
    58  			source: `foo = 1`,
    59  			seek: func(s *tokenScanner) error {
    60  				s.seek(hcl.Pos{Line: 1, Column: 7, Byte: 6}, tokenStart)
    61  				return s.seek(hcl.Pos{Line: 1, Column: 5, Byte: 4}, tokenStart)
    62  			},
    63  			wantPos: hcl.Pos{Line: 1, Column: 5, Byte: 4},
    64  			wantToken: hclsyntax.Token{
    65  				Type:  hclsyntax.TokenEqual,
    66  				Bytes: []byte("="),
    67  			},
    68  		},
    69  		{
    70  			name:   "seek to backward with tokenEnd",
    71  			source: `foo = 1`,
    72  			seek: func(s *tokenScanner) error {
    73  				s.seek(hcl.Pos{Line: 1, Column: 7, Byte: 6}, tokenStart)
    74  				return s.seek(hcl.Pos{Line: 1, Column: 6, Byte: 5}, tokenEnd)
    75  			},
    76  			wantPos: hcl.Pos{Line: 1, Column: 6, Byte: 5},
    77  			wantToken: hclsyntax.Token{
    78  				Type:  hclsyntax.TokenEqual,
    79  				Bytes: []byte("="),
    80  			},
    81  		},
    82  	}
    83  
    84  	for _, test := range tests {
    85  		t.Run(test.name, func(t *testing.T) {
    86  			scanner, diags := newTokenScanner([]byte(test.source), "main.tf")
    87  			if diags.HasErrors() {
    88  				t.Fatalf("failed to set up token scanner: %s", diags)
    89  			}
    90  			if err := test.seek(scanner); err != nil {
    91  				t.Fatalf("failed to seek: %s", err)
    92  			}
    93  
    94  			if diff := cmp.Diff(test.wantPos, scanner.pos); diff != "" {
    95  				t.Errorf("position mismatch: %s", diff)
    96  			}
    97  			opt := cmpopts.IgnoreFields(hclsyntax.Token{}, "Range")
    98  			if diff := cmp.Diff(test.wantToken, scanner.token(), opt); diff != "" {
    99  				t.Errorf("token mismatch: %s", diff)
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func TestScan(t *testing.T) {
   106  	type scanResult struct {
   107  		Pos   hcl.Pos
   108  		Token hclsyntax.Token
   109  	}
   110  
   111  	tests := []struct {
   112  		name        string
   113  		source      string
   114  		seek        func(*tokenScanner) error
   115  		scanResults []scanResult
   116  		want        hcl.Pos
   117  	}{
   118  		{
   119  			name:   "scan all tokens",
   120  			source: `foo = 1`,
   121  			seek:   func(s *tokenScanner) error { return nil },
   122  			scanResults: []scanResult{
   123  				{
   124  					Pos: hcl.Pos{Line: 1, Column: 6, Byte: 5},
   125  					Token: hclsyntax.Token{
   126  						Type:  hclsyntax.TokenEqual,
   127  						Bytes: []byte("="),
   128  					},
   129  				},
   130  				{
   131  					Pos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   132  					Token: hclsyntax.Token{
   133  						Type:  hclsyntax.TokenNumberLit,
   134  						Bytes: []byte("1"),
   135  					},
   136  				},
   137  				{
   138  					Pos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   139  					Token: hclsyntax.Token{
   140  						Type:  hclsyntax.TokenEOF,
   141  						Bytes: []byte{},
   142  					},
   143  				},
   144  			},
   145  			want: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   146  		},
   147  		{
   148  			name:   "scan tokens from the middle",
   149  			source: `foo = 1`,
   150  			seek: func(s *tokenScanner) error {
   151  				return s.seek(hcl.Pos{Line: 1, Column: 5, Byte: 4}, tokenStart)
   152  			},
   153  			scanResults: []scanResult{
   154  				{
   155  					Pos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   156  					Token: hclsyntax.Token{
   157  						Type:  hclsyntax.TokenNumberLit,
   158  						Bytes: []byte("1"),
   159  					},
   160  				},
   161  				{
   162  					Pos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   163  					Token: hclsyntax.Token{
   164  						Type:  hclsyntax.TokenEOF,
   165  						Bytes: []byte{},
   166  					},
   167  				},
   168  			},
   169  			want: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   170  		},
   171  		{
   172  			name:   "scan tokens from tokenEnd",
   173  			source: `foo = 1`,
   174  			seek: func(s *tokenScanner) error {
   175  				return s.seek(hcl.Pos{Line: 1, Column: 6, Byte: 5}, tokenEnd)
   176  			},
   177  			scanResults: []scanResult{
   178  				{
   179  					Pos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   180  					Token: hclsyntax.Token{
   181  						Type:  hclsyntax.TokenNumberLit,
   182  						Bytes: []byte("1"),
   183  					},
   184  				},
   185  				{
   186  					Pos: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   187  					Token: hclsyntax.Token{
   188  						Type:  hclsyntax.TokenEOF,
   189  						Bytes: []byte{},
   190  					},
   191  				},
   192  			},
   193  			want: hcl.Pos{Line: 1, Column: 8, Byte: 7},
   194  		},
   195  		{
   196  			name:   "no scan",
   197  			source: `foo = 1`,
   198  			seek: func(s *tokenScanner) error {
   199  				return s.seek(hcl.Pos{Line: 1, Column: 8, Byte: 7}, tokenStart)
   200  			},
   201  			scanResults: []scanResult{},
   202  			want:        hcl.Pos{Line: 1, Column: 8, Byte: 7},
   203  		},
   204  	}
   205  
   206  	for _, test := range tests {
   207  		t.Run(test.name, func(t *testing.T) {
   208  			scanner, diags := newTokenScanner([]byte(test.source), "main.tf")
   209  			if diags.HasErrors() {
   210  				t.Fatalf("failed to set up token scanner: %s", diags)
   211  			}
   212  			if err := test.seek(scanner); err != nil {
   213  				t.Fatalf("failed to seek: %s", err)
   214  			}
   215  
   216  			scanResults := []scanResult{}
   217  			for scanner.scan() {
   218  				scanResults = append(scanResults, scanResult{
   219  					Pos:   scanner.pos,
   220  					Token: scanner.token(),
   221  				})
   222  			}
   223  
   224  			opt := cmpopts.IgnoreFields(hclsyntax.Token{}, "Range")
   225  			if diff := cmp.Diff(test.scanResults, scanResults, opt); diff != "" {
   226  				t.Errorf("scan result mismatch: %s", diff)
   227  			}
   228  			if diff := cmp.Diff(test.want, scanner.pos); diff != "" {
   229  				t.Errorf("position mismatch: %s", diff)
   230  			}
   231  		})
   232  	}
   233  }
   234  
   235  func TestScanBackward(t *testing.T) {
   236  	type scanResult struct {
   237  		Pos   hcl.Pos
   238  		Token hclsyntax.Token
   239  	}
   240  
   241  	tests := []struct {
   242  		name        string
   243  		source      string
   244  		seek        func(*tokenScanner) error
   245  		scanResults []scanResult
   246  		want        hcl.Pos
   247  	}{
   248  		{
   249  			name:   "scan all tokens",
   250  			source: `foo = 1`,
   251  			seek: func(s *tokenScanner) error {
   252  				return s.seek(hcl.Pos{Line: 1, Column: 8, Byte: 7}, tokenStart)
   253  			},
   254  			scanResults: []scanResult{
   255  				{
   256  					Pos: hcl.Pos{Line: 1, Column: 7, Byte: 6},
   257  					Token: hclsyntax.Token{
   258  						Type:  hclsyntax.TokenNumberLit,
   259  						Bytes: []byte("1"),
   260  					},
   261  				},
   262  				{
   263  					Pos: hcl.Pos{Line: 1, Column: 5, Byte: 4},
   264  					Token: hclsyntax.Token{
   265  						Type:  hclsyntax.TokenEqual,
   266  						Bytes: []byte("="),
   267  					},
   268  				},
   269  				{
   270  					Pos: hcl.Pos{Line: 1, Column: 1, Byte: 0},
   271  					Token: hclsyntax.Token{
   272  						Type:  hclsyntax.TokenIdent,
   273  						Bytes: []byte("foo"),
   274  					},
   275  				},
   276  			},
   277  			want: hcl.Pos{Line: 1, Column: 1, Byte: 0},
   278  		},
   279  		{
   280  			name:   "scan tokens from the middle",
   281  			source: `foo = 1`,
   282  			seek: func(s *tokenScanner) error {
   283  				return s.seek(hcl.Pos{Line: 1, Column: 6, Byte: 5}, tokenEnd)
   284  			},
   285  			scanResults: []scanResult{
   286  				{
   287  					Pos: hcl.Pos{Line: 1, Column: 1, Byte: 0},
   288  					Token: hclsyntax.Token{
   289  						Type:  hclsyntax.TokenIdent,
   290  						Bytes: []byte("foo"),
   291  					},
   292  				},
   293  			},
   294  			want: hcl.Pos{Line: 1, Column: 1, Byte: 0},
   295  		},
   296  		{
   297  			name:   "scan tokens from tokenStart",
   298  			source: `foo = 1`,
   299  			seek: func(s *tokenScanner) error {
   300  				return s.seek(hcl.Pos{Line: 1, Column: 5, Byte: 4}, tokenStart)
   301  			},
   302  			scanResults: []scanResult{
   303  				{
   304  					Pos: hcl.Pos{Line: 1, Column: 1, Byte: 0},
   305  					Token: hclsyntax.Token{
   306  						Type:  hclsyntax.TokenIdent,
   307  						Bytes: []byte("foo"),
   308  					},
   309  				},
   310  			},
   311  			want: hcl.Pos{Line: 1, Column: 1, Byte: 0},
   312  		},
   313  		{
   314  			name:        "no scan",
   315  			source:      `foo = 1`,
   316  			seek:        func(s *tokenScanner) error { return nil },
   317  			scanResults: []scanResult{},
   318  			want:        hcl.Pos{Line: 1, Column: 1, Byte: 0},
   319  		},
   320  		{
   321  			name:   "no scan from endToken",
   322  			source: `foo = 1`,
   323  			seek: func(s *tokenScanner) error {
   324  				s.seekTokenEnd()
   325  				return nil
   326  			},
   327  			scanResults: []scanResult{},
   328  			want:        hcl.Pos{Line: 1, Column: 1, Byte: 0},
   329  		},
   330  	}
   331  
   332  	for _, test := range tests {
   333  		t.Run(test.name, func(t *testing.T) {
   334  			scanner, diags := newTokenScanner([]byte(test.source), "main.tf")
   335  			if diags.HasErrors() {
   336  				t.Fatalf("failed to set up token scanner: %s", diags)
   337  			}
   338  			if err := test.seek(scanner); err != nil {
   339  				t.Fatalf("failed to seek: %s", err)
   340  			}
   341  
   342  			scanResults := []scanResult{}
   343  			for scanner.scanBackward() {
   344  				scanResults = append(scanResults, scanResult{
   345  					Pos:   scanner.pos,
   346  					Token: scanner.token(),
   347  				})
   348  			}
   349  
   350  			opt := cmpopts.IgnoreFields(hclsyntax.Token{}, "Range")
   351  			if diff := cmp.Diff(test.scanResults, scanResults, opt); diff != "" {
   352  				t.Errorf("scan result mismatch: %s", diff)
   353  			}
   354  			if diff := cmp.Diff(test.want, scanner.pos); diff != "" {
   355  				t.Errorf("position mismatch: %s", diff)
   356  			}
   357  		})
   358  	}
   359  }