github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/converter/complete_test.go (about) 1 package converter 2 3 import ( 4 "go/token" 5 "reflect" 6 "strings" 7 "testing" 8 ) 9 10 func TestIdentifierAt(t *testing.T) { 11 type args struct { 12 src string 13 idx int 14 } 15 tests := []struct { 16 name string 17 args args 18 wantStart int 19 wantEnd int 20 }{ 21 { 22 name: "basic", 23 args: args{"abc", 0}, 24 wantStart: 0, 25 wantEnd: 3, 26 }, { 27 name: "basic", 28 args: args{"_a", 0}, 29 wantStart: 0, 30 wantEnd: 2, 31 }, { 32 args: args{"abc", 1}, 33 wantStart: 0, 34 wantEnd: 3, 35 }, { 36 args: args{"abc", 3}, 37 wantStart: 0, 38 wantEnd: 3, 39 }, { 40 args: args{"abc", 10}, 41 wantStart: -1, 42 wantEnd: -1, 43 }, { 44 args: args{"abc", -1}, 45 wantStart: -1, 46 wantEnd: -1, 47 }, { 48 args: args{"1034", 2}, 49 wantStart: -1, 50 wantEnd: -1, 51 }, { 52 args: args{"a034", 2}, 53 wantStart: 0, 54 wantEnd: 4, 55 }, { 56 args: args{"a+b", 2}, 57 wantStart: 2, 58 wantEnd: 3, 59 }, { 60 args: args{"a+b", 1}, 61 wantStart: 0, 62 wantEnd: 1, 63 }, { 64 name: "multibytes", 65 args: args{"こんにちは", 6}, 66 wantStart: 0, 67 wantEnd: 15, 68 }, { 69 name: "multibytes_invalidpos", 70 args: args{"こんにちは", 5}, 71 wantStart: -1, 72 wantEnd: -1, 73 }, 74 } 75 for _, tt := range tests { 76 t.Run(tt.name, func(t *testing.T) { 77 gotStart, gotEnd := identifierAt(tt.args.src, tt.args.idx) 78 if gotStart != tt.wantStart { 79 t.Errorf("identifierAt() gotStart = %v, want %v", gotStart, tt.wantStart) 80 } 81 if gotEnd != tt.wantEnd { 82 t.Errorf("identifierAt() gotEnd = %v, want %v", gotEnd, tt.wantEnd) 83 } 84 }) 85 } 86 } 87 88 func Test_findLastDot(t *testing.T) { 89 type args struct { 90 src string 91 idx int 92 } 93 tests := []struct { 94 name string 95 args args 96 wantDot int 97 wantIDStart int 98 wantIDEnd int 99 }{ 100 { 101 name: "basic", 102 args: args{"ab.cd", 3}, 103 wantDot: 2, 104 wantIDStart: 3, 105 wantIDEnd: 5, 106 }, { 107 name: "eos", 108 args: args{"ab.cd", 5}, 109 wantDot: 2, 110 wantIDStart: 3, 111 wantIDEnd: 5, 112 }, { 113 name: "dot", 114 args: args{"ab.cd", 2}, 115 wantDot: -1, 116 wantIDStart: -1, 117 wantIDEnd: -1, 118 }, { 119 name: "space", 120 args: args{"ab. cd", 6}, 121 wantDot: 2, 122 wantIDStart: 5, 123 wantIDEnd: 7, 124 }, { 125 name: "newline", 126 args: args{"ab.\ncd", 5}, 127 wantDot: 2, 128 wantIDStart: 4, 129 wantIDEnd: 6, 130 }, { 131 name: "not_dot", 132 args: args{"a.b/cd", 4}, 133 wantDot: -1, 134 wantIDStart: -1, 135 wantIDEnd: -1, 136 }, { 137 name: "empty_src", 138 args: args{"", 0}, 139 wantDot: -1, 140 wantIDStart: -1, 141 wantIDEnd: -1, 142 }, 143 } 144 for _, tt := range tests { 145 t.Run(tt.name, func(t *testing.T) { 146 gotDot, gotIDStart, gotIDEnd := findLastDot(tt.args.src, tt.args.idx) 147 if gotDot != tt.wantDot { 148 t.Errorf("findLastDot() gotDot = %v, want %v", gotDot, tt.wantDot) 149 } 150 if gotIDStart != tt.wantIDStart { 151 t.Errorf("findLastDot() gotIDStart = %v, want %v", gotIDStart, tt.wantIDStart) 152 } 153 if gotIDEnd != tt.wantIDEnd { 154 t.Errorf("findLastDot() gotIDEnd = %v, want %v", gotIDEnd, tt.wantIDEnd) 155 } 156 }) 157 } 158 } 159 160 func Test_isPosInFuncBody(t *testing.T) { 161 tests := []struct { 162 name string 163 src string 164 want bool 165 }{ 166 {"before", `func sum(a, b int) int[cur] { return a + b }`, false}, 167 {"brace_open", `func sum(a, b int) int [cur]{ return a + b }`, false}, 168 {"first", `func sum(a, b int) int {[cur] return a + b }`, true}, 169 {"last", `func sum(a, b int) int { return a + b[cur] }`, true}, 170 {"brace_close", `func sum(a, b int) int { return a + b [cur]}`, true}, 171 {"after", `func sum(a, b int) int { return a + b }[cur]`, false}, 172 {"funclit", `f := func (a, b int) int { [cur]return a + b }`, true}, 173 } 174 for _, tt := range tests { 175 t.Run(tt.name, func(t *testing.T) { 176 src := tt.src 177 var pos token.Pos 178 pos = token.Pos(strings.Index(src, "[cur]") + 1) 179 if pos == token.NoPos { 180 t.Error("[cur] not found in src") 181 return 182 } 183 src = strings.Replace(src, "[cur]", "", -1) 184 _, blk, err := parseLesserGoString(src) 185 if err != nil { 186 t.Errorf("Failed to parse: %v", err) 187 return 188 } 189 if got := isPosInFuncBody(blk, pos); got != tt.want { 190 t.Errorf("isPosInFuncBody() = %v, want %v", got, tt.want) 191 } 192 }) 193 } 194 } 195 196 func TestComplete(t *testing.T) { 197 const selectorSpecExample = ` 198 type T0 struct { 199 x int 200 } 201 202 func (*T0) M0() 203 204 type T1 struct { 205 y int 206 } 207 208 func (T1) M1() 209 210 type T2 struct { 211 z int 212 T1 213 *T0 214 } 215 216 func (*T2) M2() 217 218 type Q *T2 219 220 var t T2 // with t.T0 != nil 221 var p *T2 // with p != nil and (*p).T0 != nil 222 var q Q = p 223 ` 224 tests := []struct { 225 name string 226 src string 227 want []string 228 ignoreWant bool 229 wantInclude []string 230 wantExclude []string 231 }{ 232 { 233 name: "go_keyword", 234 src: ` 235 import ( 236 "bytes" 237 ) 238 go bytes.sp[cur]`, 239 want: []string{"Split", "SplitAfter", "SplitAfterN", "SplitN"}, 240 }, { 241 name: "go_keyword_in_func", 242 src: ` 243 import ( 244 "bytes" 245 ) 246 func f() { 247 go bytes.sp[cur]`, 248 want: []string{"Split", "SplitAfter", "SplitAfterN", "SplitN"}, 249 }, { 250 name: "go_with_defer_keyword", 251 src: ` 252 import ( 253 "bytes" 254 ) 255 func f(){ 256 } 257 defer f() 258 go bytes.sp[cur]`, 259 want: []string{"Split", "SplitAfter", "SplitAfterN", "SplitN"}, 260 }, { 261 name: "defer_before_go_keyword", 262 src: ` 263 func foo(){ 264 } 265 func bar(){ 266 } 267 defer fo[cur] 268 go bar()`, 269 want: []string{"foo"}, 270 }, { 271 name: "defer_between_2_go_keywords", 272 src: ` 273 func foo(){ 274 } 275 func bar(){ 276 } 277 go bar() 278 defer fo[cur] 279 go bar()`, 280 want: []string{"foo"}, 281 }, { 282 name: "non_go_defer_function_call_with_go_keyword", 283 src: ` 284 func foo(){ 285 } 286 func bar(){ 287 } 288 fo[cur] 289 go bar()`, 290 want: []string{"foo"}, 291 }, { 292 name: "package", 293 src: ` 294 import ( 295 "bytes" 296 ) 297 var buf bytes.sp[cur]`, 298 want: []string{"Split", "SplitAfter", "SplitAfterN", "SplitN"}, 299 }, { 300 name: "package_in_func", 301 src: ` 302 import ( 303 "bytes" 304 ) 305 func f() { 306 var buf bytes.sp[cur]`, 307 want: []string{"Split", "SplitAfter", "SplitAfterN", "SplitN"}, 308 }, { 309 name: "package_upper", 310 src: ` 311 import ( 312 "bytes" 313 ) 314 var buf bytes.SP[cur]`, 315 want: []string{"Split", "SplitAfter", "SplitAfterN", "SplitN"}, 316 }, { 317 name: "value", 318 src: ` 319 import ( 320 "bytes" 321 ) 322 var buf bytes.Buffer 323 buf.un[cur]`, 324 want: []string{"UnreadByte", "UnreadRune"}, 325 }, { 326 name: "value_in_func", 327 src: ` 328 import ( 329 "bytes" 330 ) 331 func f() { 332 var buf bytes.Buffer 333 buf.un[cur]`, 334 want: []string{"UnreadByte", "UnreadRune"}, 335 }, { 336 name: "pointer", 337 src: ` 338 import ( 339 "bytes" 340 ) 341 var buf *bytes.Buffer 342 buf.un[cur]`, 343 want: []string{"UnreadByte", "UnreadRune"}, 344 }, { 345 name: "selector_example1", 346 src: ` 347 [selector_example] 348 t.[cur]`, 349 want: []string{"M0", "M1", "M2", "T0", "T1", "x", "y", "z"}, 350 }, { 351 name: "selector_example2", 352 src: ` 353 [selector_example] 354 p.[cur]`, 355 want: []string{"M0", "M1", "M2", "T0", "T1", "x", "y", "z"}, 356 }, { 357 name: "selector_example3", 358 src: ` 359 [selector_example] 360 q.[cur]`, 361 want: []string{"T0", "T1", "x", "y", "z"}, 362 }, { 363 // ".(" is parsed as TypeAssertExpr. 364 name: "dot_paren", 365 src: ` 366 [selector_example] 367 q.[cur](`, 368 want: []string{"T0", "T1", "x", "y", "z"}, 369 }, { 370 name: "before_type_assert", 371 src: ` 372 [selector_example] 373 var x interface{} 374 x.(T0).[cur]`, 375 want: []string{"M0", "x"}, 376 }, { 377 name: "before_type_switch", 378 src: ` 379 [selector_example] 380 type I0 interface { 381 M0() 382 } 383 var i I0 384 switch i.[cur](type) { 385 default: 386 }`, 387 want: []string{"M0"}, 388 }, { 389 name: "lgo_context", 390 src: ` 391 _ctx.val[cur]`, 392 want: []string{"Value"}, 393 }, { 394 name: "lgo_context_infunc", 395 src: ` 396 func f() { 397 _ctx.val[cur] 398 }`, 399 want: []string{"Value"}, 400 }, { 401 name: "id_simple", 402 src: ` 403 abc := 100 404 xyz := "hello" 405 [cur] 406 zzz := 1.23 407 `, 408 ignoreWant: true, 409 wantInclude: []string{"abc", "xyz"}, 410 wantExclude: []string{"zzz"}, 411 }, { 412 name: "id_upper", 413 src: ` 414 abc := 100 415 xyz := "hello" 416 XY[cur] 417 zzz := 1.23 418 `, 419 want: []string{"xyz"}, 420 }, { 421 name: "id_camel_case", 422 src: ` 423 func testFunc(){} 424 testf[cur] 425 `, 426 want: []string{"testFunc"}, 427 }, { 428 name: "id_partial", 429 src: ` 430 abc := 100 431 xyz := "hello" 432 xy[cur] 433 `, 434 want: []string{"xyz"}, 435 }, { 436 name: "id_in_func", 437 src: ` 438 func fn() { 439 abc := 100 440 xyz := "hello" 441 [cur] 442 zzz := 1.23 443 }`, 444 ignoreWant: true, 445 wantInclude: []string{"abc", "xyz", "int64"}, 446 wantExclude: []string{"zzz"}, 447 }, { 448 name: "id_partial_in_func", 449 src: ` 450 func fn() { 451 abc := 100 452 xyz := "hello" 453 xy[cur] 454 }`, 455 want: []string{"xyz"}, 456 }, { 457 name: "sort", 458 src: ` 459 type data struct { 460 abc int 461 DEF int 462 xyz int 463 } 464 var d data 465 d.[cur] 466 `, 467 want: []string{"abc", "DEF", "xyz"}, 468 }, { 469 // https://github.com/yunabe/lgo/issues/18 470 name: "bug18", 471 src: `var [cur]`, 472 ignoreWant: true, 473 wantInclude: []string{"int64"}, 474 }, { 475 name: "bug17", 476 src: ` 477 import "bytes" 478 var buf bytes.Buffer 479 buf.[cur] 480 y := 10`, 481 ignoreWant: true, 482 // TODO: Fix issue #17. 483 // wantInclude: []string{"Bytes", "Grow", "Len"}, 484 }, { 485 // Similar to bug17, but Complete works in this case. 486 name: "bug17ok", 487 src: ` 488 import "bytes" 489 var buf bytes.Buffer 490 buf.un[cur] 491 y := 10`, 492 want: []string{"UnreadByte", "UnreadRune"}, 493 }, 494 } 495 for _, tt := range tests { 496 t.Run(tt.name, func(t *testing.T) { 497 src := tt.src 498 src = strings.Replace(src, "[selector_example]", selectorSpecExample, -1) 499 pos := token.Pos(strings.Index(src, "[cur]") + 1) 500 if pos <= 0 { 501 t.Error("[cur] not found") 502 return 503 } 504 got, _, _ := Complete(strings.Replace(src, "[cur]", "", -1), pos, &Config{}) 505 if !tt.ignoreWant && !reflect.DeepEqual(got, tt.want) { 506 t.Errorf("Expected %#v but got %#v", tt.want, got) 507 } 508 if len(tt.wantInclude) == 0 && len(tt.wantExclude) == 0 { 509 return 510 } 511 m := make(map[string]bool) 512 for _, c := range got { 513 m[c] = true 514 } 515 for _, c := range tt.wantInclude { 516 if !m[c] { 517 t.Errorf("%q is not suggested; Got %#v", c, got) 518 } 519 } 520 for _, c := range tt.wantExclude { 521 if m[c] { 522 t.Errorf("%q is suggested unexpectedly", c) 523 } 524 } 525 }) 526 } 527 } 528 529 func TestCompleteKeywords(t *testing.T) { 530 // Checks autocomplete works even if identifiers have keyword prefixes. 531 // https://golang.org/ref/spec#Keywords 532 kwds := []string{ 533 "break", "default", "func", "interface", "select", 534 "case", "defer", "go", "map", "struct", 535 "chan", "else", "goto", "package", "switch", 536 "const", "fallthrough", "if", "range", "type", 537 "continue", "for", "import", "return", "var", 538 } 539 tests := []struct { 540 name string 541 code string 542 want []string 543 }{ 544 { 545 name: "id", 546 code: ` 547 var [kwd]xyz, [kwd]abc int 548 [kwd][cur]`, 549 want: []string{"[kwd]abc", "[kwd]xyz"}, 550 }, { 551 name: "idspace", 552 code: ` 553 var [kwd]def, [kwd]ghi int 554 [kwd][cur] + 10`, 555 want: []string{"[kwd]def", "[kwd]ghi"}, 556 }, { 557 name: "dot", 558 code: ` 559 type data struct { 560 [kwd]123 int 561 [kwd]456 string 562 } 563 var d data 564 d.[kwd][cur]`, 565 want: []string{"[kwd]123", "[kwd]456"}, 566 }, 567 } 568 for _, kwd := range kwds { 569 for _, src := range tests { 570 t.Run(kwd+"_"+src.name, func(t *testing.T) { 571 code := strings.Replace(src.code, "[kwd]", kwd, -1) 572 pos := token.Pos(strings.Index(code, "[cur]") + 1) 573 if pos <= 0 { 574 t.Fatal("[cur] not found") 575 return 576 } 577 got, _, _ := Complete(strings.Replace(code, "[cur]", "", -1), pos, &Config{}) 578 var want []string 579 for _, w := range src.want { 580 want = append(want, strings.Replace(w, "[kwd]", kwd, -1)) 581 } 582 if !reflect.DeepEqual(got, want) { 583 t.Errorf("got %v; want %v", got, want) 584 } 585 }) 586 } 587 } 588 }