// There isn't a great place to anchor this error;
// the start of the blank lines between the doc and the package statement
// is at least pointing at the location of the problem.
pos:=token.Position{
Filename:endPos.Filename,
// Offset not set; it is non-trivial, and doesn't appear to be needed.
Line:endPos.Line+1,
Column:1,
}
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")
return
}
}
iff.f.Doc==nil{
f.errorf(f.f,0.2,link(ref),category("comments"),"should have a package comment, unless it's in another file for this package")
return
}
s:=f.f.Doc.Text()
ifts:=strings.TrimLeft(s," \t");ts!=s{
f.errorf(f.f.Doc,1,link(ref),category("comments"),"package comment should not have leading space")
s=ts
}
// Only non-main packages need to keep to this form.
if!f.pkg.main&&!strings.HasPrefix(s,prefix){
f.errorf(f.f.Doc,1,link(ref),category("comments"),`package comment should be of the form "%s..."`,prefix)
}
}
// lintBlankImports complains if a non-main package has blank imports that are
// not documented.
func(f*file)lintBlankImports(){
// In package main and in tests, we don't complain about blank imports.
iff.pkg.main||f.isTest(){
return
}
// The first element of each contiguous group of blank imports should have
// an explanatory comment of some kind.
fori,imp:=rangef.f.Imports{
pos:=f.fset.Position(imp.Pos())
if!isBlank(imp.Name){
continue// Ignore non-blank imports.
}
ifi>0{
prev:=f.f.Imports[i-1]
prevPos:=f.fset.Position(prev.Pos())
ifisBlank(prev.Name)&&prevPos.Line+1==pos.Line{
continue// A subsequent blank in a group.
}
}
// This is the first blank import of a group.
ifimp.Doc==nil&&imp.Comment==nil{
ref:=""
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")
}
}
}
// lintImports examines import blocks.
func(f*file)lintImports(){
fori,is:=rangef.f.Imports{
_=i
ifis.Name!=nil&&is.Name.Name=="."&&!f.isTest(){
f.errorf(is,1,link(styleGuideBase+"#import-dot"),category("imports"),"should not use dot imports")
f.errorf(f.f,1,link("http://golang.org/doc/effective_go.html#package-names"),category("naming"),"don't use an underscore in package name")
}
ifanyCapsRE.MatchString(f.f.Name.Name){
f.errorf(f.f,1,link("http://golang.org/doc/effective_go.html#package-names"),category("mixed-caps"),"don't use MixedCaps in package name; %s should be %s",f.f.Name.Name,strings.ToLower(f.f.Name.Name))
}
check:=func(id*ast.Ident,thingstring){
ifid.Name=="_"{
return
}
ifknownNameExceptions[id.Name]{
return
}
// Handle two common styles from other languages that don't belong in Go.
f.errorf(id,0.9,link("http://golang.org/doc/effective_go.html#mixed-caps"),category("naming"),"don't use underscores in Go names; %s %s should be %s",thing,id.Name,should)
return
}
f.errorf(id,0.8,link(styleGuideBase+"#initialisms"),category("naming"),"%s %s should be %s",thing,id.Name,should)
f.errorf(t,1,link(docCommentsLink),category("comments"),"exported type %v should have comment or be unexported",t.Name)
return
}
s:=doc.Text()
articles:=[...]string{"A","An","The"}
for_,a:=rangearticles{
ifstrings.HasPrefix(s,a+" "){
s=s[len(a)+1:]
break
}
}
if!strings.HasPrefix(s,t.Name.Name+" "){
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)
}
}
varcommonMethods=map[string]bool{
"Error":true,
"Read":true,
"ServeHTTP":true,
"String":true,
"Write":true,
}
// lintFuncDoc examines doc comments on functions and methods.
// It complains if they are missing, or not of the right form.
// It has specific exclusions for well-known methods (see commonMethods above).
func(f*file)lintFuncDoc(fn*ast.FuncDecl){
if!ast.IsExported(fn.Name.Name){
// func is unexported
return
}
kind:="function"
name:=fn.Name.Name
iffn.Recv!=nil&&len(fn.Recv.List)>0{
// method
kind="method"
recv:=receiverType(fn)
if!ast.IsExported(recv){
// receiver is unexported
return
}
ifcommonMethods[name]{
return
}
switchname{
case"Len","Less","Swap":
iff.pkg.sortable[recv]{
return
}
}
name=recv+"."+name
}
iffn.Doc==nil{
f.errorf(fn,1,link(docCommentsLink),category("comments"),"exported %s %s should have comment or be unexported",kind,name)
return
}
s:=fn.Doc.Text()
prefix:=fn.Name.Name+" "
if!strings.HasPrefix(s,prefix){
f.errorf(fn.Doc,1,link(docCommentsLink),category("comments"),`comment on exported %s %s should be of the form "%s..."`,kind,name,prefix)
}
}
// lintValueSpecDoc examines package-global variables and constants.
// It complains if they are not individually declared,
// or if they are not suitably documented in the right form (unless they are in a block that is commented).
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)
}
}
// zeroLiteral is a set of ast.BasicLit values that are zero values.
// It is not exhaustive.
varzeroLiteral=map[string]bool{
"false":true,// bool
// runes
`'\x00'`:true,
`'\000'`:true,
// strings
`""`:true,
"``":true,
// numerics
"0":true,
"0.":true,
"0.0":true,
"0i":true,
}
// lintVarDecls examines variable declarations. It complains about declarations with
// redundant LHS types that can be inferred from the RHS.
func(f*file)lintVarDecls(){
varlastGen*ast.GenDecl// last GenDecl entered.
f.walk(func(nodeast.Node)bool{
switchv:=node.(type){
case*ast.GenDecl:
ifv.Tok!=token.CONST&&v.Tok!=token.VAR{
returnfalse
}
lastGen=v
returntrue
case*ast.ValueSpec:
iflastGen.Tok==token.CONST{
returnfalse
}
iflen(v.Names)>1||v.Type==nil||len(v.Values)==0{
returnfalse
}
rhs:=v.Values[0]
// An underscore var appears in a common idiom for compile-time interface satisfaction,
// as in "var _ Interface = (*Concrete)(nil)".
ifisIdent(v.Names[0],"_"){
returnfalse
}
// If the RHS is a zero value, suggest dropping it.
zero:=false
iflit,ok:=rhs.(*ast.BasicLit);ok{
zero=zeroLiteral[lit.Value]
}elseifisIdent(rhs,"nil"){
zero=true
}
ifzero{
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])
returnfalse
}
lhsTyp:=f.pkg.typeOf(v.Type)
rhsTyp:=f.pkg.typeOf(rhs)
if!validType(lhsTyp)||!validType(rhsTyp){
// Type checking failed (often due to missing imports).
returnfalse
}
if!types.Identical(lhsTyp,rhsTyp){
// Assignment to a different type is not redundant.
returnfalse
}
// The next three conditions are for suppressing the warning in situations
// where we were unable to typecheck.
// If the LHS type is an interface, don't warn, since it is probably a
// concrete type on the RHS. Note that our feeble lexical check here
// will only pick up interface{} and other literal interface types;
// that covers most of the cases we care to exclude right now.
if_,ok:=v.Type.(*ast.InterfaceType);ok{
returnfalse
}
// If the RHS is an untyped const, only warn if the LHS type is its default type.
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])
returnfalse
}
returntrue
})
}
funcvalidType(Ttypes.Type)bool{
returnT!=nil&&
T!=types.Typ[types.Invalid]&&
!strings.Contains(T.String(),"invalid type")// good but not foolproof
}
// lintElses examines else blocks. It complains about any else block whose if block ends in a return.
func(f*file)lintElses(){
// We don't want to flag if { } else if { } else { } constructions.
// They will appear as an IfStmt whose Else field is also an IfStmt.
// Record such a node so we ignore it when we visit it.
ignore:=make(map[*ast.IfStmt]bool)
f.walk(func(nodeast.Node)bool{
ifStmt,ok:=node.(*ast.IfStmt)
if!ok||ifStmt.Else==nil{
returntrue
}
ifelseif,ok:=ifStmt.Else.(*ast.IfStmt);ok{
ignore[elseif]=true
returntrue
}
ifignore[ifStmt]{
returntrue
}
if_,ok:=ifStmt.Else.(*ast.BlockStmt);!ok{
// only care about elses without conditions
returntrue
}
iflen(ifStmt.Body.List)==0{
returntrue
}
shortDecl:=false// does the if statement have a ":=" initialization statement?
extra=" (move short variable declaration to its own line if necessary)"
}
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)
}
returntrue
})
}
// lintRanges examines range clauses. It complains about redundant constructions.
p:=f.errorf(rs.Key,1,category("range-loop"),"should omit values from range; this loop is equivalent to `for range ...`")
newRS:=*rs// shallow copy
newRS.Value=nil
newRS.Key=nil
p.ReplacementLine=f.firstLineOf(&newRS,rs)
returntrue
}
ifisIdent(rs.Value,"_"){
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)
newRS:=*rs// shallow copy
newRS.Value=nil
p.ReplacementLine=f.firstLineOf(&newRS,rs)
}
returntrue
})
}
// lintErrorf examines errors.New and testing.Error calls. It complains if its only argument is an fmt.Sprintf invocation.
func(f*file)lintErrorf(){
f.walk(func(nodeast.Node)bool{
ce,ok:=node.(*ast.CallExpr)
if!ok||len(ce.Args)!=1{
returntrue
}
isErrorsNew:=isPkgDot(ce.Fun,"errors","New")
varisTestingErrorbool
se,ok:=ce.Fun.(*ast.SelectorExpr)
ifok&&se.Sel.Name=="Error"{
iftyp:=f.pkg.typeOf(se.X);typ!=nil{
isTestingError=typ.String()=="*testing.T"
}
}
if!isErrorsNew&&!isTestingError{
returntrue
}
if!f.imports("errors"){
returntrue
}
arg:=ce.Args[0]
ce,ok=arg.(*ast.CallExpr)
if!ok||!isPkgDot(ce.Fun,"fmt","Sprintf"){
returntrue
}
errorfPrefix:="fmt"
ifisTestingError{
errorfPrefix=f.render(se.X)
}
p:=f.errorf(node,1,category("errors"),"should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)",f.render(se),errorfPrefix)