Provide lint problem categorisation.
diff --git a/lint.go b/lint.go
index 77188e0..5daef38 100644
--- a/lint.go
+++ b/lint.go
@@ -34,6 +34,7 @@
 	Link       string         // (optional) the link to the style guide for the problem
 	Confidence float64        // a value in (0,1] estimating the confidence in this problem's correctness
 	LineText   string         // the source line
+	Category   string         // a short name for the general category of the problem
 }
 
 func (p *Problem) String() string {
@@ -93,15 +94,35 @@
 	return f.problems
 }
 
-func (f *file) errorf(n ast.Node, confidence float64, link, format string, a ...interface{}) {
+type link string
+type category string
+
+// The variadic arguments may start with link and category types,
+// and must end with a format string and any arguments.
+func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) {
 	p := f.fset.Position(n.Pos())
-	f.problems = append(f.problems, Problem{
+	problem := Problem{
 		Position:   p,
-		Text:       fmt.Sprintf(format, a...),
-		Link:       link,
 		Confidence: confidence,
 		LineText:   srcLine(f.src, p),
-	})
+	}
+
+argLoop:
+	for len(args) > 1 { // always leave at least the format string in args
+		switch v := args[0].(type) {
+		case link:
+			problem.Link = string(v)
+		case category:
+			problem.Category = string(v)
+		default:
+			break argLoop
+		}
+		args = args[1:]
+	}
+
+	problem.Text = fmt.Sprintf(args[0].(string), args[1:]...)
+
+	f.problems = append(f.problems, problem)
 }
 
 func (f *file) scanSortable() {
@@ -151,20 +172,20 @@
 		return
 	}
 
-	const link = styleGuideBase + "#Package_Comments"
+	const ref = styleGuideBase + "#Package_Comments"
 	if f.f.Doc == nil {
-		f.errorf(f.f, 0.2, link, "should have a package comment, unless it's in another file for this package")
+		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()
 	prefix := "Package " + f.f.Name.Name + " "
 	if ts := strings.TrimLeft(s, " \t"); ts != s {
-		f.errorf(f.f.Doc, 1, link, "package comment should not have leading space")
+		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.f.Name.Name != "main" && !strings.HasPrefix(s, prefix) {
-		f.errorf(f.f.Doc, 1, link, `package comment should be of the form "%s..."`, prefix)
+		f.errorf(f.f.Doc, 1, link(ref), category("comments"), `package comment should be of the form "%s..."`, prefix)
 	}
 }
 
@@ -194,8 +215,8 @@
 
 		// This is the first blank import of a group.
 		if imp.Doc == nil && imp.Comment == nil {
-			link := ""
-			f.errorf(imp, 1, link, "a blank import should be only in a main or test package, or have a comment justifying it")
+			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")
 		}
 	}
 }
@@ -206,7 +227,7 @@
 	for i, is := range f.f.Imports {
 		_ = i
 		if is.Name != nil && is.Name.Name == "." && !f.isTest() {
-			f.errorf(is, 1, styleGuideBase+"#Import_Dot", "should not use dot imports")
+			f.errorf(is, 1, link(styleGuideBase+"#Import_Dot"), category("imports"), "should not use dot imports")
 		}
 
 	}
@@ -268,7 +289,7 @@
 func (f *file) lintNames() {
 	// Package names need slightly different handling than other names.
 	if strings.Contains(f.f.Name.Name, "_") && !strings.HasSuffix(f.f.Name.Name, "_test") {
-		f.errorf(f.f, 1, "http://golang.org/doc/effective_go.html#package-names", "don't use an underscore in package name")
+		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")
 	}
 
 	check := func(id *ast.Ident, thing string) {
@@ -278,12 +299,12 @@
 
 		// Handle two common styles from other languages that don't belong in Go.
 		if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") {
-			f.errorf(id, 0.8, styleGuideBase+"#Mixed_Caps", "don't use ALL_CAPS in Go names; use CamelCase")
+			f.errorf(id, 0.8, link(styleGuideBase+"#Mixed_Caps"), category("naming"), "don't use ALL_CAPS in Go names; use CamelCase")
 			return
 		}
 		if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' {
 			should := string(id.Name[1]+'a'-'A') + id.Name[2:]
-			f.errorf(id, 0.8, "", "don't use leading k in Go names; %s %s should be %s", thing, id.Name, should)
+			f.errorf(id, 0.8, link("don't use leading k in Go names; %s %s should be %s"), category("naming"), thing, id.Name, should)
 		}
 
 		should := lintName(id.Name)
@@ -291,10 +312,10 @@
 			return
 		}
 		if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") {
-			f.errorf(id, 0.9, "http://golang.org/doc/effective_go.html#mixed-caps", "don't use underscores in Go names; %s %s should be %s", thing, id.Name, should)
+			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, styleGuideBase+"#Initialisms", "%s %s should be %s", thing, id.Name, should)
+		f.errorf(id, 0.8, link(styleGuideBase+"#Initialisms"), category("naming"), "%s %s should be %s", thing, id.Name, should)
 	}
 	checkList := func(fl *ast.FieldList, thing string) {
 		if fl == nil {
@@ -321,6 +342,7 @@
 			if f.isTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) {
 				return true
 			}
+
 			check(v.Name, "func")
 
 			thing := "func"
@@ -487,7 +509,7 @@
 		return
 	}
 	if doc == nil {
-		f.errorf(t, 1, docCommentsLink, "exported type %v should have comment or be unexported", t.Name)
+		f.errorf(t, 1, link(docCommentsLink), category("comments"), "exported type %v should have comment or be unexported", t.Name)
 		return
 	}
 
@@ -500,7 +522,7 @@
 		}
 	}
 	if !strings.HasPrefix(s, t.Name.Name+" ") {
-		f.errorf(doc, 1, docCommentsLink, `comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.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)
 	}
 }
 
@@ -542,13 +564,13 @@
 		name = recv + "." + name
 	}
 	if fn.Doc == nil {
-		f.errorf(fn, 1, docCommentsLink, "exported %s %s should have comment or be unexported", kind, name)
+		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, docCommentsLink, `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix)
+		f.errorf(fn.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix)
 	}
 }
 
@@ -565,7 +587,7 @@
 		// Check that none are exported except for the first.
 		for _, n := range vs.Names[1:] {
 			if ast.IsExported(n.Name) {
-				f.errorf(vs, 1, "", "exported %s %s should have its own declaration", kind, n.Name)
+				f.errorf(vs, 1, category("comments"), "exported %s %s should have its own declaration", kind, n.Name)
 				return
 			}
 		}
@@ -583,14 +605,14 @@
 			if kind == "const" && gd.Lparen.IsValid() {
 				block = " (or a comment on this block)"
 			}
-			f.errorf(vs, 1, docCommentsLink, "exported %s %s should have comment%s or be unexported", kind, name, block)
+			f.errorf(vs, 1, link(docCommentsLink), category("comments"), "exported %s %s should have comment%s or be unexported", kind, name, block)
 			genDeclMissingComments[gd] = true
 		}
 		return
 	}
 	prefix := name + " "
 	if !strings.HasPrefix(vs.Doc.Text(), prefix) {
-		f.errorf(vs.Doc, 1, docCommentsLink, `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix)
+		f.errorf(vs.Doc, 1, link(docCommentsLink), category("comments"), `comment on exported %s %s should be of the form "%s..."`, kind, name, prefix)
 	}
 }
 
@@ -645,7 +667,7 @@
 				zero = true
 			}
 			if zero {
-				f.errorf(rhs, 0.9, "", "should drop = %s from declaration of var %s; it is the zero value", f.render(rhs), v.Names[0])
+				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])
 				return false
 			}
 			// If the LHS type is an interface, don't warn, since it is probably a
@@ -660,7 +682,7 @@
 			if defType, ok := isUntypedConst(rhs); ok && !isIdent(v.Type, defType) {
 				return false
 			}
-			f.errorf(v.Type, 0.8, "", "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])
+			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])
 			return false
 		}
 		return true
@@ -705,7 +727,7 @@
 			if shortDecl {
 				extra = " (move short variable declaration to its own line if necessary)"
 			}
-			f.errorf(ifStmt.Else, 1, styleGuideBase+"#Indent_Error_Flow", "if block ends with a return statement, so drop this else and outdent its block"+extra)
+			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)
 		}
 		return true
 	})
@@ -727,7 +749,7 @@
 			return true
 		}
 
-		f.errorf(rs.Value, 1, "", "should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", f.render(rs.Key), rs.Tok)
+		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)
 		return true
 	})
 }
@@ -747,7 +769,7 @@
 		if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") {
 			return true
 		}
-		f.errorf(node, 1, "", "should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...)")
+		f.errorf(node, 1, category("errors"), "should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...)")
 		return true
 	})
 }
@@ -778,7 +800,7 @@
 				prefix = "Err"
 			}
 			if !strings.HasPrefix(id.Name, prefix) {
-				f.errorf(id, 0.9, "", "error var %s should have name of the form %sFoo", id.Name, prefix)
+				f.errorf(id, 0.9, category("naming"), "error var %s should have name of the form %sFoo", id.Name, prefix)
 			}
 		}
 	}
@@ -837,7 +859,7 @@
 		if isCap {
 			conf = 0.6
 		}
-		f.errorf(str, conf, styleGuideBase+"#Error_Strings", msg)
+		f.errorf(str, conf, link(styleGuideBase+"#Error_Strings"), category("errors"), msg)
 		return true
 	})
 }
@@ -862,18 +884,18 @@
 			return true
 		}
 		name := names[0].Name
-		const link = styleGuideBase + "#Receiver_Names"
+		const ref = styleGuideBase + "#Receiver_Names"
 		if name == "_" {
-			f.errorf(n, 1, link, `receiver name should not be an underscore`)
+			f.errorf(n, 1, link(ref), category("naming"), `receiver name should not be an underscore`)
 			return true
 		}
 		if badReceiverNames[name] {
-			f.errorf(n, 1, link, `receiver name should be a reflection of its identity; don't use generic names such as "me", "this", or "self"`)
+			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"`)
 			return true
 		}
 		recv := receiverType(fn)
 		if prev, ok := typeReceiver[recv]; ok && prev != name {
-			f.errorf(n, 1, link, "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv)
+			f.errorf(n, 1, link(ref), category("naming"), "receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv)
 			return true
 		}
 		typeReceiver[recv] = name
@@ -904,7 +926,7 @@
 		default:
 			return true
 		}
-		f.errorf(as, 0.8, "", "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix)
+		f.errorf(as, 0.8, category("unary-op"), "should replace %s with %s%s", f.render(as), f.render(as.Lhs[0]), suffix)
 		return true
 	})
 }
@@ -933,7 +955,7 @@
 		if !ok || at.Len != nil {
 			return true
 		}
-		f.errorf(as, 0.8, "", `can probably use "var %s %s" instead`, f.render(as.Lhs[0]), f.render(at))
+		f.errorf(as, 0.8, category("slice"), `can probably use "var %s %s" instead`, f.render(as.Lhs[0]), f.render(at))
 		return true
 	})
 }
@@ -954,7 +976,7 @@
 		// Flag any error parameters found before the last.
 		for _, r := range ret[:len(ret)-1] {
 			if isIdent(r.Type, "error") {
-				f.errorf(fn, 0.9, "", "error should be the last type when returning multiple items")
+				f.errorf(fn, 0.9, category("arg-order"), "error should be the last type when returning multiple items")
 				break // only flag one
 			}
 		}