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 }