github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/converter/complete.go (about)

     1  package converter
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"go/types"
     7  	"regexp"
     8  	"sort"
     9  	"strings"
    10  	"unicode"
    11  	"unicode/utf8"
    12  
    13  	"github.com/yunabe/lgo/parser"
    14  )
    15  
    16  var prefixRegex map[string]*regexp.Regexp
    17  
    18  func init() {
    19  	prefixRegex = map[string]*regexp.Regexp{
    20  		"go ":    regexp.MustCompile("^|[\n\t ]+(go )"),
    21  		"defer ": regexp.MustCompile("^|[\n\t ]+(defer )"),
    22  	}
    23  }
    24  
    25  type completeTarget interface {
    26  	completeTarget()
    27  }
    28  type selectExprTarget struct {
    29  	base       ast.Expr
    30  	src        string
    31  	start, end int
    32  }
    33  type idExprTarget struct {
    34  	src        string
    35  	start, end int
    36  }
    37  
    38  func (*selectExprTarget) completeTarget() {}
    39  func (*idExprTarget) completeTarget()     {}
    40  
    41  // isIdentRune returns whether a rune can be a part of an identifier.
    42  func isIdentRune(r rune) bool {
    43  	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
    44  }
    45  
    46  func identifierAt(src string, idx int) (start, end int) {
    47  	if idx > len(src) || idx < 0 {
    48  		return -1, -1
    49  	}
    50  	end = idx
    51  	for {
    52  		r, size := utf8.DecodeRuneInString(src[end:])
    53  		if !isIdentRune(r) {
    54  			break
    55  		}
    56  		end += size
    57  	}
    58  	start = idx
    59  	for {
    60  		r, size := utf8.DecodeLastRuneInString(src[:start])
    61  		if !isIdentRune(r) {
    62  			break
    63  		}
    64  		start -= size
    65  	}
    66  	if start == end {
    67  		return -1, -1
    68  	}
    69  	if r, _ := utf8.DecodeRuneInString(src[start:]); unicode.IsDigit(r) {
    70  		// Starts with a digit, which is not an identifier.
    71  		return -1, -1
    72  	}
    73  	return
    74  }
    75  
    76  func findLastDot(src string, idx int) (dot, idStart, idEnd int) {
    77  	idStart, idEnd = identifierAt(src, idx)
    78  	var s int
    79  	if idStart < 0 {
    80  		s = idx
    81  	} else {
    82  		s = idStart
    83  	}
    84  	for {
    85  		r, size := utf8.DecodeLastRuneInString(src[:s])
    86  		if unicode.IsSpace(r) {
    87  			s -= size
    88  			continue
    89  		}
    90  		if r == '.' {
    91  			s -= size
    92  		}
    93  		break
    94  	}
    95  	if s < len(src) && src[s] == '.' {
    96  		if idStart < 0 {
    97  			return s, idx, idx
    98  		}
    99  		return s, idStart, idEnd
   100  	}
   101  	return -1, -1, -1
   102  }
   103  
   104  // findDotBaseVisitor finds 'x' of 'x.y' or 'x.(type)' expression and stores it to base.
   105  type findDotBaseVisitor struct {
   106  	dotPos token.Pos
   107  	base   ast.Expr
   108  }
   109  
   110  func (v *findDotBaseVisitor) Visit(n ast.Node) ast.Visitor {
   111  	if v.base != nil || n == nil {
   112  		return nil
   113  	}
   114  	if v.dotPos < n.Pos() || n.End() <= v.dotPos {
   115  		return nil
   116  	}
   117  	if n, _ := n.(*ast.SelectorExpr); n != nil && n.X.End() <= v.dotPos && v.dotPos < n.Sel.Pos() {
   118  		v.base = n.X
   119  		return nil
   120  	}
   121  	if n, _ := n.(*ast.TypeAssertExpr); n != nil && n.X.End() <= v.dotPos && v.dotPos < n.Lparen {
   122  		v.base = n.X
   123  		return nil
   124  	}
   125  	return v
   126  }
   127  
   128  type isPosInFuncBodyVisitor struct {
   129  	pos    token.Pos
   130  	inBody bool
   131  }
   132  
   133  func (v *isPosInFuncBodyVisitor) Visit(n ast.Node) ast.Visitor {
   134  	if n == nil || v.inBody {
   135  		return nil
   136  	}
   137  	pos := v.pos
   138  	if pos < n.Pos() || n.End() <= pos {
   139  		// pos is out side of n.
   140  		return nil
   141  	}
   142  	var body *ast.BlockStmt
   143  	switch n := n.(type) {
   144  	case *ast.FuncDecl:
   145  		body = n.Body
   146  	case *ast.FuncLit:
   147  		body = n.Body
   148  	}
   149  	if body != nil && body.Pos() < pos && pos < body.End() {
   150  		// Note: pos == n.Pos() means the cursor is right before '{'. Return false in that case.
   151  		v.inBody = true
   152  	}
   153  	return v
   154  }
   155  
   156  // isPosInFuncBody returns whether pos is inside a function body in lgo source.
   157  // Please call this method before any conversion on blk.
   158  func isPosInFuncBody(blk *parser.LGOBlock, pos token.Pos) bool {
   159  	v := isPosInFuncBodyVisitor{pos: pos}
   160  	for _, stmt := range blk.Stmts {
   161  		ast.Walk(&v, stmt)
   162  		if v.inBody {
   163  			return true
   164  		}
   165  	}
   166  	return false
   167  }
   168  
   169  type findCompleteTargetVisitor struct {
   170  	skip ast.Node
   171  	pos  token.Pos
   172  	src  string
   173  
   174  	target completeTarget
   175  	found  bool
   176  }
   177  
   178  func (v *findCompleteTargetVisitor) Visit(n ast.Node) ast.Visitor {
   179  	if v.found || n == nil {
   180  		return nil
   181  	}
   182  	if n == v.skip {
   183  		return v
   184  	}
   185  	if v.pos < n.Pos() || n.End() <= v.pos {
   186  		return nil
   187  	}
   188  	v.found = true
   189  	cv := findCompleteTargetVisitor{skip: n, pos: v.pos, src: v.src}
   190  	cv.Visit(n)
   191  	if cv.found {
   192  		v.target = cv.target
   193  		return nil
   194  	}
   195  	if _, ok := n.(*ast.Comment); ok {
   196  		return nil
   197  	}
   198  	if start, end := identifierAt(v.src, int(v.pos-1)); start != -1 {
   199  		v.target = &idExprTarget{src: v.src, start: start, end: end}
   200  		return nil
   201  	}
   202  	v.target = &idExprTarget{src: v.src, start: int(v.pos - 1), end: int(v.pos - 1)}
   203  	return nil
   204  }
   205  
   206  func findNearestScope(s *types.Scope, pos token.Pos) *types.Scope {
   207  	valid := s.Pos() != token.NoPos && s.End() != token.NoPos
   208  	if valid && (pos < s.Pos() || s.End() <= pos) {
   209  		return nil
   210  	}
   211  	for i := 0; i < s.NumChildren(); i++ {
   212  		if c := findNearestScope(s.Child(i), pos); c != nil {
   213  			return c
   214  		}
   215  	}
   216  	if valid {
   217  		return s
   218  	}
   219  	return nil
   220  }
   221  
   222  func completeTargetFromAST(src string, pos token.Pos, blk *parser.LGOBlock) completeTarget {
   223  	if dot, start, end := findLastDot(src, int(pos-1)); dot >= 0 {
   224  		var base ast.Expr
   225  		for _, stmt := range blk.Stmts {
   226  			v := &findDotBaseVisitor{dotPos: token.Pos(dot + 1)}
   227  			ast.Walk(v, stmt)
   228  			if v.base != nil {
   229  				base = v.base
   230  				break
   231  			}
   232  		}
   233  		if base == nil {
   234  			return nil
   235  		}
   236  		return &selectExprTarget{
   237  			base:  base,
   238  			src:   src,
   239  			start: start,
   240  			end:   end,
   241  		}
   242  	}
   243  	for _, stmt := range blk.Stmts {
   244  		v := findCompleteTargetVisitor{pos: pos, src: src}
   245  		v.Visit(stmt)
   246  		if v.found {
   247  			return v.target
   248  		}
   249  	}
   250  	if start, end := identifierAt(src, int(pos-1)); start != -1 {
   251  		return &idExprTarget{src: src, start: start, end: end}
   252  	}
   253  	return &idExprTarget{src: src, start: int(pos - 1), end: int(pos - 1)}
   254  }
   255  
   256  func listCandidatesFromScope(s *types.Scope, pos token.Pos, prefix string, candidates map[string]bool) {
   257  	if s == nil {
   258  		return
   259  	}
   260  	for _, name := range s.Names() {
   261  		if !strings.HasPrefix(strings.ToLower(name), prefix) {
   262  			continue
   263  		}
   264  		if _, obj := s.LookupParent(name, pos); obj != nil {
   265  			candidates[name] = true
   266  		}
   267  	}
   268  	listCandidatesFromScope(s.Parent(), pos, prefix, candidates)
   269  }
   270  
   271  func completeWithChecker(target completeTarget, checker *types.Checker, pkg *types.Package, initFunc *ast.FuncDecl) ([]string, int, int) {
   272  	if target, _ := target.(*selectExprTarget); target != nil {
   273  		match := completeFieldAndMethods(target.base, target.src[target.start:target.end], checker)
   274  		return match, target.start, target.end
   275  	}
   276  	if target, _ := target.(*idExprTarget); target != nil {
   277  		pos := token.Pos(target.start + 1)
   278  		n := findNearestScope(pkg.Scope(), pos)
   279  		if n == nil && initFunc != nil {
   280  			n = checker.Scopes[initFunc.Type]
   281  		}
   282  		prefix := strings.ToLower(target.src[target.start:target.end])
   283  
   284  		candidates := make(map[string]bool)
   285  		listCandidatesFromScope(n, pos, prefix, candidates)
   286  		if len(candidates) == 0 {
   287  			return nil, 0, 0
   288  		}
   289  		l := make([]string, 0, len(candidates))
   290  		for key := range candidates {
   291  			l = append(l, key)
   292  		}
   293  		return l, target.start, target.end
   294  	}
   295  	return nil, 0, 0
   296  }
   297  
   298  // Complete returns a list of candidates of code completion.
   299  func Complete(src string, pos token.Pos, conf *Config) ([]string, int, int) {
   300  	match, start, end := removeGoAndDeferKeywordsAndComplete(src, pos, conf)
   301  
   302  	// case-insensitive sort
   303  	sort.Slice(match, func(i, j int) bool {
   304  		c := strings.Compare(strings.ToLower(match[i]), strings.ToLower(match[j]))
   305  		if c < 0 {
   306  			return true
   307  		}
   308  		if c > 0 {
   309  			return false
   310  		}
   311  		c = strings.Compare(match[i], match[j])
   312  		if c < 0 {
   313  			return true
   314  		}
   315  		return false
   316  	})
   317  	return match, start, end
   318  }
   319  
   320  func removePrefixesFromSource(src, prefix string, pos token.Pos) (string, token.Pos) {
   321  	matchIndices := prefixRegex[prefix].FindAllStringSubmatchIndex(src[:pos-1], -1)
   322  
   323  	if len(matchIndices) == 0 {
   324  		return src, pos
   325  	}
   326  
   327  	// Replacing the nearest prefix is sufficient
   328  	tokenPos := matchIndices[len(matchIndices)-1]
   329  	if len(tokenPos) == 4 && tokenPos[2] != -1 && tokenPos[3] != -1 && tokenPos[3] < int(pos) {
   330  		src = src[:tokenPos[2]] + src[tokenPos[3]:]
   331  		pos = token.Pos(int(pos) - len(prefix))
   332  	}
   333  
   334  	return src, pos
   335  }
   336  
   337  func removeGoAndDeferKeywordsAndComplete(src string, pos token.Pos, conf *Config) ([]string, int, int) {
   338  	var tempPos token.Pos
   339  	src, tempPos = removePrefixesFromSource(src, "go ", pos)
   340  	src, tempPos = removePrefixesFromSource(src, "defer ", tempPos)
   341  
   342  	match, start, end := complete(src, tempPos, conf)
   343  
   344  	start += int(pos) - int(tempPos)
   345  	end += int(pos) - int(tempPos)
   346  
   347  	return match, start, end
   348  }
   349  
   350  func complete(src string, pos token.Pos, conf *Config) ([]string, int, int) {
   351  	fset, blk, _ := parseLesserGoString(src)
   352  
   353  	target := completeTargetFromAST(src, pos, blk)
   354  	if target == nil {
   355  		return nil, 0, 0
   356  	}
   357  
   358  	// Whether pos is inside a function body.
   359  	inFuncBody := isPosInFuncBody(blk, pos)
   360  
   361  	phase1 := convertToPhase1(blk)
   362  	makePkg := func() *types.Package {
   363  		// TODO: Add a proper name to the package though it's not used at this moment.
   364  		pkg, vscope := types.NewPackageWithOldValues("cmd/hello", "", conf.Olds)
   365  		pkg.IsLgo = true
   366  		// TODO: Come up with better implementation to resolve pkg <--> vscope circular deps.
   367  		for _, im := range conf.OldImports {
   368  			pname := types.NewPkgName(token.NoPos, pkg, im.Name(), im.Imported())
   369  			vscope.Insert(pname)
   370  		}
   371  		injectLgoContext(pkg, vscope)
   372  		return pkg
   373  	}
   374  
   375  	chConf := &types.Config{
   376  		Importer:          lgoImporter,
   377  		Error:             func(err error) {},
   378  		IgnoreFuncBodies:  true,
   379  		DontIgnoreLgoInit: true,
   380  	}
   381  	var info = types.Info{
   382  		Defs:   make(map[*ast.Ident]types.Object),
   383  		Uses:   make(map[*ast.Ident]types.Object),
   384  		Scopes: make(map[ast.Node]*types.Scope),
   385  		Types:  make(map[ast.Expr]types.TypeAndValue),
   386  	}
   387  	pkg := makePkg()
   388  	checker := types.NewChecker(chConf, fset, pkg, &info)
   389  	checker.Files([]*ast.File{phase1.file})
   390  
   391  	if !inFuncBody {
   392  		return completeWithChecker(target, checker, pkg, phase1.initFunc)
   393  	}
   394  
   395  	convertToPhase2(phase1, pkg, checker, conf)
   396  	{
   397  		chConf := &types.Config{
   398  			Importer: newImporterWithOlds(conf.Olds),
   399  			Error: func(err error) {
   400  				// Ignore errors.
   401  				// It is necessary to set this noop func because checker stops analyzing code
   402  				// when the first error is found if Error is nil.
   403  			},
   404  			IgnoreFuncBodies:  false,
   405  			DontIgnoreLgoInit: true,
   406  		}
   407  		var info = types.Info{
   408  			Defs:   make(map[*ast.Ident]types.Object),
   409  			Uses:   make(map[*ast.Ident]types.Object),
   410  			Scopes: make(map[ast.Node]*types.Scope),
   411  			Types:  make(map[ast.Expr]types.TypeAndValue),
   412  		}
   413  		// Note: Do not reuse pkg above here because variables are already defined in the scope of pkg above.
   414  		pkg := makePkg()
   415  		checker := types.NewChecker(chConf, fset, pkg, &info)
   416  		checker.Files([]*ast.File{phase1.file})
   417  		return completeWithChecker(target, checker, pkg, nil)
   418  	}
   419  }
   420  
   421  // scanFieldOrMethod scans all possible fields and methods of typ.
   422  // c.f. LookupFieldOrMethod of go/types.
   423  func scanFieldOrMethod(typ types.Type, add func(string)) {
   424  	// deref dereferences typ if it is a *Pointer and returns its base and true.
   425  	// Otherwise it returns (typ, false).
   426  	deref := func(typ types.Type) (types.Type, bool) {
   427  		if p, _ := typ.(*types.Pointer); p != nil {
   428  			return p.Elem(), true
   429  		}
   430  		return typ, false
   431  	}
   432  
   433  	var ignoreMethod bool
   434  	if named, _ := typ.(*types.Named); named != nil {
   435  		// https://golang.org/ref/spec#Selectors
   436  		// As an exception, if the type of x is a named pointer type and (*x).f is a valid selector expression
   437  		// denoting a field (but not a method), x.f is shorthand for (*x).f.
   438  		if p, _ := named.Underlying().(*types.Pointer); p != nil {
   439  			typ = p
   440  			ignoreMethod = true
   441  		}
   442  	}
   443  	typ, isPtr := deref(typ)
   444  	if isPtr && types.IsInterface(typ) {
   445  		return
   446  	}
   447  	type embeddedType struct {
   448  		typ types.Type
   449  	}
   450  	current := []embeddedType{{typ}}
   451  	var seen map[*types.Named]bool
   452  
   453  	// search current depth
   454  	for len(current) > 0 {
   455  		var next []embeddedType // embedded types found at current depth
   456  
   457  		for _, e := range current {
   458  			typ := e.typ
   459  
   460  			// If we have a named type, we may have associated methods.
   461  			// Look for those first.
   462  			if named, _ := typ.(*types.Named); named != nil {
   463  				if seen[named] {
   464  					// We have seen this type before.
   465  					continue
   466  				}
   467  				if seen == nil {
   468  					seen = make(map[*types.Named]bool)
   469  				}
   470  				seen[named] = true
   471  
   472  				if !ignoreMethod {
   473  					// scan methods
   474  					for i := 0; i < named.NumMethods(); i++ {
   475  						f := named.Method(i)
   476  						if f.Exported() {
   477  							add(f.Name())
   478  						}
   479  					}
   480  				}
   481  				// continue with underlying type
   482  				typ = named.Underlying()
   483  			}
   484  
   485  			switch t := typ.(type) {
   486  			case *types.Struct:
   487  				for i := 0; i < t.NumFields(); i++ {
   488  					f := t.Field(i)
   489  					if f.Exported() {
   490  						add(f.Name())
   491  					}
   492  					if f.Anonymous() {
   493  						typ, _ := deref(f.Type())
   494  						next = append(next, embeddedType{typ})
   495  					}
   496  				}
   497  
   498  			case *types.Interface:
   499  				// scan methods
   500  				for i := 0; i < t.NumMethods(); i++ {
   501  					if m := t.Method(i); m.Exported() {
   502  						add(m.Name())
   503  					}
   504  				}
   505  			}
   506  		}
   507  		current = next
   508  	}
   509  }
   510  
   511  func completeFieldAndMethods(expr ast.Expr, orig string, checker *types.Checker) []string {
   512  	orig = strings.ToLower(orig)
   513  	suggests := make(map[string]bool)
   514  	add := func(s string) {
   515  		if strings.HasPrefix(strings.ToLower(s), orig) {
   516  			suggests[s] = true
   517  		}
   518  	}
   519  	func() {
   520  		// Complete package fields selector (e.g. bytes.buf[cur] --> bytes.Buffer)
   521  		id, _ := expr.(*ast.Ident)
   522  		if id == nil {
   523  			return
   524  		}
   525  		obj := checker.Uses[id]
   526  		if obj == nil {
   527  			return
   528  		}
   529  		pkg, _ := obj.(*types.PkgName)
   530  		if pkg == nil {
   531  			return
   532  		}
   533  		im := pkg.Imported()
   534  		for _, name := range im.Scope().Names() {
   535  			if o := im.Scope().Lookup(name); o.Exported() {
   536  				add(name)
   537  			}
   538  		}
   539  	}()
   540  	if tv, ok := checker.Types[expr]; ok && tv.IsValue() {
   541  		scanFieldOrMethod(tv.Type, add)
   542  	}
   543  	if len(suggests) == 0 {
   544  		return nil
   545  	}
   546  	var results []string
   547  	for key := range suggests {
   548  		results = append(results, key)
   549  	}
   550  	return results
   551  }