github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/golang/lint/lint.go (about) 1 // Copyright (c) 2013 The Go Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd. 6 7 // Package lint contains a linter for Go source code. 8 package lint 9 10 import ( 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/parser" 15 "go/printer" 16 "go/token" 17 "regexp" 18 "sort" 19 "strconv" 20 "strings" 21 "unicode" 22 "unicode/utf8" 23 24 "github.com/insionng/yougam/libraries/x/tools/go/gcimporter" 25 "github.com/insionng/yougam/libraries/x/tools/go/types" 26 ) 27 28 const styleGuideBase = "https://yougam/libraries/wiki/CodeReviewComments" 29 30 // A Linter lints Go source code. 31 type Linter struct { 32 } 33 34 // Problem represents a problem in some source code. 35 type Problem struct { 36 Position token.Position // position in source file 37 Text string // the prose that describes the problem 38 Link string // (optional) the link to the style guide for the problem 39 Confidence float64 // a value in (0,1] estimating the confidence in this problem's correctness 40 LineText string // the source line 41 Category string // a short name for the general category of the problem 42 43 // If the problem has a suggested fix (the minority case), 44 // ReplacementLine is a full replacement for the relevant line of the source file. 45 ReplacementLine string 46 } 47 48 func (p *Problem) String() string { 49 if p.Link != "" { 50 return p.Text + "\n\n" + p.Link 51 } 52 return p.Text 53 } 54 55 type byPosition []Problem 56 57 func (p byPosition) Len() int { return len(p) } 58 func (p byPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 59 60 func (p byPosition) Less(i, j int) bool { 61 pi, pj := p[i].Position, p[j].Position 62 63 if pi.Filename != pj.Filename { 64 return pi.Filename < pj.Filename 65 } 66 if pi.Line != pj.Line { 67 return pi.Line < pj.Line 68 } 69 if pi.Column != pj.Column { 70 return pi.Column < pj.Column 71 } 72 73 return p[i].Text < p[j].Text 74 } 75 76 // Lint lints src. 77 func (l *Linter) Lint(filename string, src []byte) ([]Problem, error) { 78 return l.LintFiles(map[string][]byte{filename: src}) 79 } 80 81 // LintFiles lints a set of files of a single package. 82 // The argument is a map of filename to source. 83 func (l *Linter) LintFiles(files map[string][]byte) ([]Problem, error) { 84 if len(files) == 0 { 85 return nil, nil 86 } 87 pkg := &pkg{ 88 fset: token.NewFileSet(), 89 files: make(map[string]*file), 90 } 91 var pkgName string 92 for filename, src := range files { 93 f, err := parser.ParseFile(pkg.fset, filename, src, parser.ParseComments) 94 if err != nil { 95 return nil, err 96 } 97 if pkgName == "" { 98 pkgName = f.Name.Name 99 } else if f.Name.Name != pkgName { 100 return nil, fmt.Errorf("%s is in package %s, not %s", filename, f.Name.Name, pkgName) 101 } 102 pkg.files[filename] = &file{ 103 pkg: pkg, 104 f: f, 105 fset: pkg.fset, 106 src: src, 107 filename: filename, 108 } 109 } 110 return pkg.lint(), nil 111 } 112 113 // pkg represents a package being linted. 114 type pkg struct { 115 fset *token.FileSet 116 files map[string]*file 117 118 typesPkg *types.Package 119 typesInfo *types.Info 120 121 // sortable is the set of types in the package that implement sort.Interface. 122 sortable map[string]bool 123 // main is whether this is a "main" package. 124 main bool 125 126 problems []Problem 127 } 128 129 func (p *pkg) lint() []Problem { 130 if err := p.typeCheck(); err != nil { 131 /* TODO(dsymonds): Consider reporting these errors when golint operates on entire packages. 132 if e, ok := err.(types.Error); ok { 133 pos := p.fset.Position(e.Pos) 134 conf := 1.0 135 if strings.Contains(e.Msg, "can't find import: ") { 136 // Golint is probably being run in a context that doesn't support 137 // typechecking (e.g. package files aren't found), so don't warn about it. 138 conf = 0 139 } 140 if conf > 0 { 141 p.errorfAt(pos, conf, category("typechecking"), e.Msg) 142 } 143 144 // TODO(dsymonds): Abort if !e.Soft? 145 } 146 */ 147 } 148 149 p.scanSortable() 150 p.main = p.isMain() 151 152 for _, f := range p.files { 153 f.lint() 154 } 155 156 sort.Sort(byPosition(p.problems)) 157 158 return p.problems 159 } 160 161 // file represents a file being linted. 162 type file struct { 163 pkg *pkg 164 f *ast.File 165 fset *token.FileSet 166 src []byte 167 filename string 168 } 169 170 func (f *file) isTest() bool { return strings.HasSuffix(f.filename, "_test.go") } 171 172 func (f *file) lint() { 173 f.lintPackageComment() 174 f.lintImports() 175 f.lintBlankImports() 176 f.lintExported() 177 f.lintNames() 178 f.lintVarDecls() 179 f.lintElses() 180 f.lintRanges() 181 f.lintErrorf() 182 f.lintErrors() 183 f.lintErrorStrings() 184 f.lintReceiverNames() 185 f.lintIncDec() 186 f.lintMake() 187 f.lintErrorReturn() 188 f.lintUnexportedReturn() 189 f.lintTimeNames() 190 } 191 192 type link string 193 type category string 194 195 // The variadic arguments may start with link and category types, 196 // and must end with a format string and any arguments. 197 // It returns the new Problem. 198 func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem { 199 pos := f.fset.Position(n.Pos()) 200 if pos.Filename == "" { 201 pos.Filename = f.filename 202 } 203 return f.pkg.errorfAt(pos, confidence, args...) 204 } 205 206 func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem { 207 problem := Problem{ 208 Position: pos, 209 Confidence: confidence, 210 } 211 if pos.Filename != "" { 212 // The file might not exist in our mapping if a //line directive was encountered. 213 if f, ok := p.files[pos.Filename]; ok { 214 problem.LineText = srcLine(f.src, pos) 215 } 216 } 217 218 argLoop: 219 for len(args) > 1 { // always leave at least the format string in args 220 switch v := args[0].(type) { 221 case link: 222 problem.Link = string(v) 223 case category: 224 problem.Category = string(v) 225 default: 226 break argLoop 227 } 228 args = args[1:] 229 } 230 231 problem.Text = fmt.Sprintf(args[0].(string), args[1:]...) 232 233 p.problems = append(p.problems, problem) 234 return &p.problems[len(p.problems)-1] 235 } 236 237 var gcImporter = gcimporter.Import 238 239 func (p *pkg) typeCheck() error { 240 config := &types.Config{ 241 // By setting a no-op error reporter, the type checker does as much work as possible. 242 Error: func(error) {}, 243 Import: gcImporter, 244 } 245 info := &types.Info{ 246 Types: make(map[ast.Expr]types.TypeAndValue), 247 Defs: make(map[*ast.Ident]types.Object), 248 Uses: make(map[*ast.Ident]types.Object), 249 Scopes: make(map[ast.Node]*types.Scope), 250 } 251 var anyFile *file 252 var astFiles []*ast.File 253 for _, f := range p.files { 254 anyFile = f 255 astFiles = append(astFiles, f.f) 256 } 257 pkg, err := config.Check(anyFile.f.Name.Name, p.fset, astFiles, info) 258 // Remember the typechecking info, even if config.Check failed, 259 // since we will get partial information. 260 p.typesPkg = pkg 261 p.typesInfo = info 262 return err 263 } 264 265 func (p *pkg) typeOf(expr ast.Expr) types.Type { 266 if p.typesInfo == nil { 267 return nil 268 } 269 return p.typesInfo.TypeOf(expr) 270 } 271 272 func (p *pkg) isNamedType(typ types.Type, importPath, name string) bool { 273 n, ok := typ.(*types.Named) 274 if !ok { 275 return false 276 } 277 tn := n.Obj() 278 return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name 279 } 280 281 // scopeOf returns the tightest scope encompassing id. 282 func (p *pkg) scopeOf(id *ast.Ident) *types.Scope { 283 var scope *types.Scope 284 if obj := p.typesInfo.ObjectOf(id); obj != nil { 285 scope = obj.Parent() 286 } 287 if scope == p.typesPkg.Scope() { 288 // We were given a top-level identifier. 289 // Use the file-level scope instead of the package-level scope. 290 pos := id.Pos() 291 for _, f := range p.files { 292 if f.f.Pos() <= pos && pos < f.f.End() { 293 scope = p.typesInfo.Scopes[f.f] 294 break 295 } 296 } 297 } 298 return scope 299 } 300 301 func (p *pkg) scanSortable() { 302 p.sortable = make(map[string]bool) 303 304 // bitfield for which methods exist on each type. 305 const ( 306 Len = 1 << iota 307 Less 308 Swap 309 ) 310 nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap} 311 has := make(map[string]int) 312 for _, f := range p.files { 313 f.walk(func(n ast.Node) bool { 314 fn, ok := n.(*ast.FuncDecl) 315 if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 316 return true 317 } 318 // TODO(dsymonds): We could check the signature to be more precise. 319 recv := receiverType(fn) 320 if i, ok := nmap[fn.Name.Name]; ok { 321 has[recv] |= i 322 } 323 return false 324 }) 325 } 326 for typ, ms := range has { 327 if ms == Len|Less|Swap { 328 p.sortable[typ] = true 329 } 330 } 331 } 332 333 func (p *pkg) isMain() bool { 334 for _, f := range p.files { 335 if f.isMain() { 336 return true 337 } 338 } 339 return false 340 } 341 342 func (f *file) isMain() bool { 343 if f.f.Name.Name == "main" { 344 return true 345 } 346 return false 347 } 348 349 // lintPackageComment checks package comments. It complains if 350 // there is no package comment, or if it is not of the right form. 351 // This has a notable false positive in that a package comment 352 // could rightfully appear in a different file of the same package, 353 // but that's not easy to fix since this linter is file-oriented. 354 func (f *file) lintPackageComment() { 355 if f.isTest() { 356 return 357 } 358 359 const ref = styleGuideBase + "#package-comments" 360 prefix := "Package " + f.f.Name.Name + " " 361 362 // Look for a detached package comment. 363 // First, scan for the last comment that occurs before the "package" keyword. 364 var lastCG *ast.CommentGroup 365 for _, cg := range f.f.Comments { 366 if cg.Pos() > f.f.Package { 367 // Gone past "package" keyword. 368 break 369 } 370 lastCG = cg 371 } 372 if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { 373 endPos := f.fset.Position(lastCG.End()) 374 pkgPos := f.fset.Position(f.f.Package) 375 if endPos.Line+1 < pkgPos.Line { 376 // There isn't a great place to anchor this error; 377 // the start of the blank lines between the doc and the package statement 378 // is at least pointing at the location of the problem. 379 pos := token.Position{ 380 Filename: endPos.Filename, 381 // Offset not set; it is non-trivial, and doesn't appear to be needed. 382 Line: endPos.Line + 1, 383 Column: 1, 384 } 385 f.pkg.errorfAt(pos, 0.9, link(ref), category("comments"), "package comment is detached; there should be no blank lines between it and the package statement") 386 return 387 } 388 } 389 390 if f.f.Doc == nil { 391 f.errorf(f.f, 0.2, link(ref), category("comments"), "should have a package comment, unless it's in another file for this package") 392 return 393 } 394 s := f.f.Doc.Text() 395 if ts := strings.TrimLeft(s, " \t"); ts != s { 396 f.errorf(f.f.Doc, 1, link(ref), category("comments"), "package comment should not have leading space") 397 s = ts 398 } 399 // Only non-main packages need to keep to this form. 400 if f.f.Name.Name != "main" && !strings.HasPrefix(s, prefix) { 401 f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix) 402 } 403 } 404 405 // lintBlankImports complains if a non-main package has blank imports that are 406 // not documented. 407 func (f *file) lintBlankImports() { 408 // In package main and in tests, we don't complain about blank imports. 409 if f.pkg.main || f.isTest() { 410 return 411 } 412 413 // The first element of each contiguous group of blank imports should have 414 // an explanatory comment of some kind. 415 for i, imp := range f.f.Imports { 416 pos := f.fset.Position(imp.Pos()) 417 418 if !isBlank(imp.Name) { 419 continue // Ignore non-blank imports. 420 } 421 if i > 0 { 422 prev := f.f.Imports[i-1] 423 prevPos := f.fset.Position(prev.Pos()) 424 if isBlank(prev.Name) && prevPos.Line+1 == pos.Line { 425 continue // A subsequent blank in a group. 426 } 427 } 428 429 // This is the first blank import of a group. 430 if imp.Doc == nil && imp.Comment == nil { 431 ref := "" 432 f.errorf(imp, 1, link(ref), category("imports"), "a blank import should be only in a main or test package, or have a comment justifying it") 433 } 434 } 435 } 436 437 // lintImports examines import blocks. 438 func (f *file) lintImports() { 439 440 for i, is := range f.f.Imports { 441 _ = i 442 if is.Name != nil && is.Name.Name == "." && !f.isTest() { 443 f.errorf(is, 1, link(styleGuideBase+"#import-dot"), category("imports"), "should not use dot imports") 444 } 445 446 } 447 448 } 449 450 const docCommentsLink = styleGuideBase + "#doc-comments" 451 452 // lintExported examines the exported names. 453 // It complains if any required doc comments are missing, 454 // or if they are not of the right form. The exact rules are in 455 // lintFuncDoc, lintTypeDoc and lintValueSpecDoc; this function 456 // also tracks the GenDecl structure being traversed to permit 457 // doc comments for constants to be on top of the const block. 458 // It also complains if the names stutter when combined with 459 // the package name. 460 func (f *file) lintExported() { 461 if f.isTest() { 462 return 463 } 464 465 var lastGen *ast.GenDecl // last GenDecl entered. 466 467 // Set of GenDecls that have already had missing comments flagged. 468 genDeclMissingComments := make(map[*ast.GenDecl]bool) 469 470 f.walk(func(node ast.Node) bool { 471 switch v := node.(type) { 472 case *ast.GenDecl: 473 if v.Tok == token.IMPORT { 474 return false 475 } 476 // token.CONST, token.TYPE or token.VAR 477 lastGen = v 478 return true 479 case *ast.FuncDecl: 480 f.lintFuncDoc(v) 481 if v.Recv == nil { 482 // Only check for stutter on functions, not methods. 483 // Method names are not used package-qualified. 484 f.checkStutter(v.Name, "func") 485 } 486 // Don't proceed inside funcs. 487 return false 488 case *ast.TypeSpec: 489 // inside a GenDecl, which usually has the doc 490 doc := v.Doc 491 if doc == nil { 492 doc = lastGen.Doc 493 } 494 f.lintTypeDoc(v, doc) 495 f.checkStutter(v.Name, "type") 496 // Don't proceed inside types. 497 return false 498 case *ast.ValueSpec: 499 f.lintValueSpecDoc(v, lastGen, genDeclMissingComments) 500 return false 501 } 502 return true 503 }) 504 } 505 506 var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) 507 508 // lintNames examines all names in the file. 509 // It complains if any use underscores or incorrect known initialisms. 510 func (f *file) lintNames() { 511 // Package names need slightly different handling than other names. 512 if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") { 513 f.errorf(f.f, 1, link("http://yougam/libraries/doc/effective_go.html#package-names"), category("naming"), "don't use an underscore in package name") 514 } 515 516 check := func(id *ast.Ident, thing string) { 517 if id.Name == "_" { 518 return 519 } 520 521 // Handle two common styles from other languages that don't belong in Go. 522 if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { 523 f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use ALL_CAPS in Go names; use CamelCase") 524 return 525 } 526 if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { 527 should := string(id.Name[1]+'a'-'A') + id.Name[2:] 528 f.errorf(id, 0.8, link(styleGuideBase+"#mixed-caps"), category("naming"), "don't use leading k in Go names; %s %s should be %s", thing, id.Name, should) 529 } 530 531 should := lintName(id.Name) 532 if id.Name == should { 533 return 534 } 535 if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { 536 f.errorf(id, 0.9, link("http://yougam/libraries/doc/effective_go.html#mixed-caps"), category("naming"), "don't use underscores in Go names; %s %s should be %s", thing, id.Name, should) 537 return 538 } 539 f.errorf(id, 0.8, link(styleGuideBase+"#initialisms"), category("naming"), "%s %s should be %s", thing, id.Name, should) 540 } 541 checkList := func(fl *ast.FieldList, thing string) { 542 if fl == nil { 543 return 544 } 545 for _, f := range fl.List { 546 for _, id := range f.Names { 547 check(id, thing) 548 } 549 } 550 } 551 f.walk(func(node ast.Node) bool { 552 switch v := node.(type) { 553 case *ast.AssignStmt: 554 if v.Tok == token.ASSIGN { 555 return true 556 } 557 for _, exp := range v.Lhs { 558 if id, ok := exp.(*ast.Ident); ok { 559 check(id, "var") 560 } 561 } 562 case *ast.FuncDecl: 563 if f.isTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { 564 return true 565 } 566 567 thing := "func" 568 if v.Recv != nil { 569 thing = "method" 570 } 571 572 check(v.Name, thing) 573 574 checkList(v.Type.Params, thing+" parameter") 575 checkList(v.Type.Results, thing+" result") 576 case *ast.GenDecl: 577 if v.Tok == token.IMPORT { 578 return true 579 } 580 var thing string 581 switch v.Tok { 582 case token.CONST: 583 thing = "const" 584 case token.TYPE: 585 thing = "type" 586 case token.VAR: 587 thing = "var" 588 } 589 for _, spec := range v.Specs { 590 switch s := spec.(type) { 591 case *ast.TypeSpec: 592 check(s.Name, thing) 593 case *ast.ValueSpec: 594 for _, id := range s.Names { 595 check(id, thing) 596 } 597 } 598 } 599 case *ast.InterfaceType: 600 // Do not check interface method names. 601 // They are often constrainted by the method names of concrete types. 602 for _, x := range v.Methods.List { 603 ft, ok := x.Type.(*ast.FuncType) 604 if !ok { // might be an embedded interface name 605 continue 606 } 607 checkList(ft.Params, "interface method parameter") 608 checkList(ft.Results, "interface method result") 609 } 610 case *ast.RangeStmt: 611 if v.Tok == token.ASSIGN { 612 return true 613 } 614 if id, ok := v.Key.(*ast.Ident); ok { 615 check(id, "range var") 616 } 617 if id, ok := v.Value.(*ast.Ident); ok { 618 check(id, "range var") 619 } 620 case *ast.StructType: 621 for _, f := range v.Fields.List { 622 for _, id := range f.Names { 623 check(id, "struct field") 624 } 625 } 626 } 627 return true 628 }) 629 } 630 631 // lintName returns a different name if it should be different. 632 func lintName(name string) (should string) { 633 // Fast path for simple cases: "_" and all lowercase. 634 if name == "_" { 635 return name 636 } 637 allLower := true 638 for _, r := range name { 639 if !unicode.IsLower(r) { 640 allLower = false 641 break 642 } 643 } 644 if allLower { 645 return name 646 } 647 648 // Split camelCase at any lower->upper transition, and split on underscores. 649 // Check each word for common initialisms. 650 runes := []rune(name) 651 w, i := 0, 0 // index of start of word, scan 652 for i+1 <= len(runes) { 653 eow := false // whether we hit the end of a word 654 if i+1 == len(runes) { 655 eow = true 656 } else if runes[i+1] == '_' { 657 // underscore; shift the remainder forward over any run of underscores 658 eow = true 659 n := 1 660 for i+n+1 < len(runes) && runes[i+n+1] == '_' { 661 n++ 662 } 663 664 // Leave at most one underscore if the underscore is between two digits 665 if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { 666 n-- 667 } 668 669 copy(runes[i+1:], runes[i+n+1:]) 670 runes = runes[:len(runes)-n] 671 } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { 672 // lower->non-lower 673 eow = true 674 } 675 i++ 676 if !eow { 677 continue 678 } 679 680 // [w,i) is a word. 681 word := string(runes[w:i]) 682 if u := strings.ToUpper(word); commonInitialisms[u] { 683 // Keep consistent case, which is lowercase only at the start. 684 if w == 0 && unicode.IsLower(runes[w]) { 685 u = strings.ToLower(u) 686 } 687 // All the common initialisms are ASCII, 688 // so we can replace the bytes exactly. 689 copy(runes[w:], []rune(u)) 690 } else if w > 0 && strings.ToLower(word) == word { 691 // already all lowercase, and not the first word, so uppercase the first character. 692 runes[w] = unicode.ToUpper(runes[w]) 693 } 694 w = i 695 } 696 return string(runes) 697 } 698 699 // commonInitialisms is a set of common initialisms. 700 // Only add entries that are highly unlikely to be non-initialisms. 701 // For instance, "ID" is fine (Freudian code is rare), but "AND" is not. 702 var commonInitialisms = map[string]bool{ 703 "API": true, 704 "ASCII": true, 705 "CPU": true, 706 "CSS": true, 707 "DNS": true, 708 "EOF": true, 709 "GUID": true, 710 "HTML": true, 711 "HTTP": true, 712 "HTTPS": true, 713 "ID": true, 714 "IP": true, 715 "JSON": true, 716 "LHS": true, 717 "QPS": true, 718 "RAM": true, 719 "RHS": true, 720 "RPC": true, 721 "SLA": true, 722 "SMTP": true, 723 "SQL": true, 724 "SSH": true, 725 "TCP": true, 726 "TLS": true, 727 "TTL": true, 728 "UDP": true, 729 "UI": true, 730 "UID": true, 731 "UUID": true, 732 "URI": true, 733 "URL": true, 734 "UTF8": true, 735 "VM": true, 736 "XML": true, 737 "XSRF": true, 738 "XSS": true, 739 } 740 741 // lintTypeDoc examines the doc comment on a type. 742 // It complains if they are missing from an exported type, 743 // or if they are not of the standard form. 744 func (f *file) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) { 745 if !ast.IsExported(t.Name.Name) { 746 return 747 } 748 if doc == nil { 749 f.errorf(t, 1, link(docCommentsLink), category("comments"), "exported type %v should have comment or be unexported", t.Name) 750 return 751 } 752 753 s := doc.Text() 754 articles := [...]string{"A", "An", "The"} 755 for _, a := range articles { 756 if strings.HasPrefix(s, a+" ") { 757 s = s[len(a)+1:] 758 break 759 } 760 } 761 if !strings.HasPrefix(s, t.Name.Name+" ") { 762 f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name) 763 } 764 } 765 766 var commonMethods = map[string]bool{ 767 "Error": true, 768 "Read": true, 769 "ServeHTTP": true, 770 "String": true, 771 "Write": true, 772 } 773 774 // lintFuncDoc examines doc comments on functions and methods. 775 // It complains if they are missing, or not of the right form. 776 // It has specific exclusions for well-known methods (see commonMethods above). 777 func (f *file) lintFuncDoc(fn *ast.FuncDecl) { 778 if !ast.IsExported(fn.Name.Name) { 779 // func is unexported 780 return 781 } 782 kind := "function" 783 name := fn.Name.Name 784 if fn.Recv != nil && len(fn.Recv.List) > 0 { 785 // method 786 kind = "method" 787 recv := receiverType(fn) 788 if !ast.IsExported(recv) { 789 // receiver is unexported 790 return 791 } 792 if commonMethods[name] { 793 return 794 } 795 switch name { 796 case "Len", "Less", "Swap": 797 if f.pkg.sortable[recv] { 798 return 799 } 800 } 801 name = recv + "." + name 802 } 803 if fn.Doc == nil { 804 f.errorf(fn, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment or be unexported", kind, name) 805 return 806 } 807 s := fn.Doc.Text() 808 prefix := fn.Name.Name + " " 809 if !strings.HasPrefix(s, prefix) { 810 f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 811 } 812 } 813 814 // lintValueSpecDoc examines package-global variables and constants. 815 // It complains if they are not individually declared, 816 // or if they are not suitably documented in the right form (unless they are in a block that is commented). 817 func (f *file) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { 818 kind := "var" 819 if gd.Tok == token.CONST { 820 kind = "const" 821 } 822 823 if len(vs.Names) > 1 { 824 // Check that none are exported except for the first. 825 for _, n := range vs.Names[1:] { 826 if ast.IsExported(n.Name) { 827 f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name) 828 return 829 } 830 } 831 } 832 833 // Only one name. 834 name := vs.Names[0].Name 835 if !ast.IsExported(name) { 836 return 837 } 838 839 if vs.Doc == nil && gd.Doc == nil { 840 if genDeclMissingComments[gd] { 841 return 842 } 843 block := "" 844 if kind == "const" && gd.Lparen.IsValid() { 845 block = " (or a comment on this block)" 846 } 847 f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block) 848 genDeclMissingComments[gd] = true 849 return 850 } 851 // If this GenDecl has parens and a comment, we don't check its comment form. 852 if gd.Lparen.IsValid() && gd.Doc != nil { 853 return 854 } 855 // The relevant text to check will be on either vs.Doc or gd.Doc. 856 // Use vs.Doc preferentially. 857 doc := vs.Doc 858 if doc == nil { 859 doc = gd.Doc 860 } 861 prefix := name + " " 862 if !strings.HasPrefix(doc.Text(), prefix) { 863 f.errorf(doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix) 864 } 865 } 866 867 func (f *file) checkStutter(id *ast.Ident, thing string) { 868 pkg, name := f.f.Name.Name, id.Name 869 if !ast.IsExported(name) { 870 // unexported name 871 return 872 } 873 // A name stutters if the package name is a strict prefix 874 // and the next character of the name starts a new word. 875 if len(name) <= len(pkg) { 876 // name is too short to stutter. 877 // This permits the name to be the same as the package name. 878 return 879 } 880 if !strings.EqualFold(pkg, name[:len(pkg)]) { 881 return 882 } 883 // We can assume the name is well-formed UTF-8. 884 // If the next rune after the package name is uppercase or an underscore 885 // the it's starting a new word and thus this name stutters. 886 rem := name[len(pkg):] 887 if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { 888 f.errorf(id, 0.8, link(styleGuideBase+"#package-names"), category("naming"), "%s name will be used as %s.%s by other packages, and that stutters; consider calling this %s", thing, pkg, name, rem) 889 } 890 } 891 892 // zeroLiteral is a set of ast.BasicLit values that are zero values. 893 // It is not exhaustive. 894 var zeroLiteral = map[string]bool{ 895 "false": true, // bool 896 // runes 897 `'\x00'`: true, 898 `'\000'`: true, 899 // strings 900 `""`: true, 901 "``": true, 902 // numerics 903 "0": true, 904 "0.": true, 905 "0.0": true, 906 "0i": true, 907 } 908 909 // knownWeakerTypes is a set of types that are commonly used to weaken var declarations. 910 // A common form of var declarations that legitimately mentions an explicit LHS type 911 // is where the LHS type is "weaker" than the exact RHS type, where "weaker" means an 912 // interface compared to a concrete type, or an interface compared to a superset interface. 913 // A canonical example is `var out io.Writer = os.Stdout`. 914 // This is only used when type checking fails to determine the exact types. 915 var knownWeakerTypes = map[string]bool{ 916 "io.Reader": true, 917 "io.Writer": true, 918 "proto.Message": true, 919 } 920 921 // lintVarDecls examines variable declarations. It complains about declarations with 922 // redundant LHS types that can be inferred from the RHS. 923 func (f *file) lintVarDecls() { 924 var lastGen *ast.GenDecl // last GenDecl entered. 925 926 f.walk(func(node ast.Node) bool { 927 switch v := node.(type) { 928 case *ast.GenDecl: 929 if v.Tok != token.CONST && v.Tok != token.VAR { 930 return false 931 } 932 lastGen = v 933 return true 934 case *ast.ValueSpec: 935 if lastGen.Tok == token.CONST { 936 return false 937 } 938 if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 { 939 return false 940 } 941 rhs := v.Values[0] 942 // An underscore var appears in a common idiom for compile-time interface satisfaction, 943 // as in "var _ Interface = (*Concrete)(nil)". 944 if isIdent(v.Names[0], "_") { 945 return false 946 } 947 // If the RHS is a zero value, suggest dropping it. 948 zero := false 949 if lit, ok := rhs.(*ast.BasicLit); ok { 950 zero = zeroLiteral[lit.Value] 951 } else if isIdent(rhs, "nil") { 952 zero = true 953 } 954 if zero { 955 f.errorf(rhs, 0.9, category("zero-value"), "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0]) 956 return false 957 } 958 lhsTyp := f.pkg.typeOf(v.Type) 959 rhsTyp := f.pkg.typeOf(rhs) 960 if lhsTyp != nil && rhsTyp != nil && !types.Identical(lhsTyp, rhsTyp) { 961 // Assignment to a different type is not redundant. 962 return false 963 } 964 965 // The next three conditions are for suppressing the warning in situations 966 // where we were unable to typecheck. 967 968 // If the LHS type is an interface, don't warn, since it is probably a 969 // concrete type on the RHS. Note that our feeble lexical check here 970 // will only pick up interface{} and other literal interface types; 971 // that covers most of the cases we care to exclude right now. 972 if _, ok := v.Type.(*ast.InterfaceType); ok { 973 return false 974 } 975 // If the RHS is an untyped const, only warn if the LHS type is its default type. 976 if defType, ok := f.isUntypedConst(rhs); ok && !isIdent(v.Type, defType) { 977 return false 978 } 979 // If the LHS is a known weaker type, and we couldn't type check both sides, 980 // don't warn. 981 if lhsTyp == nil || rhsTyp == nil { 982 if knownWeakerTypes[f.render(v.Type)] { 983 return false 984 } 985 } 986 987 f.errorf(v.Type, 0.8, category("type-inference"), "should omit type %s from declaration of var %s; it will be inferred from the right-hand side", f.render(v.Type), v.Names[0]) 988 return false 989 } 990 return true 991 }) 992 } 993 994 // lintElses examines else blocks. It complains about any else block whose if block ends in a return. 995 func (f *file) lintElses() { 996 // We don't want to flag if { } else if { } else { } constructions. 997 // They will appear as an IfStmt whose Else field is also an IfStmt. 998 // Record such a node so we ignore it when we visit it. 999 ignore := make(map[*ast.IfStmt]bool) 1000 1001 f.walk(func(node ast.Node) bool { 1002 ifStmt, ok := node.(*ast.IfStmt) 1003 if !ok || ifStmt.Else == nil { 1004 return true 1005 } 1006 if ignore[ifStmt] { 1007 return true 1008 } 1009 if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { 1010 ignore[elseif] = true 1011 return true 1012 } 1013 if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { 1014 // only care about elses without conditions 1015 return true 1016 } 1017 if len(ifStmt.Body.List) == 0 { 1018 return true 1019 } 1020 shortDecl := false // does the if statement have a ":=" initialization statement? 1021 if ifStmt.Init != nil { 1022 if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { 1023 shortDecl = true 1024 } 1025 } 1026 lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] 1027 if _, ok := lastStmt.(*ast.ReturnStmt); ok { 1028 extra := "" 1029 if shortDecl { 1030 extra = " (move short variable declaration to its own line if necessary)" 1031 } 1032 f.errorf(ifStmt.Else, 1, link(styleGuideBase+"#indent-error-flow"), category("indent"), "if block ends with a return statement, so drop this else and outdent its block"+extra) 1033 } 1034 return true 1035 }) 1036 } 1037 1038 // lintRanges examines range clauses. It complains about redundant constructions. 1039 func (f *file) lintRanges() { 1040 f.walk(func(node ast.Node) bool { 1041 rs, ok := node.(*ast.RangeStmt) 1042 if !ok { 1043 return true 1044 } 1045 if rs.Value == nil { 1046 // for x = range m { ... } 1047 return true // single var form 1048 } 1049 if !isIdent(rs.Value, "_") { 1050 // for ?, y = range m { ... } 1051 return true 1052 } 1053 1054 p := f.errorf(rs.Value, 1, category("range-loop"), "should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", f.render(rs.Key), rs.Tok) 1055 1056 newRS := *rs // shallow copy 1057 newRS.Value = nil 1058 p.ReplacementLine = f.firstLineOf(&newRS, rs) 1059 1060 return true 1061 }) 1062 } 1063 1064 // lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation. 1065 func (f *file) lintErrorf() { 1066 f.walk(func(node ast.Node) bool { 1067 ce, ok := node.(*ast.CallExpr) 1068 if !ok || len(ce.Args) != 1 { 1069 return true 1070 } 1071 isErrorsNew := isPkgDot(ce.Fun, "errors", "New") 1072 var isTestingError bool 1073 se, ok := ce.Fun.(*ast.SelectorExpr) 1074 if ok && se.Sel.Name == "Error" { 1075 if typ := f.pkg.typeOf(se.X); typ != nil { 1076 isTestingError = typ.String() == "*testing.T" 1077 } 1078 } 1079 if !isErrorsNew && !isTestingError { 1080 return true 1081 } 1082 arg := ce.Args[0] 1083 ce, ok = arg.(*ast.CallExpr) 1084 if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { 1085 return true 1086 } 1087 errorfPrefix := "fmt" 1088 if isTestingError { 1089 errorfPrefix = f.render(se.X) 1090 } 1091 p := f.errorf(node, 1, category("errors"), "should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", f.render(se), errorfPrefix) 1092 1093 m := f.srcLineWithMatch(ce, `^(.*)`+f.render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) 1094 if m != nil { 1095 p.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] 1096 } 1097 1098 return true 1099 }) 1100 } 1101 1102 // lintErrors examines global error vars. It complains if they aren't named in the standard way. 1103 func (f *file) lintErrors() { 1104 for _, decl := range f.f.Decls { 1105 gd, ok := decl.(*ast.GenDecl) 1106 if !ok || gd.Tok != token.VAR { 1107 continue 1108 } 1109 for _, spec := range gd.Specs { 1110 spec := spec.(*ast.ValueSpec) 1111 if len(spec.Names) != 1 || len(spec.Values) != 1 { 1112 continue 1113 } 1114 ce, ok := spec.Values[0].(*ast.CallExpr) 1115 if !ok { 1116 continue 1117 } 1118 if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1119 continue 1120 } 1121 1122 id := spec.Names[0] 1123 prefix := "err" 1124 if id.IsExported() { 1125 prefix = "Err" 1126 } 1127 if !strings.HasPrefix(id.Name, prefix) { 1128 f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix) 1129 } 1130 } 1131 } 1132 } 1133 1134 func lintCapAndPunct(s string) (isCap, isPunct bool) { 1135 first, firstN := utf8.DecodeRuneInString(s) 1136 last, _ := utf8.DecodeLastRuneInString(s) 1137 isPunct = last == '.' || last == ':' || last == '!' 1138 isCap = unicode.IsUpper(first) 1139 if isCap && len(s) > firstN { 1140 // Don't flag strings starting with something that looks like an initialism. 1141 if second, _ := utf8.DecodeRuneInString(s[firstN:]); unicode.IsUpper(second) { 1142 isCap = false 1143 } 1144 } 1145 return 1146 } 1147 1148 // lintErrorStrings examines error strings. It complains if they are capitalized or end in punctuation. 1149 func (f *file) lintErrorStrings() { 1150 f.walk(func(node ast.Node) bool { 1151 ce, ok := node.(*ast.CallExpr) 1152 if !ok { 1153 return true 1154 } 1155 if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { 1156 return true 1157 } 1158 if len(ce.Args) < 1 { 1159 return true 1160 } 1161 str, ok := ce.Args[0].(*ast.BasicLit) 1162 if !ok || str.Kind != token.STRING { 1163 return true 1164 } 1165 s, _ := strconv.Unquote(str.Value) // can assume well-formed Go 1166 if s == "" { 1167 return true 1168 } 1169 isCap, isPunct := lintCapAndPunct(s) 1170 var msg string 1171 switch { 1172 case isCap && isPunct: 1173 msg = "error strings should not be capitalized and should not end with punctuation" 1174 case isCap: 1175 msg = "error strings should not be capitalized" 1176 case isPunct: 1177 msg = "error strings should not end with punctuation" 1178 default: 1179 return true 1180 } 1181 // People use proper nouns and exported Go identifiers in error strings, 1182 // so decrease the confidence of warnings for capitalization. 1183 conf := 0.8 1184 if isCap { 1185 conf = 0.6 1186 } 1187 f.errorf(str, conf, link(styleGuideBase+"#error-strings"), category("errors"), msg) 1188 return true 1189 }) 1190 } 1191 1192 var badReceiverNames = map[string]bool{ 1193 "me": true, 1194 "this": true, 1195 "self": true, 1196 } 1197 1198 // lintReceiverNames examines receiver names. It complains about inconsistent 1199 // names used for the same type and names such as "this". 1200 func (f *file) lintReceiverNames() { 1201 typeReceiver := map[string]string{} 1202 f.walk(func(n ast.Node) bool { 1203 fn, ok := n.(*ast.FuncDecl) 1204 if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { 1205 return true 1206 } 1207 names := fn.Recv.List[0].Names 1208 if len(names) < 1 { 1209 return true 1210 } 1211 name := names[0].Name 1212 const ref = styleGuideBase + "#receiver-names" 1213 if name == "_" { 1214 f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore`) 1215 return true 1216 } 1217 if badReceiverNames[name] { 1218 f.errorf(n, 1, link(ref), category("naming"), `receiver name should be a reflection of its identity; don't use generic names such as "me", "this", or "self"`) 1219 return true 1220 } 1221 recv := receiverType(fn) 1222 if prev, ok := typeReceiver[recv]; ok && prev != name { 1223 f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv) 1224 return true 1225 } 1226 typeReceiver[recv] = name 1227 return true 1228 }) 1229 } 1230 1231 // lintIncDec examines statements that increment or decrement a variable. 1232 // It complains if they don't use x++ or x--. 1233 func (f *file) lintIncDec() { 1234 f.walk(func(n ast.Node) bool { 1235 as, ok := n.(*ast.AssignStmt) 1236 if !ok { 1237 return true 1238 } 1239 if len(as.Lhs) != 1 { 1240 return true 1241 } 1242 if !isOne(as.Rhs[0]) { 1243 return true 1244 } 1245 var suffix string 1246 switch as.Tok { 1247 case token.ADD_ASSIGN: 1248 suffix = "++" 1249 case token.SUB_ASSIGN: 1250 suffix = "--" 1251 default: 1252 return true 1253 } 1254 f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix) 1255 return true 1256 }) 1257 } 1258 1259 // lintMake examines statements that declare and initialize a variable with make. 1260 // It complains if they are constructing a zero element slice. 1261 func (f *file) lintMake() { 1262 f.walk(func(n ast.Node) bool { 1263 as, ok := n.(*ast.AssignStmt) 1264 if !ok { 1265 return true 1266 } 1267 // Only want single var := assignment statements. 1268 if len(as.Lhs) != 1 || as.Tok != token.DEFINE { 1269 return true 1270 } 1271 ce, ok := as.Rhs[0].(*ast.CallExpr) 1272 if !ok { 1273 return true 1274 } 1275 // Check if ce is make([]T, 0). 1276 if !isIdent(ce.Fun, "make") || len(ce.Args) != 2 || !isZero(ce.Args[1]) { 1277 return true 1278 } 1279 at, ok := ce.Args[0].(*ast.ArrayType) 1280 if !ok || at.Len != nil { 1281 return true 1282 } 1283 f.errorf(as, 0.8, category("slice"), `can probably use "var %s %s" instead`, f.render(as.Lhs[0]), f.render(at)) 1284 return true 1285 }) 1286 } 1287 1288 // lintErrorReturn examines function declarations that return an error. 1289 // It complains if the error isn't the last parameter. 1290 func (f *file) lintErrorReturn() { 1291 f.walk(func(n ast.Node) bool { 1292 fn, ok := n.(*ast.FuncDecl) 1293 if !ok || fn.Type.Results == nil { 1294 return true 1295 } 1296 ret := fn.Type.Results.List 1297 if len(ret) <= 1 { 1298 return true 1299 } 1300 // An error return parameter should be the last parameter. 1301 // Flag any error parameters found before the last. 1302 for _, r := range ret[:len(ret)-1] { 1303 if isIdent(r.Type, "error") { 1304 f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items") 1305 break // only flag one 1306 } 1307 } 1308 return true 1309 }) 1310 } 1311 1312 // lintUnexportedReturn examines exported function declarations. 1313 // It complains if any return an unexported type. 1314 func (f *file) lintUnexportedReturn() { 1315 f.walk(func(n ast.Node) bool { 1316 fn, ok := n.(*ast.FuncDecl) 1317 if !ok { 1318 return true 1319 } 1320 if fn.Type.Results == nil { 1321 return false 1322 } 1323 if !fn.Name.IsExported() { 1324 return false 1325 } 1326 thing := "func" 1327 if fn.Recv != nil && len(fn.Recv.List) > 0 { 1328 thing = "method" 1329 if !ast.IsExported(receiverType(fn)) { 1330 // Don't report exported methods of unexported types, 1331 // such as private implementations of sort.Interface. 1332 return false 1333 } 1334 } 1335 for _, ret := range fn.Type.Results.List { 1336 typ := f.pkg.typeOf(ret.Type) 1337 if exportedType(typ) { 1338 continue 1339 } 1340 f.errorf(ret.Type, 0.8, category("unexported-type-in-api"), 1341 "exported %s %s returns unexported type %s, which can be annoying to use", 1342 thing, fn.Name.Name, typ) 1343 break // only flag one 1344 } 1345 return false 1346 }) 1347 } 1348 1349 // exportedType reports whether typ is an exported type. 1350 // It is imprecise, and will err on the side of returning true, 1351 // such as for composite types. 1352 func exportedType(typ types.Type) bool { 1353 switch T := typ.(type) { 1354 case *types.Named: 1355 // Builtin types have no package. 1356 return T.Obj().Pkg() == nil || T.Obj().Exported() 1357 case *types.Map: 1358 return exportedType(T.Key()) && exportedType(T.Elem()) 1359 case interface { 1360 Elem() types.Type 1361 }: // array, slice, pointer, chan 1362 return exportedType(T.Elem()) 1363 } 1364 // Be conservative about other types, such as struct, interface, etc. 1365 return true 1366 } 1367 1368 // timeSuffixes is a list of name suffixes that imply a time unit. 1369 // This is not an exhaustive list. 1370 var timeSuffixes = []string{ 1371 "Sec", "Secs", "Seconds", 1372 "Msec", "Msecs", 1373 "Milli", "Millis", "Milliseconds", 1374 "Usec", "Usecs", "Microseconds", 1375 "MS", "Ms", 1376 } 1377 1378 func (f *file) lintTimeNames() { 1379 f.walk(func(node ast.Node) bool { 1380 v, ok := node.(*ast.ValueSpec) 1381 if !ok { 1382 return true 1383 } 1384 for _, name := range v.Names { 1385 origTyp := f.pkg.typeOf(name) 1386 // Look for time.Duration or *time.Duration; 1387 // the latter is common when using flag.Duration. 1388 typ := origTyp 1389 if pt, ok := typ.(*types.Pointer); ok { 1390 typ = pt.Elem() 1391 } 1392 if !f.pkg.isNamedType(typ, "time", "Duration") { 1393 continue 1394 } 1395 suffix := "" 1396 for _, suf := range timeSuffixes { 1397 if strings.HasSuffix(name.Name, suf) { 1398 suffix = suf 1399 break 1400 } 1401 } 1402 if suffix == "" { 1403 continue 1404 } 1405 f.errorf(v, 0.9, category("time"), "var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix) 1406 } 1407 return true 1408 }) 1409 } 1410 1411 func receiverType(fn *ast.FuncDecl) string { 1412 switch e := fn.Recv.List[0].Type.(type) { 1413 case *ast.Ident: 1414 return e.Name 1415 case *ast.StarExpr: 1416 return e.X.(*ast.Ident).Name 1417 } 1418 panic(fmt.Sprintf("unknown method receiver AST node type %T", fn.Recv.List[0].Type)) 1419 } 1420 1421 func (f *file) walk(fn func(ast.Node) bool) { 1422 ast.Walk(walker(fn), f.f) 1423 } 1424 1425 func (f *file) render(x interface{}) string { 1426 var buf bytes.Buffer 1427 if err := printer.Fprint(&buf, f.fset, x); err != nil { 1428 panic(err) 1429 } 1430 return buf.String() 1431 } 1432 1433 func (f *file) debugRender(x interface{}) string { 1434 var buf bytes.Buffer 1435 if err := ast.Fprint(&buf, f.fset, x, nil); err != nil { 1436 panic(err) 1437 } 1438 return buf.String() 1439 } 1440 1441 // walker adapts a function to satisfy the ast.Visitor interface. 1442 // The function return whether the walk should proceed into the node's children. 1443 type walker func(ast.Node) bool 1444 1445 func (w walker) Visit(node ast.Node) ast.Visitor { 1446 if w(node) { 1447 return w 1448 } 1449 return nil 1450 } 1451 1452 func isIdent(expr ast.Expr, ident string) bool { 1453 id, ok := expr.(*ast.Ident) 1454 return ok && id.Name == ident 1455 } 1456 1457 // isBlank returns whether id is the blank identifier "_". 1458 // If id == nil, the answer is false. 1459 func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } 1460 1461 func isPkgDot(expr ast.Expr, pkg, name string) bool { 1462 sel, ok := expr.(*ast.SelectorExpr) 1463 return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) 1464 } 1465 1466 func isZero(expr ast.Expr) bool { 1467 lit, ok := expr.(*ast.BasicLit) 1468 return ok && lit.Kind == token.INT && lit.Value == "0" 1469 } 1470 1471 func isOne(expr ast.Expr) bool { 1472 lit, ok := expr.(*ast.BasicLit) 1473 return ok && lit.Kind == token.INT && lit.Value == "1" 1474 } 1475 1476 var basicTypeKinds = map[types.BasicKind]string{ 1477 types.UntypedBool: "bool", 1478 types.UntypedInt: "int", 1479 types.UntypedRune: "rune", 1480 types.UntypedFloat: "float64", 1481 types.UntypedComplex: "complex128", 1482 types.UntypedString: "string", 1483 } 1484 1485 // isUntypedConst reports whether expr is an untyped constant, 1486 // and indicates what its default type is. 1487 // scope may be nil. 1488 func (f *file) isUntypedConst(expr ast.Expr) (defType string, ok bool) { 1489 // Re-evaluate expr outside of its context to see if it's untyped. 1490 // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) 1491 exprStr := f.render(expr) 1492 tv, err := types.Eval(f.fset, f.pkg.typesPkg, expr.Pos(), exprStr) 1493 if err != nil { 1494 return "", false 1495 } 1496 if b, ok := tv.Type.(*types.Basic); ok { 1497 if dt, ok := basicTypeKinds[b.Kind()]; ok { 1498 return dt, true 1499 } 1500 } 1501 1502 return "", false 1503 } 1504 1505 // firstLineOf renders the given node and returns its first line. 1506 // It will also match the indentation of another node. 1507 func (f *file) firstLineOf(node, match ast.Node) string { 1508 line := f.render(node) 1509 if i := strings.Index(line, "\n"); i >= 0 { 1510 line = line[:i] 1511 } 1512 return f.indentOf(match) + line 1513 } 1514 1515 func (f *file) indentOf(node ast.Node) string { 1516 line := srcLine(f.src, f.fset.Position(node.Pos())) 1517 for i, r := range line { 1518 switch r { 1519 case ' ', '\t': 1520 default: 1521 return line[:i] 1522 } 1523 } 1524 return line // unusual or empty line 1525 } 1526 1527 func (f *file) srcLineWithMatch(node ast.Node, pattern string) (m []string) { 1528 line := srcLine(f.src, f.fset.Position(node.Pos())) 1529 line = strings.TrimSuffix(line, "\n") 1530 rx := regexp.MustCompile(pattern) 1531 return rx.FindStringSubmatch(line) 1532 } 1533 1534 // srcLine returns the complete line at p, including the terminating newline. 1535 func srcLine(src []byte, p token.Position) string { 1536 // Run to end of line in both directions if not at line start/end. 1537 lo, hi := p.Offset, p.Offset+1 1538 for lo > 0 && src[lo-1] != '\n' { 1539 lo-- 1540 } 1541 for hi < len(src) && src[hi-1] != '\n' { 1542 hi++ 1543 } 1544 return string(src[lo:hi]) 1545 }