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 }