pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/pkglint



Module Name:    pkgsrc
Committed By:   rillig
Date:           Sat Jan 13 23:56:14 UTC 2018

Modified Files:
        pkgsrc/pkgtools/pkglint: DESCR Makefile
        pkgsrc/pkgtools/pkglint/files: buildlink3_test.go category.go
            check_test.go distinfo.go distinfo_test.go expecter.go files.go
            files_test.go globaldata_test.go globalvars.go licenses_test.go
            line.go linechecker.go linechecker_test.go logging.go mkline.go
            mkline_test.go mklinechecker.go mklinechecker_test.go mklines.go
            mklines_test.go mkparser.go mkparser_test.go package.go
            package_test.go patches.go patches_test.go pkglint.0 pkglint.1
            pkglint.go pkglint_test.go plist.go plist_test.go shell_test.go
            substcontext_test.go vartypecheck.go vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/getopt: getopt.go getopt_test.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: autofix.go autofix_test.go
            logging_test.go
Removed Files:
        pkgsrc/pkgtools/pkglint/files: line_test.go

Log Message:
pkglint: Update to 5.5.1

Changes since 5.5:

* Allow filtering log messages by keyword (--only)
* In --autofix and --show-autofix mode, show only fixable diagnostics
* When called with --source, show the source below the diagnostics
* Don't warn about USE_LANGUAGES in ../../mk/compiler.mk
* Fix autofix for .gz in PLIST


To generate a diff of this commit:
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/DESCR
cvs rdiff -u -r1.524 -r1.525 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/autofix.go \
    pkgsrc/pkgtools/pkglint/files/autofix_test.go \
    pkgsrc/pkgtools/pkglint/files/logging_test.go
cvs rdiff -u -r1.9 -r1.10 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go \
    pkgsrc/pkgtools/pkglint/files/category.go \
    pkgsrc/pkgtools/pkglint/files/expecter.go \
    pkgsrc/pkgtools/pkglint/files/substcontext_test.go
cvs rdiff -u -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/check_test.go \
    pkgsrc/pkgtools/pkglint/files/mklines_test.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/distinfo.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/distinfo_test.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/files.go \
    pkgsrc/pkgtools/pkglint/files/patches_test.go
cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/files_test.go \
    pkgsrc/pkgtools/pkglint/files/globaldata_test.go \
    pkgsrc/pkgtools/pkglint/files/mkparser.go \
    pkgsrc/pkgtools/pkglint/files/pkglint_test.go
cvs rdiff -u -r1.8 -r1.9 pkgsrc/pkgtools/pkglint/files/globalvars.go \
    pkgsrc/pkgtools/pkglint/files/licenses_test.go \
    pkgsrc/pkgtools/pkglint/files/logging.go
cvs rdiff -u -r1.18 -r1.19 pkgsrc/pkgtools/pkglint/files/line.go \
    pkgsrc/pkgtools/pkglint/files/plist.go \
    pkgsrc/pkgtools/pkglint/files/shell_test.go
cvs rdiff -u -r1.11 -r0 pkgsrc/pkgtools/pkglint/files/line_test.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/linechecker.go \
    pkgsrc/pkgtools/pkglint/files/linechecker_test.go
cvs rdiff -u -r1.24 -r1.25 pkgsrc/pkgtools/pkglint/files/mkline.go
cvs rdiff -u -r1.27 -r1.28 pkgsrc/pkgtools/pkglint/files/mkline_test.go \
    pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.6 -r1.7 pkgsrc/pkgtools/pkglint/files/mklinechecker.go \
    pkgsrc/pkgtools/pkglint/files/mkparser_test.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/mklines.go \
    pkgsrc/pkgtools/pkglint/files/patches.go \
    pkgsrc/pkgtools/pkglint/files/plist_test.go
cvs rdiff -u -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/package.go
cvs rdiff -u -r1.15 -r1.16 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.37 -r1.38 pkgsrc/pkgtools/pkglint/files/pkglint.0
cvs rdiff -u -r1.51 -r1.52 pkgsrc/pkgtools/pkglint/files/pkglint.1
cvs rdiff -u -r1.23 -r1.24 pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go \
    pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/pkgtools/pkglint/DESCR
diff -u pkgsrc/pkgtools/pkglint/DESCR:1.2 pkgsrc/pkgtools/pkglint/DESCR:1.3
--- pkgsrc/pkgtools/pkglint/DESCR:1.2   Thu Dec  1 04:05:36 2005
+++ pkgsrc/pkgtools/pkglint/DESCR       Sat Jan 13 23:56:14 2018
@@ -1,6 +1,6 @@
-pkglint is for pkgsrc packages what lint(1) is for C files. It checks
-for various things that the used languages cannot detect, for example
-application-specific restrictions on certain variables.
+pkglint checks whether a pkgsrc package conforms to the various
+conventions established over the years. It produces warnings, errors and
+notes and, upon request, explains them.
 
 Before importing a new package or making changes to an existing package,
 pkglint should be run in the package's directory to check for common

Index: pkgsrc/pkgtools/pkglint/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.524 pkgsrc/pkgtools/pkglint/Makefile:1.525
--- pkgsrc/pkgtools/pkglint/Makefile:1.524      Wed Jan 10 00:39:52 2018
+++ pkgsrc/pkgtools/pkglint/Makefile    Sat Jan 13 23:56:14 2018
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.524 2018/01/10 00:39:52 rillig Exp $
+# $NetBSD: Makefile,v 1.525 2018/01/13 23:56:14 rillig Exp $
 
-PKGNAME=       pkglint-5.5
+PKGNAME=       pkglint-5.5.1
 DISTFILES=     # none
 CATEGORIES=    pkgtools
 

Index: pkgsrc/pkgtools/pkglint/files/buildlink3_test.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.9 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.9        Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/buildlink3_test.go    Sat Jan 13 23:56:14 2018
@@ -1,13 +1,11 @@
 package main
 
-import (
-       check "gopkg.in/check.v1"
-)
+import "gopkg.in/check.v1"
 
 func (s *Suite) Test_ChecklinesBuildlink3Mk(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "# XXX This file was created automatically using createbuildlink-@PKGVERSION@",
                "",
@@ -43,8 +41,8 @@ func (s *Suite) Test_ChecklinesBuildlink
        G.globalData.InitVartypes()
        G.Pkg = NewPackage("x11/hs-X11")
        G.Pkg.EffectivePkgbase = "X11"
-       G.Pkg.EffectivePkgnameLine = NewMkLine(NewLine("Makefile", 3, "DISTNAME=\tX11-1.0", nil))
-       mklines := s.NewMkLines("buildlink3.mk",
+       G.Pkg.EffectivePkgnameLine = T.NewMkLine("Makefile", 3, "DISTNAME=\tX11-1.0")
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\ths-X11",
@@ -68,7 +66,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_name_mismatch_multiple_inclusion(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\tpkgbase1",
@@ -90,7 +88,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_name_mismatch_abi_api(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\ths-X11",
@@ -114,7 +112,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_abi_api_versions(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\ths-X11",
@@ -138,7 +136,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_no_BUILDLINK_TREE_at_beginning(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                ".if !defined(HS_X11_BUILDLINK3_MK)",
@@ -161,7 +159,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_no_BUILDLINK_TREE_at_end(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_DEPMETHOD.hs-X11?=\tfull",
@@ -189,7 +187,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_multiple_inclusion_wrong(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\ths-X11",
@@ -207,7 +205,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_missing_endif(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\tpkgbase1",
@@ -224,7 +222,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_unknown_dependency_patterns(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+= hs-X11",
@@ -250,7 +248,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_PKGBASE_with_variable(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\t${PYPKGPREFIX}-wxWidgets",
@@ -274,7 +272,7 @@ func (s *Suite) Test_ChecklinesBuildlink
 func (s *Suite) Test_ChecklinesBuildlink3Mk_PKGBASE_with_unknown_variable(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                "BUILDLINK_TREE+=\t${LICENSE}-wxWidgets",
@@ -304,7 +302,7 @@ func (s *Suite) Test_ChecklinesBuildlink
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("buildlink3.mk",
+       mklines := T.NewMkLines("buildlink3.mk",
                mkrcsid,
                "",
                ".if ${VAAPI_AVAILABLE} == \"yes\"",
Index: pkgsrc/pkgtools/pkglint/files/category.go
diff -u pkgsrc/pkgtools/pkglint/files/category.go:1.9 pkgsrc/pkgtools/pkglint/files/category.go:1.10
--- pkgsrc/pkgtools/pkglint/files/category.go:1.9       Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/category.go   Sat Jan 13 23:56:14 2018
@@ -127,17 +127,19 @@ func CheckdirCategory() {
 
                if !fAtend && (mAtend || fCurrent < mCurrent) {
                        if !mCheck[fCurrent] {
-                               if !line.AutofixInsertBefore("SUBDIR+=\t" + fCurrent) {
-                                       line.Errorf("%q exists in the file system, but not in the Makefile.", fCurrent)
-                               }
+                               fix := line.Autofix()
+                               fix.Errorf("%q exists in the file system, but not in the Makefile.", fCurrent)
+                               fix.InsertBefore("SUBDIR+=\t" + fCurrent)
+                               fix.Apply()
                        }
                        fNeednext = true
 
                } else if !mAtend && (fAtend || mCurrent < fCurrent) {
                        if !fCheck[mCurrent] {
-                               if !line.AutofixDelete() {
-                                       line.Errorf("%q exists in the Makefile, but not in the file system.", mCurrent)
-                               }
+                               fix := line.Autofix()
+                               fix.Errorf("%q exists in the Makefile, but not in the file system.", mCurrent)
+                               fix.Delete()
+                               fix.Apply()
                        }
                        mNeednext = true
 
Index: pkgsrc/pkgtools/pkglint/files/expecter.go
diff -u pkgsrc/pkgtools/pkglint/files/expecter.go:1.9 pkgsrc/pkgtools/pkglint/files/expecter.go:1.10
--- pkgsrc/pkgtools/pkglint/files/expecter.go:1.9       Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/expecter.go   Sat Jan 13 23:56:14 2018
@@ -88,9 +88,10 @@ func (exp *Expecter) ExpectEmptyLine(war
        }
 
        if warnSpace {
-               if !exp.CurrentLine().AutofixInsertBefore("") {
-                       exp.CurrentLine().Notef("Empty line expected.")
-               }
+               fix := exp.CurrentLine().Autofix()
+               fix.Notef("Empty line expected.")
+               fix.InsertBefore("")
+               fix.Apply()
        }
        return false
 }
Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.9 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.9      Wed Jan 10 00:39:52 2018
+++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go  Sat Jan 13 23:56:14 2018
@@ -186,5 +186,5 @@ func simulateSubstLines(texts ...string)
 }
 
 func newSubstLine(lineno int, text string) MkLine {
-       return NewMkLine(NewLine("Makefile", lineno, text, nil))
+       return T.NewMkLine("Makefile", lineno, text)
 }

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.14 pkgsrc/pkgtools/pkglint/files/check_test.go:1.15
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.14    Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sat Jan 13 23:56:14 2018
@@ -10,7 +10,7 @@ import (
        "strings"
        "testing"
 
-       check "gopkg.in/check.v1"
+       "gopkg.in/check.v1"
        "netbsd.org/pkglint/textproc"
        "netbsd.org/pkglint/trace"
 )
@@ -73,54 +73,24 @@ func (s *Suite) CheckOutputLines(expecte
        s.c().Check(s.Output(), equals, expectedOutput)
 }
 
-// Arguments are either (lineno, orignl) or (lineno, orignl, textnl).
-func (s *Suite) NewRawLines(args ...interface{}) []*RawLine {
-       rawlines := make([]*RawLine, len(args)/2)
-       j := 0
-       for i := 0; i < len(args); i += 2 {
-               lineno := args[i].(int)
-               orignl := args[i+1].(string)
-               textnl := orignl
-               if i+2 < len(args) {
-                       if s, ok := args[i+2].(string); ok {
-                               textnl = s
-                               i++
-                       }
-               }
-               rawlines[j] = &RawLine{lineno, orignl, textnl}
-               j++
-       }
-       return rawlines[:j]
-}
-
-func (s *Suite) NewLines(fname string, texts ...string) []Line {
-       result := make([]Line, len(texts))
-       for i, text := range texts {
-               textnl := text + "\n"
-               result[i] = NewLine(fname, i+1, text, s.NewRawLines(i+1, textnl))
-       }
-       return result
-}
-
-func (s *Suite) NewMkLines(fname string, lines ...string) *MkLines {
-       return NewMkLines(s.NewLines(fname, lines...))
-}
-
 func (s *Suite) BeginDebugToStdout() {
-       G.logOut = os.Stdout
+       G.logOut = NewSeparatorWriter(os.Stdout)
        trace.Out = os.Stdout
        trace.Tracing = true
 }
 
 func (s *Suite) EndDebugToStdout() {
-       G.logOut = &s.stdout
+       G.logOut = NewSeparatorWriter(&s.stdout)
        trace.Out = &s.stdout
        trace.Tracing = false
 }
 
+// UseCommandLine simulates a command line for the remainder of the test.
+// See Pkglint.ParseCommandLine
 func (s *Suite) UseCommandLine(args ...string) {
        exitcode := new(Pkglint).ParseCommandLine(append([]string{"pkglint"}, args...))
        if exitcode != nil && *exitcode != 0 {
+               s.CheckOutputEmpty()
                s.c().Fatalf("Cannot parse command line: %#v", args)
        }
        G.opts.LogVerbose = true // See SetUpTest
@@ -200,7 +170,7 @@ func (s *Suite) ExpectFatalError(action 
 func (s *Suite) SetUpTest(c *check.C) {
        G = GlobalVars{Testing: true}
        textproc.Testing = true
-       G.logOut, G.logErr, trace.Out = &s.stdout, &s.stderr, &s.stdout
+       G.logOut, G.logErr, trace.Out = NewSeparatorWriter(&s.stdout), NewSeparatorWriter(&s.stderr), &s.stdout
        s.checkC = c
        s.UseCommandLine( /* no arguments */ )
        s.checkC = nil
@@ -219,3 +189,66 @@ func (s *Suite) TearDownTest(c *check.C)
 var _ = check.Suite(new(Suite))
 
 func Test(t *testing.T) { check.TestingT(t) }
+
+var T TestObjectCreator
+
+type TestObjectCreator struct{}
+
+// Arguments are either (lineno, orignl) or (lineno, orignl, textnl).
+func (t TestObjectCreator) NewRawLines(args ...interface{}) []*RawLine {
+       rawlines := make([]*RawLine, len(args)/2)
+       j := 0
+       for i := 0; i < len(args); i += 2 {
+               lineno := args[i].(int)
+               orignl := args[i+1].(string)
+               textnl := orignl
+               if i+2 < len(args) {
+                       if s, ok := args[i+2].(string); ok {
+                               textnl = s
+                               i++
+                       }
+               }
+               rawlines[j] = &RawLine{lineno, orignl, textnl}
+               j++
+       }
+       return rawlines[:j]
+}
+
+func (t TestObjectCreator) NewLine(filename string, lineno int, text string) Line {
+       textnl := text + "\n"
+       rawLine := RawLine{lineno, textnl, textnl}
+       return NewLine(filename, lineno, text, []*RawLine{&rawLine})
+}
+
+func (t TestObjectCreator) NewMkLine(fileName string, lineno int, text string) MkLine {
+       return NewMkLine(t.NewLine(fileName, lineno, text))
+}
+
+func (t TestObjectCreator) NewShellLine(fileName string, lineno int, text string) *ShellLine {
+       return NewShellLine(t.NewMkLine(fileName, lineno, text))
+}
+
+// NewLines generates a slice of simple lines,
+// i.e. each logical line has exactly one physical line.
+// To work with line continuations like in Makefiles,
+// use Suite.CreateTmpFileLines together with Suite.LoadExistingLines.
+func (t TestObjectCreator) NewLines(fname string, texts ...string) []Line {
+       return t.NewLinesAt(fname, 1, texts...)
+}
+
+func (t TestObjectCreator) NewMkLines(fname string, lines ...string) *MkLines {
+       return NewMkLines(t.NewLines(fname, lines...))
+}
+
+// NewLinesAt generates a slice of simple lines,
+// i.e. each logical line has exactly one physical line.
+// To work with line continuations like in Makefiles,
+// use Suite.CreateTmpFileLines together with Suite.LoadExistingLines.
+func (t TestObjectCreator) NewLinesAt(fname string, firstLine int, texts ...string) []Line {
+       result := make([]Line, len(texts))
+       for i, text := range texts {
+               textnl := text + "\n"
+               result[i] = NewLine(fname, i+firstLine, text, t.NewRawLines(i+firstLine, textnl))
+       }
+       return result
+}
Index: pkgsrc/pkgtools/pkglint/files/mklines_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.14 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.15
--- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.14  Sun Jan  7 01:13:21 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines_test.go       Sat Jan 13 23:56:14 2018
@@ -10,7 +10,7 @@ func (s *Suite) Test_MkLines_Check__auto
        s.Init(c)
        s.UseCommandLine("--autofix", "-Wspace")
        tmpfile := s.CreateTmpFile("fname.mk", "")
-       mklines := s.NewMkLines(tmpfile,
+       mklines := T.NewMkLines(tmpfile,
                mkrcsid,
                ".if defined(A)",
                ".for a in ${A}",
@@ -39,7 +39,7 @@ func (s *Suite) Test_MkLines_Check__auto
 
 func (s *Suite) Test_MkLines_Check__unusual_target(c *check.C) {
        s.Init(c)
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "echo: echo.c",
@@ -53,7 +53,7 @@ func (s *Suite) Test_MkLines_Check__unus
 
 func (s *Suite) Test_MkLineChecker_checkInclude__Makefile(c *check.C) {
        s.Init(c)
-       mkline := NewMkLine(NewLine("Makefile", 2, ".include \"../../other/package/Makefile\"", nil))
+       mkline := T.NewMkLine("Makefile", 2, ".include \"../../other/package/Makefile\"")
 
        MkLineChecker{mkline}.checkInclude()
 
@@ -67,7 +67,7 @@ func (s *Suite) Test_MkLines_quoting_LDF
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
        G.Pkg = NewPackage("category/pkgbase")
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "GNU_CONFIGURE=\tyes",
                "CONFIGURE_ENV+=\tX_LIBS=${X11_LDFLAGS:Q}")
@@ -157,7 +157,7 @@ func (s *Suite) Test_MkLines__variable_a
 func (s *Suite) Test_MkLines__variable_alignment_space_and_tab(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wspace")
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "VAR=    space",
@@ -175,7 +175,7 @@ func (s *Suite) Test_MkLines__for_loop_m
        s.RegisterTool(&Tool{Name: "echo", Varname: "ECHO", Predefined: true})
        s.RegisterTool(&Tool{Name: "find", Varname: "FIND", Predefined: true})
        s.RegisterTool(&Tool{Name: "pax", Varname: "PAX", Predefined: true})
-       mklines := s.NewMkLines("audio/squeezeboxserver/Makefile",
+       mklines := T.NewMkLines("audio/squeezeboxserver/Makefile",
                mkrcsid,
                "",
                ".for _list_ _dir_ in ${SBS_COPY}",
@@ -195,7 +195,7 @@ func (s *Suite) Test_MkLines__comparing_
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("databases/gdbm_compat/builtin.mk",
+       mklines := T.NewMkLines("databases/gdbm_compat/builtin.mk",
                mkrcsid,
                ".if ${USE_BUILTIN.gdbm} == \"no\"",
                ".endif",
@@ -212,7 +212,7 @@ func (s *Suite) Test_MkLines__varuse_sh_
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("lang/qore/module.mk",
+       mklines := T.NewMkLines("lang/qore/module.mk",
                mkrcsid,
                "qore-version=\tqore --short-version | ${SED} -e s/-.*//",
                "PLIST_SUBST+=\tQORE_VERSION=\"${qore-version:sh}\"")
@@ -234,7 +234,7 @@ func (s *Suite) Test_MkLines__varuse_par
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("converters/wv2/Makefile",
+       mklines := T.NewMkLines("converters/wv2/Makefile",
                mkrcsid,
                "CONFIGURE_ARGS+=\t\t${CONFIGURE_ARGS.${ICONV_TYPE}-iconv}",
                "CONFIGURE_ARGS.gnu-iconv=\t--with-libiconv=${BUILDLINK_PREFIX.iconv}")
@@ -248,7 +248,7 @@ func (s *Suite) Test_MkLines__loop_modif
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("chat/xchat/Makefile",
+       mklines := T.NewMkLines("chat/xchat/Makefile",
                mkrcsid,
                "GCONF_SCHEMAS=\tapps_xchat_url_handler.schemas",
                "post-install:",
@@ -266,7 +266,7 @@ func (s *Suite) Test_MkLines__loop_modif
 func (s *Suite) Test_MkLines__PKG_SKIP_REASON_depending_on_OPSYS(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "PKG_SKIP_REASON+=\t\"Fails everywhere\"",
                ".if ${OPSYS} == \"Cygwin\"",
@@ -283,7 +283,7 @@ func (s *Suite) Test_MkLines__PKG_SKIP_R
 func (s *Suite) Test_MkLines__indirect_variables(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       mklines := s.NewMkLines("net/uucp/Makefile",
+       mklines := T.NewMkLines("net/uucp/Makefile",
                mkrcsid,
                "",
                "post-configure:",
@@ -301,7 +301,7 @@ func (s *Suite) Test_MkLines__indirect_v
 func (s *Suite) Test_MkLines_Check__list_variable_as_part_of_word(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       mklines := s.NewMkLines("converters/chef/Makefile",
+       mklines := T.NewMkLines("converters/chef/Makefile",
                mkrcsid,
                "\tcd ${WRKSRC} && tr '\\r' '\\n' < ${DISTDIR}/${DIST_SUBDIR}/${DISTFILES} > chef.l")
 
@@ -316,7 +316,7 @@ func (s *Suite) Test_MkLines_Check__abso
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("games/heretic2-demo/Makefile",
+       mklines := T.NewMkLines("games/heretic2-demo/Makefile",
                mkrcsid,
                ".if ${OPSYS} == \"DragonFly\"",
                "TOOLS_PLATFORM.gtar=\t/usr/bin/bsdtar",
@@ -336,7 +336,7 @@ func (s *Suite) Test_MkLines_Check__abso
 func (s *Suite) Test_MkLines_checkForUsedComment(c *check.C) {
        s.Init(c)
        s.UseCommandLine("--show-autofix")
-       s.NewMkLines("Makefile.common",
+       T.NewMkLines("Makefile.common",
                mkrcsid,
                "",
                "# used by sysutils/mc",
@@ -344,24 +344,24 @@ func (s *Suite) Test_MkLines_checkForUse
 
        s.CheckOutputEmpty()
 
-       s.NewMkLines("Makefile.common").checkForUsedComment("category/package")
+       T.NewMkLines("Makefile.common").checkForUsedComment("category/package")
 
        s.CheckOutputEmpty()
 
-       s.NewMkLines("Makefile.common",
+       T.NewMkLines("Makefile.common",
                mkrcsid,
        ).checkForUsedComment("category/package")
 
        s.CheckOutputEmpty()
 
-       s.NewMkLines("Makefile.common",
+       T.NewMkLines("Makefile.common",
                mkrcsid,
                "",
        ).checkForUsedComment("category/package")
 
        s.CheckOutputEmpty()
 
-       s.NewMkLines("Makefile.common",
+       T.NewMkLines("Makefile.common",
                mkrcsid,
                "",
                "VARNAME=\tvalue",
@@ -371,7 +371,7 @@ func (s *Suite) Test_MkLines_checkForUse
                "WARN: Makefile.common:2: Please add a line \"# used by category/package\" here.",
                "AUTOFIX: Makefile.common:2: Inserting a line \"# used by category/package\" before this line.")
 
-       s.NewMkLines("Makefile.common",
+       T.NewMkLines("Makefile.common",
                mkrcsid,
                "#",
                "#",
@@ -380,10 +380,12 @@ func (s *Suite) Test_MkLines_checkForUse
        s.CheckOutputLines(
                "WARN: Makefile.common:3: Please add a line \"# used by category/package\" here.",
                "AUTOFIX: Makefile.common:3: Inserting a line \"# used by category/package\" before this line.")
+
+       c.Check(G.autofixAvailable, equals, true)
 }
 
 func (s *Suite) Test_MkLines_DetermineUsedVariables__simple(c *check.C) {
-       mklines := s.NewMkLines("fname",
+       mklines := T.NewMkLines("fname",
                "\t${VAR}")
        mkline := mklines.mklines[0]
        G.Mk = mklines
@@ -395,7 +397,7 @@ func (s *Suite) Test_MkLines_DetermineUs
 }
 
 func (s *Suite) Test_MkLines_DetermineUsedVariables__nested(c *check.C) {
-       mklines := s.NewMkLines("fname",
+       mklines := T.NewMkLines("fname",
                "\t${outer.${inner}}")
        mkline := mklines.mklines[0]
        G.Mk = mklines
@@ -412,7 +414,7 @@ func (s *Suite) Test_MkLines_PrivateTool
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("fname",
+       mklines := T.NewMkLines("fname",
                mkrcsid,
                "",
                "\tmd5sum filename")
@@ -427,7 +429,7 @@ func (s *Suite) Test_MkLines_PrivateTool
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("fname",
+       mklines := T.NewMkLines("fname",
                mkrcsid,
                "TOOLS_CREATE+=\tmd5sum",
                "",
@@ -441,7 +443,7 @@ func (s *Suite) Test_MkLines_PrivateTool
 func (s *Suite) Test_MkLines_Check_indentation(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       mklines := s.NewMkLines("options.mk",
+       mklines := T.NewMkLines("options.mk",
                mkrcsid,
                ". if !defined(GUARD_MK)",
                ". if ${OPSYS} == ${OPSYS}",
@@ -486,7 +488,7 @@ func (s *Suite) Test_MkLines_wip_categor
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
        s.RegisterTool(&Tool{Name: "rm", Varname: "RM", Predefined: true})
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "COMMENT=\tWIP pkgsrc packages",

Index: pkgsrc/pkgtools/pkglint/files/distinfo.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo.go:1.16 pkgsrc/pkgtools/pkglint/files/distinfo.go:1.17
--- pkgsrc/pkgtools/pkglint/files/distinfo.go:1.16      Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/distinfo.go   Sat Jan 13 23:56:14 2018
@@ -129,9 +129,11 @@ func (ck *distinfoLinesChecker) checkPat
                return
        }
        if distinfoSha1Hex != fileSha1Hex {
-               if !line.AutofixReplace(distinfoSha1Hex, fileSha1Hex) {
-                       line.Errorf("%s hash of %s differs (distinfo has %s, patch file has %s). Run \"%s makepatchsum\".", "SHA1", patchFname, distinfoSha1Hex, fileSha1Hex, confMake)
-               }
+               fix := line.Autofix()
+               fix.Errorf("%s hash of %s differs (distinfo has %s, patch file has %s). Run \"%s makepatchsum\".",
+                       "SHA1", patchFname, distinfoSha1Hex, fileSha1Hex, confMake)
+               fix.Replace(distinfoSha1Hex, fileSha1Hex)
+               fix.Apply()
        }
 }
 
@@ -183,7 +185,10 @@ func AutofixDistinfo(oldSha1, newSha1 st
        distinfoFilename := G.CurrentDir + "/" + G.Pkg.DistinfoFile
        if lines, err := readLines(distinfoFilename, false); err == nil {
                for _, line := range lines {
-                       line.AutofixReplace(oldSha1, newSha1)
+                       fix := line.Autofix()
+                       fix.Warnf("Silent-Magic-Diagnostic")
+                       fix.Replace(oldSha1, newSha1)
+                       fix.Apply()
                }
                SaveAutofixChanges(lines)
        }

Index: pkgsrc/pkgtools/pkglint/files/distinfo_test.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.10 pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.11
--- pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.10 Sun Jan 29 14:27:48 2017
+++ pkgsrc/pkgtools/pkglint/files/distinfo_test.go      Sat Jan 13 23:56:14 2018
@@ -1,8 +1,6 @@
 package main
 
-import (
-       check "gopkg.in/check.v1"
-)
+import "gopkg.in/check.v1"
 
 func (s *Suite) Test_ChecklinesDistinfo(c *check.C) {
        s.Init(c)
@@ -13,7 +11,7 @@ func (s *Suite) Test_ChecklinesDistinfo(
                "patch contents\n")
        G.CurrentDir = s.tmpdir
 
-       ChecklinesDistinfo(s.NewLines("distinfo",
+       ChecklinesDistinfo(T.NewLines("distinfo",
                "should be the RCS ID",
                "should be empty",
                "MD5 (distfile.tar.gz) = 12345678901234567890123456789012",
@@ -31,11 +29,11 @@ func (s *Suite) Test_ChecklinesDistinfo(
 
 func (s *Suite) Test_ChecklinesDistinfo_global_hash_mismatch(c *check.C) {
        s.Init(c)
-       otherLine := NewLine("other/distinfo", 7, "dummy", nil)
+       otherLine := T.NewLine("other/distinfo", 7, "dummy")
        G.Hash = make(map[string]*Hash)
        G.Hash["SHA512:pkgname-1.0.tar.gz"] = &Hash{"asdfasdf", otherLine}
 
-       ChecklinesDistinfo(s.NewLines("distinfo",
+       ChecklinesDistinfo(T.NewLines("distinfo",
                "$"+"NetBSD$",
                "",
                "SHA512 (pkgname-1.0.tar.gz) = 12341234"))
@@ -59,7 +57,7 @@ func (s *Suite) Test_ChecklinesDistinfo_
                "/distinfo/...\n")
        G.CurrentDir = s.tmpdir
 
-       ChecklinesDistinfo(s.NewLines(s.tmpdir+"/distinfo",
+       ChecklinesDistinfo(T.NewLines(s.tmpdir+"/distinfo",
                "$"+"NetBSD$",
                "",
                "SHA1 (patch-aa) = 5ad1fb9b3c328fff5caa1a23e8f330e707dd50c0"))
@@ -75,7 +73,7 @@ func (s *Suite) Test_ChecklinesDistinfo_
        s.CreateTmpFile("patches/patch-src-Makefile", "")
        G.CurrentDir = s.tmpdir
 
-       ChecklinesDistinfo(s.NewLines(s.tmpdir+"/distinfo",
+       ChecklinesDistinfo(T.NewLines(s.tmpdir+"/distinfo",
                "$"+"NetBSD$",
                "",
                "SHA1 (distfile.tar.gz) = ...",
@@ -93,7 +91,7 @@ func (s *Suite) Test_ChecklinesDistinfo_
        s.CreateTmpFile("patches/manual-libtool.m4", "")
        G.CurrentDir = s.tmpdir
 
-       ChecklinesDistinfo(s.NewLines(s.tmpdir+"/distinfo",
+       ChecklinesDistinfo(T.NewLines(s.tmpdir+"/distinfo",
                "$"+"NetBSD$",
                "",
                "SHA1 (patch-aa) = ..."))

Index: pkgsrc/pkgtools/pkglint/files/files.go
diff -u pkgsrc/pkgtools/pkglint/files/files.go:1.12 pkgsrc/pkgtools/pkglint/files/files.go:1.13
--- pkgsrc/pkgtools/pkglint/files/files.go:1.12 Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/files.go      Sat Jan 13 23:56:14 2018
@@ -2,8 +2,6 @@ package main
 
 import (
        "io/ioutil"
-       "netbsd.org/pkglint/trace"
-       "os"
        "strings"
 )
 
@@ -138,50 +136,3 @@ func convertToLogicalLines(fname string,
 
        return loglines
 }
-
-func SaveAutofixChanges(lines []Line) (autofixed bool) {
-       if trace.Tracing {
-               defer trace.Call0()()
-       }
-
-       if !G.opts.Autofix {
-               for _, line := range lines {
-                       if line.Changed {
-                               G.autofixAvailable = true
-                       }
-               }
-               return
-       }
-
-       changes := make(map[string][]string)
-       changed := make(map[string]bool)
-       for _, line := range lines {
-               if line.Changed {
-                       changed[line.Filename] = true
-               }
-               changes[line.Filename] = append(changes[line.Filename], line.modifiedLines()...)
-       }
-
-       for fname := range changed {
-               changedLines := changes[fname]
-               tmpname := fname + ".pkglint.tmp"
-               text := ""
-               for _, changedLine := range changedLines {
-                       text += changedLine
-               }
-               err := ioutil.WriteFile(tmpname, []byte(text), 0666)
-               if err != nil {
-                       NewLineWhole(tmpname).Errorf("Cannot write.")
-                       continue
-               }
-               err = os.Rename(tmpname, fname)
-               if err != nil {
-                       NewLineWhole(fname).Errorf("Cannot overwrite with auto-fixed content.")
-                       continue
-               }
-               msg := "Has been auto-fixed. Please re-run pkglint."
-               logs(llAutofix, fname, "", msg, msg)
-               autofixed = true
-       }
-       return
-}
Index: pkgsrc/pkgtools/pkglint/files/patches_test.go
diff -u pkgsrc/pkgtools/pkglint/files/patches_test.go:1.12 pkgsrc/pkgtools/pkglint/files/patches_test.go:1.13
--- pkgsrc/pkgtools/pkglint/files/patches_test.go:1.12  Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/patches_test.go       Sat Jan 13 23:56:14 2018
@@ -8,7 +8,7 @@ import (
 func (s *Suite) Test_ChecklinesPatch__with_comment(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       lines := s.NewLines("patch-WithComment",
+       lines := T.NewLines("patch-WithComment",
                "$"+"NetBSD$",
                "",
                "Text",
@@ -84,7 +84,7 @@ func (s *Suite) Test_ChecklinesPatch__wi
 func (s *Suite) Test_ChecklinesPatch__without_comment(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       lines := s.NewLines("patch-WithoutComment",
+       lines := T.NewLines("patch-WithoutComment",
                "$"+"NetBSD$",
                "",
                "--- file.orig",
@@ -104,7 +104,7 @@ func (s *Suite) Test_ChecklinesPatch__wi
 func (s *Suite) Test_ChecklinesPatch__git_without_comment(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "diff --git a/aa b/aa",
@@ -123,7 +123,7 @@ func (s *Suite) Test_ChecklinesPatch__gi
 
 func (s *Suite) Test_checklineOtherAbsolutePathname(c *check.C) {
        s.Init(c)
-       line := NewLine("patch-ag", 1, "+$install -s -c ./bin/rosegarden ${DESTDIR}$BINDIR", nil)
+       line := T.NewLine("patch-ag", 1, "+$install -s -c ./bin/rosegarden ${DESTDIR}$BINDIR")
 
        checklineOtherAbsolutePathname(line, line.Text)
 
@@ -133,7 +133,7 @@ func (s *Suite) Test_checklineOtherAbsol
 func (s *Suite) Test_ChecklinesPatch__error_code(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       lines := s.NewLines("patch-ErrorCode",
+       lines := T.NewLines("patch-ErrorCode",
                "$"+"NetBSD$",
                "",
                "*** Error code 1", // Looks like a context diff, but isn't.
@@ -154,7 +154,7 @@ func (s *Suite) Test_ChecklinesPatch__er
 func (s *Suite) Test_ChecklinesPatch__wrong_header_order(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       lines := s.NewLines("patch-WrongOrder",
+       lines := T.NewLines("patch-WrongOrder",
                "$"+"NetBSD$",
                "",
                "Text",
@@ -177,7 +177,7 @@ func (s *Suite) Test_ChecklinesPatch__wr
 func (s *Suite) Test_ChecklinesPatch__context_diff(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       lines := s.NewLines("patch-ctx",
+       lines := T.NewLines("patch-ctx",
                "$"+"NetBSD$",
                "",
                "diff -cr history.c.orig history.c",
@@ -193,7 +193,7 @@ func (s *Suite) Test_ChecklinesPatch__co
 
 func (s *Suite) Test_ChecklinesPatch__no_patch(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "-- oldfile",
@@ -207,7 +207,7 @@ func (s *Suite) Test_ChecklinesPatch__no
 
 func (s *Suite) Test_ChecklinesPatch__two_patched_files(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "--- oldfile",
@@ -230,7 +230,7 @@ func (s *Suite) Test_ChecklinesPatch__tw
 
 func (s *Suite) Test_ChecklinesPatch__documentation_that_looks_like_patch_lines(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "--- oldfile",
@@ -247,7 +247,7 @@ func (s *Suite) Test_ChecklinesPatch__do
 
 func (s *Suite) Test_ChecklinesPatch__only_unified_header_but_no_content(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-unified",
+       lines := T.NewLines("patch-unified",
                "$"+"NetBSD$",
                "",
                "Documentation for the patch",
@@ -263,7 +263,7 @@ func (s *Suite) Test_ChecklinesPatch__on
 
 func (s *Suite) Test_ChecklinesPatch__only_context_header_but_no_content(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-context",
+       lines := T.NewLines("patch-context",
                "$"+"NetBSD$",
                "",
                "Documentation for the patch",
@@ -281,7 +281,7 @@ func (s *Suite) Test_ChecklinesPatch__on
 
 func (s *Suite) Test_ChecklinesPatch__Makefile_with_absolute_pathnames(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-unified",
+       lines := T.NewLines("patch-unified",
                "$"+"NetBSD$",
                "",
                "Documentation for the patch",
@@ -317,7 +317,7 @@ func (s *Suite) Test_ChecklinesPatch__Ma
 
 func (s *Suite) Test_ChecklinesPatch__no_newline_with_text_following(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "comment",
@@ -339,7 +339,7 @@ func (s *Suite) Test_ChecklinesPatch__no
 
 func (s *Suite) Test_ChecklinesPatch__no_newline(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "comment",
@@ -359,7 +359,7 @@ func (s *Suite) Test_ChecklinesPatch__no
 
 func (s *Suite) Test_ChecklinesPatch__empty_lines_left_out_at_eof(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "comment",
@@ -383,7 +383,7 @@ func (s *Suite) Test_ChecklinesPatch__em
 // Since this is no problem for patch(1), pkglint also doesn't complain.
 func (s *Suite) Test_ChecklinesPatch__context_lines_with_tab_instead_of_space(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("patch-aa",
+       lines := T.NewLines("patch-aa",
                "$"+"NetBSD$",
                "",
                "comment",

Index: pkgsrc/pkgtools/pkglint/files/files_test.go
diff -u pkgsrc/pkgtools/pkglint/files/files_test.go:1.11 pkgsrc/pkgtools/pkglint/files/files_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/files_test.go:1.11    Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/files_test.go Sat Jan 13 23:56:14 2018
@@ -55,70 +55,3 @@ func (s *Suite) Test_splitRawLine(c *che
        c.Check(trailingWhitespace, equals, "   ")
        c.Check(continuation, equals, "\\")
 }
-
-func (s *Suite) Test_show_autofix(c *check.C) {
-       s.Init(c)
-       s.UseCommandLine("--show-autofix")
-       fname := s.CreateTmpFile("Makefile", ""+
-               "line1\n"+
-               "line2\n"+
-               "line3\n")
-       lines := LoadExistingLines(fname, true)
-
-       if !lines[1].AutofixReplaceRegexp(`.`, "X") {
-               lines[1].Warnf("Something's wrong here.") // Prints the autofix NOTE afterwards
-       }
-       SaveAutofixChanges(lines)
-
-       c.Check(lines[1].raw[0].textnl, equals, "XXXXX\n")
-       c.Check(s.LoadTmpFile("Makefile"), equals, "line1\nline2\nline3\n")
-       s.CheckOutputLines(
-               "WARN: ~/Makefile:2: Something's wrong here.",
-               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".")
-}
-
-func (s *Suite) Test_autofix(c *check.C) {
-       s.Init(c)
-       s.UseCommandLine("--autofix")
-       fname := s.CreateTmpFile("Makefile", ""+
-               "line1\n"+
-               "line2\n"+
-               "line3\n")
-       lines := LoadExistingLines(fname, true)
-
-       if !lines[1].AutofixReplaceRegexp(`.`, "X") {
-               lines[1].Warnf("Something's wrong here.") // Prints the autofix NOTE afterwards
-       }
-       SaveAutofixChanges(lines)
-
-       c.Check(s.LoadTmpFile("Makefile"), equals, "line1\nXXXXX\nline3\n")
-       s.CheckOutputLines(
-               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
-               "AUTOFIX: ~/Makefile: Has been auto-fixed. Please re-run pkglint.")
-}
-
-func (s *Suite) Test_autofix_MkLines(c *check.C) {
-       s.Init(c)
-       s.UseCommandLine("--autofix")
-       fname := s.CreateTmpFile("Makefile", ""+
-               "line1 := value1\n"+
-               "line2 := value2\n"+
-               "line3 := value3\n")
-       pkg := NewPackage("category/basename")
-       G.Pkg = pkg
-       mklines := pkg.loadPackageMakefile(fname)
-       G.Pkg = nil
-
-       if !mklines.mklines[1].AutofixReplaceRegexp(`.`, "X") {
-               mklines.mklines[1].Warnf("Something's wrong here.") // Prints the autofix NOTE afterwards
-       }
-       SaveAutofixChanges(mklines.lines)
-
-       c.Check(s.LoadTmpFile("Makefile"), equals, ""+
-               "line1 := value1\n"+
-               "XXXXXXXXXXXXXXX\n"+
-               "line3 := value3\n")
-       s.CheckOutputLines(
-               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
-               "AUTOFIX: ~/Makefile: Has been auto-fixed. Please re-run pkglint.")
-}
Index: pkgsrc/pkgtools/pkglint/files/globaldata_test.go
diff -u pkgsrc/pkgtools/pkglint/files/globaldata_test.go:1.11 pkgsrc/pkgtools/pkglint/files/globaldata_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/globaldata_test.go:1.11       Sun Jan 29 14:27:48 2017
+++ pkgsrc/pkgtools/pkglint/files/globaldata_test.go    Sat Jan 13 23:56:14 2018
@@ -1,7 +1,7 @@
 package main
 
 import (
-       check "gopkg.in/check.v1"
+       "gopkg.in/check.v1"
        "netbsd.org/pkglint/trace"
 )
 
@@ -13,7 +13,7 @@ func (s *Suite) Test_GlobalData_InitVart
 }
 
 func (s *Suite) Test_parselinesSuggestedUpdates(c *check.C) {
-       lines := s.NewLines("doc/TODO",
+       lines := T.NewLines("doc/TODO",
                "",
                "Suggested package updates",
                "==============",
@@ -102,9 +102,9 @@ func (s *Suite) Test_GlobalData_loadDocC
 func (s *Suite) Test_GlobalData_deprecated(c *check.C) {
        s.Init(c)
        G.globalData.loadDeprecatedVars()
+       mkline := T.NewMkLine("Makefile", 5, "USE_PERL5=\tyes")
 
-       line := NewLine("Makefile", 5, "USE_PERL5=\tyes", nil)
-       MkLineChecker{NewMkLine(line)}.checkVarassign()
+       MkLineChecker{mkline}.checkVarassign()
 
        s.CheckOutputLines(
                "WARN: Makefile:5: Definition of USE_PERL5 is deprecated. Use USE_TOOLS+=perl or USE_TOOLS+=perl:run instead.")
Index: pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.11 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.12
--- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.11      Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser.go   Sat Jan 13 23:56:14 2018
@@ -65,9 +65,10 @@ func (p *MkParser) VarUse() *MkVarUse {
                                if usingRoundParen && p.EmitWarnings {
                                        parenVaruse := repl.Since(mark)
                                        bracesVaruse := "${" + parenVaruse[2:len(parenVaruse)-1] + "}"
-                                       if !p.Line.AutofixReplace(parenVaruse, bracesVaruse) {
-                                               p.Line.Warnf("Please use curly braces {} instead of round parentheses () for %s.", varname)
-                                       }
+                                       fix := p.Line.Autofix()
+                                       fix.Warnf("Please use curly braces {} instead of round parentheses () for %s.", varname)
+                                       fix.Replace(parenVaruse, bracesVaruse)
+                                       fix.Apply()
                                }
                                return &MkVarUse{varname, modifiers}
                        }
Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.11 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.11  Sun Jan 29 14:27:48 2017
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sat Jan 13 23:56:14 2018
@@ -36,7 +36,7 @@ func (s *Suite) Test_Pkglint_Main_no_arg
 func (s *Suite) Test_Pkglint_coverage(c *check.C) {
        cmdline := os.Getenv("PKGLINT_TESTCMDLINE")
        if cmdline != "" {
-               G.logOut, G.logErr, trace.Out = os.Stdout, os.Stderr, os.Stdout
+               G.logOut, G.logErr, trace.Out = NewSeparatorWriter(os.Stdout), NewSeparatorWriter(os.Stderr), os.Stdout
                new(Pkglint).Main(append([]string{"pkglint"}, splitOnSpace(cmdline)...)...)
        }
 }
@@ -82,7 +82,7 @@ func (s *Suite) Test_Pkglint_CheckDirent
 }
 
 func (s *Suite) Test_resolveVariableRefs__circular_reference(c *check.C) {
-       mkline := NewMkLine(NewLine("fname", 1, "GCC_VERSION=${GCC_VERSION}", nil))
+       mkline := T.NewMkLine("fname", 1, "GCC_VERSION=${GCC_VERSION}")
        G.Pkg = NewPackage(".")
        G.Pkg.vardef["GCC_VERSION"] = mkline
 
@@ -92,9 +92,9 @@ func (s *Suite) Test_resolveVariableRefs
 }
 
 func (s *Suite) Test_resolveVariableRefs__multilevel(c *check.C) {
-       mkline1 := NewMkLine(NewLine("fname", 10, "_=${SECOND}", nil))
-       mkline2 := NewMkLine(NewLine("fname", 11, "_=${THIRD}", nil))
-       mkline3 := NewMkLine(NewLine("fname", 12, "_=got it", nil))
+       mkline1 := T.NewMkLine("fname", 10, "_=${SECOND}")
+       mkline2 := T.NewMkLine("fname", 11, "_=${THIRD}")
+       mkline3 := T.NewMkLine("fname", 12, "_=got it")
        G.Pkg = NewPackage(".")
        defineVar(mkline1, "FIRST")
        defineVar(mkline2, "SECOND")
@@ -106,7 +106,7 @@ func (s *Suite) Test_resolveVariableRefs
 }
 
 func (s *Suite) Test_resolveVariableRefs__special_chars(c *check.C) {
-       mkline := NewMkLine(NewLine("fname", 10, "_=x11", nil))
+       mkline := T.NewMkLine("fname", 10, "_=x11")
        G.Pkg = NewPackage("category/pkg")
        G.Pkg.vardef["GST_PLUGINS0.10_TYPE"] = mkline
 
@@ -117,7 +117,7 @@ func (s *Suite) Test_resolveVariableRefs
 
 func (s *Suite) Test_ChecklinesDescr(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("DESCR",
+       lines := T.NewLines("DESCR",
                strings.Repeat("X", 90),
                "", "", "", "", "", "", "", "", "10",
                "Try ${PREFIX}",
@@ -134,7 +134,7 @@ func (s *Suite) Test_ChecklinesDescr(c *
 
 func (s *Suite) Test_ChecklinesMessage__short(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("MESSAGE",
+       lines := T.NewLines("MESSAGE",
                "one line")
 
        ChecklinesMessage(lines)
@@ -145,7 +145,7 @@ func (s *Suite) Test_ChecklinesMessage__
 
 func (s *Suite) Test_ChecklinesMessage__malformed(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("MESSAGE",
+       lines := T.NewLines("MESSAGE",
                "1",
                "2",
                "3",

Index: pkgsrc/pkgtools/pkglint/files/globalvars.go
diff -u pkgsrc/pkgtools/pkglint/files/globalvars.go:1.8 pkgsrc/pkgtools/pkglint/files/globalvars.go:1.9
--- pkgsrc/pkgtools/pkglint/files/globalvars.go:1.8     Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/globalvars.go Sat Jan 13 23:56:14 2018
@@ -1,7 +1,6 @@
 package main
 
 import (
-       "io"
        "netbsd.org/pkglint/histogram"
 )
 
@@ -30,8 +29,8 @@ type GlobalVars struct {
        explanationsAvailable bool
        explanationsGiven     map[string]bool
        autofixAvailable      bool
-       logOut                io.Writer
-       logErr                io.Writer
+       logOut                *SeparatorWriter
+       logErr                *SeparatorWriter
 
        loghisto *histogram.Histogram
 }
@@ -76,6 +75,8 @@ type CmdOpts struct {
        PrintSource,
        PrintVersion bool
 
+       LogOnly []string
+
        args []string
 }
 
Index: pkgsrc/pkgtools/pkglint/files/licenses_test.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.8 pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.8  Sun Jan 29 14:27:48 2017
+++ pkgsrc/pkgtools/pkglint/files/licenses_test.go      Sat Jan 13 23:56:14 2018
@@ -7,7 +7,7 @@ import (
 func (s *Suite) Test_checklineLicense(c *check.C) {
        s.Init(c)
        s.CreateTmpFile("licenses/gnu-gpl-v2", "Most software \u2026")
-       mkline := NewMkLine(NewLine("Makefile", 7, "LICENSE=dummy", nil))
+       mkline := T.NewMkLine("Makefile", 7, "LICENSE=dummy")
        G.globalData.Pkgsrcdir = s.tmpdir
        G.CurrentDir = s.tmpdir
 
Index: pkgsrc/pkgtools/pkglint/files/logging.go
diff -u pkgsrc/pkgtools/pkglint/files/logging.go:1.8 pkgsrc/pkgtools/pkglint/files/logging.go:1.9
--- pkgsrc/pkgtools/pkglint/files/logging.go:1.8        Tue Jan 17 22:37:27 2017
+++ pkgsrc/pkgtools/pkglint/files/logging.go    Sat Jan 13 23:56:14 2018
@@ -22,17 +22,34 @@ var (
 
 var dummyLine = NewLine("", 0, "", nil)
 
-func shallBeLogged(fname, lineno, msg string) bool {
+func shallBeLogged(msg string) bool {
+       if len(G.opts.LogOnly) > 0 {
+               found := false
+               for _, substr := range G.opts.LogOnly {
+                       if contains(msg, substr) {
+                               found = true
+                               break
+                       }
+               }
+               if !found {
+                       return false
+               }
+       }
+
+       return true
+}
+
+func loggedAlready(fname, lineno, msg string) bool {
        uniq := path.Clean(fname) + ":" + lineno + ":" + msg
        if G.logged[uniq] {
-               return false
+               return true
        }
 
        if G.logged == nil {
                G.logged = make(map[string]bool)
        }
        G.logged[uniq] = true
-       return true
+       return false
 }
 
 func logs(level *LogLevel, fname, lineno, format, msg string) bool {
@@ -40,7 +57,7 @@ func logs(level *LogLevel, fname, lineno
                fname = cleanpath(fname)
        }
 
-       if !G.opts.LogVerbose && !shallBeLogged(fname, lineno, msg) {
+       if !G.opts.LogVerbose && loggedAlready(fname, lineno, msg) {
                return false
        }
 
@@ -60,7 +77,7 @@ func logs(level *LogLevel, fname, lineno
                text += sep + level.GccName + ":"
                sep = " "
        }
-       if G.opts.Profiling {
+       if G.opts.Profiling && format != "" {
                G.loghisto.Add(format, 1)
        }
        text += sep + msg + "\n"
@@ -70,7 +87,7 @@ func logs(level *LogLevel, fname, lineno
                out = G.logErr
        }
 
-       io.WriteString(out, text)
+       out.Write(text)
 
        switch level {
        case llFatal:
@@ -94,11 +111,11 @@ func Explain(explanation ...string) {
                }
                G.explanationsGiven[complete] = true
 
-               io.WriteString(G.logOut, "\n")
+               G.logOut.WriteLine("")
                for _, explanationLine := range explanation {
-                       io.WriteString(G.logOut, "\t"+explanationLine+"\n")
+                       G.logOut.WriteLine("\t" + explanationLine)
                }
-               io.WriteString(G.logOut, "\n")
+               G.logOut.WriteLine("")
        }
 
        if G.Testing {
@@ -118,3 +135,39 @@ func Explain(explanation ...string) {
 }
 
 type pkglintFatal struct{}
+
+// SeparatorWriter writes output, occasionally separated by an
+// empty line. This is used for layouting the diagnostics in
+// --source mode combined with --show-autofix, where each
+// log message consists of multiple lines.
+type SeparatorWriter struct {
+       out            io.Writer
+       needSeparator  bool
+       wroteSomething bool
+}
+
+func NewSeparatorWriter(out io.Writer) *SeparatorWriter {
+       return &SeparatorWriter{out, false, false}
+}
+
+func (wr *SeparatorWriter) WriteLine(text string) {
+       wr.Write(text)
+       io.WriteString(wr.out, "\n")
+}
+
+func (wr *SeparatorWriter) Write(text string) {
+       if wr.needSeparator && wr.wroteSomething {
+               io.WriteString(wr.out, "\n")
+               wr.needSeparator = false
+       }
+       io.WriteString(wr.out, text)
+       wr.wroteSomething = true
+}
+
+func (wr *SeparatorWriter) Printf(format string, args ...interface{}) {
+       wr.Write(fmt.Sprintf(format, args...))
+}
+
+func (wr *SeparatorWriter) Separate() {
+       wr.needSeparator = true
+}

Index: pkgsrc/pkgtools/pkglint/files/line.go
diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.18 pkgsrc/pkgtools/pkglint/files/line.go:1.19
--- pkgsrc/pkgtools/pkglint/files/line.go:1.18  Mon Jan  1 21:55:36 2018
+++ pkgsrc/pkgtools/pkglint/files/line.go       Sat Jan 13 23:56:14 2018
@@ -15,11 +15,8 @@ package main
 
 import (
        "fmt"
-       "io"
-       "netbsd.org/pkglint/regex"
        "path"
        "strconv"
-       "strings"
 )
 
 type Line = *LineImpl
@@ -35,15 +32,12 @@ func (rline *RawLine) String() string {
 }
 
 type LineImpl struct {
-       Filename       string
-       firstLine      int32 // Zero means not applicable, -1 means EOF
-       lastLine       int32 // Usually the same as firstLine, may differ in Makefiles
-       Text           string
-       raw            []*RawLine
-       Changed        bool
-       before         []string
-       after          []string
-       autofixMessage string
+       Filename  string
+       firstLine int32 // Zero means not applicable, -1 means EOF
+       lastLine  int32 // Usually the same as firstLine, may differ in Makefiles
+       Text      string
+       raw       []*RawLine
+       autofix   *Autofix
 }
 
 func NewLine(fname string, lineno int, text string, rawLines []*RawLine) Line {
@@ -52,7 +46,7 @@ func NewLine(fname string, lineno int, t
 
 // NewLineMulti is for logical Makefile lines that end with backslash.
 func NewLineMulti(fname string, firstLine, lastLine int, text string, rawLines []*RawLine) Line {
-       return &LineImpl{fname, int32(firstLine), int32(lastLine), text, rawLines, false, nil, nil, ""}
+       return &LineImpl{fname, int32(firstLine), int32(lastLine), text, rawLines, nil}
 }
 
 // NewLineEOF creates a dummy line for logging, with the "line number" EOF.
@@ -65,17 +59,6 @@ func NewLineWhole(fname string) Line {
        return NewLine(fname, 0, "", nil)
 }
 
-// modifiedLines returns the text after the fixes, including line breaks and newly inserted lines
-func (line *LineImpl) modifiedLines() []string {
-       var result []string
-       result = append(result, line.before...)
-       for _, raw := range line.raw {
-               result = append(result, raw.textnl)
-       }
-       result = append(result, line.after...)
-       return result
-}
-
 func (line *LineImpl) Linenos() string {
        switch {
        case line.firstLine == -1:
@@ -100,132 +83,91 @@ func (line *LineImpl) IsMultiline() bool
        return line.firstLine > 0 && line.firstLine != line.lastLine
 }
 
-func (line *LineImpl) printSource(out io.Writer) {
-       if G.opts.PrintSource {
-               io.WriteString(out, "\n")
-               for _, before := range line.before {
-                       io.WriteString(out, "+ "+before)
-               }
-               for _, rawLine := range line.raw {
+func (line *LineImpl) printSource(out *SeparatorWriter) {
+       if !G.opts.PrintSource {
+               return
+       }
+
+       printDiff := func(rawLines []*RawLine) {
+               for _, rawLine := range rawLines {
                        if rawLine.textnl != rawLine.orignl {
                                if rawLine.orignl != "" {
-                                       io.WriteString(out, "- "+rawLine.orignl)
+                                       out.Write("- " + rawLine.orignl)
                                }
                                if rawLine.textnl != "" {
-                                       io.WriteString(out, "+ "+rawLine.textnl)
+                                       out.Write("+ " + rawLine.textnl)
                                }
                        } else {
-                               io.WriteString(out, "> "+rawLine.orignl)
+                               out.Write("> " + rawLine.orignl)
                        }
                }
-               for _, after := range line.after {
-                       io.WriteString(out, "+ "+after)
+       }
+
+       if line.autofix != nil {
+               for _, before := range line.autofix.linesBefore {
+                       out.Write("+ " + before)
+               }
+               printDiff(line.autofix.lines)
+               for _, after := range line.autofix.linesAfter {
+                       out.Write("+ " + after)
                }
+       } else {
+               printDiff(line.raw)
+       }
+}
+
+func (line *LineImpl) log(level *LogLevel, format string, args []interface{}) {
+       if G.opts.PrintAutofix || G.opts.Autofix {
+               // In these two cases, the only interesting diagnostics are
+               // those that can be fixed automatically.
+               // These are logged by Autofix.Apply.
+               return
+       }
+       if !shallBeLogged(format) {
+               return
+       }
+
+       out := G.logOut
+       if level == llError {
+               out = G.logErr
+       }
+
+       logs(level, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
+       if !G.opts.PrintAutofix && G.opts.PrintSource {
+               line.printSource(out)
+               out.Separate()
        }
 }
 
 func (line *LineImpl) Fatalf(format string, args ...interface{}) {
-       line.printSource(G.logErr)
-       logs(llFatal, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
+       line.log(llFatal, format, args)
 }
 
 func (line *LineImpl) Errorf(format string, args ...interface{}) {
-       line.printSource(G.logOut)
-       logs(llError, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
-       line.logAutofix()
+       line.log(llError, format, args)
 }
 
 func (line *LineImpl) Warnf(format string, args ...interface{}) {
-       line.printSource(G.logOut)
-       logs(llWarn, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
-       line.logAutofix()
+       line.log(llWarn, format, args)
 }
 
 func (line *LineImpl) Notef(format string, args ...interface{}) {
-       line.printSource(G.logOut)
-       logs(llNote, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
-       line.logAutofix()
+       line.log(llNote, format, args)
 }
 
 func (line *LineImpl) String() string {
        return line.Filename + ":" + line.Linenos() + ": " + line.Text
 }
 
-func (line *LineImpl) logAutofix() {
-       if line.autofixMessage != "" {
-               logs(llAutofix, line.Filename, line.Linenos(), "%s", line.autofixMessage)
-               line.autofixMessage = ""
-       }
-}
-
-func (line *LineImpl) AutofixInsertBefore(text string) bool {
-       if G.opts.PrintAutofix || G.opts.Autofix {
-               line.before = append(line.before, text+"\n")
-       }
-       return line.RememberAutofix("Inserting a line %q before this line.", text)
-}
-
-func (line *LineImpl) AutofixInsertAfter(text string) bool {
-       if G.opts.PrintAutofix || G.opts.Autofix {
-               line.after = append(line.after, text+"\n")
+// Autofix returns a builder object for automatically fixing the line.
+// After building the object, call Apply to actually apply the changes to the line.
+//
+// The changed lines are not written back to disk immediately.
+// This is done by SaveAutofixChanges.
+//
+func (line *LineImpl) Autofix() *Autofix {
+       if line.autofix == nil {
+               line.autofix = NewAutofix(line)
        }
-       return line.RememberAutofix("Inserting a line %q after this line.", text)
-}
-
-func (line *LineImpl) AutofixDelete() bool {
-       if G.opts.PrintAutofix || G.opts.Autofix {
-               for _, rawLine := range line.raw {
-                       rawLine.textnl = ""
-               }
-       }
-       return line.RememberAutofix("Deleting this line.")
-}
-
-func (line *LineImpl) AutofixReplace(from, to string) bool {
-       for _, rawLine := range line.raw {
-               if rawLine.Lineno != 0 {
-                       if replaced := strings.Replace(rawLine.textnl, from, to, 1); replaced != rawLine.textnl {
-                               if G.opts.PrintAutofix || G.opts.Autofix {
-                                       rawLine.textnl = replaced
-                               }
-                               return line.RememberAutofix("Replacing %q with %q.", from, to)
-                       }
-               }
-       }
-       return false
-}
-
-func (line *LineImpl) AutofixReplaceRegexp(from regex.Pattern, to string) bool {
-       for _, rawLine := range line.raw {
-               if rawLine.Lineno != 0 {
-                       if replaced := regex.Compile(from).ReplaceAllString(rawLine.textnl, to); replaced != rawLine.textnl {
-                               if G.opts.PrintAutofix || G.opts.Autofix {
-                                       rawLine.textnl = replaced
-                               }
-                               return line.RememberAutofix("Replacing regular expression %q with %q.", from, to)
-                       }
-               }
-       }
-       return false
-}
-
-func (line *LineImpl) RememberAutofix(format string, args ...interface{}) (hasBeenFixed bool) {
-       if line.firstLine < 1 {
-               return false
-       }
-       line.Changed = true
-       if G.opts.Autofix {
-               logs(llAutofix, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
-               return true
-       }
-       if G.opts.PrintAutofix {
-               line.autofixMessage = fmt.Sprintf(format, args...)
-       }
-       return false
-}
-
-func (line *LineImpl) AutofixMark(reason string) {
-       line.RememberAutofix(reason)
-       line.logAutofix()
-       line.Changed = true
+       return line.autofix
 }
Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.18 pkgsrc/pkgtools/pkglint/files/plist.go:1.19
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.18 Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Sat Jan 13 23:56:14 2018
@@ -128,9 +128,10 @@ func (ck *PlistChecker) checkline(pline 
        } else if hasPrefix(text, "$") {
                ck.checkpath(pline)
        } else if text == "" {
-               if !pline.line.AutofixDelete() {
-                       pline.line.Warnf("PLISTs should not contain empty lines.")
-               }
+               fix := pline.line.Autofix()
+               fix.Warnf("PLISTs should not contain empty lines.")
+               fix.Delete()
+               fix.Apply()
        } else {
                pline.line.Warnf("Unknown line type.")
        }
@@ -147,14 +148,15 @@ func (ck *PlistChecker) checkpath(pline 
                pline.warnImakeMannewsuffix()
        }
        if hasPrefix(text, "${PKGMANDIR}/") {
-               if line.AutofixReplace("${PKGMANDIR}/", "man/") {
-                       pline.text = strings.Replace(pline.text, "${PKGMANDIR}/", "man/", 1)
-               } else {
-                       line.Notef("PLIST files should mention \"man/\" instead of \"${PKGMANDIR}\".")
-                       Explain(
-                               "The pkgsrc infrastructure takes care of replacing the correct value",
-                               "when generating the actual PLIST for the package.")
-               }
+               fix := pline.line.Autofix()
+               fix.Notef("PLIST files should mention \"man/\" instead of \"${PKGMANDIR}\".")
+               fix.Explain(
+                       "The pkgsrc infrastructure takes care of replacing the correct value",
+                       "when generating the actual PLIST for the package.")
+               fix.Replace("${PKGMANDIR}/", "man/")
+               fix.Apply()
+
+               pline.text = strings.Replace(pline.text, "${PKGMANDIR}/", "man/", 1)
        }
 
        topdir := ""
@@ -210,9 +212,10 @@ func (ck *PlistChecker) checkSorted(plin
                                        "To fix this, run \"pkglint -F PLIST\".")
                        }
                        if prev := ck.allFiles[text]; prev != nil && prev != pline {
-                               if !pline.line.AutofixDelete() {
-                                       pline.line.Errorf("Duplicate filename %q, already appeared in %s.", text, prev.line.ReferenceFrom(pline.line))
-                               }
+                               fix := pline.line.Autofix()
+                               fix.Errorf("Duplicate filename %q, already appeared in %s.", text, prev.line.ReferenceFrom(pline.line))
+                               fix.Delete()
+                               fix.Apply()
                        }
                }
                ck.lastFname = text
@@ -309,13 +312,16 @@ func (ck *PlistChecker) checkpathMan(pli
                }
        }
 
-       if gz != "" && !line.AutofixReplaceRegexp(`\.gz$`, "") {
-               line.Notef("The .gz extension is unnecessary for manual pages.")
-               Explain(
+       if gz != "" {
+               fix := line.Autofix()
+               fix.Notef("The .gz extension is unnecessary for manual pages.")
+               fix.Explain(
                        "Whether the manual pages are installed in compressed form or not is",
                        "configured by the pkgsrc user.  Compression and decompression takes",
                        "place automatically, no matter if the .gz extension is mentioned in",
                        "the PLIST or not.")
+               fix.ReplaceRegex(`\.gz\n`, "\n")
+               fix.Apply()
        }
 }
 
@@ -516,7 +522,10 @@ func (s *plistLineSorter) Sort() {
                return
        }
 
-       firstLine.AutofixMark("Sorting the whole file.")
+       fix := firstLine.Autofix()
+       fix.Describef("Sorting the whole file.")
+       fix.Apply()
+
        var lines []Line
        for _, pline := range s.header {
                lines = append(lines, pline.line)
@@ -527,6 +536,6 @@ func (s *plistLineSorter) Sort() {
        for _, pline := range s.footer {
                lines = append(lines, pline.line)
        }
-       lines[0].Changed = true // Without this, autofix doesn't know that anything changed
+
        s.autofixed = SaveAutofixChanges(lines)
 }
Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.18 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.18    Wed Jan 10 00:39:52 2018
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sat Jan 13 23:56:14 2018
@@ -1,7 +1,7 @@
 package main
 
 import (
-       check "gopkg.in/check.v1"
+       "gopkg.in/check.v1"
        "netbsd.org/pkglint/textproc"
 )
 
@@ -107,15 +107,20 @@ func (s *Suite) Test_splitIntoShellToken
 func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wall")
-       G.Mk = s.NewMkLines("fname",
-               "# dummy")
-       shline := NewShellLine(G.Mk.mklines[0])
 
-       shline.CheckShellCommandLine("@# Comment")
+       checkShellCommandLine := func(shellCommand string) {
+               G.Mk = T.NewMkLines("fname",
+                       "\t"+shellCommand)
+               shline := NewShellLine(G.Mk.mklines[0])
+
+               shline.CheckShellCommandLine(shline.mkline.Shellcmd())
+       }
+
+       checkShellCommandLine("@# Comment")
 
        s.CheckOutputEmpty()
 
-       shline.CheckShellCommandLine("uname=`uname`; echo $$uname; echo")
+       checkShellCommandLine("uname=`uname`; echo $$uname; echo")
 
        s.CheckOutputLines(
                "WARN: fname:1: Unknown shell command \"uname\".",
@@ -123,60 +128,58 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: fname:1: Unknown shell command \"echo\".")
 
        s.RegisterTool(&Tool{Name: "echo", Predefined: true})
-       G.Mk = s.NewMkLines("fname",
-               "# dummy")
        G.globalData.InitVartypes()
 
-       shline.CheckShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain
+       checkShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain
 
        s.CheckOutputLines(
                "WARN: fname:1: PKGNAME may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.",
                "NOTE: fname:1: The :Q operator isn't necessary for ${PKGNAME} here.")
 
-       shline.CheckShellCommandLine("echo \"${CFLAGS:Q}\"") // vucQuotDquot
+       checkShellCommandLine("echo \"${CFLAGS:Q}\"") // vucQuotDquot
 
        s.CheckOutputLines(
                "WARN: fname:1: Please don't use the :Q operator in double quotes.",
                "WARN: fname:1: CFLAGS may not be used in this file; it would be ok in Makefile, Makefile.common, options.mk, *.mk.",
                "WARN: fname:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} and make sure the variable appears outside of any quoting characters.")
 
-       shline.CheckShellCommandLine("echo '${COMMENT:Q}'") // vucQuotSquot
+       checkShellCommandLine("echo '${COMMENT:Q}'") // vucQuotSquot
 
        s.CheckOutputLines(
                "WARN: fname:1: COMMENT may not be used in any file; it is a write-only variable.",
                "WARN: fname:1: Please move ${COMMENT:Q} outside of any quoting characters.")
 
-       shline.CheckShellCommandLine("echo target=$@ exitcode=$$? '$$' \"\\$$\"")
+       checkShellCommandLine("echo target=$@ exitcode=$$? '$$' \"\\$$\"")
 
        s.CheckOutputLines(
                "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".",
                "WARN: fname:1: The $? shell variable is often not available in \"set -e\" mode.")
 
-       shline.CheckShellCommandLine("echo $$@")
+       checkShellCommandLine("echo $$@")
 
        s.CheckOutputLines(
                "WARN: fname:1: The $@ shell variable should only be used in double quotes.")
 
-       shline.CheckShellCommandLine("echo \"$$\"") // As seen by make(1); the shell sees: echo "$"
+       checkShellCommandLine("echo \"$$\"") // As seen by make(1); the shell sees: echo "$"
 
        s.CheckOutputLines(
                "WARN: fname:1: Pkglint parse error in ShTokenizer.ShAtom at \"$$\\\"\" (quoting=d)",
                "WARN: fname:1: Pkglint ShellLine.CheckShellCommand: parse error at [\"]")
 
-       shline.CheckShellCommandLine("echo \"\\n\"")
+       checkShellCommandLine("echo \"\\n\"")
 
        s.CheckOutputEmpty()
 
-       shline.CheckShellCommandLine("${RUN} for f in *.c; do echo $${f%.c}; done")
+       checkShellCommandLine("${RUN} for f in *.c; do echo $${f%.c}; done")
 
        s.CheckOutputEmpty()
 
-       shline.CheckShellCommandLine("${RUN} echo $${variable+set}")
+       checkShellCommandLine("${RUN} echo $${variable+set}")
 
        s.CheckOutputEmpty()
 
        // Based on mail/thunderbird/Makefile, rev. 1.159
-       shline.CheckShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"")
+       checkShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"")
 
        s.CheckOutputLines(
                "WARN: fname:1: The exitcode of \"unzip\" at the left of the | operator is ignored.",
@@ -184,7 +187,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: fname:1: Unknown shell command \"awk\".")
 
        // From mail/thunderbird/Makefile, rev. 1.159
-       shline.CheckShellCommandLine("" +
+       checkShellCommandLine("" +
                "${RUN} for e in ${XPI_FILES}; do " +
                "  subdir=\"`${UNZIP_CMD} -c \"$$e\" install.rdf | awk '/^    <em:id>/ {sub(\".*<em:id>\",\"\");sub(\"</em:id>.*\",\"\");print;exit;}'`\" && " +
                "  ${MKDIR} \"${WRKDIR}/extensions/$$subdir\" && " +
@@ -202,7 +205,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?")
 
        // From x11/wxGTK28/Makefile
-       shline.CheckShellCommandLine("" +
+       checkShellCommandLine("" +
                "set -e; cd ${WRKSRC}/locale; " +
                "for lang in *.po; do " +
                "  [ \"$${lang}\" = \"wxstd.po\" ] && continue; " +
@@ -214,7 +217,7 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: fname:1: Unknown shell command \"[\".",
                "WARN: fname:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".")
 
-       shline.CheckShellCommandLine("@cp from to")
+       checkShellCommandLine("@cp from to")
 
        s.CheckOutputLines(
                "WARN: fname:1: The shell command \"cp\" should not be hidden.",
@@ -224,14 +227,14 @@ func (s *Suite) Test_ShellLine_CheckShel
        G.Pkg.PlistDirs["share/pkgbase"] = true
 
        // A directory that is found in the PLIST.
-       shline.CheckShellCommandLine("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase")
+       checkShellCommandLine("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase")
 
        s.CheckOutputLines(
                "NOTE: fname:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" instead of \"${INSTALL_DATA_DIR}\".",
                "WARN: fname:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
 
        // A directory that is not found in the PLIST.
-       shline.CheckShellCommandLine("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other")
+       checkShellCommandLine("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other")
 
        s.CheckOutputLines(
                "NOTE: fname:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
@@ -239,7 +242,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        G.Pkg = nil
 
        // See PR 46570, item "1. It does not"
-       shline.CheckShellCommandLine("for x in 1 2 3; do echo \"$$x\" || exit 1; done")
+       checkShellCommandLine("for x in 1 2 3; do echo \"$$x\" || exit 1; done")
 
        s.CheckOutputEmpty() // No warning about missing error checking.
 }
@@ -249,7 +252,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
        s.RegisterTool(&Tool{Name: "echo", Predefined: true})
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
        shline := NewShellLine(G.Mk.mklines[0])
 
@@ -264,7 +267,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.UseCommandLine("-Wall", "--show-autofix")
        G.globalData.InitVartypes()
        s.RegisterTool(&Tool{Name: "echo", Predefined: true})
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
        shline := NewShellLine(G.Mk.mklines[0])
 
@@ -285,7 +288,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.RegisterTool(&Tool{Name: "printf", Predefined: true})
        s.RegisterTool(&Tool{Name: "sed", Predefined: true})
        s.RegisterTool(&Tool{Name: "right-side", Predefined: true})
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                "\t echo | right-side",
                "\t sed s,s,s, | right-side",
                "\t printf | sed s,s,s, | right-side ",
@@ -313,7 +316,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.UseCommandLine("-Wall", "--autofix")
        G.globalData.InitVartypes()
        s.RegisterTool(&Tool{Name: "echo", Predefined: true})
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                "\techo ${PKGNAME:Q}")
        shline := NewShellLine(G.Mk.mklines[0])
 
@@ -327,7 +330,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("fname",
+       G.Mk = T.NewMkLines("fname",
                "# dummy")
        shline := NewShellLine(G.Mk.mklines[0])
 
@@ -353,7 +356,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShelltext__dollar_without_variable(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("fname",
+       G.Mk = T.NewMkLines("fname",
                "# dummy")
        shline := NewShellLine(G.Mk.mklines[0])
        s.RegisterTool(&Tool{Name: "pax", Varname: "PAX"})
@@ -368,50 +371,55 @@ func (s *Suite) Test_ShellLine_CheckWord
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       shline := NewShellLine(NewMkLine(NewLine("fname", 1, "# dummy", nil)))
 
-       shline.CheckWord("${${list}}", false)
+       checkWord := func(shellWord string, checkQuoting bool) {
+               shline := T.NewShellLine("dummy.mk", 1, "\t echo "+shellWord)
+
+               shline.CheckWord(shellWord, checkQuoting)
+       }
+
+       checkWord("${${list}}", false)
+
+       checkWord("${${list}}", false)
 
        s.CheckOutputEmpty() // No warning for variables that are completely indirect.
 
-       shline.CheckWord("${SED_FILE.${id}}", false)
+       checkWord("${SED_FILE.${id}}", false)
 
        s.CheckOutputEmpty() // No warning for variables that are partly indirect.
 
-       shline.CheckWord("\"$@\"", false)
+       checkWord("\"$@\"", false)
 
        s.CheckOutputLines(
-               "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".")
+               "WARN: dummy.mk:1: Please use \"${.TARGET}\" instead of \"$@\".")
 
-       shline.CheckWord("${COMMENT:Q}", true)
+       checkWord("${COMMENT:Q}", true)
 
        s.CheckOutputLines(
-               "WARN: fname:1: COMMENT may not be used in any file; it is a write-only variable.")
+               "WARN: dummy.mk:1: COMMENT may not be used in any file; it is a write-only variable.")
 
-       shline.CheckWord("\"${DISTINFO_FILE:Q}\"", true)
+       checkWord("\"${DISTINFO_FILE:Q}\"", true)
 
        s.CheckOutputLines(
-               "WARN: fname:1: DISTINFO_FILE may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.",
-               "NOTE: fname:1: The :Q operator isn't necessary for ${DISTINFO_FILE} here.")
+               "NOTE: dummy.mk:1: The :Q operator isn't necessary for ${DISTINFO_FILE} here.")
 
-       shline.CheckWord("embed${DISTINFO_FILE:Q}ded", true)
+       checkWord("embed${DISTINFO_FILE:Q}ded", true)
 
        s.CheckOutputLines(
-               "WARN: fname:1: DISTINFO_FILE may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.",
-               "NOTE: fname:1: The :Q operator isn't necessary for ${DISTINFO_FILE} here.")
+               "NOTE: dummy.mk:1: The :Q operator isn't necessary for ${DISTINFO_FILE} here.")
 
-       shline.CheckWord("s,\\.,,", true)
+       checkWord("s,\\.,,", true)
 
        s.CheckOutputEmpty()
 
-       shline.CheckWord("\"s,\\.,,\"", true)
+       checkWord("\"s,\\.,,\"", true)
 
        s.CheckOutputEmpty()
 }
 
 func (s *Suite) Test_ShellLine_CheckWord__dollar_without_variable(c *check.C) {
        s.Init(c)
-       shline := NewShellLine(NewMkLine(NewLine("fname", 1, "# dummy", nil)))
+       shline := T.NewShellLine("fname", 1, "# dummy")
 
        shline.CheckWord("/.*~$$//g", false) // Typical argument to pax(1).
 
@@ -422,9 +430,9 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.Init(c)
        s.UseCommandLine("-Wall")
        s.RegisterTool(&Tool{Name: "echo", Varname: "ECHO", MustUseVarForm: true, Predefined: true})
-       G.Mk = s.NewMkLines("fname",
+       G.Mk = T.NewMkLines("fname",
                "# dummy")
-       mkline := NewMkLine(NewLine("fname", 3, "# dummy", nil))
+       mkline := T.NewMkLine("fname", 3, "# dummy")
 
        MkLineChecker{mkline}.checkText("echo \"hello, world\"")
 
@@ -440,7 +448,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        s.Init(c)
        text := "\tfor f in *.pl; do ${SED} s,@PREFIX@,${PREFIX}, < $f > $f.tmp && ${MV} $f.tmp $f; done"
 
-       shline := NewShellLine(NewMkLine(NewLine("Makefile", 3, text, nil)))
+       shline := T.NewShellLine("Makefile", 3, text)
        shline.CheckShellCommandLine(text)
 
        s.CheckOutputLines(
@@ -463,11 +471,11 @@ func (s *Suite) Test_ShellLine_CheckShel
 
 func (s *Suite) Test_ShellLine_checkCommandUse(c *check.C) {
        s.Init(c)
-       G.Mk = s.NewMkLines("fname",
+       G.Mk = T.NewMkLines("fname",
                "# dummy")
        G.Mk.target = "do-install"
 
-       shline := NewShellLine(NewMkLine(NewLine("fname", 1, "\tdummy", nil)))
+       shline := T.NewShellLine("fname", 1, "\tdummy")
 
        shline.checkCommandUse("sed")
 
@@ -501,7 +509,7 @@ func (s *Suite) Test_splitIntoMkWords(c 
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__sed_and_mv(c *check.C) {
        s.Init(c)
-       shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' fname > fname.tmp; ${MV} fname.tmp fname", nil)))
+       shline := T.NewShellLine("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' fname > fname.tmp; ${MV} fname.tmp fname")
 
        shline.CheckShellCommandLine(shline.mkline.Shellcmd())
 
@@ -511,7 +519,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__subshell(c *check.C) {
        s.Init(c)
-       shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} uname=$$(uname)", nil)))
+       shline := T.NewShellLine("Makefile", 85, "\t${RUN} uname=$$(uname)")
 
        shline.CheckShellCommandLine(shline.mkline.Shellcmd())
 
@@ -521,7 +529,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__install_dir(c *check.C) {
        s.Init(c)
-       shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil)))
+       shline := T.NewShellLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
 
        shline.CheckShellCommandLine(shline.mkline.Shellcmd())
 
@@ -546,7 +554,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__install_option_d(c *check.C) {
        s.Init(c)
-       shline := NewShellLine(NewMkLine(NewLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2", nil)))
+       shline := T.NewShellLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
 
        shline.CheckShellCommandLine(shline.mkline.Shellcmd())
 
@@ -571,13 +579,14 @@ func (s *Suite) Test_ShellLine__shell_co
 }
 
 func (s *Suite) Test_ShellLine_unescapeBackticks(c *check.C) {
-       shline := NewShellLine(NewMkLine(dummyLine))
+       shline := T.NewShellLine("dummy.mk", 13, "# dummy")
        // foobar="`echo \"foo   bar\"`"
        text := "foobar=\"`echo \\\"foo   bar\\\"`\""
        repl := textproc.NewPrefixReplacer(text)
        repl.AdvanceStr("foobar=\"`")
 
        backtCommand, newQuoting := shline.unescapeBackticks(text, repl, shqDquotBackt)
+
        c.Check(backtCommand, equals, "echo \"foo   bar\"")
        c.Check(newQuoting, equals, shqDquot)
        c.Check(repl.Rest(), equals, "\"")

Index: pkgsrc/pkgtools/pkglint/files/linechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/linechecker.go:1.4 pkgsrc/pkgtools/pkglint/files/linechecker.go:1.5
--- pkgsrc/pkgtools/pkglint/files/linechecker.go:1.4    Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/linechecker.go        Sat Jan 13 23:56:14 2018
@@ -49,12 +49,13 @@ func CheckLineValidCharacters(line Line,
 
 func CheckLineTrailingWhitespace(line Line) {
        if strings.HasSuffix(line.Text, " ") || strings.HasSuffix(line.Text, "\t") {
-               if !line.AutofixReplaceRegexp(`\s+\n$`, "\n") {
-                       line.Notef("Trailing white-space.")
-                       Explain(
-                               "When a line ends with some white-space, that space is in most cases",
-                               "irrelevant and can be removed.")
-               }
+               fix := line.Autofix()
+               fix.Notef("Trailing white-space.")
+               fix.Explain(
+                       "When a line ends with some white-space, that space is in most cases",
+                       "irrelevant and can be removed.")
+               fix.ReplaceRegex(`\s+\n$`, "\n")
+               fix.Apply()
        }
 }
 
@@ -67,13 +68,15 @@ func CheckLineRcsid(line Line, prefixRe 
                return true
        }
 
-       if !line.AutofixInsertBefore(suggestedPrefix + "$" + "NetBSD$") {
-               line.Errorf("Expected %q.", suggestedPrefix+"$"+"NetBSD$")
-               Explain(
-                       "Several files in pkgsrc must contain the CVS Id, so that their",
-                       "current version can be traced back later from a binary package.",
-                       "This is to ensure reproducible builds, for example for finding bugs.")
-       }
+       fix := line.Autofix()
+       fix.Errorf("Expected %q.", suggestedPrefix+"$"+"NetBSD$")
+       fix.Explain(
+               "Several files in pkgsrc must contain the CVS Id, so that their",
+               "current version can be traced back later from a binary package.",
+               "This is to ensure reproducible builds, for example for finding bugs.")
+       fix.InsertBefore(suggestedPrefix + "$" + "NetBSD$")
+       fix.Apply()
+
        return false
 }
 
Index: pkgsrc/pkgtools/pkglint/files/linechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/linechecker_test.go:1.4 pkgsrc/pkgtools/pkglint/files/linechecker_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/linechecker_test.go:1.4       Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/linechecker_test.go   Sat Jan 13 23:56:14 2018
@@ -6,7 +6,7 @@ import (
 
 func (s *Suite) Test_LineChecker_CheckAbsolutePathname(c *check.C) {
        s.Init(c)
-       line := NewLine("Makefile", 1, "# dummy", nil)
+       line := T.NewLine("Makefile", 1, "# dummy")
 
        CheckLineAbsolutePathname(line, "bindir=/bin")
        CheckLineAbsolutePathname(line, "bindir=/../lib")
@@ -17,7 +17,7 @@ func (s *Suite) Test_LineChecker_CheckAb
 
 func (s *Suite) Test_LineChecker_CheckTrailingWhitespace(c *check.C) {
        s.Init(c)
-       line := NewLine("Makefile", 32, "The line must go on   ", nil)
+       line := T.NewLine("Makefile", 32, "The line must go on   ")
 
        CheckLineTrailingWhitespace(line)
 
@@ -27,7 +27,7 @@ func (s *Suite) Test_LineChecker_CheckTr
 
 func (s *Suite) Test_LineChecker_CheckRcsid(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("fname",
+       lines := T.NewLines("fname",
                "$"+"NetBSD: dummy $",
                "$"+"NetBSD$",
                "$"+"Id: dummy $",

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.24 pkgsrc/pkgtools/pkglint/files/mkline.go:1.25
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.24        Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sat Jan 13 23:56:14 2018
@@ -67,9 +67,10 @@ func NewMkLine(line Line) (mkline *MkLin
                        case matches(varname, `^[a-z]`) && op == ":=":
                                break
                        default:
-                               if !line.AutofixReplace(varname+spaceAfterVarname+op, varname+op) {
-                                       line.Warnf("Unnecessary space after variable name %q.", varname)
-                               }
+                               fix := line.Autofix()
+                               fix.Warnf("Unnecessary space after variable name %q.", varname)
+                               fix.Replace(varname+spaceAfterVarname+op, varname+op)
+                               fix.Apply()
                        }
                }
 

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.27 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.28
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.27   Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sat Jan 13 23:56:14 2018
@@ -1,13 +1,12 @@
 package main
 
-import (
-       check "gopkg.in/check.v1"
-)
+import "gopkg.in/check.v1"
 
 func (s *Suite) Test_VaralignBlock_Check_autofix(c *check.C) {
        s.Init(c)
-       s.UseCommandLine("-Wspace", "-f")
-       lines := s.NewLines("file.mk",
+       s.UseCommandLine("-Wspace", "--show-autofix")
+
+       lines := T.NewLines("file.mk",
                "VAR=   value",    // Indentation 7, fixed to 8.
                "",                //
                "VAR=    value",   // Indentation 8, fixed to 8.
@@ -28,39 +27,25 @@ func (s *Suite) Test_VaralignBlock_Check
        }
        varalign.Finish()
 
-       c.Check(lines[0].Changed, equals, true)
-       c.Check(lines[0].raw[0].String(), equals, "1:VAR=\tvalue\n")
-       c.Check(lines[2].Changed, equals, true)
-       c.Check(lines[2].raw[0].String(), equals, "3:VAR=\tvalue\n")
-       c.Check(lines[4].Changed, equals, true)
-       c.Check(lines[4].raw[0].String(), equals, "5:VAR=\tvalue\n")
-       c.Check(lines[6].Changed, equals, true)
-       c.Check(lines[6].raw[0].String(), equals, "7:VAR=\tvalue\n")
-       c.Check(lines[8].Changed, equals, true)
-       c.Check(lines[8].raw[0].String(), equals, "9:VAR=\tvalue\n")
-       c.Check(lines[10].Changed, equals, true)
-       c.Check(lines[10].raw[0].String(), equals, "11:VAR=\tvalue\n")
-       c.Check(lines[12].Changed, equals, false)
-       c.Check(lines[12].raw[0].String(), equals, "13:VAR=\tvalue\n")
-       c.Check(s.Output(), equals, ""+
-               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 9.\n"+
-               "AUTOFIX: file.mk:1: Replacing \"VAR=   \" with \"VAR=\\t\".\n"+
-               "NOTE: file.mk:3: Variable values should be aligned with tabs, not spaces.\n"+
-               "AUTOFIX: file.mk:3: Replacing \"VAR=    \" with \"VAR=\\t\".\n"+
-               "NOTE: file.mk:5: This variable value should be aligned with tabs, not spaces, to column 9.\n"+
-               "AUTOFIX: file.mk:5: Replacing \"VAR=     \" with \"VAR=\\t\".\n"+
-               "NOTE: file.mk:7: Variable values should be aligned with tabs, not spaces.\n"+
-               "AUTOFIX: file.mk:7: Replacing \"VAR= \\t\" with \"VAR=\\t\".\n"+
-               "NOTE: file.mk:9: Variable values should be aligned with tabs, not spaces.\n"+
-               "AUTOFIX: file.mk:9: Replacing \"VAR=   \\t\" with \"VAR=\\t\".\n"+
-               "NOTE: file.mk:11: This variable value should be aligned with tabs, not spaces, to column 9.\n"+
-               "AUTOFIX: file.mk:11: Replacing \"VAR=    \\t\" with \"VAR=\\t\".\n")
+       s.CheckOutputLines(
+               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "AUTOFIX: file.mk:1: Replacing \"VAR=   \" with \"VAR=\\t\".",
+               "NOTE: file.mk:3: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:3: Replacing \"VAR=    \" with \"VAR=\\t\".",
+               "NOTE: file.mk:5: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "AUTOFIX: file.mk:5: Replacing \"VAR=     \" with \"VAR=\\t\".",
+               "NOTE: file.mk:7: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:7: Replacing \"VAR= \\t\" with \"VAR=\\t\".",
+               "NOTE: file.mk:9: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:9: Replacing \"VAR=   \\t\" with \"VAR=\\t\".",
+               "NOTE: file.mk:11: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "AUTOFIX: file.mk:11: Replacing \"VAR=    \\t\" with \"VAR=\\t\".")
 }
 
 func (s *Suite) Test_VaralignBlock_Check__reduce_indentation(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wspace")
-       mklines := s.NewMkLines("file.mk",
+       mklines := T.NewMkLines("file.mk",
                "VAR= \tvalue",
                "VAR=    \tvalue",
                "VAR=\t\t\t\tvalue",
@@ -84,7 +69,7 @@ func (s *Suite) Test_VaralignBlock_Check
 func (s *Suite) Test_VaralignBlock_Check_longest_line_no_space(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wspace")
-       mklines := s.NewMkLines("file.mk",
+       mklines := T.NewMkLines("file.mk",
                "SUBST_CLASSES+= aaaaaaaa",
                "SUBST_STAGE.aaaaaaaa= pre-configure",
                "SUBST_FILES.aaaaaaaa= *.pl",
@@ -106,7 +91,7 @@ func (s *Suite) Test_VaralignBlock_Check
 func (s *Suite) Test_VaralignBlock_Check_only_spaces(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wspace")
-       mklines := s.NewMkLines("file.mk",
+       mklines := T.NewMkLines("file.mk",
                "SUBST_CLASSES+= aaaaaaaa",
                "SUBST_STAGE.aaaaaaaa= pre-configure",
                "SUBST_FILES.aaaaaaaa= *.pl",
@@ -128,7 +113,7 @@ func (s *Suite) Test_VaralignBlock_Check
 func (s *Suite) Test_NewMkLine(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wspace")
-       mklines := NewMkLines(s.NewLines("test.mk",
+       mklines := T.NewMkLines("test.mk",
                "VARNAME.param?=value # varassign comment",
                "\tshell command # shell comment",
                "# whole line comment",
@@ -138,7 +123,7 @@ func (s *Suite) Test_NewMkLine(c *check.
                ".    include <subdir.mk> # sysinclude comment",
                "target1 target2: source1 source2",
                "target : source",
-               "VARNAME+=value"))
+               "VARNAME+=value")
        ln := mklines.mklines
 
        c.Check(ln[0].IsVarassign(), equals, true)
@@ -218,7 +203,7 @@ func (s *Suite) Test_NewMkLine__autofix_
 }
 
 func (s *Suite) Test_MkLine_VariableType_varparam(c *check.C) {
-       mkline := NewMkLine(NewLine("fname", 1, "# dummy", nil))
+       mkline := T.NewMkLine("fname", 1, "# dummy")
        G.globalData.InitVartypes()
 
        t1 := mkline.VariableType("FONT_DIRS")
@@ -234,7 +219,7 @@ func (s *Suite) Test_MkLine_VariableType
 
 func (s *Suite) Test_VarUseContext_String(c *check.C) {
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("fname", 1, "# dummy", nil))
+       mkline := T.NewMkLine("fname", 1, "# dummy")
        vartype := mkline.VariableType("PKGNAME")
        vuc := &VarUseContext{vartype, vucTimeUnknown, vucQuotBackt, false}
 
@@ -246,22 +231,22 @@ func (s *Suite) Test_VarUseContext_Strin
 // is interpreted literally.
 func (s *Suite) Test_NewMkLine_numbersign(c *check.C) {
        s.Init(c)
-       mklineVarassignEscaped := NewMkLine(NewLine("fname", 1, "SED_CMD=\t's,\\#,hash,g'", nil))
+       mklineVarassignEscaped := T.NewMkLine("fname", 1, "SED_CMD=\t's,\\#,hash,g'")
 
        c.Check(mklineVarassignEscaped.Varname(), equals, "SED_CMD")
        c.Check(mklineVarassignEscaped.Value(), equals, "'s,#,hash,g'")
 
-       mklineCommandEscaped := NewMkLine(NewLine("fname", 1, "\tsed -e 's,\\#,hash,g'", nil))
+       mklineCommandEscaped := T.NewMkLine("fname", 1, "\tsed -e 's,\\#,hash,g'")
 
        c.Check(mklineCommandEscaped.Shellcmd(), equals, "sed -e 's,\\#,hash,g'")
 
        // From shells/zsh/Makefile.common, rev. 1.78
-       mklineCommandUnescaped := NewMkLine(NewLine("fname", 1, "\t# $ sha1 patches/patch-ac", nil))
+       mklineCommandUnescaped := T.NewMkLine("fname", 1, "\t# $ sha1 patches/patch-ac")
 
        c.Check(mklineCommandUnescaped.Shellcmd(), equals, "# $ sha1 patches/patch-ac")
        s.CheckOutputEmpty() // No warning about parsing the lonely dollar sign.
 
-       mklineVarassignUnescaped := NewMkLine(NewLine("fname", 1, "SED_CMD=\t's,#,hash,'", nil))
+       mklineVarassignUnescaped := T.NewMkLine("fname", 1, "SED_CMD=\t's,#,hash,'")
 
        c.Check(mklineVarassignUnescaped.Value(), equals, "'s,")
        s.CheckOutputLines(
@@ -270,7 +255,7 @@ func (s *Suite) Test_NewMkLine_numbersig
 
 func (s *Suite) Test_NewMkLine_leading_space(c *check.C) {
        s.Init(c)
-       _ = NewMkLine(NewLine("rubyversion.mk", 427, " _RUBYVER=\t2.15", nil))
+       _ = T.NewMkLine("rubyversion.mk", 427, " _RUBYVER=\t2.15")
 
        s.CheckOutputLines(
                "WARN: rubyversion.mk:427: Makefile lines should not start with space characters.")
@@ -281,7 +266,7 @@ func (s *Suite) Test_MkLines_Check__extr
        s.UseCommandLine("-Wextra")
        G.globalData.InitVartypes()
        G.Pkg = NewPackage("category/pkgbase")
-       G.Mk = s.NewMkLines("options.mk",
+       G.Mk = T.NewMkLines("options.mk",
                mkrcsid,
                ".for word in ${PKG_FAIL_REASON}",
                "PYTHON_VERSIONS_ACCEPTED=\t27 35 30",
@@ -304,7 +289,7 @@ func (s *Suite) Test_MkLines_Check__extr
 }
 
 func (s *Suite) Test_MkLine_variableNeedsQuoting__unknown_rhs(c *check.C) {
-       mkline := NewMkLine(NewLine("fname", 1, "PKGNAME := ${UNKNOWN}", nil))
+       mkline := T.NewMkLine("fname", 1, "PKGNAME := ${UNKNOWN}")
        G.globalData.InitVartypes()
 
        vuc := &VarUseContext{G.globalData.vartypes["PKGNAME"], vucTimeParse, vucQuotUnknown, false}
@@ -318,7 +303,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
        s.RegisterMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
-       mkline := NewMkLine(NewLine("Makefile", 95, "MASTER_SITES=\t${HOMEPAGE}", nil))
+       mkline := T.NewMkLine("Makefile", 95, "MASTER_SITES=\t${HOMEPAGE}")
 
        vuc := &VarUseContext{G.globalData.vartypes["MASTER_SITES"], vucTimeRun, vucQuotPlain, false}
        nq := mkline.VariableNeedsQuoting("HOMEPAGE", G.globalData.vartypes["HOMEPAGE"], vuc)
@@ -336,7 +321,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
        s.RegisterMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
-       mkline := NewMkLine(NewLine("Makefile", 96, "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=squirrel-sql/}", nil))
+       mkline := T.NewMkLine("Makefile", 96, "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=squirrel-sql/}")
 
        MkLineChecker{mkline}.checkVarassign()
 
@@ -347,7 +332,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("builtin.mk", 3, "USE_BUILTIN.Xfixes!=\t${PKG_ADMIN} pmatch 'pkg-[0-9]*' ${BUILTIN_PKG.Xfixes:Q}", nil))
+       mkline := T.NewMkLine("builtin.mk", 3, "USE_BUILTIN.Xfixes!=\t${PKG_ADMIN} pmatch 'pkg-[0-9]*' ${BUILTIN_PKG.Xfixes:Q}")
 
        MkLineChecker{mkline}.checkVarassign()
 
@@ -360,7 +345,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("Makefile", 3, "SUBST_SED.hpath=\t-e 's|^\\(INSTALL[\t:]*=\\).*|\\1${INSTALL}|'", nil))
+       mkline := T.NewMkLine("Makefile", 3, "SUBST_SED.hpath=\t-e 's|^\\(INSTALL[\t:]*=\\).*|\\1${INSTALL}|'")
 
        MkLineChecker{mkline}.checkVarassign()
 
@@ -375,7 +360,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.RegisterTool(&Tool{Name: "find", Varname: "FIND", Predefined: true})
        s.RegisterTool(&Tool{Name: "sort", Varname: "SORT", Predefined: true})
        G.Pkg = NewPackage("category/pkgbase")
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                mkrcsid,
                "GENERATE_PLIST= cd ${DESTDIR}${PREFIX}; ${FIND} * \\( -type f -or -type l \\) | ${SORT};")
 
@@ -390,7 +375,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                mkrcsid,
                "EGDIR=\t${EGDIR}/${MACHINE_GNU_PLATFORM}")
 
@@ -411,7 +396,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.RegisterTool(&Tool{Name: "perl", Varname: "PERL5", Predefined: true})
        s.RegisterTool(&Tool{Name: "bash", Varname: "BASH", Predefined: true})
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                mkrcsid,
                "\t${RUN} cd ${WRKSRC} && ( ${ECHO} ${PERL5:Q} ; ${ECHO} ) | ${BASH} ./install",
                "\t${RUN} cd ${WRKSRC} && ( ${ECHO} ${PERL5} ; ${ECHO} ) | ${BASH} ./install")
@@ -429,7 +414,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                mkrcsid,
                "MASTER_SITES=${HOMEPAGE}archive/")
 
@@ -449,7 +434,7 @@ func (s *Suite) Test_MkLine_variableNeed
        G.globalData.InitVartypes()
        s.RegisterTool(&Tool{Name: "awk", Varname: "AWK", Predefined: true})
        s.RegisterTool(&Tool{Name: "echo", Varname: "ECHO", Predefined: true})
-       G.Mk = s.NewMkLines("xpi.mk",
+       G.Mk = T.NewMkLines("xpi.mk",
                mkrcsid,
                "\t id=$$(${AWK} '{print}' < ${WRKSRC}/idfile) && echo \"$$id\"",
                "\t id=`${AWK} '{print}' < ${WRKSRC}/idfile` && echo \"$$id\"")
@@ -468,7 +453,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("x11/mlterm/Makefile",
+       G.Mk = T.NewMkLines("x11/mlterm/Makefile",
                mkrcsid,
                "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& ${LDFLAGS:M*:Q}|g'",
                "SUBST_SED.link=-e 's|(LIBTOOL_LINK).*(LIBS)|& '${LDFLAGS:M*:Q}'|g'")
@@ -490,7 +475,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                mkrcsid,
                "PKG_SUGGESTED_OPTIONS+=\t${PKG_DEFAULT_OPTIONS:Mcdecimal} ${PKG_OPTIONS.py-trytond:Mcdecimal}")
 
@@ -504,7 +489,7 @@ func (s *Suite) Test_MkLines_Check__MAST
        s.UseCommandLine("-Wall")
        s.RegisterMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("devel/catch/Makefile",
+       G.Mk = T.NewMkLines("devel/catch/Makefile",
                mkrcsid,
                "HOMEPAGE=\t${MASTER_SITE_GITHUB:=philsquared/Catch/}",
                "HOMEPAGE=\t${MASTER_SITE_GITHUB}",
@@ -526,7 +511,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.RegisterTool(&Tool{Name: "echo", Varname: "ECHO", Predefined: true})
        s.RegisterTool(&Tool{Name: "sh", Varname: "SH", Predefined: true})
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("x11/labltk/Makefile",
+       G.Mk = T.NewMkLines("x11/labltk/Makefile",
                mkrcsid,
                "CONFIGURE_ARGS+=\t-tklibs \"`${SH} -c '${ECHO} $$TK_LD_FLAGS'`\"")
 
@@ -539,7 +524,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("x11/qt5-qtbase/Makefile.common",
+       G.Mk = T.NewMkLines("x11/qt5-qtbase/Makefile.common",
                "BUILDLINK_TRANSFORM+=opt:-ldl:${BUILDLINK_LDADD.dl:M*}")
 
        MkLineChecker{G.Mk.mklines[0]}.Check()
@@ -553,7 +538,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("benchmarks/iozone/Makefile",
+       G.Mk = T.NewMkLines("benchmarks/iozone/Makefile",
                "SUBST_MESSAGE.crlf=\tStripping EOL CR in ${REPLACE_PERL}")
 
        MkLineChecker{G.Mk.mklines[0]}.Check()
@@ -565,7 +550,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("audio/jack-rack/Makefile",
+       G.Mk = T.NewMkLines("audio/jack-rack/Makefile",
                mkrcsid,
                "LADSPA_PLUGIN_PATH?=\t${PREFIX}/lib/ladspa",
                "CPPFLAGS+=\t\t-DLADSPA_PATH=\"\\\"${LADSPA_PLUGIN_PATH}\\\"\"")
@@ -580,7 +565,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("x11/eterm/Makefile",
+       G.Mk = T.NewMkLines("x11/eterm/Makefile",
                mkrcsid,
                "DISTFILES=\t${DEFAULT_DISTFILES} ${PIXMAP_FILES}")
 
@@ -594,7 +579,7 @@ func (s *Suite) Test_MkLine_variableNeed
        s.UseCommandLine("-Wall")
        s.RegisterMasterSite("MASTER_SITE_GNOME", "http://ftp.gnome.org/";)
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("x11/gtk3/Makefile",
+       G.Mk = T.NewMkLines("x11/gtk3/Makefile",
                mkrcsid,
                "MASTER_SITES=\tftp://ftp.gtk.org/${PKGNAME}/ ${MASTER_SITE_GNOME:=subdir/}")
 
@@ -610,7 +595,7 @@ func (s *Suite) Test_MkLine_variableNeed
        G.globalData.Tools = NewToolRegistry()
        G.globalData.Tools.RegisterVarname("tar", "TAR")
 
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "CONFIGURE_ENV+=\tSYS_TAR_COMMAND_PATH=${TOOLS_TAR:Q}")
@@ -627,7 +612,7 @@ func (s *Suite) Test_MkLine_Pkgmandir(c 
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("chat/ircII/Makefile",
+       G.Mk = T.NewMkLines("chat/ircII/Makefile",
                mkrcsid,
                "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/man",
                "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/${PKGMANDIR}")
@@ -642,7 +627,7 @@ func (s *Suite) Test_MkLines_Check__VERS
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("geography/viking/Makefile",
+       mklines := T.NewMkLines("geography/viking/Makefile",
                mkrcsid,
                "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=viking/}${VERSION}/")
 
@@ -656,7 +641,7 @@ func (s *Suite) Test_MkLines_Check__shel
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("x11/lablgtk1/Makefile",
+       mklines := T.NewMkLines("x11/lablgtk1/Makefile",
                mkrcsid,
                "CONFIGURE_ENV+=\tCC=${CC}")
 
@@ -671,7 +656,7 @@ func (s *Suite) Test_MkLine_shell_varuse
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("x11/motif/Makefile",
+       mklines := T.NewMkLines("x11/motif/Makefile",
                mkrcsid,
                "post-patch:",
                "\tfiles=`${GREP} -l \".fB$${name}.fP(3)\" *.3`")
@@ -695,7 +680,7 @@ func (s *Suite) Test_MkLine_VariableType
 func (s *Suite) Test_MkLine__comment_in_comment(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "COMMENT=\tPKCS#5 v2.0 PBKDF2 Module")
 
@@ -707,7 +692,7 @@ func (s *Suite) Test_MkLine__comment_in_
 
 func (s *Suite) Test_MkLine_ConditionVars(c *check.C) {
        s.Init(c)
-       var mkline MkLine = NewMkLine(NewLine("Makefile", 45, ".include \"../../category/package/buildlink3.mk\"", nil))
+       mkline := T.NewMkLine("Makefile", 45, ".include \"../../category/package/buildlink3.mk\"")
 
        c.Check(mkline.ConditionVars(), equals, "")
 
Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.27 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.28
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.27  Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sat Jan 13 23:56:14 2018
@@ -448,25 +448,21 @@ func (cv *VartypeCheck) Homepage() {
                        }
                }
                fixedURL := baseURL + subdir
-               explain := false
+               fix := cv.Line.Autofix()
                if baseURL != "" {
-                       if !cv.Line.AutofixReplace(wrong, fixedURL) {
-                               cv.Line.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs. Use %s directly.", fixedURL)
-                               explain = true
-                       }
+                       fix.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs. Use %s directly.", fixedURL)
                } else {
-                       cv.Line.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs.")
-                       explain = true
-               }
-               if explain {
-                       Explain(
-                               "The HOMEPAGE is a single URL, while MASTER_SITES is a list of URLs.",
-                               "As long as this list has exactly one element, this works, but as",
-                               "soon as another site is added, the HOMEPAGE would not be a valid",
-                               "URL anymore.",
-                               "",
-                               "Defining MASTER_SITES=${HOMEPAGE} is ok, though.")
+                       fix.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs.")
                }
+               fix.Explain(
+                       "The HOMEPAGE is a single URL, while MASTER_SITES is a list of URLs.",
+                       "As long as this list has exactly one element, this works, but as",
+                       "soon as another site is added, the HOMEPAGE would not be a valid",
+                       "URL anymore.",
+                       "",
+                       "Defining MASTER_SITES=${HOMEPAGE} is ok, though.")
+               fix.Replace(wrong, fixedURL)
+               fix.Apply()
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.6 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.7
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.6  Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Sat Jan 13 23:56:14 2018
@@ -183,15 +183,16 @@ func (ck MkLineChecker) checkCond(forVar
 }
 
 func (ck MkLineChecker) checkDirectiveIndentation(expectedDepth int) {
-       if G.Mk == nil {
+       if G.Mk == nil || !G.opts.WarnSpace {
                return
        }
        mkline := ck.MkLine
        indent := mkline.Indent()
        if expected := strings.Repeat(" ", expectedDepth); indent != expected {
-               if G.opts.WarnSpace && !mkline.AutofixReplace("."+indent, "."+expected) {
-                       mkline.Notef("This directive should be indented by %d spaces.", expectedDepth)
-               }
+               fix := mkline.Line.Autofix()
+               fix.Notef("This directive should be indented by %d spaces.", expectedDepth)
+               fix.Replace("."+indent, "."+expected)
+               fix.Apply()
        }
 }
 
@@ -547,9 +548,10 @@ func (ck MkLineChecker) CheckVaruseShell
 
                } else if mod != correctMod {
                        if vuc.quoting == vucQuotPlain {
-                               if !mkline.AutofixReplace("${"+varname+mod+"}", "${"+varname+correctMod+"}") {
-                                       mkline.Warnf("Please use ${%s%s} instead of ${%s%s}.", varname, correctMod, varname, mod)
-                               }
+                               fix := mkline.Line.Autofix()
+                               fix.Warnf("Please use ${%s%s} instead of ${%s%s}.", varname, correctMod, varname, mod)
+                               fix.Replace("${"+varname+mod+"}", "${"+varname+correctMod+"}")
+                               fix.Apply()
                        } else {
                                mkline.Warnf("Please use ${%s%s} instead of ${%s%s} and make sure"+
                                        " the variable appears outside of any quoting characters.", varname, correctMod, varname, mod)
@@ -574,28 +576,26 @@ func (ck MkLineChecker) CheckVaruseShell
        if hasSuffix(mod, ":Q") && (needsQuoting == nqNo || needsQuoting == nqDoesntMatter) {
                bad := "${" + varname + mod + "}"
                good := "${" + varname + strings.TrimSuffix(mod, ":Q") + "}"
-               needExplain := false
-               if needsQuoting == nqNo && !mkline.AutofixReplace(bad, good) {
-                       needExplain = true
-                       mkline.Warnf("The :Q operator should not be used for ${%s} here.", varname)
-               }
-               if needsQuoting == nqDoesntMatter && !mkline.AutofixReplace(bad, good) {
-                       needExplain = true
-                       mkline.Notef("The :Q operator isn't necessary for ${%s} here.", varname)
-               }
-               if needExplain {
-                       Explain(
-                               "Many variables in pkgsrc do not need the :Q operator, since they",
-                               "are not expected to contain white-space or other special characters.",
-                               "Examples for these \"safe\" variables are:",
-                               "",
-                               "\t* filenames",
-                               "\t* directory names",
-                               "\t* user and group names",
-                               "\t* tool names and tool paths",
-                               "\t* variable names",
-                               "\t* PKGNAME")
+
+               fix := mkline.Line.Autofix()
+               if needsQuoting == nqNo {
+                       fix.Warnf("The :Q operator should not be used for ${%s} here.", varname)
+               } else {
+                       fix.Notef("The :Q operator isn't necessary for ${%s} here.", varname)
                }
+               fix.Explain(
+                       "Many variables in pkgsrc do not need the :Q operator, since they",
+                       "are not expected to contain white-space or other special characters.",
+                       "Examples for these \"safe\" variables are:",
+                       "",
+                       "\t* filenames",
+                       "\t* directory names",
+                       "\t* user and group names",
+                       "\t* tool names and tool paths",
+                       "\t* variable names",
+                       "\t* PKGNAME")
+               fix.Replace(bad, good)
+               fix.Apply()
        }
 }
 
Index: pkgsrc/pkgtools/pkglint/files/mkparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.6 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.7
--- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.6  Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go      Sat Jan 13 23:56:14 2018
@@ -7,7 +7,8 @@ import (
 func (s *Suite) Test_MkParser_MkTokens(c *check.C) {
        s.Init(c)
        checkRest := func(input string, expectedTokens []*MkToken, expectedRest string) {
-               p := NewMkParser(dummyLine, input, true)
+               line := T.NewLines("Test_MkParser_MkTokens.mk", input)[0]
+               p := NewMkParser(line, input, true)
                actualTokens := p.MkTokens()
                c.Check(actualTokens, deepEquals, expectedTokens)
                for i, expectedToken := range expectedTokens {
@@ -110,7 +111,7 @@ func (s *Suite) Test_MkParser_MkTokens(c
 
        check("$(GNUSTEP_USER_ROOT)", varuseText("$(GNUSTEP_USER_ROOT)", "GNUSTEP_USER_ROOT"))
        s.CheckOutputLines(
-               "WARN: Please use curly braces {} instead of round parentheses () for GNUSTEP_USER_ROOT.")
+               "WARN: Test_MkParser_MkTokens.mk:1: Please use curly braces {} instead of round parentheses () for GNUSTEP_USER_ROOT.")
 
        checkRest("${VAR)", nil, "${VAR)") // Opening brace, closing parenthesis
        checkRest("$(VAR}", nil, "$(VAR}") // Opening parenthesis, closing brace
@@ -119,7 +120,7 @@ func (s *Suite) Test_MkParser_MkTokens(c
        check("${PLIST_SUBST_VARS:@var@${var}=${${var}:Q}@}", varuse("PLIST_SUBST_VARS", "@var@${var}=${${var}:Q}@"))
        check("${PLIST_SUBST_VARS:@var@${var}=${${var}:Q}}", varuse("PLIST_SUBST_VARS", "@var@${var}=${${var}:Q}")) // Missing @ at the end
        s.CheckOutputLines(
-               "WARN: Modifier ${PLIST_SUBST_VARS:@var@...@} is missing the final \"@\".")
+               "WARN: Test_MkParser_MkTokens.mk:1: Modifier ${PLIST_SUBST_VARS:@var@...@} is missing the final \"@\".")
 
        checkRest("hello, ${W:L:tl}orld", []*MkToken{
                literal("hello, "),
@@ -220,7 +221,7 @@ func (s *Suite) Test_MkParser__varuse_pa
        s.UseCommandLine("--autofix")
        G.globalData.InitVartypes()
        filename := s.CreateTmpFile("Makefile", "")
-       mklines := s.NewMkLines(filename,
+       mklines := T.NewMkLines(filename,
                mkrcsid,
                "COMMENT=$(P1) $(P2)) $(P3:Q) ${BRACES}")
 

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.3 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.4
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.3     Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Sat Jan 13 23:56:14 2018
@@ -6,7 +6,7 @@ func (s *Suite) Test_MkLineChecker_Check
        s.Init(c)
        s.UseCommandLine("-Wtypes")
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("fname", 1, "COMMENT=\tA nice package", nil))
+       mkline := T.NewMkLine("fname", 1, "COMMENT=\tA nice package")
 
        vartype1 := G.globalData.vartypes["COMMENT"]
        c.Assert(vartype1, check.NotNil)
@@ -26,7 +26,7 @@ func (s *Suite) Test_MkLineChecker_Check
 
 func (s *Suite) Test_MkLineChecker_CheckVartype(c *check.C) {
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("fname", 1, "DISTNAME=gcc-${GCC_VERSION}", nil))
+       mkline := T.NewMkLine("fname", 1, "DISTNAME=gcc-${GCC_VERSION}")
 
        MkLineChecker{mkline}.CheckVartype("DISTNAME", opAssign, "gcc-${GCC_VERSION}", "")
 }
@@ -37,7 +37,7 @@ func (s *Suite) Test_MkLineChecker_check
        s.Init(c)
        G.Pkg = NewPackage("graphics/gimp-fix-ca")
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("fname", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";, nil))
+       mkline := T.NewMkLine("fname", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";)
 
        MkLineChecker{mkline}.checkVarassign()
 
@@ -49,46 +49,46 @@ func (s *Suite) Test_MkLineChecker_Check
        s.UseCommandLine("-Wtypes")
        G.globalData.InitVartypes()
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if !empty(PKGSRC_COMPILER:Mmycc)", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if !empty(PKGSRC_COMPILER:Mmycc)")}.CheckCond()
 
        c.Check(s.Stdout(), equals, "WARN: fname:1: The pattern \"mycc\" cannot match any of "+
                "{ ccache ccc clang distcc f2c gcc hp icc ido "+
                "mipspro mipspro-ucode pcc sunpro xlc } for PKGSRC_COMPILER.\n")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".elif ${A} != ${B}", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".elif ${A} != ${B}")}.CheckCond()
 
        c.Check(s.Stdout(), equals, "")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if ${HOMEPAGE} == \"mailto:someone%example.org@localhost\"";, nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if ${HOMEPAGE} == \"mailto:someone%example.org@localhost\"";)}.CheckCond()
 
        s.CheckOutputLines(
                "WARN: fname:1: \"mailto:someone%example.org@localhost\"; is not a valid URL.")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])")}.CheckCond()
 
        s.CheckOutputLines(
                "WARN: fname:1: PKGSRC_RUN_TEST should be matched against \"[yY][eE][sS]\" or \"[nN][oO]\", not \"[Y][eE][sS]\".")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])")}.CheckCond()
 
        s.CheckOutputEmpty()
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if !empty(${IS_BUILTIN.Xfixes:M[yY][eE][sS]})", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if !empty(${IS_BUILTIN.Xfixes:M[yY][eE][sS]})")}.CheckCond()
 
        s.CheckOutputLines(
                "WARN: fname:1: The empty() function takes a variable name as parameter, not a variable expression.")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if ${EMUL_PLATFORM} == \"linux-x386\"", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM} == \"linux-x386\"")}.CheckCond()
 
        s.CheckOutputLines(
                "WARN: fname:1: \"x386\" is not valid for the hardware architecture part of EMUL_PLATFORM. Use one of { aarch64 aarch64eb alpha amd64 arc arm arm26 arm32 cobalt coldfire convex 
dreamcast earm earmeb earmhf earmhfeb earmv4 earmv4eb earmv5 earmv5eb earmv6 earmv6eb earmv6hf earmv6hfeb earmv7 earmv7eb earmv7hf earmv7hfeb evbarm hpcmips hpcsh hppa hppa64 i386 i586 i686 ia64 
m68000 m68k m88k mips mips64 mips64eb mips64el mipseb mipsel mipsn32 mlrisc ns32k pc532 pmax powerpc powerpc64 rs6000 s390 sh3eb sh3el sparc sparc64 vax x86_64 } instead.")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 1, ".if ${EMUL_PLATFORM:Mlinux-x386}", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 1, ".if ${EMUL_PLATFORM:Mlinux-x386}")}.CheckCond()
 
        s.CheckOutputLines(
                "WARN: fname:1: The pattern \"x386\" cannot match any of { aarch64 aarch64eb alpha amd64 arc arm arm26 arm32 cobalt coldfire convex dreamcast earm earmeb earmhf earmhfeb earmv4 
earmv4eb earmv5 earmv5eb earmv6 earmv6eb earmv6hf earmv6hfeb earmv7 earmv7eb earmv7hf earmv7hfeb evbarm hpcmips hpcsh hppa hppa64 i386 i586 i686 ia64 m68000 m68k m88k mips mips64 mips64eb mips64el 
mipseb mipsel mipsn32 mlrisc ns32k pc532 pmax powerpc powerpc64 rs6000 s390 sh3eb sh3el sparc sparc64 vax x86_64 } for the hardware architecture part of EMUL_PLATFORM.")
 
-       MkLineChecker{NewMkLine(NewLine("fname", 98, ".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}", nil))}.CheckCond()
+       MkLineChecker{T.NewMkLine("fname", 98, ".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}")}.CheckCond()
 
        s.CheckOutputLines(
                "WARN: fname:98: The pattern \"UnknownOS\" cannot match any of { AIX BSDOS Bitrig Cygwin Darwin DragonFly FreeBSD FreeMiNT GNUkFreeBSD HPUX Haiku IRIX Interix Linux Minix MirBSD 
NetBSD OSF1 OpenBSD QNX SCO_SV SunOS UnixWare } for the operating system part of MACHINE_PLATFORM.",
@@ -99,7 +99,7 @@ func (s *Suite) Test_MkLineChecker_check
        s.Init(c)
        G.globalData.InitVartypes()
 
-       G.Mk = s.NewMkLines("Makefile",
+       G.Mk = T.NewMkLines("Makefile",
                mkrcsid,
                "ac_cv_libpari_libs+=\t-L${BUILDLINK_PREFIX.pari}/lib") // From math/clisp-pari/Makefile, rev. 1.8
 
@@ -113,7 +113,7 @@ func (s *Suite) Test_MkLineChecker_check
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mkline := NewMkLine(NewLine("options.mk", 2, "PKG_DEVELOPER?=\tyes", nil))
+       mkline := T.NewMkLine("options.mk", 2, "PKG_DEVELOPER?=\tyes")
 
        MkLineChecker{mkline}.checkVarassignDefPermissions()
 
@@ -125,7 +125,7 @@ func (s *Suite) Test_MkLineChecker_Check
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("options.mk",
+       mklines := T.NewMkLines("options.mk",
                mkrcsid,
                "COMMENT=\t${GAMES_USER}",
                "COMMENT:=\t${PKGBASE}",
@@ -148,7 +148,7 @@ func (s *Suite) Test_MkLineChecker_Check
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("options.mk",
+       mklines := T.NewMkLines("options.mk",
                mkrcsid,
                "WRKSRC:=${.CURDIR}")
 
@@ -159,7 +159,7 @@ func (s *Suite) Test_MkLineChecker_Check
 
 func (s *Suite) Test_MkLineChecker_WarnVaruseLocalbase(c *check.C) {
        s.Init(c)
-       mkline := NewMkLine(NewLine("options.mk", 56, "PKGNAME=${LOCALBASE}", nil))
+       mkline := T.NewMkLine("options.mk", 56, "PKGNAME=${LOCALBASE}")
 
        MkLineChecker{mkline}.WarnVaruseLocalbase()
 
@@ -169,7 +169,7 @@ func (s *Suite) Test_MkLineChecker_WarnV
 
 func (s *Suite) Test_MkLineChecker_CheckRelativePkgdir(c *check.C) {
        s.Init(c)
-       mkline := NewMkLine(NewLine("Makefile", 46, "# dummy", nil))
+       mkline := T.NewMkLine("Makefile", 46, "# dummy")
 
        MkLineChecker{mkline}.CheckRelativePkgdir("../pkgbase")
 
@@ -181,7 +181,7 @@ func (s *Suite) Test_MkLineChecker_Check
 // PR pkg/46570, item 2
 func (s *Suite) Test_MkLineChecker__unclosed_varuse(c *check.C) {
        s.Init(c)
-       mkline := NewMkLine(NewLine("Makefile", 93, "EGDIRS=${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d", nil))
+       mkline := T.NewMkLine("Makefile", 93, "EGDIRS=${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d")
 
        MkLineChecker{mkline}.checkVarassign()
 
@@ -195,7 +195,7 @@ func (s *Suite) Test_MkLineChecker__Varu
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("x11/xkeyboard-config/Makefile",
+       G.Mk = T.NewMkLines("x11/xkeyboard-config/Makefile",
                "FILES_SUBST+=XKBCOMP_SYMLINK=${${XKBBASE}/xkbcomp:L:Q}")
 
        MkLineChecker{G.Mk.mklines[0]}.Check()
@@ -207,7 +207,7 @@ func (s *Suite) Test_MkLineChecker_Check
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("security/openssl/Makefile",
+       G.Mk = T.NewMkLines("security/openssl/Makefile",
                mkrcsid,
                ".if ${PKGSRC_COMPILER} == \"gcc\" && ${CC} == \"cc\"",
                ".endif")
@@ -223,7 +223,7 @@ func (s *Suite) Test_MkLine_CheckCond_co
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("audio/pulseaudio/Makefile",
+       G.Mk = T.NewMkLines("audio/pulseaudio/Makefile",
                mkrcsid,
                ".if ${OPSYS} == \"Darwin\" && ${PKGSRC_COMPILER} == \"clang\"",
                ".endif")
@@ -238,7 +238,7 @@ func (s *Suite) Test_MkLineChecker_Check
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.globalData.InitVartypes()
-       G.Mk = s.NewMkLines("chat/pidgin-icb/Makefile",
+       G.Mk = T.NewMkLines("chat/pidgin-icb/Makefile",
                mkrcsid,
                "CFLAGS+=\t`pkg-config pidgin --cflags`")
        mkline := G.Mk.mklines[1]
@@ -259,7 +259,7 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVartype_CFLAGS(c *check.C) {
        s.Init(c)
        G.globalData.InitVartypes()
-       mklines := s.NewMkLines("Makefile",
+       mklines := T.NewMkLines("Makefile",
                mkrcsid,
                "CPPFLAGS.SunOS+=\t-DPIPECOMMAND=\\\"/usr/sbin/sendmail -bs %s\\\"")
 

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.17 pkgsrc/pkgtools/pkglint/files/mklines.go:1.18
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.17       Wed Jan 10 00:39:52 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Sat Jan 13 23:56:14 2018
@@ -336,30 +336,31 @@ func (va *VaralignBlock) fixalign(mkline
                return
        }
 
-       if !mkline.AutofixReplace(prefix+oldalign, prefix+newalign) {
-               wrongColumn := tabLength(prefix+oldalign) != tabLength(prefix+newalign)
-               switch {
-               case hasSpace && wrongColumn:
-                       mkline.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", goodWidth+1)
-               case hasSpace:
-                       mkline.Notef("Variable values should be aligned with tabs, not spaces.")
-               case wrongColumn:
-                       mkline.Notef("This variable value should be aligned to column %d.", goodWidth+1)
-               }
-               if wrongColumn {
-                       Explain(
-                               "Normally, all variable values in a block should start at the same",
-                               "column.  There are some exceptions to this rule:",
-                               "",
-                               "Definitions for long variable names may be indented with a single",
-                               "space instead of tabs, but only if they appear in a block that is",
-                               "otherwise indented using tabs.",
-                               "",
-                               "Variable definitions that span multiple lines are not checked for",
-                               "alignment at all.",
-                               "",
-                               "When the block contains something else than variable definitions,",
-                               "it is not checked at all.")
-               }
+       fix := mkline.Line.Autofix()
+       wrongColumn := tabLength(prefix+oldalign) != tabLength(prefix+newalign)
+       switch {
+       case hasSpace && wrongColumn:
+               fix.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", goodWidth+1)
+       case hasSpace:
+               fix.Notef("Variable values should be aligned with tabs, not spaces.")
+       case wrongColumn:
+               fix.Notef("This variable value should be aligned to column %d.", goodWidth+1)
        }
+       if wrongColumn {
+               fix.Explain(
+                       "Normally, all variable values in a block should start at the same",
+                       "column.  There are some exceptions to this rule:",
+                       "",
+                       "Definitions for long variable names may be indented with a single",
+                       "space instead of tabs, but only if they appear in a block that is",
+                       "otherwise indented using tabs.",
+                       "",
+                       "Variable definitions that span multiple lines are not checked for",
+                       "alignment at all.",
+                       "",
+                       "When the block contains something else than variable definitions,",
+                       "it is not checked at all.")
+       }
+       fix.Replace(prefix+oldalign, prefix+newalign)
+       fix.Apply()
 }
Index: pkgsrc/pkgtools/pkglint/files/patches.go
diff -u pkgsrc/pkgtools/pkglint/files/patches.go:1.17 pkgsrc/pkgtools/pkglint/files/patches.go:1.18
--- pkgsrc/pkgtools/pkglint/files/patches.go:1.17       Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/patches.go    Sat Jan 13 23:56:14 2018
@@ -181,9 +181,10 @@ func (ck *PatchChecker) checkBeginDiff(l
                        "be mentioned in this file, to prevent duplicate work.")
        }
        if G.opts.WarnSpace && !ck.previousLineEmpty {
-               if !line.AutofixInsertBefore("") {
-                       line.Notef("Empty line expected.")
-               }
+               fix := line.Autofix()
+               fix.Notef("Empty line expected.")
+               fix.InsertBefore("")
+               fix.Apply()
        }
 }
 
@@ -235,11 +236,12 @@ func (ck *PatchChecker) checktextUniHunk
 
        line := ck.exp.PreviousLine()
        if hasSuffix(line.Text, "\r") {
-               if !line.AutofixReplace("\r\n", "\n") {
-                       line.Errorf("The hunk header must not end with a CR character.")
-                       Explain(
-                               "The MacOS X patch utility cannot handle these.")
-               }
+               fix := line.Autofix()
+               fix.Errorf("The hunk header must not end with a CR character.")
+               fix.Explain(
+                       "The MacOS X patch utility cannot handle these.")
+               fix.Replace("\r\n", "\n")
+               fix.Apply()
        }
 }
 
Index: pkgsrc/pkgtools/pkglint/files/plist_test.go
diff -u pkgsrc/pkgtools/pkglint/files/plist_test.go:1.17 pkgsrc/pkgtools/pkglint/files/plist_test.go:1.18
--- pkgsrc/pkgtools/pkglint/files/plist_test.go:1.17    Sun Jan  7 17:08:15 2018
+++ pkgsrc/pkgtools/pkglint/files/plist_test.go Sat Jan 13 23:56:14 2018
@@ -8,7 +8,7 @@ func (s *Suite) Test_ChecklinesPlist(c *
        s.Init(c)
        s.UseCommandLine("-Wall")
        G.Pkg = NewPackage("category/pkgbase")
-       lines := s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "bin/i386/6c",
                "bin/program",
                "etc/my.cnf",
@@ -51,7 +51,7 @@ func (s *Suite) Test_ChecklinesPlist(c *
 
 func (s *Suite) Test_ChecklinesPlist__empty(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "@comment $"+"NetBSD$")
 
        ChecklinesPlist(lines)
@@ -78,7 +78,7 @@ func (s *Suite) Test_ChecklinesPlist__co
        s.Init(c)
        G.Pkg = NewPackage("category/pkgbase")
        G.Pkg.plistSubstCond["PLIST.bincmds"] = true
-       lines := s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "@comment $"+"NetBSD$",
                "${PLIST.bincmds}bin/subdir/command")
 
@@ -91,7 +91,7 @@ func (s *Suite) Test_ChecklinesPlist__co
 func (s *Suite) Test_ChecklinesPlist__sorting(c *check.C) {
        s.Init(c)
        s.UseCommandLine("-Wplist-sort")
-       lines := s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "@comment $"+"NetBSD$",
                "@comment Do not remove",
                "sbin/i386/6c",
@@ -110,7 +110,7 @@ func (s *Suite) Test_PlistLineSorter_Sor
        s.Init(c)
        s.UseCommandLine("--autofix")
        tmpfile := s.CreateTmpFile("PLIST", "dummy\n")
-       lines := s.NewLines(tmpfile,
+       lines := T.NewLines(tmpfile,
                "@comment $"+"NetBSD$",
                "@comment Do not remove",
                "A",
@@ -171,7 +171,7 @@ func (s *Suite) Test_PlistChecker_checkp
        s.UseCommandLine("-Wextra")
        G.Pkg = NewPackage("category/pkgpath")
 
-       ChecklinesPlist(s.NewLines("PLIST",
+       ChecklinesPlist(T.NewLines("PLIST",
                "@comment $"+"NetBSD$",
                "share/applications/pkgbase.desktop"))
 
@@ -182,10 +182,11 @@ func (s *Suite) Test_PlistChecker_checkp
 func (s *Suite) Test_PlistChecker_checkpathMan_gz(c *check.C) {
        s.Init(c)
        G.Pkg = NewPackage("category/pkgbase")
-
-       ChecklinesPlist(s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "@comment $"+"NetBSD$",
-               "man/man3/strerror.3.gz"))
+               "man/man3/strerror.3.gz")
+
+       ChecklinesPlist(lines)
 
        s.CheckOutputLines(
                "NOTE: PLIST:2: The .gz extension is unnecessary for manual pages.")
@@ -193,7 +194,7 @@ func (s *Suite) Test_PlistChecker_checkp
 
 func (s *Suite) TestPlistChecker_checkpath__PKGMANDIR(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "@comment $"+"NetBSD$",
                "${PKGMANDIR}/man1/sh.1")
 
@@ -205,7 +206,7 @@ func (s *Suite) TestPlistChecker_checkpa
 
 func (s *Suite) TestPlistChecker_checkpath__python_egg(c *check.C) {
        s.Init(c)
-       lines := s.NewLines("PLIST",
+       lines := T.NewLines("PLIST",
                "@comment $"+"NetBSD$",
                "${PYSITELIB}/gdspy-${PKGVERSION}-py${PYVERSSUFFIX}.egg-info/PKG-INFO")
 

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.22 pkgsrc/pkgtools/pkglint/files/package.go:1.23
--- pkgsrc/pkgtools/pkglint/files/package.go:1.22       Wed Jan 10 00:39:52 2018
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sat Jan 13 23:56:14 2018
@@ -414,6 +414,9 @@ func (pkg *Package) checkfilePackageMake
                        // probably contains a statement that C is
                        // really not needed.
 
+               } else if !G.Infrastructure && useLine.Filename == "../../mk/compiler.mk" {
+                       // Ignore this one
+
                } else if !matches(useLine.Value(), `(?:^|\s+)(?:c|c99|objc)(?:\s+|$)`) {
                        gnuLine.Warnf("GNU_CONFIGURE almost always needs a C compiler, but \"c\" is not added to USE_LANGUAGES in %s.",
                                useLine.ReferenceFrom(gnuLine.Line))
@@ -804,19 +807,20 @@ func (mklines *MkLines) checkForUsedComm
                i++
        }
 
-       insertLine := lines[i]
-       if !insertLine.AutofixInsertBefore(expected) {
-               insertLine.Warnf("Please add a line %q here.", expected)
-               Explain(
-                       "Since Makefile.common files usually don't have any comments and",
-                       "therefore not a clearly defined interface, they should at least",
-                       "contain references to all files that include them, so that it is",
-                       "easier to see what effects future changes may have.",
-                       "",
-                       "If there are more than five packages that use a Makefile.common,",
-                       "you should think about giving it a proper name (maybe plugin.mk) and",
-                       "documenting its interface.")
-       }
+       fix := lines[i].Autofix()
+       fix.Warnf("Please add a line %q here.", expected)
+       fix.Explain(
+               "Since Makefile.common files usually don't have any comments and",
+               "therefore not a clearly defined interface, they should at least",
+               "contain references to all files that include them, so that it is",
+               "easier to see what effects future changes may have.",
+               "",
+               "If there are more than five packages that use a Makefile.common,",
+               "you should think about giving it a proper name (maybe plugin.mk) and",
+               "documenting its interface.")
+       fix.InsertBefore(expected)
+       fix.Apply()
+
        SaveAutofixChanges(lines)
 }
 

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.15 pkgsrc/pkgtools/pkglint/files/package_test.go:1.16
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.15  Sun Jan  7 01:13:21 2018
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sat Jan 13 23:56:14 2018
@@ -1,13 +1,11 @@
 package main
 
-import (
-       check "gopkg.in/check.v1"
-)
+import "gopkg.in/check.v1"
 
 func (s *Suite) Test_Package_pkgnameFromDistname(c *check.C) {
        s.Init(c)
        pkg := NewPackage("dummy")
-       pkg.vardef["PKGNAME"] = NewMkLine(NewLine("Makefile", 5, "PKGNAME=dummy", nil))
+       pkg.vardef["PKGNAME"] = T.NewMkLine("Makefile", 5, "PKGNAME=dummy")
 
        c.Check(pkg.pkgnameFromDistname("pkgname-1.0", "whatever"), equals, "pkgname-1.0")
        c.Check(pkg.pkgnameFromDistname("${DISTNAME}", "distname-1.0"), equals, "distname-1.0")
@@ -27,7 +25,7 @@ func (s *Suite) Test_Package_ChecklinesP
        s.UseCommandLine("-Worder")
        pkg := NewPackage("x11/9term")
 
-       pkg.ChecklinesPackageMakefileVarorder(s.NewMkLines("Makefile",
+       pkg.ChecklinesPackageMakefileVarorder(T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "DISTNAME=9term",
@@ -35,7 +33,7 @@ func (s *Suite) Test_Package_ChecklinesP
 
        s.CheckOutputEmpty()
 
-       pkg.ChecklinesPackageMakefileVarorder(s.NewMkLines("Makefile",
+       pkg.ChecklinesPackageMakefileVarorder(T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "DISTNAME=9term",
@@ -83,7 +81,7 @@ func (s *Suite) Test_Package_ChecklinesP
        s.UseCommandLine("-Worder")
        pkg := NewPackage("category/package")
 
-       pkg.ChecklinesPackageMakefileVarorder(s.NewMkLines("Makefile",
+       pkg.ChecklinesPackageMakefileVarorder(T.NewMkLines("Makefile",
                mkrcsid,
                "",
                "PKGNAME=\tpackage-1.0",
@@ -96,20 +94,20 @@ func (s *Suite) Test_Package_ChecklinesP
 
 func (s *Suite) Test_Package_getNbpart(c *check.C) {
        pkg := NewPackage("category/pkgbase")
-       pkg.vardef["PKGREVISION"] = NewMkLine(NewLine("Makefile", 1, "PKGREVISION=14", nil))
+       pkg.vardef["PKGREVISION"] = T.NewMkLine("Makefile", 1, "PKGREVISION=14")
 
        c.Check(pkg.getNbpart(), equals, "nb14")
 
-       pkg.vardef["PKGREVISION"] = NewMkLine(NewLine("Makefile", 1, "PKGREVISION=asdf", nil))
+       pkg.vardef["PKGREVISION"] = T.NewMkLine("Makefile", 1, "PKGREVISION=asdf")
 
        c.Check(pkg.getNbpart(), equals, "")
 }
 
 func (s *Suite) Test_Package_determineEffectivePkgVars__precedence(c *check.C) {
        pkg := NewPackage("category/pkgbase")
-       pkgnameLine := NewMkLine(NewLine("Makefile", 3, "PKGNAME=pkgname-1.0", nil))
-       distnameLine := NewMkLine(NewLine("Makefile", 4, "DISTNAME=distname-1.0", nil))
-       pkgrevisionLine := NewMkLine(NewLine("Makefile", 5, "PKGREVISION=13", nil))
+       pkgnameLine := T.NewMkLine("Makefile", 3, "PKGNAME=pkgname-1.0")
+       distnameLine := T.NewMkLine("Makefile", 4, "DISTNAME=distname-1.0")
+       pkgrevisionLine := T.NewMkLine("Makefile", 5, "PKGREVISION=13")
 
        pkg.defineVar(pkgnameLine, pkgnameLine.Varname())
        pkg.defineVar(distnameLine, distnameLine.Varname())
@@ -127,12 +125,12 @@ func (s *Suite) Test_Package_checkPossib
        G.Pkg = NewPackage("category/pkgbase")
        G.CurPkgsrcdir = "../.."
        G.Pkg.EffectivePkgname = "package-1.0nb15"
-       G.Pkg.EffectivePkgnameLine = NewMkLine(NewLine("category/pkgbase/Makefile", 5, "PKGNAME=dummy", nil))
+       G.Pkg.EffectivePkgnameLine = T.NewMkLine("category/pkgbase/Makefile", 5, "PKGNAME=dummy")
        G.globalData.LastChange = map[string]*Change{
-               "category/pkgbase": &Change{
+               "category/pkgbase": {
                        Action:  "Updated",
                        Version: "1.8",
-                       Line:    NewLine("doc/CHANGES", 116, "dummy", nil),
+                       Line:    T.NewLine("doc/CHANGES", 116, "dummy"),
                },
        }
 

Index: pkgsrc/pkgtools/pkglint/files/pkglint.0
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.0:1.37 pkgsrc/pkgtools/pkglint/files/pkglint.0:1.38
--- pkgsrc/pkgtools/pkglint/files/pkglint.0:1.37        Sun Jun  5 11:24:32 2016
+++ pkgsrc/pkgtools/pkglint/files/pkglint.0     Sat Jan 13 23:56:14 2018
@@ -14,21 +14,12 @@ DDEESSCCRRIIPPTTIIOONN
      --CC{{[[nnoo--]]cchheecckk,,......}}  Enable or disable specific checks.  For a list of
                          checks, see below.
 
-     --FF|----aauuttooffiixx        Repair trivial things automatically.
-
-     --II                  Show the _M_a_k_e_f_i_l_e that is constructed by including
-                         all the files that are slurped in via `.include'
-                         directives.  This flag is mainly for debugging.
-
-     --VV|----vveerrssiioonn        Print the current ppkkgglliinntt version number and exit.
-
-     --WW{{[[nnoo--]]wwaarrnn,,......}}   Enable or disable specific warnings.  For a list of
-                         warnings, see below.
-
      --dd|----ddeebbuugg          Enable or disable verbose log for debugging pkglint.
 
      --ee|----eexxppllaaiinn        Print verbose explanations for diagnostics.
 
+     --FF|----aauuttooffiixx        Repair some of the warnings automatically.
+
      --gg|----ggcccc--oouuttppuutt--ffoorrmmaatt
                          Use a format for the diagnostics that is understood
                          by most programs, especially editors, so they can
@@ -36,10 +27,20 @@ DDEESSCCRRIIPPTTIIOONN
 
      --hh|----hheellpp           Show the summary of command line options, then exit.
 
+     --II                  Show the _M_a_k_e_f_i_l_e that is constructed by including
+                         all the files that are slurped in via `.include'
+                         directives.  This flag is mainly for debugging.
+
      --ii|----iimmppoorrtt         Check if a package is ready to be imported into
                          pkgsrc.  This is especially useful for packages from
                          the pkgsrc-wip project.
 
+     --oo|----oonnllyy _s_u_b_s_t_r_i_n_g
+                         Only handle those diagnostics that have _s_u_b_s_t_r_i_n_g in
+                         their text.  This is useful in combination with
+                         ----aauuttooffiixx and ----rreeccuurrssiivvee,, to fix only a single kind
+                         of warning in a large number of files.
+
      --qq|----qquuiieett          Don't print the errors and warnings summary before
                          terminating.
 
@@ -51,6 +52,11 @@ DDEESSCCRRIIPPTTIIOONN
                          diagnostics.  This is especially useful together with
                          the --ff|----sshhooww--aauuttooffiixx option.
 
+     --VV|----vveerrssiioonn        Print the current ppkkgglliinntt version number and exit.
+
+     --WW{{[[nnoo--]]wwaarrnn,,......}}   Enable or disable specific warnings.  For a list of
+                         warnings, see below.
+
    CChheecckkss
      aallll                 Enable all checks.
 
@@ -146,7 +152,7 @@ AAUUTTHHOORRSS
      Roland Illig <_r_i_l_l_i_g_@_N_e_t_B_S_D_._o_r_g>
 
 BBUUGGSS
-     If you don't understand the messages, feel free to ask on the
-     <tech-pkg%NetBSD.org@localhost> mailing list.
+     If you don't understand the messages, feel free to ask the author or on
+     the <pkgsrc-users%pkgsrc.org@localhost> mailing list.
 
-NetBSD 7.0                       June 5, 2016                       NetBSD 7.0
+NetBSD 7.0.2                   January 14, 2018                   NetBSD 7.0.2

Index: pkgsrc/pkgtools/pkglint/files/pkglint.1
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.1:1.51 pkgsrc/pkgtools/pkglint/files/pkglint.1:1.52
--- pkgsrc/pkgtools/pkglint/files/pkglint.1:1.51        Sun Jun  5 11:24:32 2016
+++ pkgsrc/pkgtools/pkglint/files/pkglint.1     Sat Jan 13 23:56:14 2018
@@ -1,4 +1,4 @@
-.\"    $NetBSD: pkglint.1,v 1.51 2016/06/05 11:24:32 rillig Exp $
+.\"    $NetBSD: pkglint.1,v 1.52 2018/01/13 23:56:14 rillig Exp $
 .\"    From FreeBSD: portlint.1,v 1.8 1997/11/25 14:53:14 itojun Exp
 .\"
 .\" Copyright (c) 1997 by Jun-ichiro Itoh <itojun%itojun.org@localhost>.
@@ -6,9 +6,9 @@
 .\"
 .\" Roland Illig <roland.illig%gmx.de@localhost>, 2004, 2005.
 .\" Thomas Klausner <wiz%NetBSD.org@localhost>, 2012.
-.\" Roland Illig <rillig%NetBSD.org@localhost>, 2015, 2016.
+.\" Roland Illig <rillig%NetBSD.org@localhost>, 2015-2018.
 .\"
-.Dd June 5, 2016
+.Dd January 14, 2018
 .Dt PKGLINT 1
 .Os
 .Sh NAME
@@ -29,35 +29,37 @@ to be bugs, or that are simply deprecate
 .It Fl C{[no-]check,...}
 Enable or disable specific checks.
 For a list of checks, see below.
-.It Fl F Ns | Ns Fl -autofix
-Repair trivial things automatically.
-.It Fl I
-Show the
-.Pa Makefile
-that is constructed by including all the files that
-are slurped in via
-.Ql .include
-directives.
-This flag is mainly for debugging.
-.It Fl V Ns | Ns Fl -version
-Print the current
-.Nm
-version number and exit.
-.It Fl W{[no-]warn,...}
-Enable or disable specific warnings.
-For a list of warnings, see below.
 .It Fl d Ns | Ns Fl -debug
 Enable or disable verbose log for debugging pkglint.
 .It Fl e Ns | Ns Fl -explain
 Print verbose explanations for diagnostics.
+.It Fl F Ns | Ns Fl -autofix
+Repair some of the warnings automatically.
 .It Fl g Ns | Ns Fl -gcc-output-format
 Use a format for the diagnostics that is understood by most programs,
 especially editors, so they can provide a point-and-goto interface.
 .It Fl h Ns | Ns Fl -help
 Show the summary of command line options, then exit.
+.It Fl I
+Show the
+.Pa Makefile
+that is constructed by including all the files that
+are slurped in via
+.Ql .include
+directives.
+This flag is mainly for debugging.
 .It Fl i Ns | Ns Fl -import
 Check if a package is ready to be imported into pkgsrc.
 This is especially useful for packages from the pkgsrc-wip project.
+.It Fl o Ns | Ns Fl -only Ar substring
+Only handle those diagnostics that have
+.Ar substring
+in their text.
+This is useful in combination with
+.Fl -autofix
+and
+.Fl -recursive,
+to fix only a single kind of warning in a large number of files.
 .It Fl q Ns | Ns Fl -quiet
 Don't print the errors and warnings summary before terminating.
 .It Fl r Ns | Ns Fl -recursive
@@ -71,6 +73,13 @@ source code along with the diagnostics.
 This is especially useful together with the
 .Fl f Ns | Ns Fl -show-autofix
 option.
+.It Fl V Ns | Ns Fl -version
+Print the current
+.Nm
+version number and exit.
+.It Fl W{[no-]warn,...}
+Enable or disable specific warnings.
+For a list of warnings, see below.
 .El
 .\" =======================================================================
 .Ss Checks
@@ -176,6 +185,7 @@ errors.
 .Sh AUTHORS
 .An Roland Illig Aq Mt rillig%NetBSD.org@localhost
 .Sh BUGS
-If you don't understand the messages, feel free to ask on the
-.Aq tech-pkg%NetBSD.org@localhost
+If you don't understand the messages, feel free to ask the author or
+on the
+.Aq pkgsrc-users%pkgsrc.org@localhost
 mailing list.

Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.23 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.24
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.23       Sun Jan  7 01:13:21 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sat Jan 13 23:56:14 2018
@@ -2,7 +2,6 @@ package main
 
 import (
        "fmt"
-       "io"
        "netbsd.org/pkglint/getopt"
        "netbsd.org/pkglint/histogram"
        "netbsd.org/pkglint/regex"
@@ -19,7 +18,7 @@ const confMake = "@BMAKE@"
 const confVersion = "@VERSION@"
 
 func main() {
-       G.logOut, G.logErr, trace.Out = os.Stdout, os.Stderr, os.Stdout
+       G.logOut, G.logErr, trace.Out = NewSeparatorWriter(os.Stdout), NewSeparatorWriter(os.Stderr), os.Stdout
        os.Exit(new(Pkglint).Main(os.Args...))
 }
 
@@ -51,7 +50,8 @@ func (pkglint *Pkglint) Main(args ...str
                regex.Profiling = true
                G.loghisto = histogram.New()
                defer func() {
-                       G.loghisto.PrintStats("loghisto", G.logOut, 0)
+                       G.logOut.Write("")
+                       G.loghisto.PrintStats("loghisto", G.logOut.out, 0)
                        regex.PrintStats()
                }()
        }
@@ -99,6 +99,7 @@ func (pkglint *Pkglint) ParseCommandLine
        opts.AddFlagVar('I', "dumpmakefile", &gopts.DumpMakefile, false, "dump the Makefile after parsing")
        opts.AddFlagVar('i', "import", &gopts.Import, false, "prepare the import of a wip package")
        opts.AddFlagVar('m', "log-verbose", &gopts.LogVerbose, false, "allow the same log message more than once")
+       opts.AddStrList('o', "only", &gopts.LogOnly, "only log messages containing the given text")
        opts.AddFlagVar('p', "profiling", &gopts.Profiling, false, "profile the executing program")
        opts.AddFlagVar('q', "quiet", &gopts.Quiet, false, "don't print a summary line when finishing")
        opts.AddFlagVar('r', "recursive", &gopts.Recursive, false, "check subdirectories, too")
@@ -133,21 +134,21 @@ func (pkglint *Pkglint) ParseCommandLine
 
        remainingArgs, err := opts.Parse(args)
        if err != nil {
-               fmt.Fprintf(G.logErr, "%s\n\n", err)
-               opts.Help(G.logErr, "pkglint [options] dir...")
+               fmt.Fprintf(G.logErr.out, "%s\n\n", err)
+               opts.Help(G.logErr.out, "pkglint [options] dir...")
                exitcode := 1
                return &exitcode
        }
        gopts.args = remainingArgs
 
        if gopts.PrintHelp {
-               opts.Help(G.logOut, "pkglint [options] dir...")
+               opts.Help(G.logOut.out, "pkglint [options] dir...")
                exitcode := 0
                return &exitcode
        }
 
        if G.opts.PrintVersion {
-               fmt.Fprintf(G.logOut, "%s\n", confVersion)
+               fmt.Fprintf(G.logOut.out, "%s\n", confVersion)
                exitcode := 0
                return &exitcode
        }
@@ -158,20 +159,20 @@ func (pkglint *Pkglint) ParseCommandLine
 func (pkglint *Pkglint) PrintSummary() {
        if !G.opts.Quiet {
                if G.errors != 0 || G.warnings != 0 {
-                       fmt.Fprintf(G.logOut, "%d %s and %d %s found.\n",
+                       G.logOut.Printf("%d %s and %d %s found.\n",
                                G.errors, ifelseStr(G.errors == 1, "error", "errors"),
                                G.warnings, ifelseStr(G.warnings == 1, "warning", "warnings"))
                } else {
-                       io.WriteString(G.logOut, "Looks fine.\n")
+                       G.logOut.WriteLine("Looks fine.")
                }
                if G.explanationsAvailable && !G.opts.Explain {
-                       fmt.Fprint(G.logOut, "(Run \"pkglint -e\" to show explanations.)\n")
+                       G.logOut.WriteLine("(Run \"pkglint -e\" to show explanations.)")
                }
                if G.autofixAvailable && !G.opts.PrintAutofix && !G.opts.Autofix {
-                       fmt.Fprint(G.logOut, "(Run \"pkglint -fs\" to show what can be fixed automatically.)\n")
+                       G.logOut.WriteLine("(Run \"pkglint -fs\" to show what can be fixed automatically.)")
                }
                if G.autofixAvailable && !G.opts.Autofix {
-                       fmt.Fprint(G.logOut, "(Run \"pkglint -F\" to automatically fix some issues.)\n")
+                       G.logOut.WriteLine("(Run \"pkglint -F\" to automatically fix some issues.)")
                }
        }
 }

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.19 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.20
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.19     Mon Jan  1 18:04:15 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sat Jan 13 23:56:14 2018
@@ -3,7 +3,7 @@ package main
 import (
        "fmt"
 
-       check "gopkg.in/check.v1"
+       "gopkg.in/check.v1"
 )
 
 func (s *Suite) Test_VartypeCheck_AwkCommand(c *check.C) {
@@ -535,7 +535,7 @@ func runVartypeChecks(varname string, op
                panic("runVartypeChecks needs an assignment operator")
        }
        for i, value := range values {
-               mkline := NewMkLine(NewLine("fname", i+1, varname+op.String()+value, nil))
+               mkline := T.NewMkLine("fname", i+1, varname+op.String()+value)
                valueNovar := mkline.WithoutMakeVariables(mkline.Value())
                vc := &VartypeCheck{mkline, mkline.Line, mkline.Varname(), mkline.Op(), mkline.Value(), valueNovar, "", false}
                checker(vc)
@@ -545,7 +545,7 @@ func runVartypeChecks(varname string, op
 func runVartypeMatchChecks(varname string, checker func(*VartypeCheck), values ...string) {
        for i, value := range values {
                text := fmt.Sprintf(".if ${%s:M%s} == \"\"", varname, value)
-               mkline := NewMkLine(NewLine("fname", i+1, text, nil))
+               mkline := T.NewMkLine("fname", i+1, text)
                valueNovar := mkline.WithoutMakeVariables(value)
                vc := &VartypeCheck{mkline, mkline.Line, varname, opUseMatch, value, valueNovar, "", false}
                checker(vc)
@@ -554,7 +554,7 @@ func runVartypeMatchChecks(varname strin
 
 func runVartypeChecksFname(fname, varname string, op MkOperator, checker func(*VartypeCheck), values ...string) {
        for i, value := range values {
-               mkline := NewMkLine(NewLine(fname, i+1, varname+op.String()+value, nil))
+               mkline := T.NewMkLine(fname, i+1, varname+op.String()+value)
                valueNovar := mkline.WithoutMakeVariables(value)
                vc := &VartypeCheck{mkline, mkline.Line, varname, op, value, valueNovar, "", false}
                checker(vc)

Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt.go
diff -u pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.2 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.3
--- pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.2  Sun Oct  8 22:31:13 2017
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt.go      Sat Jan 13 23:56:14 2018
@@ -31,6 +31,12 @@ func (o *Options) AddFlagVar(shortName r
        o.options = append(o.options, opt)
 }
 
+func (o *Options) AddStrList(shortName rune, longName string, plist *[]string, description string) {
+       *plist = []string{}
+       opt := &option{shortName, longName, "", description, plist}
+       o.options = append(o.options, opt)
+}
+
 func (o *Options) Parse(args []string) (remainingArgs []string, err error) {
        var skip int
        for i := 1; i < len(args) && err == nil; i++ {
@@ -101,6 +107,16 @@ func (o *Options) handleLongOption(args 
                        }
                }
                return 0, nil
+       case *[]string:
+               if argval != nil {
+                       *data = append(*data, *argval)
+                       return 0, nil
+               } else if i+1 < len(args) {
+                       *data = append(*data, args[i+1])
+                       return 1, nil
+               } else {
+                       return 0, optErr("option requires an argument: --" + opt.longName)
+               }
        case *FlagGroup:
                if argval == nil {
                        return 1, data.parse("--"+opt.longName+"=", args[i+1])
@@ -120,6 +136,19 @@ optchar:
                                case *bool:
                                        *data = true
                                        continue optchar
+
+                               case *[]string:
+                                       argarg := optchars[ai+utf8.RuneLen(optchar):]
+                                       if argarg != "" {
+                                               *data = append(*data, argarg)
+                                               return 0, nil
+                                       } else if i+1 < len(args) {
+                                               *data = append(*data, argarg)
+                                               return 1, nil
+                                       } else {
+                                               return 0, optErr("option requires an argument: -" + string([]rune{optchar}))
+                                       }
+
                                case *FlagGroup:
                                        argarg := optchars[ai+utf8.RuneLen(optchar):]
                                        if argarg != "" {
Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go
diff -u pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.2 pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.3
--- pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.2     Tue Jan 17 22:37:27 2017
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go Sat Jan 13 23:56:14 2018
@@ -104,3 +104,34 @@ func (s *Suite) Test_Options_Parse_mixed
        c.Check(aflag, check.Equals, true)
        c.Check(bflag, check.Equals, false)
 }
+
+func (s *Suite) Test_Options_Parse_string_list(c *check.C) {
+       opts := NewOptions()
+       var verbose bool
+       var includes []string
+       var excludes []string
+       opts.AddStrList('e', "exclude", &excludes, "")
+       opts.AddStrList('i', "include", &includes, "")
+       opts.AddFlagVar('v', "verbose", &verbose, false, "")
+
+       args, err := opts.Parse([]string{"progname",
+               "-viincluded1",
+               "--include=included2",
+               "--include", "included3",
+               "-eexcluded1",
+               "--exclude=excluded2",
+               "--exclude", "excluded3"})
+
+       c.Check(args, check.IsNil)
+       c.Check(err, check.IsNil)
+       c.Check(includes, check.DeepEquals, []string{"included1", "included2", "included3"})
+       c.Check(excludes, check.DeepEquals, []string{"excluded1", "excluded2", "excluded3"})
+
+       args, err = opts.Parse([]string{"progname", "-i"})
+
+       c.Check(err.Error(), check.Equals, "progname: option requires an argument: -i")
+
+       args, err = opts.Parse([]string{"progname", "--include"})
+
+       c.Check(err.Error(), check.Equals, "progname: option requires an argument: --include")
+}

Added files:

Index: pkgsrc/pkgtools/pkglint/files/autofix.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/autofix.go:1.1
--- /dev/null   Sat Jan 13 23:56:15 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix.go    Sat Jan 13 23:56:14 2018
@@ -0,0 +1,238 @@
+package main
+
+import (
+       "fmt"
+       "io/ioutil"
+       "netbsd.org/pkglint/regex"
+       "netbsd.org/pkglint/trace"
+       "os"
+       "strings"
+)
+
+// Autofix handles all modifications to a single line,
+// describes them in a human-readable form and formats the output.
+// The modifications are kept in memory only,
+// until they are written to disk by SaveAutofixChanges.
+type Autofix struct {
+       line        Line
+       linesBefore []string      // Newly inserted lines, including \n
+       lines       []*RawLine    // Original lines, available for diff
+       linesAfter  []string      // Newly inserted lines, including \n
+       modified    bool          // Modified in memory, but not necessarily written back to disk
+       descrFormat string        // Human-readable description of the latest modification
+       descrArgs   []interface{} //
+       level       *LogLevel     //
+       diagFormat  string        // Is printed only if it couldn't be fixed automatically
+       diagArgs    []interface{} //
+       explanation []string      // Is printed together with the diagnostic
+}
+
+func NewAutofix(line Line) *Autofix {
+       return &Autofix{
+               line:  line,
+               lines: append([]*RawLine{}, line.raw...)}
+}
+
+func (fix *Autofix) Replace(from string, to string) {
+       if fix.skip() {
+               return
+       }
+
+       for _, rawLine := range fix.lines {
+               if rawLine.Lineno != 0 {
+                       if replaced := strings.Replace(rawLine.textnl, from, to, 1); replaced != rawLine.textnl {
+                               if G.opts.PrintAutofix || G.opts.Autofix {
+                                       rawLine.textnl = replaced
+                               }
+                               fix.Describef("Replacing %q with %q.", from, to)
+                       }
+               }
+       }
+}
+
+func (fix *Autofix) ReplaceRegex(from regex.Pattern, to string) {
+       if fix.skip() {
+               return
+       }
+
+       for _, rawLine := range fix.lines {
+               if rawLine.Lineno != 0 {
+                       if replaced := regex.Compile(from).ReplaceAllString(rawLine.textnl, to); replaced != rawLine.textnl {
+                               if G.opts.PrintAutofix || G.opts.Autofix {
+                                       rawLine.textnl = replaced
+                               }
+                               fix.Describef("Replacing regular expression %q with %q.", from, to)
+                       }
+               }
+       }
+}
+
+func (fix *Autofix) InsertBefore(text string) {
+       if fix.skip() {
+               return
+       }
+
+       fix.linesBefore = append(fix.linesBefore, text+"\n")
+       fix.Describef("Inserting a line %q before this line.", text)
+}
+
+func (fix *Autofix) InsertAfter(text string) {
+       if fix.skip() {
+               return
+       }
+
+       fix.linesAfter = append(fix.linesAfter, text+"\n")
+       fix.Describef("Inserting a line %q after this line.", text)
+}
+
+func (fix *Autofix) Delete() {
+       if fix.skip() {
+               return
+       }
+
+       for _, line := range fix.lines {
+               line.textnl = ""
+       }
+       fix.Describef("Deleting this line.")
+}
+
+func (fix *Autofix) Describef(format string, args ...interface{}) {
+       fix.descrFormat = format
+       fix.descrArgs = args
+}
+
+func (fix *Autofix) Notef(format string, args ...interface{}) {
+       fix.level = llNote
+       fix.diagFormat = format
+       fix.diagArgs = args
+}
+
+func (fix *Autofix) Warnf(format string, args ...interface{}) {
+       fix.level = llWarn
+       fix.diagFormat = format
+       fix.diagArgs = args
+}
+
+func (fix *Autofix) Errorf(format string, args ...interface{}) {
+       fix.level = llError
+       fix.diagFormat = format
+       fix.diagArgs = args
+}
+
+func (fix *Autofix) Explain(explanation ...string) {
+       fix.explanation = explanation
+}
+
+// Depending on the pkglint mode, either:
+//
+// * logs the associated message (default)
+// * logs what would be fixed (--show-autofix)
+// * records the fixes in the line (--autofix)
+func (fix *Autofix) Apply() {
+       line := fix.line
+       if line.firstLine < 1 {
+               return
+       }
+
+       if shallBeLogged(fix.diagFormat) && fix.descrFormat != "" {
+               logDiagnostic := fix.level != nil && fix.diagFormat != "Silent-Magic-Diagnostic" && !G.opts.Autofix
+               if logDiagnostic {
+                       msg := fmt.Sprintf(fix.diagFormat, fix.diagArgs...)
+                       logs(fix.level, line.Filename, line.Linenos(), fix.diagFormat, msg)
+               }
+
+               logRepair := G.opts.Autofix || G.opts.PrintAutofix
+               if logRepair {
+                       msg := fmt.Sprintf(fix.descrFormat, fix.descrArgs...)
+                       logs(llAutofix, line.Filename, line.Linenos(), "", msg)
+               }
+
+               if logDiagnostic || logRepair {
+                       line.printSource(G.logOut)
+                       if G.opts.Explain && logDiagnostic && len(fix.explanation) != 0 {
+                               Explain(fix.explanation...)
+                       } else if G.opts.PrintSource {
+                               G.logOut.Separate()
+                       }
+               }
+       }
+
+       fix.modified = fix.modified || fix.descrFormat != ""
+
+       fix.descrFormat = ""
+       fix.descrArgs = nil
+       fix.level = nil
+       fix.diagFormat = ""
+       fix.diagArgs = nil
+       fix.explanation = nil
+}
+
+func (fix *Autofix) skip() bool {
+       if fix.diagFormat == "" {
+               panic("Autofix: The diagnostic must be given before the action.")
+       }
+       return !shallBeLogged(fix.diagFormat)
+}
+
+// SaveAutofixChanges writes the given lines back into their files,
+// applying the autofix changes.
+// The lines may come from different files.
+// Only files that actually have changed lines are saved.
+func SaveAutofixChanges(lines []Line) (autofixed bool) {
+       if trace.Tracing {
+               defer trace.Call0()()
+       }
+
+       if !G.opts.Autofix {
+               for _, line := range lines {
+                       if line.autofix != nil && line.autofix.modified {
+                               G.autofixAvailable = true
+                       }
+               }
+               return
+       }
+
+       changes := make(map[string][]string)
+       changed := make(map[string]bool)
+       for _, line := range lines {
+               chlines := changes[line.Filename]
+               if fix := line.autofix; fix != nil {
+                       if fix.modified {
+                               changed[line.Filename] = true
+                       }
+                       chlines = append(chlines, fix.linesBefore...)
+                       for _, raw := range line.raw {
+                               chlines = append(chlines, raw.textnl)
+                       }
+                       chlines = append(chlines, fix.linesAfter...)
+               } else {
+                       for _, raw := range line.raw {
+                               chlines = append(chlines, raw.textnl)
+                       }
+               }
+               changes[line.Filename] = chlines
+       }
+
+       for fname := range changed {
+               changedLines := changes[fname]
+               tmpname := fname + ".pkglint.tmp"
+               text := ""
+               for _, changedLine := range changedLines {
+                       text += changedLine
+               }
+               err := ioutil.WriteFile(tmpname, []byte(text), 0666)
+               if err != nil {
+                       NewLineWhole(tmpname).Errorf("Cannot write.")
+                       continue
+               }
+               err = os.Rename(tmpname, fname)
+               if err != nil {
+                       NewLineWhole(fname).Errorf("Cannot overwrite with auto-fixed content.")
+                       continue
+               }
+               msg := "Has been auto-fixed. Please re-run pkglint."
+               logs(llAutofix, fname, "", msg, msg)
+               autofixed = true
+       }
+       return
+}
Index: pkgsrc/pkgtools/pkglint/files/autofix_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.1
--- /dev/null   Sat Jan 13 23:56:15 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix_test.go       Sat Jan 13 23:56:14 2018
@@ -0,0 +1,338 @@
+package main
+
+import "gopkg.in/check.v1"
+
+func (s *Suite) Test_Autofix_ReplaceRegex(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix")
+       fname := s.CreateTmpFile("Makefile", ""+
+               "line1\n"+
+               "line2\n"+
+               "line3\n")
+       lines := LoadExistingLines(fname, true)
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Something's wrong here.")
+       fix.ReplaceRegex(`.`, "X")
+       fix.Apply()
+       SaveAutofixChanges(lines)
+
+       c.Check(lines[1].raw[0].textnl, equals, "XXXXX\n")
+       c.Check(s.LoadTmpFile("Makefile"), equals, "line1\nline2\nline3\n")
+       s.CheckOutputLines(
+               "WARN: ~/Makefile:2: Something's wrong here.",
+               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".")
+}
+
+func (s *Suite) Test_Autofix_ReplaceRegex_with_autofix(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--autofix", "--source")
+       fname := s.CreateTmpFile("Makefile", ""+
+               "line1\n"+
+               "line2\n"+
+               "line3\n")
+       lines := LoadExistingLines(fname, true)
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Something's wrong here.")
+       fix.ReplaceRegex(`.`, "X")
+       fix.Apply()
+
+       fix.Warnf("Use Y instead of X.")
+       fix.Replace("X", "Y")
+       fix.Apply()
+
+       SaveAutofixChanges(lines)
+
+       c.Check(s.LoadTmpFile("Makefile"), equals, ""+
+               "line1\n"+
+               "YXXXX\n"+
+               "line3\n")
+       s.CheckOutputLines(
+               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
+               "- line2",
+               "+ XXXXX",
+               "",
+               "AUTOFIX: ~/Makefile:2: Replacing \"X\" with \"Y\".",
+               "- line2",
+               "+ YXXXX",
+               "",
+               "AUTOFIX: ~/Makefile: Has been auto-fixed. Please re-run pkglint.")
+}
+
+func (s *Suite) Test_Autofix_ReplaceRegex_with_show_autofix(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix", "--source")
+       fname := s.CreateTmpFile("Makefile", ""+
+               "line1\n"+
+               "line2\n"+
+               "line3\n")
+       lines := LoadExistingLines(fname, true)
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Something's wrong here.")
+       fix.ReplaceRegex(`.`, "X")
+       fix.Apply()
+
+       fix.Warnf("Use Y instead of X.")
+       fix.Replace("X", "Y")
+       fix.Apply()
+
+       SaveAutofixChanges(lines)
+
+       s.CheckOutputLines(
+               "WARN: ~/Makefile:2: Something's wrong here.",
+               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
+               "- line2",
+               "+ XXXXX",
+               "",
+               "WARN: ~/Makefile:2: Use Y instead of X.",
+               "AUTOFIX: ~/Makefile:2: Replacing \"X\" with \"Y\".",
+               "- line2",
+               "+ YXXXX")
+}
+
+func (s *Suite) Test_autofix_MkLines(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--autofix")
+       fname := s.CreateTmpFile("Makefile", ""+
+               "line1 := value1\n"+
+               "line2 := value2\n"+
+               "line3 := value3\n")
+       pkg := NewPackage("category/basename")
+       G.Pkg = pkg
+       mklines := pkg.loadPackageMakefile(fname)
+       G.Pkg = nil
+
+       fix := mklines.mklines[1].Autofix()
+       fix.Warnf("Something's wrong here.")
+       fix.ReplaceRegex(`.`, "X")
+       fix.Apply()
+       SaveAutofixChanges(mklines.lines)
+
+       c.Check(s.LoadTmpFile("Makefile"), equals, ""+
+               "line1 := value1\n"+
+               "XXXXXXXXXXXXXXX\n"+
+               "line3 := value3\n")
+       s.CheckOutputLines(
+               "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
+               "AUTOFIX: ~/Makefile: Has been auto-fixed. Please re-run pkglint.")
+}
+
+func (s *Suite) Test_Autofix_multiple_modifications(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix", "--explain")
+
+       line := NewLine("fname", 1, "dummy", T.NewRawLines(1, "original\n"))
+
+       c.Check(line.autofix, check.IsNil)
+       c.Check(line.raw, check.DeepEquals, T.NewRawLines(1, "original\n"))
+
+       {
+               fix := line.Autofix()
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.ReplaceRegex(`(.)(.*)(.)`, "$3$2$1")
+               fix.Apply()
+       }
+
+       c.Check(line.autofix, check.NotNil)
+       c.Check(line.raw, check.DeepEquals, T.NewRawLines(1, "original\n", "lriginao\n"))
+       s.CheckOutputLines(
+               "AUTOFIX: fname:1: Replacing regular expression \"(.)(.*)(.)\" with \"$3$2$1\".")
+
+       {
+               fix := line.Autofix()
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.Replace("i", "u")
+               fix.Apply()
+       }
+
+       c.Check(line.autofix, check.NotNil)
+       c.Check(line.raw, check.DeepEquals, T.NewRawLines(1, "original\n", "lruginao\n"))
+       c.Check(line.raw[0].textnl, equals, "lruginao\n")
+       s.CheckOutputLines(
+               "AUTOFIX: fname:1: Replacing \"i\" with \"u\".")
+
+       {
+               fix := line.Autofix()
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.Replace("lruginao", "middle")
+               fix.Apply()
+       }
+
+       c.Check(line.autofix, check.NotNil)
+       c.Check(line.raw, check.DeepEquals, T.NewRawLines(1, "original\n", "middle\n"))
+       c.Check(line.raw[0].textnl, equals, "middle\n")
+       s.CheckOutputLines(
+               "AUTOFIX: fname:1: Replacing \"lruginao\" with \"middle\".")
+
+       {
+               fix := line.Autofix()
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.InsertBefore("before")
+               fix.Apply()
+
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.InsertBefore("between before and middle")
+               fix.Apply()
+
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.InsertAfter("between middle and after")
+               fix.Apply()
+
+               fix.Notef("This diagnostic is necessary for the following explanation.")
+               fix.Explain(
+                       "When inserting multiple lines, Apply must be called in-between.",
+                       "Otherwise the changes are not described to the human reader.")
+               fix.InsertAfter("after")
+               fix.Apply()
+       }
+
+       c.Check(line.autofix.linesBefore, check.DeepEquals, []string{
+               "before\n",
+               "between before and middle\n"})
+       c.Check(line.autofix.lines[0].textnl, equals, "middle\n")
+       c.Check(line.autofix.linesAfter, deepEquals, []string{
+               "between middle and after\n",
+               "after\n"})
+       s.CheckOutputLines(
+               "AUTOFIX: fname:1: Inserting a line \"before\" before this line.",
+               "AUTOFIX: fname:1: Inserting a line \"between before and middle\" before this line.",
+               "AUTOFIX: fname:1: Inserting a line \"between middle and after\" after this line.",
+               "NOTE: fname:1: This diagnostic is necessary for the following explanation.",
+               "AUTOFIX: fname:1: Inserting a line \"after\" after this line.",
+               "",
+               "\tWhen inserting multiple lines, Apply must be called in-between.",
+               "\tOtherwise the changes are not described to the human reader.",
+               "")
+
+       {
+               fix := line.Autofix()
+               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.Delete()
+               fix.Apply()
+       }
+
+       c.Check(line.autofix.linesBefore, check.DeepEquals, []string{
+               "before\n",
+               "between before and middle\n"})
+       c.Check(line.autofix.lines[0].textnl, equals, "")
+       c.Check(line.autofix.linesAfter, deepEquals, []string{
+               "between middle and after\n",
+               "after\n"})
+       s.CheckOutputLines(
+               "AUTOFIX: fname:1: Deleting this line.")
+}
+
+func (s *Suite) Test_Autofix_show_source_code(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix", "--source")
+       line := NewLineMulti("Makefile", 27, 29, "# old", T.NewRawLines(
+               27, "before\n",
+               28, "The old song\n",
+               29, "after\n"))
+
+       {
+               fix := line.Autofix()
+               fix.Warnf("Using \"old\" is deprecated.")
+               fix.Replace("old", "new")
+               fix.Apply()
+       }
+
+       s.CheckOutputLines(
+               "WARN: Makefile:27--29: Using \"old\" is deprecated.",
+               "AUTOFIX: Makefile:27--29: Replacing \"old\" with \"new\".",
+               "> before",
+               "- The old song",
+               "+ The new song",
+               "> after")
+}
+
+func (s *Suite) Test_Autofix_InsertBefore(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix", "--source")
+       line := NewLine("Makefile", 30, "original", T.NewRawLines(30, "original\n"))
+
+       fix := line.Autofix()
+       fix.Warnf("Dummy")
+       fix.InsertBefore("inserted")
+       fix.Apply()
+
+       s.CheckOutputLines(
+               "WARN: Makefile:30: Dummy",
+               "AUTOFIX: Makefile:30: Inserting a line \"inserted\" before this line.",
+               "+ inserted",
+               "> original")
+}
+
+func (s *Suite) Test_Autofix_Delete(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix", "--source")
+       line := NewLine("Makefile", 30, "to be deleted", T.NewRawLines(30, "to be deleted\n"))
+
+       fix := line.Autofix()
+       fix.Warnf("Dummy")
+       fix.Delete()
+       fix.Apply()
+
+       s.CheckOutputLines(
+               "WARN: Makefile:30: Dummy",
+               "AUTOFIX: Makefile:30: Deleting this line.",
+               "- to be deleted")
+}
+
+// Demonstrates that the --show-autofix option only shows those diagnostics
+// that would be fixed.
+func (s *Suite) Test_Autofix_suppress_unfixable_warnings(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--show-autofix", "--source")
+       lines := T.NewLines("Makefile",
+               "line1",
+               "line2",
+               "line3")
+
+       lines[0].Warnf("This warning is not shown since it is not automatically fixed.")
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Something's wrong here.")
+       fix.ReplaceRegex(`.`, "X")
+       fix.Apply()
+
+       fix.Warnf("The XXX marks are usually not fixed, use TODO instead.")
+       fix.Replace("XXX", "TODO")
+       fix.Apply()
+
+       lines[2].Warnf("Neither is this warning shown.")
+
+       s.CheckOutputLines(
+               "WARN: Makefile:2: Something's wrong here.",
+               "AUTOFIX: Makefile:2: Replacing regular expression \".\" with \"X\".",
+               "- line2",
+               "+ XXXXX",
+               "",
+               "WARN: Makefile:2: The XXX marks are usually not fixed, use TODO instead.",
+               "AUTOFIX: Makefile:2: Replacing \"XXX\" with \"TODO\".",
+               "- line2",
+               "+ TODOXX")
+}
+
+func (s *Suite) Test_SaveAutofixChanges(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--autofix")
+       filename := s.CreateTmpFileLines("DESCR",
+               "Line 1",
+               "Line 2")
+       lines := LoadExistingLines(filename, false)
+
+       fix := lines[0].Autofix()
+       fix.Warnf("Dummy warning.")
+       fix.Replace("X", "Y")
+       fix.Apply()
+
+       // Since nothing has been effectively changed,
+       // nothing needs to be saved.
+       SaveAutofixChanges(lines)
+
+       // And therefore, no AUTOFIX action must appear in the log.
+       s.CheckOutputEmpty()
+}
Index: pkgsrc/pkgtools/pkglint/files/logging_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/logging_test.go:1.1
--- /dev/null   Sat Jan 13 23:56:15 2018
+++ pkgsrc/pkgtools/pkglint/files/logging_test.go       Sat Jan 13 23:56:14 2018
@@ -0,0 +1,140 @@
+package main
+
+import "gopkg.in/check.v1"
+
+// Since the --source option generates multi-line diagnostics,
+// they are separated by an empty line.
+//
+// The quoted source code is written below the diagnostics.
+// In the --show-autofix and --autofix modes, this order
+// is the most useful since it first states the general rule,
+// then states how to fix this instance and then shows a concrete
+// example. Understanding the general rule is considered most
+// important of these three.
+//
+// To keep the output layout consistent between all these
+// modes, the source code is written below the diagnostic
+// even in the default (check-only) mode, for consistency.
+func (s *Suite) Test_show_source_separator(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--source")
+       fileName := s.CreateTmpFileLines("DESCR",
+               "The first line",
+               "The second line",
+               "The third line")
+       lines := LoadExistingLines(fileName, true)
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Using \"second\" is deprecated.")
+       fix.Replace("second", "silver medal")
+       fix.Apply()
+
+       lines[2].Warnf("Dummy warning.")
+
+       fix = lines[2].Autofix()
+       fix.Warnf("Using \"third\" is deprecated.")
+       fix.Replace("third", "bronze medal")
+       fix.Apply()
+
+       s.CheckOutputLines(
+               "WARN: ~/DESCR:2: Using \"second\" is deprecated.",
+               "> The second line",
+               "",
+               "WARN: ~/DESCR:3: Dummy warning.",
+               "> The third line",
+               "",
+               "WARN: ~/DESCR:3: Using \"third\" is deprecated.",
+               "> The third line")
+}
+
+func (s *Suite) Test_show_source_separator_show_autofix(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--source", "--show-autofix")
+       fileName := s.CreateTmpFileLines("DESCR",
+               "The first line",
+               "The second line",
+               "The third line")
+       lines := LoadExistingLines(fileName, true)
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Using \"second\" is deprecated.")
+       fix.Replace("second", "silver medal")
+       fix.Apply()
+
+       lines[2].Warnf("Dummy warning.")
+
+       fix = lines[2].Autofix()
+       fix.Warnf("Using \"third\" is deprecated.")
+       fix.Replace("third", "bronze medal")
+       fix.Apply()
+
+       s.CheckOutputLines(
+               "WARN: ~/DESCR:2: Using \"second\" is deprecated.",
+               "AUTOFIX: ~/DESCR:2: Replacing \"second\" with \"silver medal\".",
+               "- The second line",
+               "+ The silver medal line",
+               "",
+               "WARN: ~/DESCR:3: Using \"third\" is deprecated.",
+               "AUTOFIX: ~/DESCR:3: Replacing \"third\" with \"bronze medal\".",
+               "- The third line",
+               "+ The bronze medal line")
+}
+
+func (s *Suite) Test_show_source_separator_autofix(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--source", "--autofix")
+       fileName := s.CreateTmpFileLines("DESCR",
+               "The first line",
+               "The second line",
+               "The third line")
+       lines := LoadExistingLines(fileName, true)
+
+       fix := lines[1].Autofix()
+       fix.Warnf("Using \"second\" is deprecated.")
+       fix.Replace("second", "silver medal")
+       fix.Apply()
+
+       lines[2].Warnf("Dummy warning.")
+
+       fix = lines[2].Autofix()
+       fix.Warnf("Using \"third\" is deprecated.")
+       fix.Replace("third", "bronze medal")
+       fix.Apply()
+
+       s.CheckOutputLines(
+               "AUTOFIX: ~/DESCR:2: Replacing \"second\" with \"silver medal\".",
+               "- The second line",
+               "+ The silver medal line",
+               "",
+               "AUTOFIX: ~/DESCR:3: Replacing \"third\" with \"bronze medal\".",
+               "- The third line",
+               "+ The bronze medal line")
+}
+
+// Demonstrates how to filter log messages.
+// This is useful in combination with the --autofix option,
+// to restrict the fixes to exactly one group or topic.
+func (s *Suite) Test_Line_log_only(c *check.C) {
+       s.Init(c)
+       s.UseCommandLine("--autofix", "--source", "--only", "interesting")
+       line := NewLineMulti("Makefile", 27, 29, "Dummy text", T.NewRawLines(
+               27, "before\n",
+               28, "The old song\n",
+               29, "after\n"))
+
+       fix := line.Autofix()
+       fix.Warnf("Using \"old\" is deprecated.")
+       fix.Replace("old", "new1")
+       fix.Apply()
+
+       fix.Warnf("Using \"old\" is interesting.")
+       fix.Replace("old", "new2")
+       fix.Apply()
+
+       s.CheckOutputLines(
+               "AUTOFIX: Makefile:27--29: Replacing \"old\" with \"new2\".",
+               "> before",
+               "- The old song",
+               "+ The new2 song",
+               "> after")
+}



Home | Main Index | Thread Index | Old Index