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  }