Implement suggested replacements for problems.

This adds the infrastructure for plumbing suggestions through,
and implements a suggested fix for range-loop.
diff --git a/lint.go b/lint.go
index 8182c0b..1a8c8d4 100644
--- a/lint.go
+++ b/lint.go
@@ -39,6 +39,10 @@
 	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
+
+	// If the problem has a suggested fix (the minority case),
+	// ReplacementLine is a full replacement for the relevant line of the source file.
+	ReplacementLine string
 }
 
 func (p *Problem) String() string {
@@ -190,15 +194,16 @@
 
 // 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{}) {
+// It returns the new Problem.
+func (f *file) errorf(n ast.Node, confidence float64, args ...interface{}) *Problem {
 	pos := f.fset.Position(n.Pos())
 	if pos.Filename == "" {
 		pos.Filename = f.filename
 	}
-	f.pkg.errorfAt(pos, confidence, args...)
+	return f.pkg.errorfAt(pos, confidence, args...)
 }
 
-func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) {
+func (p *pkg) errorfAt(pos token.Position, confidence float64, args ...interface{}) *Problem {
 	problem := Problem{
 		Position:   pos,
 		Confidence: confidence,
@@ -226,6 +231,7 @@
 	problem.Text = fmt.Sprintf(args[0].(string), args[1:]...)
 
 	p.problems = append(p.problems, problem)
+	return &p.problems[len(p.problems)-1]
 }
 
 var gcImporter = gcimporter.Import
@@ -1033,7 +1039,17 @@
 			return true
 		}
 
-		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)
+		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
+		line := f.render(&newRS)
+		if i := strings.Index(line, "\n"); i >= 0 {
+			line = line[:i]
+		}
+		line = f.indentOf(rs) + line
+		p.ReplacementLine = line
+
 		return true
 	})
 }
@@ -1477,6 +1493,18 @@
 	return "", false
 }
 
+func (f *file) indentOf(node ast.Node) string {
+	line := srcLine(f.src, f.fset.Position(node.Pos()))
+	for i, r := range line {
+		switch r {
+		case ' ', '\t':
+		default:
+			return line[:i]
+		}
+	}
+	return line // unusual or empty line
+}
+
 // srcLine returns the complete line at p, including the terminating newline.
 func srcLine(src []byte, p token.Position) string {
 	// Run to end of line in both directions if not at line start/end.
diff --git a/lint_test.go b/lint_test.go
index 90da9b7..91a981e 100644
--- a/lint_test.go
+++ b/lint_test.go
@@ -70,6 +70,11 @@
 					continue
 				}
 				if in.Match.MatchString(p.Text) {
+					// check replacement if we are expecting one
+					if in.Replacement != "" && p.ReplacementLine != in.Replacement {
+						t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, p.ReplacementLine, in.Replacement)
+					}
+
 					// remove this problem from ps
 					copy(ps[i:], ps[i+1:])
 					ps = ps[:len(ps)-1]
@@ -90,8 +95,9 @@
 }
 
 type instruction struct {
-	Line  int            // the line number this applies to
-	Match *regexp.Regexp // what pattern to match
+	Line        int            // the line number this applies to
+	Match       *regexp.Regexp // what pattern to match
+	Replacement string         // what the suggested replacement line should be
 }
 
 // parseInstructions parses instructions from the comments in a Go source file.
@@ -130,9 +136,14 @@
 						t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err)
 					}
 				}
+				var repl string
+				if r, ok := extractReplacement(line); ok {
+					repl = r
+				}
 				ins = append(ins, instruction{
-					Line:  matchLine,
-					Match: rx,
+					Line:        matchLine,
+					Match:       rx,
+					Replacement: repl,
 				})
 			}
 		}
@@ -153,6 +164,18 @@
 	return rx, nil
 }
 
+func extractReplacement(line string) (string, bool) {
+	// Look for this:  / -> `
+	// (the end of a match and start of a backtick string),
+	// and then the closing backtick.
+	const start = "/ -> `"
+	a, b := strings.Index(line, start), strings.LastIndex(line, "`")
+	if a < 0 || a > b {
+		return "", false
+	}
+	return line[a+len(start) : b], true
+}
+
 func render(fset *token.FileSet, x interface{}) string {
 	var buf bytes.Buffer
 	if err := printer.Fprint(&buf, fset, x); err != nil {
diff --git a/testdata/range.go b/testdata/range.go
index a01c2bf..af36a82 100644
--- a/testdata/range.go
+++ b/testdata/range.go
@@ -7,7 +7,7 @@
 	var m map[string]int
 
 	// with :=
-	for x, _ := range m { // MATCH /should omit 2nd value.*range.*equivalent.*for x := range/
+	for x, _ := range m { // MATCH /should omit 2nd value.*range.*equivalent.*for x := range/ -> `	for x := range m {`
 		_ = x
 	}
 	// with =