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:           Wed Nov  7 20:58:23 UTC 2018

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile PLIST
        pkgsrc/pkgtools/pkglint/files: alternatives.go alternatives_test.go
            autofix.go autofix_test.go buildlink3.go buildlink3_test.go
            category.go category_test.go check_test.go distinfo.go
            distinfo_test.go expecter.go files.go files_test.go fuzzer_test.go
            licenses.go licenses_test.go line.go line_test.go linechecker.go
            linechecker_test.go logging.go logging_test.go mkline.go
            mkline_test.go mklinechecker.go mklinechecker_test.go mklines.go
            mklines_test.go mklines_varalign_test.go mkparser.go
            mkparser_test.go mkshparser.go mkshtypes.go mktypes.go
            mktypes_test.go options.go package.go package_test.go parser.go
            patches.go patches_test.go pkglint.go pkglint_test.go pkgsrc.go
            pkgsrc_test.go plist.go plist_test.go shell.go shell_test.go
            shtokenizer.go shtokenizer_test.go substcontext.go
            substcontext_test.go testnames_test.go tools.go tools_test.go
            toplevel.go toplevel_test.go util.go util_test.go vardefs.go
            vardefs_test.go vartype.go vartype_test.go vartypecheck.go
            vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/getopt: getopt.go getopt_test.go
        pkgsrc/pkgtools/pkglint/files/histogram: histogram.go
        pkgsrc/pkgtools/pkglint/files/licenses: licenses.go licenses_test.go
        pkgsrc/pkgtools/pkglint/files/regex: regex.go
        pkgsrc/pkgtools/pkglint/files/textproc: prefixreplacer.go
        pkgsrc/pkgtools/pkglint/files/trace: tracing.go tracing_test.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: expecter_test.go lines.go
        pkgsrc/pkgtools/pkglint/files/histogram: histogram_test.go
        pkgsrc/pkgtools/pkglint/files/intqa: see.go testnames.go
        pkgsrc/pkgtools/pkglint/files/textproc: lexer.go lexer_test.go

Log Message:
pkgtools/pkglint: update to 5.6.6

Changes since 5.6.5:

- Removed plist-clash since it had crashed unconditionally whenever it
  was called. This means that in the last 3 years, nobody can have
  used it in the originally intended way.

- Fixed interactions between the --source, --explain, --show-autofix,
  --autofix and --only options.

- Fixed "defined but not used" and "used but not defined" for variables
  from the pkgsrc infrastructure.

- Lots of small fixes and improvements found by the large pkglint code
  review (12% done).


To generate a diff of this commit:
cvs rdiff -u -r1.553 -r1.554 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.6 -r1.7 pkgsrc/pkgtools/pkglint/PLIST
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/alternatives.go \
    pkgsrc/pkgtools/pkglint/files/logging_test.go \
    pkgsrc/pkgtools/pkglint/files/mktypes.go \
    pkgsrc/pkgtools/pkglint/files/vardefs_test.go
cvs rdiff -u -r1.6 -r1.7 pkgsrc/pkgtools/pkglint/files/alternatives_test.go \
    pkgsrc/pkgtools/pkglint/files/options.go \
    pkgsrc/pkgtools/pkglint/files/tools.go \
    pkgsrc/pkgtools/pkglint/files/tools_test.go
cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/autofix.go \
    pkgsrc/pkgtools/pkglint/files/autofix_test.go \
    pkgsrc/pkgtools/pkglint/files/pkgsrc.go \
    pkgsrc/pkgtools/pkglint/files/shtokenizer.go
cvs rdiff -u -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/buildlink3.go \
    pkgsrc/pkgtools/pkglint/files/category_test.go \
    pkgsrc/pkgtools/pkglint/files/expecter.go \
    pkgsrc/pkgtools/pkglint/files/line_test.go \
    pkgsrc/pkgtools/pkglint/files/toplevel.go \
    pkgsrc/pkgtools/pkglint/files/toplevel_test.go
cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go \
    pkgsrc/pkgtools/pkglint/files/vartype.go
cvs rdiff -u -r1.14 -r1.15 pkgsrc/pkgtools/pkglint/files/category.go \
    pkgsrc/pkgtools/pkglint/files/substcontext.go
cvs rdiff -u -r1.27 -r1.28 pkgsrc/pkgtools/pkglint/files/check_test.go \
    pkgsrc/pkgtools/pkglint/files/shell.go
cvs rdiff -u -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/distinfo.go \
    pkgsrc/pkgtools/pkglint/files/patches_test.go
cvs rdiff -u -r1.18 -r1.19 pkgsrc/pkgtools/pkglint/files/distinfo_test.go \
    pkgsrc/pkgtools/pkglint/files/files_test.go \
    pkgsrc/pkgtools/pkglint/files/mkparser.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/expecter_test.go \
    pkgsrc/pkgtools/pkglint/files/lines.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/files.go
cvs rdiff -u -r1.1 -r1.2 pkgsrc/pkgtools/pkglint/files/fuzzer_test.go \
    pkgsrc/pkgtools/pkglint/files/testnames_test.go
cvs rdiff -u -r1.15 -r1.16 pkgsrc/pkgtools/pkglint/files/licenses.go \
    pkgsrc/pkgtools/pkglint/files/logging.go \
    pkgsrc/pkgtools/pkglint/files/substcontext_test.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/licenses_test.go \
    pkgsrc/pkgtools/pkglint/files/mkparser_test.go \
    pkgsrc/pkgtools/pkglint/files/util_test.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/line.go \
    pkgsrc/pkgtools/pkglint/files/pkglint_test.go \
    pkgsrc/pkgtools/pkglint/files/plist_test.go
cvs rdiff -u -r1.8 -r1.9 pkgsrc/pkgtools/pkglint/files/linechecker.go \
    pkgsrc/pkgtools/pkglint/files/linechecker_test.go \
    pkgsrc/pkgtools/pkglint/files/mkshtypes.go
cvs rdiff -u -r1.39 -r1.40 pkgsrc/pkgtools/pkglint/files/mkline.go \
    pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.43 -r1.44 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.21 -r1.22 pkgsrc/pkgtools/pkglint/files/mklinechecker.go
cvs rdiff -u -r1.17 -r1.18 \
    pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
cvs rdiff -u -r1.33 -r1.34 pkgsrc/pkgtools/pkglint/files/mklines.go
cvs rdiff -u -r1.29 -r1.30 pkgsrc/pkgtools/pkglint/files/mklines_test.go
cvs rdiff -u -r1.4 -r1.5 \
    pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go \
    pkgsrc/pkgtools/pkglint/files/mktypes_test.go
cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/mkshparser.go
cvs rdiff -u -r1.37 -r1.38 pkgsrc/pkgtools/pkglint/files/package.go
cvs rdiff -u -r1.31 -r1.32 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/parser.go
cvs rdiff -u -r1.23 -r1.24 pkgsrc/pkgtools/pkglint/files/patches.go
cvs rdiff -u -r1.9 -r1.10 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go \
    pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go \
    pkgsrc/pkgtools/pkglint/files/vartype_test.go
cvs rdiff -u -r1.30 -r1.31 pkgsrc/pkgtools/pkglint/files/plist.go \
    pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r1.32 -r1.33 pkgsrc/pkgtools/pkglint/files/shell_test.go
cvs rdiff -u -r1.48 -r1.49 pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.42 -r1.43 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.34 -r1.35 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go
cvs rdiff -u -r1.7 -r1.8 pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/histogram/histogram.go
cvs rdiff -u -r0 -r1.1 \
    pkgsrc/pkgtools/pkglint/files/histogram/histogram_test.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/intqa/see.go \
    pkgsrc/pkgtools/pkglint/files/intqa/testnames.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/licenses/licenses.go
cvs rdiff -u -r1.3 -r1.4 \
    pkgsrc/pkgtools/pkglint/files/licenses/licenses_test.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/regex/regex.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/textproc/lexer.go \
    pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go
cvs rdiff -u -r1.7 -r1.8 \
    pkgsrc/pkgtools/pkglint/files/textproc/prefixreplacer.go
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/files/trace/tracing.go \
    pkgsrc/pkgtools/pkglint/files/trace/tracing_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/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.553 pkgsrc/pkgtools/pkglint/Makefile:1.554
--- pkgsrc/pkgtools/pkglint/Makefile:1.553      Sun Nov  4 18:38:05 2018
+++ pkgsrc/pkgtools/pkglint/Makefile    Wed Nov  7 20:58:22 2018
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.553 2018/11/04 18:38:05 bsiegert Exp $
+# $NetBSD: Makefile,v 1.554 2018/11/07 20:58:22 rillig Exp $
 
-PKGNAME=       pkglint-5.6.5
-PKGREVISION=   1
+PKGNAME=       pkglint-5.6.6
 DISTFILES=     # none
 CATEGORIES=    pkgtools
 
@@ -17,7 +16,6 @@ AUTO_MKDIRS=  yes
 GO_SRCPATH=    netbsd.org/pkglint
 
 CHECK_RELRO_SKIP+=     bin/pkglint
-CHECK_RELRO_SKIP+=     bin/plist-clash
 
 SUBST_CLASSES+=                pkglint
 SUBST_STAGE.pkglint=   post-configure
@@ -25,11 +23,21 @@ SUBST_FILES.pkglint+=       pkglint.go
 SUBST_SED.pkglint+=    -e s\|@VERSION@\|${PKGNAME:S/pkglint-//}\|g
 SUBST_SED.pkglint+=    -e s\|@BMAKE@\|${MAKE:T:Q}\|g
 
+TOOLS_CREATE+=         goyacc
+TOOLS_PATH.goyacc=     ${WRKDIR}/bin/goyacc
+USE_TOOLS+=            goyacc
+
 do-extract:
        ${RUN} ${MKDIR} ${WRKDIR}/pkglint
        ${RUN} cd ${FILESDIR} && ${PAX} -rw . ${WRKDIR}/pkglint
+       ${RUN} ${PKGSRC_SETENV} ${MAKE_ENV} ${GO} get golang.org/x/tools/cmd/goyacc
+       ${RUN} ${PKGSRC_SETENV} ${MAKE_ENV} ${GO} install golang.org/x/tools/cmd/goyacc
+
+pre-build:
+       ${RUN} ${PKGSRC_SETENV} ${MAKE_ENV} ${GO} generate ${GO_BUILD_PATTERN}
 
 pre-install:
+       ${RUN} rm -f ${WRKDIR}/bin/goyacc
        ${RUN} rm -rf ${WRKDIR}/pkg
 
 post-install: do-install-man

Index: pkgsrc/pkgtools/pkglint/PLIST
diff -u pkgsrc/pkgtools/pkglint/PLIST:1.6 pkgsrc/pkgtools/pkglint/PLIST:1.7
--- pkgsrc/pkgtools/pkglint/PLIST:1.6   Wed Nov 25 13:29:07 2015
+++ pkgsrc/pkgtools/pkglint/PLIST       Wed Nov  7 20:58:22 2018
@@ -1,5 +1,4 @@
-@comment $NetBSD: PLIST,v 1.6 2015/11/25 13:29:07 rillig Exp $
+@comment $NetBSD: PLIST,v 1.7 2018/11/07 20:58:22 rillig Exp $
 bin/pkglint
-bin/plist-clash
 man/cat1/pkglint.0
 man/man1/pkglint.1

Index: pkgsrc/pkgtools/pkglint/files/alternatives.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives.go:1.5 pkgsrc/pkgtools/pkglint/files/alternatives.go:1.6
--- pkgsrc/pkgtools/pkglint/files/alternatives.go:1.5   Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/alternatives.go       Wed Nov  7 20:58:22 2018
@@ -2,26 +2,31 @@ package main
 
 import "strings"
 
-func CheckfileAlternatives(filename string, plistFiles map[string]bool) {
-       lines := Load(filename, NotEmpty|LogErrors)
+func CheckfileAlternatives(fileName string) {
+       lines := Load(fileName, NotEmpty|LogErrors)
        if lines == nil {
                return
        }
 
-       for _, line := range lines {
-               if m, wrapper, space, implementation := match3(line.Text, `^(\S+)([ \t]+)(\S+)`); m {
-                       if plistFiles != nil {
-                               if plistFiles[wrapper] {
+       var plist PlistContent
+       if G.Pkg != nil {
+               plist = G.Pkg.Plist
+       }
+
+       for _, line := range lines.Lines {
+               if m, wrapper, space, alternative := match3(line.Text, `^([^\t ]+)([ \t]+)([^\t ]+)`); m {
+                       if plist.Files != nil {
+                               if plist.Files[wrapper] {
                                        line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper)
                                }
 
-                               relImplementation := strings.Replace(implementation, "@PREFIX@/", "", 1)
+                               relImplementation := strings.Replace(alternative, "@PREFIX@/", "", 1)
                                plistName := replaceAll(relImplementation, `@(\w+)@`, "${$1}")
-                               if !plistFiles[plistName] && !G.Pkg.vars.Defined("ALTERNATIVES_SRC") {
-                                       if plistName != implementation {
-                                               line.Errorf("Alternative implementation %q must appear in the PLIST as %q.", implementation, plistName)
+                               if !plist.Files[plistName] && !G.Pkg.vars.Defined("ALTERNATIVES_SRC") {
+                                       if plistName != alternative {
+                                               line.Errorf("Alternative implementation %q must appear in the PLIST as %q.", alternative, plistName)
                                        } else {
-                                               line.Errorf("Alternative implementation %q must appear in the PLIST.", implementation)
+                                               line.Errorf("Alternative implementation %q must appear in the PLIST.", alternative)
                                        }
                                }
                        }
@@ -36,7 +41,7 @@ func CheckfileAlternatives(filename stri
                } else {
                        line.Errorf("Invalid ALTERNATIVES line %q.", line.Text)
                        Explain(
-                               "Run \"" + confMake + " help topic=alternatives\" for more information.")
+                               sprintf("Run %q for more information.", makeHelp("alternatives")))
                }
        }
 }
Index: pkgsrc/pkgtools/pkglint/files/logging_test.go
diff -u pkgsrc/pkgtools/pkglint/files/logging_test.go:1.5 pkgsrc/pkgtools/pkglint/files/logging_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/logging_test.go:1.5   Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/logging_test.go       Wed Nov  7 20:58:23 2018
@@ -5,16 +5,16 @@ 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.
+// Whether the quoted source code is written above or below the
+// diagnostics depends on the --show-autofix and --autofix options.
+// When any of them is given, the general rule is given first, followed
+// by a description of the fix ("replacing A with B"), finally followed
+// by the actual changes to the code.
 //
-// To keep the output layout consistent between all these
-// modes, the source code is written below the diagnostic
-// also in the default (check-only) mode.
+// In default mode, without any autofix options, the usual order is
+// to first show the code and then show the diagnostic. This allows
+// the diagnostics to underline the relevant part of the source code
+// and reminds of the squiggly line used for spellchecking.
 func (s *Suite) Test__show_source_separator(c *check.C) {
        t := s.Init(c)
 
@@ -24,27 +24,27 @@ func (s *Suite) Test__show_source_separa
                "The second line",
                "The third line")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Using \"second\" is deprecated.")
        fix.Replace("second", "silver medal")
        fix.Apply()
 
-       lines[2].Warnf("Dummy warning.")
+       lines.Lines[2].Warnf("Dummy warning.")
 
-       fix = lines[2].Autofix()
+       fix = lines.Lines[2].Autofix()
        fix.Warnf("Using \"third\" is deprecated.")
        fix.Replace("third", "bronze medal")
        fix.Apply()
 
        t.CheckOutputLines(
-               "WARN: ~/DESCR:2: Using \"second\" is deprecated.",
                ">\tThe second line",
+               "WARN: ~/DESCR:2: Using \"second\" is deprecated.",
                "",
-               "WARN: ~/DESCR:3: Dummy warning.",
                ">\tThe third line",
+               "WARN: ~/DESCR:3: Dummy warning.",
                "",
-               "WARN: ~/DESCR:3: Using \"third\" is deprecated.",
-               ">\tThe third line")
+               ">\tThe third line",
+               "WARN: ~/DESCR:3: Using \"third\" is deprecated.")
 }
 
 func (s *Suite) Test__show_source_separator_show_autofix(c *check.C) {
@@ -56,14 +56,14 @@ func (s *Suite) Test__show_source_separa
                "The second line",
                "The third line")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Using \"second\" is deprecated.")
        fix.Replace("second", "silver medal")
        fix.Apply()
 
-       lines[2].Warnf("Dummy warning.")
+       lines.Lines[2].Warnf("Dummy warning.")
 
-       fix = lines[2].Autofix()
+       fix = lines.Lines[2].Autofix()
        fix.Warnf("Using \"third\" is deprecated.")
        fix.Replace("third", "bronze medal")
        fix.Apply()
@@ -89,14 +89,14 @@ func (s *Suite) Test__show_source_separa
                "The second line",
                "The third line")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Using \"second\" is deprecated.")
        fix.Replace("second", "silver medal")
        fix.Apply()
 
-       lines[2].Warnf("Dummy warning.")
+       lines.Lines[2].Warnf("Dummy warning.")
 
-       fix = lines[2].Autofix()
+       fix = lines.Lines[2].Autofix()
        fix.Warnf("Using \"third\" is deprecated.")
        fix.Replace("third", "bronze medal")
        fix.Apply()
@@ -111,56 +111,6 @@ func (s *Suite) Test__show_source_separa
                "+\tThe 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) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("--autofix", "--source", "--only", "interesting")
-       line := t.NewLine("Makefile", 27, "The old song")
-
-       // Is completely ignored, including any autofixes.
-       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()
-
-       t.CheckOutputLines(
-               "AUTOFIX: Makefile:27: Replacing \"old\" with \"new2\".",
-               "-\tThe old song",
-               "+\tThe new2 song")
-}
-
-func (s *Suite) Test_Pkglint_PrintSummary__explanations_with_only(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("--only", "interesting")
-       line := t.NewLine("Makefile", 27, "The old song")
-
-       line.Warnf("Filtered warning.")               // Is not logged.
-       Explain("Explanation for the above warning.") // Neither would this explanation be logged.
-       G.PrintSummary()
-
-       c.Check(G.explanationsAvailable, equals, false)
-       t.CheckOutputLines(
-               "Looks fine.") // "pkglint -e" is not advertised since the above explanation is not relevant.
-
-       line.Warnf("What an interesting line.")
-       Explain("This explanation is available.")
-       G.PrintSummary()
-
-       c.Check(G.explanationsAvailable, equals, true)
-       t.CheckOutputLines(
-               "WARN: Makefile:27: What an interesting line.",
-               "0 errors and 1 warning found.",
-               "(Run \"pkglint -e\" to show explanations.)")
-}
-
 func (s *Suite) Test_Explain__only(c *check.C) {
        t := s.Init(c)
 
@@ -180,11 +130,11 @@ func (s *Suite) Test_Explain__only(c *ch
                "")
 }
 
-func (s *Suite) Test_logs__duplicate_messages(c *check.C) {
+func (s *Suite) Test_logf__duplicate_messages(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--explain")
-       G.opts.LogVerbose = false
+       G.Opts.LogVerbose = false
        line := t.NewLine("README.txt", 123, "text")
 
        // In rare cases, the explanations for the same warning may differ
@@ -202,7 +152,7 @@ func (s *Suite) Test_logs__duplicate_mes
                "")
 }
 
-func (s *Suite) Test_logs__duplicate_explanations(c *check.C) {
+func (s *Suite) Test_logf__duplicate_explanations(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--explain")
@@ -222,10 +172,31 @@ func (s *Suite) Test_logs__duplicate_exp
                "WARN: README.txt:123: Warning 2.")
 }
 
-func (s *Suite) Test_logs__panic(c *check.C) {
-       c.Check(func() {
-               logs(llError, "filename", "13", "No period", "No period")
-       }, check.Panics, "Diagnostic format \"No period\" must end in a period.")
+// Even if verbose logging is disabled, the "Replacing" diagnostics
+// must not be filtered for duplicates since each of them modifies the line.
+func (s *Suite) Test_logf__duplicate_autofix(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--explain", "--autofix")
+       G.Opts.LogVerbose = false // See SetUpTest
+       line := t.NewLine("README.txt", 123, "text")
+
+       fix := line.Autofix()
+       fix.Warnf("T should always be uppercase.")
+       fix.ReplaceRegex(`t`, "T", -1)
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "AUTOFIX: README.txt:123: Replacing \"t\" with \"T\".",
+               "AUTOFIX: README.txt:123: Replacing \"t\" with \"T\".")
+}
+
+func (s *Suite) Test_logf__panic(c *check.C) {
+       t := s.Init(c)
+
+       t.ExpectPanic(
+               func() { logf(Error, "fileName", "13", "No period", "No period") },
+               "Pkglint internal error: Diagnostic format \"No period\" must end in a period.")
 }
 
 func (s *Suite) Test_Explain__long_lines(c *check.C) {
Index: pkgsrc/pkgtools/pkglint/files/mktypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes.go:1.5 pkgsrc/pkgtools/pkglint/files/mktypes.go:1.6
--- pkgsrc/pkgtools/pkglint/files/mktypes.go:1.5        Sat Apr 28 23:32:52 2018
+++ pkgsrc/pkgtools/pkglint/files/mktypes.go    Wed Nov  7 20:58:23 2018
@@ -1,5 +1,10 @@
 package main
 
+import (
+       "netbsd.org/pkglint/textproc"
+       "unicode"
+)
+
 // MkToken represents a contiguous string from a Makefile.
 // It is either a literal string or a variable use.
 //
@@ -19,29 +24,114 @@ type MkToken struct {
 //
 // Example: ${PKGNAME:S/from/to/}
 type MkVarUse struct {
-       varname   string   // E.g. "PKGNAME", or "${BUILD_DEFS}"
-       modifiers []string // E.g. "Q", "S/from/to/"
+       varname   string             // E.g. "PKGNAME", or "${BUILD_DEFS}"
+       modifiers []MkVarUseModifier // E.g. "Q", "S/from/to/"
+}
+
+type MkVarUseModifier struct {
+       Text string
+}
+
+func (m MkVarUseModifier) IsQ() bool { return m.Text == "Q" }
+
+func (m MkVarUseModifier) IsSuffixSubst() bool {
+       // XXX: There are other cases
+       return hasPrefix(m.Text, "=")
 }
 
+func (m MkVarUseModifier) MatchSubst() (ok bool, regex bool, from string, to string, options string) {
+       l := textproc.NewLexer(m.Text)
+       regex = l.PeekByte() == 'C'
+       if l.NextByte('S') || l.NextByte('C') {
+               separator := l.PeekByte()
+               l.Skip(1)
+               if unicode.IsPunct(rune(separator)) || separator == '|' {
+                       noSeparator := func(b byte) bool { return int(b) != separator && b != '\\' }
+                       nextToken := func() string {
+                               start := l.Mark()
+                               for {
+                                       switch {
+                                       case l.NextBytesFunc(noSeparator) != "":
+                                               continue
+                                       case l.PeekByte() == '\\' && len(l.Rest()) >= 2:
+                                               // TODO: Compare with devel/bmake for the exact behavior
+                                               l.Skip(2)
+                                       default:
+                                               return l.Since(start)
+                                       }
+                               }
+                       }
+
+                       from = nextToken()
+                       if from != "" && l.NextByte(byte(separator)) {
+                               to = nextToken()
+                               if l.NextByte(byte(separator)) {
+                                       options = l.NextBytesFunc(func(b byte) bool {
+                                               return b == '1' || b == 'g' || b == 'W'
+                                       })
+                                       ok = l.EOF()
+                                       return
+                               }
+                       }
+               }
+       }
+       return
+}
+
+// Subst evaluates an S/from/to/ modifier.
+//
+// Example:
+//  MkVarUseModifier{"S,name,file,g"}.Subst("distname-1.0") => "distfile-1.0"
+func (m MkVarUseModifier) Subst(str string) string {
+       // XXX: The call to MatchSubst is usually redundant because MatchSubst
+       // is typically called directly before calling Subst.
+       ok, regex, from, to, options := m.MatchSubst()
+       G.Assertf(ok && !regex, "Subst must only be called after MatchSubst.")
+       leftAnchor := hasPrefix(from, "^")
+       if leftAnchor {
+               from = from[1:]
+       }
+       rightAnchor := hasSuffix(from, "$")
+       if rightAnchor {
+               from = from[:len(from)-1]
+       }
+
+       result := mkopSubst(str, leftAnchor, from, rightAnchor, to, options)
+       if trace.Tracing && result != str {
+               trace.Stepf("Subst: %q %q => %q", str, m.Text, result)
+       }
+       return result
+}
+
+func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string) {
+       if hasPrefix(m.Text, "M") || hasPrefix(m.Text, "N") {
+               return true, m.Text[0] == 'M', m.Text[1:]
+       }
+       return false, false, ""
+}
+
+func (m MkVarUseModifier) IsToLower() bool { return m.Text == "tl" }
+
 func (vu *MkVarUse) Mod() string {
        mod := ""
        for _, modifier := range vu.modifiers {
-               mod += ":" + modifier
+               mod += ":" + modifier.Text
        }
        return mod
 }
 
-// Whether the varname is interpreted as a variable name (the usual case)
-// or as a full expression (rare).
+// IsExpression returns whether the varname is interpreted as a variable
+// name (the usual case) or as a full expression (rare, only the modifiers
+// "?:" and "L" do this).
 func (vu *MkVarUse) IsExpression() bool {
        if len(vu.modifiers) == 0 {
                return false
        }
        mod := vu.modifiers[0]
-       return mod == "L" || hasPrefix(mod, "?")
+       return mod.Text == "L" || hasPrefix(mod.Text, "?")
 }
 
 func (vu *MkVarUse) IsQ() bool {
        mlen := len(vu.modifiers)
-       return mlen > 0 && vu.modifiers[mlen-1] == "Q"
+       return mlen > 0 && vu.modifiers[mlen-1].IsQ()
 }
Index: pkgsrc/pkgtools/pkglint/files/vardefs_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.5 pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.5   Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/vardefs_test.go       Wed Nov  7 20:58:23 2018
@@ -2,6 +2,16 @@ package main
 
 import "gopkg.in/check.v1"
 
+func (s *Suite) Test_Pkgsrc_InitVartypes(c *check.C) {
+       t := s.Init(c)
+
+       src := NewPkgsrc(t.File("."))
+       src.InitVartypes()
+
+       c.Check(src.vartypes["BSD_MAKE_ENV"].basicType.name, equals, "ShellWord")
+       c.Check(src.vartypes["USE_BUILTIN.*"].basicType.name, equals, "YesNoIndirectly")
+}
+
 func (s *Suite) Test_Pkgsrc_InitVartypes__enumFrom(c *check.C) {
        t := s.Init(c)
 
@@ -21,11 +31,11 @@ func (s *Suite) Test_Pkgsrc_InitVartypes
                "",
                "_COMPILERS=             gcc ido mipspro-ucode \\",
                "                        sunpro",
-               "_PSEUDO_COMPILERS=          ccache distcc f2c g95",
+               "_PSEUDO_COMPILERS=      ccache distcc f2c g95",
                "",
                ".for _version_ in gnu++14 c++14 gnu++11 c++11 gnu++0x c++0x gnu++03 c++03",
                ".  if !empty(USE_LANGUAGES:M${_version_})",
-               "USE_LANGUAGES+=                c++",
+               "USE_LANGUAGES+=         c++",
                ".  endif",
                ".endfor")
 
@@ -42,30 +52,47 @@ func (s *Suite) Test_Pkgsrc_InitVartypes
        checkEnumValues("PKGSRC_COMPILER", "ShellList of enum: ccache distcc f2c g95 gcc ido mipspro-ucode sunpro ")
 }
 
+func (s *Suite) Test_Pkgsrc_InitVartypes__enumFromDirs(c *check.C) {
+       t := s.Init(c)
+
+       // To make the test useful, these directories must differ from the
+       // PYPKGPREFIX default value in vardefs.go.
+       t.CreateFileLines("lang/python28/Makefile", MkRcsID)
+       t.CreateFileLines("lang/python33/Makefile", MkRcsID)
+
+       t.SetupVartypes()
+
+       checkEnumValues := func(varname, values string) {
+               vartype := G.Pkgsrc.VariableType(varname).String()
+               c.Check(vartype, equals, values)
+       }
+
+       checkEnumValues("PYPKGPREFIX", "enum: py28 py33 ")
+}
+
 func (s *Suite) Test_parseACLEntries(c *check.C) {
        t := s.Init(c)
 
-       t.ExpectFatal(
+       t.ExpectPanic(
                func() { parseACLEntries("VARNAME", "buildlink3.mk: *; *: *") },
-               "FATAL: Invalid ACL permission \"*\" for \"VARNAME\".")
+               "Pkglint internal error: Invalid ACL permission \"*\" for \"VARNAME\".")
 
-       t.ExpectFatal(
+       t.ExpectPanic(
                func() { parseACLEntries("VARNAME", "buildlink3.mk: use; *: use") },
-               "FATAL: Repeated permissions \"use\" for \"VARNAME\".")
+               "Pkglint internal error: Repeated permissions \"use\" for \"VARNAME\".")
 
-       t.ExpectFatal(
+       t.ExpectPanic(
                func() { parseACLEntries("VARNAME", "*.txt: use") },
-               "FATAL: Invalid ACL glob \"*.txt\" for \"VARNAME\".")
+               "Pkglint internal error: Invalid ACL glob \"*.txt\" for \"VARNAME\".")
 
-       t.ExpectFatal(
+       t.ExpectPanic(
                func() { parseACLEntries("VARNAME", "*.mk: use; buildlink3.mk: append") },
-               "FATAL: Ineffective ACL glob \"buildlink3.mk\" for \"VARNAME\".")
+               "Pkglint internal error: Ineffective ACL glob \"buildlink3.mk\" for \"VARNAME\".")
 }
 
 func (s *Suite) Test_Pkgsrc_InitVartypes__LP64PLATFORMS(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        pkg := t.SetupPackage("category/package",
                "BROKEN_ON_PLATFORM=\t${LP64PLATFORMS}")
 

Index: pkgsrc/pkgtools/pkglint/files/alternatives_test.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.6 pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.7
--- pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.6      Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/alternatives_test.go  Wed Nov  7 20:58:22 2018
@@ -38,7 +38,7 @@ func (s *Suite) Test_CheckfileAlternativ
 
        G.Pkg = NewPackage(".")
 
-       CheckfileAlternatives("ALTERNATIVES", G.Pkg.PlistFiles)
+       CheckfileAlternatives("ALTERNATIVES")
 
        t.CheckOutputLines(
                "ERROR: ALTERNATIVES: Must not be empty.")
Index: pkgsrc/pkgtools/pkglint/files/options.go
diff -u pkgsrc/pkgtools/pkglint/files/options.go:1.6 pkgsrc/pkgtools/pkglint/files/options.go:1.7
--- pkgsrc/pkgtools/pkglint/files/options.go:1.6        Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/options.go    Wed Nov  7 20:58:23 2018
@@ -1,10 +1,8 @@
 package main
 
-import "netbsd.org/pkglint/trace"
-
-func ChecklinesOptionsMk(mklines *MkLines) {
+func ChecklinesOptionsMk(mklines MkLines) {
        if trace.Tracing {
-               defer trace.Call1(mklines.lines[0].Filename)()
+               defer trace.Call1(mklines.lines.FileName)()
        }
 
        mklines.Check()
@@ -75,11 +73,13 @@ loop:
 
                        NewMkCondWalker().Walk(cond, &MkCondCallback{
                                Empty: func(varuse *MkVarUse) {
-                                       if varuse.varname == "PKG_OPTIONS" && len(varuse.modifiers) == 1 && hasPrefix(varuse.modifiers[0], "M") {
-                                               option := varuse.modifiers[0][1:]
-                                               if !containsVarRef(option) {
-                                                       handledOptions[option] = mkline
-                                                       optionsInDeclarationOrder = append(optionsInDeclarationOrder, option)
+                                       if varuse.varname == "PKG_OPTIONS" && len(varuse.modifiers) == 1 {
+                                               if m, positive, pattern := varuse.modifiers[0].MatchMatch(); m && positive {
+                                                       option := pattern
+                                                       if !containsVarRef(option) {
+                                                               handledOptions[option] = mkline
+                                                               optionsInDeclarationOrder = append(optionsInDeclarationOrder, option)
+                                                       }
                                                }
                                        }
                                }})
Index: pkgsrc/pkgtools/pkglint/files/tools.go
diff -u pkgsrc/pkgtools/pkglint/files/tools.go:1.6 pkgsrc/pkgtools/pkglint/files/tools.go:1.7
--- pkgsrc/pkgtools/pkglint/files/tools.go:1.6  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/tools.go      Wed Nov  7 20:58:23 2018
@@ -2,7 +2,6 @@ package main
 
 import (
        "fmt"
-       "netbsd.org/pkglint/trace"
        "sort"
        "strings"
 )
@@ -113,10 +112,10 @@ func (tr *Tools) Define(name, varname st
        }
 
        validity := tr.validity(mkline.Basename, false)
-       return tr.defTool(name, varname, false, validity)
+       return tr.def(name, varname, false, validity)
 }
 
-func (tr *Tools) defTool(name, varname string, mustUseVarForm bool, validity Validity) *Tool {
+func (tr *Tools) def(name, varname string, mustUseVarForm bool, validity Validity) *Tool {
        fresh := &Tool{name, varname, mustUseVarForm, validity}
 
        tool := tr.byName[name]
@@ -255,14 +254,14 @@ func (tr *Tools) parseUseTools(mkline Mk
        for _, dep := range deps {
                name := strings.Split(dep, ":")[0]
                if createIfAbsent || tr.ByName(name) != nil {
-                       tr.defTool(name, "", false, validity)
+                       tr.def(name, "", false, validity)
                }
        }
 }
 
 func (tr *Tools) validity(basename string, useTools bool) Validity {
        switch {
-       case IsPrefs(basename): // IsPrefs is not 100% accurate here, but good enough
+       case IsPrefs(basename): // IsPrefs is not 100% accurate here but good enough
                return AfterPrefsMk
        case basename == "Makefile" && !tr.SeenPrefs:
                return AfterPrefsMk
@@ -278,7 +277,7 @@ func (tr *Tools) ByVarname(varname strin
        if tool == nil && tr.fallback != nil {
                fallback := tr.fallback.ByVarname(varname)
                if fallback != nil {
-                       return tr.defTool(fallback.Name, fallback.Varname, fallback.MustUseVarForm, fallback.Validity)
+                       return tr.def(fallback.Name, fallback.Varname, fallback.MustUseVarForm, fallback.Validity)
                }
        }
        return tool
@@ -289,7 +288,7 @@ func (tr *Tools) ByName(name string) *To
        if tool == nil && tr.fallback != nil {
                fallback := tr.fallback.ByName(name)
                if fallback != nil {
-                       return tr.defTool(fallback.Name, fallback.Varname, fallback.MustUseVarForm, fallback.Validity)
+                       return tr.def(fallback.Name, fallback.Varname, fallback.MustUseVarForm, fallback.Validity)
                }
        }
        return tool
Index: pkgsrc/pkgtools/pkglint/files/tools_test.go
diff -u pkgsrc/pkgtools/pkglint/files/tools_test.go:1.6 pkgsrc/pkgtools/pkglint/files/tools_test.go:1.7
--- pkgsrc/pkgtools/pkglint/files/tools_test.go:1.6     Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/tools_test.go Wed Nov  7 20:58:23 2018
@@ -29,6 +29,11 @@ func (s *Suite) Test_Tool_UsableAtRunTim
        c.Check(run.UsableAtRunTime(), equals, true)
 }
 
+// USE_TOOLS is an operating-system-dependent variable.
+// Many other tool variables have the form VARNAME.${tool},
+// which confused an earlier version of pkglint into
+// thinking that the below definition was about a tool
+// called "NetBSD".
 func (s *Suite) Test_Tools_ParseToolLine(c *check.C) {
        t := s.Init(c)
 
@@ -54,7 +59,7 @@ func (s *Suite) Test_Tools_Define__inval
        reg.Define("tool:dependency", "", dummyMkLine)
        reg.Define("tool:build", "", dummyMkLine)
 
-       // Currently, the underscore is not used in any tool name.
+       // As of October 2018, the underscore is not used in any tool name.
        // If there should ever be such a case, just use a different character for testing.
        t.CheckOutputLines(
                "ERROR: Invalid tool name \"tool_name\".",
@@ -90,6 +95,8 @@ func (s *Suite) Test_Tools__USE_TOOLS_pr
 
        G.Main("pkglint", "-Wall", t.File("module.mk"))
 
+       // Since this test doesn't load the usual tool definitions via
+       // G.Pkgsrc.loadTools, AWK is not known at all.
        t.CheckOutputLines(
                "WARN: ~/module.mk:5: Unknown shell command \"${AWK}\".",
                "WARN: ~/module.mk:5: AWK is used but not defined.",
@@ -97,6 +104,9 @@ func (s *Suite) Test_Tools__USE_TOOLS_pr
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
+// It may happen that a tool is first defined without knowing its
+// variable name. When trying to define the tool with its variable name
+// later, the existing definition is amended.
 func (s *Suite) Test_Tools__add_varname_later(c *check.C) {
 
        tools := NewTools("")
@@ -105,7 +115,7 @@ func (s *Suite) Test_Tools__add_varname_
        c.Check(tool.Name, equals, "tool")
        c.Check(tool.Varname, equals, "")
 
-       // Should update the existing tool definition.
+       // Updates the existing tool definition.
        tools.Define("tool", "TOOL", dummyMkLine)
 
        c.Check(tool.Name, equals, "tool")
@@ -129,9 +139,9 @@ func (s *Suite) Test_Tools__load_from_in
 
        // All tools are defined by name, but their variable names are not yet known.
        // At this point they may not be used, neither by the pkgsrc infrastructure nor by a package.
-       c.Check(load, deepEquals, &Tool{"load", "", false, Nowhere})
-       c.Check(run, deepEquals, &Tool{"run", "", false, Nowhere})
-       c.Check(nowhere, deepEquals, &Tool{"nowhere", "", false, Nowhere})
+       c.Check(load.String(), equals, "load:::Nowhere")
+       c.Check(run.String(), equals, "run:::Nowhere")
+       c.Check(nowhere.String(), equals, "nowhere:::Nowhere")
 
        // The name RUN_CMD avoids conflicts with RUN.
        tools.ParseToolLine(t.NewMkLine("varnames.mk", 2, "_TOOLS_VARNAME.load=    LOAD"), true, false)
@@ -140,41 +150,29 @@ func (s *Suite) Test_Tools__load_from_in
 
        // At this point the tools can be found by their variable names, too.
        // They still may not be used.
-       c.Check(load, deepEquals, &Tool{"load", "LOAD", false, Nowhere})
-       c.Check(run, deepEquals, &Tool{"run", "RUN_CMD", false, Nowhere})
-       c.Check(nowhere, deepEquals, &Tool{"nowhere", "NOWHERE", false, Nowhere})
+       c.Check(load.String(), equals, "load:LOAD::Nowhere")
+       c.Check(run.String(), equals, "run:RUN_CMD::Nowhere")
+       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::Nowhere")
        c.Check(tools.ByVarname("LOAD"), equals, load)
        c.Check(tools.ByVarname("RUN_CMD"), equals, run)
        c.Check(tools.ByVarname("NOWHERE"), equals, nowhere)
-       c.Check(load.UsableAtLoadTime(false), equals, false)
-       c.Check(load.UsableAtLoadTime(true), equals, false)
-       c.Check(load.UsableAtRunTime(), equals, false)
-       c.Check(run.UsableAtLoadTime(false), equals, false)
-       c.Check(run.UsableAtLoadTime(true), equals, false)
-       c.Check(run.UsableAtRunTime(), equals, false)
-       c.Check(nowhere.UsableAtLoadTime(false), equals, false)
-       c.Check(nowhere.UsableAtLoadTime(true), equals, false)
-       c.Check(nowhere.UsableAtRunTime(), equals, false)
+       c.Check(load.String(), equals, "load:LOAD::Nowhere")
+       c.Check(run.String(), equals, "run:RUN_CMD::Nowhere")
+       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::Nowhere")
 
        tools.ParseToolLine(t.NewMkLine("bsd.prefs.mk", 2, "USE_TOOLS+= load"), true, true)
 
        // Tools that are added to USE_TOOLS in bsd.prefs.mk may be used afterwards.
        // By variable name, they may be used both at load time as well as run time.
        // By plain name, they may be used only in {pre,do,post}-* targets.
-       c.Check(load, deepEquals, &Tool{"load", "LOAD", false, AfterPrefsMk})
-       c.Check(load.UsableAtLoadTime(false), equals, false)
-       c.Check(load.UsableAtLoadTime(true), equals, true)
-       c.Check(load.UsableAtRunTime(), equals, true)
+       c.Check(load.String(), equals, "load:LOAD::AfterPrefsMk")
 
        tools.ParseToolLine(t.NewMkLine("bsd.pkg.mk", 2, "USE_TOOLS+= run"), true, true)
 
        // Tools that are added to USE_TOOLS in bsd.pkg.mk may be used afterwards at run time.
        // The {pre,do,post}-* targets may use both forms (${CAT} and cat).
        // All other targets must use the variable form (${CAT}).
-       c.Check(run, deepEquals, &Tool{"run", "RUN_CMD", false, AtRunTime})
-       c.Check(run.UsableAtLoadTime(false), equals, false)
-       c.Check(run.UsableAtLoadTime(false), equals, false)
-       c.Check(run.UsableAtRunTime(), equals, true)
+       c.Check(run.String(), equals, "run:RUN_CMD::AtRunTime")
 
        // That's all for parsing tool definitions from the pkgsrc infrastructure.
        // See Test_Tools__package_Makefile for a continuation.
@@ -216,15 +214,11 @@ func (s *Suite) Test_Tools__package_Make
 
        // The seenPrefs variable is only relevant for the package Makefile.
        // All other files must not use the tools at load time.
-       // For them, seenPrefs can be though of as being true from the beginning.
+       // For them, seenPrefs can be thought of as being true from the beginning.
 
        tools.ParseToolLine(t.NewMkLine("Makefile", 2, "USE_TOOLS+=     pkg-before-prefs"), false, true)
 
        c.Check(before.Validity, equals, AfterPrefsMk)
-       c.Check(before.UsableAtLoadTime(false), equals, false)
-       c.Check(before.UsableAtLoadTime(true), equals, true)
-       c.Check(before.UsableAtRunTime(), equals, true)
-
        c.Check(tools.SeenPrefs, equals, false)
 
        tools.ParseToolLine(t.NewMkLine("Makefile", 3, ".include \"../../mk/bsd.prefs.mk\""), false, true)
@@ -234,9 +228,6 @@ func (s *Suite) Test_Tools__package_Make
        tools.ParseToolLine(t.NewMkLine("Makefile", 4, "USE_TOOLS+=     pkg-after-prefs"), false, true)
 
        c.Check(after.Validity, equals, AtRunTime)
-       c.Check(after.UsableAtLoadTime(false), equals, false)
-       c.Check(after.UsableAtLoadTime(true), equals, false)
-       c.Check(after.UsableAtRunTime(), equals, true)
 }
 
 func (s *Suite) Test_Tools__builtin_mk(c *check.C) {
@@ -261,7 +252,7 @@ func (s *Suite) Test_Tools__builtin_mk(c
        // Tools that are defined by pkgsrc as load-time tools
        // may be used in any file at load time.
 
-       mklines := t.NewMkLines("builtin.mk",
+       mklines := t.SetupFileMkLines("builtin.mk",
                MkRcsID,
                "",
                "VAR!=   ${ECHO} 'too early'",
@@ -281,12 +272,12 @@ func (s *Suite) Test_Tools__builtin_mk(c
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: builtin.mk:3: To use the tool ${ECHO} at load time, bsd.prefs.mk has to be included before.",
-               "WARN: builtin.mk:4: To use the tool ${LOAD} at load time, bsd.prefs.mk has to be included before.",
-               "WARN: builtin.mk:5: The tool ${RUN_CMD} cannot be used at load time.",
-               "WARN: builtin.mk:6: The tool ${NOWHERE} cannot be used at load time.",
-               "WARN: builtin.mk:12: The tool ${RUN_CMD} cannot be used at load time.",
-               "WARN: builtin.mk:13: The tool ${NOWHERE} cannot be used at load time.")
+               "WARN: ~/builtin.mk:3: To use the tool ${ECHO} at load time, bsd.prefs.mk has to be included before.",
+               "WARN: ~/builtin.mk:4: To use the tool ${LOAD} at load time, bsd.prefs.mk has to be included before.",
+               "WARN: ~/builtin.mk:5: The tool ${RUN_CMD} cannot be used at load time.",
+               "WARN: ~/builtin.mk:6: The tool ${NOWHERE} cannot be used at load time.",
+               "WARN: ~/builtin.mk:12: The tool ${RUN_CMD} cannot be used at load time.",
+               "WARN: ~/builtin.mk:13: The tool ${NOWHERE} cannot be used at load time.")
 }
 
 func (s *Suite) Test_Tools__implicit_definition_in_bsd_pkg_mk(c *check.C) {
@@ -307,7 +298,7 @@ func (s *Suite) Test_Tools__implicit_def
        // In other words, this test is only there for the code coverage.
        G.Pkgsrc.LoadInfrastructure()
 
-       c.Check(G.Pkgsrc.Tools.ByName("run"), deepEquals, &Tool{"run", "", false, AtRunTime})
+       c.Check(G.Pkgsrc.Tools.ByName("run").String(), equals, "run:::AtRunTime")
 }
 
 func (s *Suite) Test_Tools__both_prefs_and_pkg_mk(c *check.C) {
@@ -397,7 +388,6 @@ func (s *Suite) Test_ToolTime_String(c *
 func (s *Suite) Test_Tools__var(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupPkgsrc()
        t.CreateFileLines("mk/tools/defaults.mk",
                "TOOLS_CREATE+=          ln",
@@ -426,26 +416,24 @@ func (s *Suite) Test_Tools__var(c *check
 // See also Pkglint.Tool.
 func (s *Suite) Test_Tools_Fallback__tools_having_the_same_variable_name_realistic(c *check.C) {
        nonGnu := NewTools("non-gnu")
-       nonGnu.defTool("sed", "SED", false, AfterPrefsMk)
+       nonGnu.def("sed", "SED", false, AfterPrefsMk)
 
        gnu := NewTools("gnu")
-       gnu.defTool("gsed", "SED", false, Nowhere)
+       gnu.def("gsed", "SED", false, Nowhere)
 
        local1 := NewTools("local")
-       local1.defTool("sed", "SED", false, AfterPrefsMk)
+       local1.def("sed", "SED", false, AfterPrefsMk)
        local1.Fallback(gnu)
 
        c.Check(local1.ByName("sed").Validity, equals, AfterPrefsMk)
        c.Check(local1.ByName("gsed").Validity, equals, Nowhere)
-       local1.Trace()
 
        local2 := NewTools("local")
-       local2.defTool("gsed", "SED", false, Nowhere)
+       local2.def("gsed", "SED", false, Nowhere)
        local2.Fallback(nonGnu)
 
        c.Check(local2.ByName("sed").Validity, equals, AfterPrefsMk)
        c.Check(local2.ByName("gsed").Validity, equals, Nowhere)
-       local2.Trace()
 
        // No matter in which order the tool definitions are encountered,
        // the non-GNU version is always chosen since the GNU version is
@@ -463,26 +451,24 @@ func (s *Suite) Test_Tools_Fallback__too
 // See also Pkglint.Tool.
 func (s *Suite) Test_Tools_Fallback__tools_having_the_same_variable_name_unrealistic(c *check.C) {
        nonGnu := NewTools("non-gnu")
-       nonGnu.defTool("sed", "SED", false, Nowhere)
+       nonGnu.def("sed", "SED", false, Nowhere)
 
        gnu := NewTools("gnu")
-       gnu.defTool("gsed", "SED", false, AfterPrefsMk)
+       gnu.def("gsed", "SED", false, AfterPrefsMk)
 
        local1 := NewTools("local")
-       local1.defTool("sed", "SED", false, Nowhere)
+       local1.def("sed", "SED", false, Nowhere)
        local1.Fallback(gnu)
 
        c.Check(local1.ByName("sed").Validity, equals, Nowhere)
        c.Check(local1.ByName("gsed").Validity, equals, AfterPrefsMk)
-       local1.Trace()
 
        local2 := NewTools("local")
-       local2.defTool("gsed", "SED", false, AfterPrefsMk)
+       local2.def("gsed", "SED", false, AfterPrefsMk)
        local2.Fallback(nonGnu)
 
        c.Check(local2.ByName("sed").Validity, equals, Nowhere)
        c.Check(local2.ByName("gsed").Validity, equals, AfterPrefsMk)
-       local2.Trace()
 
        // FIXME: Must both be gsed:SED::AfterPrefsMk
        c.Check(local1.ByVarname("SED").String(), equals, "sed:SED::Nowhere")
@@ -500,7 +486,6 @@ func (s *Suite) Test_Tools_Fallback__too
 func (s *Suite) Test_Tools__cmake(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupPackage("category/package",
                "USE_CMAKE=\tyes",
                "",

Index: pkgsrc/pkgtools/pkglint/files/autofix.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix.go:1.11 pkgsrc/pkgtools/pkglint/files/autofix.go:1.12
--- pkgsrc/pkgtools/pkglint/files/autofix.go:1.11       Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix.go    Wed Nov  7 20:58:22 2018
@@ -4,7 +4,6 @@ import (
        "fmt"
        "io/ioutil"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "os"
        "strconv"
        "strings"
@@ -16,13 +15,20 @@ import (
 // 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
+       linesBefore []string // Newly inserted lines, including \n
+       linesAfter  []string // Newly inserted lines, including \n
+       // Whether an actual fix has been applied (or, without --show-autofix,
+       // whether a fix is applicable)
+       modified bool
+
+       autofixShortTerm
+}
+
+// autofixShortTerm is the part of the Autofix that is reset after each call to Apply.
+type autofixShortTerm struct {
        actions     []autofixAction // Human-readable description of the actual autofix actions
        level       *LogLevel       //
-       diagFormat  string          // Is printed only if it couldn't be fixed automatically
+       diagFormat  string          // Is logged only if it couldn't be fixed automatically
        diagArgs    []interface{}   //
        explanation []string        // Is printed together with the diagnostic
 }
@@ -32,50 +38,72 @@ type autofixAction struct {
        lineno      int
 }
 
+// SilentAutofixFormat is used in exceptional situations when an
+// autofix action is not directly related to a diagnostic.
+//
+// To prevent confusion, the code using this magic value must ensure
+// to log a diagnostic by other means.
+const SilentAutofixFormat = "SilentAutofixFormat"
+
+// AutofixFormat is a special value that is used for logging
+// diagnostics like "Replacing \"old\" with \"new\".".
+//
+// Since these are not really diagnostics, duplicates are not suppressed.
+const AutofixFormat = "AutofixFormat"
+
 func NewAutofix(line Line) *Autofix {
-       return &Autofix{
-               line:  line,
-               lines: append([]*RawLine{}, line.raw...)}
+       return &Autofix{line: line}
 }
 
-// Custom runs a custom fix action, unless the fix is skipped anyway
-// because of the --only option.
-//
-// The fixer function must always call Describef.
-//
-// If printAutofix or autofix is true, the fix should be done in
-// memory as far as possible (e.g. changes to the text of the line).
-//
-// If autofix is true, the fix should be done persistently
-// (e.g. direct changes to the file system). Except if the fix only
-// affects the current line, then SaveAutofixChanges will do that.
-func (fix *Autofix) Custom(fixer func(printAutofix, autofix bool)) {
-       if fix.skip() {
-               return
-       }
+// Errorf remembers the error for logging it later when Apply is called.
+func (fix *Autofix) Errorf(format string, args ...interface{}) {
+       fix.setDiag(Error, format, args)
+}
+
+// Warnf remembers the warning for logging it later when Apply is called.
+func (fix *Autofix) Warnf(format string, args ...interface{}) {
+       fix.setDiag(Warn, format, args)
+}
 
-       fixer(G.opts.PrintAutofix, G.opts.Autofix)
+// Notef remembers the note for logging it later when Apply is called.
+func (fix *Autofix) Notef(format string, args ...interface{}) {
+       fix.setDiag(Note, format, args)
 }
 
+// Explain remembers the explanation for logging it later when Apply is called.
+func (fix *Autofix) Explain(explanation ...string) {
+       // Since a silent fix doesn't have a diagnostic, its explanation would
+       // not provide any clue as to what diagnostic it belongs. That would
+       // be confusing, therefore this case is not allowed.
+       G.Assertf(
+               fix.diagFormat != SilentAutofixFormat,
+               "Autofix: Silent fixes cannot have an explanation.")
+
+       fix.explanation = explanation
+}
+
+// ReplaceAfter replaces "from" with "to", a single time.
 func (fix *Autofix) Replace(from string, to string) {
        fix.ReplaceAfter("", from, to)
 }
 
-// ReplaceAfter replaces the text "prefix+from" with "prefix+to",
-// but in the diagnostic, only the replacement of "from" with "to"
-// is mentioned.
+// ReplaceAfter replaces the text "prefix+from" with "prefix+to", a single time.
+// In the diagnostic, only the replacement of "from" with "to" is mentioned.
 func (fix *Autofix) ReplaceAfter(prefix, from string, to string) {
+       fix.assertRealLine()
        if fix.skip() {
                return
        }
 
-       for _, rawLine := range fix.lines {
+       for _, rawLine := range fix.line.raw {
                if rawLine.Lineno != 0 {
-                       if replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1); replaced != rawLine.textnl {
-                               if G.opts.PrintAutofix || G.opts.Autofix {
+                       replaced := strings.Replace(rawLine.textnl, prefix+from, prefix+to, 1)
+                       if replaced != rawLine.textnl {
+                               if G.Opts.ShowAutofix || G.Opts.Autofix {
                                        rawLine.textnl = replaced
                                }
                                fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to)
+                               return
                        }
                }
        }
@@ -87,12 +115,13 @@ func (fix *Autofix) ReplaceAfter(prefix,
 // Placeholders like `$1` are _not_ expanded in the `toText`.
 // (If you know how to do the expansion correctly, feel free to implement it.)
 func (fix *Autofix) ReplaceRegex(from regex.Pattern, toText string, howOften int) {
+       fix.assertRealLine()
        if fix.skip() {
                return
        }
 
        done := 0
-       for _, rawLine := range fix.lines {
+       for _, rawLine := range fix.line.raw {
                if rawLine.Lineno != 0 {
                        var froms []string // The strings that have actually changed
 
@@ -105,8 +134,9 @@ func (fix *Autofix) ReplaceRegex(from re
                                return toText
                        }
 
-                       if replaced := replaceAllFunc(rawLine.textnl, from, replace); replaced != rawLine.textnl {
-                               if G.opts.PrintAutofix || G.opts.Autofix {
+                       replaced := replaceAllFunc(rawLine.textnl, from, replace)
+                       if replaced != rawLine.textnl {
+                               if G.Opts.ShowAutofix || G.Opts.Autofix {
                                        rawLine.textnl = replaced
                                }
                                for _, fromText := range froms {
@@ -117,175 +147,228 @@ func (fix *Autofix) ReplaceRegex(from re
        }
 }
 
-func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
-       if fix.skip() || !mkline.IsMultiline() || !(mkline.IsVarassign() || mkline.IsCommentedVarassign()) {
-               return
-       }
-
-       normalized := true // Whether all indentation is tabs, followed by spaces.
-       oldWidth := 0      // The minimum required indentation in the original lines.
-
-       {
-               // Interpreting the continuation marker as variable value
-               // is cheating, but works well.
-               text := strings.TrimSuffix(mkline.raw[0].orignl, "\n")
-               m, _, _, _, _, valueAlign, value, _, _ := MatchVarassign(text)
-               if m && value != "\\" {
-                       oldWidth = tabWidth(valueAlign)
-               }
-       }
-
-       for _, rawLine := range fix.lines[1:] {
-               _, comment, space := match2(rawLine.textnl, `^(#?)([ \t]*)`)
-               width := tabWidth(comment + space)
-               if (oldWidth == 0 || width < oldWidth) && width >= 8 && rawLine.textnl != "\n" {
-                       oldWidth = width
-               }
-               if !matches(space, `^\t* {0,7}$`) {
-                       normalized = false
-               }
-       }
+// Custom runs a custom fix action, unless the fix is skipped anyway
+// because of the --only option.
+//
+// The fixer function must check whether it can actually fix something,
+// and if so, call Describef to describe the actual fix.
+//
+// If showAutofix and autofix are both false, the fix must only be
+// described by calling Describef. No observable modification must be done,
+// not even in memory.
+//
+// If showAutofix is true but autofix is false, the fix should be done in
+// memory as far as possible. For example, changing the text of Line.raw
+// is appropriate, but changing files in the file system is not.
+//
+// Only if autofix is true, fixes other than modifying the current Line
+// should be done persistently, such as changes to the file system.
+//
+// In any case, changes to the current Line will be written back to disk
+// by SaveAutofixChanges, after fixing all the lines in the file at once.
+func (fix *Autofix) Custom(fixer func(showAutofix, autofix bool)) {
+       // Contrary to the fixes that modify the line text, this one
+       // can be run even on dummy lines (like those standing for a
+       // file at whole), for example to fix the permissions of the file.
 
-       if normalized && newWidth == oldWidth {
+       if fix.skip() {
                return
        }
 
-       // Continuation lines with the minimal unambiguous indentation
-       // attempt to keep the indentation as small as possible, so don't
-       // realign them.
-       if oldWidth == 8 {
-               return
-       }
+       fixer(G.Opts.ShowAutofix, G.Opts.Autofix)
+}
 
-       for _, rawLine := range fix.lines[1:] {
-               _, comment, oldSpace := match2(rawLine.textnl, `^(#?)([ \t]*)`)
-               newWidth := tabWidth(oldSpace) - oldWidth + newWidth
-               newSpace := strings.Repeat("\t", newWidth/8) + strings.Repeat(" ", newWidth%8)
-               replaced := strings.Replace(rawLine.textnl, comment+oldSpace, comment+newSpace, 1)
-               if replaced != rawLine.textnl {
-                       if G.opts.PrintAutofix || G.opts.Autofix {
-                               rawLine.textnl = replaced
-                       }
-                       fix.Describef(rawLine.Lineno, "Replacing indentation %q with %q.", oldSpace, newSpace)
-               }
-       }
+// Describef is used while Autofix.Custom is called to remember a description
+// of the actual fix for logging it later when Apply is called.
+// Describef may be called multiple times before calling Apply.
+func (fix *Autofix) Describef(lineno int, format string, args ...interface{}) {
+       fix.actions = append(fix.actions, autofixAction{fmt.Sprintf(format, args...), lineno})
 }
 
 // InsertBefore prepends a line before the current line.
 // The newline is added internally.
 func (fix *Autofix) InsertBefore(text string) {
+       fix.assertRealLine()
        if fix.skip() {
                return
        }
 
-       fix.linesBefore = append(fix.linesBefore, text+"\n")
-       fix.Describef(fix.lines[0].Lineno, "Inserting a line %q before this line.", text)
+       if G.Opts.ShowAutofix || G.Opts.Autofix {
+               fix.linesBefore = append(fix.linesBefore, text+"\n")
+       }
+       fix.Describef(fix.line.raw[0].Lineno, "Inserting a line %q before this line.", text)
 }
 
 // InsertAfter appends a line after the current line.
 // The newline is added internally.
 func (fix *Autofix) InsertAfter(text string) {
+       fix.assertRealLine()
        if fix.skip() {
                return
        }
 
-       fix.linesAfter = append(fix.linesAfter, text+"\n")
-       fix.Describef(fix.lines[len(fix.lines)-1].Lineno, "Inserting a line %q after this line.", text)
+       if G.Opts.ShowAutofix || G.Opts.Autofix {
+               fix.linesAfter = append(fix.linesAfter, text+"\n")
+       }
+       fix.Describef(fix.line.raw[len(fix.line.raw)-1].Lineno, "Inserting a line %q after this line.", text)
 }
 
+// Delete removes the current line completely.
+// It can be combined with InsertAfter or InsertBefore to
+// replace the complete line with some different text.
 func (fix *Autofix) Delete() {
+       fix.assertRealLine()
        if fix.skip() {
                return
        }
 
-       for _, line := range fix.lines {
+       for _, line := range fix.line.raw {
+               if G.Opts.ShowAutofix || G.Opts.Autofix {
+                       line.textnl = ""
+               }
                fix.Describef(line.Lineno, "Deleting this line.")
-               line.textnl = ""
        }
 }
 
-// Describef remembers a description of the actual fix
-// for logging it later when Apply is called.
-// There may be multiple fixes in one pass.
-func (fix *Autofix) Describef(lineno int, format string, args ...interface{}) {
-       fix.actions = append(fix.actions, autofixAction{fmt.Sprintf(format, args...), lineno})
-}
-
-// Notef remembers the note for logging it later when Apply is called.
-func (fix *Autofix) Notef(format string, args ...interface{}) {
-       fix.setDiag(llNote, format, args)
-}
-
-// Warnf remembers the warning for logging it later when Apply is called.
-func (fix *Autofix) Warnf(format string, args ...interface{}) {
-       fix.setDiag(llWarn, format, args)
-}
-
-// Errorf remembers the error for logging it later when Apply is called.
-func (fix *Autofix) Errorf(format string, args ...interface{}) {
-       fix.setDiag(llError, format, args)
-}
-
-// Explain remembers the explanation for logging it later when Apply is called.
-func (fix *Autofix) Explain(explanation ...string) {
-       fix.explanation = explanation
-}
-
 // Apply does the actual work.
 // Depending on the pkglint mode, it either:
 //
-// * logs the associated message (default)
-// * logs what would be fixed (--show-autofix)
-// * records the fixes in the line (--autofix)
+// * logs the associated message (default) but does not record the fixes in the line
+//
+// * logs what would be fixed (--show-autofix) and records the fixes in the line
+//
+// * records the fixes in the line (--autofix), ready for SaveAutofixChanges
 func (fix *Autofix) Apply() {
        line := fix.line
 
-       if fix.diagFormat == "" {
-               panic("Each autofix must have a diagnostic.")
+       // To fix this assertion, call one of Autofix.Errorf, Autofix.Warnf
+       // or Autofix.Notef before calling Apply.
+       G.Assertf(
+               fix.level != nil && fix.diagFormat != "",
+               "Each autofix must have a log level and a diagnostic.")
+
+       reset := func() {
+               if len(fix.actions) > 0 {
+                       fix.modified = true
+               }
+
+               // Reduce number of calls to runtime.writeBarrier.
+               fix.autofixShortTerm = autofixShortTerm{}
        }
+
        G.explainNext = shallBeLogged(fix.diagFormat)
-       if G.explainNext {
-               logDiagnostic := fix.level != nil && fix.diagFormat != "Silent-Magic-Diagnostic" &&
-                       !(G.opts.Autofix && !G.opts.PrintAutofix) && len(fix.actions) > 0
-               if logDiagnostic {
-                       msg := fmt.Sprintf(fix.diagFormat, fix.diagArgs...)
-                       logs(fix.level, line.Filename, line.Linenos(), fix.diagFormat, msg)
-               }
+       if !G.explainNext || len(fix.actions) == 0 {
+               reset()
+               return
+       }
 
-               logRepair := len(fix.actions) > 0 && (G.opts.Autofix || G.opts.PrintAutofix)
-               if logRepair {
-                       for _, action := range fix.actions {
-                               lineno := ""
-                               if action.lineno != 0 {
-                                       lineno = strconv.Itoa(action.lineno)
-                               }
-                               logs(llAutofix, line.Filename, lineno, "Magic-Autofix-Format", action.description)
+       logDiagnostic := (G.Opts.ShowAutofix || !G.Opts.Autofix) &&
+               fix.diagFormat != SilentAutofixFormat
+       logFix := G.Opts.Autofix || G.Opts.ShowAutofix
+
+       if logDiagnostic {
+               if !logFix {
+                       line.showSource(G.logOut)
+               }
+               msg := fmt.Sprintf(fix.diagFormat, fix.diagArgs...)
+               logf(fix.level, line.FileName, line.Linenos(), fix.diagFormat, msg)
+       }
+
+       if logFix {
+               for _, action := range fix.actions {
+                       lineno := ""
+                       if action.lineno != 0 {
+                               lineno = strconv.Itoa(action.lineno)
                        }
+                       logf(AutofixLogLevel, line.FileName, lineno, AutofixFormat, action.description)
                }
+       }
 
-               if logDiagnostic || logRepair {
-                       line.printSource(G.logOut)
-                       if logDiagnostic && len(fix.explanation) != 0 {
-                               Explain(fix.explanation...)
-                       } else if G.opts.PrintSource {
+       if logDiagnostic || logFix {
+               if logFix {
+                       line.showSource(G.logOut)
+               }
+               if logDiagnostic && len(fix.explanation) > 0 {
+                       Explain(fix.explanation...)
+               }
+               if G.Opts.ShowSource {
+                       if !G.Opts.Explain || !logDiagnostic || len(fix.explanation) == 0 {
                                G.logOut.Separate()
                        }
                }
        }
 
-       fix.modified = fix.modified || len(fix.actions) > 0
+       reset()
+}
+
+func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
+       fix.assertRealLine()
+       G.Assertf(mkline.IsMultiline(), "Line must be a multiline.")
+       G.Assertf(mkline.IsVarassign() || mkline.IsCommentedVarassign(), "Line must be a variable assignment.")
+
+       if fix.skip() {
+               return
+       }
+
+       normalized := true // Whether all indentation is tabs, followed by spaces.
+       oldWidth := 0      // The minimum required indentation in the original lines.
+
+       {
+               // Parsing the continuation marker as variable value
+               // is cheating but works well.
+               text := strings.TrimSuffix(mkline.raw[0].orignl, "\n")
+               m, _, _, _, _, valueAlign, value, _, _ := MatchVarassign(text)
+               if m && value != "\\" {
+                       oldWidth = tabWidth(valueAlign)
+               }
+       }
+
+       for _, rawLine := range fix.line.raw[1:] {
+               _, comment, space := match2(rawLine.textnl, `^(#?)([ \t]*)`)
+               width := tabWidth(comment + space)
+               if (oldWidth == 0 || width < oldWidth) && width >= 8 && rawLine.textnl != "\n" {
+                       oldWidth = width
+               }
+               if !matches(space, `^\t* {0,7}$`) {
+                       normalized = false
+               }
+       }
 
-       fix.actions = nil
-       fix.level = nil
-       fix.diagFormat = ""
-       fix.diagArgs = nil
-       fix.explanation = nil
+       if normalized && newWidth == oldWidth {
+               return
+       }
+
+       // 8 spaces is the minimum possible indentation that can be
+       // distinguished from an initial line, by looking only at the
+       // beginning of the line. Therefore, this indentation is always
+       // regarded as intentional and is not realigned.
+       if oldWidth == 8 {
+               return
+       }
+
+       for _, rawLine := range fix.line.raw[1:] {
+               _, comment, oldSpace := match2(rawLine.textnl, `^(#?)([ \t]*)`)
+               newWidth := tabWidth(oldSpace) - oldWidth + newWidth
+               newSpace := strings.Repeat("\t", newWidth/8) + strings.Repeat(" ", newWidth%8)
+               replaced := strings.Replace(rawLine.textnl, comment+oldSpace, comment+newSpace, 1)
+               if replaced != rawLine.textnl {
+                       if G.Opts.ShowAutofix || G.Opts.Autofix {
+                               rawLine.textnl = replaced
+                       }
+                       fix.Describef(rawLine.Lineno, "Replacing indentation %q with %q.", oldSpace, newSpace)
+               }
+       }
 }
 
 func (fix *Autofix) setDiag(level *LogLevel, format string, args []interface{}) {
-       if G.Testing && format != "Silent-Magic-Diagnostic" && !hasSuffix(format, ".") {
-               panic(fmt.Sprintf("Autofix: format %q must end with a period.", format))
+       if G.Testing && format != SilentAutofixFormat {
+               G.Assertf(
+                       hasSuffix(format, "."),
+                       "Autofix: format %q must end with a period.",
+                       format)
        }
+       G.Assertf(fix.level == nil, "Autofix can only have a single diagnostic.")
+       G.Assertf(fix.diagFormat == "", "Autofix can only have a single diagnostic.")
 
        fix.level = level
        fix.diagFormat = format
@@ -293,27 +376,35 @@ func (fix *Autofix) setDiag(level *LogLe
 }
 
 func (fix *Autofix) skip() bool {
+       G.Assertf(
+               fix.diagFormat != "",
+               "Autofix: The diagnostic must be given before the action.")
        // This check is necessary for the --only command line option.
-       if fix.diagFormat == "" {
-               panic("Autofix: The diagnostic must be given before the action.")
-       }
        return !shallBeLogged(fix.diagFormat)
 }
 
+func (fix *Autofix) assertRealLine() {
+       G.Assertf(fix.line.firstLine >= 1, "Cannot autofix this line since it is not a real line.")
+}
+
 // 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) {
+func SaveAutofixChanges(lines Lines) (autofixed bool) {
        if trace.Tracing {
                defer trace.Call0()()
        }
 
-       if !G.opts.Autofix {
-               for _, line := range lines {
+       // Fast lane for the case that nothing is written back to disk.
+       if !G.Opts.Autofix {
+               for _, line := range lines.Lines {
                        if line.autofix != nil && line.autofix.modified {
                                G.autofixAvailable = true
-                               G.fileCache.Evict(line.Filename)
+                               if G.Opts.ShowAutofix {
+                                       // Only in this case can the loaded lines be modified.
+                                       G.fileCache.Evict(line.FileName)
+                               }
                        }
                }
                return
@@ -321,11 +412,11 @@ func SaveAutofixChanges(lines []Line) (a
 
        changes := make(map[string][]string)
        changed := make(map[string]bool)
-       for _, line := range lines {
-               chlines := changes[line.Filename]
+       for _, line := range lines.Lines {
+               chlines := changes[line.FileName]
                if fix := line.autofix; fix != nil {
                        if fix.modified {
-                               changed[line.Filename] = true
+                               changed[line.FileName] = true
                        }
                        chlines = append(chlines, fix.linesBefore...)
                        for _, raw := range line.raw {
@@ -337,27 +428,27 @@ func SaveAutofixChanges(lines []Line) (a
                                chlines = append(chlines, raw.textnl)
                        }
                }
-               changes[line.Filename] = chlines
+               changes[line.FileName] = chlines
        }
 
-       for fname := range changed {
-               G.fileCache.Evict(fname)
-               changedLines := changes[fname]
-               tmpname := fname + ".pkglint.tmp"
+       for fileName := range changed {
+               G.fileCache.Evict(fileName)
+               changedLines := changes[fileName]
+               tmpName := fileName + ".pkglint.tmp"
                text := ""
                for _, changedLine := range changedLines {
                        text += changedLine
                }
-               err := ioutil.WriteFile(tmpname, []byte(text), 0666)
+               err := ioutil.WriteFile(tmpName, []byte(text), 0666)
                if err != nil {
-                       logs(llError, tmpname, "", "Cannot write: %s", "Cannot write: "+err.Error())
+                       logf(Error, tmpName, "", "Cannot write: %s", "Cannot write: "+err.Error())
                        continue
                }
-               err = os.Rename(tmpname, fname)
+               err = os.Rename(tmpName, fileName)
                if err != nil {
-                       logs(llError, tmpname, "",
-                               "Cannot overwrite with auto-fixed content: %s",
-                               "Cannot overwrite with auto-fixed content: "+err.Error())
+                       logf(Error, tmpName, "",
+                               "Cannot overwrite with autofixed content: %s",
+                               "Cannot overwrite with autofixed content: "+err.Error())
                        continue
                }
                autofixed = true
Index: pkgsrc/pkgtools/pkglint/files/autofix_test.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.11 pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.12
--- pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.11  Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/autofix_test.go       Wed Nov  7 20:58:22 2018
@@ -7,6 +7,104 @@ import (
        "strings"
 )
 
+func (s *Suite) Test_Autofix_Warnf__duplicate(c *check.C) {
+       t := s.Init(c)
+
+       line := t.NewLine("DESCR", 1, "Description of the package")
+
+       fix := line.Autofix()
+       fix.Warnf("Warning 1.")
+       t.ExpectPanic(
+               func() { fix.Warnf("Warning 2.") },
+               "Pkglint internal error: Autofix can only have a single diagnostic.")
+}
+
+func (s *Suite) Test_Autofix__default_leaves_line_unchanged(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--source")
+       mklines := t.SetupFileMkLines("Makefile",
+               "# row 1 \\",
+               "continuation of row 1")
+       line := mklines.lines.Lines[0]
+
+       fix := line.Autofix()
+       fix.Warnf("Row should be replaced with line.")
+       fix.Replace("row", "line")
+       fix.ReplaceRegex(`row \d+`, "the above line", -1)
+       fix.InsertBefore("above")
+       fix.InsertAfter("below")
+       fix.Delete()
+       fix.Apply()
+
+       c.Check(fix.RawText(), equals, ""+
+               "# row 1 \\\n"+
+               "continuation of row 1\n")
+       t.CheckOutputLines(
+               ">\t# row 1 \\",
+               ">\tcontinuation of row 1",
+               "WARN: ~/Makefile:1--2: Row should be replaced with line.")
+       c.Check(fix.modified, equals, true)
+}
+
+func (s *Suite) Test_Autofix__show_autofix_modifies_line(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--source", "--show-autofix")
+       mklines := t.SetupFileMkLines("Makefile",
+               "# row 1 \\",
+               "continuation of row 1")
+       line := mklines.lines.Lines[0]
+
+       fix := line.Autofix()
+       fix.Warnf("Row should be replaced with line.")
+       fix.ReplaceAfter("", "row", "line")
+       fix.ReplaceRegex(`row \d+`, "the above line", -1)
+       fix.InsertBefore("above")
+       fix.InsertAfter("below")
+       fix.Delete()
+       fix.Apply()
+
+       c.Check(fix.RawText(), equals, ""+
+               "above\n"+
+               "below\n")
+       t.CheckOutputLines(
+               "WARN: ~/Makefile:1--2: Row should be replaced with line.",
+               "AUTOFIX: ~/Makefile:1: Replacing \"row\" with \"line\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"row 1\" with \"the above line\".",
+               "AUTOFIX: ~/Makefile:1: Inserting a line \"above\" before this line.",
+               "AUTOFIX: ~/Makefile:2: Inserting a line \"below\" after this line.",
+               "AUTOFIX: ~/Makefile:1: Deleting this line.",
+               "AUTOFIX: ~/Makefile:2: Deleting this line.",
+               "+\tabove",
+               "-\t# row 1 \\",
+               "-\tcontinuation of row 1",
+               "+\tbelow")
+       c.Check(fix.modified, equals, true)
+}
+
+func (s *Suite) Test_Autofix_ReplaceAfter__autofix(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--autofix", "--source")
+       mklines := t.SetupFileMkLines("Makefile",
+               "# line 1 \\",
+               "continuation 1 \\",
+               "continuation 2")
+
+       fix := mklines.lines.Lines[0].Autofix()
+       fix.Warnf("N should be replaced with V.")
+       fix.ReplaceAfter("", "n", "v")
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "AUTOFIX: ~/Makefile:1: Replacing \"n\" with \"v\".",
+               "-\t# line 1 \\",
+               "+\t# live 1 \\",
+               ">\tcontinuation 1 \\",
+               ">\tcontinuation 2")
+}
+
 func (s *Suite) Test_Autofix_ReplaceRegex__show_autofix(c *check.C) {
        t := s.Init(c)
 
@@ -16,13 +114,13 @@ func (s *Suite) Test_Autofix_ReplaceRege
                "line2",
                "line3")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Something's wrong here.")
        fix.ReplaceRegex(`.`, "X", -1)
        fix.Apply()
        SaveAutofixChanges(lines)
 
-       c.Check(lines[1].raw[0].textnl, equals, "XXXXX\n")
+       c.Check(lines.Lines[1].raw[0].textnl, equals, "XXXXX\n")
        t.CheckFileLines("Makefile",
                "line1",
                "line2",
@@ -45,7 +143,7 @@ func (s *Suite) Test_Autofix_ReplaceRege
                "line2",
                "line3")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Something's wrong here.")
        fix.ReplaceRegex(`.`, "X", 3)
        fix.Apply()
@@ -57,6 +155,7 @@ func (s *Suite) Test_Autofix_ReplaceRege
                "-\tline2",
                "+\tXXXe2")
 
+       // After calling fix.Apply above, the autofix is ready to be used again.
        fix.Warnf("Use Y instead of X.")
        fix.Replace("X", "Y")
        fix.Apply()
@@ -84,7 +183,7 @@ func (s *Suite) Test_Autofix_ReplaceRege
                "line2",
                "line3")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Something's wrong here.")
        fix.ReplaceRegex(`.`, "X", -1)
        fix.Apply()
@@ -115,38 +214,25 @@ func (s *Suite) Test_SaveAutofixChanges(
        t := s.Init(c)
 
        t.SetupCommandLine("--autofix")
-       t.CreateFileLines("category/basename/Makefile",
+       lines := t.SetupFileLines("example.txt",
                "line1 := value1",
                "line2 := value2",
                "line3 := value3")
-       pkg := NewPackage(t.File("category/basename"))
-       G.Pkg = pkg
-       mklines := pkg.loadPackageMakefile()
-       G.Pkg = nil
 
-       fix := mklines.mklines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Something's wrong here.")
-       fix.ReplaceRegex(`...`, "XXX", -1)
+       fix.ReplaceRegex(`...`, "XXX", 2)
        fix.Apply()
 
-       fix = mklines.mklines[2].Autofix()
-       fix.Warnf("Something's wrong here.")
-       fix.ReplaceRegex(`...`, "XXX", 1)
-       fix.Apply()
-
-       SaveAutofixChanges(mklines.lines)
+       SaveAutofixChanges(lines)
 
        t.CheckOutputLines(
-               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"lin\" with \"XXX\".",
-               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"e2 \" with \"XXX\".",
-               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \":= \" with \"XXX\".",
-               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"val\" with \"XXX\".",
-               "AUTOFIX: ~/category/basename/Makefile:2: Replacing \"ue2\" with \"XXX\".",
-               "AUTOFIX: ~/category/basename/Makefile:3: Replacing \"lin\" with \"XXX\".")
-       t.CheckFileLines("category/basename/Makefile",
+               "AUTOFIX: ~/example.txt:2: Replacing \"lin\" with \"XXX\".",
+               "AUTOFIX: ~/example.txt:2: Replacing \"e2 \" with \"XXX\".")
+       t.CheckFileLines("example.txt",
                "line1 := value1",
-               "XXXXXXXXXXXXXXX",
-               "XXXe3 := value3")
+               "XXXXXX:= value2",
+               "line3 := value3")
 }
 
 func (s *Suite) Test_SaveAutofixChanges__no_changes_necessary(c *check.C) {
@@ -157,7 +243,7 @@ func (s *Suite) Test_SaveAutofixChanges_
                "Line 1",
                "Line 2")
 
-       fix := lines[0].Autofix()
+       fix := lines.Lines[0].Autofix()
        fix.Warnf("Dummy warning.")
        fix.Replace("X", "Y")
        fix.Apply()
@@ -170,19 +256,19 @@ func (s *Suite) Test_SaveAutofixChanges_
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_Autofix__multiple_modifications(c *check.C) {
+func (s *Suite) Test_Autofix__multiple_fixes(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--show-autofix", "--explain")
 
-       line := t.NewLine("fname", 1, "original")
+       line := t.NewLine("fileName", 1, "original")
 
        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.Warnf(SilentAutofixFormat)
                fix.ReplaceRegex(`(.)(.*)(.)`, "lriginao", 1) // XXX: the replacement should be "$3$2$1"
                fix.Apply()
        }
@@ -190,11 +276,11 @@ func (s *Suite) Test_Autofix__multiple_m
        c.Check(line.autofix, check.NotNil)
        c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lriginao\n"))
        t.CheckOutputLines(
-               "AUTOFIX: fname:1: Replacing \"original\" with \"lriginao\".")
+               "AUTOFIX: fileName:1: Replacing \"original\" with \"lriginao\".")
 
        {
                fix := line.Autofix()
-               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.Warnf(SilentAutofixFormat)
                fix.Replace("i", "u")
                fix.Apply()
        }
@@ -203,11 +289,11 @@ func (s *Suite) Test_Autofix__multiple_m
        c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lruginao\n"))
        c.Check(line.raw[0].textnl, equals, "lruginao\n")
        t.CheckOutputLines(
-               "AUTOFIX: fname:1: Replacing \"i\" with \"u\".")
+               "AUTOFIX: fileName:1: Replacing \"i\" with \"u\".")
 
        {
                fix := line.Autofix()
-               fix.Warnf("Silent-Magic-Diagnostic")
+               fix.Warnf(SilentAutofixFormat)
                fix.Replace("lruginao", "middle")
                fix.Apply()
        }
@@ -216,67 +302,140 @@ func (s *Suite) Test_Autofix__multiple_m
        c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "middle\n"))
        c.Check(line.raw[0].textnl, equals, "middle\n")
        t.CheckOutputLines(
-               "AUTOFIX: fname:1: Replacing \"lruginao\" with \"middle\".")
+               "AUTOFIX: fileName:1: Replacing \"lruginao\" with \"middle\".")
+
+       c.Check(line.raw[0].textnl, equals, "middle\n")
+       t.CheckOutputEmpty()
 
        {
                fix := line.Autofix()
-               fix.Warnf("Silent-Magic-Diagnostic")
-               fix.InsertBefore("before")
+               fix.Warnf(SilentAutofixFormat)
+               fix.Delete()
                fix.Apply()
+       }
 
-               fix.Warnf("Silent-Magic-Diagnostic")
-               fix.InsertBefore("between before and middle")
-               fix.Apply()
+       c.Check(line.Autofix().RawText(), equals, "")
+       t.CheckOutputLines(
+               "AUTOFIX: fileName:1: Deleting this line.")
+}
 
-               fix.Warnf("Silent-Magic-Diagnostic")
-               fix.InsertAfter("between middle and after")
-               fix.Apply()
+func (s *Suite) Test_Autofix_Explain__without_explain_option(c *check.C) {
+       t := s.Init(c)
 
-               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()
-       }
+       line := t.NewLine("Makefile", 74, "line1")
+
+       fix := line.Autofix()
+       fix.Warnf("Please write row instead of line.")
+       fix.Replace("line", "row")
+       fix.Explain("Explanation")
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "WARN: Makefile:74: Please write row instead of line.")
+       c.Check(G.explanationsAvailable, equals, true)
+}
+
+func (s *Suite) Test_Autofix_Explain__default(c *check.C) {
+       t := s.Init(c)
 
-       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"})
-       t.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.",
+       t.SetupCommandLine("--explain")
+       line := t.NewLine("Makefile", 74, "line1")
+
+       fix := line.Autofix()
+       fix.Warnf("Please write row instead of line.")
+       fix.Replace("line", "row")
+       fix.Explain("Explanation")
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "WARN: Makefile:74: Please write row instead of line.",
                "",
-               "\tWhen inserting multiple lines, Apply must be called in-between.",
-               "\tOtherwise the changes are not described to the human reader.",
+               "\tExplanation",
                "")
+       c.Check(G.explanationsAvailable, equals, true)
+}
 
-       {
-               fix := line.Autofix()
-               fix.Warnf("Silent-Magic-Diagnostic")
-               fix.Delete()
-               fix.Apply()
-       }
+func (s *Suite) Test_Autofix_Explain__show_autofix(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--show-autofix", "--explain")
+       line := t.NewLine("Makefile", 74, "line1")
+
+       fix := line.Autofix()
+       fix.Warnf("Please write row instead of line.")
+       fix.Replace("line", "row")
+       fix.Explain("Explanation")
+       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"})
        t.CheckOutputLines(
-               "AUTOFIX: fname:1: Deleting this line.")
+               "WARN: Makefile:74: Please write row instead of line.",
+               "AUTOFIX: Makefile:74: Replacing \"line\" with \"row\".",
+               "",
+               "\tExplanation",
+               "")
+       c.Check(G.explanationsAvailable, equals, true)
 }
 
-func (s *Suite) Test_Autofix__show_autofix_and_source(c *check.C) {
+func (s *Suite) Test_Autofix_Explain__autofix(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--autofix", "--explain")
+       line := t.NewLine("Makefile", 74, "line1")
+
+       fix := line.Autofix()
+       fix.Warnf("Please write row instead of line.")
+       fix.Replace("line", "row")
+       fix.Explain("Explanation")
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "AUTOFIX: Makefile:74: Replacing \"line\" with \"row\".")
+       c.Check(G.explanationsAvailable, equals, false) // Not necessary.
+}
+
+func (s *Suite) Test_Autofix_Explain__SilentAutofixFormat(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--explain")
+       line := t.NewLine("example.txt", 1, "Text")
+
+       fix := line.Autofix()
+       fix.Warnf(SilentAutofixFormat)
+       t.ExpectPanic(
+               func() { fix.Explain("Explanation for inserting a line before.") },
+               "Pkglint internal error: Autofix: Silent fixes cannot have an explanation.")
+}
+
+// To combine a silent diagnostic with an explanation, two separate autofixes
+// are necessary.
+func (s *Suite) Test_Autofix_Explain__silent_with_diagnostic(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--explain")
+       line := t.NewLine("example.txt", 1, "Text")
+
+       fix := line.Autofix()
+       fix.Warnf(SilentAutofixFormat)
+       fix.InsertBefore("before")
+       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()
+
+       t.CheckOutputLines(
+               "NOTE: example.txt:1: This diagnostic is necessary for the following explanation.",
+               "",
+               "\tWhen inserting multiple lines, Apply must be called in-between.",
+               "\tOtherwise the changes are not described to the human reader.",
+               "")
+       c.Check(fix.RawText(), equals, "Text\n")
+}
+
+func (s *Suite) Test_Autofix__show_autofix_and_source_continuation_line(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--show-autofix", "--source")
@@ -285,7 +444,7 @@ func (s *Suite) Test_Autofix__show_autof
                "# before \\",
                "The old song \\",
                "after")
-       line := mklines.lines[1]
+       line := mklines.lines.Lines[1]
 
        {
                fix := line.Autofix()
@@ -338,6 +497,53 @@ func (s *Suite) Test_Autofix_Delete(c *c
                "-\tto be deleted")
 }
 
+// Deleting a line from a Makefile also deletes its continuation lines.
+func (s *Suite) Test_Autofix_Delete__continuation_line(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--show-autofix", "--source")
+       mklines := t.SetupFileMkLines("Makefile",
+               MkRcsID,
+               "# line 1 \\",
+               "continued")
+       line := mklines.lines.Lines[1]
+
+       fix := line.Autofix()
+       fix.Warnf("Dummy warning.")
+       fix.Delete()
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "WARN: ~/Makefile:2--3: Dummy warning.",
+               "AUTOFIX: ~/Makefile:2: Deleting this line.",
+               "AUTOFIX: ~/Makefile:3: Deleting this line.",
+               "-\t# line 1 \\",
+               "-\tcontinued")
+}
+
+func (s *Suite) Test_Autofix_Delete__combined_with_insert(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--show-autofix", "--source")
+       line := t.NewLine("Makefile", 30, "to be deleted")
+
+       fix := line.Autofix()
+       fix.Warnf("This line should be replaced completely.")
+       fix.Delete()
+       fix.InsertAfter("below")
+       fix.InsertBefore("above")
+       fix.Apply()
+
+       t.CheckOutputLines(
+               "WARN: Makefile:30: This line should be replaced completely.",
+               "AUTOFIX: Makefile:30: Deleting this line.",
+               "AUTOFIX: Makefile:30: Inserting a line \"below\" after this line.",
+               "AUTOFIX: Makefile:30: Inserting a line \"above\" before this line.",
+               "+\tabove",
+               "-\tto be deleted",
+               "+\tbelow")
+}
+
 // 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) {
@@ -349,36 +555,32 @@ func (s *Suite) Test_Autofix__suppress_u
                "line2",
                "line3")
 
-       lines[0].Warnf("This warning is not shown since it is not part of a fix.")
+       lines.Lines[0].Warnf("This warning is not shown since it is not part of a fix.")
 
-       fix := lines[1].Autofix()
+       fix := lines.Lines[1].Autofix()
        fix.Warnf("Something's wrong here.")
-       fix.ReplaceRegex(`.`, "X", -1)
+       fix.ReplaceRegex(`.....`, "XXX", 1)
        fix.Apply()
 
        fix.Warnf("Since XXX marks are usually not fixed, use TODO instead to draw attention.")
        fix.Replace("XXX", "TODO")
        fix.Apply()
 
-       lines[2].Warnf("Neither is this warning shown.")
+       lines.Lines[2].Warnf("Neither is this warning shown.")
 
        t.CheckOutputLines(
                "WARN: Makefile:2: Something's wrong here.",
-               "AUTOFIX: Makefile:2: Replacing \"l\" with \"X\".",
-               "AUTOFIX: Makefile:2: Replacing \"i\" with \"X\".",
-               "AUTOFIX: Makefile:2: Replacing \"n\" with \"X\".",
-               "AUTOFIX: Makefile:2: Replacing \"e\" with \"X\".",
-               "AUTOFIX: Makefile:2: Replacing \"2\" with \"X\".",
+               "AUTOFIX: Makefile:2: Replacing \"line2\" with \"XXX\".",
                "-\tline2",
-               "+\tXXXXX",
+               "+\tXXX",
                "",
                "WARN: Makefile:2: Since XXX marks are usually not fixed, use TODO instead to draw attention.",
                "AUTOFIX: Makefile:2: Replacing \"XXX\" with \"TODO\".",
                "-\tline2",
-               "+\tTODOXX")
+               "+\tTODO")
 }
 
-// If an Autofix doesn't do anything it must not log any diagnostics.
+// If an Autofix doesn't do anything, it must not log any diagnostics.
 func (s *Suite) Test_Autofix__noop_replace(c *check.C) {
        t := s.Init(c)
 
@@ -393,9 +595,13 @@ func (s *Suite) Test_Autofix__noop_repla
        t.CheckOutputEmpty()
 }
 
-// When using Autofix.CustomFix, it is tricky to get all the details right.
+// When using Autofix.Custom, it is tricky to get all the details right.
 // For best results, see the existing examples and the documentation.
-func (s *Suite) Test_Autofix_Custom(c *check.C) {
+//
+// Since this custom fix only operates on the text of the current line,
+// it can handle both the --show-autofix and the --autofix cases using
+// the same code.
+func (s *Suite) Test_Autofix_Custom__in_memory(c *check.C) {
        t := s.Init(c)
 
        lines := t.NewLines("Makefile",
@@ -406,117 +612,165 @@ func (s *Suite) Test_Autofix_Custom(c *c
        doFix := func(line Line) {
                fix := line.Autofix()
                fix.Warnf("Please write in ALL-UPPERCASE.")
-               fix.Custom(func(printAutofix, autofix bool) {
+               fix.Custom(func(showAutofix, autofix bool) {
                        fix.Describef(int(line.firstLine), "Converting to uppercase")
-                       if printAutofix || autofix {
+                       if showAutofix || autofix {
                                line.Text = strings.ToUpper(line.Text)
                        }
                })
                fix.Apply()
        }
 
-       doFix(lines[0])
+       doFix(lines.Lines[0])
 
        t.CheckOutputLines(
                "WARN: Makefile:1: Please write in ALL-UPPERCASE.")
 
        t.SetupCommandLine("--show-autofix")
 
-       doFix(lines[1])
+       doFix(lines.Lines[1])
 
        t.CheckOutputLines(
                "WARN: Makefile:2: Please write in ALL-UPPERCASE.",
                "AUTOFIX: Makefile:2: Converting to uppercase")
-       c.Check(lines[1].Text, equals, "LINE2")
+       c.Check(lines.Lines[1].Text, equals, "LINE2")
 
        t.SetupCommandLine("--autofix")
 
-       doFix(lines[2])
+       doFix(lines.Lines[2])
 
        t.CheckOutputLines(
                "AUTOFIX: Makefile:3: Converting to uppercase")
-       c.Check(lines[2].Text, equals, "LINE3")
-}
-
-func (s *Suite) Test_Autofix_Explain(c *check.C) {
-       t := s.Init(c)
-
-       line := t.NewLine("Makefile", 74, "line1")
-
-       fix := line.Autofix()
-       fix.Warnf("Please write row instead of line.")
-       fix.Replace("line", "row")
-       fix.Explain("Explanation")
-       fix.Apply()
-
-       t.CheckOutputLines(
-               "WARN: Makefile:74: Please write row instead of line.")
-       c.Check(G.explanationsAvailable, equals, true)
+       c.Check(lines.Lines[2].Text, equals, "LINE3")
 }
 
 // Since the diagnostic doesn't contain the string "few", nothing happens.
-func (s *Suite) Test_Autofix__skip(c *check.C) {
+func (s *Suite) Test_Autofix_skip(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--only", "few", "--autofix")
 
-       lines := t.SetupFileLines("fname",
-               "111 222 333 444 555")
+       mklines := t.SetupFileMkLines("fileName",
+               "VAR=\t111 222 333 444 555 \\",
+               "666")
+       lines := mklines.lines
 
-       fix := lines[0].Autofix()
+       fix := lines.Lines[0].Autofix()
        fix.Warnf("Many.")
        fix.Explain(
                "Explanation.")
+
+       // None of the following actions has any effect because of the --only option above.
        fix.Replace("111", "___")
        fix.ReplaceAfter(" ", "222", "___")
        fix.ReplaceRegex(`\d+`, "___", 1)
        fix.InsertBefore("before")
        fix.InsertAfter("after")
        fix.Delete()
-       fix.Custom(func(printAutofix, autofix bool) {})
-       fix.Realign(dummyMkLine, 32)
+       fix.Custom(func(showAutofix, autofix bool) {})
+       fix.Realign(mklines.mklines[0], 32)
+
        fix.Apply()
 
        SaveAutofixChanges(lines)
 
        t.CheckOutputEmpty()
-       t.CheckFileLines("fname",
-               "111 222 333 444 555")
-       c.Check(lines[0].raw[0].textnl, equals, "111 222 333 444 555\n")
+       t.CheckFileLines("fileName",
+               "VAR=\t111 222 333 444 555 \\",
+               "666")
+       c.Check(fix.RawText(), equals, ""+
+               "VAR=\t111 222 333 444 555 \\\n"+
+               "666\n")
+}
+
+// Demonstrates how to filter log messages.
+// The --autofix option can restrict the fixes to exactly one group or topic.
+func (s *Suite) Test_Autofix_Apply__only(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--autofix", "--source", "--only", "interesting")
+       line := t.NewLine("Makefile", 27, "The old song")
+
+       // Is completely ignored, including any autofixes.
+       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()
+
+       t.CheckOutputLines(
+               "AUTOFIX: Makefile:27: Replacing \"old\" with \"new2\".",
+               "-\tThe old song",
+               "+\tThe new2 song")
 }
 
 func (s *Suite) Test_Autofix_Apply__panic(c *check.C) {
        t := s.Init(c)
 
-       line := t.NewLine("filename", 123, "text")
+       line := t.NewLine("fileName", 123, "text")
 
-       c.Assert(func() {
-               fix := line.Autofix()
-               fix.Apply()
-       }, check.Panics, "Each autofix must have a diagnostic.")
+       t.ExpectPanic(
+               func() {
+                       fix := line.Autofix()
+                       fix.Apply()
+               },
+               "Pkglint internal error: Each autofix must have a log level and a diagnostic.")
+
+       t.ExpectPanic(
+               func() {
+                       fix := line.Autofix()
+                       fix.Replace("from", "to")
+                       fix.Apply()
+               },
+               "Pkglint internal error: Autofix: The diagnostic must be given before the action.")
+
+       t.ExpectPanic(
+               func() {
+                       fix := line.Autofix()
+                       fix.Warnf("Warning without period")
+                       fix.Apply()
+               },
+               "Pkglint internal error: Autofix: format \"Warning without period\" must end with a period.")
+}
 
-       c.Assert(func() {
-               fix := line.Autofix()
-               fix.Replace("from", "to")
-               fix.Apply()
-       }, check.Panics, "Autofix: The diagnostic must be given before the action.")
+// Ensures that empty lines are logged between the diagnostics,
+// even when combining normal warnings and autofix warnings.
+//
+// Up to 2018-10-27, pkglint didn't insert the required empty line in this case.
+func (s *Suite) Test_Autofix_Apply__explanation_followed_by_note(c *check.C) {
+       t := s.Init(c)
 
-       c.Assert(func() {
-               fix := line.Autofix()
-               fix.Warnf("Warning without period")
-               fix.Apply()
-       }, check.Panics, "Autofix: format \"Warning without period\" must end with a period.")
+       t.SetupCommandLine("--source")
+       line := t.NewLine("README.txt", 123, "text")
+
+       fix := line.Autofix()
+       fix.Warnf("A warning with autofix.")
+       fix.Explain("Explanation.")
+       fix.Replace("text", "Text")
+       fix.Apply()
+
+       line.Notef("A note without fix.")
+
+       t.CheckOutputLines(
+               ">\ttext",
+               "WARN: README.txt:123: A warning with autofix.",
+               "",
+               ">\ttext",
+               "NOTE: README.txt:123: A note without fix.")
 }
 
-func (s *Suite) Test_Autofix_Apply__file_removed(c *check.C) {
+func (s *Suite) Test_SaveAutofixChanges__file_removed(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--autofix")
        lines := t.SetupFileLines("subdir/file.txt",
                "line 1")
-       os.RemoveAll(t.File("subdir"))
+       _ = os.RemoveAll(t.File("subdir"))
 
-       fix := lines[0].Autofix()
+       fix := lines.Lines[0].Autofix()
        fix.Warnf("Should start with an uppercase letter.")
        fix.Replace("line", "Line")
        fix.Apply()
@@ -528,7 +782,7 @@ func (s *Suite) Test_Autofix_Apply__file
                "ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot write: .*\n")
 }
 
-func (s *Suite) Test_Autofix_Apply__file_busy_Windows(c *check.C) {
+func (s *Suite) Test_SaveAutofixChanges__file_busy_Windows(c *check.C) {
        t := s.Init(c)
 
        if runtime.GOOS != "windows" {
@@ -544,7 +798,7 @@ func (s *Suite) Test_Autofix_Apply__file
        defer openFile.Close()
        c.Check(err, check.IsNil)
 
-       fix := lines[0].Autofix()
+       fix := lines.Lines[0].Autofix()
        fix.Warnf("Should start with an uppercase letter.")
        fix.Replace("line", "Line")
        fix.Apply()
@@ -553,15 +807,15 @@ func (s *Suite) Test_Autofix_Apply__file
 
        c.Check(t.Output(), check.Matches, ""+
                "AUTOFIX: ~/subdir/file.txt:1: Replacing \"line\" with \"Line\".\n"+
-               "ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot overwrite with auto-fixed content: .*\n")
+               "ERROR: ~/subdir/file.txt.pkglint.tmp: Cannot overwrite with autofixed content: .*\n")
 }
 
-// This test tests the highly unlikely situation in which a file is loaded
+// This test covers the highly unlikely situation in which a file is loaded
 // by pkglint, and just before writing the autofixed content back, another
 // process takes the file and replaces it with a directory of the same name.
 //
 // 100% code coverage sometimes requires creativity. :)
-func (s *Suite) Test_Autofix_Apply__file_converted_to_directory(c *check.C) {
+func (s *Suite) Test_SaveAutofixChanges__cannot_overwrite(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--autofix")
@@ -571,7 +825,7 @@ func (s *Suite) Test_Autofix_Apply__file
        c.Check(os.RemoveAll(t.File("file.txt")), check.IsNil)
        c.Check(os.MkdirAll(t.File("file.txt"), 0777), check.IsNil)
 
-       fix := lines[0].Autofix()
+       fix := lines.Lines[0].Autofix()
        fix.Warnf("Should start with an uppercase letter.")
        fix.Replace("line", "Line")
        fix.Apply()
@@ -580,5 +834,22 @@ func (s *Suite) Test_Autofix_Apply__file
 
        c.Check(t.Output(), check.Matches, ""+
                "AUTOFIX: ~/file.txt:1: Replacing \"line\" with \"Line\".\n"+
-               "ERROR: ~/file.txt.pkglint.tmp: Cannot overwrite with auto-fixed content: .*\n")
+               "ERROR: ~/file.txt.pkglint.tmp: Cannot overwrite with autofixed content: .*\n")
+}
+
+// RawText returns the raw text of the fixed line, including line ends.
+// This may differ from the original text when the --show-autofix
+// or --autofix options are enabled.
+func (fix *Autofix) RawText() string {
+       var text strings.Builder
+       for _, lineBefore := range fix.linesBefore {
+               text.WriteString(lineBefore)
+       }
+       for _, raw := range fix.line.raw {
+               text.WriteString(raw.textnl)
+       }
+       for _, lineAfter := range fix.linesAfter {
+               text.WriteString(lineAfter)
+       }
+       return text.String()
 }
Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.11 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.12
--- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.11        Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Wed Nov  7 20:58:23 2018
@@ -3,7 +3,6 @@ package main
 import (
        "io/ioutil"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "os"
        "path/filepath"
        "sort"
@@ -30,16 +29,16 @@ type Pkgsrc struct {
 
        PkgOptions map[string]string // "x11" => "Provides X11 support"
 
-       suggestedUpdates    []SuggestedUpdate  //
-       suggestedWipUpdates []SuggestedUpdate  //
-       LastChange          map[string]*Change //
-       latest              map[string]string  // "lang/php[0-9]*" => "lang/php70"
+       suggestedUpdates    []SuggestedUpdate   //
+       suggestedWipUpdates []SuggestedUpdate   //
+       LastChange          map[string]*Change  //
+       listVersions        map[string][]string // See ListVersions
 
        UserDefinedVars Scope               // Used for checking BUILD_DEFS
        Deprecated      map[string]string   //
        vartypes        map[string]*Vartype // varcanon => type
 
-       Hashes       map[string]*Hash // Maps "alg:fname" => hash (inter-package check).
+       Hashes       map[string]*Hash // Maps "alg:fileName" => hash (inter-package check).
        UsedLicenses map[string]bool  // Maps "license name" => true (inter-package check).
 }
 
@@ -54,7 +53,7 @@ func NewPkgsrc(dir string) *Pkgsrc {
                nil,
                nil,
                make(map[string]*Change),
-               make(map[string]string),
+               make(map[string][]string),
                NewScope(),
                make(map[string]string),
                make(map[string]*Vartype),
@@ -152,13 +151,30 @@ func (src *Pkgsrc) LoadInfrastructure() 
 }
 
 // Latest returns the latest package matching the given pattern.
-// It searches the `category` for subdirectories matching the given
-// regular expression, and returns the `repl` string, in which the
-// placeholder is filled with the best result.
+// It searches the category for subdirectories matching the given
+// regular expression, takes the latest of them and replaces its
+// name with repl.
 //
 // Example:
-//  Latest("lang", `^php[0-9]+$`, "../../lang/$0") => "../../lang/php72"
+//  Latest("lang", `^php[0-9]+$`, "../../lang/$0")
+//      => "../../lang/php72"
 func (src *Pkgsrc) Latest(category string, re regex.Pattern, repl string) string {
+       versions := src.ListVersions(category, re, repl, true)
+
+       if len(versions) > 0 {
+               return versions[len(versions)-1]
+       }
+       return ""
+}
+
+// ListVersions searches the category for subdirectories matching the given
+// regular expression, replaces their names with repl and returns a slice
+// of them, properly sorted from early to late.
+//
+// Example:
+//  ListVersions("lang", `^php[0-9]+$`, "php-$0")
+//      => {"php-53", "php-56", "php-73"}
+func (src *Pkgsrc) ListVersions(category string, re regex.Pattern, repl string, errorIfEmpty bool) []string {
        if G.Testing {
                G.Assertf(
                        hasPrefix(string(re), "^") && hasSuffix(string(re), "$"),
@@ -166,30 +182,35 @@ func (src *Pkgsrc) Latest(category strin
        }
 
        cacheKey := category + "/" + string(re) + " => " + repl
-       if latest, found := src.latest[cacheKey]; found {
+       if latest, found := src.listVersions[cacheKey]; found {
                return latest
        }
 
        categoryDir := src.File(category)
-       error := func() string {
-               dummyLine.Errorf("Cannot find latest version of %q in %q.", re, categoryDir)
-               src.latest[cacheKey] = ""
-               return ""
-       }
-
-       fileInfos, err := ioutil.ReadDir(categoryDir)
-       if err != nil {
-               return error()
+       error := func() []string {
+               if errorIfEmpty {
+                       dummyLine.Errorf("Cannot find package versions of %q in %q.", re, categoryDir)
+               }
+               src.listVersions[cacheKey] = nil
+               return nil
        }
 
        var names []string
-       for _, fileInfo := range fileInfos {
+       for _, fileInfo := range src.ReadDir(category) {
                name := fileInfo.Name()
                if matches(name, re) {
                        names = append(names, name)
                }
        }
+       if len(names) == 0 {
+               return error()
+       }
 
+       // In the pkgsrc directories, the major versions of packages are
+       // written without dots, which leads to ambiguities:
+       //
+       // databases/postgresql: 94 < 95 < 96 < 10 < 11
+       // lang/go: go19 < go110 < go111 < go2
        keys := make(map[string]int)
        for _, name := range names {
                if m, pkgbase, versionStr := match2(name, `^(\D+)(\d+)$`); m {
@@ -197,6 +218,11 @@ func (src *Pkgsrc) Latest(category strin
                        if pkgbase == "postgresql" && version < 60 {
                                version = 10 * version
                        }
+                       if pkgbase == "go" {
+                               major, _ := strconv.Atoi(versionStr[:1])
+                               minor, _ := strconv.Atoi(versionStr[1:])
+                               version = 100*major + minor
+                       }
                        keys[name] = version
                }
        }
@@ -208,16 +234,13 @@ func (src *Pkgsrc) Latest(category strin
                return naturalLess(names[i], names[j])
        })
 
-       latest := ""
-       for _, name := range names {
-               latest = replaceAll(name, re, repl)
-       }
-       if latest == "" {
-               return error()
+       var repls = make([]string, len(names), len(names))
+       for i, name := range names {
+               repls[i] = replaceAll(name, re, repl)
        }
 
-       src.latest[cacheKey] = latest
-       return latest
+       src.listVersions[cacheKey] = repls
+       return repls
 }
 
 // loadTools loads the tool definitions from `mk/tools/*`.
@@ -254,7 +277,7 @@ func (src *Pkgsrc) loadTools() {
                {"true", "TRUE", AfterPrefsMk}}
 
        for _, toolDef := range toolDefs {
-               tools.defTool(toolDef.Name, toolDef.Varname, true, toolDef.Validity)
+               tools.def(toolDef.Name, toolDef.Varname, true, toolDef.Validity)
        }
 
        for _, basename := range toolFiles {
@@ -322,7 +345,7 @@ func (src *Pkgsrc) loadUntypedVars() {
 
        handleMkFile := func(path string) {
                mklines := LoadMk(path, 0)
-               if mklines != nil {
+               if mklines != nil && len(mklines.mklines) > 0 {
                        mklines.ForEach(handleLine)
                }
        }
@@ -338,10 +361,14 @@ func (src *Pkgsrc) loadUntypedVars() {
        _ = filepath.Walk(src.File("mk"), handleFile)
 }
 
-func (src *Pkgsrc) parseSuggestedUpdates(lines []Line) []SuggestedUpdate {
+func (src *Pkgsrc) parseSuggestedUpdates(lines Lines) []SuggestedUpdate {
+       if lines == nil {
+               return nil
+       }
+
        var updates []SuggestedUpdate
        state := 0
-       for _, line := range lines {
+       for _, line := range lines.Lines {
                text := line.Text
 
                if state == 0 && text == "Suggested package updates" {
@@ -355,7 +382,7 @@ func (src *Pkgsrc) parseSuggestedUpdates
                }
 
                if state == 3 {
-                       if m, pkgname, comment := match2(text, `^\to\s(\S+)(?:\s*(.+))?$`); m {
+                       if m, pkgname, comment := match2(text, `^\to[\t ]([^\t ]+)(?:[\t ]*(.+))?$`); m {
                                if m, pkgbase, pkgversion := match2(pkgname, rePkgname); m {
                                        updates = append(updates, SuggestedUpdate{line, pkgbase, pkgversion, comment})
                                } else {
@@ -374,7 +401,7 @@ func (src *Pkgsrc) loadSuggestedUpdates(
        src.suggestedWipUpdates = src.parseSuggestedUpdates(Load(G.Pkgsrc.File("wip/TODO"), NotEmpty))
 }
 
-func (src *Pkgsrc) loadDocChangesFromFile(fname string) []*Change {
+func (src *Pkgsrc) loadDocChangesFromFile(fileName string) []*Change {
 
        parseChange := func(line Line) *Change {
                text := line.Text
@@ -408,17 +435,17 @@ func (src *Pkgsrc) loadDocChangesFromFil
        }
 
        year := ""
-       if m, yyyy := match1(fname, `-(\d+)$`); m && yyyy >= "2018" {
+       if m, yyyy := match1(fileName, `-(\d+)$`); m && yyyy >= "2018" {
                year = yyyy
        }
 
-       lines := Load(fname, MustSucceed|NotEmpty)
+       lines := Load(fileName, MustSucceed|NotEmpty)
        var changes []*Change
-       for _, line := range lines {
+       for _, line := range lines.Lines {
                if change := parseChange(line); change != nil {
                        changes = append(changes, change)
                        if year != "" && change.Date[0:4] != year {
-                               line.Warnf("Year %s for %s does not match the file name %s.", change.Date[0:4], change.Pkgpath, fname)
+                               line.Warnf("Year %s for %s does not match the file name %s.", change.Date[0:4], change.Pkgpath, fileName)
                        }
                        if len(changes) >= 2 && year != "" {
                                if prev := changes[len(changes)-2]; change.Date < prev.Date {
@@ -432,7 +459,7 @@ func (src *Pkgsrc) loadDocChangesFromFil
                                                "and which aren't.",
                                                "",
                                                "To prevent this kind of mistakes in the future, make sure that",
-                                               "your system time is correct and use \""+confMake+" cce\" to commit",
+                                               sprintf("your system time is correct and run %q to commit", bmake("cce")),
                                                "the changes entry.")
                                }
                        }
@@ -453,24 +480,24 @@ func (src *Pkgsrc) GetSuggestedPackageUp
 }
 
 func (src *Pkgsrc) loadDocChanges() {
-       docdir := G.Pkgsrc.File("doc")
-       files, err := ioutil.ReadDir(docdir)
-       if err != nil {
-               NewLineWhole(docdir).Fatalf("Cannot be read.")
+       docDir := src.File("doc")
+       files := src.ReadDir("doc")
+       if len(files) == 0 {
+               NewLineWhole(docDir).Fatalf("Cannot be read for loading the package changes.")
        }
 
-       var fnames []string
+       var fileNames []string
        for _, file := range files {
-               fname := file.Name()
-               if matches(fname, `^CHANGES-20\d\d$`) && fname >= "CHANGES-2011" {
-                       fnames = append(fnames, fname)
+               fileName := file.Name()
+               if matches(fileName, `^CHANGES-20\d\d$`) && fileName >= "CHANGES-2011" {
+                       fileNames = append(fileNames, fileName)
                }
        }
 
-       sort.Strings(fnames)
+       sort.Strings(fileNames)
        src.LastChange = make(map[string]*Change)
-       for _, fname := range fnames {
-               changes := src.loadDocChangesFromFile(docdir + "/" + fname)
+       for _, fileName := range fileNames {
+               changes := src.loadDocChangesFromFile(docDir + "/" + fileName)
                for _, change := range changes {
                        src.LastChange[change.Pkgpath] = change
                }
@@ -652,15 +679,35 @@ func (src *Pkgsrc) initDeprecatedVars() 
 }
 
 // Load loads the file relative to the pkgsrc top directory.
-func (src *Pkgsrc) Load(fileName string, options LoadOptions) []Line {
+func (src *Pkgsrc) Load(fileName string, options LoadOptions) Lines {
        return Load(src.File(fileName), options)
 }
 
 // LoadMk loads the Makefile relative to the pkgsrc top directory.
-func (src *Pkgsrc) LoadMk(fileName string, options LoadOptions) *MkLines {
+func (src *Pkgsrc) LoadMk(fileName string, options LoadOptions) MkLines {
        return LoadMk(src.File(fileName), options)
 }
 
+// ReadDir reads the file listing from the given directory (relative to the pkgsrc root),
+// filtering out any ignored files (CVS/*) and empty directories.
+func (src *Pkgsrc) ReadDir(dirName string) []os.FileInfo {
+       dir := src.File(dirName)
+       files, err := ioutil.ReadDir(dir)
+       if err != nil {
+               return nil
+       }
+
+       var relevantFiles []os.FileInfo
+       for _, dirent := range files {
+               name := dirent.Name()
+               if !dirent.IsDir() || !isIgnoredFilename(name) && !isEmptyDir(dir+"/"+name) {
+                       relevantFiles = append(relevantFiles, dirent)
+               }
+       }
+
+       return relevantFiles
+}
+
 // File resolves a file name relative to the pkgsrc top directory.
 //
 // Example:
@@ -690,20 +737,16 @@ func (src *Pkgsrc) IsBuildDef(varname st
 func (src *Pkgsrc) loadMasterSites() {
        mklines := src.LoadMk("mk/fetch/sites.mk", MustSucceed|NotEmpty)
 
-       nameToURL := src.MasterSiteVarToURL
-       urlToName := src.MasterSiteURLToVar
        for _, mkline := range mklines.mklines {
                if mkline.IsVarassign() {
                        varname := mkline.Varname()
                        if hasPrefix(varname, "MASTER_SITE_") && varname != "MASTER_SITE_BACKUP" {
                                for _, url := range fields(mkline.Value()) {
                                        if matches(url, `^(?:http://|https://|ftp://)`) {
-                                               if nameToURL[varname] == "" {
-                                                       nameToURL[varname] = url
-                                               }
-                                               urlToName[url] = varname
+                                               src.registerMasterSite(varname, url)
                                        }
                                }
+
                                // TODO: register variable type, to avoid redundant
                                // definitions in vardefs.go.
                        }
@@ -711,18 +754,28 @@ func (src *Pkgsrc) loadMasterSites() {
        }
 
        // Explicitly allowed, although not defined in mk/fetch/sites.mk.
-       nameToURL["MASTER_SITE_LOCAL"] = "ftp://ftp.NetBSD.org/pub/pkgsrc/distfiles/LOCAL_PORTS/";
+       src.registerMasterSite("MASTER_SITE_LOCAL", "ftp://ftp.NetBSD.org/pub/pkgsrc/distfiles/LOCAL_PORTS/";)
 
        if trace.Tracing {
-               trace.Stepf("Loaded %d MASTER_SITE_* URLs.", len(urlToName))
+               trace.Stepf("Loaded %d MASTER_SITE_* URLs.", len(G.Pkgsrc.MasterSiteURLToVar))
        }
 }
 
+func (src *Pkgsrc) registerMasterSite(varname, url string) {
+       nameToURL := src.MasterSiteVarToURL
+       urlToName := src.MasterSiteURLToVar
+
+       if nameToURL[varname] == "" {
+               nameToURL[varname] = url
+       }
+       urlToName[url] = varname
+}
+
 func (src *Pkgsrc) loadPkgOptions() {
        lines := src.Load("mk/defaults/options.description", MustSucceed)
 
-       for _, line := range lines {
-               if m, optname, optdescr := match2(line.Text, `^([-0-9a-z_+]+)(?:\s+(.*))?$`); m {
+       for _, line := range lines.Lines {
+               if m, optname, optdescr := match2(line.Text, `^([-0-9a-z_+]+)(?:[\t ]+(.*))?$`); m {
                        src.PkgOptions[optname] = optdescr
                } else {
                        line.Fatalf("Unknown line format: %s", line.Text)
@@ -738,10 +791,13 @@ func (src *Pkgsrc) VariableType(varname 
                defer trace.Call(varname, trace.Result(&vartype))()
        }
 
-       if vartype := src.vartypes[varname]; vartype != nil {
+       // When scanning mk/** for otherwise unknown variables, their type
+       // is set to BtUnknown. These variables must not override the guess
+       // based on the variable name.
+       if vartype = src.vartypes[varname]; vartype != nil && vartype.basicType != BtUnknown {
                return vartype
        }
-       if vartype := src.vartypes[varnameCanon(varname)]; vartype != nil {
+       if vartype = src.vartypes[varnameCanon(varname)]; vartype != nil && vartype.basicType != BtUnknown {
                return vartype
        }
 
@@ -799,6 +855,15 @@ func (src *Pkgsrc) VariableType(varname 
                gtype = &Vartype{lkNone, BtUnknown, allowAll, true}
        }
 
+       if gtype == nil {
+               if vartype = src.vartypes[varname]; vartype != nil {
+                       return vartype
+               }
+               if vartype = src.vartypes[varnameCanon(varname)]; vartype != nil {
+                       return vartype
+               }
+       }
+
        if trace.Tracing {
                if gtype != nil {
                        trace.Step2("The guessed type of %q is %q.", varname, gtype.String())
Index: pkgsrc/pkgtools/pkglint/files/shtokenizer.go
diff -u pkgsrc/pkgtools/pkglint/files/shtokenizer.go:1.11 pkgsrc/pkgtools/pkglint/files/shtokenizer.go:1.12
--- pkgsrc/pkgtools/pkglint/files/shtokenizer.go:1.11   Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/shtokenizer.go        Wed Nov  7 20:58:23 2018
@@ -82,8 +82,8 @@ func (p *ShTokenizer) shAtomPlain() *ShA
                return &ShAtom{shtWord, repl.Str(), shqSquot, nil}
        case repl.AdvanceStr("`"):
                return &ShAtom{shtWord, repl.Str(), shqBackt, nil}
-       case repl.AdvanceRegexp(`^#.*`):
-               return &ShAtom{shtComment, repl.Group(0), q, nil}
+       case repl.PeekByte() == '#':
+               return &ShAtom{shtComment, repl.AdvanceRest(), q, nil}
        case repl.AdvanceStr("$$("):
                return &ShAtom{shtSubshell, repl.Str(), shqSubsh, nil}
        }
@@ -132,7 +132,7 @@ func (p *ShTokenizer) shAtomBackt() *ShA
        return p.shAtomInternal(q, false, false)
 }
 
-// In pkgsrc, the $(...) subshell syntax is not used to preserve
+// In pkgsrc, the $(...) subshell syntax is not used, in order to preserve
 // compatibility with /bin/sh from Solaris 7.
 func (p *ShTokenizer) shAtomSubsh() *ShAtom {
        const q = shqSubsh
@@ -147,11 +147,11 @@ func (p *ShTokenizer) shAtomSubsh() *ShA
        case repl.AdvanceStr("`"):
                // FIXME: return &ShAtom{shtWord, repl.Str(), shqBackt, nil}
        case repl.AdvanceRegexp(`^#[^)]*`):
-               return &ShAtom{shtComment, repl.Group(0), q, nil}
+               return &ShAtom{shtComment, repl.Str(), q, nil}
        case repl.AdvanceStr(")"):
                return &ShAtom{shtWord, repl.Str(), shqPlain, nil}
        case repl.AdvanceRegexp(`^(?:[!#%*+,\-./0-9:=?@A-Z\[\]^_a-z{}~]+|\\[^$]|` + reShDollar + `)+`):
-               return &ShAtom{shtWord, repl.Group(0), q, nil}
+               return &ShAtom{shtWord, repl.Str(), q, nil}
        }
        return p.shOperator(q)
 }
@@ -185,7 +185,7 @@ func (p *ShTokenizer) shAtomBacktDquot()
        case repl.AdvanceStr("\""):
                return &ShAtom{shtWord, repl.Str(), shqBackt, nil}
        case repl.AdvanceRegexp(`^(?:[\t !%&()*+,\-./0-9:;<=>?@A-Z\[\]^_a-z{|}~]+|\\[^$]|` + reShDollar + `)+`):
-               return &ShAtom{shtWord, repl.Group(0), shqBacktDquot, nil}
+               return &ShAtom{shtWord, repl.Str(), shqBacktDquot, nil}
        }
        return nil
 }
@@ -197,7 +197,7 @@ func (p *ShTokenizer) shAtomBacktSquot()
        case repl.AdvanceStr("'"):
                return &ShAtom{shtWord, repl.Str(), shqBackt, nil}
        case repl.AdvanceRegexp(`^([\t !"#%&()*+,\-./0-9:;<=>?@A-Z\[\\\]^_` + "`" + `a-z{|}~]+|\$\$)+`):
-               return &ShAtom{shtWord, repl.Group(0), q, nil}
+               return &ShAtom{shtWord, repl.Str(), q, nil}
        }
        return nil
 }
@@ -208,7 +208,7 @@ func (p *ShTokenizer) shAtomSubshDquot()
        case repl.AdvanceStr("\""):
                return &ShAtom{shtWord, repl.Str(), shqSubsh, nil}
        case repl.AdvanceRegexp(`^(?:[\t !%&()*+,\-./0-9:;<=>?@A-Z\[\]^_a-z{|}~]+|\\[^$]|` + reShDollar + `)+`):
-               return &ShAtom{shtWord, repl.Group(0), shqSubshDquot, nil}
+               return &ShAtom{shtWord, repl.Str(), shqSubshDquot, nil}
        }
        return nil
 }
@@ -220,7 +220,7 @@ func (p *ShTokenizer) shAtomSubshSquot()
        case repl.AdvanceStr("'"):
                return &ShAtom{shtWord, repl.Str(), shqSubsh, nil}
        case repl.AdvanceRegexp(`^([\t !"#%&()*+,\-./0-9:;<=>?@A-Z\[\\\]^_` + "`" + `a-z{|}~]+|\$\$)+`):
-               return &ShAtom{shtWord, repl.Group(0), q, nil}
+               return &ShAtom{shtWord, repl.Str(), q, nil}
        }
        return nil
 }
@@ -232,7 +232,7 @@ func (p *ShTokenizer) shAtomDquotBacktDq
        case repl.AdvanceStr("\""):
                return &ShAtom{shtWord, repl.Str(), shqDquotBackt, nil}
        case repl.AdvanceRegexp(`^(?:[\t !%&()*+,\-./0-9:;<=>?@A-Z\[\]^_a-z{|}~]+|\\[^$]|` + reShDollar + `)+`):
-               return &ShAtom{shtWord, repl.Group(0), q, nil}
+               return &ShAtom{shtWord, repl.Str(), q, nil}
        }
        return nil
 }
@@ -243,7 +243,7 @@ func (p *ShTokenizer) shAtomDquotBacktSq
        case repl.AdvanceStr("'"):
                return &ShAtom{shtWord, repl.Str(), shqDquotBackt, nil}
        case repl.AdvanceRegexp(`^(?:[\t !"#%()*+,\-./0-9:;<=>?@A-Z\[\]^_a-z{|}~]+|\\[^$]|\\\$\$|\$\$)+`):
-               return &ShAtom{shtWord, repl.Group(0), shqDquotBacktSquot, nil}
+               return &ShAtom{shtWord, repl.Str(), shqDquotBacktSquot, nil}
        }
        return nil
 }
@@ -269,7 +269,7 @@ loop:
                switch {
                case repl.AdvanceRegexp(`^[!#%*+,\-./0-9:=?@A-Z\[\]^_a-z{}~]+`):
                case dquot && repl.AdvanceRegexp(`^[\t &'();<>|]+`):
-               case squot && repl.AdvanceByte('`'):
+               case squot && repl.AdvanceStr("`"):
                case squot && repl.AdvanceRegexp(`^[\t "&();<>\\|]+`):
                case squot && repl.AdvanceStr("$$"):
                case squot:
@@ -303,7 +303,7 @@ func (p *ShTokenizer) shOperator(q ShQuo
                repl.AdvanceStr("&"):
                return &ShAtom{shtOperator, repl.Str(), q, nil}
        case repl.AdvanceRegexp(`^\d*(?:<<-|<<|<&|<>|>>|>&|>\||<|>)`):
-               return &ShAtom{shtOperator, repl.Group(0), q, nil}
+               return &ShAtom{shtOperator, repl.Str(), q, nil}
        }
        return nil
 }
@@ -364,7 +364,7 @@ nextAtom:
        }
        repl.Reset(mark)
 
-       G.Assertf(len(atoms) != 0, "ShTokenizer.ShToken")
+       G.Assertf(len(atoms) > 0, "ShTokenizer.ShToken")
        return NewShToken(repl.Since(initialMark), atoms...)
 }
 

Index: pkgsrc/pkgtools/pkglint/files/buildlink3.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.13 pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.14
--- pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.13    Thu Aug  9 20:21:42 2018
+++ pkgsrc/pkgtools/pkglint/files/buildlink3.go Wed Nov  7 20:58:22 2018
@@ -2,20 +2,32 @@ package main
 
 import (
        "netbsd.org/pkglint/pkgver"
-       "netbsd.org/pkglint/trace"
        "strings"
 )
 
-func ChecklinesBuildlink3Mk(mklines *MkLines) {
+type Buildlink3Checker struct {
+       mklines          MkLines
+       pkgbase          string
+       pkgbaseLine      MkLine
+       abiLine, apiLine MkLine
+       abi, api         *DependencyPattern
+}
+
+func ChecklinesBuildlink3Mk(mklines MkLines) {
+       (&Buildlink3Checker{mklines: mklines}).Check()
+}
+
+func (ck *Buildlink3Checker) Check() {
+       mklines := ck.mklines
        if trace.Tracing {
-               defer trace.Call1(mklines.lines[0].Filename)()
+               defer trace.Call1(mklines.lines.FileName)()
        }
 
        mklines.Check()
 
        exp := NewMkExpecter(mklines)
 
-       for exp.AdvanceIfPrefix("#") {
+       for exp.AdvanceIf(MkLine.IsComment) {
                line := exp.PreviousLine()
                // See pkgtools/createbuildlink/files/createbuildlink
                if hasPrefix(line.Text, "# XXX This file was created automatically") {
@@ -23,177 +35,204 @@ func ChecklinesBuildlink3Mk(mklines *MkL
                }
        }
 
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
+       exp.ExpectEmptyLine()
 
-       if exp.AdvanceIfMatches(`^BUILDLINK_DEPMETHOD\.(\S+)\?=.*$`) {
+       if exp.AdvanceIfMatches(`^BUILDLINK_DEPMETHOD\.([^\t ]+)\?=.*$`) {
                exp.PreviousLine().Warnf("This line belongs inside the .ifdef block.")
                for exp.AdvanceIfEquals("") {
                }
        }
 
-       pkgbaseLine, pkgbase := exp.CurrentLine(), ""
-       var abiLine, apiLine Line
-       var abi, api *DependencyPattern
+       if !ck.checkFirstParagraph(exp) {
+               return
+       }
+       if !ck.checkSecondParagraph(exp) {
+               return
+       }
+       if !ck.checkMainPart(exp) {
+               return
+       }
+
+       // Fourth paragraph: Cleanup, corresponding to the first paragraph.
+       if !exp.ExpectText("BUILDLINK_TREE+=\t-" + ck.pkgbase) {
+               return
+       }
+
+       if !exp.EOF() {
+               exp.CurrentLine().Warnf("The file should end here.")
+       }
+
+       if G.Pkg != nil {
+               // TODO: Commenting this line doesn't make any test fail, but it should.
+               G.Pkg.checklinesBuildlink3Inclusion(mklines)
+       }
+
+       mklines.SaveAutofixChanges()
+}
+
+func (ck *Buildlink3Checker) checkFirstParagraph(exp *MkExpecter) bool {
 
        // First paragraph: Introduction of the package identifier
-       if !exp.AdvanceIfMatches(`^BUILDLINK_TREE\+=\s*(\S+)$`) {
+       if !exp.AdvanceIfMatches(`^BUILDLINK_TREE\+=[\t ]*([^\t ]+)$`) {
                exp.CurrentLine().Warnf("Expected a BUILDLINK_TREE line.")
-               return
+               return false
        }
-       pkgbase = exp.Group(1)
+
+       pkgbase := exp.Group(1)
+       pkgbaseLine := exp.PreviousMkLine()
+
        if containsVarRef(pkgbase) {
-               warned := false
-               for _, pair := range [...]struct{ varuse, simple string }{
-                       {"${PYPKGPREFIX}", "py"},
-                       {"${RUBY_BASE}", "ruby"},
-                       {"${RUBY_PKGPREFIX}", "ruby"},
-                       {"${PHP_PKG_PREFIX}", "php"},
-               } {
-                       if contains(pkgbase, pair.varuse) {
-                               pkgbaseLine.Warnf("Please use %q instead of %q (also in other variables in this file).", pair.simple, pair.varuse)
-                               warned = true
-                       }
-               }
-               if !warned {
-                       if m, varuse := match1(pkgbase, `(\$\{\w+\})`); m {
-                               pkgbaseLine.Warnf("Please replace %q with a simple string (also in other variables in this file).", varuse)
-                               warned = true
-                       }
-               }
-               if warned {
-                       Explain(
-                               "Having variable package names in the BUILDLINK_TREE is not",
-                               "necessary, since other packages depend on this package only for",
-                               "a specific version of Python, Ruby or PHP.  Since these",
-                               "package identifiers are only used at build time, they should",
-                               "not include the specific version of the language interpreter.")
-               }
+               ck.checkVaruseInPkgbase(pkgbase, pkgbaseLine)
        }
+       exp.ExpectEmptyLine()
+       ck.pkgbase = pkgbase
+       ck.pkgbaseLine = pkgbaseLine
+       return true
+}
 
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
+// checkSecondParagraph checks the multiple inclusion protection and
+// introduces the uppercase package identifier.
+func (ck *Buildlink3Checker) checkSecondParagraph(exp *MkExpecter) bool {
+       pkgbase := ck.pkgbase
+       pkgbaseLine := ck.pkgbaseLine
 
-       // Second paragraph: multiple inclusion protection and introduction
-       // of the uppercase package identifier.
-       if !exp.AdvanceIfMatches(`^\.if !defined\((\S+)_BUILDLINK3_MK\)$`) {
-               return
+       if !exp.AdvanceIfMatches(`^\.if !defined\(([^\t ]+)_BUILDLINK3_MK\)$`) {
+               return false
        }
-       pkgupperLine, pkgupper := exp.PreviousLine(), exp.Group(1)
+       pkgupperLine, pkgupper := exp.PreviousMkLine(), exp.Group(1)
 
        if !exp.ExpectText(pkgupper + "_BUILDLINK3_MK:=") {
-               return
+               return false
        }
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
+       exp.ExpectEmptyLine()
 
        // See pkgtools/createbuildlink/files/createbuildlink, keyword PKGUPPER
        ucPkgbase := strings.ToUpper(strings.Replace(pkgbase, "-", "_", -1))
        if ucPkgbase != pkgupper && !containsVarRef(pkgbase) {
                pkgupperLine.Errorf("Package name mismatch between multiple-inclusion guard %q (expected %q) and package name %q (from %s).",
-                       pkgupper, ucPkgbase, pkgbase, pkgbaseLine.ReferenceFrom(pkgupperLine))
+                       pkgupper, ucPkgbase, pkgbase, pkgupperLine.RefTo(pkgbaseLine))
        }
        if G.Pkg != nil {
                if mkbase := G.Pkg.EffectivePkgbase; mkbase != "" && mkbase != pkgbase {
                        pkgbaseLine.Errorf("Package name mismatch between %q in this file and %q from %s.",
-                               pkgbase, mkbase, G.Pkg.EffectivePkgnameLine.ReferenceFrom(pkgbaseLine))
+                               pkgbase, mkbase, pkgbaseLine.RefTo(G.Pkg.EffectivePkgnameLine))
                }
        }
 
-       // Third paragraph: Package information.
-       indentLevel := 1 // The first .if is from the second paragraph.
-       for {
-               if exp.EOF() {
-                       exp.CurrentLine().Warnf("Expected \".endif\".")
-                       return
-               }
+       return true
+}
 
-               line := exp.CurrentLine()
-               mkline := mklines.mklines[exp.Index()]
+// Third paragraph: Package information.
+func (ck *Buildlink3Checker) checkMainPart(exp *MkExpecter) bool {
+       pkgbase := ck.pkgbase
+
+       // The first .if is from the second paragraph.
+       indentLevel := 1
+
+       for !exp.EOF() && indentLevel > 0 {
+               mkline := exp.CurrentMkLine()
+               exp.Advance()
+
+               switch {
+               case mkline.IsVarassign():
+                       ck.checkVarassign(exp, mkline, pkgbase)
 
-               if mkline.IsVarassign() {
-                       exp.Advance()
-                       varname, value := mkline.Varname(), mkline.Value()
-                       doCheck := false
+               case mkline.IsDirective() && mkline.Directive() == "if":
+                       indentLevel++
 
-                       if varname == "BUILDLINK_ABI_DEPENDS."+pkgbase {
-                               abiLine = line
-                               parser := NewParser(line, value, false)
-                               if dp := parser.Dependency(); dp != nil && parser.EOF() {
-                                       abi = dp
-                               }
-                               doCheck = true
-                       }
-                       if varname == "BUILDLINK_API_DEPENDS."+pkgbase {
-                               apiLine = line
-                               parser := NewParser(line, value, false)
-                               if dp := parser.Dependency(); dp != nil && parser.EOF() {
-                                       api = dp
-                               }
-                               doCheck = true
-                       }
-                       if doCheck && abi != nil && api != nil && abi.Pkgbase != api.Pkgbase && !hasPrefix(api.Pkgbase, "{") {
-                               abiLine.Warnf("Package name mismatch between ABI %q and API %q (from %s).",
-                                       abi.Pkgbase, api.Pkgbase, apiLine.ReferenceFrom(abiLine))
-                       }
-                       if doCheck {
-                               if abi != nil && abi.Lower != "" && !containsVarRef(abi.Lower) {
-                                       if api != nil && api.Lower != "" && !containsVarRef(api.Lower) {
-                                               if pkgver.Compare(abi.Lower, api.Lower) < 0 {
-                                                       abiLine.Warnf("ABI version %q should be at least API version %q (see %s).",
-                                                               abi.Lower, api.Lower, apiLine.ReferenceFrom(abiLine))
-                                               }
-                                       }
-                               }
-                       }
+               case mkline.IsDirective() && mkline.Directive() == "endif":
+                       indentLevel--
+               }
+       }
 
-                       if varparam := mkline.Varparam(); varparam != "" && varparam != pkgbase {
-                               if hasPrefix(varname, "BUILDLINK_") && mkline.Varcanon() != "BUILDLINK_API_DEPENDS.*" {
-                                       line.Warnf("Only buildlink variables for %q, not %q may be set in this file.", pkgbase, varparam)
-                               }
-                       }
+       if indentLevel > 0 {
+               return false
+       }
 
-                       if varname == "pkgbase" {
-                               exp.AdvanceIfMatches(`^\.\s*include "../../mk/pkg-build-options\.mk"$`)
-                       }
+       if ck.apiLine == nil {
+               exp.CurrentLine().Warnf("Definition of BUILDLINK_API_DEPENDS is missing.")
+       }
+       exp.ExpectEmptyLine()
+       return true
+}
 
-               } else if exp.AdvanceIfEquals("") || exp.AdvanceIfPrefix("#") {
-                       // Comments and empty lines are fine here.
+func (ck *Buildlink3Checker) checkVarassign(exp *MkExpecter, mkline MkLine, pkgbase string) {
+       varname, value := mkline.Varname(), mkline.Value()
+       doCheck := false
 
-               } else if exp.AdvanceIfMatches(`^\.\s*include "\.\./\.\./([^/]+/[^/]+)/buildlink3\.mk"$`) ||
-                       exp.AdvanceIfMatches(`^\.\s*include "\.\./\.\./mk/(\S+)\.buildlink3\.mk"$`) {
-                       // TODO: Maybe check dependency lines.
+       if varname == "BUILDLINK_ABI_DEPENDS."+pkgbase {
+               ck.abiLine = mkline
+               parser := NewParser(mkline.Line, value, false)
+               if dp := parser.Dependency(); dp != nil && parser.EOF() {
+                       ck.abi = dp
+               }
+               doCheck = true
+       }
 
-               } else if exp.AdvanceIfMatches(`^\.if\s`) {
-                       indentLevel++
+       if varname == "BUILDLINK_API_DEPENDS."+pkgbase {
+               ck.apiLine = mkline
+               parser := NewParser(mkline.Line, value, false)
+               if dp := parser.Dependency(); dp != nil && parser.EOF() {
+                       ck.api = dp
+               }
+               doCheck = true
+       }
 
-               } else if exp.AdvanceIfMatches(`^\.endif.*$`) {
-                       indentLevel--
-                       if indentLevel == 0 {
-                               break
-                       }
+       if doCheck && ck.abi != nil && ck.api != nil && ck.abi.Pkgbase != ck.api.Pkgbase {
+               if !hasPrefix(ck.api.Pkgbase, "{") {
+                       ck.abiLine.Warnf("Package name mismatch between ABI %q and API %q (from %s).",
+                               ck.abi.Pkgbase, ck.api.Pkgbase, ck.abiLine.RefTo(ck.apiLine))
+               }
+       }
 
-               } else {
-                       if trace.Tracing {
-                               trace.Step1("Unchecked line %s in third paragraph.", exp.CurrentLine().Linenos())
+       if doCheck {
+               if ck.abi != nil && ck.abi.Lower != "" && !containsVarRef(ck.abi.Lower) {
+                       if ck.api != nil && ck.api.Lower != "" && !containsVarRef(ck.api.Lower) {
+                               if pkgver.Compare(ck.abi.Lower, ck.api.Lower) < 0 {
+                                       ck.abiLine.Warnf("ABI version %q should be at least API version %q (see %s).",
+                                               ck.abi.Lower, ck.api.Lower, ck.abiLine.RefTo(ck.apiLine))
+                               }
                        }
-                       exp.Advance()
                }
        }
-       if apiLine == nil {
-               exp.CurrentLine().Warnf("Definition of BUILDLINK_API_DEPENDS is missing.")
-       }
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
 
-       // Fourth paragraph: Cleanup, corresponding to the first paragraph.
-       if !exp.ExpectText("BUILDLINK_TREE+=\t-" + pkgbase) {
-               return
+       if varparam := mkline.Varparam(); varparam != "" && varparam != pkgbase {
+               if hasPrefix(varname, "BUILDLINK_") && mkline.Varcanon() != "BUILDLINK_API_DEPENDS.*" {
+                       mkline.Warnf("Only buildlink variables for %q, not %q may be set in this file.", pkgbase, varparam)
+               }
        }
+}
 
-       if !exp.EOF() {
-               exp.CurrentLine().Warnf("The file should end here.")
+func (ck *Buildlink3Checker) checkVaruseInPkgbase(pkgbase string, pkgbaseLine MkLine) {
+       checkSpecificVar := func(varuse, simple string) bool {
+               if contains(pkgbase, varuse) {
+                       pkgbaseLine.Warnf("Please use %q instead of %q (also in other variables in this file).", simple, varuse)
+                       return true
+               }
+               return false
+       }
+
+       warned := checkSpecificVar("${PYPKGPREFIX}", "py") ||
+               checkSpecificVar("${RUBY_BASE}", "ruby") ||
+               checkSpecificVar("${RUBY_PKGPREFIX}", "ruby") ||
+               checkSpecificVar("${PHP_PKG_PREFIX}", "php")
+
+       if !warned {
+               if m, varuse := match1(pkgbase, `(\$\{\w+\})`); m {
+                       pkgbaseLine.Warnf("Please replace %q with a simple string (also in other variables in this file).", varuse)
+                       warned = true
+               }
        }
 
-       if G.Pkg != nil {
-               G.Pkg.checklinesBuildlink3Inclusion(mklines)
+       if warned {
+               Explain(
+                       "The identifiers in the BUILDLINK_TREE variable should be plain",
+                       "strings that do not refer to any variable.",
+                       "",
+                       "Even for packages that depend on a specific version of a",
+                       "programming language, the plain name is enough since",
+                       "the version number of the programming language is stored elsewhere.",
+                       "Furthermore, these package identifiers are only used at build time,",
+                       "after the specific version has been decided.")
        }
-
-       SaveAutofixChanges(mklines.lines)
 }
Index: pkgsrc/pkgtools/pkglint/files/category_test.go
diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.13 pkgsrc/pkgtools/pkglint/files/category_test.go:1.14
--- pkgsrc/pkgtools/pkglint/files/category_test.go:1.13 Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/category_test.go      Wed Nov  7 20:58:22 2018
@@ -9,7 +9,7 @@ func (s *Suite) Test_CheckdirCategory__t
        t.CreateFileLines("archivers/Makefile",
                "# $",
                "SUBDIR+=pkg1",
-               "SUBDIR+=\u0020aaaaa",
+               "SUBDIR+= aaaaa",
                "SUBDIR-=unknown #doesn\u2019t work",
                "",
                ".include \"../mk/category.mk\"")
@@ -20,14 +20,18 @@ func (s *Suite) Test_CheckdirCategory__t
                "ERROR: ~/archivers/Makefile:1: Expected \"# $"+"NetBSD$\".",
                "WARN: ~/archivers/Makefile:4: Line contains invalid characters (U+2019).",
                "WARN: ~/archivers/Makefile:4: SUBDIR- is defined but not used.",
+               "NOTE: ~/archivers/Makefile:2: This variable value should be aligned to column 17.",
+               "NOTE: ~/archivers/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/archivers/Makefile:4: This variable value should be aligned to column 17.",
                "ERROR: ~/archivers/Makefile:6: \"../mk/category.mk\" does not exist.",
+               "NOTE: ~/archivers/Makefile:1: Empty line expected after this line.",
                "ERROR: ~/archivers/Makefile:2: COMMENT= line expected.",
-               "WARN: ~/archivers/Makefile:2: Indentation should be a single tab character.",
-               "WARN: ~/archivers/Makefile:3: Indentation should be a single tab character.",
+               "NOTE: ~/archivers/Makefile:1: Empty line expected after this line.", // XXX: Duplicate.
                "WARN: ~/archivers/Makefile:3: \"aaaaa\" should come before \"pkg1\".",
                "ERROR: ~/archivers/Makefile:4: SUBDIR+= line or empty line expected.",
-               "ERROR: ~/archivers/Makefile:2: \"pkg1\" exists in the Makefile, but not in the file system.",
-               "ERROR: ~/archivers/Makefile:3: \"aaaaa\" exists in the Makefile, but not in the file system.",
+               "ERROR: ~/archivers/Makefile:2: \"pkg1\" exists in the Makefile but not in the file system.",
+               "ERROR: ~/archivers/Makefile:3: \"aaaaa\" exists in the Makefile but not in the file system.",
+               "NOTE: ~/archivers/Makefile:3: Empty line expected after this line.",
                "WARN: ~/archivers/Makefile:4: This line should contain the following text: .include \"../mk/misc/category.mk\"",
                "ERROR: ~/archivers/Makefile:4: The file should end here.")
 }
@@ -38,6 +42,7 @@ func (s *Suite) Test_CheckdirCategory__i
        t.SetupVartypes()
        t.CreateFileLines("archivers/Makefile",
                MkRcsID,
+               "",
                "COMMENT=\t\\Make $$$$ fast\"",
                "",
                "SUBDIR+=\tpackage",
@@ -51,9 +56,15 @@ func (s *Suite) Test_CheckdirCategory__i
        CheckdirCategory(t.File("archivers"))
 
        t.CheckOutputLines(
-               "WARN: ~/archivers/Makefile:2: COMMENT contains invalid characters (U+005C U+0024 U+0024 U+0024 U+0024 U+0022).")
+               "WARN: ~/archivers/Makefile:3: COMMENT contains invalid characters (U+005C U+0024 U+0024 U+0024 U+0024 U+0022).")
 }
 
+// The pkgsrc-wip Makefile has a large section with special code below
+// the SUBDIR list. This section is skipped by pkglint since it is assumed
+// to work.
+//
+// In all other category files, pkglint checks that directly after the
+// SUBDIR section there is only an empty line and the .include line.
 func (s *Suite) Test_CheckdirCategory__wip(c *check.C) {
        t := s.Init(c)
 
@@ -61,13 +72,11 @@ func (s *Suite) Test_CheckdirCategory__w
        t.SetupVartypes()
        t.CreateFileLines("mk/misc/category.mk")
        t.CreateFileLines("wip/package/Makefile")
-       t.CreateFileLines("wip/fs-only/Makefile")
        t.CreateFileLines("wip/Makefile",
                MkRcsID,
+               "",
                "COMMENT=\tCategory comment",
                "",
-               "SUBDIR+=\tmk-only",
-               "#SUBDIR+=\tpackage",
                "SUBDIR+=\tpackage",
                "",
                "wip-specific-target: .PHONY",
@@ -78,8 +87,102 @@ func (s *Suite) Test_CheckdirCategory__w
        G.CheckDirent(t.File("wip"))
 
        t.CheckOutputLines(
-               "WARN: ~/wip/Makefile:5: \"package\" commented out without giving a reason.",
-               "ERROR: ~/wip/Makefile:6: \"package\" must only appear once.",
-               "ERROR: ~/wip/Makefile:4: \"fs-only\" exists in the file system, but not in the Makefile.",
-               "ERROR: ~/wip/Makefile:4: \"mk-only\" exists in the Makefile, but not in the file system.")
+               "WARN: ~/wip/Makefile:8: Unknown shell command \"wip-specific-command\".")
+}
+
+func (s *Suite) Test_CheckdirCategory__subdirs(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPkgsrc()
+       t.SetupVartypes()
+       t.CreateFileLines("mk/misc/category.mk")
+       t.CreateFileLines("category/in-wrong-order/Makefile")
+       t.CreateFileLines("category/duplicate/Makefile")
+       t.CreateFileLines("category/fs-only/Makefile")
+       t.CreateFileLines("category/mk-and-fs/Makefile")
+       t.CreateFileLines("category/commented-mk-and-fs/Makefile")
+       t.CreateFileLines("category/commented-without-reason/Makefile")
+       t.CreateFileLines("category/Makefile",
+               MkRcsID,
+               "",
+               "COMMENT=\tCategory comment",
+               "",
+               "SUBDIR+=\tduplicate",
+               "SUBDIR+=\tin-wrong-order",
+               "SUBDIR+=\tduplicate",
+               "SUBDIR+=\tmk-and-fs",
+               "SUBDIR+=\tmk-only",
+               "#SUBDIR+=\tcommented-mk-and-fs\t# Reason",
+               "#SUBDIR+=\tcommented-mk-only\t# Reason",
+               "#SUBDIR+=\tcommented-without-reason",
+               "",
+               ".include \"../mk/misc/category.mk\"")
+
+       CheckdirCategory(t.File("category"))
+
+       t.CheckOutputLines(
+               "ERROR: ~/category/Makefile:7: \"duplicate\" must only appear once, already seen in line 5.",
+               "WARN: ~/category/Makefile:7: \"duplicate\" should come before \"in-wrong-order\".",
+               "WARN: ~/category/Makefile:10: \"commented-mk-and-fs\" should come before \"mk-only\".",
+               "WARN: ~/category/Makefile:12: \"commented-without-reason\" commented out without giving a reason.",
+               "ERROR: ~/category/Makefile:6: \"fs-only\" exists in the file system but not in the Makefile.",
+               "ERROR: ~/category/Makefile:9: \"mk-only\" exists in the Makefile but not in the file system.",
+               "ERROR: ~/category/Makefile:11: \"commented-mk-only\" exists in the Makefile but not in the file system.")
+}
+
+// Ensures that a directory in the file system can be added at the very
+// end of the SUBDIR list. This case takes a different code path than
+// an addition in the middle.
+//
+// The warning appears in the empty line below the SUBDIR lines
+// since the new SUBDIR would be inserted before that empty line.
+// It would have also been possible to insert the new SUBDIR after
+// the last SUBDIR line, but that would have been three more lines of code.
+func (s *Suite) Test_CheckdirCategory__subdirs_file_system_at_the_bottom(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall", "--show-autofix")
+       t.SetupPkgsrc()
+       t.SetupVartypes()
+       t.CreateFileLines("mk/misc/category.mk")
+       t.CreateFileLines("category/mk-and-fs/Makefile")
+       t.CreateFileLines("category/zzz-fs-only/Makefile")
+       t.CreateFileLines("category/Makefile",
+               MkRcsID,
+               "",
+               "COMMENT=\tCategory comment",
+               "",
+               "SUBDIR+=\tmk-and-fs",
+               "",
+               ".include \"../mk/misc/category.mk\"")
+
+       CheckdirCategory(t.File("category"))
+
+       t.CheckOutputLines(
+               "ERROR: ~/category/Makefile:6: \"zzz-fs-only\" exists in the file system but not in the Makefile.",
+               "AUTOFIX: ~/category/Makefile:6: Inserting a line \"SUBDIR+=\\tzzz-fs-only\" before this line.")
+}
+
+func (s *Suite) Test_CheckdirCategory__indentation(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPkgsrc()
+       t.SetupVartypes()
+       t.CreateFileLines("mk/misc/category.mk")
+       t.CreateFileLines("category/package1/Makefile")
+       t.CreateFileLines("category/package2/Makefile")
+       t.CreateFileLines("category/Makefile",
+               MkRcsID,
+               "",
+               "COMMENT=\tCategory comment",
+               "",
+               "SUBDIR+=                    package1",
+               "SUBDIR+=\tpackage2",
+               "",
+               ".include \"../mk/misc/category.mk\"")
+
+       CheckdirCategory(t.File("category"))
+
+       t.CheckOutputLines(
+               "NOTE: ~/category/Makefile:5: This variable value should be aligned with tabs, not spaces, to column 17.")
 }
Index: pkgsrc/pkgtools/pkglint/files/expecter.go
diff -u pkgsrc/pkgtools/pkglint/files/expecter.go:1.13 pkgsrc/pkgtools/pkglint/files/expecter.go:1.14
--- pkgsrc/pkgtools/pkglint/files/expecter.go:1.13      Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/expecter.go   Wed Nov  7 20:58:23 2018
@@ -2,46 +2,44 @@ package main
 
 import (
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "strings"
 )
 
 // Expecter records the state when checking a list of lines from top to bottom.
 type Expecter struct {
-       lines []Line
+       lines Lines
        index int
-       m     []string
+       m     []string // The groups from the last successful regex match
 }
 
-func NewExpecter(lines []Line) *Expecter {
+func NewExpecter(lines Lines) *Expecter {
        return &Expecter{lines, 0, nil}
 }
 
 func (exp *Expecter) CurrentLine() Line {
-       if exp.index < len(exp.lines) {
-               return exp.lines[exp.index]
+       if exp.index < exp.lines.Len() {
+               return exp.lines.Lines[exp.index]
        }
-
-       return NewLineEOF(exp.lines[0].Filename)
+       return NewLineEOF(exp.lines.FileName)
 }
 
 func (exp *Expecter) PreviousLine() Line {
-       return exp.lines[exp.index-1]
-}
-
-func (exp *Expecter) Index() int {
-       return exp.index
+       return exp.lines.Lines[exp.index-1]
 }
 
 func (exp *Expecter) EOF() bool {
-       return !(exp.index < len(exp.lines))
+       return !(exp.index < exp.lines.Len())
 }
 
 func (exp *Expecter) Group(index int) string {
        return exp.m[index]
 }
 
+// Advance skips the current line and returns true.
 func (exp *Expecter) Advance() bool {
+       if exp.EOF() {
+               return false
+       }
        exp.index++
        exp.m = nil
        return true
@@ -57,7 +55,7 @@ func (exp *Expecter) AdvanceIfMatches(re
        }
 
        if !exp.EOF() {
-               if m := G.res.Match(exp.lines[exp.index].Text, re); m != nil {
+               if m := G.res.Match(exp.lines.Lines[exp.index].Text, re); m != nil {
                        exp.index++
                        exp.m = m
                        return true
@@ -71,7 +69,7 @@ func (exp *Expecter) AdvanceIfPrefix(pre
                defer trace.Call2(exp.CurrentLine().Text, prefix)()
        }
 
-       return !exp.EOF() && strings.HasPrefix(exp.lines[exp.index].Text, prefix) && exp.Advance()
+       return !exp.EOF() && strings.HasPrefix(exp.lines.Lines[exp.index].Text, prefix) && exp.Advance()
 }
 
 func (exp *Expecter) AdvanceIfEquals(text string) bool {
@@ -79,48 +77,52 @@ func (exp *Expecter) AdvanceIfEquals(tex
                defer trace.Call2(exp.CurrentLine().Text, text)()
        }
 
-       return !exp.EOF() && exp.lines[exp.index].Text == text && exp.Advance()
+       return !exp.EOF() && exp.lines.Lines[exp.index].Text == text && exp.Advance()
 }
 
-func (exp *Expecter) ExpectEmptyLine(warnSpace bool) bool {
+func (exp *Expecter) ExpectEmptyLine() bool {
        if exp.AdvanceIfEquals("") {
                return true
        }
 
-       if warnSpace {
-               fix := exp.CurrentLine().Autofix()
-               fix.Notef("Empty line expected.")
-               fix.InsertBefore("")
-               fix.Apply()
+       if G.Opts.WarnSpace {
+               if exp.index == 0 {
+                       fix := exp.CurrentLine().Autofix()
+                       fix.Notef("Empty line expected before this line.")
+                       fix.InsertBefore("")
+                       fix.Apply()
+               } else {
+                       fix := exp.PreviousLine().Autofix()
+                       fix.Notef("Empty line expected after this line.")
+                       fix.InsertAfter("")
+                       fix.Apply()
+               }
        }
        return false
 }
 
 func (exp *Expecter) ExpectText(text string) bool {
-       if !exp.EOF() && exp.lines[exp.index].Text == text {
-               exp.index++
-               exp.m = nil
-               return true
+       result := exp.AdvanceIfEquals(text)
+       if !result {
+               exp.CurrentLine().Warnf("This line should contain the following text: %s", text)
        }
-
-       exp.CurrentLine().Warnf("This line should contain the following text: %s", text)
-       return false
-}
-
-func (exp *Expecter) SkipToFooter() {
-       exp.index = len(exp.lines) - 2
+       return result
 }
 
 // MkExpecter records the state when checking a list of Makefile lines from top to bottom.
 type MkExpecter struct {
-       mklines *MkLines
+       mklines MkLines
        Expecter
 }
 
-func NewMkExpecter(mklines *MkLines) *MkExpecter {
+func NewMkExpecter(mklines MkLines) *MkExpecter {
        return &MkExpecter{mklines, *NewExpecter(mklines.lines)}
 }
 
+func (exp *MkExpecter) PreviousMkLine() MkLine {
+       return exp.mklines.mklines[exp.index-1]
+}
+
 func (exp *MkExpecter) CurrentMkLine() MkLine {
        return exp.mklines.mklines[exp.index]
 }
@@ -134,3 +136,7 @@ func (exp *MkExpecter) AdvanceWhile(pred
                exp.Advance()
        }
 }
+
+func (exp *MkExpecter) AdvanceIf(pred func(mkline MkLine) bool) bool {
+       return !exp.EOF() && pred(exp.CurrentMkLine()) && exp.Advance()
+}
Index: pkgsrc/pkgtools/pkglint/files/line_test.go
diff -u pkgsrc/pkgtools/pkglint/files/line_test.go:1.13 pkgsrc/pkgtools/pkglint/files/line_test.go:1.14
--- pkgsrc/pkgtools/pkglint/files/line_test.go:1.13     Tue Aug 21 19:21:41 2018
+++ pkgsrc/pkgtools/pkglint/files/line_test.go  Wed Nov  7 20:58:23 2018
@@ -8,19 +8,19 @@ func (s *Suite) Test_Line_log__gcc_forma
        t := s.Init(c)
 
        t.SetupCommandLine("--gcc-output-format")
-       line := t.NewLine("filename", 123, "text")
+       line := t.NewLine("fileName", 123, "text")
 
        line.Notef("Diagnostics can be logged in GCC-style.")
 
        t.CheckOutputLines(
-               "filename:123: note: Diagnostics can be logged in GCC-style.")
+               "fileName:123: note: Diagnostics can be logged in GCC-style.")
 }
 
 func (s *Suite) Test_Line_log__print_source(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--show-autofix", "--source")
-       line := t.NewLine("filename", 123, "text")
+       line := t.NewLine("fileName", 123, "text")
 
        fix := line.Autofix()
        fix.Notef("Diagnostics can show the differences in autofix mode.")
@@ -29,18 +29,56 @@ func (s *Suite) Test_Line_log__print_sou
        fix.Apply()
 
        t.CheckOutputLines(
-               "NOTE: filename:123: Diagnostics can show the differences in autofix mode.",
-               "AUTOFIX: filename:123: Inserting a line \"new line before\" before this line.",
-               "AUTOFIX: filename:123: Inserting a line \"new line after\" after this line.",
+               "NOTE: fileName:123: Diagnostics can show the differences in autofix mode.",
+               "AUTOFIX: fileName:123: Inserting a line \"new line before\" before this line.",
+               "AUTOFIX: fileName:123: Inserting a line \"new line after\" after this line.",
                "+\tnew line before",
                ">\ttext",
                "+\tnew line after")
 }
 
+// Ensures that when two packages produce a warning in the same file, both the
+// warning and the corresponding source code are logged only once.
+func (s *Suite) Test_Line_showSource__duplicates(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPkgsrc()
+       t.CreateFileLines("category/dependency/patches/patch-aa",
+               RcsID,
+               "",
+               "--- old file",
+               "+++ new file",
+               "@@ -1,1 +1,1 @@",
+               "-old line",
+               "+new line")
+       t.SetupPackage("category/package1",
+               "PATCHDIR=\t../../category/dependency/patches")
+       t.SetupPackage("category/package2",
+               "PATCHDIR=\t../../category/dependency/patches")
+
+       G.Main("pkglint", "--source", "-Wall", t.File("category/package1"), t.File("category/package2"))
+
+       t.CheckOutputLines(
+               "ERROR: ~/category/package1/distinfo: patch \"../dependency/patches/patch-aa\" "+
+                       "is not recorded. Run \""+confMake+" makepatchsum\".",
+               "",
+               ">\t--- old file",
+               "ERROR: ~/category/dependency/patches/patch-aa:3: Each patch must be documented.",
+               "",
+               "ERROR: ~/category/package2/distinfo: patch \"../dependency/patches/patch-aa\" "+
+                       "is not recorded. Run \""+confMake+" makepatchsum\".",
+               "",
+               ">\t--- old file",
+               // FIXME: The above source line is missing a diagnostic.
+               "",
+               "3 errors and 0 warnings found.",
+               "(Run \"pkglint -e\" to show explanations.)")
+}
+
 func (s *Suite) Test_RawLine_String(c *check.C) {
        t := s.Init(c)
 
-       line := t.NewLine("filename", 123, "text")
+       line := t.NewLine("fileName", 123, "text")
 
        c.Check(line.raw[0].String(), equals, "123:text\n")
 }
Index: pkgsrc/pkgtools/pkglint/files/toplevel.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel.go:1.13 pkgsrc/pkgtools/pkglint/files/toplevel.go:1.14
--- pkgsrc/pkgtools/pkglint/files/toplevel.go:1.13      Thu Aug 16 20:41:42 2018
+++ pkgsrc/pkgtools/pkglint/files/toplevel.go   Wed Nov  7 20:58:23 2018
@@ -1,9 +1,5 @@
 package main
 
-import (
-       "netbsd.org/pkglint/trace"
-)
-
 type Toplevel struct {
        dir            string
        previousSubdir string
@@ -16,9 +12,9 @@ func CheckdirToplevel(dir string) {
        }
 
        ctx := &Toplevel{dir, "", nil}
-       fname := dir + "/Makefile"
+       fileName := dir + "/Makefile"
 
-       mklines := LoadMk(fname, NotEmpty|LogErrors)
+       mklines := LoadMk(fileName, NotEmpty|LogErrors)
        if mklines == nil {
                return
        }
@@ -31,8 +27,8 @@ func CheckdirToplevel(dir string) {
 
        mklines.Check()
 
-       if G.opts.Recursive {
-               if G.opts.CheckGlobal {
+       if G.Opts.Recursive {
+               if G.Opts.CheckGlobal {
                        G.Pkgsrc.UsedLicenses = make(map[string]bool)
                        G.Pkgsrc.Hashes = make(map[string]*Hash)
                }
Index: pkgsrc/pkgtools/pkglint/files/toplevel_test.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.13 pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.14
--- pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.13 Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/toplevel_test.go      Wed Nov  7 20:58:23 2018
@@ -27,5 +27,9 @@ func (s *Suite) Test_CheckdirToplevel(c 
                "WARN: ~/Makefile:3: Indentation should be a single tab character.",
                "ERROR: ~/Makefile:6: Each subdir must only appear once.",
                "WARN: ~/Makefile:7: \"ignoreme\" commented out without giving a reason.",
-               "WARN: ~/Makefile:9: bbb should come before ccc.")
+               "WARN: ~/Makefile:9: bbb should come before ccc.",
+
+               // This warning is at the very end because mklines.Check() is called at the end.
+               // Ideally it would be at the same place as the other warning from Makefile:3.
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 17.")
 }

Index: pkgsrc/pkgtools/pkglint/files/buildlink3_test.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.19 pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.20
--- pkgsrc/pkgtools/pkglint/files/buildlink3_test.go:1.19       Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/buildlink3_test.go    Wed Nov  7 20:58:22 2018
@@ -2,45 +2,46 @@ package main
 
 import "gopkg.in/check.v1"
 
-func (s *Suite) Test_ChecklinesBuildlink3Mk(c *check.C) {
+func (s *Suite) Test_ChecklinesBuildlink3Mk__unfinished_url2pkg(c *check.C) {
        t := s.Init(c)
 
        t.SetupVartypes()
+       t.CreateFileLines("x11/Xbae/Makefile")
+       t.CreateFileLines("mk/motif.buildlink3.mk")
        mklines := t.SetupFileMkLines("buildlink3.mk",
                MkRcsID,
                "# XXX This file was created automatically using createbuildlink-@PKGVERSION@",
                "",
-               "BUILDLINK_TREE+=        Xbae",
+               "BUILDLINK_TREE+=\tXbae",
                "",
                "BUILDLINK_DEPMETHOD.Xbae?=\tfull",
                ".if !defined(XBAE_BUILDLINK3_MK)",
                "XBAE_BUILDLINK3_MK:=",
                "",
-               "BUILDLINK_API_DEPENDS.Xbae+=    Xbae>=4.8.4",
-               "BUILDLINK_ABI_DEPENDS.Xbae+=    Xbae>=4.51.01nb2",
-               "BUILDLINK_PKGSRCDIR.Xbae?=      ../../x11/Xbae",
+               "BUILDLINK_API_DEPENDS.Xbae+=\tXbae>=4.8.4",
+               "BUILDLINK_ABI_DEPENDS.Xbae+=\tXbae>=4.51.01nb2",
+               "BUILDLINK_PKGSRCDIR.Xbae?=\t../../x11/Xbae",
                "",
                ".include \"../../mk/motif.buildlink3.mk\"",
                ".endif # XBAE_BUILDLINK3_MK",
                "",
-               "BUILDLINK_TREE+=        -Xbae")
+               "BUILDLINK_TREE+=\t-Xbae")
 
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "ERROR: ~/buildlink3.mk:12: \"x11/Xbae\" does not exist.",
-               "ERROR: ~/buildlink3.mk:12: There is no package in \"x11/Xbae\".",
-               "ERROR: ~/buildlink3.mk:14: \"mk/motif.buildlink3.mk\" does not exist.",
                "ERROR: ~/buildlink3.mk:2: This comment indicates unfinished work (url2pkg).")
 }
 
 // Before version 5.3, pkglint wrongly warned here.
 // The mk/haskell.mk file takes care of constructing the correct PKGNAME,
 // but pkglint had not looked at that file.
-func (s *Suite) Test_ChecklinesBuildlink3Mk__name_mismatch(c *check.C) {
+//
+// Since then, pkglint also looks at files from mk/ when they are directly
+// included, and therefore finds the default definition for PKGNAME.
+func (s *Suite) Test_ChecklinesBuildlink3Mk__name_mismatch_Haskell_incomplete(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupPackage("x11/hs-X11",
                "DISTNAME=\tX11-1.0")
        t.Chdir("x11/hs-X11")
@@ -66,6 +67,43 @@ func (s *Suite) Test_ChecklinesBuildlink
                "ERROR: buildlink3.mk:3: Package name mismatch between \"hs-X11\" in this file and \"X11\" from Makefile:3.")
 }
 
+// Before version 5.3, pkglint wrongly warned here.
+// The mk/haskell.mk file takes care of constructing the correct PKGNAME,
+// but pkglint had not looked at that file.
+//
+// Since then, pkglint also looks at files from mk/ when they are directly
+// included, and therefore finds the default definition for PKGNAME.
+func (s *Suite) Test_ChecklinesBuildlink3Mk__name_mismatch_Haskell_complete(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("mk/haskell.mk",
+               MkRcsID,
+               "PKGNAME?=\ths-${DISTNAME}")
+       t.SetupPackage("x11/hs-X11",
+               "DISTNAME=\tX11-1.0",
+               "",
+               ".include \"../../mk/haskell.mk\"")
+       t.Chdir("x11/hs-X11")
+       t.CreateFileLines("buildlink3.mk",
+               MkRcsID,
+               "",
+               "BUILDLINK_TREE+=\ths-X11",
+               "",
+               ".if !defined(HS_X11_BUILDLINK3_MK)",
+               "HS_X11_BUILDLINK3_MK:=",
+               "",
+               "BUILDLINK_API_DEPENDS.hs-X11+=\ths-X11>=1.6.1",
+               "BUILDLINK_ABI_DEPENDS.hs-X11+=\ths-X11>=1.6.1.2nb2",
+               "",
+               ".endif\t# HS_X11_BUILDLINK3_MK",
+               "",
+               "BUILDLINK_TREE+=\t-hs-X11")
+
+       G.CheckDirent(".")
+
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_ChecklinesBuildlink3Mk__name_mismatch_multiple_inclusion(c *check.C) {
        t := s.Init(c)
 
@@ -141,7 +179,39 @@ func (s *Suite) Test_ChecklinesBuildlink
                "WARN: buildlink3.mk:9: ABI version \"1.6.0\" should be at least API version \"1.6.1\" (see line 8).")
 }
 
-func (s *Suite) Test_ChecklinesBuildlink3Mk__no_BUILDLINK_TREE_at_beginning(c *check.C) {
+// As of October 2018, pkglint parses package dependencies a little
+// different than the pkg_* tools.
+// In all but two cases this works, this is one of the exceptions.
+// The "{totem,totem-xine}" cannot be parsed, therefore the check skipped.
+func (s *Suite) Test_Buildlink3Checker_checkVarassign__abi_api_versions_brace(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       t.CreateFileLines("multimedia/totem/Makefile")
+       mklines := t.SetupFileMkLines("multimedia/totem/buildlink3.mk",
+               MkRcsID,
+               "",
+               "BUILDLINK_TREE+=\ttotem",
+               "",
+               ".if !defined(TOTEM_BUILDLINK3_MK)",
+               "TOTEM_BUILDLINK3_MK:=",
+               "",
+               "BUILDLINK_API_DEPENDS.totem+=\t{totem,totem-xine}>=1.4.0",
+               "BUILDLINK_ABI_DEPENDS.totem+=\ttotem>=2.32.0nb46",
+               "BUILDLINK_PKGSRCDIR.totem?=\t../../multimedia/totem",
+               "",
+               ".endif # TOTEM_BUILDLINK3_MK",
+               "",
+               "BUILDLINK_TREE+=\t-totem")
+
+       ChecklinesBuildlink3Mk(mklines)
+
+       // No warning about ABI "totem" and API "{totem,totem-xine}"
+       // because that case is explicitly not checked.
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_ChecklinesBuildlink3Mk__missing_BUILDLINK_TREE_at_beginning(c *check.C) {
        t := s.Init(c)
 
        t.SetupVartypes()
@@ -150,22 +220,43 @@ func (s *Suite) Test_ChecklinesBuildlink
                "",
                ".if !defined(HS_X11_BUILDLINK3_MK)",
                "HS_X11_BUILDLINK3_MK:=",
+               ".endif")
+
+       ChecklinesBuildlink3Mk(mklines)
+
+       t.CheckOutputLines(
+               "WARN: buildlink3.mk:3: Expected a BUILDLINK_TREE line.")
+}
+
+// In buildlink3.mk files, there is no need to place any comments
+// in the autogenerated code, therefore this warning is justified.
+func (s *Suite) Test_ChecklinesBuildlink3Mk__missing_BUILDLINK_TREE_at_end(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       mklines := t.NewMkLines("buildlink3.mk",
+               MkRcsID,
+               "",
+               "BUILDLINK_TREE+=\ths-X11",
+               "",
+               ".if !defined(HS_X11_BUILDLINK3_MK)",
+               "HS_X11_BUILDLINK3_MK:=",
                "",
-               "BUILDLINK_DEPMETHOD.hs-X11?=\tfull",
                "BUILDLINK_API_DEPENDS.hs-X11+=\ths-X11>=1.6.1",
                "BUILDLINK_ABI_DEPENDS.hs-X11+=\ths-X11>=1.6.1.2nb2",
                "",
                ".endif\t# HS_X11_BUILDLINK3_MK",
                "",
+               "# needless comment",
                "BUILDLINK_TREE+=\t-hs-X11")
 
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:3: Expected a BUILDLINK_TREE line.")
+               "WARN: buildlink3.mk:13: This line should contain the following text: BUILDLINK_TREE+=\t-hs-X11")
 }
 
-func (s *Suite) Test_ChecklinesBuildlink3Mk__no_BUILDLINK_TREE_at_end(c *check.C) {
+func (s *Suite) Test_ChecklinesBuildlink3Mk__DEPMETHOD_placement(c *check.C) {
        t := s.Init(c)
 
        t.SetupVartypes()
@@ -184,14 +275,12 @@ func (s *Suite) Test_ChecklinesBuildlink
                "",
                ".endif\t# HS_X11_BUILDLINK3_MK",
                "",
-               "# needless comment",
                "BUILDLINK_TREE+=\t-hs-X11")
 
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:3: This line belongs inside the .ifdef block.",
-               "WARN: buildlink3.mk:15: This line should contain the following text: BUILDLINK_TREE+=\t-hs-X11")
+               "WARN: buildlink3.mk:3: This line belongs inside the .ifdef block.")
 }
 
 func (s *Suite) Test_ChecklinesBuildlink3Mk__multiple_inclusion_wrong(c *check.C) {
@@ -204,11 +293,13 @@ func (s *Suite) Test_ChecklinesBuildlink
                "BUILDLINK_TREE+=\ths-X11",
                "",
                ".if !defined(HS_X11_BUILDLINK3_MK)",
-               "UNRELATED_BUILDLINK3_MK:=")
+               "UNRELATED_BUILDLINK3_MK:=",
+               ".endif")
 
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
+               "WARN: buildlink3.mk:5: HS_X11_BUILDLINK3_MK is used but not defined.",
                "WARN: buildlink3.mk:6: UNRELATED_BUILDLINK3_MK is defined but not used.",
                "WARN: buildlink3.mk:6: This line should contain the following text: HS_X11_BUILDLINK3_MK:=")
 }
@@ -228,17 +319,18 @@ func (s *Suite) Test_ChecklinesBuildlink
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:EOF: Expected \".endif\".")
+               "ERROR: buildlink3.mk:EOF: .if from line 5 must be closed.",
+               "NOTE: buildlink3.mk:6: Empty line expected after this line.")
 }
 
-func (s *Suite) Test_ChecklinesBuildlink3Mk__unknown_dependency_patterns(c *check.C) {
+func (s *Suite) Test_ChecklinesBuildlink3Mk__invalid_dependency_patterns(c *check.C) {
        t := s.Init(c)
 
        t.SetupVartypes()
        mklines := t.NewMkLines("buildlink3.mk",
                MkRcsID,
                "",
-               "BUILDLINK_TREE+= hs-X11",
+               "BUILDLINK_TREE+=\ths-X11",
                "",
                ".if !defined(HS_X11_BUILDLINK3_MK)",
                "HS_X11_BUILDLINK3_MK:=",
@@ -254,8 +346,8 @@ func (s *Suite) Test_ChecklinesBuildlink
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:9: Unknown dependency pattern \"hs-X11!=1.6.1\".",
-               "WARN: buildlink3.mk:10: Unknown dependency pattern \"hs-X11!=1.6.1.2nb2\".")
+               "WARN: buildlink3.mk:9: Invalid dependency pattern \"hs-X11!=1.6.1\".",
+               "WARN: buildlink3.mk:10: Invalid dependency pattern \"hs-X11!=1.6.1.2nb2\".")
 }
 
 func (s *Suite) Test_ChecklinesBuildlink3Mk__PKGBASE_with_variable(c *check.C) {
@@ -295,67 +387,66 @@ func (s *Suite) Test_ChecklinesBuildlink
                ".if !defined(LICENSE_BUILDLINK3_MK)",
                "LICENSE_BUILDLINK3_MK:=",
                "",
-               "BUILDLINK_API_DEPENDS.${LICENSE}-wxWidgets+=\t${PYPKGPREFIX}-wxWidgets>=2.6.1.0",
-               "BUILDLINK_ABI_DEPENDS.${LICENSE}-wxWidgets+=\t${PYPKGPREFIX}-wxWidgets>=2.8.10.1nb26",
+               "BUILDLINK_API_DEPENDS.${LICENSE}-wxWidgets+=\t${LICENSE}-wxWidgets>=2.6.1.0",
+               "BUILDLINK_ABI_DEPENDS.${LICENSE}-wxWidgets+=\t${LICENSE}-wxWidgets>=2.8.10.1nb26",
                "",
                ".endif",
                "",
-               "BUILDLINK_TREE+=\t-${PYPKGPREFIX}-wxWidgets")
+               "BUILDLINK_TREE+=\t-${LICENSE}-wxWidgets")
 
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: buildlink3.mk:3: Please replace \"${LICENSE}\" with a simple string (also in other variables in this file).",
-               "WARN: buildlink3.mk:13: This line should contain the following text: BUILDLINK_TREE+=\t-${LICENSE}-wxWidgets")
-}
-
-// Those .include lines that are not indented at all may stay as-is.
-// This special exception might have been for backwards-compatibility,
-// but ideally should be handled like everywhere else.
-// See MkLineChecker.checkInclude.
-func (s *Suite) Test_ChecklinesBuildlink3Mk__indentation(c *check.C) {
+               "WARN: buildlink3.mk:3: LICENSE may not be used in any file; it is a write-only variable.",
+               // FIXME: License is not a list type, although it can be appended to.
+               "WARN: buildlink3.mk:3: The list variable LICENSE should not be embedded in a word.",
+               "WARN: buildlink3.mk:8: LICENSE should not be evaluated indirectly at load time.",
+               "WARN: buildlink3.mk:8: LICENSE may not be used in any file; it is a write-only variable.",
+               // FIXME: License is not a list type, although it can be appended to.
+               "WARN: buildlink3.mk:8: The list variable LICENSE should not be embedded in a word.",
+               "WARN: buildlink3.mk:9: LICENSE should not be evaluated indirectly at load time.",
+               "WARN: buildlink3.mk:9: LICENSE may not be used in any file; it is a write-only variable.",
+               // FIXME: License is not a list type, although it can be appended to.
+               "WARN: buildlink3.mk:9: The list variable LICENSE should not be embedded in a word.",
+               "WARN: buildlink3.mk:13: LICENSE may not be used in any file; it is a write-only variable.",
+               // FIXME: License is not a list type, although it can be appended to.
+               "WARN: buildlink3.mk:13: The list variable LICENSE should not be embedded in a word.",
+               "WARN: buildlink3.mk:3: Please replace \"${LICENSE}\" with a simple string (also in other variables in this file).")
+}
+
+// Since the buildlink3 checker does not use MkLines.ForEach, it has to keep
+// track of the nesting depth of .if directives.
+func (s *Suite) Test_Buildlink3Checker_checkMainPart__nested_if(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
-       mklines := t.SetupFileMkLines("buildlink3.mk",
+       mklines := t.SetupFileMkLines("category/package/buildlink3.mk",
                MkRcsID,
                "",
-               ".if ${VAAPI_AVAILABLE} == \"yes\"",
-               "",
-               "BUILDLINK_TREE+=       libva",
-               "",
-               ".  if !defined(LIBVA_BUILDLINK3_MK)",
-               "LIBVA_BUILDLINK3_MK:=",
+               "BUILDLINK_TREE+=\ths-X11",
                "",
-               "BUILDLINK_API_DEPENDS.libva+=  libva>=1.0.6",
-               "BUILDLINK_PKGSRCDIR.libva?=    ../../multimedia/libva",
+               ".if !defined(HS_X11_BUILDLINK3_MK)",
+               "HS_X11_BUILDLINK3_MK:=",
                "",
-               ".include \"../../x11/libX11/buildlink3.mk\"",
+               "BUILDLINK_API_DEPENDS.hs-X11+=\ths-X11>=1.6.1",
+               "BUILDLINK_ABI_DEPENDS.hs-X11+=\ths-X11>=1.6.1.2nb2",
                "",
-               ".  endif       # LIBVA_BUILDLINK3_MK",
+               ".if ${OPSYS} == NetBSD",
+               ".endif",
                "",
-               "BUILDLINK_TREE+=       -libva",
+               ".endif\t# HS_X11_BUILDLINK3_MK",
                "",
-               ".endif # VAAPI_AVAILABLE")
+               "BUILDLINK_TREE+=\t-hs-X11")
 
        ChecklinesBuildlink3Mk(mklines)
 
-       // No warning about the indentation of the .include lines.
-       t.CheckOutputLines(
-               "WARN: ~/buildlink3.mk:3: VAAPI_AVAILABLE is used but not defined.",
-               "ERROR: ~/buildlink3.mk:11: \"multimedia/libva\" does not exist.",
-               "ERROR: ~/buildlink3.mk:11: There is no package in \"multimedia/libva\".",
-               "ERROR: ~/buildlink3.mk:13: \"x11/libX11/buildlink3.mk\" does not exist.",
-               "WARN: ~/buildlink3.mk:3: Expected a BUILDLINK_TREE line.")
+       t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_ChecklinesBuildlink3Mk__coverage(c *check.C) {
+func (s *Suite) Test_Buildlink3Checker_checkMainPart__comment_at_end_of_file(c *check.C) {
        t := s.Init(c)
 
        t.SetupVartypes()
-       t.CreateFileLines("mk/pkg-build-options.mk")
-       t.CreateFileLines("category/dependency/buildlink3.mk")
        mklines := t.SetupFileMkLines("category/package/buildlink3.mk",
                MkRcsID,
                "",
@@ -364,20 +455,9 @@ func (s *Suite) Test_ChecklinesBuildlink
                ".if !defined(HS_X11_BUILDLINK3_MK)",
                "HS_X11_BUILDLINK3_MK:=",
                "",
-               "pkgbase := dependency",
-               ".include \"../../mk/pkg-build-options.mk\"",
-               "",
                "BUILDLINK_API_DEPENDS.hs-X11+=\ths-X11>=1.6.1",
                "BUILDLINK_ABI_DEPENDS.hs-X11+=\ths-X11>=1.6.1.2nb2",
                "",
-               ".include \"../../category/dependency/buildlink3.mk\"",
-               "",
-               ".if ${OPSYS} == \"NetBSD\"",
-               ".endif",
-               "",
-               ".for var in value",
-               ".endfor",
-               "",
                ".endif\t# HS_X11_BUILDLINK3_MK",
                "",
                "BUILDLINK_TREE+=\t-hs-X11",
@@ -387,5 +467,5 @@ func (s *Suite) Test_ChecklinesBuildlink
        ChecklinesBuildlink3Mk(mklines)
 
        t.CheckOutputLines(
-               "WARN: ~/category/package/buildlink3.mk:25: The file should end here.")
+               "WARN: ~/category/package/buildlink3.mk:14: The file should end here.")
 }
Index: pkgsrc/pkgtools/pkglint/files/vartype.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.19 pkgsrc/pkgtools/pkglint/files/vartype.go:1.20
--- pkgsrc/pkgtools/pkglint/files/vartype.go:1.19       Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/vartype.go    Wed Nov  7 20:58:23 2018
@@ -149,7 +149,7 @@ func (vt *Vartype) IsBasicSafe() bool {
                BtDistSuffix,
                BtEmulPlatform,
                BtFileMode,
-               BtFilename,
+               BtFileName,
                BtIdentifier,
                BtInteger,
                BtMachineGnuPlatform,
@@ -213,8 +213,8 @@ var (
        BtDistSuffix             = &BasicType{"DistSuffix", (*VartypeCheck).DistSuffix}
        BtEmulPlatform           = &BasicType{"EmulPlatform", (*VartypeCheck).EmulPlatform}
        BtFetchURL               = &BasicType{"FetchURL", (*VartypeCheck).FetchURL}
-       BtFilename               = &BasicType{"Filename", (*VartypeCheck).Filename}
-       BtFilemask               = &BasicType{"Filemask", (*VartypeCheck).Filemask}
+       BtFileName               = &BasicType{"FileName", (*VartypeCheck).FileName}
+       BtFileMask               = &BasicType{"FileMask", (*VartypeCheck).FileMask}
        BtFileMode               = &BasicType{"FileMode", (*VartypeCheck).FileMode}
        BtGccReqd                = &BasicType{"GccReqd", (*VartypeCheck).GccReqd}
        BtHomepage               = &BasicType{"Homepage", (*VartypeCheck).Homepage}
@@ -229,8 +229,8 @@ var (
        BtMessage                = &BasicType{"Message", (*VartypeCheck).Message}
        BtOption                 = &BasicType{"Option", (*VartypeCheck).Option}
        BtPathlist               = &BasicType{"Pathlist", (*VartypeCheck).Pathlist}
-       BtPathmask               = &BasicType{"Pathmask", (*VartypeCheck).Pathmask}
-       BtPathname               = &BasicType{"Pathname", (*VartypeCheck).Pathname}
+       BtPathmask               = &BasicType{"PathMask", (*VartypeCheck).PathMask}
+       BtPathname               = &BasicType{"PathName", (*VartypeCheck).PathName}
        BtPerl5Packlist          = &BasicType{"Perl5Packlist", (*VartypeCheck).Perl5Packlist}
        BtPerms                  = &BasicType{"Perms", (*VartypeCheck).Perms}
        BtPkgName                = &BasicType{"Pkgname", (*VartypeCheck).Pkgname}

Index: pkgsrc/pkgtools/pkglint/files/category.go
diff -u pkgsrc/pkgtools/pkglint/files/category.go:1.14 pkgsrc/pkgtools/pkglint/files/category.go:1.15
--- pkgsrc/pkgtools/pkglint/files/category.go:1.14      Thu Aug 16 20:41:42 2018
+++ pkgsrc/pkgtools/pkglint/files/category.go   Wed Nov  7 20:58:22 2018
@@ -1,8 +1,8 @@
 package main
 
 import (
-       "netbsd.org/pkglint/trace"
-       "sort"
+       "fmt"
+       "netbsd.org/pkglint/textproc"
 )
 
 func CheckdirCategory(dir string) {
@@ -20,19 +20,36 @@ func CheckdirCategory(dir string) {
        exp := NewMkExpecter(mklines)
        for exp.AdvanceIfPrefix("#") {
        }
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
+       exp.ExpectEmptyLine()
+
+       if exp.AdvanceIf(func(mkline MkLine) bool { return mkline.IsVarassign() && mkline.Varname() == "COMMENT" }) {
+               mkline := exp.PreviousMkLine()
+
+               lex := textproc.NewLexer(mkline.Value())
+               valid := textproc.NewByteSet("--- '(),/0-9A-Za-z")
+               invalid := valid.Inverse()
+               uni := ""
+
+               for !lex.EOF() {
+                       _ = lex.NextBytesSet(valid)
+                       ch := lex.NextByteSet(invalid)
+                       if ch != -1 {
+                               uni += fmt.Sprintf(" %U", ch)
+                       }
+               }
+
+               if uni != "" {
+                       mkline.Warnf("%s contains invalid characters (%s).", mkline.Varname(), uni[1:])
+               }
 
-       if exp.AdvanceIfMatches(`^COMMENT=\t*(.*)`) {
-               MkLineChecker{mklines.mklines[exp.Index()-1]}.CheckValidCharactersInValue(`[- '(),/0-9A-Za-z]`)
        } else {
                exp.CurrentLine().Errorf("COMMENT= line expected.")
        }
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
+       exp.ExpectEmptyLine()
 
        type subdir struct {
-               name   string
-               line   Line
-               active bool
+               name string
+               line MkLine
        }
 
        // And now to the most complicated part of the category Makefiles,
@@ -40,46 +57,43 @@ func CheckdirCategory(dir string) {
        // collect the SUBDIRs in the Makefile and in the file system.
 
        fSubdirs := getSubdirs(dir)
-       sort.Strings(fSubdirs)
        var mSubdirs []subdir
 
-       prevSubdir := ""
+       seen := make(map[string]MkLine)
        for !exp.EOF() {
-               line := exp.CurrentLine()
-               text := line.Text
+               mkline := exp.CurrentMkLine()
 
-               if m, commentFlag, indentation, name, comment := match4(text, `^(#?)SUBDIR\+=(\s*)(\S+)\s*(?:#\s*(.*?)\s*|)$`); m {
-                       commentedOut := commentFlag == "#"
-                       if commentedOut && comment == "" {
-                               line.Warnf("%q commented out without giving a reason.", name)
+               if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) && mkline.Varname() == "SUBDIR" {
+                       exp.Advance()
+
+                       name := mkline.Value()
+                       if mkline.IsCommentedVarassign() && mkline.VarassignComment() == "" {
+                               mkline.Warnf("%q commented out without giving a reason.", name)
                        }
 
-                       if indentation != "\t" {
-                               line.Warnf("Indentation should be a single tab character.")
+                       if prev := seen[name]; prev != nil {
+                               mkline.Errorf("%q must only appear once, already seen in %s.", name, mkline.RefTo(prev))
                        }
+                       seen[name] = mkline
 
-                       if name == prevSubdir {
-                               line.Errorf("%q must only appear once.", name)
-                       } else if name < prevSubdir {
-                               line.Warnf("%q should come before %q.", name, prevSubdir)
-                       } else {
-                               // correctly ordered
+                       if len(mSubdirs) > 0 {
+                               if prev := mSubdirs[len(mSubdirs)-1].name; name < prev {
+                                       mkline.Warnf("%q should come before %q.", name, prev)
+                               }
                        }
 
-                       mSubdirs = append(mSubdirs, subdir{name, line, !commentedOut})
-                       prevSubdir = name
-                       exp.Advance()
+                       mSubdirs = append(mSubdirs, subdir{name, mkline})
 
                } else {
-                       if line.Text != "" {
-                               line.Errorf("SUBDIR+= line or empty line expected.")
+                       if !mkline.IsEmpty() {
+                               mkline.Errorf("SUBDIR+= line or empty line expected.")
                        }
                        break
                }
        }
 
        // To prevent unnecessary warnings about subdirectories that are
-       // in one list, but not in the other, we generate the sets of
+       // in one list but not in the other, generate the sets of
        // subdirs of each list.
        fCheck := make(map[string]bool)
        mCheck := make(map[string]bool)
@@ -90,82 +104,62 @@ func CheckdirCategory(dir string) {
                mCheck[msub.name] = true
        }
 
-       fIndex, fAtend, fNeednext, fCurrent := 0, false, true, ""
-       mIndex, mAtend, mNeednext, mCurrent := 0, false, true, ""
-
-       var subdirs []string
+       fRest := fSubdirs[:]
+       mRest := mSubdirs[:]
 
-       var line Line
-       mActive := false
-
-       for !(mAtend && fAtend) {
-               if !mAtend && mNeednext {
-                       mNeednext = false
-                       if mIndex >= len(mSubdirs) {
-                               mAtend = true
-                               line = exp.CurrentLine()
-                               continue
-                       } else {
-                               mCurrent = mSubdirs[mIndex].name
-                               line = mSubdirs[mIndex].line
-                               mActive = mSubdirs[mIndex].active
-                               mIndex++
-                       }
-               }
+       for len(mRest) > 0 || len(fRest) > 0 {
 
-               if !fAtend && fNeednext {
-                       fNeednext = false
-                       if fIndex >= len(fSubdirs) {
-                               fAtend = true
-                               continue
-                       } else {
-                               fCurrent = fSubdirs[fIndex]
-                               fIndex++
-                       }
-               }
-
-               if !fAtend && (mAtend || fCurrent < mCurrent) {
+               if len(fRest) > 0 && (len(mRest) == 0 || fRest[0] < mRest[0].name) {
+                       fCurrent := fRest[0]
                        if !mCheck[fCurrent] {
+                               var line Line
+                               if len(mRest) > 0 {
+                                       line = mRest[0].line.Line
+                               } else {
+                                       line = exp.CurrentLine()
+                               }
+
                                fix := line.Autofix()
-                               fix.Errorf("%q exists in the file system, but not in the Makefile.", fCurrent)
+                               fix.Errorf("%q exists in the file system but not in the Makefile.", fCurrent)
                                fix.InsertBefore("SUBDIR+=\t" + fCurrent)
                                fix.Apply()
                        }
-                       fNeednext = true
+                       fRest = fRest[1:]
 
-               } else if !mAtend && (fAtend || mCurrent < fCurrent) {
-                       if !fCheck[mCurrent] {
-                               fix := line.Autofix()
-                               fix.Errorf("%q exists in the Makefile, but not in the file system.", mCurrent)
+               } else if len(mRest) > 0 && (len(fRest) == 0 || mRest[0].name < fRest[0]) {
+                       if !fCheck[mRest[0].name] {
+                               fix := mRest[0].line.Autofix()
+                               fix.Errorf("%q exists in the Makefile but not in the file system.", mRest[0].name)
                                fix.Delete()
                                fix.Apply()
                        }
-                       mNeednext = true
+                       mRest = mRest[1:]
 
-               } else { // f_current == m_current
-                       fNeednext = true
-                       mNeednext = true
-                       if mActive {
-                               subdirs = append(subdirs, dir+"/"+mCurrent)
-                       }
+               } else {
+                       fRest = fRest[1:]
+                       mRest = mRest[1:]
                }
        }
 
        // the pkgsrc-wip category Makefile defines its own targets for
        // generating indexes and READMEs. Just skip them.
-       if G.Wip {
-               exp.SkipToFooter()
-       }
-
-       exp.ExpectEmptyLine(G.opts.WarnSpace)
-       exp.ExpectText(".include \"../mk/misc/category.mk\"")
-       if !exp.EOF() {
-               exp.CurrentLine().Errorf("The file should end here.")
+       if !G.Wip {
+               exp.ExpectEmptyLine()
+               exp.ExpectText(".include \"../mk/misc/category.mk\"")
+               if !exp.EOF() {
+                       exp.CurrentLine().Errorf("The file should end here.")
+               }
        }
 
        mklines.SaveAutofixChanges()
 
-       if G.opts.Recursive {
-               G.Todo = append(append([]string(nil), subdirs...), G.Todo...)
+       if G.Opts.Recursive {
+               var recurseInto []string
+               for _, msub := range mSubdirs {
+                       if !msub.line.IsCommentedVarassign() {
+                               recurseInto = append(recurseInto, dir+"/"+msub.name)
+                       }
+               }
+               G.Todo = append(recurseInto, G.Todo...)
        }
 }
Index: pkgsrc/pkgtools/pkglint/files/substcontext.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext.go:1.14 pkgsrc/pkgtools/pkglint/files/substcontext.go:1.15
--- pkgsrc/pkgtools/pkglint/files/substcontext.go:1.14  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/substcontext.go       Wed Nov  7 20:58:23 2018
@@ -1,7 +1,5 @@
 package main
 
-import "netbsd.org/pkglint/trace"
-
 // SubstContext records the state of a block of variable assignments
 // that make up a SUBST class (see `mk/subst.mk`).
 type SubstContext struct {
@@ -11,6 +9,7 @@ type SubstContext struct {
        curr          *SubstContextStats
        inAllBranches SubstContextStats
        filterCmd     string
+       vars          map[string]bool
 }
 
 func NewSubstContext() *SubstContext {
@@ -70,15 +69,23 @@ func (ctx *SubstContext) Varassign(mklin
                return
        }
 
+       foreign := true
        switch varcanon {
-       case "SUBST_STAGE.*":
-       case "SUBST_MESSAGE.*":
-       case "SUBST_FILES.*":
-       case "SUBST_SED.*":
-       case "SUBST_VARS.*":
-       case "SUBST_FILTER_CMD.*":
+       case
+               "SUBST_STAGE.*",
+               "SUBST_MESSAGE.*",
+               "SUBST_FILES.*",
+               "SUBST_SED.*",
+               "SUBST_VARS.*",
+               "SUBST_FILTER_CMD.*":
+               foreign = false
+       }
+
+       if foreign && ctx.vars[varname] {
+               foreign = false
+       }
 
-       default:
+       if foreign {
                if ctx.id != "" {
                        mkline.Warnf("Foreign variable %q in SUBST block.", varname)
                }
@@ -90,7 +97,7 @@ func (ctx *SubstContext) Varassign(mklin
                ctx.id = varparam
        }
 
-       if varparam != ctx.id {
+       if hasPrefix(varname, "SUBST_") && varparam != ctx.id {
                if ctx.IsComplete() {
                        // XXX: This code sometimes produces weird warnings. See
                        // meta-pkgs/xorg/Makefile.common 1.41 for an example.
@@ -127,7 +134,7 @@ func (ctx *SubstContext) Varassign(mklin
                if G.Pkg != nil && (value == "pre-configure" || value == "post-configure") {
                        if noConfigureLine := G.Pkg.vars.FirstDefinition("NO_CONFIGURE"); noConfigureLine != nil {
                                mkline.Warnf("SUBST_STAGE %s has no effect when NO_CONFIGURE is set (in %s).",
-                                       value, noConfigureLine.ReferenceFrom(mkline.Line))
+                                       value, mkline.RefTo(noConfigureLine))
                                Explain(
                                        "To fix this properly, remove the definition of NO_CONFIGURE.")
                        }
@@ -143,6 +150,12 @@ func (ctx *SubstContext) Varassign(mklin
        case "SUBST_VARS.*":
                ctx.dupBool(mkline, &ctx.curr.seenVars, varname, op, value)
                ctx.curr.seenTransform = true
+               for _, substVar := range mkline.ValueSplit(value, "") {
+                       if ctx.vars == nil {
+                               ctx.vars = make(map[string]bool)
+                       }
+                       ctx.vars[substVar] = true
+               }
        case "SUBST_FILTER_CMD.*":
                ctx.dupString(mkline, &ctx.filterCmd, varname, value)
                ctx.curr.seenTransform = true
@@ -201,11 +214,7 @@ func (ctx *SubstContext) Finish(mkline M
                mkline.Warnf("Incomplete SUBST block: SUBST_SED.%[1]s, SUBST_VARS.%[1]s or SUBST_FILTER_CMD.%[1]s missing.", id)
        }
 
-       ctx.id = ""
-       ctx.stage = ""
-       ctx.message = ""
-       ctx.curr = &SubstContextStats{}
-       ctx.filterCmd = ""
+       *ctx = *NewSubstContext()
 }
 
 func (ctx *SubstContext) dupString(mkline MkLine, pstr *string, varname, value string) {

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.27 pkgsrc/pkgtools/pkglint/files/check_test.go:1.28
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.27    Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Wed Nov  7 20:58:23 2018
@@ -14,7 +14,6 @@ import (
 
        "gopkg.in/check.v1"
        "netbsd.org/pkglint/textproc"
-       "netbsd.org/pkglint/trace"
 )
 
 var equals = check.Equals
@@ -30,15 +29,19 @@ type Suite struct {
 
 // Init creates and returns a test helper that allows to:
 //
-// * create files for the test
+// * create files for the test:
+// CreateFileLines, SetupPkgsrc, SetupPackage
 //
-// * load these files into Line and MkLine objects (for tests spanning multiple files)
+// * load these files into Line and MkLine objects (for tests spanning multiple files):
+// SetupFileLines, SetupFileMkLines
 //
-// * create new in-memory Line and MkLine objects (for simple tests)
+// * create new in-memory Line and MkLine objects (for simple tests):
+// NewLine, NewLines, NewMkLine, NewMkLines
 //
-// * check the files that have been changed by the --autofix feature
+// * check the files that have been changed by the --autofix feature:
+// CheckFileLines
 //
-// * check the pkglint diagnostics
+// * check the pkglint diagnostics: CheckLinesEmpty, CheckLinesOutput
 func (s *Suite) Init(c *check.C) *Tester {
 
        // Note: the check.C object from SetUpTest cannot be used here,
@@ -46,15 +49,15 @@ func (s *Suite) Init(c *check.C) *Tester
        // see https://github.com/go-check/check/issues/22.
 
        t := s.Tester // Has been initialized by SetUpTest
-       if t.checkC != nil {
+       if t.c != nil {
                panic("Suite.Init must only be called once.")
        }
-       t.checkC = c
+       t.c = c
        return t
 }
 
 func (s *Suite) SetUpTest(c *check.C) {
-       t := &Tester{checkC: c}
+       t := &Tester{c: c}
        s.Tester = t
 
        G = NewPkglint()
@@ -63,13 +66,17 @@ func (s *Suite) SetUpTest(c *check.C) {
        G.logOut = NewSeparatorWriter(&t.stdout)
        G.logErr = NewSeparatorWriter(&t.stderr)
        trace.Out = &t.stdout
+
+       // XXX: Maybe the tests can run a bit faster when they don't
+       // create a temporary directory each.
        G.Pkgsrc = NewPkgsrc(t.File("."))
 
-       t.checkC = c
-       t.SetupCommandLine( /* no arguments */ )
-       t.checkC = nil
+       t.c = c
+       t.SetupCommandLine("-Wall") // To catch duplicate warnings
+       t.c = nil
 
-       G.opts.LogVerbose = true // To detect duplicate work being done
+       // To improve code coverage and ensure that trace.Result works
+       // in all cases. The latter cannot be ensured at compile time.
        t.EnableSilentTracing()
 
        prevdir, err := os.Getwd()
@@ -81,17 +88,26 @@ func (s *Suite) SetUpTest(c *check.C) {
 
 func (s *Suite) TearDownTest(c *check.C) {
        t := s.Tester
-       t.checkC = nil // No longer usable; see https://github.com/go-check/check/issues/22
+       t.c = nil // No longer usable; see https://github.com/go-check/check/issues/22
 
        if err := os.Chdir(t.prevdir); err != nil {
-               fmt.Fprintf(os.Stderr, "Cannot chdir back to previous dir: %s", err)
+               _, _ = fmt.Fprintf(os.Stderr, "Cannot chdir back to previous dir: %s", err)
        }
 
        G = Pkglint{} // unusable because of missing logOut and logErr
        textproc.Testing = false
        if out := t.Output(); out != "" {
-               fmt.Fprintf(os.Stderr, "Unchecked output in %q; check with: t.CheckOutputLines(%#v)",
-                       c.TestName(), strings.Split(out, "\n"))
+               var msg strings.Builder
+               msg.WriteString("\n")
+               _, _ = fmt.Fprintf(&msg, "Unchecked output in %s; check with:\n", c.TestName())
+               msg.WriteString("\n")
+               msg.WriteString("t.CheckOutputLines(\n")
+               lines := strings.Split(strings.TrimSpace(out), "\n")
+               for i, line := range lines {
+                       _, _ = fmt.Fprintf(&msg, "\t%q%s\n", line, ifelseStr(i == len(lines)-1, ")", ","))
+               }
+               _, _ = fmt.Fprintf(&msg, "\n")
+               _, _ = os.Stderr.WriteString(msg.String())
        }
        t.tmpdir = ""
        t.DisableTracing()
@@ -109,20 +125,16 @@ type Tester struct {
        stdout  bytes.Buffer
        stderr  bytes.Buffer
        tmpdir  string
-       checkC  *check.C // Only usable during the test method itself
+       c       *check.C // Only usable during the test method itself
        prevdir string   // The current working directory before the test started
-       relcwd  string
-}
-
-func (t *Tester) c() *check.C {
-       if t.checkC == nil {
-               panic("Suite.Init must be called before accessing check.C.")
-       }
-       return t.checkC
+       relCwd  string   // See Tester.Chdir
 }
 
 // SetupCommandLine simulates a command line for the remainder of the test.
 // See Pkglint.ParseCommandLine.
+//
+// If SetupCommandLine is not called explicitly in a test, the command line
+// "-Wall" is used, to provide a high code coverage in the tests.
 func (t *Tester) SetupCommandLine(args ...string) {
 
        // Prevent tracing from being disabled; see EnableSilentTracing.
@@ -132,27 +144,31 @@ func (t *Tester) SetupCommandLine(args .
        exitcode := G.ParseCommandLine(append([]string{"pkglint"}, args...))
        if exitcode != nil && *exitcode != 0 {
                t.CheckOutputEmpty()
-               t.c().Fatalf("Cannot parse command line: %#v", args)
+               t.c.Fatalf("Cannot parse command line: %#v", args)
        }
-       G.opts.LogVerbose = true // See SetUpTest
+
+       // Duplicate diagnostics often mean that the checking code is run
+       // twice, which is unnecessary.
+       //
+       // It also reveals diagnostics that are logged multiple times per
+       // line and thus can easily get annoying to the pkgsrc developers.
+       G.Opts.LogVerbose = true
 }
 
 // SetupVartypes registers a few hundred variables like MASTER_SITES,
 // WRKSRC, SUBST_SED.*, so that their data types are known to pkglint.
+//
+// Without calling this, there will be many warnings about undefined
+// or unused variables, or unknown shell commands.
+//
+// See SetupTool for registering tools like echo, awk, perl.
 func (t *Tester) SetupVartypes() {
        G.Pkgsrc.InitVartypes()
 }
 
 func (t *Tester) SetupMasterSite(varname string, urls ...string) {
-       name2url := &G.Pkgsrc.MasterSiteVarToURL
-       url2name := &G.Pkgsrc.MasterSiteURLToVar
-       if *name2url == nil {
-               *name2url = make(map[string]string)
-               *url2name = make(map[string]string)
-       }
-       (*name2url)[varname] = urls[0]
        for _, url := range urls {
-               (*url2name)[url] = varname
+               G.Pkgsrc.registerMasterSite(varname, url)
        }
 }
 
@@ -162,21 +178,25 @@ func (t *Tester) SetupOption(name, descr
 }
 
 func (t *Tester) SetupTool(name, varname string, validity Validity) *Tool {
-       return G.Pkgsrc.Tools.defTool(name, varname, false, validity)
+       return G.Pkgsrc.Tools.def(name, varname, false, validity)
 }
 
 // SetupFileLines creates a temporary file and writes the given lines to it.
-// The file is then read in, without considering line continuations.
-func (t *Tester) SetupFileLines(relativeFilename string, lines ...string) []Line {
-       filename := t.CreateFileLines(relativeFilename, lines...)
-       return Load(filename, MustSucceed)
+// The file is then read in, without interpreting line continuations.
+//
+// See SetupFileMkLines for loading a Makefile fragment.
+func (t *Tester) SetupFileLines(relativeFileName string, lines ...string) Lines {
+       fileName := t.CreateFileLines(relativeFileName, lines...)
+       return Load(fileName, MustSucceed)
 }
 
 // SetupFileLines creates a temporary file and writes the given lines to it.
 // The file is then read in, handling line continuations for Makefiles.
-func (t *Tester) SetupFileMkLines(relativeFilename string, lines ...string) *MkLines {
-       filename := t.CreateFileLines(relativeFilename, lines...)
-       return LoadMk(filename, MustSucceed)
+//
+// See SetupFileLines for loading an ordinary file.
+func (t *Tester) SetupFileMkLines(relativeFileName string, lines ...string) MkLines {
+       fileName := t.CreateFileLines(relativeFileName, lines...)
+       return LoadMk(fileName, MustSucceed)
 }
 
 // SetupPkgsrc sets up a minimal but complete pkgsrc installation in the
@@ -205,47 +225,63 @@ func (t *Tester) SetupPkgsrc() {
        t.CreateFileLines("licenses/gnu-gpl-v2",
                "The licenses for most software ...")
 
-       // The MASTER_SITES in the package Makefile are searched here.
+       // The various MASTER_SITE_* variables for use in the
+       // MASTER_SITES are defined in this file.
+       //
        // See Pkgsrc.loadMasterSites.
        t.CreateFileLines("mk/fetch/sites.mk",
                MkRcsID)
 
-       // The options for the PKG_OPTIONS framework must be readable.
+       // The options for the PKG_OPTIONS framework are defined here.
+       //
        // See Pkgsrc.loadPkgOptions.
-       t.CreateFileLines("mk/defaults/options.description")
+       t.CreateFileLines("mk/defaults/options.description",
+               "example-option   Description for the example option",
+               "example-option-without-description")
 
        // The user-defined variables are read in to check for missing
        // BUILD_DEFS declarations in the package Makefile.
        t.CreateFileLines("mk/defaults/mk.conf",
                MkRcsID)
 
-       // The tool definitions are read in to check for missing
-       // USE_TOOLS declarations in the package Makefile.
-       // They spread over several files from the pkgsrc infrastructure.
+       // The tool definitions are defined in various files in mk/tools/.
+       // The relevant files are listed in bsd.tools.mk.
+       // The tools that are defined here can be used in USE_TOOLS.
        t.CreateFileLines("mk/tools/bsd.tools.mk",
                ".include \"defaults.mk\"")
        t.CreateFileLines("mk/tools/defaults.mk",
                MkRcsID)
-       t.CreateFileLines("mk/bsd.prefs.mk", // Some tools are defined here.
+
+       // Those tools that are added to USE_TOOLS in bsd.prefs.mk may be
+       // used at load time by packages.
+       t.CreateFileLines("mk/bsd.prefs.mk",
                MkRcsID)
+
+       // Category Makefiles require this file for the common definitions.
+       t.CreateFileLines("mk/misc/category.mk")
 }
 
 // SetupCategory makes the given category valid by creating a dummy Makefile.
+// After that, it can be mentioned in the CATEGORIES variable of a package.
 func (t *Tester) SetupCategory(name string) {
-       if _, err := os.Stat(name + "/Makefile"); os.IsNotExist(err) {
+       G.Assertf(!contains(name, "/"), "Category must not contain a slash.")
+
+       if _, err := os.Stat(t.File(name + "/Makefile")); os.IsNotExist(err) {
                t.CreateFileLines(name+"/Makefile",
                        MkRcsID)
        }
 }
 
 // SetupPackage sets up all files for a package (including the pkgsrc
-// infrastructure) so that it does not produce any warnings. After calling
-// this method, individual files can be overwritten as necessary.
+// infrastructure) so that it does not produce any warnings.
 //
 // The given makefileLines start in line 20. Except if they are variable
 // definitions for already existing variables, then they replace that line.
 //
 // Returns the path to the package, ready to be used with Pkglint.CheckDirent.
+//
+// After calling this method, individual files can be overwritten as necessary.
+// Then, G.Pkgsrc.LoadInfrastructure should be called to load all the files.
 func (t *Tester) SetupPackage(pkgpath string, makefileLines ...string) string {
        category := path.Dir(pkgpath)
 
@@ -266,19 +302,18 @@ func (t *Tester) SetupPackage(pkgpath st
                "SHA512 (distfile-1.0.tar.gz) = 12341234...",
                "Size (distfile-1.0.tar.gz) = 12341234")
 
-       var mlines []string
-       mlines = append(mlines,
+       mlines := []string{
                MkRcsID,
                "",
                "DISTNAME=\tdistname-1.0",
-               "CATEGORIES=\t"+category,
+               "CATEGORIES=\t" + category,
                "MASTER_SITES=\t# none",
                "",
                "MAINTAINER=\tpkgsrc-users%NetBSD.org@localhost",
                "HOMEPAGE=\t# none",
                "COMMENT=\tDummy package",
                "LICENSE=\t2-clause-bsd",
-               "")
+               ""}
        for len(mlines) < 19 {
                mlines = append(mlines, "# empty")
        }
@@ -306,18 +341,23 @@ line:
        return t.File(pkgpath)
 }
 
-func (t *Tester) CreateFileLines(relativeFilename string, lines ...string) (fileName string) {
-       content := ""
+// CreateFileLines creates a file in the temporary directory and writes the
+// given lines to it.
+//
+// It returns the full path to the created file.
+func (t *Tester) CreateFileLines(relativeFileName string, lines ...string) (fileName string) {
+       var content bytes.Buffer
        for _, line := range lines {
-               content += line + "\n"
+               content.WriteString(line)
+               content.WriteString("\n")
        }
 
-       fileName = t.File(relativeFilename)
+       fileName = t.File(relativeFileName)
        err := os.MkdirAll(path.Dir(fileName), 0777)
-       t.c().Assert(err, check.IsNil)
+       t.c.Assert(err, check.IsNil)
 
-       err = ioutil.WriteFile(fileName, []byte(content), 0666)
-       t.c().Check(err, check.IsNil)
+       err = ioutil.WriteFile(fileName, []byte(content.Bytes()), 0666)
+       t.c.Assert(err, check.IsNil)
 
        G.fileCache.Evict(fileName)
 
@@ -342,14 +382,14 @@ func (t *Tester) CreateFileDummyPatch(re
 // File returns the absolute path to the given file in the
 // temporary directory. It doesn't check whether that file exists.
 // Calls to Tester.Chdir change the base directory for the relative file name.
-func (t *Tester) File(relativeFilename string) string {
+func (t *Tester) File(relativeFileName string) string {
        if t.tmpdir == "" {
-               t.tmpdir = filepath.ToSlash(t.c().MkDir())
+               t.tmpdir = filepath.ToSlash(t.c.MkDir())
        }
-       if t.relcwd != "" {
-               return cleanpath(relativeFilename)
+       if t.relCwd != "" {
+               return path.Clean(relativeFileName)
        }
-       return cleanpath(t.tmpdir + "/" + relativeFilename)
+       return path.Clean(t.tmpdir + "/" + relativeFileName)
 }
 
 // Chdir changes the current working directory to the given subdirectory
@@ -364,30 +404,37 @@ func (t *Tester) File(relativeFilename s
 //
 // As long as this method is not called in a test, the current working
 // directory is indeterminate.
-func (t *Tester) Chdir(relativeFilename string) {
-       if t.relcwd != "" {
+func (t *Tester) Chdir(relativeDirName string) {
+       if t.relCwd != "" {
                // When multiple calls of Chdir are mixed with calls to CreateFileLines,
-               // the resulting []Line and MkLines variables will use relative file names,
+               // the resulting Lines and MkLines variables will use relative file names,
                // and these will point to different areas in the file system. This is
                // usually not indented and therefore prevented.
-               t.checkC.Fatalf("Chdir must only be called once per test; already in %q.", t.relcwd)
+               t.c.Fatalf("Chdir must only be called once per test; already in %q.", t.relCwd)
        }
 
-       _ = os.MkdirAll(t.File(relativeFilename), 0700)
-       if err := os.Chdir(t.File(relativeFilename)); err != nil {
-               t.checkC.Fatalf("Cannot chdir: %s", err)
+       _ = os.MkdirAll(t.File(relativeDirName), 0700)
+       if err := os.Chdir(t.File(relativeDirName)); err != nil {
+               t.c.Fatalf("Cannot chdir: %s", err)
        }
-       t.relcwd = relativeFilename
+       t.relCwd = relativeDirName
 }
 
 // Remove removes the file from the temporary directory. The file must exist.
-func (t *Tester) Remove(relativeFilename string) {
-       fileName := t.File(relativeFilename)
+func (t *Tester) Remove(relativeFileName string) {
+       fileName := t.File(relativeFileName)
        err := os.Remove(fileName)
-       t.c().Check(err, check.IsNil)
+       t.c.Assert(err, check.IsNil)
        G.fileCache.Evict(fileName)
 }
 
+// Check delegates a check to the check.Check function.
+// Thereby, there is no need to distinguish between c.Check and t.Check
+// in the test code.
+func (t *Tester) Check(obj interface{}, checker check.Checker, args ...interface{}) bool {
+       return t.c.Check(obj, checker, args...)
+}
+
 // ExpectFatal runs the given action and expects that this action calls
 // Line.Fatalf or uses some other way to panic with a pkglintFatal.
 //
@@ -399,7 +446,7 @@ func (t *Tester) ExpectFatal(action func
        defer func() {
                r := recover()
                if r == nil {
-                       panic("Expected a pkglint fatal error, but didn't get one.")
+                       panic("Expected a pkglint fatal error but didn't get one.")
                } else if _, ok := r.(pkglintFatal); ok {
                        t.CheckOutputLines(expectedLines...)
                } else {
@@ -412,7 +459,7 @@ func (t *Tester) ExpectFatal(action func
 
 // ExpectFatalMatches runs the given action and expects that this action
 // calls Line.Fatalf or uses some other way to panic with a pkglintFatal.
-// It then matches the output against a regular expression.
+// It then matches the output against the given regular expression.
 //
 // Usage:
 //  t.ExpectFatalMatches(
@@ -422,9 +469,9 @@ func (t *Tester) ExpectFatalMatches(acti
        defer func() {
                r := recover()
                if r == nil {
-                       panic("Expected a pkglint fatal error, but didn't get one.")
+                       panic("Expected a pkglint fatal error but didn't get one.")
                } else if _, ok := r.(pkglintFatal); ok {
-                       t.c().Check(t.Output(), check.Matches, string(expected))
+                       t.Check(t.Output(), check.Matches, string(expected))
                } else {
                        panic(r)
                }
@@ -433,7 +480,23 @@ func (t *Tester) ExpectFatalMatches(acti
        action()
 }
 
-// Arguments are either (lineno, orignl) or (lineno, orignl, textnl).
+// ExpectPanic runs the given action and expects that this action calls
+// Pkglint.Assertf or uses some other way to panic.
+//
+// Usage:
+//  t.ExpectPanic(
+//      func() { /* do something that panics */ },
+//      "FATAL: ~/Makefile:1: Must not be empty")
+func (t *Tester) ExpectPanic(action func(), expectedMessage string) {
+       t.Check(action, check.Panics, expectedMessage)
+}
+
+// NewRawLines creates lines from line numbers and raw text, including newlines.
+//
+// Arguments are sequences of either (lineno, orignl) or (lineno, orignl, textnl).
+//
+// Specifying textnl is only useful when simulating a line that has already been
+// modified by Autofix.
 func (t *Tester) NewRawLines(args ...interface{}) []*RawLine {
        rawlines := make([]*RawLine, len(args)/2)
        j := 0
@@ -453,12 +516,15 @@ func (t *Tester) NewRawLines(args ...int
        return rawlines[:j]
 }
 
-func (t *Tester) NewLine(filename string, lineno int, text string) Line {
+// NewLine creates an in-memory line with the given text.
+// This line does not correspond to any line in a file.
+func (t *Tester) NewLine(fileName string, lineno int, text string) Line {
        textnl := text + "\n"
        rawLine := RawLine{lineno, textnl, textnl}
-       return NewLine(filename, lineno, text, []*RawLine{&rawLine})
+       return NewLine(fileName, lineno, text, []*RawLine{&rawLine})
 }
 
+// NewMkLine creates an in-memory line in the Makefile format with the given text.
 func (t *Tester) NewMkLine(fileName string, lineno int, text string) MkLine {
        return NewMkLine(t.NewLine(fileName, lineno, text))
 }
@@ -467,37 +533,41 @@ func (t *Tester) NewShellLine(fileName s
        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 CreateFileLines together with LoadExistingLines.
-func (t *Tester) NewLines(fileName string, lines ...string) []Line {
+// NewLines returns a list of simple lines that belong together.
+//
+// To work with line continuations like in Makefiles, use SetupFileMkLines.
+func (t *Tester) NewLines(fileName string, lines ...string) Lines {
        return t.NewLinesAt(fileName, 1, 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.CreateFileLines together with Suite.LoadExistingLines.
-func (t *Tester) NewLinesAt(fileName string, firstLine int, texts ...string) []Line {
-       result := make([]Line, len(texts))
+// NewLinesAt returns a list of simple lines that belong together.
+//
+// To work with line continuations like in Makefiles, use SetupFileMkLines.
+func (t *Tester) NewLinesAt(fileName string, firstLine int, texts ...string) Lines {
+       lines := make([]Line, len(texts))
        for i, text := range texts {
-               textnl := text + "\n"
-               result[i] = NewLine(fileName, i+firstLine, text, t.NewRawLines(i+firstLine, textnl))
+               lines[i] = t.NewLine(fileName, i+firstLine, text)
        }
-       return result
+       return NewLines(fileName, lines)
 }
 
-func (t *Tester) NewMkLines(fileName string, lines ...string) *MkLines {
-       rawText := ""
+// NewMkLines returns a list of lines in Makefile format,
+// as if they were parsed from a Makefile fragment,
+// taking continuation lines into account.
+//
+// No actual file is created for the lines;
+// see SetupFileMkLines for loading Makefile fragments with line continuations.
+func (t *Tester) NewMkLines(fileName string, lines ...string) MkLines {
+       var rawText strings.Builder
        for _, line := range lines {
-               rawText += line + "\n"
+               rawText.WriteString(line)
+               rawText.WriteString("\n")
        }
-       return NewMkLines(convertToLogicalLines(fileName, rawText, true))
+       return NewMkLines(convertToLogicalLines(fileName, rawText.String(), true))
 }
 
 // Returns and consumes the output from both stdout and stderr.
-// The temporary directory is replaced with a tilde (~).
+// In the output, the temporary directory is replaced with a tilde (~).
 func (t *Tester) Output() string {
        stdout := t.stdout.String()
        stderr := t.stderr.String()
@@ -508,26 +578,41 @@ func (t *Tester) Output() string {
        output := stdout + stderr
        if t.tmpdir != "" {
                output = strings.Replace(output, t.tmpdir, "~", -1)
+       } else {
+               panic("asdfgsfas")
        }
        return output
 }
 
+// CheckOutputEmpty ensures that the output up to now is empty.
+//
+// See CheckOutputLines.
 func (t *Tester) CheckOutputEmpty() {
-       t.CheckOutputLines( /* none */ )
+       output := t.Output()
+       actualLines := strings.Split(output, "\n")
+       actualLines = actualLines[:len(actualLines)-1]
+       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(nil))
 }
 
 // CheckOutputLines checks that the output up to now equals the given lines.
 // After the comparison, the output buffers are cleared so that later
 // calls only check against the newly added output.
+//
+// See CheckOutputEmpty.
 func (t *Tester) CheckOutputLines(expectedLines ...string) {
+       G.Assertf(len(expectedLines) > 0, "To check empty lines, use CheckLinesEmpty instead.")
+
        output := t.Output()
        actualLines := strings.Split(output, "\n")
        actualLines = actualLines[:len(actualLines)-1]
-       t.c().Check(emptyToNil(actualLines), deepEquals, emptyToNil(expectedLines))
+       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(expectedLines))
 }
 
-// EnableTracing redirects all logging output (which is normally captured
-// in an in-memory buffer) additionally to stdout.
+// EnableTracing logs the tracing output to os.Stdout instead of silently discarding it.
+// The normal diagnostics are written to the in-memory buffer as usual,
+// and additionally they are written to os.Stdout,
+// where they are shown together with the trace log.
+//
 // This is useful when stepping through the code, especially
 // in combination with SetupCommandLine("--debug").
 //
@@ -542,21 +627,25 @@ func (t *Tester) EnableTracing() {
 // EnableTracingToLog enables the tracing and writes the tracing output
 // to the test log that can be examined with Tester.Output.
 func (t *Tester) EnableTracingToLog() {
-       G.logOut = NewSeparatorWriter(io.MultiWriter(os.Stdout, &t.stdout))
+       t.EnableTracing()
        trace.Out = &t.stdout
-       trace.Tracing = true
 }
 
-// EnableSilentTracing enables tracing mode, but discards any tracing output.
-// This can be used to improve code coverage without any side-effects,
-// since tracing output is quite large.
+// EnableSilentTracing enables tracing mode but discards any tracing output.
+// This is the default mode when running the tests.
+// The diagnostics go to the in-memory buffer.
+//
+// It is used to check all calls to trace.Result, since the compiler
+// cannot check them.
 func (t *Tester) EnableSilentTracing() {
+       G.logOut = NewSeparatorWriter(&t.stdout)
        trace.Out = ioutil.Discard
        trace.Tracing = true
 }
 
-// DisableTracing logs the output to the buffers again, ready to be
-// checked with CheckOutputLines.
+// DisableTracing skips all tracing code.
+// The diagnostics go to the in-memory buffer again,
+// ready to be checked with CheckOutputLines.
 func (t *Tester) DisableTracing() {
        G.logOut = NewSeparatorWriter(&t.stdout)
        trace.Tracing = false
@@ -567,25 +656,23 @@ func (t *Tester) DisableTracing() {
 // they equal the given lines.
 func (t *Tester) CheckFileLines(relativeFileName string, lines ...string) {
        content, err := ioutil.ReadFile(t.File(relativeFileName))
-       t.c().Assert(err, check.IsNil)
-       text := string(content)
-       actualLines := strings.Split(text, "\n")
+       t.c.Assert(err, check.IsNil)
+       actualLines := strings.Split(string(content), "\n")
        actualLines = actualLines[:len(actualLines)-1]
-       t.c().Check(emptyToNil(actualLines), deepEquals, emptyToNil(lines))
+       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(lines))
 }
 
 // CheckFileLinesDetab loads the lines from the temporary file and checks
 // that they equal the given lines. The loaded file may use tabs or spaces
 // for indentation, while the lines in the code use spaces exclusively,
-// in order to make the depth of the indentation clearly visible.
+// in order to make the depth of the indentation clearly visible in the test code.
 func (t *Tester) CheckFileLinesDetab(relativeFileName string, lines ...string) {
        actualLines := Load(t.File(relativeFileName), MustSucceed)
 
-       var detabbed []string
-       for _, line := range actualLines {
-               rawText := strings.TrimRight(detab(line.raw[0].orignl), "\n")
-               detabbed = append(detabbed, rawText)
+       var detabbedLines []string
+       for _, line := range actualLines.Lines {
+               detabbedLines = append(detabbedLines, detab(line.Text))
        }
 
-       t.c().Check(detabbed, deepEquals, lines)
+       t.Check(detabbedLines, deepEquals, lines)
 }
Index: pkgsrc/pkgtools/pkglint/files/shell.go
diff -u pkgsrc/pkgtools/pkglint/files/shell.go:1.27 pkgsrc/pkgtools/pkglint/files/shell.go:1.28
--- pkgsrc/pkgtools/pkglint/files/shell.go:1.27 Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/shell.go      Wed Nov  7 20:58:23 2018
@@ -4,7 +4,6 @@ package main
 
 import (
        "netbsd.org/pkglint/textproc"
-       "netbsd.org/pkglint/trace"
        "path"
 )
 
@@ -15,6 +14,8 @@ const (
        reShDollar       = `\\\$\$|` + reShVaruse + `|\$\$[,\-/]`
 )
 
+// TODO: Can ShellLine and ShellProgramChecker be merged into one type?
+
 type ShellLine struct {
        mkline MkLine
 }
@@ -91,7 +92,7 @@ outer:
                                repl.AdvanceRegexp(`^\$\$\{([0-9A-Z_a-z]+|#)\}`),
                                repl.AdvanceRegexp(`^\$\$(\$)\$`):
                                shvarname := repl.Group(1)
-                               if G.opts.WarnQuoting && checkQuoting && shline.variableNeedsQuoting(shvarname) {
+                               if G.Opts.WarnQuoting && checkQuoting && shline.variableNeedsQuoting(shvarname) {
                                        line.Warnf("Unquoted shell variable %q.", shvarname)
                                        Explain(
                                                "When a shell variable contains white-space, it is expanded (split",
@@ -103,9 +104,9 @@ outer:
                                                "",
                                                "Example:",
                                                "\tfname=\"Curriculum vitae.doc\"",
-                                               "\tcp $fname /tmp",
+                                               "\tcp $fileName /tmp",
                                                "\t# tries to copy the two files \"Curriculum\" and \"Vitae.doc\"",
-                                               "\tcp \"$fname\" /tmp",
+                                               "\tcp \"$fileName\" /tmp",
                                                "\t# copies one file, as intended")
                                }
 
@@ -131,11 +132,11 @@ outer:
 
                case quoting == shqSquot:
                        switch {
-                       case repl.AdvanceRegexp(`^'`):
+                       case repl.AdvanceStr("'"):
                                quoting = shqPlain
-                       case repl.AdvanceRegexp(`^[^\$\']+`):
+                       case repl.NextBytesFunc(func(b byte) bool { return b != '$' && b != '\'' }) != "":
                                // just skip
-                       case repl.AdvanceRegexp(`^\$\$`):
+                       case repl.AdvanceStr("$$"):
                                // just skip
                        default:
                                break outer
@@ -147,7 +148,7 @@ outer:
                                quoting = shqPlain
                        case repl.AdvanceStr("`"):
                                quoting = shqDquotBackt
-                       case repl.AdvanceRegexp("^[^$\"\\\\`]+"):
+                       case repl.NextBytesFunc(func(b byte) bool { return b != '$' && b != '"' && b != '\\' && b != '`' }) != "":
                                break
                        case repl.AdvanceStr("\\$$"):
                                break
@@ -222,7 +223,7 @@ func (shline *ShellLine) unescapeBacktic
        }
 
        line := shline.mkline.Line
-       for !repl.EOF() {
+       for repl.Rest() != "" {
                switch {
                case repl.AdvanceStr("`"):
                        if quoting == shqBackt {
@@ -282,7 +283,8 @@ func (shline *ShellLine) CheckShellComma
                        "to understand, since all the complexity of using sed and mv is",
                        "hidden behind the scenes.",
                        "",
-                       "Run \""+confMake+" help topic=subst\" for more information.")
+                       // TODO: Provide a copy-and-paste example.
+                       sprintf("Run %q for more information.", makeHelp("subst")))
                if contains(shelltext, "#") {
                        Explain(
                                "When migrating to the SUBST framework, pay attention to \"#\"",
@@ -302,19 +304,18 @@ func (shline *ShellLine) CheckShellComma
                line.Notef("You don't need to use \"-\" before %q.", cmd)
        }
 
-       repl := G.NewPrefixReplacer(shelltext)
-       repl.SkipHspace()
-       if repl.AdvanceRegexp(`^[-@]+`) {
-               shline.checkHiddenAndSuppress(repl.Group(0), repl.Rest())
-       }
-       setE := false
-       if repl.AdvanceStr("${RUN}") {
-               setE = true
-       } else {
-               repl.AdvanceStr("${_PKG_SILENT}${_PKG_DEBUG}")
+       lexer := textproc.NewLexer(shelltext)
+       lexer.NextHspace()
+       hiddenAndSuppress := lexer.NextBytesFunc(func(b byte) bool { return b == '-' || b == '@' })
+       if hiddenAndSuppress != "" {
+               shline.checkHiddenAndSuppress(hiddenAndSuppress, lexer.Rest())
+       }
+       setE := lexer.NextString("${RUN}") != ""
+       if !setE {
+               lexer.NextString("${_PKG_SILENT}${_PKG_DEBUG}")
        }
 
-       shline.CheckShellCommand(repl.Rest(), &setE, RunTime)
+       shline.CheckShellCommand(lexer.Rest(), &setE, RunTime)
 }
 
 func (shline *ShellLine) CheckShellCommand(shellcmd string, pSetE *bool, time ToolTime) {
@@ -345,7 +346,7 @@ func (shline *ShellLine) CheckShellComma
                }
        }
        walker.Callback.AndOr = func(andor *MkShAndOr) {
-               if G.opts.WarnExtra && !*pSetE && walker.Current().Index != 0 {
+               if G.Opts.WarnExtra && !*pSetE && walker.Current().Index != 0 {
                        spc.checkSetE(walker.Parent(1).(*MkShList), walker.Current().Index, andor)
                }
        }
@@ -405,7 +406,7 @@ func (shline *ShellLine) checkHiddenAndS
                                        "cannot be assigned to the command, which is very difficult to debug.",
                                        "",
                                        "It is better to insert ${RUN} at the beginning of the whole command",
-                                       "line.  This will hide the command by default, but shows it when",
+                                       "line.  This will hide the command by default but shows it when",
                                        "PKG_DEBUG_LEVEL is set.")
                        }
                }
@@ -463,7 +464,7 @@ func (scc *SimpleCommandChecker) checkCo
        case matches(shellword, `\$\{(PKGSRCDIR|PREFIX)(:Q)?\}`):
        case scc.handleComment():
        default:
-               if G.opts.WarnExtra && !(G.Mk != nil && G.Mk.indentation.DependsOn("OPSYS")) {
+               if G.Opts.WarnExtra && !(G.Mk != nil && G.Mk.indentation.DependsOn("OPSYS")) {
                        scc.shline.mkline.Warnf("Unknown shell command %q.", shellword)
                        Explain(
                                "If you want your package to be portable to all platforms that pkgsrc",
@@ -632,7 +633,7 @@ func (scc *SimpleCommandChecker) checkAu
        for _, arg := range scc.strcmd.Args {
                if !contains(arg, "$$") && !matches(arg, `\$\{[_.]*[a-z]`) {
                        if m, dirname := match1(arg, `^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/(.*)`); m {
-                               if G.Pkg != nil && G.Pkg.PlistDirs[dirname] {
+                               if G.Pkg != nil && G.Pkg.Plist.Dirs[dirname] {
                                        scc.shline.mkline.Notef("You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname)
                                        Explain(
                                                "Many packages include a list of all needed directories in their",
@@ -792,7 +793,7 @@ func (spc *ShellProgramChecker) checkPip
                return false, ""
        }
 
-       if G.opts.WarnExtra && len(pipeline.Cmds) > 1 {
+       if G.Opts.WarnExtra && len(pipeline.Cmds) > 1 {
                if canFail, cmd := canFail(); canFail {
                        if cmd != "" {
                                line.Warnf("The exitcode of %q at the left of the | operator is ignored.", cmd)
@@ -1005,6 +1006,8 @@ func splitIntoShellTokens(line Line, tex
 
 // Example: "word1 word2;;;" => "word1", "word2;;;"
 // Compare devel/bmake/files/str.c, function brk_string.
+//
+// TODO: Move to mkline.go or mkparser.go.
 func splitIntoMkWords(line Line, text string) (words []string, rest string) {
        if trace.Tracing {
                defer trace.Call(line, text)()

Index: pkgsrc/pkgtools/pkglint/files/distinfo.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo.go:1.22 pkgsrc/pkgtools/pkglint/files/distinfo.go:1.23
--- pkgsrc/pkgtools/pkglint/files/distinfo.go:1.22      Thu Aug 16 20:41:42 2018
+++ pkgsrc/pkgtools/pkglint/files/distinfo.go   Wed Nov  7 20:58:23 2018
@@ -3,19 +3,17 @@ package main
 import (
        "bytes"
        "crypto/sha1"
-       "fmt"
        "io/ioutil"
-       "netbsd.org/pkglint/trace"
        "path"
        "strings"
 )
 
-func ChecklinesDistinfo(lines []Line) {
+func ChecklinesDistinfo(lines Lines) {
        if trace.Tracing {
-               defer trace.Call1(lines[0].Filename)()
+               defer trace.Call1(lines.FileName)()
        }
 
-       fname := lines[0].Filename
+       fileName := lines.FileName
        patchdir := "patches"
        if G.Pkg != nil && dirExists(G.Pkg.File(G.Pkg.Patchdir)) {
                patchdir = G.Pkg.Patchdir
@@ -24,77 +22,63 @@ func ChecklinesDistinfo(lines []Line) {
                trace.Step1("patchdir=%q", patchdir)
        }
 
-       distinfoIsCommitted := isCommitted(fname)
+       distinfoIsCommitted := isCommitted(fileName)
        ck := &distinfoLinesChecker{
-               fname, patchdir, distinfoIsCommitted,
-               make(map[string]bool), nil, "", unknown, nil}
+               lines, patchdir, distinfoIsCommitted,
+               make(map[string]bool), "", nil, unknown, nil}
        ck.checkLines(lines)
        ChecklinesTrailingEmptyLines(lines)
        ck.checkUnrecordedPatches()
        SaveAutofixChanges(lines)
 }
 
+// XXX: Maybe an approach that first groups the lines by file name
+// is easier to understand.
+
 type distinfoLinesChecker struct {
-       distinfoFilename    string
+       distinfoLines       Lines
        patchdir            string // Relative to G.Pkg
        distinfoIsCommitted bool
 
-       patches          map[string]bool // "patch-aa" => true
-       currentFirstLine Line
-       currentFilename  string
-       isPatch          YesNoUnknown
-       algorithms       []string
+       // All patches that are mentioned in the distinfo file.
+       patches map[string]bool // "patch-aa" => true
+
+       currentFileName  string
+       currentFirstLine Line         // The first line of the currentFileName group
+       isPatch          YesNoUnknown // Whether currentFileName is a patch, as opposed to a distfile
+       algorithms       []string     // The algorithms seen for currentFileName
 }
 
-func (ck *distinfoLinesChecker) checkLines(lines []Line) {
-       CheckLineRcsid(lines[0], ``, "")
-       if 1 < len(lines) && lines[1].Text != "" {
-               lines[1].Notef("Empty line expected.")
+func (ck *distinfoLinesChecker) checkLines(lines Lines) {
+       CheckLineRcsid(lines.Lines[0], ``, "")
+       if 1 < len(lines.Lines) && lines.Lines[1].Text != "" {
+               lines.Lines[1].Notef("Empty line expected.")
        }
 
-       for i, line := range lines {
+       for i, line := range lines.Lines {
                if i < 2 {
                        continue
                }
-               m, alg, filename, hash := match3(line.Text, `^(\w+) \((\w[^)]*)\) = (.*)(?: bytes)?$`)
+               m, alg, fileName, hash := match3(line.Text, `^(\w+) \((\w[^)]*)\) = (.*)(?: bytes)?$`)
                if !m {
-                       line.Errorf("Invalid line.")
+                       line.Errorf("Invalid line: %s", line.Text)
                        continue
                }
 
-               if filename != ck.currentFilename {
-                       ck.onFilenameChange(line, filename)
+               if fileName != ck.currentFileName {
+                       ck.onFilenameChange(line, fileName)
                }
                ck.algorithms = append(ck.algorithms, alg)
 
-               ck.checkGlobalMismatch(line, filename, alg, hash)
-               ck.checkUncommittedPatch(line, filename, alg, hash)
+               ck.checkGlobalDistfileMismatch(line, fileName, alg, hash)
+               ck.checkUncommittedPatch(line, fileName, alg, hash)
        }
-       ck.onFilenameChange(NewLineEOF(ck.distinfoFilename), "")
+       ck.onFilenameChange(ck.distinfoLines.EOFLine(), "")
 }
 
 func (ck *distinfoLinesChecker) onFilenameChange(line Line, nextFname string) {
-       currentFname := ck.currentFilename
-       if currentFname != "" {
-               algorithms := strings.Join(ck.algorithms, ", ")
-               if ck.isPatch == yes {
-                       if algorithms != "SHA1" {
-                               line.Errorf("Expected SHA1 hash for %s, got %s.", currentFname, algorithms)
-                       }
-               } else if ck.isPatch == unknown {
-               } else if G.Pkg != nil && G.Pkg.IgnoreMissingPatches {
-               } else if hasPrefix(currentFname, "patch-") && algorithms == "SHA1" {
-                       pathToPatchdir := relpath(path.Dir(ck.currentFirstLine.Filename), G.Pkg.File(ck.patchdir))
-                       ck.currentFirstLine.Warnf("Patch file %q does not exist in directory %q.", currentFname, pathToPatchdir)
-                       Explain(
-                               "If the patches directory looks correct, the patch may have been",
-                               "removed without updating the distinfo file.  In such a case please",
-                               "update the distinfo file.",
-                               "",
-                               "If the patches directory looks wrong, pkglint needs to be improved.")
-               } else if algorithms != "SHA1, RMD160, Size" && algorithms != "SHA1, RMD160, SHA512, Size" {
-                       line.Errorf("Expected SHA1, RMD160, SHA512, Size checksums for %q, got %s.", currentFname, algorithms)
-               }
+       if ck.currentFileName != "" {
+               ck.checkAlgorithms(line)
        }
 
        if !hasPrefix(nextFname, "patch-") {
@@ -107,41 +91,80 @@ func (ck *distinfoLinesChecker) onFilena
                ck.isPatch = no
        }
 
-       ck.currentFilename = nextFname
+       ck.currentFileName = nextFname
        ck.currentFirstLine = line
        ck.algorithms = nil
 }
 
+func (ck *distinfoLinesChecker) checkAlgorithms(line Line) {
+       fileName := ck.currentFileName
+       algorithms := strings.Join(ck.algorithms, ", ")
+
+       switch {
+
+       case ck.isPatch == yes:
+               if algorithms != "SHA1" {
+                       line.Errorf("Expected SHA1 hash for %s, got %s.", fileName, algorithms)
+               }
+
+       case ck.isPatch == unknown:
+               break
+
+       case G.Pkg != nil && G.Pkg.IgnoreMissingPatches:
+               break
+
+       case hasPrefix(fileName, "patch-") && algorithms == "SHA1":
+               pathToPatchdir := relpath(path.Dir(ck.currentFirstLine.FileName), G.Pkg.File(ck.patchdir))
+               ck.currentFirstLine.Warnf("Patch file %q does not exist in directory %q.", fileName, pathToPatchdir)
+               Explain(
+                       "If the patches directory looks correct, the patch may have been",
+                       "removed without updating the distinfo file.  In such a case please",
+                       "update the distinfo file.",
+                       "",
+                       "If the patches directory looks wrong, pkglint needs to be improved.")
+
+       case algorithms != "SHA1, RMD160, Size" && algorithms != "SHA1, RMD160, SHA512, Size":
+               line.Errorf("Expected SHA1, RMD160, SHA512, Size checksums for %q, got %s.", fileName, algorithms)
+       }
+}
+
 func (ck *distinfoLinesChecker) checkUnrecordedPatches() {
        if G.Pkg == nil {
                return
        }
-       files, err := ioutil.ReadDir(G.Pkg.File(ck.patchdir))
+       patchFiles, err := ioutil.ReadDir(G.Pkg.File(ck.patchdir))
        if err != nil {
                if trace.Tracing {
-                       trace.Stepf("Cannot read patchesDir %q: %s", ck.patchdir, err)
+                       trace.Stepf("Cannot read patchdir %q: %s", ck.patchdir, err)
                }
                return
        }
 
-       for _, file := range files {
-               patch := file.Name()
-               if file.Mode().IsRegular() && !ck.patches[patch] && hasPrefix(patch, "patch-") {
-                       NewLineWhole(ck.distinfoFilename).Errorf("patch %q is not recorded. Run \"%s makepatchsum\".", ck.patchdir+"/"+patch, confMake)
+       for _, file := range patchFiles {
+               patchName := file.Name()
+               if file.Mode().IsRegular() && !ck.patches[patchName] && hasPrefix(patchName, "patch-") {
+                       ck.distinfoLines.Errorf("patch %q is not recorded. Run %q.",
+                               // FIXME: Relative paths must not be "../dependency" but the full "../../category/dependency" instead.
+                               cleanpath(relpath(path.Dir(ck.distinfoLines.FileName), G.Pkg.File(ck.patchdir+"/"+patchName))),
+                               bmake("makepatchsum"))
                }
        }
 }
 
 // Inter-package check for differing distfile checksums.
-func (ck *distinfoLinesChecker) checkGlobalMismatch(line Line, fname, alg, hash string) {
+func (ck *distinfoLinesChecker) checkGlobalDistfileMismatch(line Line, fileName, alg, hash string) {
        hashes := G.Pkgsrc.Hashes
-       if hashes != nil && !hasPrefix(fname, "patch-") { // Intentionally checking the filename instead of ck.isPatch
-               key := alg + ":" + fname
+
+       // Intentionally checking the file name instead of ck.isPatch.
+       // Missing the few distfiles that actually start with patch-*
+       // is more convenient than having lots of false positive mismatches.
+       if hashes != nil && !hasPrefix(fileName, "patch-") {
+               key := alg + ":" + fileName
                otherHash := hashes[key]
                if otherHash != nil {
                        if otherHash.hash != hash {
-                               line.Errorf("The hash %s for %s is %s, which differs from %s in %s.",
-                                       alg, fname, hash, otherHash.hash, otherHash.line.ReferenceFrom(line))
+                               line.Errorf("The %s hash for %s is %s, which conflicts with %s in %s.",
+                                       alg, fileName, hash, otherHash.hash, line.RefTo(otherHash.line))
                        }
                } else {
                        hashes[key] = &Hash{hash, line}
@@ -151,27 +174,31 @@ func (ck *distinfoLinesChecker) checkGlo
 
 func (ck *distinfoLinesChecker) checkUncommittedPatch(line Line, patchName, alg, hash string) {
        if ck.isPatch == yes {
-               patchFname := ck.patchdir + "/" + patchName
-               if ck.distinfoIsCommitted && !isCommitted(G.Pkg.File(patchFname)) {
-                       line.Warnf("%s is registered in distinfo but not added to CVS.", patchFname)
+               patchFileName := ck.patchdir + "/" + patchName
+               resolvedPatchFileName := G.Pkg.File(patchFileName)
+               if ck.distinfoIsCommitted && !isCommitted(resolvedPatchFileName) {
+                       line.Warnf("%s is registered in distinfo but not added to CVS.", line.PathToFile(resolvedPatchFileName))
                }
                if alg == "SHA1" {
-                       ck.checkPatchSha1(line, patchFname, hash)
+                       ck.checkPatchSha1(line, patchFileName, hash)
                }
                ck.patches[patchName] = true
        }
 }
 
-func (ck *distinfoLinesChecker) checkPatchSha1(line Line, patchFname, distinfoSha1Hex string) {
-       fileSha1Hex, err := computePatchSha1Hex(G.Pkg.File(patchFname))
+func (ck *distinfoLinesChecker) checkPatchSha1(line Line, patchFileName, distinfoSha1Hex string) {
+       fileSha1Hex, err := computePatchSha1Hex(G.Pkg.File(patchFileName))
        if err != nil {
-               line.Errorf("%s does not exist.", patchFname)
+               line.Errorf("Patch %s does not exist.", patchFileName)
                return
        }
        if distinfoSha1Hex != fileSha1Hex {
                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.Errorf("SHA1 hash of %s differs (distinfo has %s, patch file has %s).",
+                       line.PathToFile(G.Pkg.File(patchFileName)), distinfoSha1Hex, fileSha1Hex)
+               fix.Explain(
+                       "To fix the hashes, either let pkglint --autofix do the work",
+                       sprintf("or run %q.", bmake("makepatchsum")))
                fix.Replace(distinfoSha1Hex, fileSha1Hex)
                fix.Apply()
        }
@@ -184,22 +211,22 @@ func computePatchSha1Hex(patchFilename s
                return "", err
        }
 
-       hash := sha1.New()
-       netbsd := []byte("$" + "NetBSD")
+       hasher := sha1.New()
+       skipText := []byte("$" + "NetBSD")
        for _, patchLine := range bytes.SplitAfter(patchBytes, []byte("\n")) {
-               if !bytes.Contains(patchLine, netbsd) {
-                       hash.Write(patchLine)
+               if !bytes.Contains(patchLine, skipText) {
+                       hasher.Write(patchLine)
                }
        }
-       return fmt.Sprintf("%x", hash.Sum(nil)), nil
+       return sprintf("%x", hasher.Sum(nil)), nil
 }
 
 func AutofixDistinfo(oldSha1, newSha1 string) {
        distinfoFilename := G.Pkg.File(G.Pkg.DistinfoFile)
        if lines := Load(distinfoFilename, NotEmpty|LogErrors); lines != nil {
-               for _, line := range lines {
+               for _, line := range lines.Lines {
                        fix := line.Autofix()
-                       fix.Warnf("Silent-Magic-Diagnostic")
+                       fix.Warnf(SilentAutofixFormat)
                        fix.Replace(oldSha1, newSha1)
                        fix.Apply()
                }
Index: pkgsrc/pkgtools/pkglint/files/patches_test.go
diff -u pkgsrc/pkgtools/pkglint/files/patches_test.go:1.22 pkgsrc/pkgtools/pkglint/files/patches_test.go:1.23
--- pkgsrc/pkgtools/pkglint/files/patches_test.go:1.22  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/patches_test.go       Wed Nov  7 20:58:23 2018
@@ -6,7 +6,6 @@ import "gopkg.in/check.v1"
 func (s *Suite) Test_ChecklinesPatch__with_comment(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.NewLines("patch-WithComment",
                RcsID,
                "",
@@ -54,7 +53,7 @@ func (s *Suite) Test_ChecklinesPatch__wi
        ChecklinesPatch(patchLines)
 
        t.CheckOutputLines(
-               "AUTOFIX: patch-WithoutEmptyLines:2: Inserting a line \"\" before this line.",
+               "AUTOFIX: patch-WithoutEmptyLines:1: Inserting a line \"\" after this line.",
                "AUTOFIX: patch-WithoutEmptyLines:3: Inserting a line \"\" before this line.",
                "AUTOFIX: distinfo:3: Replacing \"49abd735b7e706ea9ed6671628bb54e91f7f5ffb\" "+
                        "with \"4938fc8c0b483dc2e33e741b0da883d199e78164\".")
@@ -88,8 +87,6 @@ func (s *Suite) Test_ChecklinesPatch__no
                "-old line",
                "+new line")
 
-       t.SetupCommandLine("-Wall")
-
        ChecklinesPatch(patchLines)
 
        // These duplicate notes are actually correct. There should be an
@@ -98,7 +95,7 @@ func (s *Suite) Test_ChecklinesPatch__no
        // the same. Outside of the testing environment, this duplicate
        // diagnostic is suppressed; see LogVerbose.
        t.CheckOutputLines(
-               "NOTE: ~/patch-WithoutEmptyLines:2: Empty line expected.",
+               "NOTE: ~/patch-WithoutEmptyLines:1: Empty line expected after this line.",
                "ERROR: ~/patch-WithoutEmptyLines:2: Each patch must be documented.",
                "NOTE: ~/patch-WithoutEmptyLines:2: Empty line expected.")
 }
@@ -106,7 +103,6 @@ func (s *Suite) Test_ChecklinesPatch__no
 func (s *Suite) Test_ChecklinesPatch__without_comment(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.NewLines("patch-WithoutComment",
                RcsID,
                "",
@@ -129,7 +125,6 @@ func (s *Suite) Test_ChecklinesPatch__wi
 func (s *Suite) Test_ChecklinesPatch__git_without_comment(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.NewLines("patch-aa",
                RcsID,
                "",
@@ -160,8 +155,7 @@ func (s *Suite) Test_PatchChecker_checkl
                "@@ -0,0 +1,3 @@",
                "+const char abspath[] = PREFIX \"/bin/program\";",
                "+val abspath = libdir + \"/libiconv.so.1.0\"",
-               "+const char abspath[] = \"/dev/scd0\";",
-       )
+               "+const char abspath[] = \"/dev/scd0\";")
 
        ChecklinesPatch(lines)
 
@@ -198,11 +192,10 @@ func (s *Suite) Test_PatchChecker_checkl
 func (s *Suite) Test_ChecklinesPatch__error_code(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.NewLines("patch-ErrorCode",
                RcsID,
                "",
-               "*** Error code 1", // Looks like a context diff, but isn't.
+               "*** Error code 1", // Looks like a context diff but isn't.
                "",
                "--- file.orig",
                "+++ file",
@@ -220,7 +213,6 @@ func (s *Suite) Test_ChecklinesPatch__er
 func (s *Suite) Test_ChecklinesPatch__wrong_header_order(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.NewLines("patch-WrongOrder",
                RcsID,
                "",
@@ -245,7 +237,6 @@ func (s *Suite) Test_ChecklinesPatch__wr
 func (s *Suite) Test_ChecklinesPatch__context_diff(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.NewLines("patch-ctx",
                RcsID,
                "",
@@ -354,9 +345,13 @@ func (s *Suite) Test_ChecklinesPatch__on
                "WARN: patch-context:5: Please use unified diffs (diff -u) for patches.")
 }
 
+// TODO: Maybe this should only be checked if the patch changes
+// an absolute path to a relative one, because otherwise these
+// absolute paths may be intentional.
 func (s *Suite) Test_ChecklinesPatch__Makefile_with_absolute_pathnames(c *check.C) {
        t := s.Init(c)
 
+       t.SetupCommandLine( /*none*/ )
        lines := t.NewLines("patch-unified",
                RcsID,
                "",
@@ -382,10 +377,7 @@ func (s *Suite) Test_ChecklinesPatch__Ma
 
        // With extra warnings turned on, absolute paths in the context lines
        // are also checked, to detect absolute paths that might be overlooked.
-       // TODO: Maybe this should only be checked if the patch changes
-       // an absolute path to a relative one, because otherwise these
-       // absolute paths may be intentional.
-       G.opts.WarnExtra = true
+       G.Opts.WarnExtra = true
 
        ChecklinesPatch(lines)
 
@@ -539,7 +531,6 @@ func (s *Suite) Test_ChecklinesPatch__cr
 func (s *Suite) Test_ChecklinesPatch__autogenerated(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",
@@ -560,7 +551,6 @@ func (s *Suite) Test_ChecklinesPatch__au
 func (s *Suite) Test_ChecklinesPatch__empty_context_lines_in_hunk(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",
@@ -586,7 +576,6 @@ func (s *Suite) Test_ChecklinesPatch__em
 func (s *Suite) Test_ChecklinesPatch__invalid_line_in_hunk(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",
@@ -614,7 +603,6 @@ func (s *Suite) Test_ChecklinesPatch__in
 func (s *Suite) Test_PatchChecker_checklineAdded__shell(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",
@@ -634,7 +622,6 @@ func (s *Suite) Test_PatchChecker_checkl
 func (s *Suite) Test_PatchChecker_checklineAdded__text(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",
@@ -654,7 +641,6 @@ func (s *Suite) Test_PatchChecker_checkl
 func (s *Suite) Test_PatchChecker_checklineAdded__unknown(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",
@@ -674,7 +660,6 @@ func (s *Suite) Test_PatchChecker_checkl
 func (s *Suite) Test_PatchChecker_checktextRcsid(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("patch-aa",
                RcsID,
                "",

Index: pkgsrc/pkgtools/pkglint/files/distinfo_test.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.18 pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.18 Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/distinfo_test.go      Wed Nov  7 20:58:23 2018
@@ -30,50 +30,75 @@ func (s *Suite) Test_ChecklinesDistinfo(
                "NOTE: distinfo:2: Empty line expected.",
                "ERROR: distinfo:5: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile.tar.gz\", got MD5, SHA1.",
                "ERROR: distinfo:7: Expected SHA1 hash for patch-aa, got SHA1, Size.",
-               "ERROR: distinfo:8: Invalid line.",
+               "ERROR: distinfo:8: Invalid line: Another invalid line",
                "WARN: distinfo:9: Patch file \"patch-nonexistent\" does not exist in directory \"patches\".")
 }
 
-func (s *Suite) Test_ChecklinesDistinfo__global_hash_mismatch(c *check.C) {
+// When checking the complete pkgsrc tree, pkglint has all information it needs
+// to check whether different packages use the same distfile but require
+// different hashes for it.
+//
+// In such a case, typically one of the packages should put its distfiles into
+// a DIST_SUBDIR.
+func (s *Suite) Test_distinfoLinesChecker_checkGlobalDistfileMismatch(c *check.C) {
        t := s.Init(c)
 
-       otherLine := t.NewLine("other/distinfo", 7, "dummy")
-       G.Pkgsrc.Hashes = map[string]*Hash{"SHA512:pkgname-1.0.tar.gz": {"Some-512-bit-hash", otherLine}}
-       lines := t.NewLines("distinfo",
+       t.SetupPkgsrc()
+       t.SetupPackage("category/package1")
+       t.SetupPackage("category/package2")
+       t.CreateFileLines("category/package1/distinfo",
+               RcsID,
+               "",
+               "SHA512 (distfile-1.0.tar.gz) = 1234567811111111",
+               "SHA512 (distfile-1.1.tar.gz) = 1111111111111111")
+       t.CreateFileLines("category/package2/distinfo",
                RcsID,
                "",
-               "SHA512 (pkgname-1.0.tar.gz) = 12341234",
-               "SHA512 (pkgname-1.1.tar.gz) = 12341234")
+               "SHA512 (distfile-1.0.tar.gz) = 1234567822222222",
+               "SHA512 (distfile-1.1.tar.gz) = 1111111111111111")
+       t.CreateFileLines("Makefile",
+               MkRcsID,
+               "",
+               "COMMENT=\tThis is pkgsrc",
+               "",
+               "SUBDIR+=\tcategory")
+       t.CreateFileLines("category/Makefile",
+               MkRcsID,
+               "",
+               "COMMENT=\tUseful programs",
+               "",
+               "SUBDIR+=\tpackage1",
+               "SUBDIR+=\tpackage2",
+               "",
+               ".include \"../mk/misc/category.mk\"")
 
-       ChecklinesDistinfo(lines)
+       G.Main("pkglint", "-r", "-Wall", "-Call", t.File("."))
 
        t.CheckOutputLines(
-               "ERROR: distinfo:3: The hash SHA512 for pkgname-1.0.tar.gz is 12341234, which differs from Some-512-bit-hash in other/distinfo:7.",
-               "ERROR: distinfo:4: Expected SHA1, RMD160, SHA512, Size checksums for \"pkgname-1.0.tar.gz\", got SHA512.",
-               "ERROR: distinfo:EOF: Expected SHA1, RMD160, SHA512, Size checksums for \"pkgname-1.1.tar.gz\", got SHA512.")
+               "ERROR: ~/category/package1/distinfo:4: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile-1.0.tar.gz\", got SHA512.",
+               "ERROR: ~/category/package1/distinfo:EOF: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile-1.1.tar.gz\", got SHA512.",
+               "ERROR: ~/category/package2/distinfo:3: The SHA512 hash for distfile-1.0.tar.gz is 1234567822222222, "+
+                       "which conflicts with 1234567811111111 in ../package1/distinfo:3.",
+               "ERROR: ~/category/package2/distinfo:4: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile-1.0.tar.gz\", got SHA512.",
+               "ERROR: ~/category/package2/distinfo:EOF: Expected SHA1, RMD160, SHA512, Size checksums for \"distfile-1.1.tar.gz\", got SHA512.",
+               "WARN: ~/licenses/gnu-gpl-v2: This license seems to be unused.",
+               "5 errors and 1 warning found.")
 }
 
 func (s *Suite) Test_ChecklinesDistinfo__uncommitted_patch(c *check.C) {
        t := s.Init(c)
 
+       t.SetupPackage("category/package")
        t.Chdir("category/package")
-       t.CreateFileLines("patches/patch-aa",
-               RcsID,
-               "",
-               "--- oldfile",
-               "+++ newfile",
-               "@@ -1,1 +1,1 @@",
-               "-old",
-               "+new")
+       t.CreateFileDummyPatch("patches/patch-aa")
        t.CreateFileLines("CVS/Entries",
                "/distinfo/...")
-       lines := t.SetupFileLines("distinfo",
+       t.SetupFileLines("distinfo",
                RcsID,
                "",
-               "SHA1 (patch-aa) = 5ad1fb9b3c328fff5caa1a23e8f330e707dd50c0")
-       G.Pkg = NewPackage(".")
+               "SHA1 (patch-aa) = ebbf34b0641bcb508f17d5a27f2bf2a536d810ac")
 
-       ChecklinesDistinfo(lines)
+       G.checkdirPackage(".")
 
        t.CheckOutputLines(
                "WARN: distinfo:3: patches/patch-aa is registered in distinfo but not added to CVS.")
@@ -82,26 +107,87 @@ func (s *Suite) Test_ChecklinesDistinfo_
 func (s *Suite) Test_ChecklinesDistinfo__unrecorded_patches(c *check.C) {
        t := s.Init(c)
 
+       t.SetupPackage("category/package")
        t.Chdir("category/package")
        t.CreateFileLines("patches/CVS/Entries")
-       t.CreateFileLines("patches/patch-aa")
-       t.CreateFileLines("patches/patch-src-Makefile")
-       lines := t.SetupFileLines("distinfo",
+       t.CreateFileDummyPatch("patches/patch-aa")
+       t.CreateFileDummyPatch("patches/patch-src-Makefile")
+       t.SetupFileLines("distinfo",
                RcsID,
                "",
                "SHA1 (distfile.tar.gz) = ...",
                "RMD160 (distfile.tar.gz) = ...",
                "SHA512 (distfile.tar.gz) = ...",
                "Size (distfile.tar.gz) = 1024 bytes")
-       G.Pkg = NewPackage(".")
 
-       ChecklinesDistinfo(lines)
+       G.checkdirPackage(".")
 
        t.CheckOutputLines(
                "ERROR: distinfo: patch \"patches/patch-aa\" is not recorded. Run \""+confMake+" makepatchsum\".",
                "ERROR: distinfo: patch \"patches/patch-src-Makefile\" is not recorded. Run \""+confMake+" makepatchsum\".")
 }
 
+// The distinfo file and the patches are usually placed in the package
+// directory. By defining PATCHDIR or DISTINFO_FILE, a package can define
+// that they are somewhere else in pkgsrc.
+func (s *Suite) Test_ChecklinesDistinfo__relative_path_in_distinfo(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPackage("category/package",
+               "DISTINFO_FILE=\t../../other/common/distinfo",
+               "PATCHDIR=\t../../devel/patches/patches")
+       t.Remove("category/package/distinfo")
+       t.CreateFileLines("devel/patches/patches/CVS/Entries")
+       t.CreateFileDummyPatch("devel/patches/patches/patch-aa")
+       t.CreateFileDummyPatch("devel/patches/patches/patch-only-in-patches")
+       t.SetupFileLines("other/common/distinfo",
+               RcsID,
+               "",
+               "SHA1 (patch-aa) = ...",
+               "SHA1 (patch-only-in-distinfo) = ...")
+       t.Chdir("category/package")
+
+       G.checkdirPackage(".")
+
+       t.CheckOutputLines(
+               "ERROR: ../../other/common/distinfo:3: SHA1 hash of ../../devel/patches/patches/patch-aa differs "+
+                       "(distinfo has ..., patch file has ebbf34b0641bcb508f17d5a27f2bf2a536d810ac).",
+               "WARN: ../../other/common/distinfo:4: Patch file \"patch-only-in-distinfo\" "+
+                       "does not exist in directory \"../../devel/patches/patches\".",
+               "ERROR: ../../other/common/distinfo: patch \"../../devel/patches/patches/patch-only-in-patches\" "+
+                       "is not recorded. Run \""+confMake+" makepatchsum\".")
+}
+
+// When the distinfo file and the patches are placed in the same package,
+// their diagnostics use short relative paths.
+func (s *Suite) Test_ChecklinesDistinfo__distinfo_and_patches_in_separate_directory(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPackage("category/package",
+               "DISTINFO_FILE=\t../../other/common/distinfo",
+               "PATCHDIR=\t../../other/common/patches")
+       t.Remove("category/package/distinfo")
+       t.CreateFileLines("other/common/patches/CVS/Entries")
+       t.CreateFileDummyPatch("other/common/patches/patch-aa")
+       t.CreateFileDummyPatch("other/common/patches/patch-only-in-patches")
+       t.SetupFileLines("other/common/distinfo",
+               RcsID,
+               "",
+               "SHA1 (patch-aa) = ...",
+               "SHA1 (patch-only-in-distinfo) = ...")
+       t.Chdir("category/package")
+
+       G.checkdirPackage(".")
+
+       t.CheckOutputLines(
+               "ERROR: ../../other/common/distinfo:3: SHA1 hash of patches/patch-aa differs "+
+                       "(distinfo has ..., patch file has ebbf34b0641bcb508f17d5a27f2bf2a536d810ac).",
+               "WARN: ../../other/common/distinfo:4: Patch file \"patch-only-in-distinfo\" "+
+                       "does not exist in directory \"patches\".",
+               "ERROR: ../../other/common/distinfo: patch \"patches/patch-only-in-patches\" "+
+                       "is not recorded. Run \""+confMake+" makepatchsum\".")
+}
+
 func (s *Suite) Test_ChecklinesDistinfo__manual_patches(c *check.C) {
        t := s.Init(c)
 
@@ -123,7 +209,7 @@ func (s *Suite) Test_ChecklinesDistinfo_
        ChecklinesDistinfo(lines)
 
        // When a distinfo file is checked in the context of a package,
-       // the PATCHDIR is known, therefore the checks are active.
+       // the PATCHDIR is known, therefore the check is active.
        t.CheckOutputLines(
                "WARN: distinfo:3: Patch file \"patch-aa\" does not exist in directory \"patches\".")
 }
@@ -132,6 +218,8 @@ func (s *Suite) Test_ChecklinesDistinfo_
 // their own patches directory. Therefore the distinfo file refers to missing
 // patches. Since this strange situation is caused by the pkgsrc
 // infrastructure, there is nothing a package author can do about.
+//
+// XXX: Re-check the documentation for this test.
 func (s *Suite) Test_ChecklinesDistinfo__missing_php_patches(c *check.C) {
        t := s.Init(c)
 
@@ -143,7 +231,7 @@ func (s *Suite) Test_ChecklinesDistinfo_
                "",
                "PHPEXT_MK=      # defined",
                "PHPPKGSRCDIR=   ../../lang/php72",
-               "LICENSE?=        unknown-license",
+               "LICENSE?=       unknown-license",
                "COMMENT?=       Some PHP package",
                "GENERATE_PLIST+=# none",
                "",
@@ -153,20 +241,11 @@ func (s *Suite) Test_ChecklinesDistinfo_
                ".if defined(USE_PHP_EXT_PATCHES)",
                "PATCHDIR=       ${.CURDIR}/${PHPPKGSRCDIR}/patches",
                ".endif")
-       t.CreateFileLines("lang/php72/patches/patch-php72",
-               RcsID,
-               "",
-               "Documentation",
-               "",
-               "--- old file",
-               "+++ new file",
-               "@@ -1,1 +1,1 @@",
-               "-old",
-               "+new")
+       t.CreateFileDummyPatch("lang/php72/patches/patch-php72")
        t.CreateFileLines("lang/php72/distinfo",
                RcsID,
                "",
-               "SHA1 (patch-php72) = c109b2089f5ddbc5372b2ab28115ff558ee4187d")
+               "SHA1 (patch-php72) = ebbf34b0641bcb508f17d5a27f2bf2a536d810ac")
 
        t.CreateFileLines("archivers/php-bz2/Makefile",
                MkRcsID,
@@ -175,13 +254,15 @@ func (s *Suite) Test_ChecklinesDistinfo_
                "",
                ".include \"../../lang/php/ext.mk\"",
                ".include \"../../mk/bsd.pkg.mk\"")
+
+       G.CheckDirent(t.File("archivers/php-bz2"))
+
        t.CreateFileLines("archivers/php-zlib/Makefile",
                MkRcsID,
                "",
                ".include \"../../lang/php/ext.mk\"",
                ".include \"../../mk/bsd.pkg.mk\"")
 
-       G.CheckDirent(t.File("archivers/php-bz2"))
        G.CheckDirent(t.File("archivers/php-zlib"))
 
        t.CheckOutputEmpty()
@@ -191,10 +272,11 @@ func (s *Suite) Test_distinfoLinesChecke
        t := s.Init(c)
 
        G.Pkg = NewPackage(t.File("category/package"))
+       distinfoLine := t.NewLine(t.File("category/package/distinfo"), 5, "")
 
        checker := &distinfoLinesChecker{}
-       checker.checkPatchSha1(dummyLine, "patch-nonexistent", "distinfo-sha1")
+       checker.checkPatchSha1(distinfoLine, "patch-nonexistent", "distinfo-sha1")
 
        t.CheckOutputLines(
-               "ERROR: patch-nonexistent does not exist.")
+               "ERROR: ~/category/package/distinfo:5: Patch patch-nonexistent does not exist.")
 }
Index: pkgsrc/pkgtools/pkglint/files/files_test.go
diff -u pkgsrc/pkgtools/pkglint/files/files_test.go:1.18 pkgsrc/pkgtools/pkglint/files/files_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/files_test.go:1.18    Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/files_test.go Wed Nov  7 20:58:23 2018
@@ -9,24 +9,37 @@ func (s *Suite) Test_convertToLogicalLin
                "first line\n" +
                "second line\n"
 
-       lines := convertToLogicalLines("fname_nocont", rawText, false)
+       lines := convertToLogicalLines("fileName", rawText, false)
 
-       c.Check(lines, check.HasLen, 2)
-       c.Check(lines[0].String(), equals, "fname_nocont:1: first line")
-       c.Check(lines[1].String(), equals, "fname_nocont:2: second line")
+       c.Check(lines.Len(), equals, 2)
+       c.Check(lines.Lines[0].String(), equals, "fileName:1: first line")
+       c.Check(lines.Lines[1].String(), equals, "fileName:2: second line")
 }
 
 func (s *Suite) Test_convertToLogicalLines__continuation(c *check.C) {
        rawText := "" +
-               "first line \\\n" +
-               "second line\n" +
-               "third\n"
-
-       lines := convertToLogicalLines("fname_cont", rawText, true)
-
-       c.Check(lines, check.HasLen, 2)
-       c.Check(lines[0].String(), equals, "fname_cont:1--2: first line second line")
-       c.Check(lines[1].String(), equals, "fname_cont:3: third")
+               "first line, \\\n" +
+               "still first line\n" +
+               "second line\n"
+
+       lines := convertToLogicalLines("fileName", rawText, true)
+
+       c.Check(lines.Len(), equals, 2)
+       c.Check(lines.Lines[0].String(), equals, "fileName:1--2: first line, still first line")
+       c.Check(lines.Lines[1].String(), equals, "fileName:3: second line")
+}
+
+func (s *Suite) Test_convertToLogicalLines__continuation_in_last_line(c *check.C) {
+       t := s.Init(c)
+
+       rawText := "" +
+               "last line\\\n"
+
+       lines := convertToLogicalLines("fileName", rawText, true)
+
+       c.Check(lines.Len(), equals, 1)
+       c.Check(lines.Lines[0].String(), equals, "fileName:1: last line\\")
+       t.CheckOutputEmpty()
 }
 
 // In Makefiles, comment lines can also have continuations.
@@ -42,25 +55,25 @@ func (s *Suite) Test_convertToLogicalLin
                "# Another escaped comment \\",
                "that \\",
                "goes \\",
-               "on",
+               "on and on",
                "# This is NOT an escaped comment due to the double backslashes \\\\",
                "VAR=\tThis is not a comment",
                "",
                "#\\",
-               "This is a comment",
+               "\tThis is a comment",
                "#\\\\",
-               "This is no comment",
+               "\tThis is no comment",
                "#\\\\\\",
-               "This is a comment",
+               "\tThis is a comment",
                "#\\\\\\\\",
-               "This is no comment",
+               "\tThis is no comment",
                "#\\\\\\\\\\",
-               "This is a comment",
+               "\tThis is a comment",
                "#\\\\\\\\\\\\",
-               "This is no comment")
+               "\tThis is no comment")
 
        var texts []string
-       for _, line := range mklines.lines {
+       for _, line := range mklines.lines.Lines {
                texts = append(texts, line.Text)
        }
 
@@ -68,82 +81,61 @@ func (s *Suite) Test_convertToLogicalLin
                "# This is a comment",
                "",
                "# Multiline comment",
-               "# Another escaped comment that goes on",
+               "# Another escaped comment that goes on and on",
                "# This is NOT an escaped comment due to the double backslashes \\",
                "VAR=\tThis is not a comment",
                "",
                "# This is a comment",
                "#\\",
-               "This is no comment",
+               "\tThis is no comment",
                "#\\ This is a comment",
                "#\\\\",
-               "This is no comment",
+               "\tThis is no comment",
                "#\\\\ This is a comment",
                "#\\\\\\",
-               "This is no comment"})
+               "\tThis is no comment"})
 
-       var rawTexts []string
-       for _, line := range mklines.lines {
-               for _, rawLine := range line.raw {
-                       rawTexts = append(rawTexts, rawLine.textnl)
-               }
-       }
+       t.CheckOutputEmpty()
+}
 
-       c.Check(rawTexts, deepEquals, []string{
-               "# This is a comment\n",
-               "\n",
-               "#\\\n",
-               "\tMultiline comment\n",
-               "# Another escaped comment \\\n",
-               "that \\\n",
-               "goes \\\n",
-               "on\n",
-               "# This is NOT an escaped comment due to the double backslashes \\\\\n",
-               "VAR=\tThis is not a comment\n",
-               "\n",
-               "#\\\n",
-               "This is a comment\n",
-               "#\\\\\n",
-               "This is no comment\n",
-               "#\\\\\\\n",
-               "This is a comment\n",
-               "#\\\\\\\\\n",
-               "This is no comment\n",
-               "#\\\\\\\\\\\n",
-               "This is a comment\n",
-               "#\\\\\\\\\\\\\n",
-               "This is no comment\n"})
+func (s *Suite) Test_convertToLogicalLines__missing_newline_at_eof(c *check.C) {
+       t := s.Init(c)
 
-       // This is just a side-effect and not relevant for this particular test.
+       rawText := "" +
+               "The package description\n" +
+               "takes 2 lines"
+
+       lines := convertToLogicalLines("DESCR", rawText, true)
+
+       c.Check(lines.Len(), equals, 2)
+       c.Check(lines.Lines[1].String(), equals, "DESCR:2: takes 2 lines")
        t.CheckOutputLines(
-               "ERROR: ~/comment.mk:15: Unknown Makefile line format: \"This is no comment\".",
-               "ERROR: ~/comment.mk:19: Unknown Makefile line format: \"This is no comment\".",
-               "ERROR: ~/comment.mk:23: Unknown Makefile line format: \"This is no comment\".")
+               "ERROR: DESCR:2: File must end with a newline.")
 }
 
-func (s *Suite) Test_convertToLogicalLines__continuation_in_last_line(c *check.C) {
+func (s *Suite) Test_convertToLogicalLines__missing_newline_at_eof_in_continuation_line(c *check.C) {
        t := s.Init(c)
 
        rawText := "" +
                "last line\\"
 
-       lines := convertToLogicalLines("fname_contlast", rawText, true)
+       lines := convertToLogicalLines("fileName", rawText, true)
 
-       c.Check(lines, check.HasLen, 1)
-       c.Check(lines[0].String(), equals, "fname_contlast:1: last line\\")
+       c.Check(lines.Len(), equals, 1)
+       c.Check(lines.Lines[0].String(), equals, "fileName:1: last line\\")
        t.CheckOutputLines(
-               "ERROR: fname_contlast:EOF: File must end with a newline.")
+               "ERROR: fileName:1: File must end with a newline.")
 }
 
-func (s *Suite) Test_splitRawLine(c *check.C) {
-       leadingWhitespace, text, trailingWhitespace, continuation := splitRawLine("\n")
+func (s *Suite) Test_matchContinuationLine(c *check.C) {
+       leadingWhitespace, text, trailingWhitespace, continuation := matchContinuationLine("\n")
 
        c.Check(leadingWhitespace, equals, "")
        c.Check(text, equals, "")
        c.Check(trailingWhitespace, equals, "")
        c.Check(continuation, equals, "")
 
-       leadingWhitespace, text, trailingWhitespace, continuation = splitRawLine("\tword   \\\n")
+       leadingWhitespace, text, trailingWhitespace, continuation = matchContinuationLine("\tword   \\\n")
 
        c.Check(leadingWhitespace, equals, "\t")
        c.Check(text, equals, "word")
@@ -151,7 +143,7 @@ func (s *Suite) Test_splitRawLine(c *che
        c.Check(continuation, equals, "\\")
 }
 
-func (s *Suite) Test_Load(c *check.C) {
+func (s *Suite) Test_Load__errors(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("empty")
Index: pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.18 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.19
--- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.18      Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser.go   Wed Nov  7 20:58:23 2018
@@ -2,7 +2,6 @@ package main
 
 import (
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "strings"
 )
 
@@ -12,6 +11,8 @@ type MkParser struct {
        *Parser
 }
 
+// NewMkParser creates a new parser for the given text.
+// If emitWarnings is false, line may be nil.
 func NewMkParser(line Line, text string, emitWarnings bool) *MkParser {
        return &MkParser{NewParser(line, text, emitWarnings)}
 }
@@ -105,10 +106,11 @@ func (p *MkParser) VarUse() *MkVarUse {
        return nil
 }
 
-func (p *MkParser) VarUseModifiers(varname, closing string) []string {
+func (p *MkParser) VarUseModifiers(varname, closing string) []MkVarUseModifier {
        repl := p.repl
 
-       var modifiers []string
+       var modifiers []MkVarUseModifier
+       appendModifier := func(s string) { modifiers = append(modifiers, MkVarUseModifier{s}) }
        mayOmitColon := false
 loop:
        for repl.AdvanceStr(":") || mayOmitColon {
@@ -118,7 +120,7 @@ loop:
                switch repl.PeekByte() {
                case 'E', 'H', 'L', 'O', 'Q', 'R', 'T', 's', 't', 'u':
                        if repl.AdvanceRegexp(`^(E|H|L|Ox?|Q|R|T|sh|tA|tW|tl|tu|tw|u)`) {
-                               modifiers = append(modifiers, repl.Since(modifierMark))
+                               appendModifier(repl.Since(modifierMark))
                                continue
                        }
                        if repl.AdvanceStr("ts") {
@@ -130,7 +132,7 @@ loop:
                                } else {
                                        break loop
                                }
-                               modifiers = append(modifiers, repl.Since(modifierMark))
+                               appendModifier(repl.Since(modifierMark))
                                continue
                        }
 
@@ -139,7 +141,7 @@ loop:
                                for p.VarUse() != nil || repl.AdvanceRegexp(regex.Pattern(`^([^$:\\`+closing+`]|\$\$|\\.)+`)) {
                                }
                                arg := repl.Since(modifierMark)
-                               modifiers = append(modifiers, strings.Replace(arg, "\\:", ":", -1))
+                               appendModifier(strings.Replace(arg, "\\:", ":", -1))
                                continue
                        }
 
@@ -156,7 +158,7 @@ loop:
                                        }
                                        if repl.AdvanceStr(separator) {
                                                repl.AdvanceRegexp(`^[1gW]`)
-                                               modifiers = append(modifiers, repl.Since(modifierMark))
+                                               appendModifier(repl.Since(modifierMark))
                                                mayOmitColon = true
                                                continue
                                        }
@@ -171,13 +173,13 @@ loop:
                                if !repl.AdvanceStr("@") && p.EmitWarnings {
                                        p.Line.Warnf("Modifier ${%s:@%s@...@} is missing the final \"@\".", varname, loopvar)
                                }
-                               modifiers = append(modifiers, repl.Since(modifierMark))
+                               appendModifier(repl.Since(modifierMark))
                                continue
                        }
 
                case '[':
                        if repl.AdvanceRegexp(`^\[(?:[-.\d]+|#)\]`) {
-                               modifiers = append(modifiers, repl.Since(modifierMark))
+                               appendModifier(repl.Since(modifierMark))
                                continue
                        }
 
@@ -189,7 +191,7 @@ loop:
                        if repl.AdvanceStr(":") {
                                for p.VarUse() != nil || repl.AdvanceRegexp(re) {
                                }
-                               modifiers = append(modifiers, repl.Since(modifierMark))
+                               appendModifier(repl.Since(modifierMark))
                                continue
                        }
                }
@@ -199,7 +201,7 @@ loop:
                for p.VarUse() != nil || repl.AdvanceRegexp(regex.Pattern(`^([^:$`+closing+`]|\$\$)+`)) {
                }
                if suffixSubst := repl.Since(modifierMark); contains(suffixSubst, "=") {
-                       modifiers = append(modifiers, suffixSubst)
+                       appendModifier(suffixSubst)
                        continue
                }
        }
@@ -243,7 +245,7 @@ func (p *MkParser) mkCondAnd() MkCond {
        atoms := []MkCond{atom}
        for {
                mark := p.repl.Mark()
-               if !p.repl.AdvanceRegexp(`^\s*&&\s*`) {
+               if !p.repl.AdvanceRegexp(`^[\t ]*&&[\t ]*`) {
                        break
                }
                next := p.mkCondAtom()
@@ -281,23 +283,23 @@ func (p *MkParser) mkCondAtom() MkCond {
                                return cond
                        }
                }
-       case repl.HasPrefix("defined") && repl.AdvanceRegexp(`^defined\s*\(`):
+       case repl.HasPrefix("defined") && repl.AdvanceRegexp(`^defined[\t ]*\(`):
                if varname := p.Varname(); varname != "" {
                        if repl.AdvanceStr(")") {
                                return &mkCond{Defined: varname}
                        }
                }
-       case repl.HasPrefix("empty") && repl.AdvanceRegexp(`^empty\s*\(`):
+       case repl.HasPrefix("empty") && repl.AdvanceRegexp(`^empty[\t ]*\(`):
                if varname := p.Varname(); varname != "" {
                        modifiers := p.VarUseModifiers(varname, ")")
                        if repl.AdvanceStr(")") {
                                return &mkCond{Empty: &MkVarUse{varname, modifiers}}
                        }
                }
-       case uint(repl.PeekByte()-'a') <= 'z'-'a' && repl.AdvanceRegexp(`^(commands|exists|make|target)\s*\(`):
+       case uint(repl.PeekByte()-'a') <= 'z'-'a' && repl.AdvanceRegexp(`^(commands|exists|make|target)[\t ]*\(`):
                funcname := repl.Group(1)
                argMark := repl.Mark()
-               for p.VarUse() != nil || repl.AdvanceRegexp(`^[^$)]+`) {
+               for p.VarUse() != nil || repl.NextBytesFunc(func(b byte) bool { return b != '$' && b != ')' }) != "" {
                }
                arg := repl.Since(argMark)
                if repl.AdvanceStr(")") {
@@ -314,15 +316,15 @@ func (p *MkParser) mkCondAtom() MkCond {
                        }
                }
                if lhs != nil {
-                       if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*(\d+(?:\.\d+)?)`) {
+                       if repl.AdvanceRegexp(`^[\t ]*(<|<=|==|!=|>=|>)[\t ]*(\d+(?:\.\d+)?)`) {
                                return &mkCond{CompareVarNum: &MkCondCompareVarNum{lhs, repl.Group(1), repl.Group(2)}}
                        }
-                       if repl.AdvanceRegexp(`^\s*(<|<=|==|!=|>=|>)\s*`) {
+                       if repl.AdvanceRegexp(`^[\t ]*(<|<=|==|!=|>=|>)[\t ]*`) {
                                op := repl.Group(1)
                                if (op == "!=" || op == "==") && repl.AdvanceRegexp(`^"([^"\$\\]*)"`) {
                                        return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Group(1)}}
                                } else if repl.AdvanceRegexp(`^\w+`) {
-                                       return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Group(0)}}
+                                       return &mkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, repl.Str()}}
                                } else if rhs := p.VarUse(); rhs != nil {
                                        return &mkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, rhs}}
                                } else if repl.PeekByte() == '"' {
@@ -341,7 +343,7 @@ func (p *MkParser) mkCondAtom() MkCond {
                        }
                }
                if repl.AdvanceRegexp(`^\d+(?:\.\d+)?`) {
-                       return &mkCond{Num: repl.Group(0)}
+                       return &mkCond{Num: repl.Str()}
                }
        }
        repl.Reset(mark)
@@ -356,7 +358,7 @@ func (p *MkParser) Varname() string {
        isVarnameChar := func(c byte) bool {
                return 'A' <= c && c <= 'Z' || c == '_' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.' || c == '*'
        }
-       for p.VarUse() != nil || repl.AdvanceBytesFunc(isVarnameChar) {
+       for p.VarUse() != nil || repl.NextBytesFunc(isVarnameChar) != "" {
        }
        return repl.Since(mark)
 }

Index: pkgsrc/pkgtools/pkglint/files/files.go
diff -u pkgsrc/pkgtools/pkglint/files/files.go:1.20 pkgsrc/pkgtools/pkglint/files/files.go:1.21
--- pkgsrc/pkgtools/pkglint/files/files.go:1.20 Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/files.go      Wed Nov  7 20:58:23 2018
@@ -15,9 +15,8 @@ const (
        LogErrors                           //
 )
 
-func Load(fileName string, options LoadOptions) []Line {
-       fromCache := G.fileCache.Get(fileName, options)
-       if fromCache != nil {
+func Load(fileName string, options LoadOptions) Lines {
+       if fromCache := G.fileCache.Get(fileName, options); fromCache != nil {
                return fromCache
        }
 
@@ -43,7 +42,7 @@ func Load(fileName string, options LoadO
                return nil
        }
 
-       if G.opts.Profiling {
+       if G.Opts.Profiling {
                G.loaded.Add(path.Clean(fileName), 1)
        }
 
@@ -54,7 +53,7 @@ func Load(fileName string, options LoadO
        return result
 }
 
-func LoadMk(fileName string, options LoadOptions) *MkLines {
+func LoadMk(fileName string, options LoadOptions) MkLines {
        lines := Load(fileName, options|Makefile)
        if lines == nil {
                return nil
@@ -62,72 +61,69 @@ func LoadMk(fileName string, options Loa
        return NewMkLines(lines)
 }
 
-func nextLogicalLine(fname string, rawLines []*RawLine, pindex *int) Line {
+func nextLogicalLine(fileName string, rawLines []*RawLine, index int) (Line, int) {
        { // Handle the common case efficiently
-               index := *pindex
                rawLine := rawLines[index]
                textnl := rawLine.textnl
                if hasSuffix(textnl, "\n") && !hasSuffix(textnl, "\\\n") {
-                       *pindex = index + 1
-                       return NewLine(fname, rawLine.Lineno, textnl[:len(textnl)-1], rawLines[index:index+1])
+                       return NewLine(fileName, rawLine.Lineno, textnl[:len(textnl)-1], rawLines[index:index+1]), index + 1
                }
        }
 
-       text := ""
-       index := *pindex
+       var text strings.Builder
        firstlineno := rawLines[index].Lineno
        var lineRawLines []*RawLine
        interestingRawLines := rawLines[index:]
 
        for i, rawLine := range interestingRawLines {
-               indent, rawText, outdent, cont := splitRawLine(rawLine.textnl)
+               indent, rawText, outdent, cont := matchContinuationLine(rawLine.textnl)
 
-               if text == "" {
-                       text += indent
+               if text.Len() == 0 {
+                       text.WriteString(indent)
                }
-               text += rawText
+               text.WriteString(rawText)
                lineRawLines = append(lineRawLines, rawLine)
 
                if cont != "" && i != len(interestingRawLines)-1 {
-                       text += " "
+                       text.WriteString(" ")
                        index++
                } else {
-                       text += outdent + cont
+                       text.WriteString(outdent)
+                       text.WriteString(cont)
                        break
                }
        }
 
        lastlineno := rawLines[index].Lineno
-       *pindex = index + 1
 
-       return NewLineMulti(fname, firstlineno, lastlineno, text, lineRawLines)
+       return NewLineMulti(fileName, firstlineno, lastlineno, text.String(), lineRawLines), index + 1
 }
 
-func splitRawLine(textnl string) (leadingWhitespace, text, trailingWhitespace, cont string) {
-       end := len(textnl)
+func matchContinuationLine(textnl string) (leadingWhitespace, text, trailingWhitespace, cont string) {
+       j := len(textnl)
 
-       if end-1 >= 0 && textnl[end-1] == '\n' {
-               end--
+       if j > 0 && textnl[j-1] == '\n' {
+               j--
        }
 
        backslashes := 0
-       for end-1 >= 0 && textnl[end-1] == '\\' {
-               end--
+       for j > 0 && textnl[j-1] == '\\' {
+               j--
                backslashes++
        }
-       cont = textnl[end : end+backslashes%2]
-       end += backslashes / 2
+       cont = textnl[j : j+backslashes%2]
+       j += backslashes / 2
 
-       trailingEnd := end
-       for end-1 >= 0 && (textnl[end-1] == ' ' || textnl[end-1] == '\t') {
-               end--
+       trailingEnd := j
+       for j > 0 && isHspace(textnl[j-1]) {
+               j--
        }
-       trailingStart := end
+       trailingStart := j
        trailingWhitespace = textnl[trailingStart:trailingEnd]
 
        i := 0
        leadingStart := i
-       for i < end && (textnl[i] == ' ' || textnl[i] == '\t') {
+       for i < j && isHspace(textnl[i]) {
                i++
        }
        leadingEnd := i
@@ -137,7 +133,7 @@ func splitRawLine(textnl string) (leadin
        return
 }
 
-func convertToLogicalLines(fname string, rawText string, joinBackslashLines bool) []Line {
+func convertToLogicalLines(fileName string, rawText string, joinBackslashLines bool) Lines {
        var rawLines []*RawLine
        for lineno, rawLine := range strings.SplitAfter(rawText, "\n") {
                if rawLine != "" {
@@ -148,19 +144,21 @@ func convertToLogicalLines(fname string,
        var loglines []Line
        if joinBackslashLines {
                for lineno := 0; lineno < len(rawLines); {
-                       loglines = append(loglines, nextLogicalLine(fname, rawLines, &lineno))
+                       line, nextLineno := nextLogicalLine(fileName, rawLines, lineno)
+                       loglines = append(loglines, line)
+                       lineno = nextLineno
                }
        } else {
                for _, rawLine := range rawLines {
                        text := strings.TrimSuffix(rawLine.textnl, "\n")
-                       logline := NewLine(fname, rawLine.Lineno, text, []*RawLine{rawLine})
+                       logline := NewLine(fileName, rawLine.Lineno, text, []*RawLine{rawLine})
                        loglines = append(loglines, logline)
                }
        }
 
-       if 0 < len(rawLines) && !hasSuffix(rawLines[len(rawLines)-1].textnl, "\n") {
-               NewLineEOF(fname).Errorf("File must end with a newline.")
+       if rawText != "" && !hasSuffix(rawText, "\n") {
+               loglines[len(loglines)-1].Errorf("File must end with a newline.")
        }
 
-       return loglines
+       return NewLines(fileName, loglines)
 }

Index: pkgsrc/pkgtools/pkglint/files/fuzzer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/fuzzer_test.go:1.1 pkgsrc/pkgtools/pkglint/files/fuzzer_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/fuzzer_test.go:1.1    Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/fuzzer_test.go        Wed Nov  7 20:58:23 2018
@@ -5,6 +5,8 @@ import (
        "math/rand"
 )
 
+// Fuzzer generates random strings.
+// The structure of the strings is configurable.
 type Fuzzer struct {
        seed  int64
        rnd   *rand.Rand
@@ -12,14 +14,17 @@ type Fuzzer struct {
                r      rune
                weight int
        }
-       total int
-       last  string
-       ok    bool
+       total int    // The sum of all stock weights
+       last  string //
+       ok    bool   // Whether the last string was processed correctly
 }
 
+// NewFuzzer returns a fuzzer.
+// If no seed is passed, a random seed is chosen.
+// To reproduce a previous run, pass the seed from that run as the parameter.
 func NewFuzzer(seed ...int64) *Fuzzer {
        var actualSeed int64
-       if len(seed) != 0 {
+       if len(seed) > 0 {
                actualSeed = seed[0]
        } else {
                actualSeed = rand.Int63()
@@ -52,12 +57,12 @@ func (f *Fuzzer) addChar(r rune, weight 
 }
 
 func (f *Fuzzer) Generate(length int) string {
-       s := ""
+       rs := make([]rune, length, length)
        for i := 0; i < length; i++ {
-               s += string(f.randomChar())
+               rs[i] = f.randomChar()
        }
-       f.last = s
-       return s
+       f.last = string(rs)
+       return f.last
 }
 
 func (f *Fuzzer) randomChar() rune {
@@ -71,17 +76,20 @@ func (f *Fuzzer) randomChar() rune {
        panic("Out of stock")
 }
 
+// CheckOk is typically used in a defer statement and is run after all
+// the tests to check whether they have been marked as ok.
 func (f *Fuzzer) CheckOk() {
        if !f.ok {
-               dummyLine.Errorf("Fuzzing failed with seed %d, last generated value: %s", f.seed, f.last)
+               panic(sprintf("Fuzzing failed with seed %d, last generated value: %s", f.seed, f.last))
        }
 }
 
+// Ok marks the current string as processed correctly.
 func (f *Fuzzer) Ok() { f.ok = true }
 
 func (s *Suite) Test_Fuzzer__out_of_stock(c *check.C) {
        fuzzer := NewFuzzer(0)
-       fuzzer.total = 1 // Trick the fuzzer to achieve full code coverage.
+       fuzzer.total = 1 // Intentionally damage the fuzzer to achieve full code coverage.
 
        c.Check(
                func() { fuzzer.Generate(1) },
Index: pkgsrc/pkgtools/pkglint/files/testnames_test.go
diff -u pkgsrc/pkgtools/pkglint/files/testnames_test.go:1.1 pkgsrc/pkgtools/pkglint/files/testnames_test.go:1.2
--- pkgsrc/pkgtools/pkglint/files/testnames_test.go:1.1 Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/testnames_test.go     Wed Nov  7 20:58:23 2018
@@ -1,130 +1,26 @@
 package main
 
 import (
-       "go/ast"
-       "go/parser"
-       "go/token"
        "gopkg.in/check.v1"
-       "os"
-       "sort"
-       "strings"
+       "netbsd.org/pkglint/intqa"
 )
 
 // Ensures that all test names follow a common naming scheme:
 //
 //  Test_${Type}_${Method}__${description_using_underscores}
 func (s *Suite) Test__test_names(c *check.C) {
-
-       // addTestee adds a single type or function declaration
-       // to the testees.
-       addTestee := func(testees *[]string, decl ast.Decl) {
-               switch decl := decl.(type) {
-
-               case *ast.GenDecl:
-                       for _, spec := range decl.Specs {
-                               switch spec := spec.(type) {
-                               case *ast.TypeSpec:
-                                       *testees = append(*testees, spec.Name.Name)
-                               }
-                       }
-
-               case *ast.FuncDecl:
-                       typePrefix := ""
-                       if decl.Recv != nil {
-                               typeExpr := decl.Recv.List[0].Type.(ast.Expr)
-                               var typeName string
-                               if star, ok := typeExpr.(*ast.StarExpr); ok {
-                                       typeName = star.X.(*ast.Ident).Name
-                               } else {
-                                       typeName = typeExpr.(*ast.Ident).Name
-                               }
-                               typePrefix = strings.TrimSuffix(typeName, "Impl") + "."
-                       }
-                       *testees = append(*testees, typePrefix+decl.Name.Name)
-               }
-       }
-
-       // loadAllTestees returns all type, function and method names
-       // from the current package, in the form FunctionName or
-       // TypeName.MethodName (omitting the * from the type name).
-       loadAllTestees := func() []string {
-               fset := token.NewFileSet()
-               pkgs, err := parser.ParseDir(fset, ".", func(fi os.FileInfo) bool { return true }, 0)
-               if err != nil {
-                       panic(err)
-               }
-
-               var typesAndFunctions []string
-               for _, file := range pkgs["main"].Files {
-                       for _, decl := range file.Decls {
-                               addTestee(&typesAndFunctions, decl)
-                       }
-               }
-
-               sort.Strings(typesAndFunctions)
-               return typesAndFunctions
-       }
-
-       generateAllowedPrefixes := func(typesAndFunctions []string) map[string]bool {
-               prefixes := make(map[string]bool)
-               for _, funcName := range typesAndFunctions {
-                       prefix := strings.Replace(funcName, ".", "_", 1)
-                       prefixes[prefix] = true
-               }
-
-               // Allow some special test name prefixes.
-               prefixes["Varalign"] = true
-               prefixes["ShellParser"] = true
-               return prefixes
-       }
-
-       checkTestName := func(fullTestMethod string, testee string, descr string, prefixes map[string]bool) {
-               if !prefixes[testee] {
-                       c.Errorf("%s: Testee %q not found.\n", fullTestMethod, testee)
-               }
-               if matches(descr, `\p{Ll}\p{Lu}`) {
-                       switch descr {
-                       case "comparing_YesNo_variable_to_string",
-                               "GitHub",
-                               "enumFrom",
-                               "dquotBacktDquot",
-                               "and_getSubdirs":
-                               // These exceptions are ok.
-
-                       default:
-                               c.Errorf("%s: Test description must not use CamelCase.\n", fullTestMethod)
-                       }
-               }
-       }
-
-       checkAll := func(typesAndFunctions []string, prefixes map[string]bool) {
-               for _, funcName := range typesAndFunctions {
-                       typeAndMethod := strings.SplitN(funcName, ".", 2)
-                       if len(typeAndMethod) == 2 {
-                               method := typeAndMethod[1]
-                               switch {
-                               case !hasPrefix(method, "Test"):
-                                       // Ignore
-
-                               case hasPrefix(method, "Test__"):
-                                       // OK
-
-                               case hasPrefix(method, "Test_"):
-                                       refAndDescr := strings.SplitN(method[5:], "__", 2)
-                                       descr := ""
-                                       if len(refAndDescr) > 1 {
-                                               descr = refAndDescr[1]
-                                       }
-                                       checkTestName(funcName, refAndDescr[0], descr, prefixes)
-
-                               default:
-                                       c.Errorf("%s: Missing underscore.\n", funcName)
-                               }
-                       }
-               }
-       }
-
-       testees := loadAllTestees()
-       prefixes := generateAllowedPrefixes(testees)
-       checkAll(testees, prefixes)
+       ck := intqa.NewTestNameChecker(c)
+       ck.IgnoreFiles("*yacc.go")
+       ck.AllowPrefix("Varalign", "mklines_varalign.go")
+       ck.AllowPrefix("ShellParser", "mkshparser.go")
+       ck.AllowCamelCaseDescriptions(
+               "comparing_YesNo_variable_to_string",
+               "GitHub",
+               "enumFrom",
+               "enumFromDirs",
+               "dquotBacktDquot",
+               "and_getSubdirs",
+               "SilentAutofixFormat")
+       ck.ShowWarnings(false)
+       ck.Check()
 }

Index: pkgsrc/pkgtools/pkglint/files/licenses.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.15 pkgsrc/pkgtools/pkglint/files/licenses.go:1.16
--- pkgsrc/pkgtools/pkglint/files/licenses.go:1.15      Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses.go   Wed Nov  7 20:58:23 2018
@@ -1,24 +1,20 @@
 package main
 
-import (
-       "io/ioutil"
-       "netbsd.org/pkglint/licenses"
-)
+import "netbsd.org/pkglint/licenses"
 
-func checkToplevelUnusedLicenses() {
-       usedLicenses := G.Pkgsrc.UsedLicenses
+func (src *Pkgsrc) checkToplevelUnusedLicenses() {
+       usedLicenses := src.UsedLicenses
        if usedLicenses == nil {
                return
        }
 
-       licensedir := G.Pkgsrc.File("licenses")
-       files, _ := ioutil.ReadDir(licensedir)
-       for _, licensefile := range files {
-               licensename := licensefile.Name()
-               licensepath := licensedir + "/" + licensename
-               if fileExists(licensepath) {
-                       if !usedLicenses[licensename] {
-                               NewLineWhole(licensepath).Warnf("This license seems to be unused.")
+       licensesDir := src.File("licenses")
+       for _, licenseFile := range src.ReadDir("licenses") {
+               licenseName := licenseFile.Name()
+               if !usedLicenses[licenseName] {
+                       licensePath := licensesDir + "/" + licenseName
+                       if fileExists(licensePath) {
+                               NewLineWhole(licensePath).Warnf("This license seems to be unused.")
                        }
                }
        }
@@ -30,9 +26,9 @@ type LicenseChecker struct {
 
 func (lc *LicenseChecker) Check(value string, op MkOperator) {
        expanded := resolveVariableRefs(value) // For ${PERL5_LICENSE}
-       licenses := licenses.Parse(ifelseStr(op == opAssignAppend, "append-placeholder ", "")+expanded, &G.res)
+       cond := licenses.Parse(ifelseStr(op == opAssignAppend, "append-placeholder ", "") + expanded)
 
-       if licenses == nil {
+       if cond == nil {
                if op == opAssign {
                        lc.MkLine.Errorf("Parse error for license condition %q.", value)
                } else {
@@ -41,7 +37,7 @@ func (lc *LicenseChecker) Check(value st
                return
        }
 
-       licenses.Walk(lc.checkNode)
+       cond.Walk(lc.checkNode)
 }
 
 func (lc *LicenseChecker) checkLicenseName(license string) {
Index: pkgsrc/pkgtools/pkglint/files/logging.go
diff -u pkgsrc/pkgtools/pkglint/files/logging.go:1.15 pkgsrc/pkgtools/pkglint/files/logging.go:1.16
--- pkgsrc/pkgtools/pkglint/files/logging.go:1.15       Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/logging.go    Wed Nov  7 20:58:23 2018
@@ -13,21 +13,25 @@ type LogLevel struct {
 }
 
 var (
-       llFatal   = &LogLevel{"FATAL", "fatal"}
-       llError   = &LogLevel{"ERROR", "error"}
-       llWarn    = &LogLevel{"WARN", "warning"}
-       llNote    = &LogLevel{"NOTE", "note"}
-       llAutofix = &LogLevel{"AUTOFIX", "autofix"}
+       Fatal           = &LogLevel{"FATAL", "fatal"}
+       Error           = &LogLevel{"ERROR", "error"}
+       Warn            = &LogLevel{"WARN", "warning"}
+       Note            = &LogLevel{"NOTE", "note"}
+       AutofixLogLevel = &LogLevel{"AUTOFIX", "autofix"}
 )
 
 var dummyLine = NewLine("", 0, "", nil)
 var dummyMkLine = NewMkLine(dummyLine)
 
-func shallBeLogged(msg string) bool {
-       if len(G.opts.LogOnly) > 0 {
+// shallBeLogged tests whether a diagnostic with the given format should
+// be logged. It only inspects the --only arguments.
+//
+// Duplicates are handled in main.logf.
+func shallBeLogged(format string) bool {
+       if len(G.Opts.LogOnly) > 0 {
                found := false
-               for _, substr := range G.opts.LogOnly {
-                       if contains(msg, substr) {
+               for _, substr := range G.Opts.LogOnly {
+                       if contains(format, substr) {
                                found = true
                                break
                        }
@@ -40,8 +44,8 @@ func shallBeLogged(msg string) bool {
        return true
 }
 
-func loggedAlready(fname, lineno, msg string) bool {
-       uniq := path.Clean(fname) + ":" + lineno + ":" + msg
+func loggedAlready(fileName, lineno, msg string) bool {
+       uniq := path.Clean(fileName) + ":" + lineno + ":" + msg
        if G.logged[uniq] {
                return true
        }
@@ -53,53 +57,55 @@ func loggedAlready(fname, lineno, msg st
        return false
 }
 
-func logs(level *LogLevel, fname, lineno, format, msg string) bool {
-       if fname != "" {
-               fname = cleanpath(fname)
+func logf(level *LogLevel, fileName, lineno, format, msg string) bool {
+       // TODO: Only ever output ASCII, no matter what's in the message.
+
+       if fileName != "" {
+               fileName = cleanpath(fileName)
        }
-       if G.Testing && format != "Magic-Autofix-Format" && !hasSuffix(format, ".") && !hasSuffix(format, ": %s") && !hasSuffix(format, ". %s") {
-               panic(fmt.Sprintf("Diagnostic format %q must end in a period.", format))
+       if G.Testing && format != AutofixFormat && !hasSuffix(format, ": %s") && !hasSuffix(format, ". %s") {
+               G.Assertf(hasSuffix(format, "."), "Diagnostic format %q must end in a period.", format)
        }
 
-       if !G.opts.LogVerbose && loggedAlready(fname, lineno, msg) {
+       if !G.Opts.LogVerbose && format != AutofixFormat && loggedAlready(fileName, lineno, msg) {
                G.explainNext = false
                return false
        }
 
        var text, sep string
-       if !G.opts.GccOutput {
+       if !G.Opts.GccOutput {
                text += sep + level.TraditionalName + ":"
                sep = " "
        }
-       if fname != "" {
-               text += sep + fname
+       if fileName != "" {
+               text += sep + fileName
                sep = ": "
                if lineno != "" {
                        text += ":" + lineno
                }
        }
-       if G.opts.GccOutput {
+       if G.Opts.GccOutput {
                text += sep + level.GccName + ":"
                sep = " "
        }
-       if G.opts.Profiling && format != "Magic-Autofix-Format" {
+       if G.Opts.Profiling && format != AutofixFormat && level != Fatal {
                G.loghisto.Add(format, 1)
        }
        text += sep + msg + "\n"
 
        out := G.logOut
-       if level == llFatal {
+       if level == Fatal {
                out = G.logErr
        }
 
        out.Write(text)
 
        switch level {
-       case llFatal:
+       case Fatal:
                panic(pkglintFatal{})
-       case llError:
+       case Error:
                G.errors++
-       case llWarn:
+       case Warn:
                G.warnings++
        }
        return true
@@ -130,7 +136,7 @@ func Explain(explanation ...string) {
                return
        }
        G.explanationsAvailable = true
-       if !G.opts.Explain {
+       if !G.Opts.Explain {
                return
        }
 
@@ -169,12 +175,12 @@ func NewSeparatorWriter(out io.Writer) *
 
 func (wr *SeparatorWriter) WriteLine(text string) {
        wr.Write(text)
-       io.WriteString(wr.out, "\n")
+       _, _ = io.WriteString(wr.out, "\n")
 }
 
 func (wr *SeparatorWriter) Write(text string) {
        if wr.needSeparator && wr.wroteSomething {
-               io.WriteString(wr.out, "\n")
+               _, _ = io.WriteString(wr.out, "\n")
                wr.needSeparator = false
        }
        n, err := io.WriteString(wr.out, text)
Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.15 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.16
--- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.15     Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go  Wed Nov  7 20:58:23 2018
@@ -58,7 +58,7 @@ func (s *Suite) Test_SubstContext__compl
 func (s *Suite) Test_SubstContext__OPSYSVARS(c *check.C) {
        t := s.Init(c)
 
-       G.opts.WarnExtra = true
+       G.Opts.WarnExtra = true
        ctx := NewSubstContext()
 
        ctx.Varassign(newSubstLine(t, 11, "SUBST_CLASSES.SunOS+=prefix"))
@@ -301,6 +301,56 @@ func (s *Suite) Test_SubstContext__do_pa
        t.CheckOutputEmpty()
 }
 
+// Variables mentioned in SUBST_VARS are not considered "foreign"
+// in the block and may be mixed with the other SUBST variables.
+func (s *Suite) Test_SubstContext__SUBST_VARS_defined_in_block(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wextra,no-space")
+       t.SetupVartypes()
+
+       mklines := t.NewMkLines("os.mk",
+               MkRcsID,
+               "",
+               "SUBST_CLASSES+=         os",
+               "SUBST_STAGE.os=         pre-configure",
+               "SUBST_FILES.os=         guess-os.h",
+               "SUBST_VARS.os=          TODAY1",
+               "TODAY1!=                date",
+               "TODAY2!=                date")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: os.mk:8: TODAY2 is defined but not used.",
+               "WARN: os.mk:8: Foreign variable \"TODAY2\" in SUBST block.")
+}
+
+// Variables mentioned in SUBST_VARS may appear in the same paragraph,
+// or alternatively anywhere else in the file.
+func (s *Suite) Test_SubstContext__SUBST_VARS_in_next_paragraph(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wextra,no-space")
+       t.SetupVartypes()
+
+       mklines := t.NewMkLines("os.mk",
+               MkRcsID,
+               "",
+               "SUBST_CLASSES+=         os",
+               "SUBST_STAGE.os=         pre-configure",
+               "SUBST_FILES.os=         guess-os.h",
+               "SUBST_VARS.os=          TODAY1",
+               "",
+               "TODAY1!=                date",
+               "TODAY2!=                date")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: os.mk:9: TODAY2 is defined but not used.")
+}
+
 // simulateSubstLines only tests some of the inner workings of SubstContext.
 // It is not realistic for all cases. If in doubt, use MkLines.Check.
 func simulateSubstLines(t *Tester, texts ...string) {

Index: pkgsrc/pkgtools/pkglint/files/licenses_test.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.16 pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.17
--- pkgsrc/pkgtools/pkglint/files/licenses_test.go:1.16 Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses_test.go      Wed Nov  7 20:58:23 2018
@@ -44,7 +44,7 @@ func (s *Suite) Test_LicenseChecker_Chec
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_checkToplevelUnusedLicenses(c *check.C) {
+func (s *Suite) Test_Pkgsrc_checkToplevelUnusedLicenses(c *check.C) {
        t := s.Init(c)
 
        t.SetupPkgsrc()
Index: pkgsrc/pkgtools/pkglint/files/mkparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.16 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.17
--- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.16 Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go      Wed Nov  7 20:58:23 2018
@@ -10,7 +10,7 @@ func (s *Suite) Test_MkParser_MkTokens(c
        t := s.Init(c)
 
        checkRest := func(input string, expectedTokens []*MkToken, expectedRest string) {
-               line := t.NewLines("Test_MkParser_MkTokens.mk", input)[0]
+               line := t.NewLines("Test_MkParser_MkTokens.mk", input).Lines[0]
                p := NewMkParser(line, input, true)
                actualTokens := p.MkTokens()
                c.Check(actualTokens, deepEquals, expectedTokens)
@@ -34,10 +34,10 @@ func (s *Suite) Test_MkParser_MkTokens(c
                        text += ":" + modifier
                }
                text += "}"
-               return &MkToken{Text: text, Varuse: &MkVarUse{varname: varname, modifiers: modifiers}}
+               return &MkToken{Text: text, Varuse: NewMkVarUse(varname, modifiers...)}
        }
        varuseText := func(text, varname string, modifiers ...string) *MkToken {
-               return &MkToken{Text: text, Varuse: &MkVarUse{varname: varname, modifiers: modifiers}}
+               return &MkToken{Text: text, Varuse: NewMkVarUse(varname, modifiers...)}
        }
 
        check("literal", literal("literal"))
@@ -147,7 +147,7 @@ func (s *Suite) Test_MkParser_MkTokens(c
        checkRest("${VAR:S,a,b,c,d,e,f}",
                []*MkToken{{
                        Text:   "${VAR:S,a,b,c,d,e,f}",
-                       Varuse: &MkVarUse{varname: "VAR", modifiers: []string{"S,a,b,"}}}},
+                       Varuse: NewMkVarUse("VAR", "S,a,b,")}},
                "")
 }
 
@@ -161,9 +161,7 @@ func (s *Suite) Test_MkParser_MkCond(c *
        check := func(input string, expectedTree MkCond) {
                checkRest(input, expectedTree, "")
        }
-       varuse := func(varname string, modifiers ...string) *MkVarUse {
-               return &MkVarUse{varname: varname, modifiers: modifiers}
-       }
+       varuse := NewMkVarUse
 
        check("${OPSYS:MNetBSD}",
                &mkCond{Not: &mkCond{Empty: varuse("OPSYS", "MNetBSD")}})
@@ -286,7 +284,12 @@ func (s *Suite) Test_MkCondWalker_Walk(c
        var events []string
 
        varuseStr := func(varuse *MkVarUse) string {
-               return strings.Join(append([]string{varuse.varname}, varuse.modifiers...), ":")
+               strs := make([]string, 1+len(varuse.modifiers), 1+len(varuse.modifiers))
+               strs[0] = varuse.varname
+               for i, mod := range varuse.modifiers {
+                       strs[1+i] = mod.Text
+               }
+               return strings.Join(strs, ":")
        }
 
        addEvent := func(name string, args ...string) {
Index: pkgsrc/pkgtools/pkglint/files/util_test.go
diff -u pkgsrc/pkgtools/pkglint/files/util_test.go:1.16 pkgsrc/pkgtools/pkglint/files/util_test.go:1.17
--- pkgsrc/pkgtools/pkglint/files/util_test.go:1.16     Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/util_test.go  Wed Nov  7 20:58:23 2018
@@ -84,23 +84,34 @@ func (s *Suite) Test_cleanpath(c *check.
        c.Check(cleanpath("dir/"), equals, "dir")
 }
 
-func (s *Suite) Test_relpath__failure(c *check.C) {
+// Relpath is called so often that handling the most common calls
+// without file system IO makes sense.
+func (s *Suite) Test_relpath__quick(c *check.C) {
+       c.Check(relpath("some/dir", "some/dir/../.."), equals, "../..")
+       c.Check(relpath("some/dir", "some/dir/./././../.."), equals, "../..")
+       c.Check(relpath("some/dir", "some/dir/"), equals, ".")
+       c.Check(relpath("some/dir", "some/directory"), equals, "../directory")
+}
+
+// This is not really an internal error but won't happen in practice anyway.
+// Therefore using ExpectPanic instead of ExpectFatal is ok.
+func (s *Suite) Test_relpath__failure_on_Windows(c *check.C) {
        t := s.Init(c)
 
        if runtime.GOOS == "windows" {
-               t.ExpectFatal(
+               t.ExpectPanic(
                        func() { relpath("c:/", "d:/") },
-                       "FATAL: Pkglint internal error: relpath \"c:/\" \"d:/\".")
+                       "Pkglint internal error: relpath \"c:/\" \"d:/\".")
        }
 }
 
-func (s *Suite) Test_abspath(c *check.C) {
+func (s *Suite) Test_abspath__on_Windows(c *check.C) {
        t := s.Init(c)
 
        if runtime.GOOS == "windows" {
-               t.ExpectFatal(
+               t.ExpectPanic(
                        func() { abspath("file\u0000name") },
-                       "FATAL: Pkglint internal error: abspath \"file\\x00name\".")
+                       "Pkglint internal error: abspath \"file\\x00name\".")
        }
 }
 
@@ -156,8 +167,8 @@ func (s *Suite) Test_detab(c *check.C) {
        c.Check(detab("12345678\t"), equals, "12345678        ")
 }
 
-const reMkIncludeBenchmark = `^\.(\s*)(s?include)\s+\"([^\"]+)\"\s*(?:#.*)?$`
-const reMkIncludeBenchmarkPositive = `^\.(\s*)(s?include)\s+\"(.+)\"\s*(?:#.*)?$`
+const reMkIncludeBenchmark = `^\.([\t ]*)(s?include)[\t ]+\"([^\"]+)\"[\t ]*(?:#.*)?$`
+const reMkIncludeBenchmarkPositive = `^\.([\t ]*)(s?include)[\t ]+\"(.+)\"[\t ]*(?:#.*)?$`
 
 func Benchmark_match3_buildlink3(b *testing.B) {
        for i := 0; i < b.N; i++ {
@@ -246,6 +257,7 @@ func (s *Suite) Test_isLocallyModified(c
        c.Check(isLocallyModified(modified), equals, true)
        c.Check(isLocallyModified(t.File("enoent")), equals, true)
        c.Check(isLocallyModified(t.File("not_mentioned")), equals, false)
+       c.Check(isLocallyModified(t.File("subdir/file")), equals, false)
 }
 
 func (s *Suite) Test_Scope_Defined(c *check.C) {
@@ -359,13 +371,15 @@ func (s *Suite) Test_FileCache(c *check.
        c.Check(cache.Get("Makefile", MustSucceed|LogErrors), check.IsNil) // Wrong LoadOptions.
 
        linesFromCache := cache.Get("Makefile", 0)
-       c.Check(linesFromCache, check.HasLen, 2)
-       c.Check(linesFromCache[1].Filename, equals, "Makefile")
+       c.Check(linesFromCache.FileName, equals, "Makefile")
+       c.Check(linesFromCache.Lines, check.HasLen, 2)
+       c.Check(linesFromCache.Lines[0].FileName, equals, "Makefile")
 
        // Cache keys are normalized using path.Clean.
        linesFromCache2 := cache.Get("./Makefile", 0)
-       c.Check(linesFromCache2, check.HasLen, 2)
-       c.Check(linesFromCache2[1].Filename, equals, "./Makefile")
+       c.Check(linesFromCache2.FileName, equals, "./Makefile")
+       c.Check(linesFromCache2.Lines, check.HasLen, 2)
+       c.Check(linesFromCache2.Lines[0].FileName, equals, "./Makefile")
 
        cache.Put("file1.mk", 0, lines)
        cache.Put("file2.mk", 0, lines)
@@ -392,4 +406,16 @@ func (s *Suite) Test_FileCache(c *check.
        c.Check(cache.mapping, check.HasLen, 1)
        c.Check(cache.hits, equals, 7)
        c.Check(cache.misses, equals, 5)
+
+       t.CheckOutputLines(
+               "FileCache \"Makefile\" with count 4.",
+               "FileCache \"file1.mk\" with count 2.",
+               "FileCache \"file2.mk\" with count 2.",
+               "FileCache.Evict \"file2.mk\" with count 2.",
+               "FileCache.Evict \"file1.mk\" with count 2.",
+               "FileCache.Halve \"Makefile\" with count 4.")
+}
+
+func (s *Suite) Test_makeHelp(c *check.C) {
+       c.Check(makeHelp("subst"), equals, confMake+" help topic=subst")
 }

Index: pkgsrc/pkgtools/pkglint/files/line.go
diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.26 pkgsrc/pkgtools/pkglint/files/line.go:1.27
--- pkgsrc/pkgtools/pkglint/files/line.go:1.26  Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/line.go       Wed Nov  7 20:58:23 2018
@@ -10,7 +10,7 @@ package main
 // do not.
 //
 // Some methods allow modification of the raw lines contained in the
-// logical line, but leave the Text field untouched. These methods are
+// logical line but leave the Text field untouched. These methods are
 // used in the --autofix mode.
 
 import (
@@ -19,10 +19,9 @@ import (
        "strconv"
 )
 
-type Line = *LineImpl
-
 type RawLine struct {
        Lineno int
+       // XXX: This is only needed for Autofix; probably should be moved there.
        orignl string
        textnl string
 }
@@ -31,8 +30,10 @@ func (rline *RawLine) String() string {
        return strconv.Itoa(rline.Lineno) + ":" + rline.textnl
 }
 
+type Line = *LineImpl
+
 type LineImpl struct {
-       Filename  string
+       FileName  string
        Basename  string
        firstLine int32 // Zero means not applicable, -1 means EOF
        lastLine  int32 // Usually the same as firstLine, may differ in Makefiles
@@ -42,23 +43,23 @@ type LineImpl struct {
        Once
 }
 
-func NewLine(fname string, lineno int, text string, rawLines []*RawLine) Line {
-       return NewLineMulti(fname, lineno, lineno, text, rawLines)
+func NewLine(fileName string, lineno int, text string, rawLines []*RawLine) Line {
+       return NewLineMulti(fileName, lineno, lineno, text, rawLines)
 }
 
 // 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, path.Base(fname), int32(firstLine), int32(lastLine), text, rawLines, nil, Once{}}
+func NewLineMulti(fileName string, firstLine, lastLine int, text string, rawLines []*RawLine) Line {
+       return &LineImpl{fileName, path.Base(fileName), int32(firstLine), int32(lastLine), text, rawLines, nil, Once{}}
 }
 
 // NewLineEOF creates a dummy line for logging, with the "line number" EOF.
-func NewLineEOF(fname string) Line {
-       return NewLineMulti(fname, -1, 0, "", nil)
+func NewLineEOF(fileName string) Line {
+       return NewLineMulti(fileName, -1, 0, "", nil)
 }
 
 // NewLineWhole creates a dummy line for logging messages that affect a file as a whole.
-func NewLineWhole(fname string) Line {
-       return NewLine(fname, 0, "", nil)
+func NewLineWhole(fileName string) Line {
+       return NewLine(fileName, 0, "", nil)
 }
 
 func (line *LineImpl) Linenos() string {
@@ -74,19 +75,28 @@ func (line *LineImpl) Linenos() string {
        }
 }
 
-func (line *LineImpl) ReferenceFrom(other Line) string {
-       if line.Filename != other.Filename {
-               return cleanpath(relpath(path.Dir(other.Filename), line.Filename)) + ":" + line.Linenos()
-       }
-       return "line " + line.Linenos()
+// RefTo returns a reference to another line,
+// which can be in the same file or in a different file.
+func (line *LineImpl) RefTo(other Line) string {
+       if line.FileName != other.FileName {
+               return cleanpath(relpath(path.Dir(line.FileName), other.FileName)) + ":" + other.Linenos()
+       }
+       return "line " + other.Linenos()
+}
+
+// PathToFile returns the relative path from this line to the given file path.
+// This is typically used for arguments in diagnostics, which should always be
+// relative to the line with which the diagnostic is associated.
+func (line *LineImpl) PathToFile(filePath string) string {
+       return relpath(path.Dir(line.FileName), filePath)
 }
 
 func (line *LineImpl) IsMultiline() bool {
        return line.firstLine > 0 && line.firstLine != line.lastLine
 }
 
-func (line *LineImpl) printSource(out *SeparatorWriter) {
-       if !G.opts.PrintSource {
+func (line *LineImpl) showSource(out *SeparatorWriter) {
+       if !G.Opts.ShowSource {
                return
        }
 
@@ -109,7 +119,7 @@ func (line *LineImpl) printSource(out *S
                for _, before := range line.autofix.linesBefore {
                        out.Write("+\t" + before)
                }
-               printDiff(line.autofix.lines)
+               printDiff(line.raw)
                for _, after := range line.autofix.linesAfter {
                        out.Write("+\t" + after)
                }
@@ -119,7 +129,7 @@ func (line *LineImpl) printSource(out *S
 }
 
 func (line *LineImpl) log(level *LogLevel, format string, args []interface{}) {
-       if G.opts.PrintAutofix || G.opts.Autofix {
+       if G.Opts.ShowAutofix || G.Opts.Autofix {
                // In these two cases, the only interesting diagnostics are
                // those that can be fixed automatically.
                // These are logged by Autofix.Apply.
@@ -130,36 +140,33 @@ func (line *LineImpl) log(level *LogLeve
                return
        }
 
-       out := G.logOut
-       if level == llError {
-               out = G.logErr
+       if G.Opts.ShowSource {
+               line.showSource(G.logOut)
        }
-
-       logs(level, line.Filename, line.Linenos(), format, fmt.Sprintf(format, args...))
-       if !G.opts.PrintAutofix && G.opts.PrintSource {
-               line.printSource(out)
-               out.Separate()
+       logf(level, line.FileName, line.Linenos(), format, fmt.Sprintf(format, args...))
+       if G.Opts.ShowSource {
+               G.logOut.Separate()
        }
 }
 
 func (line *LineImpl) Fatalf(format string, args ...interface{}) {
-       line.log(llFatal, format, args)
+       line.log(Fatal, format, args)
 }
 
 func (line *LineImpl) Errorf(format string, args ...interface{}) {
-       line.log(llError, format, args)
+       line.log(Error, format, args)
 }
 
 func (line *LineImpl) Warnf(format string, args ...interface{}) {
-       line.log(llWarn, format, args)
+       line.log(Warn, format, args)
 }
 
 func (line *LineImpl) Notef(format string, args ...interface{}) {
-       line.log(llNote, format, args)
+       line.log(Note, format, args)
 }
 
 func (line *LineImpl) String() string {
-       return line.Filename + ":" + line.Linenos() + ": " + line.Text
+       return line.FileName + ":" + line.Linenos() + ": " + line.Text
 }
 
 // Autofix returns the autofix instance belonging to the line.
@@ -178,11 +185,11 @@ func (line *LineImpl) String() string {
 //
 //  fix.Replace("from", "to")
 //  fix.ReplaceAfter("prefix", "from", "to")
-//  fix.ReplaceRegex(`\s+`, "space", -1)
+//  fix.ReplaceRegex(`[\t ]+`, "space", -1)
 //  fix.InsertBefore("new line")
 //  fix.InsertAfter("new line")
 //  fix.Delete()
-//  fix.Custom(func(...))
+//  fix.Custom(func(showAutofix, autofix bool) {})
 //
 //  fix.Apply()
 func (line *LineImpl) Autofix() *Autofix {
Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.26 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.26  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Wed Nov  7 20:58:23 2018
@@ -7,7 +7,6 @@ import (
        "time"
 
        "gopkg.in/check.v1"
-       "netbsd.org/pkglint/trace"
        "os"
 )
 
@@ -37,7 +36,7 @@ func (s *Suite) Test_Pkglint_Main__no_ar
 
        c.Check(exitcode, equals, 1)
        t.CheckOutputLines(
-               "FATAL: \".\" is not inside a pkgsrc tree.")
+               "FATAL: \".\" must be inside a pkgsrc tree.")
 }
 
 func (s *Suite) Test_Pkglint_Main__only(c *check.C) {
@@ -48,7 +47,7 @@ func (s *Suite) Test_Pkglint_Main__only(
        if c.Check(exitcode, check.NotNil) {
                c.Check(*exitcode, equals, 0)
        }
-       c.Check(G.opts.LogOnly, deepEquals, []string{":Q"})
+       c.Check(G.Opts.LogOnly, deepEquals, []string{":Q"})
        t.CheckOutputLines(
                confVersion)
 }
@@ -68,18 +67,18 @@ func (s *Suite) Test_Pkglint_Main__unkno
                "  -d, --debug                 log verbose call traces for debugging",
                "  -e, --explain               explain the diagnostics or give further help",
                "  -f, --show-autofix          show what pkglint can fix automatically",
-               "  -F, --autofix               try to automatically fix some errors (experimental)",
+               "  -F, --autofix               try to automatically fix some errors",
                "  -g, --gcc-output-format     mimic the gcc output format",
-               "  -h, --help                  print a detailed usage message",
+               "  -h, --help                  show a detailed usage message",
                "  -I, --dumpmakefile          dump the Makefile after parsing",
                "  -i, --import                prepare the import of a wip package",
                "  -m, --log-verbose           allow the same log message more than once",
                "  -o, --only                  only log messages containing the given text",
                "  -p, --profiling             profile the executing program",
-               "  -q, --quiet                 don't print a summary line when finishing",
+               "  -q, --quiet                 don't show a summary line when finishing",
                "  -r, --recursive             check subdirectories, too",
                "  -s, --source                show the source lines together with diagnostics",
-               "  -V, --version               print the version number of pkglint",
+               "  -V, --version               show the version number of pkglint",
                "  -W, --warning=warning,...   enable or disable groups of warnings",
                "",
                "  Flags for -C, --check:",
@@ -135,7 +134,7 @@ func (s *Suite) Test_Pkglint_Main__panic
 // initialize only those parts of the infrastructure they really
 // need.
 //
-// Especially covers Pkglint.PrintSummary and Pkglint.Checkfile.
+// Especially covers Pkglint.ShowSummary and Pkglint.Checkfile.
 func (s *Suite) Test_Pkglint_Main__complete_package(c *check.C) {
        t := s.Init(c)
 
@@ -242,8 +241,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                "ERROR: ~/sysutils/checkperms/README: Packages in main pkgsrc must not have a README file.",
                "ERROR: ~/sysutils/checkperms/TODO: Packages in main pkgsrc must not have a TODO file.",
                "ERROR: ~/sysutils/checkperms/distinfo:7: SHA1 hash of patches/patch-checkperms.c differs "+
-                       "(distinfo has asdfasdf, patch file has e775969de639ec703866c0336c4c8e0fdd96309c). "+
-                       "Run \""+confMake+" makepatchsum\".",
+                       "(distinfo has asdfasdf, patch file has e775969de639ec703866c0336c4c8e0fdd96309c).",
                "WARN: ~/sysutils/checkperms/patches/patch-checkperms.c:12: Premature end of patch hunk "+
                        "(expected 1 lines to be deleted and 0 lines to be added).",
                "4 errors and 2 warnings found.",
@@ -349,7 +347,7 @@ func (s *Suite) Test_Pkglint_CheckDirent
 func (s *Suite) Test_resolveVariableRefs__circular_reference(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine("fname", 1, "GCC_VERSION=${GCC_VERSION}")
+       mkline := t.NewMkLine("fileName", 1, "GCC_VERSION=${GCC_VERSION}")
        G.Pkg = NewPackage(t.File("category/pkgbase"))
        G.Pkg.vars.Define("GCC_VERSION", mkline)
 
@@ -361,9 +359,9 @@ func (s *Suite) Test_resolveVariableRefs
 func (s *Suite) Test_resolveVariableRefs__multilevel(c *check.C) {
        t := s.Init(c)
 
-       mkline1 := t.NewMkLine("fname", 10, "_=${SECOND}")
-       mkline2 := t.NewMkLine("fname", 11, "_=${THIRD}")
-       mkline3 := t.NewMkLine("fname", 12, "_=got it")
+       mkline1 := t.NewMkLine("fileName", 10, "_=${SECOND}")
+       mkline2 := t.NewMkLine("fileName", 11, "_=${THIRD}")
+       mkline3 := t.NewMkLine("fileName", 12, "_=got it")
        G.Pkg = NewPackage(t.File("category/pkgbase"))
        defineVar(mkline1, "FIRST")
        defineVar(mkline2, "SECOND")
@@ -380,7 +378,7 @@ func (s *Suite) Test_resolveVariableRefs
 func (s *Suite) Test_resolveVariableRefs__special_chars(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine("fname", 10, "_=x11")
+       mkline := t.NewMkLine("fileName", 10, "_=x11")
        G.Pkg = NewPackage(t.File("category/pkg"))
        G.Pkg.vars.Define("GST_PLUGINS0.10_TYPE", mkline)
 
@@ -478,7 +476,7 @@ func (s *Suite) Test_Pkglint_Checkfile__
        lines := t.SetupFileLines("category/package/ALTERNATIVES",
                "bin/tar @PREFIX@/bin/gnu-tar")
 
-       G.Main("pkglint", lines[0].Filename)
+       G.Main("pkglint", lines.FileName)
 
        t.CheckOutputLines(
                "NOTE: ~/category/package/ALTERNATIVES:1: @PREFIX@/ can be omitted from the file name.",
@@ -608,7 +606,7 @@ func (s *Suite) Test_Pkglint_Tool__looku
        t := s.Init(c)
 
        G.Mk = t.NewMkLines("Makefile", MkRcsID)
-       G.Pkgsrc.Tools.defTool("tool", "TOOL", false, Nowhere)
+       G.Pkgsrc.Tools.def("tool", "TOOL", false, Nowhere)
 
        loadTimeTool, loadTimeUsable := G.Tool("${TOOL}", LoadTime)
        runTimeTool, runTimeUsable := G.Tool("${TOOL}", RunTime)
@@ -623,7 +621,7 @@ func (s *Suite) Test_Pkglint_Tool__looku
        t := s.Init(c)
 
        G.Mk = t.NewMkLines("Makefile", MkRcsID)
-       G.Pkgsrc.Tools.defTool("tool", "TOOL", false, AtRunTime)
+       G.Pkgsrc.Tools.def("tool", "TOOL", false, AtRunTime)
 
        loadTimeTool, loadTimeUsable := G.Tool("${TOOL}", LoadTime)
        runTimeTool, runTimeUsable := G.Tool("${TOOL}", RunTime)
@@ -652,7 +650,7 @@ func (s *Suite) Test_Pkglint_ToolByVarna
        t := s.Init(c)
 
        G.Mk = t.NewMkLines("Makefile", MkRcsID)
-       G.Pkgsrc.Tools.defTool("tool", "TOOL", false, AtRunTime)
+       G.Pkgsrc.Tools.def("tool", "TOOL", false, AtRunTime)
 
        c.Check(G.ToolByVarname("TOOL", LoadTime).String(), equals, "tool:TOOL::AtRunTime")
        c.Check(G.ToolByVarname("TOOL", RunTime).String(), equals, "tool:TOOL::AtRunTime")
@@ -855,6 +853,112 @@ func (s *Suite) Test_Pkglint_checkMode__
                "ERROR: device: Only files and directories are allowed in pkgsrc.")
 }
 
+func (s *Suite) Test_Pkglint_checkdirPackage(c *check.C) {
+       t := s.Init(c)
+
+       t.Chdir("category/package")
+       t.CreateFileLines("Makefile",
+               MkRcsID)
+
+       G.checkdirPackage(".")
+
+       t.CheckOutputLines(
+               "WARN: Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
+               "WARN: distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
+               "ERROR: Makefile: Each package must define its LICENSE.",
+               "WARN: Makefile: No COMMENT given.")
+}
+
+func (s *Suite) Test_Pkglint_checkdirPackage__PKGDIR(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       t.SetupPkgsrc()
+       t.CreateFileLines("category/Makefile")
+       t.CreateFileLines("other/package/Makefile",
+               MkRcsID)
+       t.CreateFileLines("other/package/PLIST",
+               PlistRcsID,
+               "bin/program")
+       t.CreateFileLines("other/package/distinfo",
+               RcsID,
+               "",
+               "SHA1 (patch-aa) = da39a3ee5e6b4b0d3255bfef95601890afd80709")
+       t.CreateFileLines("category/package/patches/patch-aa",
+               RcsID)
+       t.Chdir("category/package")
+       t.CreateFileLines("Makefile",
+               MkRcsID,
+               "",
+               "CATEGORIES=\tcategory",
+               "",
+               "COMMENT=\tComment",
+               "LICENSE=\t2-clause-bsd",
+               "PKGDIR=\t\t../../other/package")
+
+       // DISTINFO_FILE is resolved relative to PKGDIR, the other places
+       // are resolved relative to the package base directory.
+       G.checkdirPackage(".")
+
+       t.CheckOutputLines(
+               "ERROR: patches/patch-aa:1: Patch files must not be empty.")
+}
+
+func (s *Suite) Test_Pkglint_checkdirPackage__patch_without_distinfo(c *check.C) {
+       t := s.Init(c)
+
+       pkg := t.SetupPackage("category/package")
+       t.CreateFileDummyPatch("category/package/patches/patch-aa")
+       t.Remove("category/package/distinfo")
+
+       G.CheckDirent(pkg)
+
+       // FIXME: One of the below warnings is redundant.
+       t.CheckOutputLines(
+               "WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
+               "WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makepatchsum\".")
+}
+
+func (s *Suite) Test_Pkglint_checkdirPackage__meta_package_without_license(c *check.C) {
+       t := s.Init(c)
+
+       t.Chdir("category/package")
+       t.CreateFileLines("Makefile",
+               MkRcsID,
+               "",
+               "META_PACKAGE=\tyes")
+       t.SetupVartypes()
+
+       G.checkdirPackage(".")
+
+       t.CheckOutputLines(
+               "WARN: Makefile: No COMMENT given.") // No error about missing LICENSE.
+}
+
+func (s *Suite) Test_Pkglint_checkdirPackage__filename_with_variable(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wall,no-order")
+       pkg := t.SetupPackage("category/package",
+               ".include \"../../mk/bsd.prefs.mk\"",
+               "",
+               "RUBY_VERSIONS_ACCEPTED=\t22 23 24 25", // As of 2018.
+               ".for rv in ${RUBY_VERSIONS_ACCEPTED}",
+               "RUBY_VER?=\t\t${rv}",
+               ".endfor",
+               "",
+               "RUBY_PKGDIR=\t../../lang/ruby-${RUBY_VER}-base",
+               "DISTINFO_FILE=\t${RUBY_PKGDIR}/distinfo")
+
+       // Pkglint cannot currently resolve the location of DISTINFO_FILE completely
+       // because the variable \"rv\" comes from a .for loop.
+       //
+       // TODO: iterate over variables in simple .for loops like the above.
+       G.CheckDirent(pkg)
+
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_Pkglint_checkdirPackage__ALTERNATIVES(c *check.C) {
        t := s.Init(c)
 
@@ -870,13 +974,38 @@ func (s *Suite) Test_Pkglint_checkdirPac
                        "Alternative implementation \"bin/wrapper-impl\" must appear in the PLIST.")
 }
 
+func (s *Suite) Test_Pkglint_ShowSummary__explanations_with_only(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("--only", "interesting")
+       line := t.NewLine("Makefile", 27, "The old song")
+
+       line.Warnf("Filtered warning.")               // Is not logged.
+       Explain("Explanation for the above warning.") // Neither would this explanation be logged.
+       G.ShowSummary()
+
+       c.Check(G.explanationsAvailable, equals, false)
+       t.CheckOutputLines(
+               "Looks fine.") // "pkglint -e" is not advertised since the above explanation is not relevant.
+
+       line.Warnf("What an interesting line.")
+       Explain("This explanation is available.")
+       G.ShowSummary()
+
+       c.Check(G.explanationsAvailable, equals, true)
+       t.CheckOutputLines(
+               "WARN: Makefile:27: What an interesting line.",
+               "0 errors and 1 warning found.",
+               "(Run \"pkglint -e\" to show explanations.)")
+}
+
 func (s *Suite) Test_CheckfileMk__enoent(c *check.C) {
        t := s.Init(c)
 
-       CheckfileMk(t.File("fname.mk"))
+       CheckfileMk(t.File("fileName.mk"))
 
        t.CheckOutputLines(
-               "ERROR: ~/fname.mk: Cannot be read.")
+               "ERROR: ~/fileName.mk: Cannot be read.")
 }
 
 func (s *Suite) Test_Pkglint_checkExecutable(c *check.C) {
@@ -899,6 +1028,20 @@ func (s *Suite) Test_Pkglint_checkExecut
                "AUTOFIX: ~/file.mk: Clearing executable bits")
 }
 
+func (s *Suite) Test_Pkglint_checkExecutable__already_committed(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("CVS/Entries",
+               "/file.mk/modified////")
+       fileName := t.File("file.mk")
+       fileInfo := ExecutableFileInfo{path.Base(fileName)}
+
+       G.checkExecutable(fileName, fileInfo)
+
+       // See the "Too late" comment in Pkglint.checkExecutable.
+       t.CheckOutputEmpty()
+}
+
 func (s *Suite) Test_main(c *check.C) {
        t := s.Init(c)
 
Index: pkgsrc/pkgtools/pkglint/files/plist_test.go
diff -u pkgsrc/pkgtools/pkglint/files/plist_test.go:1.26 pkgsrc/pkgtools/pkglint/files/plist_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/plist_test.go:1.26    Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/plist_test.go Wed Nov  7 20:58:23 2018
@@ -5,7 +5,6 @@ import "gopkg.in/check.v1"
 func (s *Suite) Test_ChecklinesPlist(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        G.Pkg = NewPackage(t.File("category/pkgbase"))
        lines := t.NewLines("PLIST",
                "bin/i386/6c",
@@ -47,7 +46,7 @@ func (s *Suite) Test_ChecklinesPlist(c *
                "WARN: PLIST:14: Packages that install icon theme files should set ICON_THEMES.",
                "ERROR: PLIST:15: Packages that install hicolor icons "+
                        "must include \"../../graphics/hicolor-icon-theme/buildlink3.mk\" in the Makefile.",
-               "ERROR: PLIST:18: Duplicate filename \"share/tzinfo\", already appeared in line 17.")
+               "ERROR: PLIST:18: Duplicate file name \"share/tzinfo\", already appeared in line 17.")
 }
 
 func (s *Suite) Test_ChecklinesPlist__empty(c *check.C) {
@@ -136,11 +135,11 @@ func (s *Suite) Test_plistLineSorter_Sor
        plines := ck.NewLines(lines)
 
        sorter1 := NewPlistLineSorter(plines)
-       c.Check(sorter1.unsortable, equals, lines[5])
+       c.Check(sorter1.unsortable, equals, lines.Lines[5])
 
-       cleanedLines := append(append(lines[0:5], lines[6:8]...), lines[9:]...) // Remove ${UNKNOWN} and @exec
+       cleanedLines := append(append(lines.Lines[0:5], lines.Lines[6:8]...), lines.Lines[9:]...) // Remove ${UNKNOWN} and @exec
 
-       sorter2 := NewPlistLineSorter((&PlistChecker{nil, nil, "", Once{}}).NewLines(cleanedLines))
+       sorter2 := NewPlistLineSorter((&PlistChecker{nil, nil, "", Once{}}).NewLines(NewLines(lines.FileName, cleanedLines)))
 
        c.Check(sorter2.unsortable, check.IsNil)
 
@@ -209,8 +208,6 @@ func (s *Suite) Test_PlistChecker_checkp
 func (s *Suite) Test_PlistChecker__autofix(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
-
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "lib/libvirt/connection-driver/libvirt_driver_storage.la",
@@ -278,7 +275,6 @@ func (s *Suite) Test_PlistChecker__autof
 func (s *Suite) Test_PlistChecker__remove_same_entries(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "${PLIST.option1}bin/true",
@@ -292,11 +288,11 @@ func (s *Suite) Test_PlistChecker__remov
        ChecklinesPlist(lines)
 
        t.CheckOutputLines(
-               "ERROR: ~/PLIST:2: Duplicate filename \"bin/true\", already appeared in line 3.",
-               "ERROR: ~/PLIST:4: Duplicate filename \"bin/true\", already appeared in line 3.",
-               "ERROR: ~/PLIST:5: Duplicate filename \"bin/true\", already appeared in line 3.",
+               "ERROR: ~/PLIST:2: Duplicate file name \"bin/true\", already appeared in line 3.",
+               "ERROR: ~/PLIST:4: Duplicate file name \"bin/true\", already appeared in line 3.",
+               "ERROR: ~/PLIST:5: Duplicate file name \"bin/true\", already appeared in line 3.",
                "WARN: ~/PLIST:6: \"bin/false\" should be sorted before \"bin/true\".",
-               "ERROR: ~/PLIST:8: Duplicate filename \"bin/true\", already appeared in line 3.")
+               "ERROR: ~/PLIST:8: Duplicate file name \"bin/true\", already appeared in line 3.")
 
        t.SetupCommandLine("-Wall", "--autofix")
 
@@ -340,8 +336,6 @@ func (s *Suite) Test_PlistChecker__autof
 func (s *Suite) Test_PlistChecker__exec_MKDIR(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
-
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "bin/program",
@@ -355,8 +349,6 @@ func (s *Suite) Test_PlistChecker__exec_
 func (s *Suite) Test_PlistChecker__empty_line(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
-
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "",
@@ -425,17 +417,17 @@ func (s *Suite) Test_PlistChecker__unwan
 
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
+               "share/perllocal.pod",
                "share/pkgbase/CVS/Entries",
-               "share/pkgbase/Makefile.orig",
-               "share/perllocal.pod")
+               "share/pkgbase/Makefile.orig")
        G.Pkg = NewPackage(t.File("category/package"))
 
        ChecklinesPlist(lines)
 
        t.CheckOutputLines(
-               "WARN: ~/PLIST:2: CVS files should not be in the PLIST.",
-               "WARN: ~/PLIST:3: .orig files should not be in the PLIST.",
-               "WARN: ~/PLIST:4: perllocal.pod files should not be in the PLIST.")
+               "WARN: ~/PLIST:2: perllocal.pod files should not be in the PLIST.",
+               "WARN: ~/PLIST:3: CVS files should not be in the PLIST.",
+               "WARN: ~/PLIST:4: .orig files should not be in the PLIST.")
 }
 
 func (s *Suite) Test_PlistChecker_checkpathInfo(c *check.C) {
@@ -457,19 +449,19 @@ func (s *Suite) Test_PlistChecker_checkp
 
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
-               "lib/package/liberty-1.0.so",
                "lib/charset.alias",
+               "lib/liberty-1.0.la",
                "lib/locale/de_DE/liberty.mo",
-               "lib/liberty-1.0.la")
+               "lib/package/liberty-1.0.so")
        G.Pkg = NewPackage(t.File("category/package"))
        G.Pkg.EffectivePkgbase = "package"
 
        ChecklinesPlist(lines)
 
        t.CheckOutputLines(
-               "ERROR: ~/PLIST:3: Only the libiconv package may install lib/charset.alias.",
-               "ERROR: ~/PLIST:4: \"lib/locale\" must not be listed. Use ${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead.",
-               "WARN: ~/PLIST:5: Packages that install libtool libraries should define USE_LIBTOOL.")
+               "ERROR: ~/PLIST:2: Only the libiconv package may install lib/charset.alias.",
+               "WARN: ~/PLIST:3: Packages that install libtool libraries should define USE_LIBTOOL.",
+               "ERROR: ~/PLIST:4: \"lib/locale\" must not be listed. Use ${PKGLOCALEDIR}/locale and set USE_PKGLOCALEDIR instead.")
 }
 
 func (s *Suite) Test_PlistChecker_checkpathMan(c *check.C) {
@@ -477,20 +469,19 @@ func (s *Suite) Test_PlistChecker_checkp
 
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
-               "man/manx/program.x",
-               "man/man1/program.8")
+               "man/man1/program.8",
+               "man/manx/program.x")
 
        ChecklinesPlist(lines)
 
        t.CheckOutputLines(
-               "WARN: ~/PLIST:2: Unknown section \"x\" for manual page.",
-               "WARN: ~/PLIST:3: Mismatch between the section (1) and extension (8) of the manual page.")
+               "WARN: ~/PLIST:2: Mismatch between the section (1) and extension (8) of the manual page.",
+               "WARN: ~/PLIST:3: Unknown section \"x\" for manual page.")
 }
 
 func (s *Suite) Test_PlistChecker_checkpathShare(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "share/doc/html/package/index.html",
@@ -515,7 +506,6 @@ func (s *Suite) Test_PlistChecker_checkp
 func (s *Suite) Test_PlistLine_CheckTrailingWhitespace(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "bin/program \t")
@@ -529,7 +519,6 @@ func (s *Suite) Test_PlistLine_CheckTrai
 func (s *Suite) Test_PlistLine_CheckDirective(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        lines := t.SetupFileLines("PLIST",
                PlistRcsID,
                "@unexec rmdir %D/bin",

Index: pkgsrc/pkgtools/pkglint/files/linechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/linechecker.go:1.8 pkgsrc/pkgtools/pkglint/files/linechecker.go:1.9
--- pkgsrc/pkgtools/pkglint/files/linechecker.go:1.8    Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/linechecker.go        Wed Nov  7 20:58:23 2018
@@ -3,7 +3,6 @@ package main
 import (
        "fmt"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "strings"
 )
 
@@ -19,7 +18,7 @@ func CheckLineAbsolutePathname(line Line
        //
        // Another context where absolute pathnames usually appear is in
        // assignments like "bindir=/bin".
-       if m, path := match1(text, `(?:^|\s|\$[{(]DESTDIR[)}]|[\w_]+\s*=\s*)(/(?:[^"'\s\\]|"[^"*]"|'[^']*')*)`); m {
+       if m, path := match1(text, `(?:^|[\t ]|\$[{(]DESTDIR[)}]|[\w_]+[\t ]*=[\t ]*)(/(?:[^"' \t\\]|"[^"*]"|'[^']*')*)`); m {
                if matches(path, `^/\w`) {
                        CheckwordAbsolutePathname(line, path)
                }
Index: pkgsrc/pkgtools/pkglint/files/linechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/linechecker_test.go:1.8 pkgsrc/pkgtools/pkglint/files/linechecker_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/linechecker_test.go:1.8       Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/linechecker_test.go   Wed Nov  7 20:58:23 2018
@@ -44,19 +44,19 @@ func (s *Suite) Test_CheckLineTrailingWh
 func (s *Suite) Test_CheckLineRcsid(c *check.C) {
        t := s.Init(c)
 
-       lines := t.NewLines("fname",
+       lines := t.NewLines("fileName",
                "$"+"NetBSD: dummy $",
                "$"+"NetBSD$",
                "$"+"Id: dummy $",
                "$"+"Id$",
                "$"+"FreeBSD$")
 
-       for _, line := range lines {
+       for _, line := range lines.Lines {
                CheckLineRcsid(line, ``, "")
        }
 
        t.CheckOutputLines(
-               "ERROR: fname:3: Expected \"$"+"NetBSD$\".",
-               "ERROR: fname:4: Expected \"$"+"NetBSD$\".",
-               "ERROR: fname:5: Expected \"$"+"NetBSD$\".")
+               "ERROR: fileName:3: Expected \"$"+"NetBSD$\".",
+               "ERROR: fileName:4: Expected \"$"+"NetBSD$\".",
+               "ERROR: fileName:5: Expected \"$"+"NetBSD$\".")
 }
Index: pkgsrc/pkgtools/pkglint/files/mkshtypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshtypes.go:1.8 pkgsrc/pkgtools/pkglint/files/mkshtypes.go:1.9
--- pkgsrc/pkgtools/pkglint/files/mkshtypes.go:1.8      Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/mkshtypes.go  Wed Nov  7 20:58:23 2018
@@ -101,7 +101,7 @@ type MkShForClause struct {
 
 // MkShCaseClause is a "case" statement, including all its branches.
 //
-// Example: case $filename in *.c) echo "C source" ;; esac
+// Example: case $fileName in *.c) echo "C source" ;; esac
 type MkShCaseClause struct {
        Word  *ShToken
        Cases []*MkShCaseItem

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.39 pkgsrc/pkgtools/pkglint/files/mkline.go:1.40
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.39        Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Wed Nov  7 20:58:23 2018
@@ -4,7 +4,7 @@ package main
 
 import (
        "fmt"
-       "netbsd.org/pkglint/trace"
+       "netbsd.org/pkglint/textproc"
        "path"
        "strings"
 )
@@ -69,7 +69,7 @@ func NewMkLine(line Line) *MkLineImpl {
        }
 
        if m, commented, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment := MatchVarassign(text); m {
-               if G.opts.WarnSpace && spaceAfterVarname != "" {
+               if G.Opts.WarnSpace && spaceAfterVarname != "" {
                        switch {
                        case hasSuffix(varname, "+") && op == "=":
                                break
@@ -126,11 +126,11 @@ func NewMkLine(line Line) *MkLineImpl {
                return &MkLineImpl{line, &mkLineIncludeImpl{directive == "include", false, indent, includefile, ""}}
        }
 
-       if m, indent, directive, includefile := match3(text, `^\.(\s*)(s?include)\s+<([^>]+)>\s*(?:#.*)?$`); m {
+       if m, indent, directive, includefile := match3(text, `^\.([\t ]*)(s?include)[\t ]+<([^>]+)>[\t ]*(?:#.*)?$`); m {
                return &MkLineImpl{line, &mkLineIncludeImpl{directive == "include", true, indent, includefile, ""}}
        }
 
-       if m, targets, whitespace, sources := match3(text, `^([^\s:]+(?:\s*[^\s:]+)*)(\s*):\s*([^#]*?)(?:\s*#.*)?$`); m {
+       if m, targets, whitespace, sources := match3(text, `^([^\t :]+(?:[\t ]*[^\t :]+)*)([\t ]*):[\t ]*([^#]*?)(?:[\t ]*#.*)?$`); m {
                if whitespace != "" {
                        line.Warnf("Space before colon in dependency line.")
                }
@@ -146,7 +146,7 @@ func NewMkLine(line Line) *MkLineImpl {
 }
 
 func (mkline *MkLineImpl) String() string {
-       return fmt.Sprintf("%s:%s", mkline.Filename, mkline.Linenos())
+       return fmt.Sprintf("%s:%s", mkline.FileName, mkline.Linenos())
 }
 
 func (mkline *MkLineImpl) IsVarassign() bool {
@@ -356,10 +356,20 @@ func (mkline *MkLineImpl) ResolveVarsInR
        if G.Pkg != nil {
                basedir = G.Pkg.File(".")
        } else {
-               basedir = path.Dir(mkline.Filename)
+               basedir = path.Dir(mkline.FileName)
        }
        pkgsrcdir := relpath(basedir, G.Pkgsrc.File("."))
 
+       if G.Testing {
+               // Relative pkgsrc paths usually only contain two or three levels.
+               // A possible reason for reaching this assertion is:
+               // Tests that access the file system must create their lines
+               // using t.SetupFileMkLines, not using t.NewMkLines.
+               G.Assertf(!contains(pkgsrcdir, "../../../../.."),
+                       "Relative path %q for %q is too deep below the pkgsrc root %q.",
+                       pkgsrcdir, basedir, G.Pkgsrc.File("."))
+       }
+
        tmp := relativePath
        tmp = strings.Replace(tmp, "${PKGSRCDIR}", pkgsrcdir, -1)
        tmp = strings.Replace(tmp, "${.CURDIR}", ".", -1)
@@ -413,6 +423,15 @@ func (mkline *MkLineImpl) ExplainRelativ
                "main pkgsrc repository.")
 }
 
+// RefTo returns a reference to another line,
+// which can be in the same file or in a different file.
+//
+// If there is a type mismatch when calling this function, try to add ".line" to
+// either the method receiver or the other line.
+func (mkline *MkLineImpl) RefTo(other MkLine) string {
+       return mkline.Line.RefTo(other.Line)
+}
+
 func matchMkDirective(text string) (m bool, indent, directive, args, comment string) {
        i, n := 0, len(text)
        if i < n && text[i] == '.' {
@@ -604,7 +623,7 @@ func (mkline *MkLineImpl) DetermineUsedV
                }
                searchIn(varname)
                for _, mod := range varuse.modifiers {
-                       searchIn(mod)
+                       searchIn(mod.Text)
                }
        }
 
@@ -774,7 +793,7 @@ func (ind *Indentation) String() string 
        s := ""
        for _, level := range ind.levels[1:] {
                s += fmt.Sprintf(" %d", level.depth)
-               if len(level.conditionalVars) != 0 {
+               if len(level.conditionalVars) > 0 {
                        s += " (" + strings.Join(level.conditionalVars, " ") + ")"
                }
        }
@@ -876,15 +895,15 @@ func (ind *Indentation) Condition() stri
        return ind.top().condition
 }
 
-func (ind *Indentation) AddCheckedFile(filename string) {
+func (ind *Indentation) AddCheckedFile(fileName string) {
        top := ind.top()
-       top.checkedFiles = append(top.checkedFiles, filename)
+       top.checkedFiles = append(top.checkedFiles, fileName)
 }
 
-func (ind *Indentation) IsCheckedFile(filename string) bool {
+func (ind *Indentation) IsCheckedFile(fileName string) bool {
        for _, level := range ind.levels {
                for _, levelFilename := range level.checkedFiles {
-                       if filename == levelFilename {
+                       if fileName == levelFilename {
                                return true
                        }
                }
@@ -964,6 +983,18 @@ func (ind *Indentation) TrackAfter(mklin
        }
 }
 
+func (ind *Indentation) CheckFinish(fileName string) {
+       if ind.Len() <= 1 {
+               return
+       }
+       eofLine := NewLineEOF(fileName)
+       for ind.Len() > 1 {
+               openingMkline := ind.levels[ind.Len()-1].mkline
+               eofLine.Errorf(".%s from %s must be closed.", openingMkline.Directive(), eofLine.RefTo(openingMkline.Line))
+               ind.Pop()
+       }
+}
+
 func MatchVarassign(text string) (m, commented bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) {
        i, n := 0, len(text)
 
@@ -1068,24 +1099,26 @@ func MatchVarassign(text string) (m, com
        return
 }
 
-func MatchMkInclude(text string) (m bool, indentation, directive, filename string) {
-       repl := G.NewPrefixReplacer(text)
-       if repl.AdvanceStr(".") {
-               if repl.AdvanceHspace() {
-                       indentation = repl.Str()
-               }
-               if repl.AdvanceStr("include") || repl.AdvanceStr("sinclude") {
-                       directive = repl.Str()
-                       repl.SkipHspace()
-                       if repl.AdvanceByte('"') {
-                               if repl.AdvanceBytesFunc(func(c byte) bool { return c != '"' }) {
-                                       filename = repl.Str()
-                                       if repl.AdvanceByte('"') {
-                                               repl.SkipHspace()
-                                               if repl.EOF() || repl.PeekByte() == '#' {
-                                                       m = true
-                                                       return
-                                               }
+func MatchMkInclude(text string) (m bool, indentation, directive, fileName string) {
+       lexer := textproc.NewLexer(text)
+       if lexer.NextString(".") != "" {
+               indentation = lexer.NextHspace()
+               directive = lexer.NextString("include")
+               if directive == "" {
+                       directive = lexer.NextString("sinclude")
+               }
+               if directive != "" {
+                       lexer.NextHspace()
+                       if lexer.NextByte('"') {
+                               // Note: strictly speaking, the full MkVarUse would have to be parsed
+                               // here. But since these usually don't contain double quotes, it has
+                               // worked fine up to now.
+                               fileName = lexer.NextBytesFunc(func(c byte) bool { return c != '"' })
+                               if fileName != "" && lexer.NextByte('"') {
+                                       lexer.NextHspace()
+                                       if lexer.EOF() || lexer.NextByte('#') {
+                                               m = true
+                                               return
                                        }
                                }
                        }
Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.39 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.40
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.39       Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Wed Nov  7 20:58:23 2018
@@ -6,7 +6,7 @@ import (
        "netbsd.org/pkglint/histogram"
        "netbsd.org/pkglint/regex"
        "netbsd.org/pkglint/textproc"
-       "netbsd.org/pkglint/trace"
+       tracePkg "netbsd.org/pkglint/trace"
        "os"
        "os/user"
        "path"
@@ -27,10 +27,10 @@ const confVersion = "@VERSION@"
 //  tracing.Out        (not thread-safe)
 //  tracing.traceDepth (not thread-safe)
 type Pkglint struct {
-       opts   CmdOpts  // Command line options.
+       Opts   CmdOpts  // Command line options.
        Pkgsrc *Pkgsrc  // Global data, mostly extracted from mk/*.
        Pkg    *Package // The package that is currently checked.
-       Mk     *MkLines // The Makefile (or fragment) that is currently checked.
+       Mk     MkLines  // The Makefile (or fragment) that is currently checked.
 
        Todo            []string // The files or directories that still need to be checked.
        Wip             bool     // Is the currently checked item from pkgsrc-wip?
@@ -38,7 +38,7 @@ type Pkglint struct {
        Testing         bool     // Is pkglint in self-testing mode (only during development)?
        CurrentUsername string   // For checking against OWNER and MAINTAINER
        CvsEntriesDir   string   // Cached to avoid I/O
-       CvsEntriesLines []Line
+       CvsEntriesLines Lines
 
        errors                int
        warnings              int
@@ -92,16 +92,16 @@ type CmdOpts struct {
        Explain,
        Autofix,
        GccOutput,
-       PrintHelp,
+       ShowHelp,
        DumpMakefile,
        Import,
        LogVerbose,
        Profiling,
        Quiet,
        Recursive,
-       PrintAutofix,
-       PrintSource,
-       PrintVersion bool
+       ShowAutofix,
+       ShowSource,
+       ShowVersion bool
 
        LogOnly []string
 
@@ -114,10 +114,12 @@ type Hash struct {
 }
 
 // G is the abbreviation for "global state";
-// it is the only global variable in this Go package
-var G = NewPkglint()
-
-var exit = os.Exit // Indirect access, to allow main() to be tested.
+// these are the only global variable in this Go package
+var (
+       G     = NewPkglint()
+       trace tracePkg.Tracer
+       exit  = os.Exit // Indirect access, to allow main() to be tested.
+)
 
 func main() {
        G.logOut = NewSeparatorWriter(os.Stdout)
@@ -132,6 +134,8 @@ func main() {
 // Note: during tests, calling this method disables tracing
 // because the command line option --debug sets trace.Tracing
 // back to false.
+//
+// It also discards the -Wall option that is used by default in other tests.
 func (pkglint *Pkglint) Main(argv ...string) (exitcode int) {
        defer func() {
                if r := recover(); r != nil {
@@ -147,10 +151,10 @@ func (pkglint *Pkglint) Main(argv ...str
                return *exitcode
        }
 
-       if pkglint.opts.Profiling {
+       if pkglint.Opts.Profiling {
                f, err := os.Create("pkglint.pprof")
                if err != nil {
-                       G.Panicf("Cannot create profiling file: %s", err)
+                       dummyLine.Fatalf("Cannot create profiling file: %s", err)
                }
                defer f.Close()
 
@@ -162,14 +166,14 @@ func (pkglint *Pkglint) Main(argv ...str
                pkglint.loaded = histogram.New()
                defer func() {
                        pkglint.logOut.Write("")
-                       pkglint.loghisto.PrintStats("loghisto", pkglint.logOut.out, -1)
+                       pkglint.loghisto.PrintStats(pkglint.logOut.out, "loghisto", -1)
                        G.res.PrintStats(pkglint.logOut.out)
-                       pkglint.loaded.PrintStats("loaded", pkglint.logOut.out, 10)
+                       pkglint.loaded.PrintStats(pkglint.logOut.out, "loaded", 10)
                        pkglint.logOut.WriteLine(fmt.Sprintf("fileCache: %d hits, %d misses", pkglint.fileCache.hits, pkglint.fileCache.misses))
                }()
        }
 
-       for _, arg := range pkglint.opts.args {
+       for _, arg := range pkglint.Opts.args {
                pkglint.Todo = append(pkglint.Todo, filepath.ToSlash(arg))
        }
        if len(pkglint.Todo) == 0 {
@@ -182,7 +186,11 @@ func (pkglint *Pkglint) Main(argv ...str
        }
        relTopdir := findPkgsrcTopdir(firstArg)
        if relTopdir == "" {
-               G.Panicf("%q is not inside a pkgsrc tree.", firstArg)
+               // If the first argument to pkglint is not inside a pkgsrc tree,
+               // pkglint doesn't know where to load the infrastructure files from,
+               // and these are needed for virtually every single check.
+               // Therefore, the only sensible thing to do is to quit immediately.
+               dummyLine.Fatalf("%q must be inside a pkgsrc tree.", firstArg)
        }
 
        pkglint.Pkgsrc = NewPkgsrc(firstArg + "/" + relTopdir)
@@ -194,14 +202,15 @@ func (pkglint *Pkglint) Main(argv ...str
                pkglint.CurrentUsername = replaceAll(currentUser.Username, `^.*\\`, "")
        }
 
-       for len(pkglint.Todo) != 0 {
+       for len(pkglint.Todo) > 0 {
                item := pkglint.Todo[0]
                pkglint.Todo = pkglint.Todo[1:]
                pkglint.CheckDirent(item)
        }
 
-       checkToplevelUnusedLicenses()
-       pkglint.PrintSummary()
+       pkglint.Pkgsrc.checkToplevelUnusedLicenses()
+
+       pkglint.ShowSummary()
        if pkglint.errors != 0 {
                return 1
        }
@@ -209,25 +218,25 @@ func (pkglint *Pkglint) Main(argv ...str
 }
 
 func (pkglint *Pkglint) ParseCommandLine(args []string) *int {
-       gopts := &pkglint.opts
+       gopts := &pkglint.Opts
        opts := getopt.NewOptions()
 
        check := opts.AddFlagGroup('C', "check", "check,...", "enable or disable specific checks")
        opts.AddFlagVar('d', "debug", &trace.Tracing, false, "log verbose call traces for debugging")
        opts.AddFlagVar('e', "explain", &gopts.Explain, false, "explain the diagnostics or give further help")
-       opts.AddFlagVar('f', "show-autofix", &gopts.PrintAutofix, false, "show what pkglint can fix automatically")
-       opts.AddFlagVar('F', "autofix", &gopts.Autofix, false, "try to automatically fix some errors (experimental)")
+       opts.AddFlagVar('f', "show-autofix", &gopts.ShowAutofix, false, "show what pkglint can fix automatically")
+       opts.AddFlagVar('F', "autofix", &gopts.Autofix, false, "try to automatically fix some errors")
        opts.AddFlagVar('g', "gcc-output-format", &gopts.GccOutput, false, "mimic the gcc output format")
-       opts.AddFlagVar('h', "help", &gopts.PrintHelp, false, "print a detailed usage message")
+       opts.AddFlagVar('h', "help", &gopts.ShowHelp, false, "show a detailed usage message")
        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('q', "quiet", &gopts.Quiet, false, "don't show a summary line when finishing")
        opts.AddFlagVar('r', "recursive", &gopts.Recursive, false, "check subdirectories, too")
-       opts.AddFlagVar('s', "source", &gopts.PrintSource, false, "show the source lines together with diagnostics")
-       opts.AddFlagVar('V', "version", &gopts.PrintVersion, false, "print the version number of pkglint")
+       opts.AddFlagVar('s', "source", &gopts.ShowSource, false, "show the source lines together with diagnostics")
+       opts.AddFlagVar('V', "version", &gopts.ShowVersion, false, "show the version number of pkglint")
        warn := opts.AddFlagGroup('W', "warning", "warning,...", "enable or disable groups of warnings")
 
        check.AddFlagVar("ALTERNATIVES", &gopts.CheckAlternatives, true, "check ALTERNATIVES files")
@@ -265,14 +274,14 @@ func (pkglint *Pkglint) ParseCommandLine
        }
        gopts.args = remainingArgs
 
-       if gopts.PrintHelp {
+       if gopts.ShowHelp {
                opts.Help(pkglint.logOut.out, "pkglint [options] dir...")
                exitcode := 0
                return &exitcode
        }
 
-       if pkglint.opts.PrintVersion {
-               fmt.Fprintf(pkglint.logOut.out, "%s\n", confVersion)
+       if pkglint.Opts.ShowVersion {
+               _, _ = fmt.Fprintf(pkglint.logOut.out, "%s\n", confVersion)
                exitcode := 0
                return &exitcode
        }
@@ -280,8 +289,8 @@ func (pkglint *Pkglint) ParseCommandLine
        return nil
 }
 
-func (pkglint *Pkglint) PrintSummary() {
-       if !pkglint.opts.Quiet && !pkglint.opts.Autofix {
+func (pkglint *Pkglint) ShowSummary() {
+       if !pkglint.Opts.Quiet && !pkglint.Opts.Autofix {
                if pkglint.errors != 0 || pkglint.warnings != 0 {
                        pkglint.logOut.Printf("%d %s and %d %s found.\n",
                                pkglint.errors, ifelseStr(pkglint.errors == 1, "error", "errors"),
@@ -289,46 +298,46 @@ func (pkglint *Pkglint) PrintSummary() {
                } else {
                        pkglint.logOut.WriteLine("Looks fine.")
                }
-               if pkglint.explanationsAvailable && !pkglint.opts.Explain {
+               if pkglint.explanationsAvailable && !pkglint.Opts.Explain {
                        pkglint.logOut.WriteLine("(Run \"pkglint -e\" to show explanations.)")
                }
-               if pkglint.autofixAvailable && !pkglint.opts.PrintAutofix {
+               if pkglint.autofixAvailable && !pkglint.Opts.ShowAutofix {
                        pkglint.logOut.WriteLine("(Run \"pkglint -fs\" to show what can be fixed automatically.)")
                }
-               if pkglint.autofixAvailable && !pkglint.opts.Autofix {
+               if pkglint.autofixAvailable && !pkglint.Opts.Autofix {
                        pkglint.logOut.WriteLine("(Run \"pkglint -F\" to automatically fix some issues.)")
                }
        }
 }
 
-func (pkglint *Pkglint) CheckDirent(fname string) {
+func (pkglint *Pkglint) CheckDirent(fileName string) {
        if trace.Tracing {
-               defer trace.Call1(fname)()
+               defer trace.Call1(fileName)()
        }
 
-       st, err := os.Lstat(fname)
+       st, err := os.Lstat(fileName)
        if err != nil || !st.Mode().IsDir() && !st.Mode().IsRegular() {
-               NewLineWhole(fname).Errorf("No such file or directory.")
+               NewLineWhole(fileName).Errorf("No such file or directory.")
                return
        }
        isDir := st.Mode().IsDir()
        isReg := st.Mode().IsRegular()
 
-       dir := ifelseStr(isReg, path.Dir(fname), fname)
+       dir := ifelseStr(isReg, path.Dir(fileName), fileName)
        pkgsrcRel := G.Pkgsrc.ToRel(dir)
        pkglint.Wip = matches(pkgsrcRel, `^wip(/|$)`)
        pkglint.Infrastructure = matches(pkgsrcRel, `^mk(/|$)`)
        pkgsrcdir := findPkgsrcTopdir(dir)
        if pkgsrcdir == "" {
-               NewLineWhole(fname).Errorf("Cannot determine the pkgsrc root directory for %q.", cleanpath(dir))
+               NewLineWhole(fileName).Errorf("Cannot determine the pkgsrc root directory for %q.", cleanpath(dir))
                return
        }
 
        switch {
-       case isDir && isEmptyDir(fname):
+       case isDir && isEmptyDir(fileName):
                return
        case isReg:
-               pkglint.Checkfile(fname)
+               pkglint.Checkfile(fileName)
                return
        }
 
@@ -340,24 +349,99 @@ func (pkglint *Pkglint) CheckDirent(fnam
        case ".":
                CheckdirToplevel(dir)
        default:
-               NewLineWhole(fname).Errorf("Cannot check directories outside a pkgsrc tree.")
+               NewLineWhole(fileName).Errorf("Cannot check directories outside a pkgsrc tree.")
        }
 }
 
-func (pkglint *Pkglint) Panicf(format string, args ...interface{}) {
-       prefix := ifelseStr(G.opts.GccOutput, llFatal.GccName, llFatal.TraditionalName)
-       pkglint.logErr.Write(prefix + ": " + fmt.Sprintf(format, args...) + "\n")
-       panic(pkglintFatal{})
+// checkdirPackage checks a complete pkgsrc package, including each
+// of the files individually, and also when seen in combination.
+func (pkglint *Pkglint) checkdirPackage(dir string) {
+       if trace.Tracing {
+               defer trace.Call1(dir)()
+       }
+
+       G.Pkg = NewPackage(dir)
+       defer func() { G.Pkg = nil }()
+       pkg := G.Pkg
+
+       // we need to handle the Makefile first to get some variables
+       mklines := pkg.loadPackageMakefile()
+       if mklines == nil {
+               return
+       }
+
+       files := dirglob(pkg.File("."))
+       if pkg.Pkgdir != "." {
+               files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
+       }
+       if G.Opts.CheckExtra {
+               files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
+       }
+       files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
+       if pkg.DistinfoFile != pkg.vars.fallback["DISTINFO_FILE"] {
+               files = append(files, pkg.File(pkg.DistinfoFile))
+       }
+
+       haveDistinfo := false
+       havePatches := false
+
+       // Determine the used variables and PLIST directories before checking any of the Makefile fragments.
+       for _, fileName := range files {
+               basename := path.Base(fileName)
+               if (hasPrefix(basename, "Makefile.") || hasSuffix(fileName, ".mk")) &&
+                       !matches(fileName, `patch-`) &&
+                       !contains(fileName, pkg.Pkgdir+"/") &&
+                       !contains(fileName, pkg.Filesdir+"/") {
+                       if fragmentMklines := LoadMk(fileName, MustSucceed); fragmentMklines != nil {
+                               fragmentMklines.DetermineUsedVariables()
+                       }
+               }
+               if hasPrefix(basename, "PLIST") {
+                       pkg.loadPlistDirs(fileName)
+               }
+       }
+
+       for _, fileName := range files {
+               if containsVarRef(fileName) {
+                       if trace.Tracing {
+                               trace.Stepf("Skipping file %q because the name contains an unresolved variable.", fileName)
+                       }
+                       continue
+               }
+
+               if path.Base(fileName) == "Makefile" {
+                       if st, err := os.Lstat(fileName); err == nil {
+                               pkglint.checkExecutable(fileName, st)
+                       }
+                       if G.Opts.CheckMakefile {
+                               pkg.checkfilePackageMakefile(fileName, mklines)
+                       }
+               } else {
+                       pkglint.Checkfile(fileName)
+               }
+               if contains(fileName, "/patches/patch-") {
+                       havePatches = true
+               } else if hasSuffix(fileName, "/distinfo") {
+                       haveDistinfo = true
+               }
+               pkg.checkLocallyModified(fileName)
+       }
+
+       if pkg.Pkgdir == "." && G.Opts.CheckDistinfo && G.Opts.CheckPatches {
+               if havePatches && !haveDistinfo {
+                       NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run %q.", bmake("makepatchsum"))
+               }
+       }
 }
 
 // Assertf checks that the condition is true. Otherwise it terminates the
 // process with a fatal error message, prefixed with "Pkglint internal error".
 //
 // This method must only be used for programming errors.
-// For runtime errors, use Panicf.
+// For runtime errors, use dummyLine.Fatalf.
 func (pkglint *Pkglint) Assertf(cond bool, format string, args ...interface{}) {
        if !cond {
-               pkglint.Panicf("Pkglint internal error: "+format, args...)
+               panic("Pkglint internal error: " + fmt.Sprintf(format, args...))
        }
 }
 
@@ -366,9 +450,9 @@ func (pkglint *Pkglint) NewPrefixReplace
 }
 
 // Returns the pkgsrc top-level directory, relative to the given file or directory.
-func findPkgsrcTopdir(fname string) string {
+func findPkgsrcTopdir(fileName string) string {
        for _, dir := range [...]string{".", "..", "../..", "../../.."} {
-               if fileExists(fname + "/" + dir + "/mk/bsd.pkg.mk") {
+               if fileExists(fileName + "/" + dir + "/mk/bsd.pkg.mk") {
                        return dir
                }
        }
@@ -413,22 +497,22 @@ func resolveVariableRefs(text string) (r
        }
 }
 
-func CheckfileExtra(fname string) {
+func CheckfileExtra(fileName string) {
        if trace.Tracing {
-               defer trace.Call1(fname)()
+               defer trace.Call1(fileName)()
        }
 
-       if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
+       if lines := Load(fileName, NotEmpty|LogErrors); lines != nil {
                ChecklinesTrailingEmptyLines(lines)
        }
 }
 
-func ChecklinesDescr(lines []Line) {
+func ChecklinesDescr(lines Lines) {
        if trace.Tracing {
-               defer trace.Call1(lines[0].Filename)()
+               defer trace.Call1(lines.FileName)()
        }
 
-       for _, line := range lines {
+       for _, line := range lines.Lines {
                CheckLineLength(line, 80)
                CheckLineTrailingWhitespace(line)
                CheckLineValidCharacters(line)
@@ -438,10 +522,10 @@ func ChecklinesDescr(lines []Line) {
        }
        ChecklinesTrailingEmptyLines(lines)
 
-       if maxlines := 24; len(lines) > maxlines {
-               line := lines[maxlines]
+       if maxLines := 24; lines.Len() > maxLines {
+               line := lines.Lines[maxLines]
 
-               line.Warnf("File too long (should be no more than %d lines).", maxlines)
+               line.Warnf("File too long (should be no more than %d lines).", maxLines)
                Explain(
                        "The DESCR file should fit on a traditional terminal of 80x25",
                        "characters.  It is also intended to give a _brief_ summary about",
@@ -451,9 +535,9 @@ func ChecklinesDescr(lines []Line) {
        SaveAutofixChanges(lines)
 }
 
-func ChecklinesMessage(lines []Line) {
+func ChecklinesMessage(lines Lines) {
        if trace.Tracing {
-               defer trace.Call1(lines[0].Filename)()
+               defer trace.Call1(lines.FileName)()
        }
 
        explanation := []string{
@@ -462,30 +546,29 @@ func ChecklinesMessage(lines []Line) {
                "empty line, your text and finally the footer line, which is the",
                "same as the header line."}
 
-       if len(lines) < 3 {
-               lastLine := lines[len(lines)-1]
-               lastLine.Warnf("File too short.")
+       if lines.Len() < 3 {
+               lines.LastLine().Warnf("File too short.")
                Explain(explanation...)
                return
        }
 
        hline := strings.Repeat("=", 75)
-       if line := lines[0]; line.Text != hline {
+       if line := lines.Lines[0]; line.Text != hline {
                fix := line.Autofix()
                fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
                fix.Explain(explanation...)
                fix.InsertBefore(hline)
                fix.Apply()
-               CheckLineRcsid(lines[0], ``, "")
-       } else if 1 < len(lines) {
-               CheckLineRcsid(lines[1], ``, "")
+               CheckLineRcsid(lines.Lines[0], ``, "")
+       } else if 1 < lines.Len() {
+               CheckLineRcsid(lines.Lines[1], ``, "")
        }
-       for _, line := range lines {
+       for _, line := range lines.Lines {
                CheckLineLength(line, 80)
                CheckLineTrailingWhitespace(line)
                CheckLineValidCharacters(line)
        }
-       if lastLine := lines[len(lines)-1]; lastLine.Text != hline {
+       if lastLine := lines.LastLine(); lastLine.Text != hline {
                fix := lastLine.Autofix()
                fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
                fix.Explain(explanation...)
@@ -497,12 +580,12 @@ func ChecklinesMessage(lines []Line) {
        SaveAutofixChanges(lines)
 }
 
-func CheckfileMk(fname string) {
+func CheckfileMk(fileName string) {
        if trace.Tracing {
-               defer trace.Call1(fname)()
+               defer trace.Call1(fileName)()
        }
 
-       mklines := LoadMk(fname, NotEmpty|LogErrors)
+       mklines := LoadMk(fileName, NotEmpty|LogErrors)
        if mklines == nil {
                return
        }
@@ -511,18 +594,18 @@ func CheckfileMk(fname string) {
        mklines.SaveAutofixChanges()
 }
 
-func (pkglint *Pkglint) Checkfile(fname string) {
+func (pkglint *Pkglint) Checkfile(fileName string) {
        if trace.Tracing {
-               defer trace.Call1(fname)()
+               defer trace.Call1(fileName)()
        }
 
-       basename := path.Base(fname)
-       pkgsrcRel := G.Pkgsrc.ToRel(fname)
+       basename := path.Base(fileName)
+       pkgsrcRel := G.Pkgsrc.ToRel(fileName)
        depth := strings.Count(pkgsrcRel, "/")
 
        if depth == 2 && !G.Wip {
                if contains(basename, "README") || contains(basename, "TODO") {
-                       NewLineWhole(fname).Errorf("Packages in main pkgsrc must not have a %s file.", basename)
+                       NewLineWhole(fileName).Errorf("Packages in main pkgsrc must not have a %s file.", basename)
                        return
                }
        }
@@ -534,142 +617,138 @@ func (pkglint *Pkglint) Checkfile(fname 
                hasSuffix(basename, ".rej"),
                contains(basename, "README") && depth == 2,
                contains(basename, "TODO") && depth == 2:
-               if pkglint.opts.Import {
-                       NewLineWhole(fname).Errorf("Must be cleaned up before committing the package.")
+               if pkglint.Opts.Import {
+                       NewLineWhole(fileName).Errorf("Must be cleaned up before committing the package.")
                }
                return
        }
 
-       st, err := os.Lstat(fname)
+       st, err := os.Lstat(fileName)
        if err != nil {
-               NewLineWhole(fname).Errorf("Cannot determine file type: %s", err)
+               NewLineWhole(fileName).Errorf("Cannot determine file type: %s", err)
                return
        }
 
-       pkglint.checkExecutable(fname, st)
-       pkglint.checkMode(fname, st.Mode())
+       pkglint.checkExecutable(fileName, st)
+       pkglint.checkMode(fileName, st.Mode())
 }
 
 // checkMode checks a directory entry based on its file name and its mode
 // (regular file, directory, symlink).
-func (pkglint *Pkglint) checkMode(fname string, mode os.FileMode) {
-       basename := path.Base(fname)
+func (pkglint *Pkglint) checkMode(fileName string, mode os.FileMode) {
+       basename := path.Base(fileName)
        switch {
        case mode.IsDir():
                switch {
                case basename == "files" || basename == "patches" || isIgnoredFilename(basename):
                        // Ok
-               case matches(fname, `(?:^|/)files/[^/]*$`):
+               case matches(fileName, `(?:^|/)files/[^/]*$`):
                        // Ok
-               case !isEmptyDir(fname):
-                       NewLineWhole(fname).Warnf("Unknown directory name.")
+               case !isEmptyDir(fileName):
+                       NewLineWhole(fileName).Warnf("Unknown directory name.")
                }
 
        case mode&os.ModeSymlink != 0:
                if !hasPrefix(basename, "work") {
-                       NewLineWhole(fname).Warnf("Unknown symlink name.")
+                       NewLineWhole(fileName).Warnf("Unknown symlink name.")
                }
 
        case !mode.IsRegular():
-               NewLineWhole(fname).Errorf("Only files and directories are allowed in pkgsrc.")
+               NewLineWhole(fileName).Errorf("Only files and directories are allowed in pkgsrc.")
 
        case basename == "ALTERNATIVES":
-               if pkglint.opts.CheckAlternatives {
-                       var plistFiles map[string]bool
-                       if G.Pkg != nil {
-                               plistFiles = G.Pkg.PlistFiles
-                       }
-                       CheckfileAlternatives(fname, plistFiles)
+               if pkglint.Opts.CheckAlternatives {
+                       CheckfileAlternatives(fileName)
                }
 
        case basename == "buildlink3.mk":
-               if pkglint.opts.CheckBuildlink3 {
-                       if mklines := LoadMk(fname, NotEmpty|LogErrors); mklines != nil {
+               if pkglint.Opts.CheckBuildlink3 {
+                       if mklines := LoadMk(fileName, NotEmpty|LogErrors); mklines != nil {
                                ChecklinesBuildlink3Mk(mklines)
                        }
                }
 
        case hasPrefix(basename, "DESCR"):
-               if pkglint.opts.CheckDescr {
-                       if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
+               if pkglint.Opts.CheckDescr {
+                       if lines := Load(fileName, NotEmpty|LogErrors); lines != nil {
                                ChecklinesDescr(lines)
                        }
                }
 
        case basename == "distinfo":
-               if pkglint.opts.CheckDistinfo {
-                       if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
+               if pkglint.Opts.CheckDistinfo {
+                       if lines := Load(fileName, NotEmpty|LogErrors); lines != nil {
                                ChecklinesDistinfo(lines)
                        }
                }
 
        case basename == "DEINSTALL" || basename == "INSTALL":
-               if pkglint.opts.CheckInstall {
-                       CheckfileExtra(fname)
+               if pkglint.Opts.CheckInstall {
+                       CheckfileExtra(fileName)
                }
 
        case hasPrefix(basename, "MESSAGE"):
-               if pkglint.opts.CheckMessage {
-                       if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
+               if pkglint.Opts.CheckMessage {
+                       if lines := Load(fileName, NotEmpty|LogErrors); lines != nil {
                                ChecklinesMessage(lines)
                        }
                }
 
        case basename == "options.mk":
-               if pkglint.opts.CheckOptions {
-                       if mklines := LoadMk(fname, NotEmpty|LogErrors); mklines != nil {
+               if pkglint.Opts.CheckOptions {
+                       if mklines := LoadMk(fileName, NotEmpty|LogErrors); mklines != nil {
                                ChecklinesOptionsMk(mklines)
                        }
                }
 
        case matches(basename, `^patch-[-A-Za-z0-9_.~+]*[A-Za-z0-9_]$`):
-               if pkglint.opts.CheckPatches {
-                       if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
+               if pkglint.Opts.CheckPatches {
+                       if lines := Load(fileName, NotEmpty|LogErrors); lines != nil {
                                ChecklinesPatch(lines)
                        }
                }
 
-       case matches(fname, `(?:^|/)patches/manual[^/]*$`):
+       case matches(fileName, `(?:^|/)patches/manual[^/]*$`):
                if trace.Tracing {
-                       trace.Step1("Unchecked file %q.", fname)
+                       trace.Step1("Unchecked file %q.", fileName)
                }
 
-       case matches(fname, `(?:^|/)patches/[^/]*$`):
-               NewLineWhole(fname).Warnf("Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.")
+       case matches(fileName, `(?:^|/)patches/[^/]*$`):
+               NewLineWhole(fileName).Warnf("Patch files should be named \"patch-\", followed by letters, '-', '_', '.', and digits only.")
 
-       case matches(basename, `^(?:.*\.mk|Makefile.*)$`) && !matches(fname, `files/`) && !matches(fname, `patches/`):
-               if pkglint.opts.CheckMk {
-                       CheckfileMk(fname)
+       case matches(basename, `^(?:.*\.mk|Makefile.*)$`) && !matches(fileName, `files/`) && !matches(fileName, `patches/`):
+               if pkglint.Opts.CheckMk {
+                       CheckfileMk(fileName)
                }
 
        case hasPrefix(basename, "PLIST"):
-               if pkglint.opts.CheckPlist {
-                       if lines := Load(fname, NotEmpty|LogErrors); lines != nil {
+               if pkglint.Opts.CheckPlist {
+                       if lines := Load(fileName, NotEmpty|LogErrors); lines != nil {
                                ChecklinesPlist(lines)
                        }
                }
 
        case hasPrefix(basename, "CHANGES-"):
-               // This only checks the file, but doesn't register the changes globally.
-               _ = pkglint.Pkgsrc.loadDocChangesFromFile(fname)
+               // This only checks the file but doesn't register the changes globally.
+               _ = pkglint.Pkgsrc.loadDocChangesFromFile(fileName)
 
-       case matches(fname, `(?:^|/)files/[^/]*$`):
+       case matches(fileName, `(?:^|/)files/[^/]*$`):
                // Skip
 
        case basename == "spec":
-               if !hasPrefix(G.Pkgsrc.ToRel(fname), "regress/") {
-                       NewLineWhole(fname).Warnf("Only packages in regress/ may have spec files.")
+               if !hasPrefix(G.Pkgsrc.ToRel(fileName), "regress/") {
+                       NewLineWhole(fileName).Warnf("Only packages in regress/ may have spec files.")
                }
 
        default:
-               NewLineWhole(fname).Warnf("Unexpected file found.")
-               if pkglint.opts.CheckExtra {
-                       CheckfileExtra(fname)
+               NewLineWhole(fileName).Warnf("Unexpected file found.")
+               if pkglint.Opts.CheckExtra {
+                       CheckfileExtra(fileName)
                }
        }
 }
 
-func (pkglint *Pkglint) checkExecutable(fname string, st os.FileInfo) {
+func (pkglint *Pkglint) checkExecutable(fileName string, st os.FileInfo) {
        switch {
        case !st.Mode().IsRegular():
                // Directories and other entries may be executable.
@@ -677,13 +756,13 @@ func (pkglint *Pkglint) checkExecutable(
        case st.Mode().Perm()&0111 == 0:
                // Good.
 
-       case isCommitted(fname):
+       case isCommitted(fileName):
                // Too late to be fixed by the package developer, since
                // CVS remembers the executable bit in the repo file.
                // At this point, it can only be reset by the CVS admins.
 
        default:
-               line := NewLine(fname, 0, "", nil)
+               line := NewLine(fileName, 0, "", nil)
                fix := line.Autofix()
                fix.Warnf("Should not be executable.")
                fix.Explain(
@@ -691,10 +770,10 @@ func (pkglint *Pkglint) checkExecutable(
                        "DEINSTALL scripts are usually not usable in the form they have in",
                        "the package, as the pathnames get adjusted during installation.",
                        "So there is no need to have any file executable.")
-               fix.Custom(func(printAutofix, autofix bool) {
+               fix.Custom(func(showAutofix, autofix bool) {
                        fix.Describef(0, "Clearing executable bits")
                        if autofix {
-                               if err := os.Chmod(fname, st.Mode()&^0111); err != nil {
+                               if err := os.Chmod(fileName, st.Mode()&^0111); err != nil {
                                        line.Errorf("Cannot clear executable bits: %s", err)
                                }
                        }
@@ -703,14 +782,16 @@ func (pkglint *Pkglint) checkExecutable(
        }
 }
 
-func ChecklinesTrailingEmptyLines(lines []Line) {
-       max := len(lines)
+func ChecklinesTrailingEmptyLines(lines Lines) {
+       // XXX: Maybe move to LinesChecker if there are enough similar functions.
+
+       max := lines.Len()
        last := max
-       for last > 1 && lines[last-1].Text == "" {
+       for last > 1 && lines.Lines[last-1].Text == "" {
                last--
        }
        if last != max {
-               lines[last].Notef("Trailing empty lines.")
+               lines.Lines[last].Notef("Trailing empty lines.")
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.43 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.44
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.43   Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Wed Nov  7 20:58:23 2018
@@ -2,118 +2,6 @@ package main
 
 import "gopkg.in/check.v1"
 
-func (s *Suite) Test_VaralignBlock_Check__autofix(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wspace", "--show-autofix")
-
-       lines := t.NewLines("file.mk",
-               "VAR=   value",    // Indentation 7, fixed to 8.
-               "",                //
-               "VAR=    value",   // Indentation 8, fixed to 8.
-               "",                //
-               "VAR=     value",  // Indentation 9, fixed to 8.
-               "",                //
-               "VAR= \tvalue",    // Mixed indentation 8, fixed to 8.
-               "",                //
-               "VAR=   \tvalue",  // Mixed indentation 8, fixed to 8.
-               "",                //
-               "VAR=    \tvalue", // Mixed indentation 16, fixed to 16.
-               "",                //
-               "VAR=\tvalue")     // Already aligned with tabs only, left unchanged.
-
-       varalign := &VaralignBlock{}
-       for _, line := range lines {
-               varalign.Check(NewMkLine(line))
-       }
-       varalign.Finish()
-
-       t.CheckOutputLines(
-               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 9.",
-               "AUTOFIX: file.mk:1: Replacing \"   \" with \"\\t\".",
-               "NOTE: file.mk:3: Variable values should be aligned with tabs, not spaces.",
-               "AUTOFIX: file.mk:3: Replacing \"    \" with \"\\t\".",
-               "NOTE: file.mk:5: This variable value should be aligned with tabs, not spaces, to column 9.",
-               "AUTOFIX: file.mk:5: Replacing \"     \" with \"\\t\".",
-               "NOTE: file.mk:7: Variable values should be aligned with tabs, not spaces.",
-               "AUTOFIX: file.mk:7: Replacing \" \\t\" with \"\\t\".",
-               "NOTE: file.mk:9: Variable values should be aligned with tabs, not spaces.",
-               "AUTOFIX: file.mk:9: Replacing \"   \\t\" with \"\\t\".",
-               "NOTE: file.mk:11: Variable values should be aligned with tabs, not spaces.",
-               "AUTOFIX: file.mk:11: Replacing \"    \\t\" with \"\\t\\t\".")
-}
-
-func (s *Suite) Test_VaralignBlock_Check__reduce_indentation(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wspace")
-       mklines := t.NewMkLines("file.mk",
-               "VAR= \tvalue",
-               "VAR=    \tvalue",
-               "VAR=\t\t\t\tvalue",
-               "",
-               "VAR=\t\t\tneedlessly", // Nothing to be fixed here, since it looks good.
-               "VAR=\t\t\tdeep",
-               "VAR=\t\t\tindentation")
-
-       varalign := new(VaralignBlock)
-       for _, mkline := range mklines.mklines {
-               varalign.Check(mkline)
-       }
-       varalign.Finish()
-
-       t.CheckOutputLines(
-               "NOTE: file.mk:1: Variable values should be aligned with tabs, not spaces.",
-               "NOTE: file.mk:2: This variable value should be aligned with tabs, not spaces, to column 9.",
-               "NOTE: file.mk:3: This variable value should be aligned to column 9.")
-}
-
-func (s *Suite) Test_VaralignBlock_Check__longest_line_no_space(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wspace")
-       mklines := t.NewMkLines("file.mk",
-               "SUBST_CLASSES+= aaaaaaaa",
-               "SUBST_STAGE.aaaaaaaa= pre-configure",
-               "SUBST_FILES.aaaaaaaa= *.pl",
-               "SUBST_FILTER_CMD.aaaaaaaa=cat")
-
-       varalign := new(VaralignBlock)
-       for _, mkline := range mklines.mklines {
-               varalign.Check(mkline)
-       }
-       varalign.Finish()
-
-       t.CheckOutputLines(
-               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 33.",
-               "NOTE: file.mk:2: This variable value should be aligned with tabs, not spaces, to column 33.",
-               "NOTE: file.mk:3: This variable value should be aligned with tabs, not spaces, to column 33.",
-               "NOTE: file.mk:4: This variable value should be aligned to column 33.")
-}
-
-func (s *Suite) Test_VaralignBlock_Check__only_spaces(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wspace")
-       mklines := t.NewMkLines("file.mk",
-               "SUBST_CLASSES+= aaaaaaaa",
-               "SUBST_STAGE.aaaaaaaa= pre-configure",
-               "SUBST_FILES.aaaaaaaa= *.pl",
-               "SUBST_FILTER_CMD.aaaaaaaa= cat")
-
-       varalign := new(VaralignBlock)
-       for _, mkline := range mklines.mklines {
-               varalign.Check(mkline)
-       }
-       varalign.Finish()
-
-       t.CheckOutputLines(
-               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 33.",
-               "NOTE: file.mk:2: This variable value should be aligned with tabs, not spaces, to column 33.",
-               "NOTE: file.mk:3: This variable value should be aligned with tabs, not spaces, to column 33.",
-               "NOTE: file.mk:4: This variable value should be aligned with tabs, not spaces, to column 33.")
-}
-
 func (s *Suite) Test_NewMkLine(c *check.C) {
        t := s.Init(c)
 
@@ -190,14 +78,14 @@ func (s *Suite) Test_NewMkLine__autofix_
        t := s.Init(c)
 
        t.SetupCommandLine("-Wspace")
-       fname := t.CreateFileLines("Makefile",
+       fileName := t.CreateFileLines("Makefile",
                MkRcsID,
                "VARNAME +=\t${VARNAME}",
                "VARNAME+ =\t${VARNAME+}",
                "VARNAME+ +=\t${VARNAME+}",
                "pkgbase := pkglint")
 
-       CheckfileMk(fname)
+       CheckfileMk(fileName)
 
        t.CheckOutputLines(
                "WARN: ~/Makefile:2: Unnecessary space after variable name \"VARNAME\".",
@@ -205,7 +93,7 @@ func (s *Suite) Test_NewMkLine__autofix_
 
        t.SetupCommandLine("-Wspace", "--autofix")
 
-       CheckfileMk(fname)
+       CheckfileMk(fileName)
 
        t.CheckOutputLines(
                "AUTOFIX: ~/Makefile:2: Replacing \"VARNAME +=\" with \"VARNAME+=\".",
@@ -230,23 +118,6 @@ func (s *Suite) Test_MkLine_Cond(c *chec
        c.Check(mkline.Cond(), equals, cond)
 }
 
-// Guessing the variable type works for both plain and parameterized variable names.
-func (s *Suite) Test_Pkgsrc_VariableType__varparam(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupVartypes()
-
-       t1 := G.Pkgsrc.VariableType("FONT_DIRS")
-
-       c.Assert(t1, check.NotNil)
-       c.Check(t1.String(), equals, "ShellList of Pathmask (guessed)")
-
-       t2 := G.Pkgsrc.VariableType("FONT_DIRS.ttf")
-
-       c.Assert(t2, check.NotNil)
-       c.Check(t2.String(), equals, "ShellList of Pathmask (guessed)")
-}
-
 func (s *Suite) Test_VarUseContext_String(c *check.C) {
        t := s.Init(c)
 
@@ -263,26 +134,26 @@ func (s *Suite) Test_VarUseContext_Strin
 func (s *Suite) Test_NewMkLine__number_sign(c *check.C) {
        t := s.Init(c)
 
-       mklineVarassignEscaped := t.NewMkLine("fname", 1, "SED_CMD=\t's,\\#,hash,g'")
+       mklineVarassignEscaped := t.NewMkLine("fileName", 1, "SED_CMD=\t's,\\#,hash,g'")
 
        c.Check(mklineVarassignEscaped.Varname(), equals, "SED_CMD")
        c.Check(mklineVarassignEscaped.Value(), equals, "'s,#,hash,g'")
 
-       mklineCommandEscaped := t.NewMkLine("fname", 1, "\tsed -e 's,\\#,hash,g'")
+       mklineCommandEscaped := t.NewMkLine("fileName", 1, "\tsed -e 's,\\#,hash,g'")
 
        c.Check(mklineCommandEscaped.ShellCommand(), equals, "sed -e 's,\\#,hash,g'")
 
        // From shells/zsh/Makefile.common, rev. 1.78
-       mklineCommandUnescaped := t.NewMkLine("fname", 1, "\t# $ sha1 patches/patch-ac")
+       mklineCommandUnescaped := t.NewMkLine("fileName", 1, "\t# $ sha1 patches/patch-ac")
 
        c.Check(mklineCommandUnescaped.ShellCommand(), equals, "# $ sha1 patches/patch-ac")
        t.CheckOutputEmpty() // No warning about parsing the lonely dollar sign.
 
-       mklineVarassignUnescaped := t.NewMkLine("fname", 1, "SED_CMD=\t's,#,hash,'")
+       mklineVarassignUnescaped := t.NewMkLine("fileName", 1, "SED_CMD=\t's,#,hash,'")
 
        c.Check(mklineVarassignUnescaped.Value(), equals, "'s,")
        t.CheckOutputLines(
-               "WARN: fname:1: The # character starts a comment.")
+               "WARN: fileName:1: The # character starts a comment.")
 }
 
 func (s *Suite) Test_NewMkLine__leading_space(c *check.C) {
@@ -328,42 +199,14 @@ func (s *Suite) Test_NewMkLine__infrastr
        t.CheckOutputLines(
                "WARN: infra.mk:2: USE_BUILTIN.${_pkg_:S/^-//} is defined but not used.",
                "ERROR: infra.mk:5: \".export\" requires arguments.",
+               "NOTE: infra.mk:2: This variable value should be aligned to column 41.",
                "ERROR: infra.mk:10: Unmatched .endif.")
 }
 
-func (s *Suite) Test_MkLines_Check__extra(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wextra")
-       t.SetupVartypes()
-       G.Pkg = NewPackage(t.File("category/pkgbase"))
-       G.Mk = t.NewMkLines("options.mk",
-               MkRcsID,
-               ".for word in ${PKG_FAIL_REASON}",
-               "PYTHON_VERSIONS_ACCEPTED=\t27 35 30",
-               "CONFIGURE_ARGS+=\t--sharedir=${PREFIX}/share/kde",
-               "COMMENT=\t# defined",
-               ".endfor",
-               "GAMES_USER?=pkggames",
-               "GAMES_GROUP?=pkggames",
-               "PLIST_SUBST+= CONDITIONAL=${CONDITIONAL}",
-               "CONDITIONAL=\"@comment\"",
-               "BUILD_DIRS=\t${WRKSRC}/../build")
-
-       G.Mk.Check()
-
-       t.CheckOutputLines(
-               "WARN: options.mk:3: The values for PYTHON_VERSIONS_ACCEPTED should be in decreasing order.",
-               "NOTE: options.mk:5: Please use \"# empty\", \"# none\" or \"yes\" instead of \"# defined\".",
-               "WARN: options.mk:7: Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".",
-               "WARN: options.mk:11: Building the package should take place entirely inside ${WRKSRC}, not \"${WRKSRC}/..\".",
-               "NOTE: options.mk:11: You can use \"../build\" instead of \"${WRKSRC}/../build\".")
-}
-
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__unknown_rhs(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine("fname", 1, "PKGNAME := ${UNKNOWN}")
+       mkline := t.NewMkLine("fileName", 1, "PKGNAME:= ${UNKNOWN}")
        t.SetupVartypes()
 
        vuc := &VarUseContext{G.Pkgsrc.vartypes["PKGNAME"], vucTimeParse, vucQuotUnknown, false}
@@ -375,7 +218,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__append_URL_to_list_of_URLs(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
        mkline := t.NewMkLine("Makefile", 95, "MASTER_SITES=\t${HOMEPAGE}")
@@ -393,7 +235,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__append_list_to_list(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupMasterSite("MASTER_SITE_SOURCEFORGE", "http://downloads.sourceforge.net/sourceforge/";)
        mkline := t.NewMkLine("Makefile", 96, "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=squirrel-sql/}")
@@ -407,7 +248,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__eval_shell(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mkline := t.NewMkLine("builtin.mk", 3, "USE_BUILTIN.Xfixes!=\t${PKG_ADMIN} pmatch 'pkg-[0-9]*' ${BUILTIN_PKG.Xfixes:Q}")
 
@@ -421,7 +261,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__command_in_single_quotes(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mkline := t.NewMkLine("Makefile", 3, "SUBST_SED.hpath=\t-e 's|^\\(INSTALL[\t:]*=\\).*|\\1${INSTALL}|'")
 
@@ -434,7 +273,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__command_in_command(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("find", "FIND", AtRunTime)
        t.SetupTool("sort", "SORT", AtRunTime)
@@ -453,7 +291,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__word_as_part_of_word(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
@@ -464,16 +301,15 @@ func (s *Suite) Test_MkLine_VariableNeed
        t.CheckOutputEmpty()
 }
 
-// As an argument to ${ECHO}, the :Q modifier should be used, but pkglint
-// currently does not know all shell commands and how they handle their
-// arguments. As an argument to xargs(1), the :Q modifier would be misplaced,
-// therefore no warning is issued in both these cases.
+// As an argument to ${ECHO}, the :Q modifier should be used, but as of
+// October 2018, pkglint does not know all shell commands and how they
+// handle their arguments. As an argument to xargs(1), the :Q modifier
+// would be misplaced, therefore no warning is issued in both these cases.
 //
 // Based on graphics/circos/Makefile.
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__command_as_command_argument(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupTool("perl", "PERL5", AtRunTime)
        t.SetupTool("bash", "BASH", AtRunTime)
        t.SetupVartypes()
@@ -493,7 +329,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__URL_as_part_of_word_in_list(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
@@ -512,7 +347,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__command_in_subshell(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("awk", "AWK", AtRunTime)
        t.SetupTool("echo", "ECHO", AtRunTime)
@@ -535,7 +369,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__LDFLAGS_in_single_quotes(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/mlterm/Makefile",
                MkRcsID,
@@ -558,7 +391,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__package_options(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("Makefile",
                MkRcsID,
@@ -569,32 +401,9 @@ func (s *Suite) Test_MkLine_VariableNeed
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_MkLines_Check__MASTER_SITE_in_HOMEPAGE(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wall")
-       t.SetupMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
-       t.SetupVartypes()
-       G.Mk = t.NewMkLines("devel/catch/Makefile",
-               MkRcsID,
-               "HOMEPAGE=\t${MASTER_SITE_GITHUB:=philsquared/Catch/}",
-               "HOMEPAGE=\t${MASTER_SITE_GITHUB}",
-               "HOMEPAGE=\t${MASTER_SITES}",
-               "HOMEPAGE=\t${MASTER_SITES}${GITHUB_PROJECT}")
-
-       G.Mk.Check()
-
-       t.CheckOutputLines(
-               "WARN: devel/catch/Makefile:2: HOMEPAGE should not be defined in terms of MASTER_SITEs. Use https://github.com/philsquared/Catch/ directly.",
-               "WARN: devel/catch/Makefile:3: HOMEPAGE should not be defined in terms of MASTER_SITEs. Use https://github.com/ directly.",
-               "WARN: devel/catch/Makefile:4: HOMEPAGE should not be defined in terms of MASTER_SITEs.",
-               "WARN: devel/catch/Makefile:5: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
-}
-
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__tool_in_quotes_in_subshell_in_shellwords(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupTool("echo", "ECHO", AtRunTime)
        t.SetupTool("sh", "SH", AtRunTime)
        t.SetupVartypes()
@@ -611,7 +420,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__LDADD_in_BUILDLINK_TRANSFORM(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/qt5-qtbase/Makefile.common",
                "BUILDLINK_TRANSFORM+=opt:-ldl:${BUILDLINK_LDADD.dl:M*}")
@@ -626,7 +434,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__command_in_message(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("benchmarks/iozone/Makefile",
                "SUBST_MESSAGE.crlf=\tStripping EOL CR in ${REPLACE_PERL}")
@@ -640,7 +447,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__guessed_list_variable_in_quotes(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("audio/jack-rack/Makefile",
                MkRcsID,
@@ -656,7 +462,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__list_in_list(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/eterm/Makefile",
                MkRcsID,
@@ -672,7 +477,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__PKGNAME_and_URL_list_in_URL_list(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupMasterSite("MASTER_SITE_GNOME", "http://ftp.gnome.org/";)
        t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/gtk3/Makefile",
@@ -687,7 +491,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__tool_in_CONFIGURE_ENV(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("tar", "TAR", AtRunTime)
        mklines := t.NewMkLines("Makefile",
@@ -708,7 +511,6 @@ func (s *Suite) Test_MkLine_VariableNeed
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__backticks(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("cat", "CAT", AtRunTime)
        mklines := t.NewMkLines("Makefile",
@@ -824,61 +626,9 @@ func (s *Suite) Test_MkLine_VariableNeed
                "WARN: ~/Makefile:6: PATH should not be evaluated at load time.")
 }
 
-func (s *Suite) Test_ShellLine_CheckWord__PKGMANDIR(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wall")
-       t.SetupVartypes()
-       G.Mk = t.NewMkLines("chat/ircII/Makefile",
-               MkRcsID,
-               "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/man",
-               "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/${PKGMANDIR}")
-
-       G.Mk.Check()
-
-       t.CheckOutputLines(
-               "WARN: chat/ircII/Makefile:2: Please use ${PKGMANDIR} instead of \"man\".",
-               "NOTE: chat/ircII/Makefile:2: This variable value should be aligned to column 25.",
-               "NOTE: chat/ircII/Makefile:3: This variable value should be aligned to column 25.")
-}
-
-func (s *Suite) Test_MkLines_Check__VERSION_as_wordpart_in_MASTER_SITES(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wall")
-       t.SetupVartypes()
-       mklines := t.NewMkLines("geography/viking/Makefile",
-               MkRcsID,
-               "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=viking/}${VERSION}/")
-
-       mklines.Check()
-
-       t.CheckOutputLines(
-               "WARN: geography/viking/Makefile:2: "+
-                       "The list variable MASTER_SITE_SOURCEFORGE should not be embedded in a word.",
-               "WARN: geography/viking/Makefile:2: VERSION is used but not defined.")
-}
-
-func (s *Suite) Test_MkLines_Check__shell_command_as_wordpart_in_ENV_list(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wall")
-       t.SetupVartypes()
-       mklines := t.NewMkLines("x11/lablgtk1/Makefile",
-               MkRcsID,
-               "CONFIGURE_ENV+=\tCC=${CC}")
-
-       mklines.Check()
-
-       t.CheckOutputLines(
-               "WARN: x11/lablgtk1/Makefile:2: Please use ${CC:Q} instead of ${CC}.",
-               "WARN: x11/lablgtk1/Makefile:2: Please use ${CC:Q} instead of ${CC}.")
-}
-
 func (s *Suite) Test_MkLine__shell_varuse_in_backt_dquot(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("grep", "GREP", AtRunTime)
        mklines := t.NewMkLines("x11/motif/Makefile",
@@ -892,35 +642,6 @@ func (s *Suite) Test_MkLine__shell_varus
        t.CheckOutputEmpty()
 }
 
-// See PR 46570, Ctrl+F "3. In lang/perl5".
-func (s *Suite) Test_Pkgsrc_VariableType(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupVartypes()
-
-       checkType := func(varname string, vartype string) {
-               actualType := G.Pkgsrc.VariableType(varname)
-               if vartype == "" {
-                       c.Check(actualType, check.IsNil)
-               } else {
-                       if c.Check(actualType, check.NotNil) {
-                               c.Check(actualType.String(), equals, vartype)
-                       }
-               }
-       }
-
-       checkType("_PERL5_PACKLIST_AWK_STRIP_DESTDIR", "")
-       checkType("SOME_DIR", "Pathname (guessed)")
-       checkType("SOMEDIR", "Pathname (guessed)")
-       checkType("SEARCHPATHS", "ShellList of Pathname (guessed)")
-       checkType("MYPACKAGE_USER", "UserGroupName (guessed)")
-       checkType("MYPACKAGE_GROUP", "UserGroupName (guessed)")
-       checkType("MY_CMD_ENV", "ShellList of ShellWord (guessed)")
-       checkType("MY_CMD_ARGS", "ShellList of ShellWord (guessed)")
-       checkType("MY_CMD_CFLAGS", "ShellList of CFlag (guessed)")
-       checkType("PLIST.abcde", "Yes")
-}
-
 // PR 51696, security/py-pbkdf2/Makefile, r1.2
 func (s *Suite) Test_MkLine__comment_in_comment(c *check.C) {
        t := s.Init(c)
@@ -980,14 +701,17 @@ func (s *Suite) Test_MkLine_ValueSplit(c
 func (s *Suite) Test_MkLine_ResolveVarsInRelativePath(c *check.C) {
        t := s.Init(c)
 
-       checkResolve := func(before string, after string) {
-               c.Check(dummyMkLine.ResolveVarsInRelativePath(before, false), equals, after)
-       }
-
        t.CreateFileLines("lang/lua53/Makefile")
        t.CreateFileLines("lang/php72/Makefile")
        t.CreateFileLines("emulators/suse100_base/Makefile")
        t.CreateFileLines("lang/python36/Makefile")
+       mklines := t.SetupFileMkLines("Makefile",
+               MkRcsID)
+       mkline := mklines.mklines[0]
+
+       checkResolve := func(before string, after string) {
+               c.Check(mkline.ResolveVarsInRelativePath(before, false), equals, after)
+       }
 
        checkResolve("", "")
        checkResolve("${LUA_PKGSRCDIR}", "../../lang/lua53")
@@ -1004,6 +728,20 @@ func (s *Suite) Test_MkLine_ResolveVarsI
        checkResolve("${PKGDIR}", ".")
 }
 
+func (s *Suite) Test_MkLine_ResolveVarsInRelativePath__directory_depth(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       mklines := t.SetupFileMkLines("multimedia/totem/bla.mk",
+               MkRcsID,
+               "BUILDLINK_PKGSRCDIR.totem?=\t../../multimedia/totem")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "ERROR: ~/multimedia/totem/bla.mk:2: There is no package in \"multimedia/totem\".")
+}
+
 func (s *Suite) Test_MatchVarassign(c *check.C) {
        s.Init(c)
 
@@ -1024,7 +762,7 @@ func (s *Suite) Test_MatchVarassign(c *c
        checkNotVarassign := func(text string) {
                m, _, _, _, _, _, _, _, _ := MatchVarassign(text)
                if m {
-                       c.Errorf("Text %q matches variable assignment, but shouldn't.", text)
+                       c.Errorf("Text %q matches variable assignment but shouldn't.", text)
                }
        }
 
@@ -1043,6 +781,7 @@ func (s *Suite) Test_MatchVarassign(c *c
        checkNotVarassign("?=value")
        checkNotVarassign("<=value")
        checkNotVarassign("#")
+       checkNotVarassign("VAR.$$=value")
 
        // A single space is typically used for writing documentation,
        // not for commenting out code.

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.21 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.22
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.21 Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Wed Nov  7 20:58:23 2018
@@ -1,9 +1,7 @@
 package main
 
 import (
-       "fmt"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "os"
        "path"
        "strconv"
@@ -27,7 +25,7 @@ func (ck MkLineChecker) Check() {
        case mkline.IsShellCommand():
                shellCommand := mkline.ShellCommand()
 
-               if G.opts.WarnSpace && hasPrefix(mkline.Text, "\t\t") {
+               if G.Opts.WarnSpace && hasPrefix(mkline.Text, "\t\t") {
                        fix := mkline.Autofix()
                        fix.Notef("Shell programs should be indented with a single tab.")
                        fix.Explain(
@@ -65,7 +63,7 @@ func (ck MkLineChecker) checkInclude() {
        includefile := mkline.IncludeFile()
        mustExist := mkline.MustExist()
        if trace.Tracing {
-               trace.Step2("includingFile=%s includefile=%s", mkline.Filename, includefile)
+               trace.Step2("includingFile=%s includefile=%s", mkline.FileName, includefile)
        }
        ck.CheckRelativePath(includefile, mustExist)
 
@@ -173,7 +171,7 @@ func (ck MkLineChecker) checkDirectiveFo
        mkline := ck.MkLine
        args := mkline.Args()
 
-       if m, vars, values := match2(args, `^(\S+(?:\s*\S+)*?)\s+in\s+(.*)$`); m {
+       if m, vars, values := match2(args, `^([^\t ]+(?:[\t ]*[^\t ]+)*?)[\t ]+in[\t ]+(.*)$`); m {
                for _, forvar := range fields(vars) {
                        indentation.AddVar(forvar)
                        if !G.Infrastructure && hasPrefix(forvar, "_") {
@@ -211,7 +209,7 @@ func (ck MkLineChecker) checkDirectiveFo
 }
 
 func (ck MkLineChecker) checkDirectiveIndentation(expectedDepth int) {
-       if G.Mk == nil || !G.opts.WarnSpace {
+       if G.Mk == nil || !G.Opts.WarnSpace {
                return
        }
        mkline := ck.MkLine
@@ -259,7 +257,7 @@ func (ck MkLineChecker) checkDependencyR
                                "In the rare case that you actually want a file-based make(1)",
                                "target, write it like this:",
                                "",
-                               "\t${.CURDIR}/my-filename:")
+                               "\t${.CURDIR}/my-file:")
                }
        }
 }
@@ -269,7 +267,7 @@ func (ck MkLineChecker) checkDependencyR
 //
 // See checkVarusePermissions.
 func (ck MkLineChecker) checkVarassignPermissions() {
-       if !G.opts.WarnPerm || G.Infrastructure {
+       if !G.Opts.WarnPerm || G.Infrastructure {
                return
        }
        if trace.Tracing {
@@ -352,12 +350,15 @@ func (ck MkLineChecker) CheckVaruse(varu
        varname := varuse.varname
        vartype := G.Pkgsrc.VariableType(varname)
        switch {
-       case !G.opts.WarnExtra:
+       case !G.Opts.WarnExtra:
        case vartype != nil && !vartype.guessed:
                // Well-known variables are probably defined by the infrastructure.
        case varIsDefinedSimilar(varname):
        case containsVarRef(varname):
+       case G.Pkgsrc.vartypes[varname] != nil:
+       case G.Pkgsrc.vartypes[varnameCanon(varname)] != nil:
        case G.Mk != nil && !G.Mk.FirstTime("used but not defined: "+varname):
+
        default:
                mkline.Warnf("%s is used but not defined.", varname)
        }
@@ -379,7 +380,7 @@ func (ck MkLineChecker) CheckVaruse(varu
 
        needsQuoting := mkline.VariableNeedsQuoting(varname, vartype, vuc)
 
-       if G.opts.WarnQuoting && vuc.quoting != vucQuotUnknown && needsQuoting != nqDontKnow {
+       if G.Opts.WarnQuoting && vuc.quoting != vucQuotUnknown && needsQuoting != nqDontKnow {
                ck.CheckVaruseShellword(varname, vartype, vuc, varuse.Mod(), needsQuoting)
        }
 
@@ -400,7 +401,7 @@ func (ck MkLineChecker) checkVaruseMod(v
                return
        }
 
-       if hasPrefix(mods[0], "=") && vartype != nil && !vartype.IsConsideredList() {
+       if mods[0].IsSuffixSubst() && vartype != nil && !vartype.IsConsideredList() {
                ck.MkLine.Warnf("The :from=to modifier should only be used with lists, not with %s.", varuse.varname)
                Explain(
                        "Instead of:",
@@ -413,9 +414,10 @@ func (ck MkLineChecker) checkVaruseMod(v
        }
 
        if len(mods) == 3 {
-               if m, magic := match1(mods[0], `^[CS]/\^/(\w+)/1$`); m {
-                       if mods[1] == "M"+magic+"*" {
-                               if matches(mods[2], regex.Pattern(`^[CS]/\^`+magic+`//$`)) {
+               if m, _, from, to, options := mods[0].MatchSubst(); m && from == "^" && matches(to, `^\w+$`) && options == "1" {
+                       magic := to
+                       if m, positive, pattern := mods[1].MatchMatch(); m && positive && pattern == magic+"*" {
+                               if m, _, from, to, options = mods[2].MatchSubst(); m && from == "^"+magic && to == "" && options == "" {
                                        fix := ck.MkLine.Autofix()
                                        fix.Notef("The modifier %q can be written as %q.", varuse.Mod(), ":[1]")
                                        fix.Explain(
@@ -435,7 +437,7 @@ func (ck MkLineChecker) checkVaruseMod(v
 //
 // See checkVarassignPermissions.
 func (ck MkLineChecker) checkVarusePermissions(varname string, vartype *Vartype, vuc *VarUseContext) {
-       if !G.opts.WarnPerm {
+       if !G.Opts.WarnPerm {
                return
        }
        if trace.Tracing {
@@ -456,35 +458,37 @@ func (ck MkLineChecker) checkVarusePermi
        mkline := ck.MkLine
        perms := vartype.EffectivePermissions(mkline.Basename)
 
-       warnLoadTime := false // Will the variable be used at load time?
-
-       // Might the variable be used indirectly at load time, for example
-       // by assigning it to another variable which then gets evaluated?
-       warnIndirect := false
-
-       switch {
-       case vuc.vartype != nil && vuc.vartype.guessed:
-               // Don't warn about unknown variables.
-
-       case vuc.time == vucTimeParse && !perms.Contains(aclpUseLoadtime):
-               warnLoadTime = true
-
-       case vuc.vartype != nil && vuc.vartype.Union().Contains(aclpUseLoadtime) && !perms.Contains(aclpUseLoadtime):
-               warnLoadTime = true
-               warnIndirect = true
+       // Is the variable used at load time although that is not allowed?
+       directly := false
+       indirectly := false
+       if !perms.Contains(aclpUseLoadtime) { // May not be used at load time.
+               if vuc.time == vucTimeParse {
+                       directly = true
+               } else if vuc.vartype != nil && vuc.vartype.Union().Contains(aclpUseLoadtime) {
+                       indirectly = true
+               }
        }
 
-       if warnLoadTime {
+       if (directly || indirectly) && !vartype.guessed {
                if tool := G.ToolByVarname(varname, LoadTime); tool != nil {
-                       ck.checkVaruseToolLoadTime(varname, tool)
+                       if !tool.UsableAtLoadTime(G.Mk.Tools.SeenPrefs) {
+                               ck.warnVaruseToolLoadTime(varname, tool)
+                       }
+
                } else {
-                       ck.warnVaruseLoadTime(varname, warnIndirect)
+                       // Might the variable be used indirectly at load time, for example
+                       // by assigning it to another variable which then gets evaluated?
+                       isIndirect := vuc.time != vucTimeParse && // Otherwise it would be directly.
+                               // The context might be used at load time somewhere.
+                               vuc.vartype != nil && vuc.vartype.Union().Contains(aclpUseLoadtime)
+
+                       ck.warnVaruseLoadTime(varname, isIndirect)
                }
        }
 
        if !perms.Contains(aclpUseLoadtime) && !perms.Contains(aclpUse) {
                needed := aclpUse
-               if warnLoadTime {
+               if directly || indirectly {
                        needed = aclpUseLoadtime
                }
                alternativeFiles := vartype.AllowedFiles(needed)
@@ -502,12 +506,9 @@ func (ck MkLineChecker) checkVarusePermi
        }
 }
 
-// checkVaruseToolLoadTime checks whether the tool ${varname} may be used at load time.
-func (ck MkLineChecker) checkVaruseToolLoadTime(varname string, tool *Tool) {
-       if tool.UsableAtLoadTime(G.Mk.Tools.SeenPrefs) {
-               return
-       }
-
+// warnVaruseToolLoadTime logs a warning that the tool ${varname}
+// may not be used at load time.
+func (ck MkLineChecker) warnVaruseToolLoadTime(varname string, tool *Tool) {
        if tool.Validity == AfterPrefsMk {
                ck.MkLine.Warnf("To use the tool ${%s} at load time, bsd.prefs.mk has to be included before.", varname)
                return
@@ -555,14 +556,12 @@ func (ck MkLineChecker) warnVaruseLoadTi
                return
        }
 
-       if isIndirect {
-               mkline.Warnf("%s should not be evaluated indirectly at load time.", varname)
-               Explain(
-                       "The variable on the left-hand side may be evaluated at load time,",
-                       "but the variable on the right-hand side may not.  Because of the",
-                       "assignment in this line, the variable might be used indirectly",
-                       "at load time, before it is guaranteed to be properly initialized.")
-       }
+       mkline.Warnf("%s should not be evaluated indirectly at load time.", varname)
+       Explain(
+               "The variable on the left-hand side may be evaluated at load time,",
+               "but the variable on the right-hand side may not.  Because of the",
+               "assignment in this line, the variable might be used indirectly",
+               "at load time, before it is guaranteed to be properly initialized.")
 }
 
 // CheckVaruseShellword checks whether a variable use of the form ${VAR}
@@ -779,8 +778,8 @@ func (ck MkLineChecker) checkVarassignVa
        tokens := NewMkParser(mkline.Line, mkline.Value(), false).MkTokens()
        for i, token := range tokens {
                if token.Varuse != nil {
-                       spaceLeft := i-1 < 0 || matches(tokens[i-1].Text, `\s$`)
-                       spaceRight := i+1 >= len(tokens) || matches(tokens[i+1].Text, `^\s`)
+                       spaceLeft := i-1 < 0 || matches(tokens[i-1].Text, `[\t ]$`)
+                       spaceRight := i+1 >= len(tokens) || matches(tokens[i+1].Text, `^[\t ]`)
                        isWordPart := !(spaceLeft && spaceRight)
                        vuc := &VarUseContext{vartype, time, vucQuotPlain, isWordPart}
                        ck.CheckVaruse(token.Varuse, vuc)
@@ -868,7 +867,7 @@ func (ck MkLineChecker) checkVarassignBs
                return
        }
 
-       if !G.opts.WarnExtra ||
+       if !G.Opts.WarnExtra ||
                G.Infrastructure ||
                mkline.Op() != opAssignDefault ||
                G.Mk.Tools.SeenPrefs ||
@@ -894,7 +893,7 @@ func (ck MkLineChecker) CheckVartype(var
                defer trace.Call(varname, op, value, comment)()
        }
 
-       if !G.opts.WarnTypes {
+       if !G.Opts.WarnTypes {
                return
        }
 
@@ -1031,12 +1030,11 @@ func (ck MkLineChecker) checkDirectiveCo
 
                modifiers := varuse.modifiers
                for _, modifier := range modifiers {
-                       if modifier[0] == 'M' || (modifier[0] == 'N' && len(modifiers) == 1) {
-                               ck.CheckVartype(varname, opUseMatch, modifier[1:], "")
+                       if m, positive, pattern := modifier.MatchMatch(); m && (positive || len(modifiers) == 1) {
+                               ck.CheckVartype(varname, opUseMatch, pattern, "")
 
-                               value := modifier[1:]
                                vartype := G.Pkgsrc.VariableType(varname)
-                               if matches(value, `^[\w-/]+$`) && vartype != nil && !vartype.IsConsideredList() {
+                               if matches(pattern, `^[\w-/]+$`) && vartype != nil && !vartype.IsConsideredList() {
                                        mkline.Notef("%s should be compared using == instead of the :M or :N modifier without wildcards.", varname)
                                        Explain(
                                                "This variable has a single value, not a list of values.  Therefore",
@@ -1056,8 +1054,10 @@ func (ck MkLineChecker) checkDirectiveCo
                varmods := varuse.modifiers
                if len(varmods) == 0 {
                        ck.checkCompareVarStr(varname, op, value)
-               } else if len(varmods) == 1 && matches(varmods[0], `^[MN]`) && value != "" {
-                       ck.CheckVartype(varname, opUseMatch, value, "")
+               } else if len(varmods) == 1 {
+                       if m, _, _ := varmods[0].MatchMatch(); m && value != "" {
+                               ck.CheckVartype(varname, opUseMatch, value, "")
+                       }
                }
        }
 
@@ -1085,18 +1085,6 @@ func (ck MkLineChecker) checkCompareVarS
        }
 }
 
-func (ck MkLineChecker) CheckValidCharactersInValue(reValid regex.Pattern) {
-       mkline := ck.MkLine
-       rest := replaceAll(mkline.Value(), reValid, "")
-       if rest != "" {
-               uni := ""
-               for _, c := range rest {
-                       uni += fmt.Sprintf(" %U", c)
-               }
-               mkline.Warnf("%s contains invalid characters (%s).", mkline.Varname(), uni[1:])
-       }
-}
-
 func (ck MkLineChecker) CheckRelativePkgdir(pkgdir string) {
        if trace.Tracing {
                defer trace.Call1(pkgdir)()
@@ -1137,7 +1125,7 @@ func (ck MkLineChecker) CheckRelativePat
 
        abs := resolvedPath
        if !hasPrefix(abs, "/") {
-               abs = path.Dir(mkline.Filename) + "/" + abs
+               abs = path.Dir(mkline.FileName) + "/" + abs
        }
        if _, err := os.Stat(abs); err != nil {
                if mustExist {
@@ -1152,7 +1140,7 @@ func (ck MkLineChecker) CheckRelativePat
                // From a package to the infrastructure.
        case matches(relativePath, `^\.\./\.\./[^/]+/[^/]`):
                // From a package to another package.
-       case hasPrefix(relativePath, "../mk/") && relpath(path.Dir(mkline.Filename), G.Pkgsrc.File(".")) == "..":
+       case hasPrefix(relativePath, "../mk/") && relpath(path.Dir(mkline.FileName), G.Pkgsrc.File(".")) == "..":
                // For category Makefiles.
        default:
                mkline.Warnf("Invalid relative path %q.", relativePath)

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.17 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.18
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.17    Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Wed Nov  7 20:58:23 2018
@@ -7,12 +7,12 @@ func (s *Suite) Test_MkLineChecker_Check
 
        t.SetupVartypes()
 
-       mkline := t.NewMkLine("fname.mk", 1, "# url2pkg-marker")
+       mkline := t.NewMkLine("fileName.mk", 1, "# url2pkg-marker")
 
        MkLineChecker{mkline}.Check()
 
        t.CheckOutputLines(
-               "ERROR: fname.mk:1: This comment indicates unfinished work (url2pkg).")
+               "ERROR: fileName.mk:1: This comment indicates unfinished work (url2pkg).")
 }
 
 func (s *Suite) Test_MkLineChecker_Check__buildlink3_include_prefs(c *check.C) {
@@ -42,7 +42,7 @@ func (s *Suite) Test_MkLineChecker_check
        t.CreateFileLines("graphics/jpeg/buildlink3.mk")
        t.CreateFileLines("devel/intltool/buildlink3.mk")
        t.CreateFileLines("devel/intltool/builtin.mk")
-       mklines := t.SetupFileMkLines("category/package/fname.mk",
+       mklines := t.SetupFileMkLines("category/package/fileName.mk",
                MkRcsID,
                "",
                ".include \"../../pkgtools/x11-links/buildlink3.mk\"",
@@ -53,21 +53,33 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "ERROR: ~/category/package/fname.mk:3: ../../pkgtools/x11-links/buildlink3.mk must not be included directly. "+
+               "ERROR: ~/category/package/fileName.mk:3: ../../pkgtools/x11-links/buildlink3.mk must not be included directly. "+
                        "Include \"../../mk/x11.buildlink3.mk\" instead.",
-               "ERROR: ~/category/package/fname.mk:4: ../../graphics/jpeg/buildlink3.mk must not be included directly. "+
+               "ERROR: ~/category/package/fileName.mk:4: ../../graphics/jpeg/buildlink3.mk must not be included directly. "+
                        "Include \"../../mk/jpeg.buildlink3.mk\" instead.",
-               "WARN: ~/category/package/fname.mk:5: Please write \"USE_TOOLS+= intltool\" instead of this line.",
-               "ERROR: ~/category/package/fname.mk:6: ../../devel/intltool/builtin.mk must not be included directly. "+
+               "WARN: ~/category/package/fileName.mk:5: Please write \"USE_TOOLS+= intltool\" instead of this line.",
+               "ERROR: ~/category/package/fileName.mk:6: ../../devel/intltool/builtin.mk must not be included directly. "+
                        "Include \"../../devel/intltool/buildlink3.mk\" instead.")
 }
 
+func (s *Suite) Test_MkLineChecker_checkInclude__Makefile(c *check.C) {
+       t := s.Init(c)
+
+       mkline := t.NewMkLine(t.File("Makefile"), 2, ".include \"../../other/package/Makefile\"")
+
+       MkLineChecker{mkline}.checkInclude()
+
+       t.CheckOutputLines(
+               "ERROR: ~/Makefile:2: \"other/package/Makefile\" does not exist.",
+               "ERROR: ~/Makefile:2: Other Makefiles must not be included directly.")
+}
+
 func (s *Suite) Test_MkLineChecker_checkDirective(c *check.C) {
        t := s.Init(c)
 
        t.SetupVartypes()
 
-       mklines := t.NewMkLines("category/package/fname.mk",
+       mklines := t.NewMkLines("category/package/fileName.mk",
                MkRcsID,
                "",
                ".for",
@@ -95,15 +107,15 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "ERROR: category/package/fname.mk:3: \".for\" requires arguments.",
-               "ERROR: category/package/fname.mk:6: \".if\" requires arguments.",
-               "ERROR: category/package/fname.mk:7: \".else\" does not take arguments. If you meant \"else if\", use \".elif\".",
-               "ERROR: category/package/fname.mk:8: \".endif\" does not take arguments.",
-               "WARN: category/package/fname.mk:10: The \".ifdef\" directive is deprecated. Please use \".if defined(FNAME_MK)\" instead.",
-               "WARN: category/package/fname.mk:12: The \".ifndef\" directive is deprecated. Please use \".if !defined(FNAME_MK)\" instead.",
-               "NOTE: category/package/fname.mk:17: Using \".undef\" after a \".for\" loop is unnecessary.",
-               "WARN: category/package/fname.mk:19: .for variable names should not contain uppercase letters.",
-               "ERROR: category/package/fname.mk:22: Invalid variable name \"$\".")
+               "ERROR: category/package/fileName.mk:3: \".for\" requires arguments.",
+               "ERROR: category/package/fileName.mk:6: \".if\" requires arguments.",
+               "ERROR: category/package/fileName.mk:7: \".else\" does not take arguments. If you meant \"else if\", use \".elif\".",
+               "ERROR: category/package/fileName.mk:8: \".endif\" does not take arguments.",
+               "WARN: category/package/fileName.mk:10: The \".ifdef\" directive is deprecated. Please use \".if defined(FNAME_MK)\" instead.",
+               "WARN: category/package/fileName.mk:12: The \".ifndef\" directive is deprecated. Please use \".if !defined(FNAME_MK)\" instead.",
+               "NOTE: category/package/fileName.mk:17: Using \".undef\" after a \".for\" loop is unnecessary.",
+               "WARN: category/package/fileName.mk:19: .for variable names should not contain uppercase letters.",
+               "ERROR: category/package/fileName.mk:22: Invalid variable name \"$\".")
 }
 
 func (s *Suite) Test_MkLineChecker_checkDependencyRule(c *check.C) {
@@ -111,7 +123,7 @@ func (s *Suite) Test_MkLineChecker_check
 
        t.SetupVartypes()
 
-       mklines := t.NewMkLines("category/package/fname.mk",
+       mklines := t.NewMkLines("category/package/fileName.mk",
                MkRcsID,
                "",
                ".PHONY: target-1",
@@ -124,7 +136,7 @@ func (s *Suite) Test_MkLineChecker_check
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: category/package/fname.mk:8: Unusual target \"target-3\".")
+               "WARN: category/package/fileName.mk:8: Unusual target \"target-3\".")
 }
 
 func (s *Suite) Test_MkLineChecker_CheckVartype__simple_type(c *check.C) {
@@ -154,7 +166,7 @@ func (s *Suite) Test_MkLineChecker_Check
        t := s.Init(c)
 
        t.SetupVartypes()
-       mkline := t.NewMkLine("fname", 1, "DISTNAME=gcc-${GCC_VERSION}")
+       mkline := t.NewMkLine("fileName", 1, "DISTNAME=gcc-${GCC_VERSION}")
 
        MkLineChecker{mkline}.CheckVartype("DISTNAME", opAssign, "gcc-${GCC_VERSION}", "")
 
@@ -166,7 +178,7 @@ func (s *Suite) Test_MkLineChecker_Check
 
        t.SetupCommandLine("-Wno-types")
        t.SetupVartypes()
-       mkline := t.NewMkLine("fname", 1, "DISTNAME=invalid:::distname")
+       mkline := t.NewMkLine("fileName", 1, "DISTNAME=invalid:::distname")
 
        MkLineChecker{mkline}.Check()
 
@@ -176,9 +188,8 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVartype__append_to_non_list(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
-       mklines := t.NewMkLines("fname.mk",
+       mklines := t.NewMkLines("fileName.mk",
                MkRcsID,
                "DISTNAME+=\tsuffix",
                "COMMENT=\tComment for",
@@ -187,8 +198,8 @@ func (s *Suite) Test_MkLineChecker_Check
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: fname.mk:2: The variable DISTNAME may not be appended to (only set, given a default value) in this file.",
-               "WARN: fname.mk:2: The \"+=\" operator should only be used with lists, not with DISTNAME.")
+               "WARN: fileName.mk:2: The variable DISTNAME may not be appended to (only set, given a default value) in this file.",
+               "WARN: fileName.mk:2: The \"+=\" operator should only be used with lists, not with DISTNAME.")
 }
 
 // Pkglint once interpreted all lists as consisting of shell tokens,
@@ -198,7 +209,7 @@ func (s *Suite) Test_MkLineChecker_check
 
        G.Pkg = NewPackage(t.File("graphics/gimp-fix-ca"))
        t.SetupVartypes()
-       mkline := t.NewMkLine("fname", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";)
+       mkline := t.NewMkLine("fileName", 10, "MASTER_SITES=http://registry.gimp.org/file/fix-ca.c?action=download&id=9884&file=";)
 
        MkLineChecker{mkline}.checkVarassign()
 
@@ -212,30 +223,34 @@ func (s *Suite) Test_MkLineChecker_Check
        t.SetupVartypes()
 
        testCond := func(cond string, output ...string) {
-               MkLineChecker{t.NewMkLine("fname", 1, cond)}.checkDirectiveCond()
-               t.CheckOutputLines(output...)
+               MkLineChecker{t.NewMkLine("fileName", 1, cond)}.checkDirectiveCond()
+               if len(output) > 0 {
+                       t.CheckOutputLines(output...)
+               } else {
+                       t.CheckOutputEmpty()
+               }
        }
 
        testCond(".if !empty(PKGSRC_COMPILER:Mmycc)",
-               "WARN: fname:1: The pattern \"mycc\" cannot match any of "+
+               "WARN: fileName: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.")
 
        testCond(".elif ${A} != ${B}")
 
        testCond(".if ${HOMEPAGE} == \"mailto:someone%example.org@localhost\"";,
-               "WARN: fname:1: \"mailto:someone%example.org@localhost\"; is not a valid URL.")
+               "WARN: fileName:1: \"mailto:someone%example.org@localhost\"; is not a valid URL.")
 
        testCond(".if !empty(PKGSRC_RUN_TEST:M[Y][eE][sS])",
-               "WARN: fname:1: PKGSRC_RUN_TEST should be matched against \"[yY][eE][sS]\" or \"[nN][oO]\", not \"[Y][eE][sS]\".")
+               "WARN: fileName:1: PKGSRC_RUN_TEST should be matched against \"[yY][eE][sS]\" or \"[nN][oO]\", not \"[Y][eE][sS]\".")
 
        testCond(".if !empty(IS_BUILTIN.Xfixes:M[yY][eE][sS])")
 
        testCond(".if !empty(${IS_BUILTIN.Xfixes:M[yY][eE][sS]})",
-               "WARN: fname:1: The empty() function takes a variable name as parameter, not a variable expression.")
+               "WARN: fileName:1: The empty() function takes a variable name as parameter, not a variable expression.")
 
        testCond(".if ${EMUL_PLATFORM} == \"linux-x386\"",
-               "WARN: fname:1: "+
+               "WARN: fileName: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 "+
@@ -246,7 +261,7 @@ func (s *Suite) Test_MkLineChecker_Check
                        "} instead.")
 
        testCond(".if ${EMUL_PLATFORM:Mlinux-x386}",
-               "WARN: fname:1: "+
+               "WARN: fileName: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 "+
@@ -254,15 +269,15 @@ func (s *Suite) Test_MkLineChecker_Check
                        "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.",
-               "NOTE: fname:1: EMUL_PLATFORM should be compared using == instead of the :M or :N modifier without wildcards.")
+               "NOTE: fileName:1: EMUL_PLATFORM should be compared using == instead of the :M or :N modifier without wildcards.")
 
        testCond(".if ${MACHINE_PLATFORM:MUnknownOS-*-*} || ${MACHINE_ARCH:Mx86}",
-               "WARN: fname:1: "+
+               "WARN: fileName:1: "+
                        "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.",
-               "WARN: fname:1: "+
+               "WARN: fileName:1: "+
                        "The pattern \"x86\" 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 "+
@@ -270,7 +285,7 @@ func (s *Suite) Test_MkLineChecker_Check
                        "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 MACHINE_ARCH.",
-               "NOTE: fname:1: MACHINE_ARCH should be compared using == instead of the :M or :N modifier without wildcards.")
+               "NOTE: fileName:1: MACHINE_ARCH should be compared using == instead of the :M or :N modifier without wildcards.")
 
        testCond(".if ${MASTER_SITES:Mftp://*} == \"ftp://netbsd.org/\"";)
 }
@@ -312,7 +327,6 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_checkVarassignPermissions__infrastructure(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.CreateFileLines("mk/infra.mk",
                MkRcsID,
@@ -328,7 +342,6 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("options.mk",
                MkRcsID,
@@ -349,7 +362,6 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_checkVarusePermissions__load_time(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("options.mk",
                MkRcsID,
@@ -368,6 +380,49 @@ func (s *Suite) Test_MkLineChecker_check
                "NOTE: options.mk:2: This variable value should be aligned to column 17.")
 }
 
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__load_time_guessed(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       t.SetupTool("install", "", AtRunTime)
+       mklines := t.NewMkLines("install-docfiles.mk",
+               MkRcsID,
+               "DOCFILES=\ta b c",
+               "do-install:",
+               ".for f in ${DOCFILES}",
+               "\tinstall -c ${WRKSRC}/${f} ${DESTDIR}${PREFIX}/${f}",
+               ".endfor")
+
+       mklines.Check()
+
+       // No warning for using DOCFILES at compile-time. Since the variable
+       // name is not one of the predefined names from vardefs.go, the
+       // variable's type is guessed based on the name (see
+       // Pkgsrc.VariableType).
+       //
+       // These guessed variables are typically defined and used only in
+       // a single file, and in this context, mistakes are usually found
+       // quickly.
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_MkLineChecker_checkVarusePermissions__PKGREVISION(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       mklines := t.NewMkLines("any.mk",
+               MkRcsID,
+               // PKGREVISION may only be set in Makefile, not used at load time; see vardefs.go.
+               ".if defined(PKGREVISION)",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: any.mk:2: PKGREVISION should not be evaluated at load time.",
+               "WARN: any.mk:2: PKGREVISION may not be used in any file; it is a write-only variable.")
+}
+
 func (s *Suite) Test_MkLineChecker__warn_varuse_LOCALBASE(c *check.C) {
        t := s.Init(c)
 
@@ -383,13 +438,14 @@ func (s *Suite) Test_MkLineChecker__warn
 func (s *Suite) Test_MkLineChecker_CheckRelativePkgdir(c *check.C) {
        t := s.Init(c)
 
-       mkline := t.NewMkLine("Makefile", 46, "# dummy")
+       mklines := t.SetupFileMkLines("Makefile",
+               "# dummy")
 
-       MkLineChecker{mkline}.CheckRelativePkgdir("../pkgbase")
+       MkLineChecker{mklines.mklines[0]}.CheckRelativePkgdir("../pkgbase")
 
        t.CheckOutputLines(
-               "ERROR: Makefile:46: \"../pkgbase\" does not exist.",
-               "WARN: Makefile:46: \"../pkgbase\" is not a valid relative package directory.")
+               "ERROR: ~/Makefile:1: \"../pkgbase\" does not exist.",
+               "WARN: ~/Makefile:1: \"../pkgbase\" is not a valid relative package directory.")
 }
 
 // PR pkg/46570, item 2
@@ -398,7 +454,7 @@ func (s *Suite) Test_MkLineChecker__uncl
 
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
-               "EGDIRS=${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d")
+               "EGDIRS=\t${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d")
 
        mklines.Check()
 
@@ -412,7 +468,6 @@ func (s *Suite) Test_MkLineChecker__uncl
 func (s *Suite) Test_MkLineChecker__Varuse_Modifier_L(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("x11/xkeyboard-config/Makefile",
                "FILES_SUBST+=XKBCOMP_SYMLINK=${${XKBBASE}/xkbcomp:L:Q}")
@@ -426,7 +481,6 @@ func (s *Suite) Test_MkLineChecker__Varu
 func (s *Suite) Test_MkLineChecker_checkDirectiveCond__comparison_with_shell_command(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("security/openssl/Makefile",
                MkRcsID,
@@ -443,7 +497,6 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_checkDirectiveCond__comparing_PKGSRC_COMPILER_with_eqeq(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("audio/pulseaudio/Makefile",
                MkRcsID,
@@ -459,7 +512,6 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_CheckVartype__CFLAGS_with_backticks(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Mk = t.NewMkLines("chat/pidgin-icb/Makefile",
                MkRcsID,
@@ -528,7 +580,6 @@ func (s *Suite) Test_MkLineChecker_check
 func (s *Suite) Test_MkLineChecker_CheckVaruseShellword(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.SetupFileMkLines("options.mk",
                MkRcsID,
@@ -598,7 +649,6 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVaruseShellword__q_not_needed(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        pkg := t.SetupPackage("category/package",
                "MASTER_SITES=\t${HOMEPAGE:Q}")
        G.Pkgsrc.LoadInfrastructure()
@@ -614,7 +664,6 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVaruse__eq_nonlist(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
        mklines := t.SetupFileMkLines("options.mk",
@@ -631,7 +680,6 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVaruse__for(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
        mklines := t.SetupFileMkLines("options.mk",
@@ -648,7 +696,6 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckVaruse__defined_in_infrastructure(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupPkgsrc()
        t.SetupVartypes()
        t.CreateFileLines("mk/deeply/nested/infra.mk",
@@ -790,5 +837,6 @@ func (s *Suite) Test_MkLineChecker_Check
        t.CheckOutputLines(
                "ERROR: ~/category/package/module.mk:2: A main pkgsrc package must not depend on a pkgsrc-wip package.",
                "ERROR: ~/category/package/module.mk:3: A main pkgsrc package must not depend on a pkgsrc-wip package.",
+               "WARN: ~/category/package/module.mk:5: LATEST_PYTHON is used but not defined.",
                "WARN: ~/category/package/module.mk:11: Invalid relative path \"../package/module.mk\".")
 }

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.33 pkgsrc/pkgtools/pkglint/files/mklines.go:1.34
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.33       Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Wed Nov  7 20:58:23 2018
@@ -1,14 +1,15 @@
 package main
 
 import (
-       "netbsd.org/pkglint/trace"
        "strings"
 )
 
 // MkLines contains data for the Makefile (or *.mk) that is currently checked.
-type MkLines struct {
+type MkLines = *MkLinesImpl
+
+type MkLinesImpl struct {
        mklines       []MkLine
-       lines         []Line
+       lines         Lines
        forVars       map[string]bool // The variables currently used in .for loops
        target        string          // Current make(1) target
        vars          Scope
@@ -21,21 +22,16 @@ type MkLines struct {
        Once
 }
 
-func NewMkLines(lines []Line) *MkLines {
-       mklines := make([]MkLine, len(lines))
-       for i, line := range lines {
+func NewMkLines(lines Lines) MkLines {
+       mklines := make([]MkLine, lines.Len())
+       for i, line := range lines.Lines {
                mklines[i] = NewMkLine(line)
        }
 
-       traceName := "MkLines"
-       if len(lines) != 0 {
-               traceName = lines[0].Filename
-       }
-
-       tools := NewTools(traceName)
+       tools := NewTools(lines.FileName)
        tools.Fallback(G.Pkgsrc.Tools)
 
-       return &MkLines{
+       return &MkLinesImpl{
                mklines,
                lines,
                make(map[string]bool),
@@ -50,16 +46,16 @@ func NewMkLines(lines []Line) *MkLines {
                Once{}}
 }
 
-func (mklines *MkLines) UseVar(mkline MkLine, varname string) {
+func (mklines *MkLinesImpl) UseVar(mkline MkLine, varname string) {
        mklines.vars.Use(varname, mkline)
        if G.Pkg != nil {
                G.Pkg.vars.Use(varname, mkline)
        }
 }
 
-func (mklines *MkLines) Check() {
+func (mklines *MkLinesImpl) Check() {
        if trace.Tracing {
-               defer trace.Call1(mklines.lines[0].Filename)()
+               defer trace.Call1(mklines.lines.FileName)()
        }
 
        G.Mk = mklines
@@ -78,7 +74,7 @@ func (mklines *MkLines) Check() {
        SaveAutofixChanges(mklines.lines)
 }
 
-func (mklines *MkLines) checkAll() {
+func (mklines *MkLinesImpl) checkAll() {
        allowedTargets := func() map[string]bool {
                targets := make(map[string]bool)
                prefixes := [...]string{"pre", "do", "post"}
@@ -91,11 +87,11 @@ func (mklines *MkLines) checkAll() {
                return targets
        }()
 
-       CheckLineRcsid(mklines.lines[0], `#\s+`, "# ")
+       CheckLineRcsid(mklines.lines.Lines[0], `#[\t ]+`, "# ")
 
        substContext := NewSubstContext()
        var varalign VaralignBlock
-       isHacksMk := mklines.lines[0].Basename == "hacks.mk"
+       isHacksMk := mklines.lines.BaseName == "hacks.mk"
 
        lineAction := func(mkline MkLine) bool {
                if isHacksMk {
@@ -154,10 +150,7 @@ func (mklines *MkLines) checkAll() {
        }
 
        atEnd := func(mkline MkLine) {
-               ind := mklines.indentation
-               if ind.Len() != 1 && ind.Depth("") != 0 {
-                       mkline.Errorf("Directive indentation is not 0, but %d.", ind.Depth(""))
-               }
+               mklines.indentation.CheckFinish(mklines.lines.FileName)
        }
 
        // TODO: Extract this code so that it is clearly visible in the stack trace.
@@ -166,7 +159,7 @@ func (mklines *MkLines) checkAll() {
        }
        mklines.ForEachEnd(lineAction, atEnd)
 
-       substContext.Finish(NewMkLine(NewLineEOF(mklines.lines[0].Filename)))
+       substContext.Finish(NewMkLine(mklines.lines.EOFLine())) // TODO: mklines.EOFLine()
        varalign.Finish()
 
        ChecklinesTrailingEmptyLines(mklines.lines)
@@ -175,7 +168,7 @@ func (mklines *MkLines) checkAll() {
 // ForEach calls the action for each line, until the action returns false.
 // It keeps track of the indentation (see MkLines.indentation)
 // and all conditional variables (see Indentation.IsConditional).
-func (mklines *MkLines) ForEach(action func(mkline MkLine)) {
+func (mklines *MkLinesImpl) ForEach(action func(mkline MkLine)) {
        mklines.ForEachEnd(
                func(mkline MkLine) bool { action(mkline); return true },
                func(mkline MkLine) {})
@@ -184,7 +177,13 @@ func (mklines *MkLines) ForEach(action f
 // ForEachEnd calls the action for each line, until the action returns false.
 // It keeps track of the indentation and all conditional variables.
 // At the end, atEnd is called with the last line as its argument.
-func (mklines *MkLines) ForEachEnd(action func(mkline MkLine) bool, atEnd func(lastMkline MkLine)) {
+func (mklines *MkLinesImpl) ForEachEnd(action func(mkline MkLine) bool, atEnd func(lastMkline MkLine)) {
+
+       // XXX: To avoid looping over the lines multiple times, it would
+       // be nice to have an interface LinesChecker that checks a single thing.
+       // Multiple of these line checkers could be run in parallel, so that
+       // the diagnostics appear in the correct order, from top to bottom.
+
        mklines.indentation = NewIndentation()
        mklines.Tools.SeenPrefs = false
 
@@ -200,7 +199,7 @@ func (mklines *MkLines) ForEachEnd(actio
        mklines.indentation = nil
 }
 
-func (mklines *MkLines) DetermineDefinedVariables() {
+func (mklines *MkLinesImpl) DetermineDefinedVariables() {
        if trace.Tracing {
                defer trace.Call0()()
        }
@@ -265,7 +264,7 @@ func (mklines *MkLines) DetermineDefined
        }
 }
 
-func (mklines *MkLines) collectPlistVars() {
+func (mklines *MkLinesImpl) collectPlistVars() {
        for _, mkline := range mklines.mklines {
                if mkline.IsVarassign() {
                        switch mkline.Varcanon() {
@@ -290,12 +289,12 @@ func (mklines *MkLines) collectPlistVars
        }
 }
 
-func (mklines *MkLines) collectElse() {
+func (mklines *MkLinesImpl) collectElse() {
        // Make a dry-run over the lines, which sets data.elseLine (in mkline.go) as a side-effect.
        mklines.ForEach(func(mkline MkLine) {})
 }
 
-func (mklines *MkLines) DetermineUsedVariables() {
+func (mklines *MkLinesImpl) DetermineUsedVariables() {
        for _, mkline := range mklines.mklines {
                for _, varname := range mkline.DetermineUsedVariables() {
                        mklines.UseVar(mkline, varname)
@@ -306,7 +305,7 @@ func (mklines *MkLines) DetermineUsedVar
 }
 
 // Loosely based on mk/help/help.awk, revision 1.28
-func (mklines *MkLines) determineDocumentedVariables() {
+func (mklines *MkLinesImpl) determineDocumentedVariables() {
        scope := NewScope()
        commentLines := 0
        relevant := true
@@ -358,7 +357,7 @@ func (mklines *MkLines) determineDocumen
        finish()
 }
 
-func (mklines *MkLines) CheckRedundantVariables() {
+func (mklines *MkLinesImpl) CheckRedundantVariables() {
        scope := NewRedundantScope()
        isRelevant := func(old, new MkLine) bool {
                if old.Basename != "Makefile" && new.Basename == "Makefile" {
@@ -371,12 +370,12 @@ func (mklines *MkLines) CheckRedundantVa
        }
        scope.OnIgnore = func(old, new MkLine) {
                if isRelevant(old, new) && old.Value() == new.Value() {
-                       old.Notef("Definition of %s is redundant because of %s.", new.Varname(), new.ReferenceFrom(old.Line))
+                       old.Notef("Definition of %s is redundant because of %s.", new.Varname(), old.RefTo(new))
                }
        }
        scope.OnOverwrite = func(old, new MkLine) {
                if isRelevant(old, new) {
-                       old.Warnf("Variable %s is overwritten in %s.", new.Varname(), new.ReferenceFrom(old.Line))
+                       old.Warnf("Variable %s is overwritten in %s.", new.Varname(), old.RefTo(new))
                        Explain(
                                "The variable definition in this line does not have an effect since",
                                "it is overwritten elsewhere.  This typically happens because of a",
@@ -388,8 +387,43 @@ func (mklines *MkLines) CheckRedundantVa
        mklines.ForEach(scope.Handle)
 }
 
-func (mklines *MkLines) SaveAutofixChanges() {
-       SaveAutofixChanges(mklines.lines)
+func (mklines *MkLinesImpl) CheckForUsedComment(relativeName string) {
+       lines := mklines.lines
+       if lines.Len() < 3 {
+               return
+       }
+
+       expected := "# used by " + relativeName
+       for _, line := range lines.Lines {
+               if line.Text == expected {
+                       return
+               }
+       }
+
+       i := 0
+       for i < 2 && hasPrefix(lines.Lines[i].Text, "#") {
+               i++
+       }
+
+       fix := lines.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)
+}
+
+func (mklines *MkLinesImpl) SaveAutofixChanges() {
+       mklines.lines.SaveAutofixChanges()
 }
 
 // VaralignBlock checks that all variable assignments from a paragraph
@@ -398,7 +432,7 @@ func (mklines *MkLines) SaveAutofixChang
 //
 // In general, all values should be aligned using tabs.
 // As an exception, very long lines may be aligned with a single space.
-// A typical example is a SITES.very-long-filename.tar.gz variable
+// A typical example is a SITES.very-long-file-name.tar.gz variable
 // between HOMEPAGE and DISTFILES.
 type VaralignBlock struct {
        infos []*varalignBlockInfo
@@ -416,7 +450,7 @@ type varalignBlockInfo struct {
 
 func (va *VaralignBlock) Check(mkline MkLine) {
        switch {
-       case !G.opts.WarnSpace:
+       case !G.Opts.WarnSpace:
                return
 
        case mkline.IsEmpty():
@@ -461,7 +495,7 @@ func (va *VaralignBlock) Check(mkline Mk
        continuation := false
        if mkline.IsMultiline() {
                // Interpreting the continuation marker as variable value
-               // is cheating, but works well.
+               // is cheating but works well.
                text := strings.TrimSuffix(mkline.raw[0].orignl, "\n")
                m, _, _, _, _, _, value, _, _ := MatchVarassign(text)
                continuation = m && value == "\\"
@@ -578,15 +612,24 @@ func (va *VaralignBlock) realign(mkline 
                if hasPrefix(oldSpace, "\t") {
                        // Even though it is an outlier, it uses a tab and therefore
                        // didn't seem to be too long to the original developer.
-                       // Therefore, leave it as-is, but still fix any continuation lines.
+                       // Therefore, leave it as-is but still fix any continuation lines.
                        newSpace = oldSpace
                } else {
                        newSpace = " "
                }
        }
 
-       fix := mkline.Autofix()
+       va.realignInitialLine(mkline, varnameOp, oldSpace, newSpace, hasSpace, newWidth)
+       if mkline.IsMultiline() {
+               va.realignContinuationLines(mkline, newWidth)
+       }
+}
+
+func (va *VaralignBlock) realignInitialLine(mkline MkLine, varnameOp string, oldSpace string, newSpace string, hasSpace bool, newWidth int) {
        wrongColumn := tabWidth(varnameOp+oldSpace) != tabWidth(varnameOp+newSpace)
+
+       fix := mkline.Autofix()
+
        switch {
        case hasSpace && wrongColumn:
                fix.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", newWidth+1)
@@ -595,8 +638,9 @@ func (va *VaralignBlock) realign(mkline 
        case wrongColumn:
                fix.Notef("This variable value should be aligned to column %d.", newWidth+1)
        default:
-               fix.Notef("Silent-Magic-Diagnostic")
+               return
        }
+
        if wrongColumn {
                fix.Explain(
                        "Normally, all variable values in a block should start at the same",
@@ -612,14 +656,15 @@ func (va *VaralignBlock) realign(mkline 
                        "When the block contains something else than variable definitions",
                        "and directives like .if or .for, it is not checked at all.")
        }
+
        fix.ReplaceAfter(varnameOp, oldSpace, newSpace)
        fix.Apply()
+}
 
-       if mkline.IsMultiline() {
-               indentation := strings.Repeat("\t", newWidth/8) + strings.Repeat(" ", newWidth%8)
-               fix := mkline.Autofix()
-               fix.Notef("This line should be aligned with %q.", indentation)
-               fix.Realign(mkline, newWidth)
-               fix.Apply()
-       }
+func (va *VaralignBlock) realignContinuationLines(mkline MkLine, newWidth int) {
+       indentation := strings.Repeat("\t", newWidth/8) + strings.Repeat(" ", newWidth%8)
+       fix := mkline.Autofix()
+       fix.Notef("This line should be aligned with %q.", indentation)
+       fix.Realign(mkline, newWidth)
+       fix.Apply()
 }

Index: pkgsrc/pkgtools/pkglint/files/mklines_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.29 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.30
--- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.29  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines_test.go       Wed Nov  7 20:58:23 2018
@@ -10,7 +10,7 @@ func (s *Suite) Test_MkLines_Check__auto
        t := s.Init(c)
 
        t.SetupCommandLine("--autofix", "-Wspace")
-       lines := t.SetupFileLines("fname.mk",
+       lines := t.SetupFileLines("fileName.mk",
                MkRcsID,
                ".if defined(A)",
                ".for a in ${A}",
@@ -23,11 +23,11 @@ func (s *Suite) Test_MkLines_Check__auto
        mklines.Check()
 
        t.CheckOutputLines(
-               "AUTOFIX: ~/fname.mk:3: Replacing \".\" with \".  \".",
-               "AUTOFIX: ~/fname.mk:4: Replacing \".\" with \".    \".",
-               "AUTOFIX: ~/fname.mk:5: Replacing \".\" with \".    \".",
-               "AUTOFIX: ~/fname.mk:6: Replacing \".\" with \".  \".")
-       t.CheckFileLines("fname.mk",
+               "AUTOFIX: ~/fileName.mk:3: Replacing \".\" with \".  \".",
+               "AUTOFIX: ~/fileName.mk:4: Replacing \".\" with \".    \".",
+               "AUTOFIX: ~/fileName.mk:5: Replacing \".\" with \".    \".",
+               "AUTOFIX: ~/fileName.mk:6: Replacing \".\" with \".  \".")
+       t.CheckFileLines("fileName.mk",
                "# $"+"NetBSD$",
                ".if defined(A)",
                ".  for a in ${A}",
@@ -48,26 +48,18 @@ func (s *Suite) Test_MkLines_Check__unus
 
        mklines.Check()
 
+       // FIXME: .TARGET is always defined.
+       // FIXME: .IMPSRC is always defined.
        t.CheckOutputLines(
-               "WARN: Makefile:3: Unusual target \"echo\".")
-}
-
-func (s *Suite) Test_MkLineChecker_checkInclude__Makefile(c *check.C) {
-       t := s.Init(c)
-
-       mkline := t.NewMkLine(t.File("Makefile"), 2, ".include \"../../other/package/Makefile\"")
-
-       MkLineChecker{mkline}.checkInclude()
-
-       t.CheckOutputLines(
-               "ERROR: ~/Makefile:2: \"other/package/Makefile\" does not exist.",
-               "ERROR: ~/Makefile:2: Other Makefiles must not be included directly.")
+               "WARN: Makefile:3: Unusual target \"echo\".",
+               "WARN: Makefile:4: Unknown shell command \"cc\".",
+               "WARN: Makefile:4: .TARGET is used but not defined.",
+               "WARN: Makefile:4: .IMPSRC is used but not defined.")
 }
 
 func (s *Suite) Test_MkLines__quoting_LDFLAGS_for_GNU_configure(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        G.Pkg = NewPackage(t.File("category/pkgbase"))
        mklines := t.NewMkLines("Makefile",
@@ -85,7 +77,6 @@ func (s *Suite) Test_MkLines__quoting_LD
 func (s *Suite) Test_MkLines__for_loop_multiple_variables(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("echo", "ECHO", AtRunTime)
        t.SetupTool("find", "FIND", AtRunTime)
@@ -110,7 +101,6 @@ func (s *Suite) Test_MkLines__for_loop_m
 func (s *Suite) Test_MkLines__comparing_YesNo_variable_to_string(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("databases/gdbm_compat/builtin.mk",
                MkRcsID,
@@ -129,7 +119,6 @@ func (s *Suite) Test_MkLines__comparing_
 func (s *Suite) Test_MkLines__varuse_sh_modifier(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("sed", "SED", AfterPrefsMk)
        mklines := t.NewMkLines("lang/qore/module.mk",
@@ -154,7 +143,6 @@ func (s *Suite) Test_MkLines__varuse_sh_
 func (s *Suite) Test_MkLines__varuse_parameterized(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("converters/wv2/Makefile",
                MkRcsID,
@@ -174,7 +162,6 @@ func (s *Suite) Test_MkLines__varuse_par
 func (s *Suite) Test_MkLines__loop_modifier(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("chat/xchat/Makefile",
                MkRcsID,
@@ -211,7 +198,6 @@ func (s *Suite) Test_MkLines__PKG_SKIP_R
 func (s *Suite) Test_MkLines__indirect_variables(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupTool("echo", "ECHO", AfterPrefsMk)
        mklines := t.NewMkLines("net/uucp/Makefile",
                MkRcsID,
@@ -234,7 +220,6 @@ func (s *Suite) Test_MkLines__indirect_v
 func (s *Suite) Test_MkLines_Check__list_variable_as_part_of_word(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("converters/chef/Makefile",
                MkRcsID,
@@ -250,7 +235,6 @@ func (s *Suite) Test_MkLines_Check__list
 func (s *Suite) Test_MkLines_Check__absolute_pathname_depending_on_OPSYS(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("games/heretic2-demo/Makefile",
                MkRcsID,
@@ -269,7 +253,7 @@ func (s *Suite) Test_MkLines_Check__abso
                "WARN: games/heretic2-demo/Makefile:5: Unknown shell command \"/usr/bin/bsdtar\".")
 }
 
-func (s *Suite) Test_MkLines_checkForUsedComment(c *check.C) {
+func (s *Suite) Test_MkLines_CheckForUsedComment(c *check.C) {
        t := s.Init(c)
 
        t.SetupCommandLine("--show-autofix")
@@ -277,24 +261,24 @@ func (s *Suite) Test_MkLines_checkForUse
                MkRcsID,
                "",
                "# used by sysutils/mc",
-       ).checkForUsedComment("sysutils/mc")
+       ).CheckForUsedComment("sysutils/mc")
 
        t.CheckOutputEmpty()
 
-       t.NewMkLines("Makefile.common").checkForUsedComment("category/package")
+       t.NewMkLines("Makefile.common").CheckForUsedComment("category/package")
 
        t.CheckOutputEmpty()
 
        t.NewMkLines("Makefile.common",
                MkRcsID,
-       ).checkForUsedComment("category/package")
+       ).CheckForUsedComment("category/package")
 
        t.CheckOutputEmpty()
 
        t.NewMkLines("Makefile.common",
                MkRcsID,
                "",
-       ).checkForUsedComment("category/package")
+       ).CheckForUsedComment("category/package")
 
        t.CheckOutputEmpty()
 
@@ -302,7 +286,7 @@ func (s *Suite) Test_MkLines_checkForUse
                MkRcsID,
                "",
                "VARNAME=\tvalue",
-       ).checkForUsedComment("category/package")
+       ).CheckForUsedComment("category/package")
 
        t.CheckOutputLines(
                "WARN: Makefile.common:2: Please add a line \"# used by category/package\" here.",
@@ -312,7 +296,7 @@ func (s *Suite) Test_MkLines_checkForUse
                MkRcsID,
                "#",
                "#",
-       ).checkForUsedComment("category/package")
+       ).CheckForUsedComment("category/package")
 
        t.CheckOutputLines(
                "WARN: Makefile.common:3: Please add a line \"# used by category/package\" here.",
@@ -356,8 +340,6 @@ func (s *Suite) Test_MkLines_DetermineDe
        t.CheckOutputLines(
                // FIXME: the below warning is wrong; it's ok to have SUBST blocks in all files, maybe except buildlink3.mk.
                "WARN: determine-defined-variables.mk:12: The variable SUBST_VARS.subst may not be set (only given a default value, appended to) in this file; it would be ok in Makefile, 
Makefile.common, options.mk.",
-               // FIXME: the below warning is wrong; variables mentioned in SUBST_VARS should be allowed in that block.
-               "WARN: determine-defined-variables.mk:13: Foreign variable \"SUV\" in SUBST block.",
                "WARN: determine-defined-variables.mk:16: Unknown shell command \"unknown-command\".")
 }
 
@@ -389,7 +371,7 @@ func (s *Suite) Test_MkLines_DetermineDe
 func (s *Suite) Test_MkLines_DetermineUsedVariables__simple(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.NewMkLines("fname",
+       mklines := t.NewMkLines("fileName",
                "\t${VAR}")
        mkline := mklines.mklines[0]
        G.Mk = mklines
@@ -403,7 +385,7 @@ func (s *Suite) Test_MkLines_DetermineUs
 func (s *Suite) Test_MkLines_DetermineUsedVariables__nested(c *check.C) {
        t := s.Init(c)
 
-       mklines := t.NewMkLines("fname",
+       mklines := t.NewMkLines("fileName",
                "\t${outer.${inner}}")
        mkline := mklines.mklines[0]
        G.Mk = mklines
@@ -419,41 +401,38 @@ func (s *Suite) Test_MkLines_DetermineUs
 func (s *Suite) Test_MkLines__private_tool_undefined(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
-       mklines := t.NewMkLines("fname",
+       mklines := t.NewMkLines("fileName",
                MkRcsID,
                "",
-               "\tmd5sum filename")
+               "\tmd5sum fileName")
 
        mklines.Check()
 
        t.CheckOutputLines(
-               "WARN: fname:3: Unknown shell command \"md5sum\".")
+               "WARN: fileName:3: Unknown shell command \"md5sum\".")
 }
 
 func (s *Suite) Test_MkLines__private_tool_defined(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
-       mklines := t.NewMkLines("fname",
+       mklines := t.NewMkLines("fileName",
                MkRcsID,
                "TOOLS_CREATE+=\tmd5sum",
                "",
-               "\tmd5sum filename")
+               "\tmd5sum fileName")
 
        mklines.Check()
 
        // TODO: Is it necessary to add the tool to USE_TOOLS? If not, why not?
        t.CheckOutputLines(
-               "WARN: fname:4: The \"md5sum\" tool is used but not added to USE_TOOLS.")
+               "WARN: fileName:4: The \"md5sum\" tool is used but not added to USE_TOOLS.")
 }
 
 func (s *Suite) Test_MkLines_Check__indentation(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("options.mk",
                MkRcsID,
@@ -498,10 +477,35 @@ func (s *Suite) Test_MkLines_Check__inde
                "ERROR: options.mk:15: Unmatched .endif.")
 }
 
+// The .include directives do not need to be indented. They have the
+// syntactical form of directives but cannot be nested in a single file.
+// Therefore they may be either indented at the correct indentation depth
+// or not indented at all.
+func (s *Suite) Test_MkLines_Check__indentation_include(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       t.CreateFileLines("included.mk")
+       mklines := t.SetupFileMkLines("module.mk",
+               MkRcsID,
+               "",
+               ".if ${PKGPATH} == \"category/package\"",
+               ".include \"included.mk\"",
+               ". include \"included.mk\"",
+               ".  include \"included.mk\"",
+               ".    include \"included.mk\"",
+               ".endif")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "NOTE: ~/module.mk:5: This directive should be indented by 2 spaces.",
+               "NOTE: ~/module.mk:7: This directive should be indented by 2 spaces.")
+}
+
 func (s *Suite) Test_MkLines_Check__endif_comment(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("opsys.mk",
                MkRcsID,
@@ -539,7 +543,6 @@ func (s *Suite) Test_MkLines_Check__endi
 func (s *Suite) Test_MkLines_Check__unbalanced_directives(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("opsys.mk",
                MkRcsID,
@@ -552,13 +555,15 @@ func (s *Suite) Test_MkLines_Check__unba
        mklines.Check()
 
        t.CheckOutputLines(
-               "ERROR: opsys.mk:6: Directive indentation is not 0, but 8.")
+               "ERROR: opsys.mk:EOF: .if from line 6 must be closed.",
+               "ERROR: opsys.mk:EOF: .if from line 5 must be closed.",
+               "ERROR: opsys.mk:EOF: .if from line 4 must be closed.",
+               "ERROR: opsys.mk:EOF: .for from line 3 must be closed.")
 }
 
 func (s *Suite) Test_MkLines_Check__incomplete_subst_at_end(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("subst.mk",
                MkRcsID,
@@ -614,14 +619,13 @@ func (s *Suite) Test_MkLines__wip_catego
                "\tIn the rare case that you actually want a file-based make(1)",
                "\ttarget, write it like this:",
                "\t",
-               "\t\t${.CURDIR}/my-filename:",
+               "\t\t${.CURDIR}/my-file:",
                "")
 }
 
 func (s *Suite) Test_MkLines_determineDocumentedVariables(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("rm", "RM", AtRunTime)
        mklines := t.NewMkLines("Makefile",
@@ -672,7 +676,6 @@ func (s *Suite) Test_MkLines_determineDo
 func (s *Suite) Test_MkLines__shell_command_indentation(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
@@ -692,7 +695,6 @@ func (s *Suite) Test_MkLines__shell_comm
 func (s *Suite) Test_MkLines__unknown_options(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupOption("known", "")
        mklines := t.NewMkLines("options.mk",
@@ -716,8 +718,8 @@ func (s *Suite) Test_MkLines_CheckRedund
                "VAR=\tnew value")
        makefile := t.NewMkLines("Makefile",
                "VAR=\tthe package may overwrite variables from other files")
-       allLines := append(append([]Line(nil), included.lines...), makefile.lines...)
-       mklines := NewMkLines(allLines)
+       allLines := append(append([]Line(nil), included.lines.Lines...), makefile.lines.Lines...)
+       mklines := NewMkLines(NewLines(included.lines.FileName, allLines))
 
        // XXX: The warnings from here are not in the same order as the other warnings.
        // XXX: There may be some warnings for the same file separated by warnings for other files.
@@ -954,6 +956,87 @@ func (s *Suite) Test_MkLines_Check__hack
        t.CheckOutputEmpty()
 }
 
+func (s *Suite) Test_MkLines_Check__MASTER_SITE_in_HOMEPAGE(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupMasterSite("MASTER_SITE_GITHUB", "https://github.com/";)
+       t.SetupVartypes()
+       G.Mk = t.NewMkLines("devel/catch/Makefile",
+               MkRcsID,
+               "HOMEPAGE=\t${MASTER_SITE_GITHUB:=philsquared/Catch/}",
+               "HOMEPAGE=\t${MASTER_SITE_GITHUB}",
+               "HOMEPAGE=\t${MASTER_SITES}",
+               "HOMEPAGE=\t${MASTER_SITES}${GITHUB_PROJECT}")
+
+       G.Mk.Check()
+
+       t.CheckOutputLines(
+               "WARN: devel/catch/Makefile:2: HOMEPAGE should not be defined in terms of MASTER_SITEs. Use https://github.com/philsquared/Catch/ directly.",
+               "WARN: devel/catch/Makefile:3: HOMEPAGE should not be defined in terms of MASTER_SITEs. Use https://github.com/ directly.",
+               "WARN: devel/catch/Makefile:4: HOMEPAGE should not be defined in terms of MASTER_SITEs.",
+               "WARN: devel/catch/Makefile:5: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
+}
+
+func (s *Suite) Test_MkLines_Check__VERSION_as_wordpart_in_MASTER_SITES(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       mklines := t.NewMkLines("geography/viking/Makefile",
+               MkRcsID,
+               "MASTER_SITES=\t${MASTER_SITE_SOURCEFORGE:=viking/}${VERSION}/")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: geography/viking/Makefile:2: "+
+                       "The list variable MASTER_SITE_SOURCEFORGE should not be embedded in a word.",
+               "WARN: geography/viking/Makefile:2: VERSION is used but not defined.")
+}
+
+func (s *Suite) Test_MkLines_Check__shell_command_as_word_part_in_ENV_list(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       mklines := t.NewMkLines("x11/lablgtk1/Makefile",
+               MkRcsID,
+               "CONFIGURE_ENV+=\tCC=${CC}")
+
+       mklines.Check()
+
+       t.CheckOutputLines(
+               "WARN: x11/lablgtk1/Makefile:2: Please use ${CC:Q} instead of ${CC}.",
+               "WARN: x11/lablgtk1/Makefile:2: Please use ${CC:Q} instead of ${CC}.")
+}
+
+func (s *Suite) Test_MkLines_Check__extra_warnings(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wextra")
+       t.SetupVartypes()
+       G.Pkg = NewPackage(t.File("category/pkgbase"))
+       G.Mk = t.NewMkLines("options.mk",
+               MkRcsID,
+               ".for word in ${PKG_FAIL_REASON}",
+               "PYTHON_VERSIONS_ACCEPTED=\t27 35 30",
+               "CONFIGURE_ARGS+=\t--sharedir=${PREFIX}/share/kde",
+               "COMMENT=\t# defined",
+               ".endfor",
+               "GAMES_USER?=pkggames",
+               "GAMES_GROUP?=pkggames",
+               "PLIST_SUBST+= CONDITIONAL=${CONDITIONAL}",
+               "CONDITIONAL=\"@comment\"",
+               "BUILD_DIRS=\t${WRKSRC}/../build")
+
+       G.Mk.Check()
+
+       t.CheckOutputLines(
+               "WARN: options.mk:3: The values for PYTHON_VERSIONS_ACCEPTED should be in decreasing order.",
+               "NOTE: options.mk:5: Please use \"# empty\", \"# none\" or \"yes\" instead of \"# defined\".",
+               "WARN: options.mk:7: Please include \"../../mk/bsd.prefs.mk\" before using \"?=\".",
+               "WARN: options.mk:11: Building the package should take place entirely inside ${WRKSRC}, not \"${WRKSRC}/..\".",
+               "NOTE: options.mk:11: You can use \"../build\" instead of \"${WRKSRC}/../build\".")
+}
+
 func (s *Suite) Test_MkLines_ForEach__conditional_variables(c *check.C) {
        t := s.Init(c)
 
@@ -989,3 +1072,115 @@ func (s *Suite) Test_MkLines_ForEach__co
        c.Check(seenDeveloper, equals, true)
        c.Check(seenUsesGettext, equals, true)
 }
+
+func (s *Suite) Test_VaralignBlock_Check__autofix(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wspace", "--show-autofix")
+
+       lines := t.NewLines("file.mk",
+               "VAR=   value",    // Indentation 7, fixed to 8.
+               "",                //
+               "VAR=    value",   // Indentation 8, fixed to 8.
+               "",                //
+               "VAR=     value",  // Indentation 9, fixed to 8.
+               "",                //
+               "VAR= \tvalue",    // Mixed indentation 8, fixed to 8.
+               "",                //
+               "VAR=   \tvalue",  // Mixed indentation 8, fixed to 8.
+               "",                //
+               "VAR=    \tvalue", // Mixed indentation 16, fixed to 16.
+               "",                //
+               "VAR=\tvalue")     // Already aligned with tabs only, left unchanged.
+
+       varalign := &VaralignBlock{}
+       for _, line := range lines.Lines {
+               varalign.Check(NewMkLine(line))
+       }
+       varalign.Finish()
+
+       t.CheckOutputLines(
+               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "AUTOFIX: file.mk:1: Replacing \"   \" with \"\\t\".",
+               "NOTE: file.mk:3: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:3: Replacing \"    \" with \"\\t\".",
+               "NOTE: file.mk:5: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "AUTOFIX: file.mk:5: Replacing \"     \" with \"\\t\".",
+               "NOTE: file.mk:7: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:7: Replacing \" \\t\" with \"\\t\".",
+               "NOTE: file.mk:9: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:9: Replacing \"   \\t\" with \"\\t\".",
+               "NOTE: file.mk:11: Variable values should be aligned with tabs, not spaces.",
+               "AUTOFIX: file.mk:11: Replacing \"    \\t\" with \"\\t\\t\".")
+}
+
+func (s *Suite) Test_VaralignBlock_Check__reduce_indentation(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wspace")
+       mklines := t.NewMkLines("file.mk",
+               "VAR= \tvalue",
+               "VAR=    \tvalue",
+               "VAR=\t\t\t\tvalue",
+               "",
+               "VAR=\t\t\tneedlessly", // Nothing to be fixed here, since it looks good.
+               "VAR=\t\t\tdeep",
+               "VAR=\t\t\tindentation")
+
+       varalign := new(VaralignBlock)
+       for _, mkline := range mklines.mklines {
+               varalign.Check(mkline)
+       }
+       varalign.Finish()
+
+       t.CheckOutputLines(
+               "NOTE: file.mk:1: Variable values should be aligned with tabs, not spaces.",
+               "NOTE: file.mk:2: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "NOTE: file.mk:3: This variable value should be aligned to column 9.")
+}
+
+func (s *Suite) Test_VaralignBlock_Check__longest_line_no_space(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wspace")
+       mklines := t.NewMkLines("file.mk",
+               "SUBST_CLASSES+= aaaaaaaa",
+               "SUBST_STAGE.aaaaaaaa= pre-configure",
+               "SUBST_FILES.aaaaaaaa= *.pl",
+               "SUBST_FILTER_CMD.aaaaaaaa=cat")
+
+       varalign := new(VaralignBlock)
+       for _, mkline := range mklines.mklines {
+               varalign.Check(mkline)
+       }
+       varalign.Finish()
+
+       t.CheckOutputLines(
+               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 33.",
+               "NOTE: file.mk:2: This variable value should be aligned with tabs, not spaces, to column 33.",
+               "NOTE: file.mk:3: This variable value should be aligned with tabs, not spaces, to column 33.",
+               "NOTE: file.mk:4: This variable value should be aligned to column 33.")
+}
+
+func (s *Suite) Test_VaralignBlock_Check__only_spaces(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupCommandLine("-Wspace")
+       mklines := t.NewMkLines("file.mk",
+               "SUBST_CLASSES+= aaaaaaaa",
+               "SUBST_STAGE.aaaaaaaa= pre-configure",
+               "SUBST_FILES.aaaaaaaa= *.pl",
+               "SUBST_FILTER_CMD.aaaaaaaa= cat")
+
+       varalign := new(VaralignBlock)
+       for _, mkline := range mklines.mklines {
+               varalign.Check(mkline)
+       }
+       varalign.Finish()
+
+       t.CheckOutputLines(
+               "NOTE: file.mk:1: This variable value should be aligned with tabs, not spaces, to column 33.",
+               "NOTE: file.mk:2: This variable value should be aligned with tabs, not spaces, to column 33.",
+               "NOTE: file.mk:3: This variable value should be aligned with tabs, not spaces, to column 33.",
+               "NOTE: file.mk:4: This variable value should be aligned with tabs, not spaces, to column 33.")
+}

Index: pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go:1.4 pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go:1.4  Wed Sep  5 17:56:22 2018
+++ pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go      Wed Nov  7 20:58:23 2018
@@ -46,6 +46,8 @@ func (vt *VaralignTester) Run() {
 }
 
 func (vt *VaralignTester) run(autofix bool) {
+       t := vt.tester
+
        cmdline := []string{"-Wall"}
        if autofix {
                cmdline = append(cmdline, "--autofix")
@@ -53,9 +55,9 @@ func (vt *VaralignTester) run(autofix bo
        if vt.source {
                cmdline = append(cmdline, "--source")
        }
-       vt.tester.SetupCommandLine(cmdline...)
+       t.SetupCommandLine(cmdline...)
 
-       mklines := vt.tester.SetupFileMkLines("Makefile", vt.input...)
+       mklines := t.SetupFileMkLines("Makefile", vt.input...)
 
        var varalign VaralignBlock
        for _, mkline := range mklines.mklines {
@@ -64,12 +66,20 @@ func (vt *VaralignTester) run(autofix bo
        varalign.Finish()
 
        if autofix {
-               vt.tester.CheckOutputLines(vt.autofixes...)
+               if len(vt.autofixes) > 0 {
+                       t.CheckOutputLines(vt.autofixes...)
+               } else {
+                       t.CheckOutputEmpty()
+               }
 
                SaveAutofixChanges(mklines.lines)
-               vt.tester.CheckFileLinesDetab("Makefile", vt.fixed...)
+               t.CheckFileLinesDetab("Makefile", vt.fixed...)
        } else {
-               vt.tester.CheckOutputLines(vt.diagnostics...)
+               if len(vt.diagnostics) > 0 {
+                       t.CheckOutputLines(vt.diagnostics...)
+               } else {
+                       t.CheckOutputEmpty()
+               }
        }
 }
 
@@ -686,7 +696,7 @@ func (s *Suite) Test_Varalign__outlier_6
        vt.Run()
 }
 
-// The long line is not an outlier, but very close. One more space, and
+// The long line is not an outlier but very close. One more space, and
 // it would count.
 func (s *Suite) Test_Varalign__outlier_10(c *check.C) {
        vt := NewVaralignTester(s, c)
@@ -758,7 +768,7 @@ func (s *Suite) Test_Varalign__outlier_1
 // since compared to the DIST line, it is at least two tabs away.
 // Pkglint before 2018-26-01 suggested that it "should be aligned to column 9",
 // which is not possible since the variable name is already longer.
-func (s *Suite) Test_MkLines__variable_alignment__long_short(c *check.C) {
+func (s *Suite) Test_Varalign__long_short(c *check.C) {
        vt := NewVaralignTester(s, c)
        vt.Input(
                "INSTALLATION_DIRS=\tbin",
Index: pkgsrc/pkgtools/pkglint/files/mktypes_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.4 pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.4   Wed Sep  5 17:56:22 2018
+++ pkgsrc/pkgtools/pkglint/files/mktypes_test.go       Wed Nov  7 20:58:23 2018
@@ -5,11 +5,15 @@ import (
 )
 
 func NewMkVarUse(varname string, modifiers ...string) *MkVarUse {
-       return &MkVarUse{varname, modifiers}
+       var mods []MkVarUseModifier
+       for _, modifier := range modifiers {
+               mods = append(mods, MkVarUseModifier{modifier})
+       }
+       return &MkVarUse{varname, mods}
 }
 
 func (s *Suite) Test_MkVarUse_Mod(c *check.C) {
-       varuse := &MkVarUse{"varname", []string{"Q"}}
+       varuse := NewMkVarUse("varname", "Q")
 
        c.Check(varuse.Mod(), equals, ":Q")
 }
@@ -19,3 +23,27 @@ func (list *MkShList) AddCommand(command
        andOr := NewMkShAndOr(pipeline)
        return list.AddAndOr(andOr)
 }
+
+func (s *Suite) Test_MkVarUseModifier_MatchSubst(c *check.C) {
+       mod := MkVarUseModifier{"S/from/to/1g"}
+
+       ok, regex, from, to, options := mod.MatchSubst()
+
+       c.Check(ok, equals, true)
+       c.Check(regex, equals, false)
+       c.Check(from, equals, "from")
+       c.Check(to, equals, "to")
+       c.Check(options, equals, "1g")
+}
+
+func (s *Suite) Test_MkVarUseModifier_MatchSubst__backslash(c *check.C) {
+       mod := MkVarUseModifier{"S/\\//\\:/"}
+
+       ok, regex, from, to, options := mod.MatchSubst()
+
+       c.Check(ok, equals, true)
+       c.Check(regex, equals, false)
+       c.Check(from, equals, "\\/")
+       c.Check(to, equals, "\\:")
+       c.Check(options, equals, "")
+}

Index: pkgsrc/pkgtools/pkglint/files/mkshparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshparser.go:1.7 pkgsrc/pkgtools/pkglint/files/mkshparser.go:1.8
--- pkgsrc/pkgtools/pkglint/files/mkshparser.go:1.7     Wed Sep  5 17:56:22 2018
+++ pkgsrc/pkgtools/pkglint/files/mkshparser.go Wed Nov  7 20:58:23 2018
@@ -2,7 +2,6 @@ package main
 
 import (
        "fmt"
-       "netbsd.org/pkglint/trace"
        "strconv"
 )
 

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.37 pkgsrc/pkgtools/pkglint/files/package.go:1.38
--- pkgsrc/pkgtools/pkglint/files/package.go:1.37       Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/package.go    Wed Nov  7 20:58:23 2018
@@ -3,11 +3,7 @@ package main
 import (
        "fmt"
        "netbsd.org/pkglint/pkgver"
-       "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
-       "os"
        "path"
-       "regexp"
        "strconv"
        "strings"
 )
@@ -16,22 +12,21 @@ const rePkgname = `^([\w\-.+]+)-(\d[.0-9
 
 // Package contains data for the pkgsrc package that is currently checked.
 type Package struct {
-       dir                  string          // The directory of the package, for resolving files
-       Pkgpath              string          // e.g. "category/pkgdir"
-       Pkgdir               string          // PKGDIR from the package Makefile
-       Filesdir             string          // FILESDIR from the package Makefile
-       Patchdir             string          // PATCHDIR from the package Makefile
-       DistinfoFile         string          // DISTINFO_FILE from the package Makefile
-       EffectivePkgname     string          // PKGNAME or DISTNAME from the package Makefile, including nb13
-       EffectivePkgbase     string          // The effective PKGNAME without the version
-       EffectivePkgversion  string          // The version part of the effective PKGNAME, excluding nb13
-       EffectivePkgnameLine MkLine          // The origin of the three effective_* values
-       PlistDirs            map[string]bool // Directories mentioned in the PLIST files
-       PlistFiles           map[string]bool // Regular files mentioned in the PLIST files
+       dir                  string       // The directory of the package, for resolving files
+       Pkgpath              string       // e.g. "category/pkgdir"
+       Pkgdir               string       // PKGDIR from the package Makefile
+       Filesdir             string       // FILESDIR from the package Makefile
+       Patchdir             string       // PATCHDIR from the package Makefile
+       DistinfoFile         string       // DISTINFO_FILE from the package Makefile
+       EffectivePkgname     string       // PKGNAME or DISTNAME from the package Makefile, including nb13
+       EffectivePkgbase     string       // The effective PKGNAME without the version
+       EffectivePkgversion  string       // The version part of the effective PKGNAME, excluding nb13
+       EffectivePkgnameLine MkLine       // The origin of the three effective_* values
+       Plist                PlistContent // Files and directories mentioned in the PLIST files
 
        vars                  Scope
        bl3                   map[string]Line // buildlink3.mk name => line; contains only buildlink3.mk files that are directly included.
-       included              map[string]Line // fname => line
+       included              map[string]Line // fileName => line
        seenMakefileCommon    bool            // Does the package have any .includes?
        conditionalIncludes   map[string]MkLine
        unconditionalIncludes map[string]MkLine
@@ -52,8 +47,7 @@ func NewPackage(dir string) *Package {
                Filesdir:              "files",
                Patchdir:              "patches",
                DistinfoFile:          "${PKGDIR}/distinfo",
-               PlistDirs:             make(map[string]bool),
-               PlistFiles:            make(map[string]bool),
+               Plist:                 NewPlistContent(),
                vars:                  NewScope(),
                bl3:                   make(map[string]Line),
                included:              make(map[string]Line),
@@ -73,11 +67,11 @@ func NewPackage(dir string) *Package {
        return pkg
 }
 
-// File returns the (possibly absolute) path to relativeFilename,
+// File returns the (possibly absolute) path to relativeFileName,
 // as resolved from the package's directory.
 // Variables that are known in the package are resolved, e.g. ${PKGDIR}.
-func (pkg *Package) File(relativeFilename string) string {
-       return cleanpath(resolveVariableRefs(pkg.dir + "/" + relativeFilename))
+func (pkg *Package) File(relativeFileName string) string {
+       return cleanpath(resolveVariableRefs(pkg.dir + "/" + relativeFileName))
 }
 
 func (pkg *Package) checkPossibleDowngrade() {
@@ -103,7 +97,7 @@ func (pkg *Package) checkPossibleDowngra
        if change.Action == "Updated" {
                changeVersion := replaceAll(change.Version, `nb\d+$`, "")
                if pkgver.Compare(pkgversion, changeVersion) < 0 {
-                       mkline.Warnf("The package is being downgraded from %s (see %s) to %s.", change.Version, change.Line.ReferenceFrom(mkline.Line), pkgversion)
+                       mkline.Warnf("The package is being downgraded from %s (see %s) to %s.", change.Version, mkline.Line.RefTo(change.Line), pkgversion)
                        Explain(
                                "The files in doc/CHANGES-*, in which all version changes are",
                                "recorded, have a higher version number than what the package says.",
@@ -113,7 +107,7 @@ func (pkg *Package) checkPossibleDowngra
        }
 }
 
-func (pkg *Package) checklinesBuildlink3Inclusion(mklines *MkLines) {
+func (pkg *Package) checklinesBuildlink3Inclusion(mklines MkLines) {
        if trace.Tracing {
                defer trace.Call0()()
        }
@@ -141,109 +135,29 @@ func (pkg *Package) checklinesBuildlink3
        }
 }
 
-// checkdirPackage checks a complete pkgsrc package, including each
-// of the files individually, and also when seen in combination.
-func (pkglint *Pkglint) checkdirPackage(dir string) {
+func (pkg *Package) loadPackageMakefile() MkLines {
+       fileName := pkg.File("Makefile")
        if trace.Tracing {
-               defer trace.Call1(dir)()
+               defer trace.Call1(fileName)()
        }
 
-       G.Pkg = NewPackage(dir)
-       defer func() { G.Pkg = nil }()
-       pkg := G.Pkg
-
-       // we need to handle the Makefile first to get some variables
-       lines := pkg.loadPackageMakefile()
-       if lines == nil {
-               return
-       }
-
-       files := dirglob(pkg.File("."))
-       if pkg.Pkgdir != "." {
-               files = append(files, dirglob(pkg.File(pkg.Pkgdir))...)
-       }
-       if G.opts.CheckExtra {
-               files = append(files, dirglob(pkg.File(pkg.Filesdir))...)
-       }
-       files = append(files, dirglob(pkg.File(pkg.Patchdir))...)
-       if pkg.DistinfoFile != pkg.vars.fallback["DISTINFO_FILE"] {
-               files = append(files, pkg.File(pkg.DistinfoFile))
-       }
-
-       haveDistinfo := false
-       havePatches := false
-
-       // Determine the used variables and PLIST directories before checking any of the Makefile fragments.
-       for _, fname := range files {
-               basename := path.Base(fname)
-               if (hasPrefix(basename, "Makefile.") || hasSuffix(fname, ".mk")) &&
-                       !matches(fname, `patch-`) &&
-                       !contains(fname, pkg.Pkgdir+"/") &&
-                       !contains(fname, pkg.Filesdir+"/") {
-                       if mklines := LoadMk(fname, MustSucceed); mklines != nil {
-                               mklines.DetermineUsedVariables()
-                       }
-               }
-               if hasPrefix(basename, "PLIST") {
-                       pkg.loadPlistDirs(fname)
-               }
-       }
-
-       for _, fname := range files {
-               if containsVarRef(fname) {
-                       if trace.Tracing {
-                               trace.Stepf("Skipping file %q because the name contains an unresolved variable.", fname)
-                       }
-                       continue
-               }
-
-               if path.Base(fname) == "Makefile" {
-                       if st, err := os.Lstat(fname); err == nil {
-                               pkglint.checkExecutable(fname, st)
-                       }
-                       if G.opts.CheckMakefile {
-                               pkg.checkfilePackageMakefile(fname, lines)
-                       }
-               } else {
-                       pkglint.Checkfile(fname)
-               }
-               if contains(fname, "/patches/patch-") {
-                       havePatches = true
-               } else if hasSuffix(fname, "/distinfo") {
-                       haveDistinfo = true
-               }
-               pkg.checkLocallyModified(fname)
-       }
-
-       if pkg.Pkgdir == "." && G.opts.CheckDistinfo && G.opts.CheckPatches {
-               if havePatches && !haveDistinfo {
-                       NewLineWhole(pkg.File(pkg.DistinfoFile)).Warnf("File not found. Please run \"%s makepatchsum\".", confMake)
-               }
-       }
-}
-
-func (pkg *Package) loadPackageMakefile() *MkLines {
-       fname := pkg.File("Makefile")
-       if trace.Tracing {
-               defer trace.Call1(fname)()
-       }
-
-       mainLines, allLines := NewMkLines(nil), NewMkLines(nil)
-       if _, result := pkg.readMakefile(fname, mainLines, allLines, ""); !result {
-               LoadMk(fname, NotEmpty|LogErrors) // Just for the LogErrors.
+       mainLines := NewMkLines(NewLines(fileName, nil))
+       allLines := NewMkLines(NewLines("", nil))
+       if _, result := pkg.readMakefile(fileName, mainLines, allLines, ""); !result {
+               LoadMk(fileName, NotEmpty|LogErrors) // Just for the LogErrors.
                return nil
        }
 
-       if G.opts.DumpMakefile {
+       if G.Opts.DumpMakefile {
                G.logOut.WriteLine("Whole Makefile (with all included files) follows:")
-               for _, line := range allLines.lines {
+               for _, line := range allLines.lines.Lines {
                        G.logOut.WriteLine(line.String())
                }
        }
 
        if pkg.vars.Defined("USE_CMAKE") {
-               mainLines.Tools.defTool("cmake", "", false, AtRunTime)
-               mainLines.Tools.defTool("cpack", "", false, AtRunTime)
+               mainLines.Tools.def("cmake", "", false, AtRunTime)
+               mainLines.Tools.def("cpack", "", false, AtRunTime)
        }
 
        allLines.DetermineUsedVariables()
@@ -279,12 +193,12 @@ func (pkg *Package) loadPackageMakefile(
        return mainLines
 }
 
-func (pkg *Package) readMakefile(fname string, mainLines *MkLines, allLines *MkLines, includingFnameForUsedCheck string) (exists bool, result bool) {
+func (pkg *Package) readMakefile(fileName string, mainLines MkLines, allLines MkLines, includingFnameForUsedCheck string) (exists bool, result bool) {
        if trace.Tracing {
-               defer trace.Call1(fname)()
+               defer trace.Call1(fileName)()
        }
 
-       fileMklines := LoadMk(fname, NotEmpty)
+       fileMklines := LoadMk(fileName, NotEmpty)
        if fileMklines == nil {
                return false, false
        }
@@ -296,17 +210,17 @@ func (pkg *Package) readMakefile(fname s
        lineAction := func(mkline MkLine) bool {
                if isMainMakefile {
                        mainLines.mklines = append(mainLines.mklines, mkline)
-                       mainLines.lines = append(mainLines.lines, mkline.Line)
+                       mainLines.lines.Lines = append(mainLines.lines.Lines, mkline.Line)
                }
                allLines.mklines = append(allLines.mklines, mkline)
-               allLines.lines = append(allLines.lines, mkline.Line)
+               allLines.lines.Lines = append(allLines.lines.Lines, mkline.Line)
 
                var includeFile, incDir, incBase string
                if mkline.IsInclude() {
                        inc := mkline.IncludeFile()
                        includeFile = resolveVariableRefs(mkline.ResolveVarsInRelativePath(inc, true))
                        if containsVarRef(includeFile) {
-                               if !contains(fname, "/mk/") {
+                               if !contains(fileName, "/mk/") {
                                        mkline.Notef("Skipping include file %q. This may result in false warnings.", includeFile)
                                }
                                includeFile = ""
@@ -340,16 +254,16 @@ func (pkg *Package) readMakefile(fname s
                                pkg.seenMakefileCommon = true
                        }
 
-                       skip := contains(fname, "/mk/") || hasSuffix(includeFile, "/bsd.pkg.mk") || IsPrefs(includeFile)
+                       skip := contains(fileName, "/mk/") || hasSuffix(includeFile, "/bsd.pkg.mk") || IsPrefs(includeFile)
                        if !skip {
-                               dirname, _ := path.Split(fname)
+                               dirname, _ := path.Split(fileName)
                                dirname = cleanpath(dirname)
 
                                fullIncluded := dirname + "/" + includeFile
                                if trace.Tracing {
                                        trace.Step1("Including %q.", fullIncluded)
                                }
-                               fullIncluding := ifelseStr(incBase == "Makefile.common" && incDir != "", fname, "")
+                               fullIncluding := ifelseStr(incBase == "Makefile.common" && incDir != "", fileName, "")
                                innerExists, innerResult := pkg.readMakefile(fullIncluded, mainLines, allLines, fullIncluding)
 
                                if !innerExists {
@@ -398,15 +312,15 @@ func (pkg *Package) readMakefile(fname s
        fileMklines.ForEachEnd(lineAction, atEnd)
 
        if includingFnameForUsedCheck != "" {
-               fileMklines.checkForUsedComment(G.Pkgsrc.ToRel(includingFnameForUsedCheck))
+               fileMklines.CheckForUsedComment(G.Pkgsrc.ToRel(includingFnameForUsedCheck))
        }
 
        return
 }
 
-func (pkg *Package) checkfilePackageMakefile(fname string, mklines *MkLines) {
+func (pkg *Package) checkfilePackageMakefile(fileName string, mklines MkLines) {
        if trace.Tracing {
-               defer trace.Call1(fname)()
+               defer trace.Call1(fileName)()
        }
 
        vars := pkg.vars
@@ -415,7 +329,7 @@ func (pkg *Package) checkfilePackageMake
                !vars.Defined("META_PACKAGE") &&
                !fileExists(pkg.File(pkg.Pkgdir+"/PLIST")) &&
                !fileExists(pkg.File(pkg.Pkgdir+"/PLIST.common")) {
-               NewLineWhole(fname).Warnf("Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.")
+               NewLineWhole(fileName).Warnf("Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.")
        }
 
        if (vars.Defined("NO_CHECKSUM") || vars.Defined("META_PACKAGE")) && isEmptyDir(pkg.File(pkg.Patchdir)) {
@@ -424,16 +338,17 @@ func (pkg *Package) checkfilePackageMake
                }
        } else {
                if distinfoFile := pkg.File(pkg.DistinfoFile); !containsVarRef(distinfoFile) && !fileExists(distinfoFile) {
-                       NewLineWhole(distinfoFile).Warnf("File not found. Please run \"%s makesum\" or define NO_CHECKSUM=yes in the package Makefile.", confMake)
+                       NewLineWhole(distinfoFile).Warnf(
+                               "File not found. Please run %q or define NO_CHECKSUM=yes in the package Makefile.", bmake("makesum"))
                }
        }
 
        if perlLine, noconfLine := vars.FirstDefinition("REPLACE_PERL"), vars.FirstDefinition("NO_CONFIGURE"); perlLine != nil && noconfLine != nil {
-               perlLine.Warnf("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s).", noconfLine.ReferenceFrom(perlLine.Line))
+               perlLine.Warnf("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s).", perlLine.RefTo(noconfLine))
        }
 
        if !vars.Defined("LICENSE") && !vars.Defined("META_PACKAGE") && pkg.once.FirstTime("LICENSE") {
-               NewLineWhole(fname).Errorf("Each package must define its LICENSE.")
+               NewLineWhole(fileName).Errorf("Each package must define its LICENSE.")
        }
 
        pkg.checkGnuConfigureUseLanguages()
@@ -441,12 +356,12 @@ func (pkg *Package) checkfilePackageMake
        pkg.checkPossibleDowngrade()
 
        if !vars.Defined("COMMENT") {
-               NewLineWhole(fname).Warnf("No COMMENT given.")
+               NewLineWhole(fileName).Warnf("No COMMENT given.")
        }
 
        if imake, x11 := vars.FirstDefinition("USE_IMAKE"), vars.FirstDefinition("USE_X11"); imake != nil && x11 != nil {
-               if !hasSuffix(x11.Filename, "/mk/x11.buildlink3.mk") {
-                       imake.Notef("USE_IMAKE makes USE_X11 in %s superfluous.", x11.ReferenceFrom(imake.Line))
+               if !hasSuffix(x11.FileName, "/mk/x11.buildlink3.mk") {
+                       imake.Notef("USE_IMAKE makes USE_X11 in %s superfluous.", imake.RefTo(x11))
                }
        }
 
@@ -465,9 +380,9 @@ func (pkg *Package) checkGnuConfigureUse
                        // probably contains a statement that C is
                        // really not needed.
 
-               } else if !matches(useLine.Value(), `(?:^|\s+)(?:c|c99|objc)(?:\s+|$)`) {
+               } else if !matches(useLine.Value(), `(?:^|[\t ]+)(?:c|c99|objc)(?:[\t ]+|$)`) {
                        gnuLine.Warnf("GNU_CONFIGURE almost always needs a C compiler, but \"c\" is not added to USE_LANGUAGES in %s.",
-                               useLine.ReferenceFrom(gnuLine.Line))
+                               gnuLine.RefTo(useLine))
                }
        }
 }
@@ -532,28 +447,15 @@ func (pkg *Package) determineEffectivePk
 func (pkg *Package) pkgnameFromDistname(pkgname, distname string) string {
        tokens := NewMkParser(dummyLine, pkgname, false).MkTokens()
 
-       // Example:
-       //  subst("distname-1.0", "S,name,file,g") => "distfile-1.0"
-       subst := func(str, smod string) string {
-               qsep := regexp.QuoteMeta(smod[1:2])
-               m, left, from, right, to, flags := match5(smod, regex.Pattern(`^S`+qsep+`(\^?)([^:]*?)(\$?)`+qsep+`([^:]*)`+qsep+`([1g]*)$`))
-               G.Assertf(m, "pkgnameFromDistname %q", smod)
-               result := mkopSubst(str, left != "", from, right != "", to, flags)
-               if trace.Tracing && result != str {
-                       trace.Stepf("pkgnameFromDistname.subst: %q %q => %q", str, smod, result)
-               }
-               return result
-       }
-
        result := ""
        for _, token := range tokens {
                if token.Varuse != nil && token.Varuse.varname == "DISTNAME" {
                        newDistname := distname
                        for _, mod := range token.Varuse.modifiers {
-                               if mod == "tl" {
+                               if mod.IsToLower() {
                                        newDistname = strings.ToLower(newDistname)
-                               } else if hasPrefix(mod, "S") {
-                                       newDistname = subst(newDistname, mod)
+                               } else if m, regex, _, _, _ := mod.MatchSubst(); m && !regex {
+                                       newDistname = mod.Subst(newDistname)
                                } else {
                                        newDistname = token.Text
                                        break
@@ -600,12 +502,12 @@ func (pkg *Package) checkUpdate() {
 // the most common variables appear in a fixed order.
 // The order itself is a little arbitrary but provides
 // at least a bit of consistency.
-func (pkg *Package) CheckVarorder(mklines *MkLines) {
+func (pkg *Package) CheckVarorder(mklines MkLines) {
        if trace.Tracing {
                defer trace.Call0()()
        }
 
-       if !G.opts.WarnOrder || pkg.seenMakefileCommon {
+       if !G.Opts.WarnOrder || pkg.seenMakefileCommon {
                return
        }
 
@@ -725,10 +627,10 @@ func (pkg *Package) CheckVarorder(mkline
                interesting := mklines.mklines[firstRelevant : lastRelevant+1]
 
                varcanon := func() string {
-                       for len(interesting) != 0 && interesting[0].IsComment() {
+                       for len(interesting) > 0 && interesting[0].IsComment() {
                                interesting = interesting[1:]
                        }
-                       if len(interesting) != 0 && (interesting[0].IsVarassign() || interesting[0].IsCommentedVarassign()) {
+                       if len(interesting) > 0 && (interesting[0].IsVarassign() || interesting[0].IsCommentedVarassign()) {
                                return interesting[0].Varcanon()
                        }
                        return ""
@@ -759,7 +661,7 @@ func (pkg *Package) CheckVarorder(mkline
                                }
                        }
 
-                       for len(interesting) != 0 && (interesting[0].IsEmpty() || interesting[0].IsComment()) {
+                       for len(interesting) > 0 && (interesting[0].IsEmpty() || interesting[0].IsComment()) {
                                interesting = interesting[1:]
                        }
                }
@@ -787,11 +689,11 @@ func (pkg *Package) CheckVarorder(mkline
                                canonical = append(canonical, variable.varname)
                        }
                }
-               if len(canonical) != 0 && canonical[len(canonical)-1] != "empty line" {
+               if len(canonical) > 0 && canonical[len(canonical)-1] != "empty line" {
                        canonical = append(canonical, "empty line")
                }
        }
-       if len(canonical) != 0 && canonical[len(canonical)-1] == "empty line" {
+       if len(canonical) > 0 && canonical[len(canonical)-1] == "empty line" {
                canonical = canonical[:len(canonical)-1]
        }
 
@@ -805,44 +707,9 @@ func (pkg *Package) CheckVarorder(mkline
                "\"Package components\", subsection \"Makefile\" for more information.")
 }
 
-func (mklines *MkLines) checkForUsedComment(relativeName string) {
-       lines := mklines.lines
-       if len(lines) < 3 {
-               return
-       }
-
-       expected := "# used by " + relativeName
-       for _, line := range lines {
-               if line.Text == expected {
-                       return
-               }
-       }
-
-       i := 0
-       for i < 2 && hasPrefix(lines[i].Text, "#") {
-               i++
-       }
-
-       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)
-}
-
-func (pkg *Package) checkLocallyModified(fname string) {
+func (pkg *Package) checkLocallyModified(fileName string) {
        if trace.Tracing {
-               defer trace.Call(fname)()
+               defer trace.Call(fileName)()
        }
 
        owner, _ := pkg.vars.Value("OWNER")
@@ -863,15 +730,15 @@ func (pkg *Package) checkLocallyModified
                return
        }
 
-       if isLocallyModified(fname) {
+       if isLocallyModified(fileName) {
                if owner != "" {
-                       NewLineWhole(fname).Warnf("Don't commit changes to this file without asking the OWNER, %s.", owner)
+                       NewLineWhole(fileName).Warnf("Don't commit changes to this file without asking the OWNER, %s.", owner)
                        Explain(
                                "See the pkgsrc guide, section \"Package components\",",
                                "keyword \"owner\", for more information.")
                }
                if maintainer != "" {
-                       NewLineWhole(fname).Notef("Please only commit changes that %s would approve.", maintainer)
+                       NewLineWhole(fileName).Notef("Please only commit changes that %s would approve.", maintainer)
                        Explain(
                                "See the pkgsrc guide, section \"Package components\",",
                                "keyword \"maintainer\", for more information.")
@@ -886,20 +753,20 @@ func (pkg *Package) CheckInclude(mkline 
                mkline.SetConditionalVars(conditionalVars)
        }
 
-       if path.Dir(abspath(mkline.Filename)) == abspath(pkg.File(".")) {
+       if path.Dir(abspath(mkline.FileName)) == abspath(pkg.File(".")) {
                includefile := mkline.IncludeFile()
 
                if indentation.IsConditional() {
                        pkg.conditionalIncludes[includefile] = mkline
                        if other := pkg.unconditionalIncludes[includefile]; other != nil {
                                mkline.Warnf("%q is included conditionally here (depending on %s) and unconditionally in %s.",
-                                       cleanpath(includefile), mkline.ConditionalVars(), other.ReferenceFrom(mkline.Line))
+                                       cleanpath(includefile), mkline.ConditionalVars(), mkline.RefTo(other))
                        }
                } else {
                        pkg.unconditionalIncludes[includefile] = mkline
                        if other := pkg.conditionalIncludes[includefile]; other != nil {
                                mkline.Warnf("%q is included unconditionally here and conditionally in %s (depending on %s).",
-                                       cleanpath(includefile), other.ReferenceFrom(mkline.Line), other.ConditionalVars())
+                                       cleanpath(includefile), mkline.RefTo(other), other.ConditionalVars())
                        }
                }
        }
@@ -907,14 +774,25 @@ func (pkg *Package) CheckInclude(mkline 
 
 func (pkg *Package) loadPlistDirs(plistFilename string) {
        lines := Load(plistFilename, MustSucceed)
-       for _, line := range lines {
+       for _, line := range lines.Lines {
                text := line.Text
-               pkg.PlistFiles[text] = true // XXX: ignores PLIST conditions for now
+               pkg.Plist.Files[text] = true // XXX: ignores PLIST conditions for now
                // Keep in sync with PlistChecker.collectFilesAndDirs
                if !contains(text, "$") && !contains(text, "@") {
                        for dir := path.Dir(text); dir != "."; dir = path.Dir(dir) {
-                               pkg.PlistDirs[dir] = true
+                               pkg.Plist.Dirs[dir] = true
                        }
                }
        }
 }
+
+type PlistContent struct {
+       Dirs  map[string]bool
+       Files map[string]bool
+}
+
+func NewPlistContent() PlistContent {
+       return PlistContent{
+               make(map[string]bool),
+               make(map[string]bool)}
+}

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.31 pkgsrc/pkgtools/pkglint/files/package_test.go:1.32
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.31  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Wed Nov  7 20:58:23 2018
@@ -23,7 +23,7 @@ func (s *Suite) Test_Package_checklinesB
 
        t.CreateFileLines("category/dependency/buildlink3.mk")
        G.Pkg = NewPackage(t.File("category/package"))
-       G.Pkg.bl3["../../category/dependency/buildlink3.mk"] = t.NewLine("fname", 1, "")
+       G.Pkg.bl3["../../category/dependency/buildlink3.mk"] = t.NewLine("fileName", 1, "")
        mklines := t.NewMkLines("category/package/buildlink3.mk",
                MkRcsID)
 
@@ -403,88 +403,6 @@ func (s *Suite) Test_Package_loadPackage
                "~/category/package/Makefile:6: LICENSE=\t2-clause-bsd")
 }
 
-func (s *Suite) Test_Pkglint_checkdirPackage(c *check.C) {
-       t := s.Init(c)
-
-       t.Chdir("category/package")
-       t.CreateFileLines("Makefile",
-               MkRcsID)
-
-       G.checkdirPackage(".")
-
-       t.CheckOutputLines(
-               "WARN: Makefile: Neither PLIST nor PLIST.common exist, and PLIST_SRC is unset.",
-               "WARN: distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
-               "ERROR: Makefile: Each package must define its LICENSE.",
-               "WARN: Makefile: No COMMENT given.")
-}
-
-func (s *Suite) Test_Pkglint_checkdirPackage__PKGDIR(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupVartypes()
-       t.SetupPkgsrc()
-       t.CreateFileLines("category/Makefile")
-       t.CreateFileLines("other/package/Makefile",
-               MkRcsID)
-       t.CreateFileLines("other/package/PLIST",
-               PlistRcsID,
-               "bin/program")
-       t.CreateFileLines("other/package/distinfo",
-               RcsID,
-               "",
-               "SHA1 (patch-aa) = da39a3ee5e6b4b0d3255bfef95601890afd80709")
-       t.CreateFileLines("category/package/patches/patch-aa",
-               RcsID)
-       t.Chdir("category/package")
-       t.CreateFileLines("Makefile",
-               MkRcsID,
-               "",
-               "CATEGORIES=category",
-               "",
-               "COMMENT=\tComment",
-               "LICENSE=\t2-clause-bsd",
-               "PKGDIR=\t../../other/package")
-
-       // DISTINFO_FILE is resolved relative to PKGDIR, the other places
-       // are resolved relative to the package base directory.
-       G.checkdirPackage(".")
-
-       t.CheckOutputLines(
-               "ERROR: patches/patch-aa:1: Patch files must not be empty.")
-}
-
-func (s *Suite) Test_Pkglint_checkdirPackage__patch_without_distinfo(c *check.C) {
-       t := s.Init(c)
-
-       pkg := t.SetupPackage("category/package")
-       t.CreateFileDummyPatch("category/package/patches/patch-aa")
-       t.Remove("category/package/distinfo")
-
-       G.CheckDirent(pkg)
-
-       // FIXME: One of the below warnings is redundant.
-       t.CheckOutputLines(
-               "WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makesum\" or define NO_CHECKSUM=yes in the package Makefile.",
-               "WARN: ~/category/package/distinfo: File not found. Please run \""+confMake+" makepatchsum\".")
-}
-
-func (s *Suite) Test_Pkglint_checkdirPackage__meta_package_without_license(c *check.C) {
-       t := s.Init(c)
-
-       t.Chdir("category/package")
-       t.CreateFileLines("Makefile",
-               MkRcsID,
-               "",
-               "META_PACKAGE=\tyes")
-       t.SetupVartypes()
-
-       G.checkdirPackage(".")
-
-       t.CheckOutputLines(
-               "WARN: Makefile: No COMMENT given.") // No error about missing LICENSE.
-}
-
 func (s *Suite) Test_Package__varuse_at_load_time(c *check.C) {
        t := s.Init(c)
 
@@ -582,8 +500,10 @@ func (s *Suite) Test_Package_loadPackage
        // A file including itself does not lead to an endless loop while parsing
        // but may still produce unexpected warnings, such as redundant definitions.
        t.CheckOutputLines(
-               "NOTE: ~/category/package/Makefile:3: Definition of PKGNAME is redundant because of Makefile:3.",
-               "NOTE: ~/category/package/Makefile:4: Definition of DISTNAME is redundant because of Makefile:4.")
+               "NOTE: ~/category/package/Makefile:3: Definition of PKGNAME is redundant "+
+                       "because of ../../category/package/Makefile:3.",
+               "NOTE: ~/category/package/Makefile:4: Definition of DISTNAME is redundant "+
+                       "because of ../../category/package/Makefile:4.")
 }
 
 func (s *Suite) Test_Package_loadPackageMakefile__PECL_VERSION(c *check.C) {
@@ -609,8 +529,6 @@ func (s *Suite) Test_Package_loadPackage
                ".include \"../../lang/php/ext.mk\"")
 
        G.CheckDirent(pkg)
-
-       t.CheckOutputLines()
 }
 
 func (s *Suite) Test_Package_CheckInclude__conditional_and_unconditional_include(c *check.C) {
@@ -626,8 +544,8 @@ func (s *Suite) Test_Package_CheckInclud
        t.CreateFileLines("Makefile",
                MkRcsID,
                "",
-               "COMMENT\t=Description",
-               "LICENSE\t= gnu-gpl-v2",
+               "COMMENT=\tDescription",
+               "LICENSE=\tgnu-gpl-v2",
                ".include \"../../devel/zlib/buildlink3.mk\"",
                ".if ${OPSYS} == \"Linux\"",
                ".include \"../../sysutils/coreutils/buildlink3.mk\"",
@@ -757,7 +675,7 @@ func (s *Suite) Test_Package__redundant_
        G.checkdirPackage(t.File("math/R-date"))
 
        t.CheckOutputLines(
-               "NOTE: ~/math/R-date/Makefile:6: Definition of MASTER_SITES is redundant because of ../R/Makefile.extension:4.")
+               "NOTE: ~/math/R-date/Makefile:6: Definition of MASTER_SITES is redundant because of ../../math/R/Makefile.extension:4.")
 }
 
 func (s *Suite) Test_Package_checkUpdate(c *check.C) {
@@ -1035,27 +953,3 @@ func (s *Suite) Test_Package_checkLocall
 
        t.CheckOutputEmpty()
 }
-
-func (s *Suite) Test_Pkglint_checkdirPackage__filename_with_variable(c *check.C) {
-       t := s.Init(c)
-
-       t.SetupCommandLine("-Wall,no-order")
-       pkg := t.SetupPackage("category/package",
-               ".include \"../../mk/bsd.prefs.mk\"",
-               "",
-               "RUBY_VERSIONS_ACCEPTED=\t22 23 24 25", // As of 2018.
-               ".for rv in ${RUBY_VERSIONS_ACCEPTED}",
-               "RUBY_VER?=\t\t${rv}",
-               ".endfor",
-               "",
-               "RUBY_PKGDIR=\t../../lang/ruby-${RUBY_VER}-base",
-               "DISTINFO_FILE=\t${RUBY_PKGDIR}/distinfo")
-
-       // Pkglint cannot currently resolve the location of DISTINFO_FILE completely
-       // because the variable \"rv\" comes from a .for loop.
-       //
-       // TODO: iterate over variables in simple .for loops like the above.
-       G.CheckDirent(pkg)
-
-       t.CheckOutputEmpty()
-}

Index: pkgsrc/pkgtools/pkglint/files/parser.go
diff -u pkgsrc/pkgtools/pkglint/files/parser.go:1.10 pkgsrc/pkgtools/pkglint/files/parser.go:1.11
--- pkgsrc/pkgtools/pkglint/files/parser.go:1.10        Wed Oct  3 22:27:53 2018
+++ pkgsrc/pkgtools/pkglint/files/parser.go     Wed Nov  7 20:58:23 2018
@@ -16,7 +16,7 @@ func NewParser(line Line, s string, emit
 }
 
 func (p *Parser) EOF() bool {
-       return p.repl.EOF()
+       return p.repl.Rest() == ""
 }
 
 func (p *Parser) Rest() string {
@@ -30,7 +30,7 @@ func (p *Parser) PkgbasePattern() (pkgba
                if repl.AdvanceRegexp(`^\$\{\w+\}`) ||
                        repl.AdvanceRegexp(`^[\w.*+,{}]+`) ||
                        repl.AdvanceRegexp(`^\[[\d-]+\]`) {
-                       pkgbase += repl.Group(0)
+                       pkgbase += repl.Str()
                        continue
                }
 
@@ -73,7 +73,7 @@ func (p *Parser) Dependency() *Dependenc
                op := repl.Str()
                if repl.AdvanceRegexp(`^(?:(?:\$\{\w+\})+|\d[\w.]*)`) {
                        dp.LowerOp = op
-                       dp.Lower = repl.Group(0)
+                       dp.Lower = repl.Str()
                } else {
                        repl.Reset(mark2)
                }
@@ -82,7 +82,7 @@ func (p *Parser) Dependency() *Dependenc
                op := repl.Str()
                if repl.AdvanceRegexp(`^(?:(?:\$\{\w+\})+|\d[\w.]*)`) {
                        dp.UpperOp = op
-                       dp.Upper = repl.Group(0)
+                       dp.Upper = repl.Str()
                } else {
                        repl.Reset(mark2)
                }
@@ -90,7 +90,7 @@ func (p *Parser) Dependency() *Dependenc
        if dp.LowerOp != "" || dp.UpperOp != "" {
                return &dp
        }
-       if repl.AdvanceStr("-") && !repl.EOF() {
+       if repl.AdvanceStr("-") && repl.Rest() != "" {
                dp.Wildcard = repl.AdvanceRest()
                return &dp
        }

Index: pkgsrc/pkgtools/pkglint/files/patches.go
diff -u pkgsrc/pkgtools/pkglint/files/patches.go:1.23 pkgsrc/pkgtools/pkglint/files/patches.go:1.24
--- pkgsrc/pkgtools/pkglint/files/patches.go:1.23       Wed Sep  5 17:56:22 2018
+++ pkgsrc/pkgtools/pkglint/files/patches.go    Wed Nov  7 20:58:23 2018
@@ -3,29 +3,28 @@ package main
 // Checks for patch files.
 
 import (
-       "netbsd.org/pkglint/trace"
        "path"
        "strings"
 )
 
-func ChecklinesPatch(lines []Line) {
+func ChecklinesPatch(lines Lines) {
        if trace.Tracing {
-               defer trace.Call1(lines[0].Filename)()
+               defer trace.Call1(lines.FileName)()
        }
 
        (&PatchChecker{lines, NewExpecter(lines), false, false}).Check()
 }
 
 type PatchChecker struct {
-       lines             []Line
+       lines             Lines
        exp               *Expecter
        seenDocumentation bool
        previousLineEmpty bool
 }
 
 const (
-       rePatchUniFileDel = `^---\s(\S+)(?:\s+(.*))?$`
-       rePatchUniFileAdd = `^\+\+\+\s(\S+)(?:\s+(.*))?$`
+       rePatchUniFileDel = `^---[\t ]([^\t ]+)(?:[\t ]+(.*))?$`
+       rePatchUniFileAdd = `^\+\+\+[\t ]([^\t ]+)(?:[\t ]+(.*))?$`
        rePatchUniHunk    = `^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$`
 )
 
@@ -34,15 +33,15 @@ func (ck *PatchChecker) Check() {
                defer trace.Call0()()
        }
 
-       if CheckLineRcsid(ck.lines[0], ``, "") {
+       if CheckLineRcsid(ck.lines.Lines[0], ``, "") {
                ck.exp.Advance()
        }
        if ck.exp.EOF() {
-               ck.lines[0].Errorf("Patch files must not be empty.")
+               ck.lines.Lines[0].Errorf("Patch files must not be empty.")
                return
        }
 
-       ck.previousLineEmpty = ck.exp.ExpectEmptyLine(G.opts.WarnSpace)
+       ck.previousLineEmpty = ck.exp.ExpectEmptyLine()
 
        patchedFiles := 0
        for !ck.exp.EOF() {
@@ -71,8 +70,8 @@ func (ck *PatchChecker) Check() {
                        ck.exp.StepBack()
                }
 
-               if ck.exp.AdvanceIfMatches(`^\*\*\*\s(\S+)(.*)$`) {
-                       if ck.exp.AdvanceIfMatches(`^---\s(\S+)(.*)$`) {
+               if ck.exp.AdvanceIfMatches(`^\*\*\*[\t ]([^\t ]+)(.*)$`) {
+                       if ck.exp.AdvanceIfMatches(`^---[\t ]([^\t ]+)(.*)$`) {
                                ck.checkBeginDiff(line, patchedFiles)
                                line.Warnf("Please use unified diffs (diff -u) for patches.")
                                return
@@ -89,15 +88,15 @@ func (ck *PatchChecker) Check() {
        }
 
        if patchedFiles > 1 {
-               NewLineWhole(ck.lines[0].Filename).Warnf("Contains patches for %d files, should be only one.", patchedFiles)
+               ck.lines.Warnf("Contains patches for %d files, should be only one.", patchedFiles)
        } else if patchedFiles == 0 {
-               NewLineWhole(ck.lines[0].Filename).Errorf("Contains no patch.")
+               ck.lines.Errorf("Contains no patch.")
        }
 
        ChecklinesTrailingEmptyLines(ck.lines)
-       sha1Before, err := computePatchSha1Hex(ck.lines[0].Filename)
+       sha1Before, err := computePatchSha1Hex(ck.lines.FileName)
        if SaveAutofixChanges(ck.lines) && G.Pkg != nil && err == nil {
-               sha1After, err := computePatchSha1Hex(ck.lines[0].Filename)
+               sha1After, err := computePatchSha1Hex(ck.lines.FileName)
                if err == nil {
                        AutofixDistinfo(sha1Before, sha1After)
                }
@@ -204,7 +203,7 @@ func (ck *PatchChecker) checkBeginDiff(l
                        "submitting a patch upstream, the corresponding bug report should",
                        "be mentioned in this file, to prevent duplicate work.")
        }
-       if G.opts.WarnSpace && !ck.previousLineEmpty {
+       if G.Opts.WarnSpace && !ck.previousLineEmpty {
                fix := line.Autofix()
                fix.Notef("Empty line expected.")
                fix.InsertBefore("")
@@ -217,7 +216,7 @@ func (ck *PatchChecker) checklineContext
                defer trace.Call2(text, patchedFileType.String())()
        }
 
-       if G.opts.WarnExtra {
+       if G.Opts.WarnExtra {
                ck.checklineAdded(text, patchedFileType)
        } else {
                ck.checktextRcsid(text)
@@ -315,12 +314,12 @@ func (ft FileType) String() string {
 }
 
 // This is used to select the proper subroutine for detecting absolute pathnames.
-func guessFileType(fname string) (fileType FileType) {
+func guessFileType(fileName string) (fileType FileType) {
        if trace.Tracing {
-               defer trace.Call(fname, trace.Result(&fileType))()
+               defer trace.Call(fileName, trace.Result(&fileType))()
        }
 
-       basename := path.Base(fname)
+       basename := path.Base(fileName)
        basename = strings.TrimSuffix(basename, ".in") // doesn't influence the content type
        ext := strings.ToLower(strings.TrimLeft(path.Ext(basename), "."))
 
@@ -343,7 +342,7 @@ func guessFileType(fname string) (fileTy
        }
 
        if trace.Tracing {
-               trace.Step1("Unknown file type for %q", fname)
+               trace.Step1("Unknown file type for %q", fileName)
        }
        return ftUnknown
 }
@@ -359,10 +358,10 @@ func (ck *PatchChecker) checklineSourceA
                }
 
                switch {
-               case matches(before, `[A-Z_]\s*$`):
+               case matches(before, `[A-Z_][\t ]*$`):
                        // ok; C example: const char *echo_cmd = PREFIX "/bin/echo";
 
-               case matches(before, `\+\s*$`):
+               case matches(before, `\+[\t ]*$`):
                        // ok; Python example: libdir = prefix + '/lib'
 
                default:
@@ -388,7 +387,7 @@ func (ck *PatchChecker) checklineOtherAb
                        // Example: @prefix@/bin
                        // Example: ${prefix}/bin
 
-               case matches(before, `\+\s*["']$`):
+               case matches(before, `\+[\t ]*["']$`):
                        // Example: prefix + '/lib'
 
                // XXX new: case matches(before, `\bs.$`): // Example: sed -e s,/usr,@PREFIX@,

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.9 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.9    Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go        Wed Nov  7 20:58:23 2018
@@ -27,16 +27,6 @@ func (s *Suite) Test_Pkgsrc_loadMasterSi
        c.Check(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_B"], equals, "https://b.example.org/distfiles/";)
 }
 
-func (s *Suite) Test_Pkgsrc_InitVartypes(c *check.C) {
-       t := s.Init(c)
-
-       src := NewPkgsrc(t.File("."))
-       src.InitVartypes()
-
-       c.Check(src.vartypes["BSD_MAKE_ENV"].basicType.name, equals, "ShellWord")
-       c.Check(src.vartypes["USE_BUILTIN.*"].basicType.name, equals, "YesNoIndirectly")
-}
-
 func (s *Suite) Test_Pkgsrc_parseSuggestedUpdates(c *check.C) {
        t := s.Init(c)
 
@@ -54,8 +44,8 @@ func (s *Suite) Test_Pkgsrc_parseSuggest
        todo := G.Pkgsrc.parseSuggestedUpdates(lines)
 
        c.Check(todo, check.DeepEquals, []SuggestedUpdate{
-               {lines[5], "CSP", "0.34", ""},
-               {lines[6], "freeciv-client", "2.5.0", "(urgent)"}})
+               {lines.Lines[5], "CSP", "0.34", ""},
+               {lines.Lines[6], "freeciv-client", "2.5.0", "(urgent)"}})
 }
 
 func (s *Suite) Test_Pkgsrc_loadTools(c *check.C) {
@@ -125,7 +115,6 @@ func (s *Suite) Test_Pkgsrc_loadTools(c 
 func (s *Suite) Test_Pkgsrc_loadTools__BUILD_DEFS(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupTool("echo", "ECHO", AtRunTime)
        pkg := t.SetupPackage("category/package",
                "pre-configure:",
@@ -142,7 +131,6 @@ func (s *Suite) Test_Pkgsrc_loadTools__B
 
        // FIXME: There should be a warning for VARBASE, but G.Pkgsrc.UserDefinedVars
        // does not contain anything at mklinechecker.go:/UserDefinedVars/.
-       t.CheckOutputLines()
 }
 
 func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile(c *check.C) {
@@ -189,7 +177,7 @@ func (s *Suite) Test_Pkgsrc_loadDocChang
 
        t.ExpectFatal(
                G.Pkgsrc.loadDocChanges,
-               "FATAL: ~/doc: Cannot be read.")
+               "FATAL: ~/doc: Cannot be read for loading the package changes.")
 }
 
 func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__not_found(c *check.C) {
@@ -223,6 +211,7 @@ func (s *Suite) Test_Pkgsrc_loadDocChang
 func (s *Suite) Test_Pkgsrc__deprecated(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("echo", "ECHO", AtRunTime)
        G.Pkgsrc.initDeprecatedVars()
        mklines := t.NewMkLines("Makefile",
                "USE_PERL5=\tyes",
@@ -236,29 +225,46 @@ func (s *Suite) Test_Pkgsrc__deprecated(
                "WARN: Makefile:2: Definition of SUBST_POSTCMD.class is deprecated. Has been removed, as it seemed unused.")
 }
 
-func (s *Suite) Test_Pkgsrc_Latest__no_basedir(c *check.C) {
+func (s *Suite) Test_Pkgsrc_ListVersions__no_basedir(c *check.C) {
        t := s.Init(c)
 
-       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       versions := G.Pkgsrc.ListVersions("lang", `^python[0-9]+$`, "../../lang/$0", true)
 
-       c.Check(latest, equals, "")
+       c.Check(versions, check.HasLen, 0)
        t.CheckOutputLines(
-               "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~/lang\".")
+               "ERROR: Cannot find package versions of \"^python[0-9]+$\" in \"~/lang\".")
 }
 
-func (s *Suite) Test_Pkgsrc_Latest__no_subdirs(c *check.C) {
+func (s *Suite) Test_Pkgsrc_ListVersions__no_subdirs(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("lang/Makefile")
 
-       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+       versions := G.Pkgsrc.ListVersions("lang", `^python[0-9]+$`, "../../lang/$0", true)
 
-       c.Check(latest, equals, "")
+       c.Check(versions, check.HasLen, 0)
        t.CheckOutputLines(
-               "ERROR: Cannot find latest version of \"^python[0-9]+$\" in \"~/lang\".")
+               "ERROR: Cannot find package versions of \"^python[0-9]+$\" in \"~/lang\".")
 }
 
-func (s *Suite) Test_Pkgsrc_Latest__single(c *check.C) {
+// Ensures that failed lookups are also cached since they can be assumed
+// not to change during a single pkglint run.
+func (s *Suite) Test_Pkgsrc_ListVersions__error_is_cached(c *check.C) {
+       t := s.Init(c)
+
+       versions := G.Pkgsrc.ListVersions("lang", `^python[0-9]+$`, "../../lang/$0", true)
+
+       c.Check(versions, check.HasLen, 0)
+       t.CheckOutputLines(
+               "ERROR: Cannot find package versions of \"^python[0-9]+$\" in \"~/lang\".")
+
+       versions2 := G.Pkgsrc.ListVersions("lang", `^python[0-9]+$`, "../../lang/$0", true)
+
+       c.Check(versions2, check.HasLen, 0)
+       t.CheckOutputEmpty() // No repeated error message
+}
+
+func (s *Suite) Test_Pkgsrc__caching(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("lang/Makefile")
@@ -285,20 +291,41 @@ func (s *Suite) Test_Pkgsrc_Latest__mult
        c.Check(latest, equals, "../../lang/python35")
 }
 
-func (s *Suite) Test_Pkgsrc_Latest__numeric(c *check.C) {
+func (s *Suite) Test_Pkgsrc_Latest__not_found(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("lang/Makefile")
+
+       latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
+
+       c.Check(latest, equals, "")
+
+       t.CheckOutputLines(
+               "ERROR: Cannot find package versions of \"^python[0-9]+$\" in \"~/lang\".")
+}
+
+// In 2017, PostgreSQL changed their versioning scheme to SemVer,
+// and since the pkgsrc directory contains the major version,
+// without any separating dots, the case of version 10 being
+// later than 95 needs to be handled specially.
+func (s *Suite) Test_Pkgsrc_ListVersions__postgresql(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("databases/postgresql95/Makefile")
        t.CreateFileLines("databases/postgresql97/Makefile")
-       t.CreateFileLines("databases/postgresql100/Makefile")
-       t.CreateFileLines("databases/postgresql104/Makefile")
+       t.CreateFileLines("databases/postgresql10/Makefile")
+       t.CreateFileLines("databases/postgresql11/Makefile")
 
-       latest := G.Pkgsrc.Latest("databases", `^postgresql[0-9]+$`, "$0")
+       versions := G.Pkgsrc.ListVersions("databases", `^postgresql[0-9]+$`, "$0", true)
 
-       c.Check(latest, equals, "postgresql104")
+       c.Check(versions, check.DeepEquals, []string{
+               "postgresql95",
+               "postgresql97",
+               "postgresql10",
+               "postgresql11"})
 }
 
-func (s *Suite) Test_Pkgsrc_Latest__numeric_multiple_numbers(c *check.C) {
+func (s *Suite) Test_Pkgsrc_ListVersions__numeric_multiple_numbers(c *check.C) {
        t := s.Init(c)
 
        t.CreateFileLines("emulators/suse_131_32_gtk2/Makefile")
@@ -306,34 +333,45 @@ func (s *Suite) Test_Pkgsrc_Latest__nume
        t.CreateFileLines("emulators/suse_131_gtk2/Makefile")
        t.CreateFileLines("emulators/suse_131_qt5/Makefile")
 
-       latest := G.Pkgsrc.Latest("emulators", `^suse_(\d+).*$`, "$1")
+       versions := G.Pkgsrc.ListVersions("emulators", `^suse_(\d+).*$`, "$1", true)
 
-       c.Check(latest, equals, "131")
+       c.Check(versions, deepEquals, []string{
+               "131",
+               "131",
+               "131",
+               "131"})
 }
 
-// In 2017, PostgreSQL changed their versioning scheme to SemVer,
-// and since the pkgsrc directory contains the major version,
-// without any separating dots, the case of version 10 being
-// later than 95 needs to be handled specially.
-func (s *Suite) Test_Pkgsrc_Latest__postgresql(c *check.C) {
+func (s *Suite) Test_Pkgsrc_ListVersions__go(c *check.C) {
        t := s.Init(c)
 
-       t.CreateFileLines("databases/postgresql95/Makefile")
-       t.CreateFileLines("databases/postgresql97/Makefile")
-       t.CreateFileLines("databases/postgresql10/Makefile")
-       t.CreateFileLines("databases/postgresql11/Makefile")
+       t.CreateFileLines("lang/go14/Makefile")
+       t.CreateFileLines("lang/go19/Makefile")
+       t.CreateFileLines("lang/go111/Makefile")
+       t.CreateFileLines("lang/go2/Makefile")
+
+       versionsUpTo2 := G.Pkgsrc.ListVersions("lang", `^go[0-9]+$`, "$0", true)
+
+       c.Check(versionsUpTo2, deepEquals, []string{"go14", "go19", "go111", "go2"})
+
+       t.CreateFileLines("lang/go37/Makefile")
+
+       // Clear the cache; pkglint doesn't expect file system changes during the scan.
+       for k := range G.Pkgsrc.listVersions {
+               delete(G.Pkgsrc.listVersions, k)
+       }
 
-       latest := G.Pkgsrc.Latest("databases", `^postgresql[0-9]+$`, "$0")
+       versionsUpTo37 := G.Pkgsrc.ListVersions("lang", `^go[0-9]+$`, "$0", true)
 
-       c.Check(latest, equals, "postgresql11")
+       c.Check(versionsUpTo37, deepEquals, []string{"go14", "go19", "go111", "go2", "go37"})
 }
 
-func (s *Suite) Test_Pkgsrc_Latest__invalid_argument(c *check.C) {
+func (s *Suite) Test_Pkgsrc_ListVersions__invalid_argument(c *check.C) {
        t := s.Init(c)
 
-       t.ExpectFatal(
-               func() { G.Pkgsrc.Latest("databases", `postgresql[0-9]+`, "$0") },
-               "FATAL: Pkglint internal error: Regular expression \"postgresql[0-9]+\" must be anchored at both ends.")
+       t.ExpectPanic(
+               func() { G.Pkgsrc.ListVersions("databases", `postgresql[0-9]+`, "$0", true) },
+               "Pkglint internal error: Regular expression \"postgresql[0-9]+\" must be anchored at both ends.")
 }
 
 func (s *Suite) Test_Pkgsrc_loadPkgOptions(c *check.C) {
@@ -370,3 +408,87 @@ func (s *Suite) Test_Pkgsrc_loadTools__n
                G.Pkgsrc.loadTools,
                "FATAL: ~/mk/tools/bsd.tools.mk: Too few tool files.")
 }
+
+// See PR 46570, Ctrl+F "3. In lang/perl5".
+func (s *Suite) Test_Pkgsrc_VariableType(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+
+       checkType := func(varname string, vartype string) {
+               actualType := G.Pkgsrc.VariableType(varname)
+               if vartype == "" {
+                       c.Check(actualType, check.IsNil)
+               } else {
+                       if c.Check(actualType, check.NotNil) {
+                               c.Check(actualType.String(), equals, vartype)
+                       }
+               }
+       }
+
+       checkType("_PERL5_PACKLIST_AWK_STRIP_DESTDIR", "")
+       checkType("SOME_DIR", "PathName (guessed)")
+       checkType("SOMEDIR", "PathName (guessed)")
+       checkType("SEARCHPATHS", "ShellList of PathName (guessed)")
+       checkType("MYPACKAGE_USER", "UserGroupName (guessed)")
+       checkType("MYPACKAGE_GROUP", "UserGroupName (guessed)")
+       checkType("MY_CMD_ENV", "ShellList of ShellWord (guessed)")
+       checkType("MY_CMD_ARGS", "ShellList of ShellWord (guessed)")
+       checkType("MY_CMD_CFLAGS", "ShellList of CFlag (guessed)")
+       checkType("MY_CMD_LDFLAGS", "ShellList of LdFlag (guessed)")
+       checkType("PLIST.abcde", "Yes")
+}
+
+// Guessing the variable type works for both plain and parameterized variable names.
+func (s *Suite) Test_Pkgsrc_VariableType__varparam(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+
+       t1 := G.Pkgsrc.VariableType("FONT_DIRS")
+
+       c.Assert(t1, check.NotNil)
+       c.Check(t1.String(), equals, "ShellList of PathMask (guessed)")
+
+       t2 := G.Pkgsrc.VariableType("FONT_DIRS.ttf")
+
+       c.Assert(t2, check.NotNil)
+       c.Check(t2.String(), equals, "ShellList of PathMask (guessed)")
+}
+
+// Guessing the variable type also works for variables that are
+// not known to pkglint but are found when scanning mk/* for otherwise
+// unknown variables.
+func (s *Suite) Test_Pkgsrc_VariableType__from_mk(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupPkgsrc()
+       t.CreateFileLines("mk/sys-vars.mk",
+               MkRcsID,
+               "",
+               "PKGSRC_MAKE_ENV?=\t# none",
+               "CPPPATH?=\tcpp")
+
+       pkg := t.SetupPackage("category/package",
+               "PKGSRC_MAKE_ENV+=\tCPP=${CPPPATH:Q}",
+               "PKGSRC_UNKNOWN_ENV+=\tCPP=${ABCPATH:Q}")
+
+       G.Main("pkglint", "-Wall", pkg)
+
+       if typ := G.Pkgsrc.VariableType("PKGSRC_MAKE_ENV"); c.Check(typ, check.NotNil) {
+               c.Check(typ.String(), equals, "ShellList of ShellWord (guessed)")
+       }
+
+       if typ := G.Pkgsrc.VariableType("CPPPATH"); c.Check(typ, check.NotNil) {
+               c.Check(typ.String(), equals, "Pathlist (guessed)")
+       }
+
+       // No warnings about "defined but not used" or "used but not defined"
+       // (which both rely on VariableType) may appear here for PKGSRC_MAKE_ENV
+       // and CPPPATH since these two variables are defined somewhere in the
+       // infrastructure.
+       t.CheckOutputLines(
+               "WARN: ~/category/package/Makefile:21: ABCPATH is used but not defined.",
+               "WARN: ~/category/package/Makefile:21: PKGSRC_UNKNOWN_ENV is defined but not used.",
+               "0 errors and 2 warnings found.")
+}
Index: pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.9 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.9       Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go   Wed Nov  7 20:58:23 2018
@@ -40,7 +40,7 @@ func (s *Suite) Test_ShTokenizer_ShAtom(
                        text += ":" + strings.Replace(strings.Replace(modifier, "\\", "\\\\", -1), ":", "\\:", -1)
                }
                text += "}"
-               varuse := &MkVarUse{varname: varname, modifiers: modifiers}
+               varuse := NewMkVarUse(varname, modifiers...)
                return &ShAtom{shtVaruse, text, shqPlain, varuse}
        }
        text := func(s string) *ShAtom { return atom(shtWord, s) }
@@ -112,11 +112,11 @@ func (s *Suite) Test_ShTokenizer_ShAtom(
        check("`",
                backt(text("`")))
 
-       check("`cat fname`",
+       check("`cat fileName`",
                backt(text("`")),
                backt(text("cat")),
                backt(space),
-               backt(text("fname")),
+               backt(text("fileName")),
                text("`"))
 
        check("hello, \"world\"",
Index: pkgsrc/pkgtools/pkglint/files/vartype_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.9 pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.9   Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/vartype_test.go       Wed Nov  7 20:58:23 2018
@@ -10,7 +10,7 @@ func (s *Suite) Test_Vartype_EffectivePe
        t.SetupVartypes()
 
        if typ := G.Pkgsrc.vartypes["PREFIX"]; c.Check(typ, check.NotNil) {
-               c.Check(typ.basicType.name, equals, "Pathname")
+               c.Check(typ.basicType.name, equals, "PathName")
                c.Check(typ.aclEntries, check.DeepEquals, []ACLEntry{{glob: "*", permissions: aclpUse}})
                c.Check(typ.EffectivePermissions("Makefile"), equals, aclpUse)
        }

Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.30 pkgsrc/pkgtools/pkglint/files/plist.go:1.31
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.30 Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Wed Nov  7 20:58:23 2018
@@ -1,21 +1,20 @@
 package main
 
 import (
-       "netbsd.org/pkglint/trace"
        "path"
        "sort"
        "strings"
 )
 
-func ChecklinesPlist(lines []Line) {
+func ChecklinesPlist(lines Lines) {
        if trace.Tracing {
-               defer trace.Call1(lines[0].Filename)()
+               defer trace.Call1(lines.FileName)()
        }
 
-       CheckLineRcsid(lines[0], `@comment `, "@comment ")
+       CheckLineRcsid(lines.Lines[0], `@comment `, "@comment ")
 
-       if len(lines) == 1 {
-               lines[0].Warnf("PLIST files shouldn't be empty.")
+       if lines.Len() == 1 {
+               lines.Lines[0].Warnf("PLIST files shouldn't be empty.")
                Explain(
                        "One reason for empty PLISTs is that this is a newly created package",
                        "and that the author didn't run \"bmake print-PLIST\" after installing",
@@ -49,12 +48,12 @@ type PlistLine struct {
        text      string // Line.Text without any conditions of the form ${PLIST.cond}
 }
 
-func (ck *PlistChecker) Check(plainLines []Line) {
+func (ck *PlistChecker) Check(plainLines Lines) {
        plines := ck.NewLines(plainLines)
        ck.collectFilesAndDirs(plines)
 
        if plines[0].Basename == "PLIST.common_end" {
-               commonLines := Load(strings.TrimSuffix(plines[0].Filename, "_end"), NotEmpty)
+               commonLines := Load(strings.TrimSuffix(plines[0].FileName, "_end"), NotEmpty)
                if commonLines != nil {
                        ck.collectFilesAndDirs(ck.NewLines(commonLines))
                }
@@ -66,7 +65,7 @@ func (ck *PlistChecker) Check(plainLines
        }
        ChecklinesTrailingEmptyLines(plainLines)
 
-       if G.opts.WarnPlistSort {
+       if G.Opts.WarnPlistSort {
                sorter := NewPlistLineSorter(plines)
                sorter.Sort()
                if !sorter.autofixed {
@@ -77,9 +76,9 @@ func (ck *PlistChecker) Check(plainLines
        }
 }
 
-func (ck *PlistChecker) NewLines(lines []Line) []*PlistLine {
-       plines := make([]*PlistLine, len(lines))
-       for i, line := range lines {
+func (ck *PlistChecker) NewLines(lines Lines) []*PlistLine {
+       plines := make([]*PlistLine, lines.Len())
+       for i, line := range lines.Lines {
                condition, text := "", line.Text
                if hasPrefix(text, "${PLIST.") {
                        if m, cond, rest := match2(text, `^(?:\$\{(PLIST\.[\w-.]+)\})+(.*)`); m {
@@ -122,7 +121,7 @@ func (ck *PlistChecker) checkline(pline 
        text := pline.text
        if hasAlnumPrefix(text) {
                ck.checkpath(pline)
-       } else if m, cmd, arg := match2(text, `^(?:\$\{[\w.]+\})?@([a-z-]+)\s*(.*)`); m {
+       } else if m, cmd, arg := match2(text, `^(?:\$\{[\w.]+\})?@([a-z-]+)[\t ]*(.*)`); m {
                pline.CheckDirective(cmd, arg)
        } else if hasPrefix(text, "$") {
                ck.checkpath(pline)
@@ -203,9 +202,9 @@ func (ck *PlistChecker) checkpath(pline 
 }
 
 func (ck *PlistChecker) checkSorted(pline *PlistLine) {
-       if text := pline.text; G.opts.WarnPlistSort && hasAlnumPrefix(text) && !containsVarRef(text) {
+       if text := pline.text; G.Opts.WarnPlistSort && hasAlnumPrefix(text) && !containsVarRef(text) {
                if ck.lastFname != "" {
-                       if ck.lastFname > text && !G.opts.Autofix {
+                       if ck.lastFname > text && !G.Opts.Autofix {
                                pline.Warnf("%q should be sorted before %q.", text, ck.lastFname)
                                Explain(
                                        "The files in the PLIST should be sorted alphabetically.",
@@ -228,8 +227,7 @@ func (ck *PlistChecker) checkDuplicate(p
        }
 
        fix := pline.Autofix()
-       fix.Errorf("Duplicate filename %q, already appeared in %s.",
-               text, prev.ReferenceFrom(pline.Line))
+       fix.Errorf("Duplicate file name %q, already appeared in %s.", text, pline.RefTo(prev.Line))
        fix.Delete()
        fix.Apply()
 }
@@ -291,8 +289,7 @@ func (ck *PlistChecker) checkpathLib(pli
        if contains(basename, ".a") || contains(basename, ".so") {
                if m, noext := match1(pline.text, `^(.*)(?:\.a|\.so[0-9.]*)$`); m {
                        if laLine := ck.allFiles[noext+".la"]; laLine != nil {
-                               pline.Warnf("Redundant library found. The libtool library is in %s.",
-                                       laLine.ReferenceFrom(pline.Line))
+                               pline.Warnf("Redundant library found. The libtool library is in %s.", pline.RefTo(laLine.Line))
                        }
                }
        }
@@ -301,7 +298,7 @@ func (ck *PlistChecker) checkpathLib(pli
 func (ck *PlistChecker) checkpathMan(pline *PlistLine) {
        m, catOrMan, section, manpage, ext, gz := match5(pline.text, `^man/(cat|man)(\w+)/(.*?)\.(\w+)(\.gz)?$`)
        if !m {
-               // maybe: line.Warnf("Invalid filename %q for manual page.", text)
+               // maybe: line.Warnf("Invalid file name %q for manual page.", text)
                return
        }
 
@@ -370,7 +367,7 @@ func (ck *PlistChecker) checkpathShare(p
                }
 
        case hasPrefix(text, "share/doc/html/"):
-               if G.opts.WarnPlistDepr {
+               if G.Opts.WarnPlistDepr {
                        pline.Warnf("Use of \"share/doc/html\" is deprecated. Use \"share/doc/${PKGBASE}\" instead.")
                }
 
@@ -494,7 +491,7 @@ func NewPlistLineSorter(plines []*PlistL
 
 func (s *plistLineSorter) Sort() {
        if line := s.unsortable; line != nil {
-               if G.opts.PrintAutofix || G.opts.Autofix {
+               if G.Opts.ShowAutofix || G.Opts.Autofix {
                        trace.Stepf("%s: This line prevents pkglint from sorting the PLIST automatically.", line)
                }
                return
@@ -524,7 +521,7 @@ func (s *plistLineSorter) Sort() {
        }
 
        fix := firstLine.Autofix()
-       fix.Notef("Silent-Magic-Diagnostic")
+       fix.Notef(SilentAutofixFormat)
        fix.Describef(int(firstLine.firstLine), "Sorting the whole file.")
        fix.Apply()
 
@@ -539,5 +536,5 @@ func (s *plistLineSorter) Sort() {
                lines = append(lines, pline.Line)
        }
 
-       s.autofixed = SaveAutofixChanges(lines)
+       s.autofixed = SaveAutofixChanges(NewLines(lines[0].FileName, lines))
 }
Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.30 pkgsrc/pkgtools/pkglint/files/util.go:1.31
--- pkgsrc/pkgtools/pkglint/files/util.go:1.30  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/util.go       Wed Nov  7 20:58:23 2018
@@ -4,7 +4,6 @@ import (
        "fmt"
        "io/ioutil"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "os"
        "path"
        "path/filepath"
@@ -37,6 +36,9 @@ func hasPrefix(s, prefix string) bool {
 func hasSuffix(s, suffix string) bool {
        return strings.HasSuffix(s, suffix)
 }
+func sprintf(format string, args ...interface{}) string {
+       return fmt.Sprintf(format, args...)
+}
 func fields(s string) []string {
        return strings.Fields(s)
 }
@@ -72,15 +74,19 @@ func replaceAllFunc(s string, re regex.P
 func trimHspace(str string) string {
        start := 0
        end := len(str)
-       for start < end && (str[start] == ' ' || str[start] == '\t') {
+       for start < end && isHspace(str[start]) {
                start++
        }
-       for start < end && (str[end-1] == ' ' || str[end-1] == '\t') {
+       for start < end && isHspace(str[end-1]) {
                end--
        }
        return str[start:end]
 }
 
+func isHspace(ch byte) bool {
+       return ch == ' ' || ch == '\t'
+}
+
 func ifelseStr(cond bool, a, b string) string {
        if cond {
                return a
@@ -111,9 +117,9 @@ func mustMatch(s string, re regex.Patter
        panic(fmt.Sprintf("mustMatch %q %q", s, re))
 }
 
-func isEmptyDir(fname string) bool {
-       dirents, err := ioutil.ReadDir(fname)
-       if err != nil || hasSuffix(fname, "/CVS") {
+func isEmptyDir(fileName string) bool {
+       dirents, err := ioutil.ReadDir(fileName)
+       if err != nil || hasSuffix(fileName, "/CVS") {
                return true
        }
        for _, dirent := range dirents {
@@ -121,7 +127,7 @@ func isEmptyDir(fname string) bool {
                if isIgnoredFilename(name) {
                        continue
                }
-               if dirent.IsDir() && isEmptyDir(fname+"/"+name) {
+               if dirent.IsDir() && isEmptyDir(fileName+"/"+name) {
                        continue
                }
                return false
@@ -129,16 +135,16 @@ func isEmptyDir(fname string) bool {
        return true
 }
 
-func getSubdirs(fname string) []string {
-       dirents, err := ioutil.ReadDir(fname)
+func getSubdirs(fileName string) []string {
+       dirents, err := ioutil.ReadDir(fileName)
        if err != nil {
-               NewLineWhole(fname).Fatalf("Cannot be read: %s", err)
+               NewLineWhole(fileName).Fatalf("Cannot be read: %s", err)
        }
 
        var subdirs []string
        for _, dirent := range dirents {
                name := dirent.Name()
-               if dirent.IsDir() && !isIgnoredFilename(name) && !isEmptyDir(fname+"/"+name) {
+               if dirent.IsDir() && !isIgnoredFilename(name) && !isEmptyDir(fileName+"/"+name) {
                        subdirs = append(subdirs, name)
                }
        }
@@ -154,10 +160,13 @@ func isIgnoredFilename(fileName string) 
 }
 
 // Checks whether a file is already committed to the CVS repository.
-func isCommitted(fname string) bool {
-       lines := loadCvsEntries(fname)
-       needle := "/" + path.Base(fname) + "/"
-       for _, line := range lines {
+func isCommitted(fileName string) bool {
+       lines := loadCvsEntries(fileName)
+       if lines == nil {
+               return false
+       }
+       needle := "/" + path.Base(fileName) + "/"
+       for _, line := range lines.Lines {
                if hasPrefix(line.Text, needle) {
                        return true
                }
@@ -165,14 +174,18 @@ func isCommitted(fname string) bool {
        return false
 }
 
-func isLocallyModified(fname string) bool {
-       baseName := path.Base(fname)
+func isLocallyModified(fileName string) bool {
+       baseName := path.Base(fileName)
 
-       lines := loadCvsEntries(fname)
-       for _, line := range lines {
+       lines := loadCvsEntries(fileName)
+       if lines == nil {
+               return false
+       }
+
+       for _, line := range lines.Lines {
                fields := strings.Split(line.Text, "/")
                if 3 < len(fields) && fields[1] == baseName {
-                       st, err := os.Stat(fname)
+                       st, err := os.Stat(fileName)
                        if err != nil {
                                return true
                        }
@@ -190,8 +203,8 @@ func isLocallyModified(fname string) boo
        return false
 }
 
-func loadCvsEntries(fname string) []Line {
-       dir := path.Dir(fname)
+func loadCvsEntries(fileName string) Lines {
+       dir := path.Dir(fileName)
        if dir == G.CvsEntriesDir {
                return G.CvsEntriesLines
        }
@@ -289,13 +302,13 @@ func varIsUsedSimilar(varname string) bo
                G.Pkg != nil && G.Pkg.vars.UsedSimilar(varname)
 }
 
-func fileExists(fname string) bool {
-       st, err := os.Stat(fname)
+func fileExists(fileName string) bool {
+       st, err := os.Stat(fileName)
        return err == nil && st.Mode().IsRegular()
 }
 
-func dirExists(fname string) bool {
-       st, err := os.Stat(fname)
+func dirExists(fileName string) bool {
+       st, err := os.Stat(fileName)
        return err == nil && st.Mode().IsDir()
 }
 
@@ -339,6 +352,10 @@ func mkopSubst(s string, left bool, from
 
 // relpath returns the relative path from `from` to `to`.
 func relpath(from, to string) string {
+       if hasPrefix(to, from) && len(to) > len(from)+1 && to[len(from)] == '/' {
+               return path.Clean(to[len(from)+1:])
+       }
+
        absFrom := abspath(from)
        absTo := abspath(to)
        rel, err := filepath.Rel(absFrom, absTo)
@@ -350,17 +367,17 @@ func relpath(from, to string) string {
        return result
 }
 
-func abspath(fname string) string {
-       abs, err := filepath.Abs(fname)
-       G.Assertf(err == nil, "abspath %q.", fname)
+func abspath(fileName string) string {
+       abs, err := filepath.Abs(fileName)
+       G.Assertf(err == nil, "abspath %q.", fileName)
        return filepath.ToSlash(abs)
 }
 
 // Differs from path.Clean in that only "../../" is replaced, not "../".
 // Also, the initial directory is always kept.
 // This is to provide the package path as context in recursive invocations of pkglint.
-func cleanpath(fname string) string {
-       tmp := fname
+func cleanpath(fileName string) string {
+       tmp := fileName
        for len(tmp) > 2 && hasPrefix(tmp, "./") {
                tmp = tmp[2:]
        }
@@ -745,7 +762,7 @@ type fileCacheEntry struct {
        count   int
        key     string
        options LoadOptions
-       lines   []Line
+       lines   Lines
 }
 
 func NewFileCache(size int) *FileCache {
@@ -756,7 +773,7 @@ func NewFileCache(size int) *FileCache {
                0}
 }
 
-func (c *FileCache) Put(fileName string, options LoadOptions, lines []Line) {
+func (c *FileCache) Put(fileName string, options LoadOptions, lines Lines) {
        key := c.key(fileName)
 
        entry := c.mapping[key]
@@ -777,11 +794,9 @@ func (c *FileCache) Put(fileName string,
 }
 
 func (c *FileCache) removeOldEntries() {
-       printStats := func() bool { return false }()
-
        sort.Slice(c.table, func(i, j int) bool { return c.table[j].count < c.table[i].count })
 
-       if printStats {
+       if G.Testing {
                for _, e := range c.table {
                        G.logOut.Printf("FileCache %q with count %d.\n", e.key, e.count)
                }
@@ -791,7 +806,7 @@ func (c *FileCache) removeOldEntries() {
        newLen := len(c.table)
        for newLen > 0 && c.table[newLen-1].count == minCount {
                e := c.table[newLen-1]
-               if printStats {
+               if G.Testing {
                        G.logOut.Printf("FileCache.Evict %q with count %d.\n", e.key, e.count)
                }
                delete(c.mapping, e.key)
@@ -801,25 +816,25 @@ func (c *FileCache) removeOldEntries() {
 
        // To avoid files getting stuck in the cache.
        for _, e := range c.table {
-               if printStats {
+               if G.Testing {
                        G.logOut.Printf("FileCache.Halve %q with count %d.\n", e.key, e.count)
                }
                e.count /= 2
        }
 }
 
-func (c *FileCache) Get(fileName string, options LoadOptions) []Line {
+func (c *FileCache) Get(fileName string, options LoadOptions) Lines {
        key := c.key(fileName)
        entry, found := c.mapping[key]
        if found && entry.options == options {
                c.hits++
                entry.count++
 
-               lines := make([]Line, len(entry.lines))
-               for i, line := range entry.lines {
+               lines := make([]Line, entry.lines.Len())
+               for i, line := range entry.lines.Lines {
                        lines[i] = NewLineMulti(fileName, int(line.firstLine), int(line.lastLine), line.Text, line.raw)
                }
-               return lines
+               return NewLines(fileName, lines)
        }
        c.misses++
        return nil
@@ -841,3 +856,7 @@ func (c *FileCache) Evict(fileName strin
 func (c *FileCache) key(fileName string) string {
        return path.Clean(fileName)
 }
+
+func makeHelp(topic string) string { return bmake("help topic=" + topic) }
+
+func bmake(target string) string { return sprintf("%s %s", confMake, target) }

Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.32 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.33
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.32    Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Wed Nov  7 20:58:23 2018
@@ -135,7 +135,6 @@ func (s *Suite) Test_splitIntoShellToken
 func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("awk", "AWK", AtRunTime)
        t.SetupTool("cp", "CP", AtRunTime)
@@ -143,7 +142,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        t.SetupTool("unzip", "UNZIP_CMD", AtRunTime)
 
        checkShellCommandLine := func(shellCommand string) {
-               G.Mk = t.NewMkLines("fname",
+               G.Mk = t.NewMkLines("fileName",
                        "\t"+shellCommand)
                shline := NewShellLine(G.Mk.mklines[0])
 
@@ -159,10 +158,10 @@ func (s *Suite) Test_ShellLine_CheckShel
        checkShellCommandLine("uname=`uname`; echo $$uname; echo; ${PREFIX}/bin/command")
 
        t.CheckOutputLines(
-               "WARN: fname:1: Unknown shell command \"uname\".",
-               "WARN: fname:1: Please switch to \"set -e\" mode before using a semicolon (after \"uname=`uname`\") to separate commands.",
-               "WARN: fname:1: Unknown shell command \"echo\".",
-               "WARN: fname:1: Unknown shell command \"echo\".")
+               "WARN: fileName:1: Unknown shell command \"uname\".",
+               "WARN: fileName:1: Please switch to \"set -e\" mode before using a semicolon (after \"uname=`uname`\") to separate commands.",
+               "WARN: fileName:1: Unknown shell command \"echo\".",
+               "WARN: fileName:1: Unknown shell command \"echo\".")
 
        t.SetupTool("echo", "", AtRunTime)
        t.SetupVartypes()
@@ -170,39 +169,39 @@ func (s *Suite) Test_ShellLine_CheckShel
        checkShellCommandLine("echo ${PKGNAME:Q}") // vucQuotPlain
 
        t.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.")
+               "WARN: fileName:1: PKGNAME may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.",
+               "NOTE: fileName:1: The :Q operator isn't necessary for ${PKGNAME} here.")
 
        checkShellCommandLine("echo \"${CFLAGS:Q}\"") // vucQuotDquot
 
        t.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; "+
+               "WARN: fileName:1: Please don't use the :Q operator in double quotes.",
+               "WARN: fileName: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} "+
+               "WARN: fileName:1: Please use ${CFLAGS:M*:Q} instead of ${CFLAGS:Q} "+
                        "and make sure the variable appears outside of any quoting characters.")
 
        checkShellCommandLine("echo '${COMMENT:Q}'") // vucQuotSquot
 
        t.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.")
+               "WARN: fileName:1: COMMENT may not be used in any file; it is a write-only variable.",
+               "WARN: fileName:1: Please move ${COMMENT:Q} outside of any quoting characters.")
 
        checkShellCommandLine("echo target=$@ exitcode=$$? '$$' \"\\$$\"")
 
        t.CheckOutputLines(
-               "WARN: fname:1: Please use \"${.TARGET}\" instead of \"$@\".",
-               "WARN: fname:1: The $? shell variable is often not available in \"set -e\" mode.")
+               "WARN: fileName:1: Please use \"${.TARGET}\" instead of \"$@\".",
+               "WARN: fileName:1: The $? shell variable is often not available in \"set -e\" mode.")
 
        checkShellCommandLine("echo $$@")
 
        t.CheckOutputLines(
-               "WARN: fname:1: The $@ shell variable should only be used in double quotes.")
+               "WARN: fileName:1: The $@ shell variable should only be used in double quotes.")
 
        checkShellCommandLine("echo \"$$\"") // As seen by make(1); the shell sees: echo "$"
 
        t.CheckOutputLines(
-               "WARN: fname:1: Unescaped $ or strange shell variable found.")
+               "WARN: fileName:1: Unescaped $ or strange shell variable found.")
 
        checkShellCommandLine("echo \"\\n\"")
 
@@ -220,7 +219,7 @@ func (s *Suite) Test_ShellLine_CheckShel
        checkShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"")
 
        t.CheckOutputLines(
-               "WARN: fname:1: The exitcode of \"unzip\" at the left of the | operator is ignored.")
+               "WARN: fileName:1: The exitcode of \"unzip\" at the left of the | operator is ignored.")
 
        // From mail/thunderbird/Makefile, rev. 1.159
        checkShellCommandLine("" +
@@ -233,8 +232,8 @@ func (s *Suite) Test_ShellLine_CheckShel
                "done")
 
        t.CheckOutputLines(
-               "WARN: fname:1: XPI_FILES is used but not defined.",
-               "WARN: fname:1: The exitcode of \"${UNZIP_CMD}\" at the left of the | operator is ignored.")
+               "WARN: fileName:1: XPI_FILES is used but not defined.",
+               "WARN: fileName:1: The exitcode of \"${UNZIP_CMD}\" at the left of the | operator is ignored.")
 
        // From x11/wxGTK28/Makefile
        checkShellCommandLine("" +
@@ -245,42 +244,42 @@ func (s *Suite) Test_ShellLine_CheckShel
                "done")
 
        t.CheckOutputLines(
-               "WARN: fname:1: WRKSRC may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.",
-               "WARN: fname:1: Unknown shell command \"[\".",
-               "WARN: fname:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".")
+               "WARN: fileName:1: WRKSRC may not be used in this file; it would be ok in Makefile, Makefile.*, *.mk.",
+               "WARN: fileName:1: Unknown shell command \"[\".",
+               "WARN: fileName:1: Unknown shell command \"${TOOLS_PATH.msgfmt}\".")
 
        checkShellCommandLine("@cp from to")
 
        t.CheckOutputLines(
-               "WARN: fname:1: The shell command \"cp\" should not be hidden.")
+               "WARN: fileName:1: The shell command \"cp\" should not be hidden.")
 
        checkShellCommandLine("-cp from to")
 
        t.CheckOutputLines(
-               "WARN: fname:1: Using a leading \"-\" to suppress errors is deprecated.")
+               "WARN: fileName:1: Using a leading \"-\" to suppress errors is deprecated.")
 
        checkShellCommandLine("-${MKDIR} deeply/nested/subdir")
 
        t.CheckOutputLines(
-               "NOTE: fname:1: You don't need to use \"-\" before \"${MKDIR} deeply/nested/subdir\".",
-               "WARN: fname:1: Using a leading \"-\" to suppress errors is deprecated.")
+               "NOTE: fileName:1: You don't need to use \"-\" before \"${MKDIR} deeply/nested/subdir\".",
+               "WARN: fileName:1: Using a leading \"-\" to suppress errors is deprecated.")
 
        G.Pkg = NewPackage(t.File("category/pkgbase"))
-       G.Pkg.PlistDirs["share/pkgbase"] = true
+       G.Pkg.Plist.Dirs["share/pkgbase"] = true
 
        // A directory that is found in the PLIST.
        checkShellCommandLine("${RUN} ${INSTALL_DATA_DIR} share/pkgbase ${PREFIX}/share/pkgbase")
 
        t.CheckOutputLines(
-               "NOTE: fname:1: You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= share/pkgbase\" "+
+               "NOTE: fileName: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.")
+               "WARN: fileName:1: The INSTALL_*_DIR commands can only handle one directory at a time.")
 
        // A directory that is not found in the PLIST.
        checkShellCommandLine("${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/share/other")
 
        t.CheckOutputLines(
-               "NOTE: fname:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
+               "NOTE: fileName:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
 
        G.Pkg = nil
 
@@ -293,10 +292,8 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__strip(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
-
        checkShellCommandLine := func(shellCommand string) {
-               G.Mk = t.NewMkLines("fname",
+               G.Mk = t.NewMkLines("fileName",
                        "\t"+shellCommand)
 
                G.Mk.ForEach(func(mkline MkLine) {
@@ -308,8 +305,8 @@ func (s *Suite) Test_ShellLine_CheckShel
        checkShellCommandLine("${STRIP} executable")
 
        t.CheckOutputLines(
-               "WARN: fname:1: Unknown shell command \"${STRIP}\".",
-               "WARN: fname:1: STRIP is used but not defined.")
+               "WARN: fileName:1: Unknown shell command \"${STRIP}\".",
+               "WARN: fileName:1: STRIP is used but not defined.")
 
        t.SetupVartypes()
 
@@ -321,7 +318,6 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__nofix(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("echo", "", AtRunTime)
        G.Mk = t.NewMkLines("Makefile",
@@ -354,7 +350,6 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellProgramChecker_checkPipeExitcode(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("cat", "", AtRunTime)
        t.SetupTool("echo", "", AtRunTime)
@@ -368,7 +363,7 @@ func (s *Suite) Test_ShellProgramChecker
                "\t cat | right-side",
                "\t cat | echo | right-side",
                "\t echo | cat | right-side",
-               "\t sed s,s,s, filename | right-side",
+               "\t sed s,s,s, fileName | right-side",
                "\t sed s,s,s < input | right-side",
                "\t ./unknown | right-side",
                "\t var=value | right-side",
@@ -408,9 +403,8 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__implementation(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
-       G.Mk = t.NewMkLines("fname",
+       G.Mk = t.NewMkLines("fileName",
                "# dummy")
        shline := NewShellLine(G.Mk.mklines[0])
 
@@ -425,13 +419,13 @@ func (s *Suite) Test_ShellLine_CheckShel
        G.Mk.ForEach(func(mkline MkLine) { shline.CheckWord(text, false, RunTime) })
 
        t.CheckOutputLines(
-               "WARN: fname:1: Unknown shell command \"echo\".")
+               "WARN: fileName:1: Unknown shell command \"echo\".")
 
        G.Mk.ForEach(func(mkline MkLine) { shline.CheckShellCommandLine(text) })
 
        // No parse errors
        t.CheckOutputLines(
-               "WARN: fname:1: Unknown shell command \"echo\".")
+               "WARN: fileName:1: Unknown shell command \"echo\".")
 }
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__dollar_without_variable(c *check.C) {
@@ -439,7 +433,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 
        t.SetupVartypes()
        t.SetupTool("pax", "", AtRunTime)
-       G.Mk = t.NewMkLines("fname",
+       G.Mk = t.NewMkLines("fileName",
                "# dummy")
        shline := NewShellLine(G.Mk.mklines[0])
 
@@ -451,7 +445,6 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckWord(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
 
        checkWord := func(shellWord string, checkQuoting bool) {
@@ -515,7 +508,7 @@ func (s *Suite) Test_ShellLine_CheckWord
 func (s *Suite) Test_ShellLine_CheckWord__dollar_without_variable(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("fname", 1, "# dummy")
+       shline := t.NewShellLine("fileName", 1, "# dummy")
 
        shline.CheckWord("/.*~$$//g", false, RunTime) // Typical argument to pax(1).
 
@@ -525,57 +518,74 @@ func (s *Suite) Test_ShellLine_CheckWord
 func (s *Suite) Test_ShellLine_CheckWord__backslash_plus(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("fname", 1, "\tfind . -exec rm -rf {} \\+")
+       t.SetupTool("find", "FIND", AtRunTime)
+       shline := t.NewShellLine("fileName", 1, "\tfind . -exec rm -rf {} \\+")
 
        shline.CheckShellCommandLine(shline.mkline.ShellCommand())
 
        // FIXME: A backslash before any other character than "\` keeps its original meaning.
        t.CheckOutputLines(
-               "WARN: fname:1: Pkglint parse error in ShellLine.CheckWord at \"\\\\+\" (quoting=plain), rest: \\+")
+               "WARN: fileName:1: Pkglint parse error in ShellLine.CheckWord at \"\\\\+\" (quoting=plain), rest: \\+")
 }
 
 func (s *Suite) Test_ShellLine_CheckWord__squot_dollar(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("fname", 1, "\t'$")
+       shline := t.NewShellLine("fileName", 1, "\t'$")
 
        shline.CheckWord(shline.mkline.ShellCommand(), false, RunTime)
 
        // FIXME: Should be parsed correctly. Make passes the dollar through (probably),
        // and the shell parser should complain about the unfinished string literal.
        t.CheckOutputLines(
-               "WARN: fname:1: Pkglint parse error in ShellLine.CheckWord at \"'$\" (quoting=s), rest: $")
+               "WARN: fileName:1: Pkglint parse error in ShellLine.CheckWord at \"'$\" (quoting=s), rest: $")
 }
 
 func (s *Suite) Test_ShellLine_CheckWord__dquot_dollar(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("fname", 1, "\t\"$")
+       shline := t.NewShellLine("fileName", 1, "\t\"$")
 
        shline.CheckWord(shline.mkline.ShellCommand(), false, RunTime)
 
        // FIXME: Should be parsed correctly. Make passes the dollar through (probably),
        // and the shell parser should complain about the unfinished string literal.
        t.CheckOutputLines(
-               "WARN: fname:1: Pkglint parse error in ShellLine.CheckWord at \"\\\"$\" (quoting=d), rest: $")
+               "WARN: fileName:1: Pkglint parse error in ShellLine.CheckWord at \"\\\"$\" (quoting=d), rest: $")
 }
 
 func (s *Suite) Test_ShellLine_CheckWord__dollar_subshell(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("fname", 1, "\t$$(echo output)")
+       shline := t.NewShellLine("fileName", 1, "\t$$(echo output)")
 
        shline.CheckWord(shline.mkline.ShellCommand(), false, RunTime)
 
        t.CheckOutputLines(
-               "WARN: fname:1: Invoking subshells via $(...) is not portable enough.")
+               "WARN: fileName:1: Invoking subshells via $(...) is not portable enough.")
+}
+
+func (s *Suite) Test_ShellLine_CheckWord__PKGMANDIR(c *check.C) {
+       t := s.Init(c)
+
+       t.SetupVartypes()
+       G.Mk = t.NewMkLines("chat/ircII/Makefile",
+               MkRcsID,
+               "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/man",
+               "CONFIGURE_ARGS+=--mandir=${DESTDIR}${PREFIX}/${PKGMANDIR}")
+
+       G.Mk.Check()
+
+       t.CheckOutputLines(
+               "WARN: chat/ircII/Makefile:2: Please use ${PKGMANDIR} instead of \"man\".",
+               "NOTE: chat/ircII/Makefile:2: This variable value should be aligned to column 25.",
+               "NOTE: chat/ircII/Makefile:3: This variable value should be aligned to column 25.")
 }
 
 func (s *Suite) Test_ShellLine_unescapeBackticks__unfinished(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
-       mklines := t.NewMkLines("fname.mk",
+       mklines := t.NewMkLines("fileName.mk",
                MkRcsID,
                "",
                "pre-configure:",
@@ -589,15 +599,13 @@ func (s *Suite) Test_ShellLine_unescapeB
 
        // FIXME: Mention the unfinished backquote.
        t.CheckOutputLines(
-               "WARN: fname.mk:4: Pkglint ShellLine.CheckShellCommand: parse error at []string{\"\"}",
-               "WARN: fname.mk:5: Pkglint ShellLine.CheckShellCommand: parse error at []string{\"echo\"}")
+               "WARN: fileName.mk:4: Pkglint ShellLine.CheckShellCommand: parse error at []string{\"\"}",
+               "WARN: fileName.mk:5: Pkglint ShellLine.CheckShellCommand: parse error at []string{\"echo\"}")
 }
 
 func (s *Suite) Test_ShellLine_unescapeBackticks__unfinished_direct(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
-
        // This call is unrealistic. It doesn't happen in practice, and this
        // direct, forcing test is only to reach the code coverage.
        NewShellLine(dummyMkLine).unescapeBackticks(
@@ -612,10 +620,9 @@ func (s *Suite) Test_ShellLine_unescapeB
 func (s *Suite) Test_ShellLine_variableNeedsQuoting(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("cp", "", AtRunTime)
-       mklines := t.NewMkLines("fname.mk",
+       mklines := t.NewMkLines("fileName.mk",
                MkRcsID,
                "",
                // It's a bit silly to use shell variables in CONFIGURE_ARGS,
@@ -629,18 +636,17 @@ func (s *Suite) Test_ShellLine_variableN
        // Quoting check is currently disabled for real shell commands.
        // See ShellLine.CheckShellCommand, spc.checkWord.
        t.CheckOutputLines(
-               "WARN: fname.mk:3: Unquoted shell variable \"target\".")
+               "WARN: fileName.mk:3: Unquoted shell variable \"target\".")
 }
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__echo(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        echo := t.SetupTool("echo", "ECHO", AtRunTime)
        echo.MustUseVarForm = true
-       G.Mk = t.NewMkLines("fname",
+       G.Mk = t.NewMkLines("fileName",
                "# dummy")
-       mkline := t.NewMkLine("fname", 3, "# dummy")
+       mkline := t.NewMkLine("fileName", 3, "# dummy")
 
        MkLineChecker{mkline}.checkText("echo \"hello, world\"")
 
@@ -649,12 +655,17 @@ func (s *Suite) Test_ShellLine_CheckShel
        NewShellLine(mkline).CheckShellCommandLine("echo \"hello, world\"")
 
        t.CheckOutputLines(
-               "WARN: fname:3: Please use \"${ECHO}\" instead of \"echo\".")
+               "WARN: fileName:3: Please use \"${ECHO}\" instead of \"echo\".")
 }
 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__shell_variables(c *check.C) {
        t := s.Init(c)
 
+       t.SetupVartypes()
+       t.SetupTool("install", "INSTALL", AtRunTime)
+       t.SetupTool("cp", "CP", AtRunTime)
+       t.SetupTool("mv", "MV", AtRunTime)
+       t.SetupTool("sed", "SED", AtRunTime)
        text := "\tfor f in *.pl; do ${SED} s,@PREFIX@,${PREFIX}, < $f > $f.tmp && ${MV} $f.tmp $f; done"
 
        shline := t.NewShellLine("Makefile", 3, text)
@@ -666,7 +677,11 @@ func (s *Suite) Test_ShellLine_CheckShel
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Makefile variable or $$f if you mean a shell variable.",
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Makefile variable or $$f if you mean a shell variable.",
                "WARN: Makefile:3: $f is ambiguous. Use ${f} if you mean a Makefile variable or $$f if you mean a shell variable.",
-               "NOTE: Makefile:3: Please use the SUBST framework instead of ${SED} and ${MV}.")
+               "NOTE: Makefile:3: Please use the SUBST framework instead of ${SED} and ${MV}.",
+               "WARN: Makefile:3: f is used but not defined.",
+               "WARN: Makefile:3: f is used but not defined.",
+               "WARN: Makefile:3: f is used but not defined.",
+               "WARN: Makefile:3: f is used but not defined.")
 
        shline.CheckShellCommandLine("install -c manpage.1 ${PREFIX}/man/man1/manpage.1")
 
@@ -682,21 +697,21 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_checkInstallCommand(c *check.C) {
        t := s.Init(c)
 
-       G.Mk = t.NewMkLines("fname",
+       G.Mk = t.NewMkLines("fileName",
                "# dummy")
        G.Mk.target = "do-install"
 
-       shline := t.NewShellLine("fname", 1, "\tdummy")
+       shline := t.NewShellLine("fileName", 1, "\tdummy")
 
        shline.checkInstallCommand("sed")
 
        t.CheckOutputLines(
-               "WARN: fname:1: The shell command \"sed\" should not be used in the install phase.")
+               "WARN: fileName:1: The shell command \"sed\" should not be used in the install phase.")
 
        shline.checkInstallCommand("cp")
 
        t.CheckOutputLines(
-               "WARN: fname:1: ${CP} should not be used to install files.")
+               "WARN: fileName:1: ${CP} should not be used to install files.")
 }
 
 func (s *Suite) Test_splitIntoMkWords(c *check.C) {
@@ -721,7 +736,10 @@ func (s *Suite) Test_splitIntoMkWords(c 
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__sed_and_mv(c *check.C) {
        t := s.Init(c)
 
-       shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' fname > fname.tmp; ${MV} fname.tmp fname")
+       t.SetupVartypes()
+       t.SetupTool("sed", "SED", AtRunTime)
+       t.SetupTool("mv", "MV", AtRunTime)
+       shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${SED} 's,#,// comment:,g' fileName > fileName.tmp; ${MV} fileName.tmp fileName")
 
        shline.CheckShellCommandLine(shline.mkline.ShellCommand())
 
@@ -743,6 +761,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__install_dir(c *check.C) {
        t := s.Init(c)
 
+       t.SetupVartypes()
        shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${INSTALL_DATA_DIR} ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
 
        shline.CheckShellCommandLine(shline.mkline.ShellCommand())
@@ -769,6 +788,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommandLine__install_option_d(c *check.C) {
        t := s.Init(c)
 
+       t.SetupVartypes()
        shline := t.NewShellLine("Makefile", 85, "\t${RUN} ${INSTALL} -d ${DESTDIR}${PREFIX}/dir1 ${DESTDIR}${PREFIX}/dir2")
 
        shline.CheckShellCommandLine(shline.mkline.ShellCommand())
@@ -827,7 +847,6 @@ func (s *Suite) Test_ShellLine_unescapeB
 func (s *Suite) Test_ShellLine__variable_outside_quotes(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        mklines := t.NewMkLines("dummy.mk",
                MkRcsID,
@@ -844,6 +863,7 @@ func (s *Suite) Test_ShellLine__variable
 func (s *Suite) Test_ShellLine_CheckShellCommand__cd_inside_if(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("echo", "ECHO", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -859,6 +879,8 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommand__negated_pipe(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("echo", "ECHO", AtRunTime)
+       t.SetupTool("test", "TEST", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -874,6 +896,7 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_CheckShellCommand__subshell(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("echo", "ECHO", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -917,6 +940,8 @@ func (s *Suite) Test_ShellLine_CheckShel
 func (s *Suite) Test_ShellLine_checkHiddenAndSuppress(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("echo", "ECHO", AtRunTime)
+       t.SetupTool("ls", "LS", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -986,7 +1011,7 @@ func (s *Suite) Test_SimpleCommandChecke
                ".include \"extra.mk\"")
        t.CreateFileLines("category/package/extra.mk",
                MkRcsID,
-               "PYTHON_BIN= my_cmd")
+               "PYTHON_BIN=\tmy_cmd")
 
        G.CheckDirent(pkg)
 
@@ -1007,6 +1032,8 @@ func (s *Suite) Test_SimpleCommandChecke
 func (s *Suite) Test_SimpleCommandChecker_checkPaxPe(c *check.C) {
        t := s.Init(c)
 
+       t.SetupVartypes()
+       t.SetupTool("pax", "PAX", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -1024,6 +1051,8 @@ func (s *Suite) Test_SimpleCommandChecke
 func (s *Suite) Test_SimpleCommandChecker_checkEchoN(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("echo", "ECHO", AtRunTime)
+       t.SetupTool("echo -n", "ECHO_N", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "",
@@ -1041,6 +1070,9 @@ func (s *Suite) Test_SimpleCommandChecke
 func (s *Suite) Test_ShellProgramChecker_checkConditionalCd(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("ls", "LS", AtRunTime)
+       t.SetupTool("printf", "PRINTF", AtRunTime)
+       t.SetupTool("tr", "TR", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "pre-configure:",
@@ -1053,12 +1085,15 @@ func (s *Suite) Test_ShellProgramChecker
        // FIXME: Fix the parse error.
        t.CheckOutputLines(
                "ERROR: Makefile:3: The Solaris /bin/sh cannot handle \"cd\" inside conditionals.",
-               "WARN: Pkglint parse error in ShTokenizer.ShAtom at \"$$\" (quoting=plain).")
+               "WARN: Pkglint parse error in ShTokenizer.ShAtom at \"$$\" (quoting=plain).",
+               "WARN: Makefile:4: The exitcode of \"ls\" at the left of the | operator is ignored.")
 }
 
 func (s *Suite) Test_SimpleCommandChecker_checkRegexReplace(c *check.C) {
        t := s.Init(c)
 
+       t.SetupTool("pax", "PAX", AtRunTime)
+       t.SetupTool("sed", "SED", AtRunTime)
        mklines := t.NewMkLines("Makefile",
                MkRcsID,
                "pre-configure:",
@@ -1084,7 +1119,6 @@ func (s *Suite) Test_SimpleCommandChecke
 func (s *Suite) Test_ShellProgramChecker_checkSetE__simple_commands(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupTool("echo", "", AtRunTime)
        t.SetupTool("rm", "", AtRunTime)
        t.SetupTool("touch", "", AtRunTime)
@@ -1104,7 +1138,6 @@ func (s *Suite) Test_ShellProgramChecker
 func (s *Suite) Test_ShellProgramChecker_checkSetE__compound_commands(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupTool("echo", "", AtRunTime)
        t.SetupTool("touch", "", AtRunTime)
        mklines := t.NewMkLines("Makefile",
@@ -1122,7 +1155,6 @@ func (s *Suite) Test_ShellProgramChecker
 func (s *Suite) Test_ShellProgramChecker_canFail(c *check.C) {
        t := s.Init(c)
 
-       t.SetupCommandLine("-Wall")
        t.SetupVartypes()
        t.SetupTool("echo", "", AtRunTime)
        t.SetupTool("grep", "GREP", AtRunTime)

Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.48 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.49
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.48       Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Wed Nov  7 20:58:23 2018
@@ -1,7 +1,7 @@
 package main
 
 import (
-       "netbsd.org/pkglint/trace"
+       "netbsd.org/pkglint/regex"
        "path"
        "strings"
 )
@@ -110,6 +110,12 @@ func (src *Pkgsrc) InitVartypes() {
                        return joined
                }())
 
+       // enumFrom parses all variable definitions for the given file,
+       // and for all variables matching one of the varcanons, all values
+       // are added as allowed values.
+       //
+       // If the file cannot be found, the allowed values are taken from
+       // defval. This is mostly useful when testing pkglint.
        enumFrom := func(fileName string, defval string, varcanons ...string) *BasicType {
                mklines := LoadMk(src.File(fileName), NotEmpty)
                values := make(map[string]bool)
@@ -132,7 +138,7 @@ func (src *Pkgsrc) InitVartypes() {
                        }
                }
 
-               if len(values) != 0 {
+               if len(values) > 0 {
                        joined := keysJoined(values)
                        if trace.Tracing {
                                trace.Stepf("Enum from %s in: %s", strings.Join(varcanons, " "), fileName, joined)
@@ -146,6 +152,20 @@ func (src *Pkgsrc) InitVartypes() {
                return enum(defval)
        }
 
+       // enumFromDirs reads the directories from category, takes all
+       // that have a single number in them and ranks them from earliest
+       // to latest.
+       //
+       // If the directories cannot be found, the allowed values are taken
+       // from defval. This is mostly useful when testing pkglint.
+       enumFromDirs := func(category string, re regex.Pattern, repl string, defval string) *BasicType {
+               versions := src.ListVersions(category, re, repl, false)
+               if len(versions) == 0 {
+                       return enum(defval)
+               }
+               return enum(strings.Join(versions, " "))
+       }
+
        compilers := enumFrom(
                "mk/compiler.mk",
                "ccache ccc clang distcc f2c gcc hp icc ido mipspro mipspro-ucode pcc sunpro xlc",
@@ -342,7 +362,7 @@ func (src *Pkgsrc) InitVartypes() {
        usrpkg("ICECAST_CHUNKLEN", lkNone, BtInteger)
        usrpkg("ICECAST_SOURCE_BUFFSIZE", lkNone, BtInteger)
        usrpkg("IMAP_UW_CCLIENT_MBOX_FMT", lkNone, enum("mbox mbx mh mmdf mtx mx news phile tenex unix"))
-       usrpkg("IMAP_UW_MAILSPOOLHOME", lkNone, BtFilename)
+       usrpkg("IMAP_UW_MAILSPOOLHOME", lkNone, BtFileName)
        usrpkg("IMDICTDIR", lkNone, BtPathname)
        usrpkg("INN_DATA_DIR", lkNone, BtPathname)
        usrpkg("INN_USER", lkNone, BtUserGroupName)
@@ -419,7 +439,7 @@ func (src *Pkgsrc) InitVartypes() {
        usrpkg("PILRC_USE_GTK", lkNone, BtYesNo)
        usrpkg("PKG_JVM_DEFAULT", lkNone, jvms)
        usrpkg("POPTOP_USE_MPPE", lkNone, BtYes)
-       usrpkg("PROCMAIL_MAILSPOOLHOME", lkNone, BtFilename)
+       usrpkg("PROCMAIL_MAILSPOOLHOME", lkNone, BtFileName)
        usrpkg("PROCMAIL_TRUSTED_IDS", lkShell, BtUnknown) // Comma-separated list of string or integer literals.
        usrpkg("PVM_SSH", lkNone, BtPathname)
        usrpkg("QMAILDIR", lkNone, BtPathname)
@@ -437,7 +457,7 @@ func (src *Pkgsrc) InitVartypes() {
        usrpkg("RSSH_CVS_PATH", lkNone, BtPathname)
        usrpkg("RSSH_RDIST_PATH", lkNone, BtPathname)
        usrpkg("RSSH_RSYNC_PATH", lkNone, BtPathname)
-       usrpkg("SAWFISH_THEMES", lkShell, BtFilename)
+       usrpkg("SAWFISH_THEMES", lkShell, BtFileName)
        usrpkg("SCREWS_GROUP", lkNone, BtUserGroupName)
        usrpkg("SCREWS_USER", lkNone, BtUserGroupName)
        usrpkg("SDIST_PAWD", lkNone, enum("pawd pwd"))
@@ -470,7 +490,7 @@ func (src *Pkgsrc) InitVartypes() {
        acl(".TARGET", lkNone, BtPathname, "buildlink3.mk:; *: use, use-loadtime")
        acl("@", lkNone, BtPathname, "buildlink3.mk:; *: use, use-loadtime")
        acl("ALL_ENV", lkShell, BtShellWord, "")
-       acl("ALTERNATIVES_FILE", lkNone, BtFilename, "")
+       acl("ALTERNATIVES_FILE", lkNone, BtFileName, "")
        acl("ALTERNATIVES_SRC", lkShell, BtPathname, "")
        pkg("APACHE_MODULE", lkNone, BtYes)
        sys("AR", lkNone, BtShellCommand)
@@ -625,15 +645,15 @@ func (src *Pkgsrc) InitVartypes() {
        acl("DESTDIR_VARNAME", lkNone, BtVariableName, "Makefile, Makefile.common: set")
        sys("DEVOSSAUDIO", lkNone, BtPathname)
        sys("DEVOSSSOUND", lkNone, BtPathname)
-       pkglist("DISTFILES", lkShell, BtFilename)
+       pkglist("DISTFILES", lkShell, BtFileName)
        pkg("DISTINFO_FILE", lkNone, BtRelativePkgPath)
-       pkg("DISTNAME", lkNone, BtFilename)
+       pkg("DISTNAME", lkNone, BtFileName)
        pkg("DIST_SUBDIR", lkNone, BtPathname)
        acl("DJB_BUILD_ARGS", lkShell, BtShellWord, "")
        acl("DJB_BUILD_TARGETS", lkShell, BtIdentifier, "")
        acl("DJB_CONFIG_CMDS", lkNone, BtShellCommands, "options.mk: set")
        acl("DJB_CONFIG_DIRS", lkShell, BtWrksrcSubdirectory, "")
-       acl("DJB_CONFIG_HOME", lkNone, BtFilename, "")
+       acl("DJB_CONFIG_HOME", lkNone, BtFileName, "")
        acl("DJB_CONFIG_PREFIX", lkNone, BtPathname, "")
        acl("DJB_INSTALL_TARGETS", lkShell, BtIdentifier, "")
        acl("DJB_MAKE_TARGETS", lkNone, BtYesNo, "")
@@ -652,7 +672,7 @@ func (src *Pkgsrc) InitVartypes() {
        acl("ECHO", lkNone, BtShellCommand, "*: use")
        sys("ECHO_MSG", lkNone, BtShellCommand)
        sys("ECHO_N", lkNone, BtShellCommand)
-       pkg("EGDIR", lkNone, BtPathname) // Not defined anywhere, but used in many places like this.
+       pkg("EGDIR", lkNone, BtPathname) // Not defined anywhere but used in many places like this.
        sys("EMACS_BIN", lkNone, BtPathname)
        sys("EMACS_ETCPREFIX", lkNone, BtPathname)
        sys("EMACS_FLAVOR", lkNone, enum("emacs xemacs"))
@@ -723,7 +743,7 @@ func (src *Pkgsrc) InitVartypes() {
        pkglist("GENERATE_PLIST", lkNone, BtShellCommands)
        pkg("GITHUB_PROJECT", lkNone, BtIdentifier)
        pkg("GITHUB_TAG", lkNone, BtIdentifier)
-       pkg("GITHUB_RELEASE", lkNone, BtFilename)
+       pkg("GITHUB_RELEASE", lkNone, BtFileName)
        pkg("GITHUB_TYPE", lkNone, enum("tag release"))
        pkg("GMAKE_REQD", lkNone, BtVersion)
        acl("GNU_ARCH", lkNone, enum("mips"), "")
@@ -774,9 +794,9 @@ func (src *Pkgsrc) InitVartypes() {
        sys("JAVA_BINPREFIX", lkNone, BtPathname)
        pkg("JAVA_CLASSPATH", lkNone, BtShellWord)
        pkg("JAVA_HOME", lkNone, BtPathname)
-       pkg("JAVA_NAME", lkNone, BtFilename)
+       pkg("JAVA_NAME", lkNone, BtFileName)
        pkglist("JAVA_UNLIMIT", lkShell, enum("cmdsize datasize stacksize"))
-       pkglist("JAVA_WRAPPERS", lkSpace, BtFilename)
+       pkglist("JAVA_WRAPPERS", lkSpace, BtFileName)
        pkg("JAVA_WRAPPER_BIN.*", lkNone, BtPathname)
        sys("KRB5BASE", lkNone, BtPathname)
        acl("KRB5_ACCEPTED", lkShell, enum("heimdal mit-krb5"), "")
@@ -908,7 +928,7 @@ func (src *Pkgsrc) InitVartypes() {
        sys("PAMBASE", lkNone, BtPathname)
        usr("PAM_DEFAULT", lkNone, enum("linux-pam openpam solaris-pam"))
        acl("PATCHDIR", lkNone, BtRelativePkgPath, "Makefile: set; Makefile.common: default, set")
-       pkglist("PATCHFILES", lkShell, BtFilename)
+       pkglist("PATCHFILES", lkShell, BtFileName)
        acl("PATCH_ARGS", lkShell, BtShellWord, "")
        acl("PATCH_DIST_ARGS", lkShell, BtShellWord, "Makefile: set, append")
        acl("PATCH_DIST_CAT", lkNone, BtShellCommand, "")
@@ -963,7 +983,7 @@ func (src *Pkgsrc) InitVartypes() {
        acl("PKGTOOLS_ENV", lkShell, BtShellWord, "")
        sys("PKGVERSION", lkNone, BtVersion)
        sys("PKGVERSION_NOREV", lkNone, BtVersion) // Without the nb* part.
-       sys("PKGWILDCARD", lkNone, BtFilemask)
+       sys("PKGWILDCARD", lkNone, BtFileMask)
        sys("PKG_ADMIN", lkNone, BtShellCommand)
        sys("PKG_APACHE", lkNone, enum("apache24"))
        pkg("PKG_APACHE_ACCEPTED", lkShell, enum("apache24"))
@@ -1042,8 +1062,10 @@ func (src *Pkgsrc) InitVartypes() {
        acl("PTHREAD_OPTS", lkShell, enum("native optional require"), "Makefile: set, append; Makefile.common, buildlink3.mk: append")
        sysload("PTHREAD_TYPE", lkNone, BtIdentifier) // Or "native" or "none".
        pkg("PY_PATCHPLIST", lkNone, BtYes)
-       acl("PYPKGPREFIX", lkNone, enum("py27 py34 py35 py36"), "pyversion.mk: set; *: use-loadtime, use")
-       pkg("PYTHON_FOR_BUILD_ONLY", lkNone, BtYes)
+       acl("PYPKGPREFIX", lkNone, enumFromDirs("lang", `^python(\d+)$`, "py$1", "py27 py36"), ""+
+               "pyversion.mk: set; "+
+               "*: use-loadtime, use")
+       pkg("PYTHON_FOR_BUILD_ONLY", lkNone, enum("yes no test tool YES")) // See lang/python/pyversion.mk
        pkglist("REPLACE_PYTHON", lkShell, BtPathmask)
        pkglist("PYTHON_VERSIONS_ACCEPTED", lkShell, BtVersion)
        pkglist("PYTHON_VERSIONS_INCOMPATIBLE", lkShell, BtVersion)
@@ -1051,7 +1073,7 @@ func (src *Pkgsrc) InitVartypes() {
        usr("PYTHON_VERSION_REQD", lkNone, BtVersion)
        pkglist("PYTHON_VERSIONED_DEPENDENCIES", lkShell, BtPythonDependency)
        sys("RANLIB", lkNone, BtShellCommand)
-       pkglist("RCD_SCRIPTS", lkShell, BtFilename)
+       pkglist("RCD_SCRIPTS", lkShell, BtFileName)
        acl("RCD_SCRIPT_SRC.*", lkNone, BtPathname, "Makefile: set")
        acl("RCD_SCRIPT_WRK.*", lkNone, BtPathname, "Makefile: set")
        usr("REAL_ROOT_USER", lkNone, BtUserGroupName)
@@ -1064,7 +1086,7 @@ func (src *Pkgsrc) InitVartypes() {
        acl("REPLACE_FILES.*", lkShell, BtPathmask, "Makefile, Makefile.common: set, append")
        acl("REPLACE_INTERPRETER", lkShell, BtIdentifier, "Makefile, Makefile.common: append")
        pkglist("REPLACE_KSH", lkShell, BtPathmask)
-       pkglist("REPLACE_LOCALEDIR_PATTERNS", lkShell, BtFilemask)
+       pkglist("REPLACE_LOCALEDIR_PATTERNS", lkShell, BtFileMask)
        pkglist("REPLACE_LUA", lkShell, BtPathmask)
        pkglist("REPLACE_PERL", lkShell, BtPathmask)
        pkglist("REPLACE_PYTHON", lkShell, BtPathmask)
@@ -1095,16 +1117,16 @@ func (src *Pkgsrc) InitVartypes() {
        acl("SITES.*", lkShell, BtFetchURL, "Makefile, Makefile.common, options.mk: set, append, use")
        usr("SMF_PREFIS", lkNone, BtPathname)
        pkg("SMF_SRCDIR", lkNone, BtPathname)
-       pkg("SMF_NAME", lkNone, BtFilename)
+       pkg("SMF_NAME", lkNone, BtFileName)
        pkg("SMF_MANIFEST", lkNone, BtPathname)
        pkg("SMF_INSTANCES", lkShell, BtIdentifier)
-       pkg("SMF_METHODS", lkShell, BtFilename)
+       pkg("SMF_METHODS", lkShell, BtFileName)
        pkg("SMF_METHOD_SRC.*", lkNone, BtPathname)
        pkg("SMF_METHOD_SHELL", lkNone, BtShellCommand)
        pkglist("SPECIAL_PERMS", lkShell, BtPerms)
        sys("STEP_MSG", lkNone, BtShellCommand)
        sys("STRIP", lkNone, BtShellCommand) // see mk/tools/strip.mk
-       acl("SUBDIR", lkShell, BtFilename, "Makefile: append; *:")
+       acl("SUBDIR", lkShell, BtFileName, "Makefile: append; *:")
        acl("SUBST_CLASSES", lkShell, BtIdentifier, "Makefile: set, append; *: append")
        acl("SUBST_CLASSES.*", lkShell, BtIdentifier, "Makefile: set, append; *: append")
        acl("SUBST_FILES.*", lkShell, BtPathmask, "Makefile, Makefile.*, *.mk: set, append")
@@ -1120,7 +1142,7 @@ func (src *Pkgsrc) InitVartypes() {
        acl("TEST_TARGET", lkShell, BtIdentifier, "Makefile: set; Makefile.common: default, set; options.mk: set, append")
        pkglist("TEXINFO_REQD", lkShell, BtVersion)
        acl("TOOL_DEPENDS", lkSpace, BtDependencyWithPath, "Makefile, Makefile.common, *.mk: append")
-       sys("TOOLS_ALIASES", lkShell, BtFilename)
+       sys("TOOLS_ALIASES", lkShell, BtFileName)
        sys("TOOLS_BROKEN", lkShell, BtTool)
        sys("TOOLS_CMD.*", lkNone, BtPathname)
        acl("TOOLS_CREATE", lkShell, BtTool, "Makefile, Makefile.common, options.mk: append")
@@ -1212,9 +1234,7 @@ func parseACLEntries(varname string, acl
                } else {
                        globs = strings.TrimSuffix(arg, ":")
                }
-               if perms == prevperms {
-                       G.Panicf("Repeated permissions %q for %q.", perms, varname)
-               }
+               G.Assertf(perms != prevperms, "Repeated permissions %q for %q.", perms, varname)
                prevperms = perms
 
                var permissions ACLPermissions
@@ -1233,7 +1253,7 @@ func parseACLEntries(varname string, acl
                        case "":
                                break
                        default:
-                               G.Panicf("Invalid ACL permission %q for %q.", perm, varname)
+                               G.Assertf(false, "Invalid ACL permission %q for %q.", perm, varname)
                        }
                }
 
@@ -1245,12 +1265,11 @@ func parseACLEntries(varname string, acl
                                "bsd.options.mk", "pkgconfig-builtin.mk", "pyversion.mk":
                                break
                        default:
-                               G.Panicf("Invalid ACL glob %q for %q.", glob, varname)
+                               G.Assertf(false, "Invalid ACL glob %q for %q.", glob, varname)
                        }
                        for _, prev := range result {
-                               if matched, err := path.Match(prev.glob, glob); err != nil || matched {
-                                       G.Panicf("Ineffective ACL glob %q for %q.", glob, varname)
-                               }
+                               matched, err := path.Match(prev.glob, glob)
+                               G.Assertf(err == nil && !matched, "Ineffective ACL glob %q for %q.", glob, varname)
                        }
                        result = append(result, ACLEntry{glob, permissions})
                }

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.42 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.43
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.42  Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Wed Nov  7 20:58:23 2018
@@ -1,7 +1,6 @@
 package main
 
 import (
-       "netbsd.org/pkglint/trace"
        "path"
        "sort"
        "strings"
@@ -11,7 +10,11 @@ type VartypeCheck struct {
        MkLine MkLine
        Line   Line
 
-       // The name of the variable being checked. In some cases it may also be the "description" of the variable.
+       // The name of the variable being checked.
+       //
+       // In some cases (see WithVarnameValueMatch) it contains not the
+       // variable name but more a "description" of a part of a variable.
+       // See MachinePlatform for an example.
        Varname    string
        Op         MkOperator
        Value      string
@@ -20,13 +23,67 @@ type VartypeCheck struct {
        Guessed    bool // Whether the type definition is guessed (based on the variable name) or explicitly defined (see vardefs.go).
 }
 
-// NewVartypeCheckValue creates a VartypeCheck context by copying all
-// fields except the value. This is typically used when checking parts
-// of composite types.
-func NewVartypeCheckValue(vc *VartypeCheck, value string) *VartypeCheck {
-       newVc := *vc
+func (cv *VartypeCheck) Errorf(format string, args ...interface{}) { cv.Line.Errorf(format, args...) }
+func (cv *VartypeCheck) Warnf(format string, args ...interface{})  { cv.Line.Warnf(format, args...) }
+func (cv *VartypeCheck) Notef(format string, args ...interface{})  { cv.Line.Notef(format, args...) }
+
+// Autofix returns the autofix instance belonging to the line.
+//
+// Usage:
+//
+//  fix := cv.Autofix()
+//
+//  fix.Errorf("Must not be ...")
+//  fix.Warnf("Should not be ...")
+//  fix.Notef("It is also possible ...")
+//
+//  fix.Explain(
+//      "Explanation ...",
+//      "... end of explanation.")
+//
+//  fix.Replace("from", "to")
+//  fix.ReplaceAfter("prefix", "from", "to")
+//  fix.ReplaceRegex(`[\t ]+`, "space", -1)
+//  fix.InsertBefore("new line")
+//  fix.InsertAfter("new line")
+//  fix.Delete()
+//  fix.Custom(func(showAutofix, autofix bool) {})
+//
+//  fix.Apply()
+func (cv *VartypeCheck) Autofix() *Autofix { return cv.Line.Autofix() }
+
+// WithValue returns a new VartypeCheck context by copying all
+// fields except the value.
+//
+// This is typically used when calling a related check.
+func (cv *VartypeCheck) WithValue(value string) *VartypeCheck {
+       return cv.WithVarnameValue(cv.Varname, value)
+}
+
+// WithVarnameValue returns a new VartypeCheck context by copying all
+// fields except the variable name and the value.
+//
+// This is typically used when checking parts of composite types.
+func (cv *VartypeCheck) WithVarnameValue(varname, value string) *VartypeCheck {
+       newVc := *cv
+       newVc.Varname = varname
        newVc.Value = value
-       newVc.ValueNoVar = vc.MkLine.WithoutMakeVariables(value)
+       newVc.ValueNoVar = cv.MkLine.WithoutMakeVariables(value)
+       return &newVc
+}
+
+// WithVarnameValueMatch returns a new VartypeCheck context by copying all
+// fields except the variable name, the operator (it is set to opUseMatch)
+// and the value.
+//
+// This is typically used when checking parts of composite types,
+// especially patterns.
+func (cv *VartypeCheck) WithVarnameValueMatch(varname, value string) *VartypeCheck {
+       newVc := *cv
+       newVc.Varname = varname
+       newVc.Op = opUseMatch
+       newVc.Value = value
+       newVc.ValueNoVar = cv.MkLine.WithoutMakeVariables(value)
        return &newVc
 }
 
@@ -100,8 +157,8 @@ func (cv *VartypeCheck) BasicRegularExpr
 }
 
 func (cv *VartypeCheck) BuildlinkDepmethod() {
-       if !containsVarRef(cv.Value) && cv.Value != "build" && cv.Value != "full" {
-               cv.Line.Warnf("Invalid dependency method %q. Valid methods are \"build\" or \"full\".", cv.Value)
+       if cv.Value == cv.ValueNoVar && cv.Value != "build" && cv.Value != "full" {
+               cv.Warnf("Invalid dependency method %q. Valid methods are \"build\" or \"full\".", cv.Value)
        }
 }
 
@@ -123,11 +180,13 @@ func (cv *VartypeCheck) Category() {
                "windowmaker",
                "xmms":
        default:
-               cv.Line.Errorf("Invalid category %q.", cv.Value)
+               cv.Errorf("Invalid category %q.", cv.Value)
        }
 }
 
-// A single option to the C/C++ compiler.
+// CFlag is a single option to the C/C++ compiler.
+//
+// XXX: How can flags like "-D NAME" be handled?
 func (cv *VartypeCheck) CFlag() {
        if cv.Op == opUseMatch {
                return
@@ -144,24 +203,24 @@ func (cv *VartypeCheck) CFlag() {
                containsVarRef(cflag):
                return
        case hasPrefix(cflag, "-"):
-               cv.Line.Warnf("Unknown compiler flag %q.", cflag)
+               cv.Warnf("Unknown compiler flag %q.", cflag)
        default:
-               cv.Line.Warnf("Compiler flag %q should start with a hyphen.", cflag)
+               cv.Warnf("Compiler flag %q should start with a hyphen.", cflag)
        }
 }
 
 // Comment checks for the single-line description of the package.
 func (cv *VartypeCheck) Comment() {
-       line, value := cv.Line, cv.Value
+       value := cv.Value
 
        if value == "TODO: Short description of the package" { // See pkgtools/url2pkg/files/url2pkg.pl, keyword "COMMENT".
-               line.Errorf("COMMENT must be set.")
+               cv.Errorf("COMMENT must be set.")
        }
-       if m, first := match1(value, `^(?i)(a|an)\s`); m {
-               line.Warnf("COMMENT should not begin with %q.", first)
+       if m, first := match1(value, `^(?i)(a|an)[\t ]`); m {
+               cv.Warnf("COMMENT should not begin with %q.", first)
        }
        if m, isA := match1(value, ` (is a|is an) `); m {
-               line.Warnf("COMMENT should not contain %q.", isA)
+               cv.Warnf("COMMENT should not contain %q.", isA)
                Explain(
                        "The words \"package is a\" are redundant.  Since every package comment",
                        "could start with them, it is better to remove this redundancy in all",
@@ -169,8 +228,8 @@ func (cv *VartypeCheck) Comment() {
        }
        if G.Pkg != nil && G.Pkg.EffectivePkgbase != "" {
                pkgbase := G.Pkg.EffectivePkgbase
-               if strings.HasPrefix(strings.ToLower(value), strings.ToLower(pkgbase+" ")) {
-                       line.Warnf("COMMENT should not start with the package name.")
+               if hasPrefix(strings.ToLower(value), strings.ToLower(pkgbase+" ")) {
+                       cv.Warnf("COMMENT should not start with the package name.")
                        Explain(
                                "The COMMENT is usually displayed together with the package name.",
                                "Therefore it does not need to repeat the package name but should",
@@ -178,31 +237,35 @@ func (cv *VartypeCheck) Comment() {
                }
        }
        if matches(value, `^[a-z]`) && cv.Op == opAssign {
-               line.Warnf("COMMENT should start with a capital letter.")
+               cv.Warnf("COMMENT should start with a capital letter.")
        }
        if hasSuffix(value, ".") {
-               line.Warnf("COMMENT should not end with a period.")
+               cv.Warnf("COMMENT should not end with a period.")
        }
        if len(value) > 70 {
-               line.Warnf("COMMENT should not be longer than 70 characters.")
+               cv.Warnf("COMMENT should not be longer than 70 characters.")
        }
        if hasPrefix(value, "\"") && hasSuffix(value, "\"") ||
                hasPrefix(value, "'") && hasSuffix(value, "'") {
-               line.Warnf("COMMENT should not be enclosed in quotes.")
+               cv.Warnf("COMMENT should not be enclosed in quotes.")
        }
 }
 
+// ConfFiles checks pairs of example file, configuration file.
+//
+// When a package is installed, the example file is installed as usual
+// and is then copied to its final location.
 func (cv *VartypeCheck) ConfFiles() {
        words, _ := splitIntoMkWords(cv.MkLine.Line, cv.Value)
        if len(words)%2 != 0 {
-               cv.Line.Warnf("Values for %s should always be pairs of paths.", cv.Varname)
+               cv.Warnf("Values for %s should always be pairs of paths.", cv.Varname)
        }
 
        for i, word := range words {
-               NewVartypeCheckValue(cv, word).Pathname()
+               cv.WithValue(word).PathName()
 
                if i%2 == 1 && !hasPrefix(word, "${") {
-                       cv.Line.Warnf("The destination file %q should start with a variable reference.", word)
+                       cv.Warnf("The destination file %q should start with a variable reference.", word)
                        Explain(
                                "Since pkgsrc can be installed in different locations, the",
                                "configuration files will also end up in different locations.",
@@ -213,12 +276,12 @@ func (cv *VartypeCheck) ConfFiles() {
 }
 
 func (cv *VartypeCheck) Dependency() {
-       line, value := cv.Line, cv.Value
+       value := cv.Value
 
-       parser := NewParser(line, value, false)
+       parser := NewParser(cv.Line, value, false)
        deppat := parser.Dependency()
        if deppat != nil && deppat.Wildcard == "" && (parser.Rest() == "{,nb*}" || parser.Rest() == "{,nb[0-9]*}") {
-               line.Warnf("Dependency patterns of the form pkgbase>=1.0 don't need the \"{,nb*}\" extension.")
+               cv.Warnf("Dependency patterns of the form pkgbase>=1.0 don't need the \"{,nb*}\" extension.")
                Explain(
                        "The \"{,nb*}\" extension is only necessary for dependencies of the",
                        "form \"pkgbase-1.2\", since the pattern \"pkgbase-1.2\" doesn't match",
@@ -226,7 +289,7 @@ func (cv *VartypeCheck) Dependency() {
                        "comparison operators, this is not necessary.")
 
        } else if deppat == nil || !parser.EOF() {
-               line.Warnf("Unknown dependency pattern %q.", value)
+               cv.Warnf("Invalid dependency pattern %q.", value)
                Explain(
                        "Typical dependencies have the following forms:",
                        "",
@@ -240,7 +303,7 @@ func (cv *VartypeCheck) Dependency() {
        wildcard := deppat.Wildcard
        if m, inside := match1(wildcard, `^\[(.*)\]\*$`); m {
                if inside != "0-9" {
-                       line.Warnf("Only [0-9]* is allowed in the numeric part of a dependency.")
+                       cv.Warnf("Only [0-9]* is allowed in the numeric part of a dependency.")
                        Explain(
                                "The pattern -[0-9] means any version.  All other version patterns",
                                "should be expressed using the comparison operators like < or >= or",
@@ -253,30 +316,30 @@ func (cv *VartypeCheck) Dependency() {
 
        } else if m, ver, suffix := match2(wildcard, `^(\d\w*(?:\.\w+)*)(\.\*|\{,nb\*\}|\{,nb\[0-9\]\*\}|\*|)$`); m {
                if suffix == "" {
-                       line.Warnf("Please use %q instead of %q as the version pattern.", ver+"{,nb*}", ver)
+                       cv.Warnf("Please use %q instead of %q as the version pattern.", ver+"{,nb*}", ver)
                        Explain(
                                "Without the \"{,nb*}\" suffix, this version pattern only matches",
                                "package versions that don't have a PKGREVISION (which is the part",
                                "after the \"nb\").")
                }
                if suffix == "*" {
-                       line.Warnf("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
+                       cv.Warnf("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
                        Explain(
                                "For example, the version \"1*\" also matches \"10.0.0\", which is",
                                "probably not intended.")
                }
 
        } else if wildcard == "*" {
-               line.Warnf("Please use \"%[1]s-[0-9]*\" instead of \"%[1]s-*\".", deppat.Pkgbase)
+               cv.Warnf("Please use \"%[1]s-[0-9]*\" instead of \"%[1]s-*\".", deppat.Pkgbase)
                Explain(
                        "If you use a * alone, the package specification may match other",
-                       "packages that have the same prefix, but a longer name.  For example,",
-                       "foo-* matches foo-1.2, but also foo-client-1.2 and foo-server-1.2.")
+                       "packages that have the same prefix but a longer name.  For example,",
+                       "foo-* matches foo-1.2 but also foo-client-1.2 and foo-server-1.2.")
        }
 
        withoutCharClasses := replaceAll(wildcard, `\[[\d-]+\]`, "")
        if contains(withoutCharClasses, "-") {
-               line.Warnf("The version pattern %q should not contain a hyphen.", wildcard)
+               cv.Warnf("The version pattern %q should not contain a hyphen.", wildcard)
                Explain(
                        "Pkgsrc interprets package names with version numbers like this:",
                        "",
@@ -288,7 +351,7 @@ func (cv *VartypeCheck) Dependency() {
 }
 
 func (cv *VartypeCheck) DependencyWithPath() {
-       line, value := cv.Line, cv.Value
+       value := cv.Value
        if value != cv.ValueNoVar {
                return // It's probably not worth checking this.
        }
@@ -298,11 +361,11 @@ func (cv *VartypeCheck) DependencyWithPa
 
                switch pkg {
                case "gettext":
-                       line.Warnf("Please use USE_TOOLS+=msgfmt instead of this dependency.")
+                       cv.Warnf("Please use USE_TOOLS+=msgfmt instead of this dependency.")
                case "perl5":
-                       line.Warnf("Please use USE_TOOLS+=perl:run instead of this dependency.")
+                       cv.Warnf("Please use USE_TOOLS+=perl:run instead of this dependency.")
                case "gmake":
-                       line.Warnf("Please use USE_TOOLS+=gmake instead of this dependency.")
+                       cv.Warnf("Please use USE_TOOLS+=gmake instead of this dependency.")
                }
 
                MkLineChecker{cv.MkLine}.CheckVartypePrimitive(cv.Varname, BtDependency, cv.Op, pattern, cv.MkComment, cv.Guessed)
@@ -310,12 +373,12 @@ func (cv *VartypeCheck) DependencyWithPa
        }
 
        if matches(value, `:\.\./[^/]+$`) {
-               line.Warnf("Dependencies should have the form \"../../category/package\".")
+               cv.Warnf("Dependencies should have the form \"../../category/package\".")
                cv.MkLine.ExplainRelativeDirs()
                return
        }
 
-       line.Warnf("Unknown dependency pattern with path %q.", value)
+       cv.Warnf("Invalid dependency pattern with path %q.", value)
        Explain(
                "Examples for valid dependency patterns with path are:",
                "  package-[0-9]*:../../category/package",
@@ -325,7 +388,7 @@ func (cv *VartypeCheck) DependencyWithPa
 
 func (cv *VartypeCheck) DistSuffix() {
        if cv.Value == ".tar.gz" {
-               cv.Line.Notef("%s is \".tar.gz\" by default, so this definition may be redundant.", cv.Varname)
+               cv.Notef("%s is \".tar.gz\" by default, so this definition may be redundant.", cv.Varname)
        }
 }
 
@@ -333,31 +396,13 @@ func (cv *VartypeCheck) EmulPlatform() {
        const rePart = `(?:\[[^\]]+\]|[^-\[])+`
        const rePair = `^(` + rePart + `)-(` + rePart + `)$`
        if m, opsysPattern, archPattern := match2(cv.Value, rePair); m {
-               opsysCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the operating system part of " + cv.Varname,
-                       cv.Op,
-                       opsysPattern,
-                       opsysPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               opsysCv := cv.WithVarnameValue("the operating system part of "+cv.Varname, opsysPattern)
                enumEmulOpsys.checker(opsysCv)
 
-               // no check for os_version
-
-               archCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the hardware architecture part of " + cv.Varname,
-                       cv.Op,
-                       archPattern,
-                       archPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               archCv := cv.WithVarnameValue("the hardware architecture part of "+cv.Varname, archPattern)
                enumEmulArch.checker(archCv)
        } else {
-               cv.Line.Warnf("%q is not a valid emulation platform.", cv.Value)
+               cv.Warnf("%q is not a valid emulation platform.", cv.Value)
                Explain(
                        "An emulation platform has the form <OPSYS>-<MACHINE_ARCH>.",
                        "OPSYS is the lower-case name of the operating system, and",
@@ -375,21 +420,21 @@ func (cv *VartypeCheck) Enum(vmap map[st
                        canMatch := false
                        for value := range vmap {
                                if ok, err := path.Match(cv.Value, value); err != nil {
-                                       cv.Line.Warnf("Invalid match pattern %q.", cv.Value)
+                                       cv.Warnf("Invalid match pattern %q.", cv.Value)
                                        break
                                } else if ok {
                                        canMatch = true
                                }
                        }
                        if !canMatch {
-                               cv.Line.Warnf("The pattern %q cannot match any of { %s } for %s.", cv.Value, basicType.AllowedEnums(), cv.Varname)
+                               cv.Warnf("The pattern %q cannot match any of { %s } for %s.", cv.Value, basicType.AllowedEnums(), cv.Varname)
                        }
                }
                return
        }
 
        if cv.Value == cv.ValueNoVar && !vmap[cv.Value] {
-               cv.Line.Warnf("%q is not valid for %s. Use one of { %s } instead.", cv.Value, cv.Varname, basicType.AllowedEnums())
+               cv.Warnf("%q is not valid for %s. Use one of { %s } instead.", cv.Value, cv.Varname, basicType.AllowedEnums())
        }
 }
 
@@ -401,10 +446,10 @@ func (cv *VartypeCheck) FetchURL() {
                        subdir := cv.Value[len(siteURL):]
                        if hasPrefix(cv.Value, "https://github.com/";) {
                                subdir = strings.SplitAfter(subdir, "/")[0]
-                               cv.Line.Warnf("Please use ${%s:=%s} instead of %q and run \"%s help topic=github\" for further tips.",
-                                       siteName, subdir, cv.Value, confMake)
+                               cv.Warnf("Please use ${%s:=%s} instead of %q and run %q for further tips.",
+                                       siteName, subdir, cv.Value, makeHelp("github"))
                        } else {
-                               cv.Line.Warnf("Please use ${%s:=%s} instead of %q.", siteName, subdir, cv.Value)
+                               cv.Warnf("Please use ${%s:=%s} instead of %q.", siteName, subdir, cv.Value)
                        }
                        return
                }
@@ -412,36 +457,36 @@ func (cv *VartypeCheck) FetchURL() {
 
        if m, name, subdir := match2(cv.Value, `\$\{(MASTER_SITE_[^:]*).*:=(.*)\}$`); m {
                if G.Pkgsrc.MasterSiteVarToURL[name] == "" {
-                       cv.Line.Errorf("The site %s does not exist.", name)
+                       cv.Errorf("The site %s does not exist.", name)
                }
                if !hasSuffix(subdir, "/") {
-                       cv.Line.Errorf("The subdirectory in %s must end with a slash.", name)
+                       cv.Errorf("The subdirectory in %s must end with a slash.", name)
                }
        }
 }
 
-// See Pathname.
+// See PathName.
 //
 // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_169
-func (cv *VartypeCheck) Filename() {
+func (cv *VartypeCheck) FileName() {
        switch {
        case cv.Op == opUseMatch:
                break
        case contains(cv.ValueNoVar, "/"):
-               cv.Line.Warnf("A filename should not contain a slash.")
+               cv.Warnf("A file name should not contain a slash.")
        case !matches(cv.ValueNoVar, `^[-0-9@A-Za-z.,_~+%]*$`):
-               cv.Line.Warnf("%q is not a valid filename.", cv.Value)
+               cv.Warnf("%q is not a valid file name.", cv.Value)
        }
 }
 
-func (cv *VartypeCheck) Filemask() {
+func (cv *VartypeCheck) FileMask() {
        switch {
        case cv.Op == opUseMatch:
                break
        case contains(cv.ValueNoVar, "/"):
-               cv.Line.Warnf("A filename mask should not contain a slash.")
+               cv.Warnf("A file name mask should not contain a slash.")
        case !matches(cv.ValueNoVar, `^[#%*+\-./0-9?@A-Z\[\]_a-z~]*$`):
-               cv.Line.Warnf("%q is not a valid filename mask.", cv.Value)
+               cv.Warnf("%q is not a valid file name mask.", cv.Value)
        }
 }
 
@@ -452,7 +497,7 @@ func (cv *VartypeCheck) FileMode() {
        case matches(cv.Value, `^[0-7]{3,4}$`):
                // Fine.
        default:
-               cv.Line.Warnf("Invalid file mode %q.", cv.Value)
+               cv.Warnf("Invalid file mode %q.", cv.Value)
        }
 }
 
@@ -460,7 +505,7 @@ func (cv *VartypeCheck) GccReqd() {
        cv.Version()
 
        if m, major := match1(cv.Value, `^([5-9])\.\d+$`); m {
-               fix := cv.Line.Autofix()
+               fix := cv.Autofix()
 
                fix.Warnf("GCC version numbers should only contain the major version (%s).", major)
                fix.Explain(
@@ -487,7 +532,7 @@ func (cv *VartypeCheck) Homepage() {
                        }
                }
                fixedURL := baseURL + subdir
-               fix := cv.Line.Autofix()
+               fix := cv.Autofix()
                if baseURL != "" {
                        fix.Warnf("HOMEPAGE should not be defined in terms of MASTER_SITEs. Use %s directly.", fixedURL)
                } else {
@@ -508,7 +553,7 @@ func (cv *VartypeCheck) Homepage() {
 func (cv *VartypeCheck) Identifier() {
        if cv.Op == opUseMatch {
                if cv.Value == cv.ValueNoVar && !matches(cv.Value, `^[\w*?]`) {
-                       cv.Line.Warnf("Invalid identifier pattern %q for %s.", cv.Value, cv.Varname)
+                       cv.Warnf("Invalid identifier pattern %q for %s.", cv.Value, cv.Varname)
                }
                return
        }
@@ -521,13 +566,13 @@ func (cv *VartypeCheck) Identifier() {
        case cv.Value != "" && cv.ValueNoVar == "":
                // Don't warn here.
        default:
-               cv.Line.Warnf("Invalid identifier %q.", cv.Value)
+               cv.Warnf("Invalid identifier %q.", cv.Value)
        }
 }
 
 func (cv *VartypeCheck) Integer() {
        if !matches(cv.Value, `^\d+$`) {
-               cv.Line.Warnf("Invalid integer %q.", cv.Value)
+               cv.Warnf("Invalid integer %q.", cv.Value)
        }
 }
 
@@ -537,7 +582,7 @@ func (cv *VartypeCheck) LdFlag() {
        }
        ldflag := cv.Value
        if m, rpathFlag := match1(ldflag, `^(-Wl,(?:-R|-rpath|--rpath))`); m {
-               cv.Line.Warnf("Please use \"${COMPILER_RPATH_FLAG}\" instead of %q.", rpathFlag)
+               cv.Warnf("Please use \"${COMPILER_RPATH_FLAG}\" instead of %q.", rpathFlag)
                return
        }
 
@@ -552,9 +597,9 @@ func (cv *VartypeCheck) LdFlag() {
                ldflag != cv.ValueNoVar:
                return
        case hasPrefix(ldflag, "-"):
-               cv.Line.Warnf("Unknown linker flag %q.", cv.Value)
+               cv.Warnf("Unknown linker flag %q.", cv.Value)
        default:
-               cv.Line.Warnf("Linker flag %q should start with a hyphen.", cv.Value)
+               cv.Warnf("Linker flag %q should start with a hyphen.", cv.Value)
        }
 }
 
@@ -578,32 +623,20 @@ func (cv *VartypeCheck) MachineGnuPlatfo
        }
 
        if m, archPattern, vendorPattern, opsysPattern := match3(pattern, reTriple); m {
-               archCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the hardware architecture part of " + cv.Varname,
-                       opUseMatch, // Always allow patterns, since this is a PlatformPattern.
-                       archPattern,
-                       archPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               archCv := cv.WithVarnameValueMatch(
+                       "the hardware architecture part of "+cv.Varname,
+                       archPattern)
                enumMachineGnuArch.checker(archCv)
 
                _ = vendorPattern
 
-               opsysCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the operating system part of " + cv.Varname,
-                       opUseMatch, // Always allow patterns, since this is a PlatformPattern.
-                       opsysPattern,
-                       opsysPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               opsysCv := cv.WithVarnameValueMatch(
+                       "the operating system part of "+cv.Varname,
+                       opsysPattern)
                enumMachineGnuPlatformOpsys.checker(opsysCv)
 
        } else {
-               cv.Line.Warnf("%q is not a valid platform pattern.", cv.Value)
+               cv.Warnf("%q is not a valid platform pattern.", cv.Value)
                Explain(
                        "A platform pattern has the form <OPSYS>-<OS_VERSION>-<MACHINE_ARCH>.",
                        "Each of these components may be a shell globbing expression.",
@@ -616,46 +649,45 @@ func (cv *VartypeCheck) MachineGnuPlatfo
 }
 
 func (cv *VartypeCheck) MailAddress() {
-       line, value := cv.Line, cv.Value
+       value := cv.Value
 
-       if m, _, domain := match2(value, `^([+\-.0-9A-Z_a-z]+)@([-\w\d.]+)$`); m {
-               if strings.EqualFold(domain, "NetBSD.org") && domain != "NetBSD.org" {
-                       line.Warnf("Please write \"NetBSD.org\" instead of %q.", domain)
-               }
-               if matches(value, `(?i)^(tech-pkg|packages)@NetBSD\.org$`) {
-                       line.Errorf("This mailing list address is obsolete. Use pkgsrc-users%NetBSD.org@localhost instead.")
-               }
+       m, _, domain := match2(value, `^([+\-.0-9A-Z_a-z]+)@([-\w\d.]+)$`)
+       if !m {
+               cv.Warnf("\"%s\" is not a valid mail address.", value)
+               return
+       }
 
-       } else {
-               line.Warnf("\"%s\" is not a valid mail address.", value)
+       if strings.EqualFold(domain, "NetBSD.org") && domain != "NetBSD.org" {
+               cv.Warnf("Please write \"NetBSD.org\" instead of %q.", domain)
+       }
+       if matches(value, `(?i)^(tech-pkg|packages)@NetBSD\.org$`) {
+               cv.Errorf("This mailing list address is obsolete. Use pkgsrc-users%NetBSD.org@localhost instead.")
        }
 }
 
-// See ${STEP_MSG}, ${PKG_FAIL_REASON}
+// Message is a plain string. It should not be enclosed in quotes since
+// that is the job of the code that uses the message.
+//
+// Lists of messages use a different type since they need the quotes
+// around each message; see PKG_FAIL_REASON.
 func (cv *VartypeCheck) Message() {
-       line, varname, value := cv.Line, cv.Varname, cv.Value
+       varname, value := cv.Varname, cv.Value
 
        if matches(value, `^[\"'].*[\"']$`) {
-               line.Warnf("%s should not be quoted.", varname)
+               cv.Warnf("%s should not be quoted.", varname)
                Explain(
                        "The quoting is only needed for variables which are interpreted as",
                        "multiple words (or, generally speaking, a list of something).  A",
                        "single text message does not belong to this class, since it is only",
-                       "printed as a whole.",
-                       "",
-                       "On the other hand, PKG_FAIL_REASON is a _list_ of text messages, so",
-                       "in that case, the quoting has to be done.")
+                       "printed as a whole.")
        }
 }
 
 // Option checks whether a single package option from options.mk conforms to the naming conventions.
 func (cv *VartypeCheck) Option() {
-       line, value, valueNovar := cv.Line, cv.Value, cv.ValueNoVar
+       value := cv.Value
 
-       if value != valueNovar {
-               if trace.Tracing {
-                       trace.Step1("Unchecked option name: %q", value)
-               }
+       if value != cv.ValueNoVar {
                return
        }
 
@@ -665,7 +697,7 @@ func (cv *VartypeCheck) Option() {
                }
 
                if _, found := G.Pkgsrc.PkgOptions[optname]; !found { // There's a difference between empty and absent here.
-                       line.Warnf("Unknown option %q.", optname)
+                       cv.Warnf("Unknown option %q.", optname)
                        Explain(
                                "This option is not documented in the mk/defaults/options.description",
                                "file.  Please think of a brief but precise description and either",
@@ -676,86 +708,100 @@ func (cv *VartypeCheck) Option() {
        }
 
        if matches(value, `^-?([a-z][-0-9a-z_\+]*)$`) {
-               line.Warnf("Use of the underscore character in option names is deprecated.")
+               cv.Warnf("Use of the underscore character in option names is deprecated.")
                return
        }
 
-       line.Errorf("Invalid option name %q. Option names must start with a lowercase letter and be all-lowercase.", value)
+       cv.Errorf("Invalid option name %q. Option names must start with a lowercase letter and be all-lowercase.", value)
 }
 
 // Pathlist checks variables like the PATH environment variable.
 func (cv *VartypeCheck) Pathlist() {
+       value := cv.Value
+
        // Sometimes, variables called PATH contain a single pathname,
        // especially those with auto-guessed type from MkLineImpl.VariableType.
-       if !contains(cv.Value, ":") && cv.Guessed {
-               MkLineChecker{cv.MkLine}.CheckVartypePrimitive(cv.Varname, BtPathname, cv.Op, cv.Value, cv.MkComment, cv.Guessed)
+       if !contains(value, ":") && cv.Guessed {
+               cv.PathName()
                return
        }
 
-       for _, path := range cv.MkLine.ValueSplit(cv.Value, ":") {
+       for _, path := range cv.MkLine.ValueSplit(value, ":") {
                if hasPrefix(path, "${") {
                        continue
                }
 
                pathNoVar := cv.MkLine.WithoutMakeVariables(path)
                if !matches(pathNoVar, `^[-0-9A-Za-z._~+%/]*$`) {
-                       cv.Line.Warnf("%q is not a valid pathname.", path)
+                       cv.Warnf("%q is not a valid pathname.", path)
                }
 
                if !hasPrefix(path, "/") {
-                       cv.Line.Warnf("All components of %s (in this case %q) should be absolute paths.", cv.Varname, path)
+                       cv.Warnf("All components of %s (in this case %q) should be absolute paths.", cv.Varname, path)
                }
        }
 }
 
-// Shell globbing including slashes.
-// See Filemask
-func (cv *VartypeCheck) Pathmask() {
+// PathMask is a shell pattern for pathnames, possibly including slashes.
+//
+// See FileMask.
+func (cv *VartypeCheck) PathMask() {
        if cv.Op == opUseMatch {
                return
        }
        if !matches(cv.ValueNoVar, `^[#%*+\-./0-9?@A-Z\[\]_a-z~]*$`) {
-               cv.Line.Warnf("%q is not a valid pathname mask.", cv.Value)
+               cv.Warnf("%q is not a valid pathname mask.", cv.Value)
        }
        CheckLineAbsolutePathname(cv.Line, cv.Value)
 }
 
-// Like Filename, but including slashes.
+// PathName checks for pathnames.
+//
+// Like FileName, but including slashes.
 //
 // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_266
-func (cv *VartypeCheck) Pathname() {
+func (cv *VartypeCheck) PathName() {
        if cv.Op == opUseMatch {
                return
        }
        if !matches(cv.ValueNoVar, `^[#\-0-9A-Za-z._~+%/]*$`) {
-               cv.Line.Warnf("%q is not a valid pathname.", cv.Value)
+               cv.Warnf("%q is not a valid pathname.", cv.Value)
        }
        CheckLineAbsolutePathname(cv.Line, cv.Value)
 }
 
 func (cv *VartypeCheck) Perl5Packlist() {
        if cv.Value != cv.ValueNoVar {
-               cv.Line.Warnf("%s should not depend on other variables.", cv.Varname)
+               cv.Warnf("%s should not depend on other variables.", cv.Varname)
        }
 }
 
 func (cv *VartypeCheck) Perms() {
        if cv.Value == "${ROOT_USER}" || cv.Value == "${ROOT_GROUP}" {
-               valuename := cv.Value[2 : len(cv.Value)-1]
-               cv.Line.Errorf("%s must not be used in permission definitions. Use REAL_%[1]s instead.", valuename)
+               valueName := cv.Value[2 : len(cv.Value)-1]
+               fix := cv.Autofix()
+               fix.Errorf("%s must not be used in permission definitions. Use REAL_%[1]s instead.", valueName)
+               fix.Replace(valueName, "REAL_"+valueName)
+               fix.Apply()
        }
 }
 
 func (cv *VartypeCheck) Pkgname() {
-       if cv.Op != opUseMatch && cv.Value == cv.ValueNoVar && !matches(cv.Value, rePkgname) {
-               cv.Line.Warnf("%q is not a valid package name. A valid package name has the form packagename-version, where version consists only of digits, letters and dots.", cv.Value)
+       value := cv.Value
+
+       if cv.Op != opUseMatch && value == cv.ValueNoVar && !matches(value, rePkgname) {
+               cv.Warnf("%q is not a valid package name.", value)
+               Explain(
+                       "A valid package name has the form packagename-version, where version",
+                       "consists only of digits, letters and dots.")
        }
 }
 
 func (cv *VartypeCheck) PkgOptionsVar() {
-       MkLineChecker{cv.MkLine}.CheckVartypePrimitive(cv.Varname, BtVariableName, cv.Op, cv.Value, cv.MkComment, cv.Guessed)
+       cv.VariableName()
+
        if matches(cv.Value, `\$\{PKGBASE[:\}]`) {
-               cv.Line.Errorf("PKGBASE must not be used in PKG_OPTIONS_VAR.")
+               cv.Errorf("PKGBASE must not be used in PKG_OPTIONS_VAR.")
                Explain(
                        "PKGBASE is defined in bsd.pkg.mk, which is included as the",
                        "very last file, but PKG_OPTIONS_VAR is evaluated earlier.",
@@ -764,23 +810,23 @@ func (cv *VartypeCheck) PkgOptionsVar() 
 
        // PR 46570, item "6. It should complain in PKG_OPTIONS_VAR is wrong"
        if !hasPrefix(cv.Value, "PKG_OPTIONS.") {
-               cv.Line.Errorf("PKG_OPTIONS_VAR must be of the form %q, not %q.", "PKG_OPTIONS.*", cv.Value)
+               cv.Errorf("PKG_OPTIONS_VAR must be of the form %q, not %q.", "PKG_OPTIONS.*", cv.Value)
        }
 }
 
-// A directory name relative to the top-level pkgsrc directory.
+// PkgPath checks a directory name relative to the top-level pkgsrc directory.
 // Despite its name, it is more similar to RelativePkgDir than to RelativePkgPath.
 func (cv *VartypeCheck) PkgPath() {
-       pkgsrcdir := relpath(path.Dir(cv.MkLine.Filename), G.Pkgsrc.File("."))
+       pkgsrcdir := relpath(path.Dir(cv.MkLine.FileName), G.Pkgsrc.File("."))
        MkLineChecker{cv.MkLine}.CheckRelativePkgdir(pkgsrcdir + "/" + cv.Value)
 }
 
 func (cv *VartypeCheck) PkgRevision() {
        if !matches(cv.Value, `^[1-9]\d*$`) {
-               cv.Line.Warnf("%s must be a positive integer number.", cv.Varname)
+               cv.Warnf("%s must be a positive integer number.", cv.Varname)
        }
        if cv.Line.Basename != "Makefile" {
-               cv.Line.Errorf("%s only makes sense directly in the package Makefile.", cv.Varname)
+               cv.Errorf("%s only makes sense directly in the package Makefile.", cv.Varname)
                Explain(
                        "Usually, different packages using the same Makefile.common have",
                        "different dependencies and will be bumped at different times (e.g.",
@@ -809,41 +855,17 @@ func (cv *VartypeCheck) MachinePlatformP
        }
 
        if m, opsysPattern, versionPattern, archPattern := match3(pattern, reTriple); m {
-               opsysCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the operating system part of " + cv.Varname,
-                       opUseMatch, // Always allow patterns, since this is a platform pattern.
-                       opsysPattern,
-                       opsysPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               opsysCv := cv.WithVarnameValueMatch("the operating system part of "+cv.Varname, opsysPattern)
                enumMachineOpsys.checker(opsysCv)
 
-               versionCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the version part of " + cv.Varname,
-                       opUseMatch, // Always allow patterns, since this is a platform pattern.
-                       versionPattern,
-                       versionPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               versionCv := cv.WithVarnameValueMatch("the version part of "+cv.Varname, versionPattern)
                versionCv.Version()
 
-               archCv := &VartypeCheck{
-                       cv.MkLine,
-                       cv.Line,
-                       "the hardware architecture part of " + cv.Varname,
-                       opUseMatch, // Always allow patterns, since this is a platform pattern.
-                       archPattern,
-                       archPattern,
-                       cv.MkComment,
-                       cv.Guessed}
+               archCv := cv.WithVarnameValueMatch("the hardware architecture part of "+cv.Varname, archPattern)
                enumMachineArch.checker(archCv)
 
        } else {
-               cv.Line.Warnf("%q is not a valid platform pattern.", cv.Value)
+               cv.Warnf("%q is not a valid platform pattern.", cv.Value)
                Explain(
                        "A platform pattern has the form <OPSYS>-<OS_VERSION>-<MACHINE_ARCH>.",
                        "Each of these components may be a shell globbing expression.",
@@ -855,18 +877,22 @@ func (cv *VartypeCheck) MachinePlatformP
        }
 }
 
-// A pathname relative to ${PREFIX}.
+// PrefixPathname checks for a pathname relative to ${PREFIX}.
 func (cv *VartypeCheck) PrefixPathname() {
-       if m, mansubdir := match1(cv.Value, `^man/(.+)`); m {
-               cv.Line.Warnf("Please use \"${PKGMANDIR}/%s\" instead of %q.", mansubdir, cv.Value)
+       if m, manSubdir := match1(cv.Value, `^man/(.+)`); m {
+               from := "${PKGMANDIR}/" + manSubdir
+               fix := cv.Autofix()
+               fix.Warnf("Please use %q instead of %q.", from, cv.Value)
+               fix.Replace(cv.Value, from)
+               fix.Apply()
        }
 }
 
 func (cv *VartypeCheck) PythonDependency() {
        if cv.Value != cv.ValueNoVar {
-               cv.Line.Warnf("Python dependencies should not contain variables.")
+               cv.Warnf("Python dependencies should not contain variables.")
        } else if !matches(cv.ValueNoVar, `^[+\-.0-9A-Z_a-z]+(?:|:link|:build)$`) {
-               cv.Line.Warnf("Invalid Python dependency %q.", cv.Value)
+               cv.Warnf("Invalid Python dependency %q.", cv.Value)
                Explain(
                        "Python dependencies must be an identifier for a package, as",
                        "specified in lang/python/versioned_dependencies.mk.  This",
@@ -875,19 +901,22 @@ func (cv *VartypeCheck) PythonDependency
        }
 }
 
-// Refers to a package directory, e.g. ../../category/pkgbase.
+// RelativePkgDir refers to a package directory, e.g. ../../category/pkgbase.
 func (cv *VartypeCheck) RelativePkgDir() {
        MkLineChecker{cv.MkLine}.CheckRelativePkgdir(cv.Value)
 }
 
-// Refers to a file or directory, e.g. ../../category/pkgbase, ../../category/pkgbase/Makefile.
+// RelativePkgPath refers to a file or directory, e.g. ../../category/pkgbase,
+// ../../category/pkgbase/Makefile.
+//
+// See RelativePkgDir, which requires a directory, not a file.
 func (cv *VartypeCheck) RelativePkgPath() {
        MkLineChecker{cv.MkLine}.CheckRelativePath(cv.Value, true)
 }
 
 func (cv *VartypeCheck) Restricted() {
        if cv.Value != "${RESTRICTED}" {
-               cv.Line.Warnf("The only valid value for %s is ${RESTRICTED}.", cv.Varname)
+               cv.Warnf("The only valid value for %s is ${RESTRICTED}.", cv.Varname)
                Explain(
                        "These variables are used to control which files may be mirrored on",
                        "FTP servers or CD-ROM collections.  They are not intended to mark",
@@ -899,12 +928,10 @@ func (cv *VartypeCheck) SedCommand() {
 }
 
 func (cv *VartypeCheck) SedCommands() {
-       line := cv.Line
-
-       tokens, rest := splitIntoShellTokens(line, cv.Value)
+       tokens, rest := splitIntoShellTokens(cv.Line, cv.Value)
        if rest != "" {
-               if strings.Contains(line.Text, "#") {
-                       line.Errorf("Invalid shell words %q in sed commands.", rest)
+               if contains(cv.Line.Text, "#") {
+                       cv.Errorf("Invalid shell words %q in sed commands.", rest)
                        Explain(
                                "When sed commands have embedded \"#\" characters, they need to be",
                                "escaped with a backslash, otherwise make(1) will interpret them as a",
@@ -927,7 +954,7 @@ func (cv *VartypeCheck) SedCommands() {
                                i++
                                ncommands++
                                if ncommands > 1 {
-                                       line.Notef("Each sed command should appear in an assignment of its own.")
+                                       cv.Notef("Each sed command should appear in an assignment of its own.")
                                        Explain(
                                                "For example, instead of",
                                                "    SUBST_SED.foo+=        -e s,command1,, -e s,command2,,",
@@ -939,7 +966,7 @@ func (cv *VartypeCheck) SedCommands() {
                                }
                                MkLineChecker{cv.MkLine}.CheckVartypePrimitive(cv.Varname, BtSedCommand, cv.Op, tokens[i], cv.MkComment, cv.Guessed)
                        } else {
-                               line.Errorf("The -e option to sed requires an argument.")
+                               cv.Errorf("The -e option to sed requires an argument.")
                        }
                case token == "-E":
                        // Switch to extended regular expressions mode.
@@ -948,10 +975,10 @@ func (cv *VartypeCheck) SedCommands() {
                        // Don't print lines per default.
 
                case i == 0 && matches(token, `^(["']?)(?:\d*|/.*/)s.+["']?$`):
-                       line.Notef("Please always use \"-e\" in sed commands, even if there is only one substitution.")
+                       cv.Notef("Please always use \"-e\" in sed commands, even if there is only one substitution.")
 
                default:
-                       line.Warnf("Unknown sed command %q.", token)
+                       cv.Warnf("Unknown sed command %q.", token)
                }
        }
 }
@@ -975,7 +1002,7 @@ func (cv *VartypeCheck) ShellWord() {
 
 func (cv *VartypeCheck) Stage() {
        if !matches(cv.Value, `^(?:pre|do|post)-(?:extract|patch|configure|build|test|install)`) {
-               cv.Line.Warnf("Invalid stage name %q. Use one of {pre,do,post}-{extract,patch,configure,build,test,install}.", cv.Value)
+               cv.Warnf("Invalid stage name %q. Use one of {pre,do,post}-{extract,patch,configure,build,test,install}.", cv.Value)
        }
 }
 
@@ -986,16 +1013,16 @@ func (cv *VartypeCheck) Tool() {
 
        } else if m, toolname, tooldep := match2(cv.Value, `^([-\w]+|\[)(?::(\w+))?$`); m {
                if tool, _ := G.Tool(toolname, RunTime); tool == nil {
-                       cv.Line.Errorf("Unknown tool %q.", toolname)
+                       cv.Errorf("Unknown tool %q.", toolname)
                }
 
                switch tooldep {
                case "", "bootstrap", "build", "pkgsrc", "run", "test":
                default:
-                       cv.Line.Errorf("Unknown tool dependency %q. Use one of \"bootstrap\", \"build\", \"pkgsrc\", \"run\" or \"test\".", tooldep)
+                       cv.Errorf("Unknown tool dependency %q. Use one of \"bootstrap\", \"build\", \"pkgsrc\", \"run\" or \"test\".", tooldep)
                }
        } else if cv.Op != opUseMatch && cv.Value == cv.ValueNoVar {
-               cv.Line.Errorf("Malformed tool dependency: %q.", cv.Value)
+               cv.Errorf("Malformed tool dependency: %q.", cv.Value)
                Explain(
                        "A tool dependency typically looks like \"sed\" or \"sed:run\".")
        }
@@ -1007,7 +1034,7 @@ func (cv *VartypeCheck) Unknown() {
 }
 
 func (cv *VartypeCheck) URL() {
-       line, value := cv.Line, cv.Value
+       value := cv.Value
 
        if value == "" && hasPrefix(cv.MkComment, "#") {
                // Ok
@@ -1017,7 +1044,7 @@ func (cv *VartypeCheck) URL() {
 
        } else if m, _, host, _, _ := match4(value, `^(https?|ftp|gopher)://([-0-9A-Za-z.]+)(?::(\d+))?/([-%&+,./0-9:;=?@A-Z_a-z~]|#)*$`); m {
                if matches(host, `(?i)\.NetBSD\.org$`) && !matches(host, `\.NetBSD\.org$`) {
-                       fix := line.Autofix()
+                       fix := cv.Autofix()
                        fix.Warnf("Please write NetBSD.org instead of %s.", host)
                        fix.ReplaceRegex(`(?i)NetBSD\.org`, "NetBSD.org", 1)
                        fix.Apply()
@@ -1026,29 +1053,30 @@ func (cv *VartypeCheck) URL() {
        } else if m, scheme, _, absPath := match3(value, `^([0-9A-Za-z]+)://([^/]+)(.*)$`); m {
                switch {
                case scheme != "ftp" && scheme != "http" && scheme != "https" && scheme != "gopher":
-                       line.Warnf("%q is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.", value)
+                       cv.Warnf("%q is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.", value)
 
                case absPath == "":
-                       line.Notef("For consistency, please add a trailing slash to %q.", value)
+                       cv.Notef("For consistency, please add a trailing slash to %q.", value)
 
                default:
-                       line.Warnf("%q is not a valid URL.", value)
+                       cv.Warnf("%q is not a valid URL.", value)
                }
 
        } else {
-               line.Warnf("%q is not a valid URL.", value)
+               cv.Warnf("%q is not a valid URL.", value)
        }
 }
 
 func (cv *VartypeCheck) UserGroupName() {
        if cv.Value == cv.ValueNoVar && !matches(cv.Value, `^[0-9_a-z]+$`) {
-               cv.Line.Warnf("Invalid user or group name %q.", cv.Value)
+               cv.Warnf("Invalid user or group name %q.", cv.Value)
        }
 }
 
+// VariableName checks that the value is a valid variable name.
 func (cv *VartypeCheck) VariableName() {
        if cv.Value == cv.ValueNoVar && !matches(cv.Value, `^[A-Z_][0-9A-Z_]*(?:[.].*)?$`) {
-               cv.Line.Warnf("%q is not a valid variable name.", cv.Value)
+               cv.Warnf("%q is not a valid variable name.", cv.Value)
                Explain(
                        "Variable names are restricted to only uppercase letters and the",
                        "underscore in the basename, and arbitrary characters in the",
@@ -1061,12 +1089,11 @@ func (cv *VartypeCheck) VariableName() {
 }
 
 func (cv *VartypeCheck) Version() {
-       line := cv.Line
        value := cv.Value
 
        if cv.Op == opUseMatch {
                if value != "*" && !matches(value, `^[\d?\[][\w\-.*?\[\]]+$`) {
-                       line.Warnf("Invalid version number pattern %q.", value)
+                       cv.Warnf("Invalid version number pattern %q.", value)
                        return
                }
 
@@ -1074,7 +1101,7 @@ func (cv *VartypeCheck) Version() {
                const alnum = `(?:\w|\[[\d-]+\])`
                if m, ver, suffix := match2(value, `^(`+digit+alnum+`*(?:\.`+alnum+`+)*)(\.\*|\*|)$`); m {
                        if suffix == "*" && ver != "[0-9]" {
-                               line.Warnf("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
+                               cv.Warnf("Please use %q instead of %q as the version pattern.", ver+".*", ver+"*")
                                Explain(
                                        "For example, the version \"1*\" also matches \"10.0.0\", which is",
                                        "probably not intended.")
@@ -1084,13 +1111,13 @@ func (cv *VartypeCheck) Version() {
        }
 
        if value == cv.ValueNoVar && !matches(value, `^\d[\w.]*$`) {
-               line.Warnf("Invalid version number %q.", value)
+               cv.Warnf("Invalid version number %q.", value)
        }
 }
 
 func (cv *VartypeCheck) WrapperReorder() {
        if !matches(cv.Value, `^reorder:l:([\w\-]+):([\w\-]+)$`) {
-               cv.Line.Warnf("Unknown wrapper reorder command %q.", cv.Value)
+               cv.Warnf("Unknown wrapper reorder command %q.", cv.Value)
        }
 }
 
@@ -1103,7 +1130,7 @@ func (cv *VartypeCheck) WrapperTransform
                matches(cmd, `^["']?s[|:,]`) {
                return
        }
-       cv.Line.Warnf("Unknown wrapper transform command %q.", cmd)
+       cv.Warnf("Unknown wrapper transform command %q.", cmd)
 }
 
 func (cv *VartypeCheck) WrkdirSubdirectory() {
@@ -1116,7 +1143,7 @@ func (cv *VartypeCheck) WrksrcSubdirecto
                if rest == "" {
                        rest = "."
                }
-               cv.Line.Notef("You can use %q instead of %q.", rest, cv.Value)
+               cv.Notef("You can use %q instead of %q.", rest, cv.Value)
                Explain(
                        "These directories are interpreted relative to ${WRKSRC}.")
 
@@ -1124,14 +1151,14 @@ func (cv *VartypeCheck) WrksrcSubdirecto
                // The value of another variable
 
        } else if !matches(cv.ValueNoVar, `^(?:\.|[0-9A-Za-z_@][-0-9A-Za-z_@./+]*)$`) {
-               cv.Line.Warnf("%q is not a valid subdirectory of ${WRKSRC}.", cv.Value)
+               cv.Warnf("%q is not a valid subdirectory of ${WRKSRC}.", cv.Value)
        }
 }
 
 func (cv *VartypeCheck) Yes() {
        switch cv.Op {
        case opUseMatch:
-               cv.Line.Warnf("%s should only be used in a \".if defined(...)\" condition.", cv.Varname)
+               cv.Warnf("%s should only be used in a \".if defined(...)\" condition.", cv.Varname)
                Explain(
                        "This variable can have only two values: defined or undefined.",
                        "When it is defined, it means \"yes\", even when its value is",
@@ -1141,8 +1168,8 @@ func (cv *VartypeCheck) Yes() {
                        "but using \".if defined(VARNAME)\" alone.")
 
        default:
-               if !matches(cv.Value, `^(?:YES|yes)(?:\s+#.*)?$`) {
-                       cv.Line.Warnf("%s should be set to YES or yes.", cv.Varname)
+               if !matches(cv.Value, `^(?:YES|yes)(?:[\t ]+#.*)?$`) {
+                       cv.Warnf("%s should be set to YES or yes.", cv.Varname)
                        Explain(
                                "This variable means \"yes\" if it is defined, and \"no\" if it is",
                                "undefined.  Even when it has the value \"no\", this means \"yes\".",
@@ -1163,16 +1190,16 @@ func (cv *VartypeCheck) YesNo() {
                switch cv.Value {
                case yes1, yes2, no1, no2:
                default:
-                       cv.Line.Warnf("%s should be matched against %q or %q, not %q.", cv.Varname, yes1, no1, cv.Value)
+                       cv.Warnf("%s should be matched against %q or %q, not %q.", cv.Varname, yes1, no1, cv.Value)
                }
        } else if cv.Op == opUseCompare {
-               cv.Line.Warnf("%s should be matched against %q or %q, not compared with %q.", cv.Varname, yes1, no1, cv.Value)
+               cv.Warnf("%s should be matched against %q or %q, not compared with %q.", cv.Varname, yes1, no1, cv.Value)
                Explain(
                        "The yes/no value can be written in either upper or lower case, and",
                        "both forms are actually used.  As long as this is the case, when",
                        "checking the variable value, both must be accepted.")
-       } else if !matches(cv.Value, `^(?:YES|yes|NO|no)(?:\s+#.*)?$`) {
-               cv.Line.Warnf("%s should be set to YES, yes, NO, or no.", cv.Varname)
+       } else if !matches(cv.Value, `^(?:YES|yes|NO|no)(?:[\t ]+#.*)?$`) {
+               cv.Warnf("%s should be set to YES, yes, NO, or no.", cv.Varname)
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.34 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.35
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.34     Tue Oct  9 23:17:17 2018
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Wed Nov  7 20:58:23 2018
@@ -16,7 +16,7 @@ func (s *Suite) Test_VartypeCheck_AwkCom
                "{print $$0}")
 
        vt.Output(
-               "WARN: fname:1: $0 is ambiguous. Use ${0} if you mean a Makefile variable or $$0 if you mean a shell variable.")
+               "WARN: fileName:1: $0 is ambiguous. Use ${0} if you mean a Makefile variable or $$0 if you mean a shell variable.")
 }
 
 func (s *Suite) Test_VartypeCheck_BasicRegularExpression(c *check.C) {
@@ -28,7 +28,7 @@ func (s *Suite) Test_VartypeCheck_BasicR
                ".*\\.pl$$")
 
        vt.Output(
-               "WARN: fname:1: Pkglint parse error in MkLine.Tokenize at \"$\".")
+               "WARN: fileName:1: Pkglint parse error in MkLine.Tokenize at \"$\".")
 }
 
 func (s *Suite) Test_VartypeCheck_BuildlinkDepmethod(c *check.C) {
@@ -41,7 +41,7 @@ func (s *Suite) Test_VartypeCheck_Buildl
                "unknown")
 
        vt.Output(
-               "WARN: fname:2: Invalid dependency method \"unknown\". Valid methods are \"build\" or \"full\".")
+               "WARN: fileName:2: Invalid dependency method \"unknown\". Valid methods are \"build\" or \"full\".")
 }
 
 func (s *Suite) Test_VartypeCheck_Category(c *check.C) {
@@ -61,8 +61,8 @@ func (s *Suite) Test_VartypeCheck_Catego
                "wip")
 
        vt.Output(
-               "ERROR: fname:2: Invalid category \"arabic\".",
-               "ERROR: fname:4: Invalid category \"wip\".")
+               "ERROR: fileName:2: Invalid category \"arabic\".",
+               "ERROR: fileName:4: Invalid category \"wip\".")
 }
 
 func (s *Suite) Test_VartypeCheck_CFlag(c *check.C) {
@@ -79,9 +79,9 @@ func (s *Suite) Test_VartypeCheck_CFlag(
                "`pkg-config pidgin --cflags`")
 
        vt.Output(
-               "WARN: fname:2: Compiler flag \"/W3\" should start with a hyphen.",
-               "WARN: fname:3: Compiler flag \"target:sparc64\" should start with a hyphen.",
-               "WARN: fname:5: Unknown compiler flag \"-XX:+PrintClassHistogramAfterFullGC\".")
+               "WARN: fileName:2: Compiler flag \"/W3\" should start with a hyphen.",
+               "WARN: fileName:3: Compiler flag \"target:sparc64\" should start with a hyphen.",
+               "WARN: fileName:5: Unknown compiler flag \"-XX:+PrintClassHistogramAfterFullGC\".")
 
        vt.Op(opUseMatch)
        vt.Values(
@@ -111,17 +111,17 @@ func (s *Suite) Test_VartypeCheck_Commen
                "Converter converts between measurement units")
 
        vt.Output(
-               "ERROR: fname:2: COMMENT must be set.",
-               "WARN: fname:3: COMMENT should not begin with \"A\".",
-               "WARN: fname:3: COMMENT should not end with a period.",
-               "WARN: fname:4: COMMENT should start with a capital letter.",
-               "WARN: fname:4: COMMENT should not be longer than 70 characters.",
-               "WARN: fname:5: COMMENT should not be enclosed in quotes.",
-               "WARN: fname:6: COMMENT should not be enclosed in quotes.",
-               "WARN: fname:7: COMMENT should not contain \"is a\".",
-               "WARN: fname:8: COMMENT should not contain \"is an\".",
-               "WARN: fname:9: COMMENT should not contain \"is a\".",
-               "WARN: fname:10: COMMENT should not start with the package name.")
+               "ERROR: fileName:2: COMMENT must be set.",
+               "WARN: fileName:3: COMMENT should not begin with \"A\".",
+               "WARN: fileName:3: COMMENT should not end with a period.",
+               "WARN: fileName:4: COMMENT should start with a capital letter.",
+               "WARN: fileName:4: COMMENT should not be longer than 70 characters.",
+               "WARN: fileName:5: COMMENT should not be enclosed in quotes.",
+               "WARN: fileName:6: COMMENT should not be enclosed in quotes.",
+               "WARN: fileName:7: COMMENT should not contain \"is a\".",
+               "WARN: fileName:8: COMMENT should not contain \"is an\".",
+               "WARN: fileName:9: COMMENT should not contain \"is a\".",
+               "WARN: fileName:10: COMMENT should not start with the package name.")
 }
 
 func (s *Suite) Test_VartypeCheck_ConfFiles(c *check.C) {
@@ -137,10 +137,10 @@ func (s *Suite) Test_VartypeCheck_ConfFi
                "share/etc/bootrc /etc/bootrc")
 
        vt.Output(
-               "WARN: fname:1: Values for CONF_FILES should always be pairs of paths.",
-               "WARN: fname:3: Values for CONF_FILES should always be pairs of paths.",
-               "WARN: fname:5: Found absolute pathname: /etc/bootrc",
-               "WARN: fname:5: The destination file \"/etc/bootrc\" should start with a variable reference.")
+               "WARN: fileName:1: Values for CONF_FILES should always be pairs of paths.",
+               "WARN: fileName:3: Values for CONF_FILES should always be pairs of paths.",
+               "WARN: fileName:5: Found absolute pathname: /etc/bootrc",
+               "WARN: fileName:5: The destination file \"/etc/bootrc\" should start with a variable reference.")
 }
 
 func (s *Suite) Test_VartypeCheck_Dependency(c *check.C) {
@@ -172,16 +172,16 @@ func (s *Suite) Test_VartypeCheck_Depend
                "gnome-control-center>=2.20.1{,nb*}")
 
        vt.Output(
-               "WARN: fname:1: Unknown dependency pattern \"Perl\".",
-               "WARN: fname:3: Please use \"perl5-[0-9]*\" instead of \"perl5-*\".",
-               "WARN: fname:5: Only [0-9]* is allowed in the numeric part of a dependency.",
-               "WARN: fname:5: The version pattern \"[5.10-5.22]*\" should not contain a hyphen.",
-               "WARN: fname:6: Unknown dependency pattern \"py-docs\".",
-               "WARN: fname:10: Please use \"5.22{,nb*}\" instead of \"5.22\" as the version pattern.",
-               "WARN: fname:11: Please use \"5.*\" instead of \"5*\" as the version pattern.",
-               "WARN: fname:12: The version pattern \"2.0-[0-9]*\" should not contain a hyphen.",
-               "WARN: fname:20: The version pattern \"[0-9]*,openssh-[0-9]*}\" should not contain a hyphen.", // XXX
-               "WARN: fname:21: Dependency patterns of the form pkgbase>=1.0 don't need the \"{,nb*}\" extension.")
+               "WARN: fileName:1: Invalid dependency pattern \"Perl\".",
+               "WARN: fileName:3: Please use \"perl5-[0-9]*\" instead of \"perl5-*\".",
+               "WARN: fileName:5: Only [0-9]* is allowed in the numeric part of a dependency.",
+               "WARN: fileName:5: The version pattern \"[5.10-5.22]*\" should not contain a hyphen.",
+               "WARN: fileName:6: Invalid dependency pattern \"py-docs\".",
+               "WARN: fileName:10: Please use \"5.22{,nb*}\" instead of \"5.22\" as the version pattern.",
+               "WARN: fileName:11: Please use \"5.*\" instead of \"5*\" as the version pattern.",
+               "WARN: fileName:12: The version pattern \"2.0-[0-9]*\" should not contain a hyphen.",
+               "WARN: fileName:20: The version pattern \"[0-9]*,openssh-[0-9]*}\" should not contain a hyphen.", // XXX
+               "WARN: fileName:21: Dependency patterns of the form pkgbase>=1.0 don't need the \"{,nb*}\" extension.")
 }
 
 func (s *Suite) Test_VartypeCheck_DependencyWithPath(c *check.C) {
@@ -196,7 +196,7 @@ func (s *Suite) Test_VartypeCheck_Depend
 
        vt.Varname("DEPENDS")
        vt.Op(opAssignAppend)
-       vt.File(G.Pkg.File("fname.mk"))
+       vt.File(G.Pkg.File("fileName.mk"))
        vt.Values(
                "Perl",
                "perl5>=5.22:../perl5",
@@ -214,21 +214,21 @@ func (s *Suite) Test_VartypeCheck_Depend
                "gmake-[0-9]*:../../devel/gmake")
 
        vt.Output(
-               "WARN: ~/category/package/fname.mk:1: Unknown dependency pattern with path \"Perl\".",
-               "WARN: ~/category/package/fname.mk:2: Dependencies should have the form \"../../category/package\".",
-               "ERROR: ~/category/package/fname.mk:3: \"../../lang/perl5\" does not exist.",
-               "ERROR: ~/category/package/fname.mk:3: There is no package in \"lang/perl5\".",
-               "WARN: ~/category/package/fname.mk:3: Please use USE_TOOLS+=perl:run instead of this dependency.",
-               "WARN: ~/category/package/fname.mk:4: Unknown dependency pattern \"broken0.12.1\".",
-               "WARN: ~/category/package/fname.mk:5: Unknown dependency pattern \"broken[0-9]*\".",
-               "WARN: ~/category/package/fname.mk:6: Unknown dependency pattern with path \"broken[0-9]*../../x11/alacarte\".",
-               "WARN: ~/category/package/fname.mk:7: Unknown dependency pattern \"broken>=\".",
-               "WARN: ~/category/package/fname.mk:8: Unknown dependency pattern \"broken=0\".",
-               "WARN: ~/category/package/fname.mk:9: Unknown dependency pattern \"broken=\".",
-               "WARN: ~/category/package/fname.mk:10: Unknown dependency pattern \"broken-\".",
-               "WARN: ~/category/package/fname.mk:11: Unknown dependency pattern \"broken>\".",
-               "WARN: ~/category/package/fname.mk:13: Please use USE_TOOLS+=msgfmt instead of this dependency.",
-               "WARN: ~/category/package/fname.mk:14: Please use USE_TOOLS+=gmake instead of this dependency.")
+               "WARN: ~/category/package/fileName.mk:1: Invalid dependency pattern with path \"Perl\".",
+               "WARN: ~/category/package/fileName.mk:2: Dependencies should have the form \"../../category/package\".",
+               "ERROR: ~/category/package/fileName.mk:3: \"../../lang/perl5\" does not exist.",
+               "ERROR: ~/category/package/fileName.mk:3: There is no package in \"lang/perl5\".",
+               "WARN: ~/category/package/fileName.mk:3: Please use USE_TOOLS+=perl:run instead of this dependency.",
+               "WARN: ~/category/package/fileName.mk:4: Invalid dependency pattern \"broken0.12.1\".",
+               "WARN: ~/category/package/fileName.mk:5: Invalid dependency pattern \"broken[0-9]*\".",
+               "WARN: ~/category/package/fileName.mk:6: Invalid dependency pattern with path \"broken[0-9]*../../x11/alacarte\".",
+               "WARN: ~/category/package/fileName.mk:7: Invalid dependency pattern \"broken>=\".",
+               "WARN: ~/category/package/fileName.mk:8: Invalid dependency pattern \"broken=0\".",
+               "WARN: ~/category/package/fileName.mk:9: Invalid dependency pattern \"broken=\".",
+               "WARN: ~/category/package/fileName.mk:10: Invalid dependency pattern \"broken-\".",
+               "WARN: ~/category/package/fileName.mk:11: Invalid dependency pattern \"broken>\".",
+               "WARN: ~/category/package/fileName.mk:13: Please use USE_TOOLS+=msgfmt instead of this dependency.",
+               "WARN: ~/category/package/fileName.mk:14: Please use USE_TOOLS+=gmake instead of this dependency.")
 }
 
 func (s *Suite) Test_VartypeCheck_DistSuffix(c *check.C) {
@@ -240,7 +240,7 @@ func (s *Suite) Test_VartypeCheck_DistSu
                ".tar.bz2")
 
        vt.Output(
-               "NOTE: fname:1: EXTRACT_SUFX is \".tar.gz\" by default, so this definition may be redundant.")
+               "NOTE: fileName:1: EXTRACT_SUFX is \".tar.gz\" by default, so this definition may be redundant.")
 }
 
 func (s *Suite) Test_VartypeCheck_EmulPlatform(c *check.C) {
@@ -253,12 +253,12 @@ func (s *Suite) Test_VartypeCheck_EmulPl
                "${LINUX}")
 
        vt.Output(
-               "WARN: fname:2: \"nextbsd\" is not valid for the operating system part of EMUL_PLATFORM. "+
+               "WARN: fileName:2: \"nextbsd\" is not valid for the operating system part of EMUL_PLATFORM. "+
                        "Use one of "+
                        "{ bitrig bsdos cygwin darwin dragonfly freebsd haiku hpux "+
                        "interix irix linux mirbsd netbsd openbsd osf1 solaris sunos "+
                        "} instead.",
-               "WARN: fname:2: \"8087\" is not valid for the hardware architecture part of EMUL_PLATFORM. "+
+               "WARN: fileName:2: \"8087\" 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 "+
@@ -266,7 +266,7 @@ func (s *Suite) Test_VartypeCheck_EmulPl
                        "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.",
-               "WARN: fname:3: \"${LINUX}\" is not a valid emulation platform.")
+               "WARN: fileName:3: \"${LINUX}\" is not a valid emulation platform.")
 }
 
 func (s *Suite) Test_VartypeCheck_Enum(c *check.C) {
@@ -282,9 +282,9 @@ func (s *Suite) Test_VartypeCheck_Enum(c
                "[")
 
        vt.Output(
-               "WARN: fname:3: The pattern \"sun-jdk*\" cannot match any of { jdk1 jdk2 jdk4 } for JDK.",
-               "WARN: fname:5: Invalid match pattern \"[\".",
-               "WARN: fname:5: The pattern \"[\" cannot match any of { jdk1 jdk2 jdk4 } for JDK.")
+               "WARN: fileName:3: The pattern \"sun-jdk*\" cannot match any of { jdk1 jdk2 jdk4 } for JDK.",
+               "WARN: fileName:5: Invalid match pattern \"[\".",
+               "WARN: fileName:5: The pattern \"[\" cannot match any of { jdk1 jdk2 jdk4 } for JDK.")
 }
 
 func (s *Suite) Test_VartypeCheck_Enum__use_match(c *check.C) {
@@ -324,64 +324,64 @@ func (s *Suite) Test_VartypeCheck_FetchU
                "${MASTER_SITE_INVALID:=subdir/}")
 
        vt.Output(
-               "WARN: fname:1: Please use ${MASTER_SITE_GITHUB:=example/} "+
+               "WARN: fileName:1: Please use ${MASTER_SITE_GITHUB:=example/} "+
                        "instead of \"https://github.com/example/project/\"; "+
                        "and run \""+confMake+" help topic=github\" for further tips.",
-               "WARN: fname:2: Please use ${MASTER_SITE_GNU:=bison} instead of \"http://ftp.gnu.org/pub/gnu/bison\".";,
-               "ERROR: fname:3: The subdirectory in MASTER_SITE_GNU must end with a slash.",
-               "ERROR: fname:4: The site MASTER_SITE_INVALID does not exist.")
+               "WARN: fileName:2: Please use ${MASTER_SITE_GNU:=bison} instead of \"http://ftp.gnu.org/pub/gnu/bison\".";,
+               "ERROR: fileName:3: The subdirectory in MASTER_SITE_GNU must end with a slash.",
+               "ERROR: fileName:4: The site MASTER_SITE_INVALID does not exist.")
 
        // PR 46570, keyword gimp-fix-ca
        vt.Values(
-               "https://example.org/download.cgi?fname=fname&sha1=12341234";)
+               "https://example.org/download.cgi?fileName=fileName&sha1=12341234";)
 
        t.CheckOutputEmpty()
 
        vt.Values(
                "http://example.org/distfiles/";,
-               "http://example.org/download?fname=distfile;version=1.0";,
-               "http://example.org/download?fname=<distfile>;version=<version>")
+               "http://example.org/download?fileName=distfile;version=1.0";,
+               "http://example.org/download?fileName=<distfile>;version=<version>")
 
        vt.Output(
-               "WARN: fname:8: \"http://example.org/download?fname=<distfile>;version=<version>\" is not a valid URL.")
+               "WARN: fileName:8: \"http://example.org/download?fileName=<distfile>;version=<version>\" is not a valid URL.")
 }
 
-func (s *Suite) Test_VartypeCheck_Filename(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).Filename)
+func (s *Suite) Test_VartypeCheck_FileName(c *check.C) {
+       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).FileName)
 
        vt.Varname("FNAME")
        vt.Values(
-               "Filename with spaces.docx",
+               "FileName with spaces.docx",
                "OS/2-manual.txt")
 
        vt.Output(
-               "WARN: fname:1: \"Filename with spaces.docx\" is not a valid filename.",
-               "WARN: fname:2: A filename should not contain a slash.")
+               "WARN: fileName:1: \"FileName with spaces.docx\" is not a valid file name.",
+               "WARN: fileName:2: A file name should not contain a slash.")
 
        vt.Op(opUseMatch)
        vt.Values(
-               "Filename with spaces.docx")
+               "FileName with spaces.docx")
 
        // There's no guarantee that a file name only contains [A-Za-z0-9.].
        // Therefore there are no useful checks in this situation.
        vt.OutputEmpty()
 }
 
-func (s *Suite) Test_VartypeCheck_Filemask(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).Filemask)
+func (s *Suite) Test_VartypeCheck_FileMask(c *check.C) {
+       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).FileMask)
 
        vt.Varname("FNAME")
        vt.Values(
-               "Filemask with spaces.docx",
+               "FileMask with spaces.docx",
                "OS/2-manual.txt")
 
        vt.Output(
-               "WARN: fname:1: \"Filemask with spaces.docx\" is not a valid filename mask.",
-               "WARN: fname:2: A filename mask should not contain a slash.")
+               "WARN: fileName:1: \"FileMask with spaces.docx\" is not a valid file name mask.",
+               "WARN: fileName:2: A file name mask should not contain a slash.")
 
        vt.Op(opUseMatch)
        vt.Values(
-               "Filemask with spaces.docx")
+               "FileMask with spaces.docx")
 
        // There's no guarantee that a file name only contains [A-Za-z0-9.].
        // Therefore there are no useful checks in this situation.
@@ -400,8 +400,8 @@ func (s *Suite) Test_VartypeCheck_FileMo
                "${OTHER_PERMS}")
 
        vt.Output(
-               "WARN: fname:1: Invalid file mode \"u+rwx\".",
-               "WARN: fname:4: Invalid file mode \"12345\".")
+               "WARN: fileName:1: Invalid file mode \"u+rwx\".",
+               "WARN: fileName:4: Invalid file mode \"12345\".")
 
        vt.Op(opUseMatch)
        vt.Values(
@@ -410,7 +410,7 @@ func (s *Suite) Test_VartypeCheck_FileMo
        // There's no guarantee that a file name only contains [A-Za-z0-9.].
        // Therefore there are no useful checks in this situation.
        vt.Output(
-               "WARN: fname:11: Invalid file mode \"u+rwx\".")
+               "WARN: fileName:11: Invalid file mode \"u+rwx\".")
 }
 
 func (s *Suite) Test_VartypeCheck_GccReqd(c *check.C) {
@@ -427,8 +427,8 @@ func (s *Suite) Test_VartypeCheck_GccReq
                "6",
                "7.3")
        vt.Output(
-               "WARN: fname:5: GCC version numbers should only contain the major version (5).",
-               "WARN: fname:7: GCC version numbers should only contain the major version (7).")
+               "WARN: fileName:5: GCC version numbers should only contain the major version (5).",
+               "WARN: fileName:7: GCC version numbers should only contain the major version (7).")
 }
 
 func (s *Suite) Test_VartypeCheck_Homepage(c *check.C) {
@@ -440,7 +440,7 @@ func (s *Suite) Test_VartypeCheck_Homepa
                "${MASTER_SITES}")
 
        vt.Output(
-               "WARN: fname:1: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
+               "WARN: fileName:1: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
 
        G.Pkg = NewPackage(t.File("category/package"))
        G.Pkg.vars.Define("MASTER_SITES", t.NewMkLine(G.Pkg.File("Makefile"), 5, "MASTER_SITES=\thttps://cdn.NetBSD.org/pub/pkgsrc/distfiles/";))
@@ -449,7 +449,7 @@ func (s *Suite) Test_VartypeCheck_Homepa
                "${MASTER_SITES}")
 
        vt.Output(
-               "WARN: fname:2: HOMEPAGE should not be defined in terms of MASTER_SITEs. Use https://cdn.NetBSD.org/pub/pkgsrc/distfiles/ directly.")
+               "WARN: fileName:2: HOMEPAGE should not be defined in terms of MASTER_SITEs. Use https://cdn.NetBSD.org/pub/pkgsrc/distfiles/ directly.")
 }
 
 func (s *Suite) Test_VartypeCheck_Identifier(c *check.C) {
@@ -467,9 +467,9 @@ func (s *Suite) Test_VartypeCheck_Identi
                "A*B")
 
        vt.Output(
-               "WARN: fname:2: Invalid identifier \"identifiers cannot contain spaces\".",
-               "WARN: fname:3: Invalid identifier \"id/cannot/contain/slashes\".",
-               "WARN: fname:11: Invalid identifier pattern \"[A-Z]\" for SUBST_CLASSES.")
+               "WARN: fileName:2: Invalid identifier \"identifiers cannot contain spaces\".",
+               "WARN: fileName:3: Invalid identifier \"id/cannot/contain/slashes\".",
+               "WARN: fileName:11: Invalid identifier pattern \"[A-Z]\" for SUBST_CLASSES.")
 }
 
 func (s *Suite) Test_VartypeCheck_Integer(c *check.C) {
@@ -484,8 +484,8 @@ func (s *Suite) Test_VartypeCheck_Intege
                "11111111111111111111111111111111111111111111111")
 
        vt.Output(
-               "WARN: fname:1: Invalid integer \"${OTHER_VAR}\".",
-               "WARN: fname:3: Invalid integer \"-13\".")
+               "WARN: fileName:1: Invalid integer \"${OTHER_VAR}\".",
+               "WARN: fileName:3: Invalid integer \"-13\".")
 }
 
 func (s *Suite) Test_VartypeCheck_LdFlag(c *check.C) {
@@ -505,9 +505,9 @@ func (s *Suite) Test_VartypeCheck_LdFlag
                "anything")
 
        vt.Output(
-               "WARN: fname:4: Unknown linker flag \"-unknown\".",
-               "WARN: fname:5: Linker flag \"no-hyphen\" should start with a hyphen.",
-               "WARN: fname:6: Please use \"${COMPILER_RPATH_FLAG}\" instead of \"-Wl,--rpath\".")
+               "WARN: fileName:4: Unknown linker flag \"-unknown\".",
+               "WARN: fileName:5: Linker flag \"no-hyphen\" should start with a hyphen.",
+               "WARN: fileName:6: Please use \"${COMPILER_RPATH_FLAG}\" instead of \"-Wl,--rpath\".")
 }
 
 func (s *Suite) Test_VartypeCheck_License(c *check.C) {
@@ -519,8 +519,8 @@ func (s *Suite) Test_VartypeCheck_Licens
                "AND mit")
 
        vt.Output(
-               "WARN: fname:1: License file ~/licenses/gnu-gpl-v2 does not exist.",
-               "ERROR: fname:2: Parse error for license condition \"AND mit\".")
+               "WARN: fileName:1: License file ~/licenses/gnu-gpl-v2 does not exist.",
+               "ERROR: fileName:2: Parse error for license condition \"AND mit\".")
 
        vt.Op(opAssignAppend)
        vt.Values(
@@ -528,8 +528,8 @@ func (s *Suite) Test_VartypeCheck_Licens
                "AND mit")
 
        vt.Output(
-               "ERROR: fname:11: Parse error for appended license condition \"gnu-gpl-v2\".",
-               "WARN: fname:12: License file ~/licenses/mit does not exist.")
+               "ERROR: fileName:11: Parse error for appended license condition \"gnu-gpl-v2\".",
+               "WARN: fileName:12: License file ~/licenses/mit does not exist.")
 }
 
 func (s *Suite) Test_VartypeCheck_MachineGnuPlatform(c *check.C) {
@@ -545,17 +545,17 @@ func (s *Suite) Test_VartypeCheck_Machin
                "${OTHER_VAR}")
 
        vt.Output(
-               "WARN: fname:2: The pattern \"Cygwin\" cannot match any of "+
+               "WARN: fileName:2: The pattern \"Cygwin\" cannot match any of "+
                        "{ aarch64 aarch64_be alpha amd64 arc arm armeb armv4 armv4eb armv6 armv6eb armv7 armv7eb "+
                        "cobalt convex dreamcast hpcmips hpcsh hppa hppa64 i386 i486 ia64 m5407 m68010 m68k m88k "+
                        "mips mips64 mips64el mipseb mipsel mipsn32 mlrisc ns32k pc532 pmax powerpc powerpc64 "+
                        "rs6000 s390 sh shle sparc sparc64 vax x86_64 "+
                        "} for the hardware architecture part of MACHINE_GNU_PLATFORM.",
-               "WARN: fname:2: The pattern \"amd64\" cannot match any of "+
+               "WARN: fileName:2: The pattern \"amd64\" cannot match any of "+
                        "{ bitrig bsdos cygwin darwin dragonfly freebsd haiku hpux interix irix linux mirbsd "+
                        "netbsd openbsd osf1 solaris sunos } "+
                        "for the operating system part of MACHINE_GNU_PLATFORM.",
-               "WARN: fname:4: \"*-*-*-*\" is not a valid platform pattern.")
+               "WARN: fileName:4: \"*-*-*-*\" is not a valid platform pattern.")
 }
 
 func (s *Suite) Test_VartypeCheck_MailAddress(c *check.C) {
@@ -569,10 +569,10 @@ func (s *Suite) Test_VartypeCheck_MailAd
                "user1%example.org@localhost,user2%example.org@localhost")
 
        vt.Output(
-               "WARN: fname:1: Please write \"NetBSD.org\" instead of \"netbsd.org\".",
-               "ERROR: fname:2: This mailing list address is obsolete. Use pkgsrc-users%NetBSD.org@localhost instead.",
-               "ERROR: fname:3: This mailing list address is obsolete. Use pkgsrc-users%NetBSD.org@localhost instead.",
-               "WARN: fname:4: \"user1%example.org@localhost,user2%example.org@localhost\" is not a valid mail address.")
+               "WARN: fileName:1: Please write \"NetBSD.org\" instead of \"netbsd.org\".",
+               "ERROR: fileName:2: This mailing list address is obsolete. Use pkgsrc-users%NetBSD.org@localhost instead.",
+               "ERROR: fileName:3: This mailing list address is obsolete. Use pkgsrc-users%NetBSD.org@localhost instead.",
+               "WARN: fileName:4: \"user1%example.org@localhost,user2%example.org@localhost\" is not a valid mail address.")
 }
 
 func (s *Suite) Test_VartypeCheck_Message(c *check.C) {
@@ -584,7 +584,7 @@ func (s *Suite) Test_VartypeCheck_Messag
                "Correct paths")
 
        vt.Output(
-               "WARN: fname:1: SUBST_MESSAGE.id should not be quoted.")
+               "WARN: fileName:1: SUBST_MESSAGE.id should not be quoted.")
 }
 
 func (s *Suite) Test_VartypeCheck_Option(c *check.C) {
@@ -602,9 +602,9 @@ func (s *Suite) Test_VartypeCheck_Option
                "UPPER")
 
        vt.Output(
-               "WARN: fname:3: Unknown option \"unknown\".",
-               "WARN: fname:4: Use of the underscore character in option names is deprecated.",
-               "ERROR: fname:5: Invalid option name \"UPPER\". "+
+               "WARN: fileName:3: Unknown option \"unknown\".",
+               "WARN: fileName:4: Use of the underscore character in option names is deprecated.",
+               "ERROR: fileName:5: Invalid option name \"UPPER\". "+
                        "Option names must start with a lowercase letter and be all-lowercase.")
 }
 
@@ -617,13 +617,13 @@ func (s *Suite) Test_VartypeCheck_Pathli
                "/directory with spaces")
 
        vt.Output(
-               "WARN: fname:1: All components of PATH (in this case \".\") should be absolute paths.",
-               "WARN: fname:1: All components of PATH (in this case \"\") should be absolute paths.",
-               "WARN: fname:2: \"/directory with spaces\" is not a valid pathname.")
+               "WARN: fileName:1: All components of PATH (in this case \".\") should be absolute paths.",
+               "WARN: fileName:1: All components of PATH (in this case \"\") should be absolute paths.",
+               "WARN: fileName:2: \"/directory with spaces\" is not a valid pathname.")
 }
 
-func (s *Suite) Test_VartypeCheck_Pathmask(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).Pathmask)
+func (s *Suite) Test_VartypeCheck_PathMask(c *check.C) {
+       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).PathMask)
 
        vt.Varname("DISTDIRS")
        vt.Values(
@@ -632,8 +632,8 @@ func (s *Suite) Test_VartypeCheck_Pathma
                "src/*/*")
 
        vt.Output(
-               "WARN: fname:1: Found absolute pathname: /home/user/*",
-               "WARN: fname:2: \"src/*&*\" is not a valid pathname mask.")
+               "WARN: fileName:1: Found absolute pathname: /home/user/*",
+               "WARN: fileName:2: \"src/*&*\" is not a valid pathname mask.")
 
        vt.Op(opUseMatch)
        vt.Values("any")
@@ -641,8 +641,8 @@ func (s *Suite) Test_VartypeCheck_Pathma
        vt.OutputEmpty()
 }
 
-func (s *Suite) Test_VartypeCheck_Pathname(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).Pathname)
+func (s *Suite) Test_VartypeCheck_PathName(c *check.C) {
+       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).PathName)
 
        vt.Varname("EGDIR")
        vt.Values(
@@ -655,8 +655,8 @@ func (s *Suite) Test_VartypeCheck_Pathna
                "anything")
 
        vt.Output(
-               "WARN: fname:1: \"${PREFIX}/*\" is not a valid pathname.",
-               "WARN: fname:4: Found absolute pathname: /bin")
+               "WARN: fileName:1: \"${PREFIX}/*\" is not a valid pathname.",
+               "WARN: fileName:4: Found absolute pathname: /bin")
 }
 
 func (s *Suite) Test_VartypeCheck_Perl5Packlist(c *check.C) {
@@ -668,7 +668,7 @@ func (s *Suite) Test_VartypeCheck_Perl5P
                "anything else")
 
        vt.Output(
-               "WARN: fname:1: PERL5_PACKLIST should not depend on other variables.")
+               "WARN: fileName:1: PERL5_PACKLIST should not depend on other variables.")
 }
 
 func (s *Suite) Test_VartypeCheck_Perms(c *check.C) {
@@ -683,7 +683,7 @@ func (s *Suite) Test_VartypeCheck_Perms(
                "${REAL_ROOT_USER}")
 
        vt.Output(
-               "ERROR: fname:2: ROOT_USER must not be used in permission definitions. Use REAL_ROOT_USER instead.")
+               "ERROR: fileName:2: ROOT_USER must not be used in permission definitions. Use REAL_ROOT_USER instead.")
 }
 
 func (s *Suite) Test_VartypeCheck_Pkgname(c *check.C) {
@@ -702,9 +702,7 @@ func (s *Suite) Test_VartypeCheck_Pkgnam
                "pkgbase-3.1.4.1.5.9.2.6.5.3.5.8.9.7.9")
 
        vt.Output(
-               "WARN: fname:8: \"pkgbase-z1\" is not a valid package name. " +
-                       "A valid package name has the form packagename-version, " +
-                       "where version consists only of digits, letters and dots.")
+               "WARN: fileName:8: \"pkgbase-z1\" is not a valid package name.")
 }
 
 func (s *Suite) Test_VartypeCheck_PkgOptionsVar(c *check.C) {
@@ -717,8 +715,8 @@ func (s *Suite) Test_VartypeCheck_PkgOpt
                "PKG_OPTS.mc")
 
        vt.Output(
-               "ERROR: fname:1: PKGBASE must not be used in PKG_OPTIONS_VAR.",
-               "ERROR: fname:3: PKG_OPTIONS_VAR must be of the form \"PKG_OPTIONS.*\", not \"PKG_OPTS.mc\".")
+               "ERROR: fileName:1: PKGBASE must not be used in PKG_OPTIONS_VAR.",
+               "ERROR: fileName:3: PKG_OPTIONS_VAR must be of the form \"PKG_OPTIONS.*\", not \"PKG_OPTS.mc\".")
 }
 
 func (s *Suite) Test_VartypeCheck_PkgPath(c *check.C) {
@@ -736,10 +734,10 @@ func (s *Suite) Test_VartypeCheck_PkgPat
                "../../invalid/relative")
 
        vt.Output(
-               "ERROR: fname:3: \"../../invalid\" does not exist.",
-               "WARN: fname:3: \"../../invalid\" is not a valid relative package directory.",
-               "ERROR: fname:4: \"../../../../invalid/relative\" does not exist.",
-               "WARN: fname:4: \"../../../../invalid/relative\" is not a valid relative package directory.")
+               "ERROR: fileName:3: \"../../invalid\" does not exist.",
+               "WARN: fileName:3: \"../../invalid\" is not a valid relative package directory.",
+               "ERROR: fileName:4: \"../../../../invalid/relative\" does not exist.",
+               "WARN: fileName:4: \"../../../../invalid/relative\" is not a valid relative package directory.")
 }
 
 func (s *Suite) Test_VartypeCheck_PkgRevision(c *check.C) {
@@ -750,8 +748,8 @@ func (s *Suite) Test_VartypeCheck_PkgRev
                "3a")
 
        vt.Output(
-               "WARN: fname:1: PKGREVISION must be a positive integer number.",
-               "ERROR: fname:1: PKGREVISION only makes sense directly in the package Makefile.")
+               "WARN: fileName:1: PKGREVISION must be a positive integer number.",
+               "ERROR: fileName:1: PKGREVISION only makes sense directly in the package Makefile.")
 
        vt.File("Makefile")
        vt.Values(
@@ -776,31 +774,31 @@ func (s *Suite) Test_VartypeCheck_Machin
                "NetBSD-[0-1]*-*")
 
        vt.Output(
-               "WARN: fname:1: \"linux-i386\" is not a valid platform pattern.",
-               "WARN: fname:2: The pattern \"nextbsd\" cannot match any of "+
+               "WARN: fileName:1: \"linux-i386\" is not a valid platform pattern.",
+               "WARN: fileName:2: The pattern \"nextbsd\" 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 ONLY_FOR_PLATFORM.",
-               "WARN: fname:2: The pattern \"8087\" cannot match any of "+
+               "WARN: fileName:2: The pattern \"8087\" 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 ONLY_FOR_PLATFORM.",
-               "WARN: fname:3: The pattern \"netbsd\" cannot match any of "+
+               "WARN: fileName:3: The pattern \"netbsd\" 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 ONLY_FOR_PLATFORM.",
-               "WARN: fname:3: The pattern \"l*\" cannot match any of "+
+               "WARN: fileName:3: The pattern \"l*\" 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 ONLY_FOR_PLATFORM.",
-               "WARN: fname:5: \"FreeBSD*\" is not a valid platform pattern.",
-               "WARN: fname:8: Please use \"[0-1].*\" instead of \"[0-1]*\" as the version pattern.")
+               "WARN: fileName:5: \"FreeBSD*\" is not a valid platform pattern.",
+               "WARN: fileName:8: Please use \"[0-1].*\" instead of \"[0-1]*\" as the version pattern.")
 }
 
 func (s *Suite) Test_VartypeCheck_PythonDependency(c *check.C) {
@@ -813,8 +811,8 @@ func (s *Suite) Test_VartypeCheck_Python
                "cairo,X")
 
        vt.Output(
-               "WARN: fname:2: Python dependencies should not contain variables.",
-               "WARN: fname:3: Invalid Python dependency \"cairo,X\".")
+               "WARN: fileName:2: Python dependencies should not contain variables.",
+               "WARN: fileName:3: Invalid Python dependency \"cairo,X\".")
 }
 
 func (s *Suite) Test_VartypeCheck_PrefixPathname(c *check.C) {
@@ -826,7 +824,7 @@ func (s *Suite) Test_VartypeCheck_Prefix
                "share/locale")
 
        vt.Output(
-               "WARN: fname:1: Please use \"${PKGMANDIR}/man1\" instead of \"man/man1\".")
+               "WARN: fileName:1: Please use \"${PKGMANDIR}/man1\" instead of \"man/man1\".")
 }
 
 func (s *Suite) Test_VartypeCheck_RelativePkgPath(c *check.C) {
@@ -845,9 +843,9 @@ func (s *Suite) Test_VartypeCheck_Relati
                "../../invalid/relative")
 
        vt.Output(
-               "ERROR: fname:1: \"category/other-package\" does not exist.",
-               "ERROR: fname:4: \"invalid\" does not exist.",
-               "ERROR: fname:5: \"../../invalid/relative\" does not exist.")
+               "ERROR: fileName:1: \"category/other-package\" does not exist.",
+               "ERROR: fileName:4: \"invalid\" does not exist.",
+               "ERROR: fileName:5: \"../../invalid/relative\" does not exist.")
 }
 
 func (s *Suite) Test_VartypeCheck_Restricted(c *check.C) {
@@ -858,7 +856,7 @@ func (s *Suite) Test_VartypeCheck_Restri
                "May only be distributed free of charge")
 
        vt.Output(
-               "WARN: fname:1: The only valid value for NO_BIN_ON_CDROM is ${RESTRICTED}.")
+               "WARN: fileName:1: The only valid value for NO_BIN_ON_CDROM is ${RESTRICTED}.")
 }
 
 func (s *Suite) Test_VartypeCheck_SedCommands(c *check.C) {
@@ -877,16 +875,18 @@ func (s *Suite) Test_VartypeCheck_SedCom
                "-e")
 
        vt.Output(
-               "NOTE: fname:1: Please always use \"-e\" in sed commands, even if there is only one substitution.",
-               "NOTE: fname:2: Each sed command should appear in an assignment of its own.",
-               "WARN: fname:3: The # character starts a comment.",
-               "ERROR: fname:3: Invalid shell words \"\\\"s,\" in sed commands.",
-               "WARN: fname:8: Unknown sed command \"1d\".",
-               "ERROR: fname:9: The -e option to sed requires an argument.")
+               "NOTE: fileName:1: Please always use \"-e\" in sed commands, even if there is only one substitution.",
+               "NOTE: fileName:2: Each sed command should appear in an assignment of its own.",
+               "WARN: fileName:3: The # character starts a comment.",
+               "ERROR: fileName:3: Invalid shell words \"\\\"s,\" in sed commands.",
+               "WARN: fileName:8: Unknown sed command \"1d\".",
+               "ERROR: fileName:9: The -e option to sed requires an argument.")
 }
 
 func (s *Suite) Test_VartypeCheck_ShellCommand(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).ShellCommand)
+       t := s.Init(c)
+       t.SetupVartypes()
+       vt := NewVartypeCheckTester(t, (*VartypeCheck).ShellCommand)
 
        vt.Varname("INSTALL_CMD")
        vt.Values(
@@ -896,7 +896,10 @@ func (s *Suite) Test_VartypeCheck_ShellC
 }
 
 func (s *Suite) Test_VartypeCheck_ShellCommands(c *check.C) {
-       vt := NewVartypeCheckTester(s.Init(c), (*VartypeCheck).ShellCommands)
+       t := s.Init(c)
+       t.SetupVartypes()
+       t.SetupTool("echo", "ECHO", AtRunTime)
+       vt := NewVartypeCheckTester(t, (*VartypeCheck).ShellCommands)
 
        vt.Varname("GENERATE_PLIST")
        vt.Values(
@@ -904,7 +907,7 @@ func (s *Suite) Test_VartypeCheck_ShellC
                "echo bin/program;")
 
        vt.Output(
-               "WARN: fname:1: This shell command list should end with a semicolon.")
+               "WARN: fileName:1: This shell command list should end with a semicolon.")
 }
 
 func (s *Suite) Test_VartypeCheck_Stage(c *check.C) {
@@ -917,7 +920,7 @@ func (s *Suite) Test_VartypeCheck_Stage(
                "pre-test")
 
        vt.Output(
-               "WARN: fname:2: Invalid stage name \"post-modern\". " +
+               "WARN: fileName:2: Invalid stage name \"post-modern\". " +
                        "Use one of {pre,do,post}-{extract,patch,configure,build,test,install}.")
 }
 
@@ -939,10 +942,10 @@ func (s *Suite) Test_VartypeCheck_Tool(c
                "unknown")
 
        vt.Output(
-               "ERROR: fname:2: Unknown tool dependency \"unknown\". "+
+               "ERROR: fileName:2: Unknown tool dependency \"unknown\". "+
                        "Use one of \"bootstrap\", \"build\", \"pkgsrc\", \"run\" or \"test\".",
-               "ERROR: fname:4: Malformed tool dependency: \"mal:formed:tool\".",
-               "ERROR: fname:5: Unknown tool \"unknown\".")
+               "ERROR: fileName:4: Malformed tool dependency: \"mal:formed:tool\".",
+               "ERROR: fileName:5: Unknown tool \"unknown\".")
 
        vt.Varname("USE_TOOLS.NetBSD")
        vt.Op(opAssignAppend)
@@ -951,7 +954,7 @@ func (s *Suite) Test_VartypeCheck_Tool(c
                "tool2:unknown")
 
        vt.Output(
-               "ERROR: fname:12: Unknown tool dependency \"unknown\". " +
+               "ERROR: fileName:12: Unknown tool dependency \"unknown\". " +
                        "Use one of \"bootstrap\", \"build\", \"pkgsrc\", \"run\" or \"test\".")
 
        vt.Varname("TOOLS_NOOP")
@@ -978,12 +981,12 @@ func (s *Suite) Test_VartypeCheck_URL(c 
                "string with spaces")
 
        vt.Output(
-               "WARN: fname:3: Please write NetBSD.org instead of www.netbsd.org.",
-               "WARN: fname:4: \"mailto:someone%example.org@localhost\"; is not a valid URL.",
-               "WARN: fname:5: \"httpxs://www.example.org\" is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.",
-               "NOTE: fname:6: For consistency, please add a trailing slash to \"https://www.example.org\".";,
-               "WARN: fname:7: \"https://www.example.org/path with spaces\" is not a valid URL.",
-               "WARN: fname:8: \"string with spaces\" is not a valid URL.")
+               "WARN: fileName:3: Please write NetBSD.org instead of www.netbsd.org.",
+               "WARN: fileName:4: \"mailto:someone%example.org@localhost\"; is not a valid URL.",
+               "WARN: fileName:5: \"httpxs://www.example.org\" is not a valid URL. Only ftp, gopher, http, and https URLs are allowed here.",
+               "NOTE: fileName:6: For consistency, please add a trailing slash to \"https://www.example.org\".";,
+               "WARN: fileName:7: \"https://www.example.org/path with spaces\" is not a valid URL.",
+               "WARN: fileName:8: \"string with spaces\" is not a valid URL.")
 }
 
 func (s *Suite) Test_VartypeCheck_UserGroupName(c *check.C) {
@@ -999,8 +1002,8 @@ func (s *Suite) Test_VartypeCheck_UserGr
                "${OTHER_VAR}")
 
        vt.Output(
-               "WARN: fname:1: Invalid user or group name \"user with spaces\".",
-               "WARN: fname:4: Invalid user or group name \"domain\\\\user\".")
+               "WARN: fileName:1: Invalid user or group name \"user with spaces\".",
+               "WARN: fileName:4: Invalid user or group name \"domain\\\\user\".")
 }
 
 func (s *Suite) Test_VartypeCheck_VariableName(c *check.C) {
@@ -1014,7 +1017,7 @@ func (s *Suite) Test_VartypeCheck_Variab
                "${INDIRECT}")
 
        vt.Output(
-               "WARN: fname:2: \"VarBase\" is not a valid variable name.")
+               "WARN: fileName:2: \"VarBase\" is not a valid variable name.")
 }
 
 func (s *Suite) Test_VartypeCheck_Version(c *check.C) {
@@ -1029,7 +1032,7 @@ func (s *Suite) Test_VartypeCheck_Versio
                "4.1-SNAPSHOT",
                "4pre7")
        vt.Output(
-               "WARN: fname:4: Invalid version number \"4.1-SNAPSHOT\".")
+               "WARN: fileName:4: Invalid version number \"4.1-SNAPSHOT\".")
 
        vt.Op(opUseMatch)
        vt.Values(
@@ -1041,10 +1044,10 @@ func (s *Suite) Test_VartypeCheck_Versio
                "1.[2-7].*",
                "[0-9]*")
        vt.Output(
-               "WARN: fname:11: Invalid version number pattern \"a*\".",
-               "WARN: fname:12: Invalid version number pattern \"1.2/456\".",
-               "WARN: fname:13: Please use \"4.*\" instead of \"4*\" as the version pattern.",
-               "WARN: fname:15: Please use \"1.[234].*\" instead of \"1.[234]*\" as the version pattern.")
+               "WARN: fileName:11: Invalid version number pattern \"a*\".",
+               "WARN: fileName:12: Invalid version number pattern \"1.2/456\".",
+               "WARN: fileName:13: Please use \"4.*\" instead of \"4*\" as the version pattern.",
+               "WARN: fileName:15: Please use \"1.[234].*\" instead of \"1.[234]*\" as the version pattern.")
 }
 
 func (s *Suite) Test_VartypeCheck_WrapperReorder(c *check.C) {
@@ -1057,8 +1060,8 @@ func (s *Suite) Test_VartypeCheck_Wrappe
                "reorder:l:first",
                "omit:first")
        vt.Output(
-               "WARN: fname:2: Unknown wrapper reorder command \"reorder:l:first\".",
-               "WARN: fname:3: Unknown wrapper reorder command \"omit:first\".")
+               "WARN: fileName:2: Unknown wrapper reorder command \"reorder:l:first\".",
+               "WARN: fileName:3: Unknown wrapper reorder command \"omit:first\".")
 }
 
 func (s *Suite) Test_VartypeCheck_WrapperTransform(c *check.C) {
@@ -1076,8 +1079,8 @@ func (s *Suite) Test_VartypeCheck_Wrappe
                "rpath:/usr/lib",
                "unknown")
        vt.Output(
-               "WARN: fname:7: Unknown wrapper transform command \"rpath:/usr/lib\".",
-               "WARN: fname:8: Unknown wrapper transform command \"unknown\".")
+               "WARN: fileName:7: Unknown wrapper transform command \"rpath:/usr/lib\".",
+               "WARN: fileName:8: Unknown wrapper transform command \"unknown\".")
 }
 
 func (s *Suite) Test_VartypeCheck_WrksrcSubdirectory(c *check.C) {
@@ -1094,12 +1097,12 @@ func (s *Suite) Test_VartypeCheck_Wrksrc
                "${WRKSRC}/directory with spaces",
                "directory with spaces")
        vt.Output(
-               "NOTE: fname:1: You can use \".\" instead of \"${WRKSRC}\".",
-               "NOTE: fname:2: You can use \".\" instead of \"${WRKSRC}/\".",
-               "NOTE: fname:3: You can use \".\" instead of \"${WRKSRC}/.\".",
-               "NOTE: fname:4: You can use \"subdir\" instead of \"${WRKSRC}/subdir\".",
-               "NOTE: fname:6: You can use \"directory with spaces\" instead of \"${WRKSRC}/directory with spaces\".",
-               "WARN: fname:7: \"directory with spaces\" is not a valid subdirectory of ${WRKSRC}.")
+               "NOTE: fileName:1: You can use \".\" instead of \"${WRKSRC}\".",
+               "NOTE: fileName:2: You can use \".\" instead of \"${WRKSRC}/\".",
+               "NOTE: fileName:3: You can use \".\" instead of \"${WRKSRC}/.\".",
+               "NOTE: fileName:4: You can use \"subdir\" instead of \"${WRKSRC}/subdir\".",
+               "NOTE: fileName:6: You can use \"directory with spaces\" instead of \"${WRKSRC}/directory with spaces\".",
+               "WARN: fileName:7: \"directory with spaces\" is not a valid subdirectory of ${WRKSRC}.")
 }
 
 func (s *Suite) Test_VartypeCheck_Yes(c *check.C) {
@@ -1112,8 +1115,8 @@ func (s *Suite) Test_VartypeCheck_Yes(c 
                "${YESVAR}")
 
        vt.Output(
-               "WARN: fname:2: APACHE_MODULE should be set to YES or yes.",
-               "WARN: fname:3: APACHE_MODULE should be set to YES or yes.")
+               "WARN: fileName:2: APACHE_MODULE should be set to YES or yes.",
+               "WARN: fileName:3: APACHE_MODULE should be set to YES or yes.")
 
        vt.Varname("PKG_DEVELOPER")
        vt.Op(opUseMatch)
@@ -1123,9 +1126,9 @@ func (s *Suite) Test_VartypeCheck_Yes(c 
                "${YESVAR}")
 
        vt.Output(
-               "WARN: fname:11: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.",
-               "WARN: fname:12: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.",
-               "WARN: fname:13: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.")
+               "WARN: fileName:11: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.",
+               "WARN: fileName:12: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.",
+               "WARN: fileName:13: PKG_DEVELOPER should only be used in a \".if defined(...)\" condition.")
 }
 
 func (s *Suite) Test_VartypeCheck_YesNo(c *check.C) {
@@ -1139,8 +1142,8 @@ func (s *Suite) Test_VartypeCheck_YesNo(
                "${YESVAR}")
 
        vt.Output(
-               "WARN: fname:3: GNU_CONFIGURE should be set to YES, yes, NO, or no.",
-               "WARN: fname:4: GNU_CONFIGURE should be set to YES, yes, NO, or no.")
+               "WARN: fileName:3: GNU_CONFIGURE should be set to YES, yes, NO, or no.",
+               "WARN: fileName:4: GNU_CONFIGURE should be set to YES, yes, NO, or no.")
 }
 
 func (s *Suite) Test_VartypeCheck_YesNoIndirectly(c *check.C) {
@@ -1154,7 +1157,7 @@ func (s *Suite) Test_VartypeCheck_YesNoI
                "${YESVAR}")
 
        vt.Output(
-               "WARN: fname:3: GNU_CONFIGURE should be set to YES, yes, NO, or no.")
+               "WARN: fileName:3: GNU_CONFIGURE should be set to YES, yes, NO, or no.")
 }
 
 // VartypeCheckTester helps to test the many different checks in VartypeCheck.
@@ -1169,13 +1172,13 @@ type VartypeCheckTester struct {
        op       MkOperator
 }
 
-// NewVartypeCheckTester starts the test with a file name of "fname", at line 1,
+// NewVartypeCheckTester starts the test with a file name of "fileName", at line 1,
 // with "=" as the operator. The variable has to be initialized explicitly.
 func NewVartypeCheckTester(t *Tester, checker func(cv *VartypeCheck)) *VartypeCheckTester {
        return &VartypeCheckTester{
                t,
                checker,
-               "fname",
+               "fileName",
                1,
                "",
                opAssign}

Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt.go
diff -u pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.5 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.6
--- pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.5  Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt.go      Wed Nov  7 20:58:23 2018
@@ -3,6 +3,7 @@
 package getopt
 
 import (
+       "errors"
        "fmt"
        "io"
        "strings"
@@ -26,9 +27,9 @@ func NewOptions() *Options {
 //  opts := NewOptions()
 //  warnings := opts.AddFlagGroup('W', "warnings", "warning,...", "Enable the given warnings")
 //  warnings.AddFlagVar("extra", &extra, false, "Print extra warnings")
-func (o *Options) AddFlagGroup(shortName rune, longName, argDescription, description string) *FlagGroup {
+func (o *Options) AddFlagGroup(shortName rune, longName, argsName, description string) *FlagGroup {
        grp := new(FlagGroup)
-       opt := &option{shortName, longName, argDescription, description, grp}
+       opt := &option{shortName, longName, argsName, description, grp}
        o.options = append(o.options, opt)
        return grp
 }
@@ -108,9 +109,9 @@ func (o *Options) handleLongOption(args 
                        *data = true
                } else {
                        switch *argval {
-                       case "true", "on", "enabled", "1":
+                       case "true", "on", "enabled", "1", "yes":
                                *data = true
-                       case "false", "off", "disabled", "0":
+                       case "false", "off", "disabled", "0", "no":
                                *data = false
                        default:
                                return 0, optErr("invalid argument for option --" + opt.longName)
@@ -138,7 +139,7 @@ func (o *Options) handleLongOption(args 
                        return 0, optErr("option requires an argument: --" + opt.longName)
                }
        }
-       panic("getopt: unknown option type")
+       panic("getopt: internal error: unknown option type")
 }
 
 func (o *Options) parseShortOptions(args []string, i int, optchars string) (skip int, err error) {
@@ -182,100 +183,113 @@ optchar:
        return 0, nil
 }
 
+// Help writes a summary of the options to the given writer,
+// in tabular format.
 func (o *Options) Help(out io.Writer, generalUsage string) {
        wr := tabwriter.NewWriter(out, 1, 0, 2, ' ', tabwriter.TabIndent)
 
-       io.WriteString(wr, "usage: "+generalUsage+"\n")
-       io.WriteString(wr, "\n")
-       wr.Flush()
+       rowf := func(format string, args ...interface{}) {
+               _, _ = fmt.Fprintf(wr, format, args...)
+               _, _ = io.WriteString(wr, "\n")
+       }
+       finishTable := func() { _ = wr.Flush() }
+
+       rowf("usage: %s", generalUsage)
+       rowf("")
+       finishTable()
 
        for _, opt := range o.options {
-               if opt.argDescription == "" {
-                       fmt.Fprintf(wr, "  -%c, --%s\t %s\n",
+               if opt.argsName == "" {
+                       rowf("  -%c, --%s\t %s",
                                opt.shortName, opt.longName, opt.description)
                } else {
-                       fmt.Fprintf(wr, "  -%c, --%s=%s\t %s\n",
-                               opt.shortName, opt.longName, opt.argDescription, opt.description)
+                       rowf("  -%c, --%s=%s\t %s",
+                               opt.shortName, opt.longName, opt.argsName, opt.description)
                }
        }
-       wr.Flush()
+       finishTable()
 
        hasFlagGroups := false
        for _, opt := range o.options {
                switch flagGroup := opt.data.(type) {
                case *FlagGroup:
                        hasFlagGroups = true
-                       io.WriteString(wr, "\n")
-                       fmt.Fprintf(wr, "  Flags for -%c, --%s:\n", opt.shortName, opt.longName)
-                       io.WriteString(wr, "    all\t all of the following\n")
-                       io.WriteString(wr, "    none\t none of the following\n")
+                       rowf("")
+                       rowf("  Flags for -%c, --%s:", opt.shortName, opt.longName)
+                       rowf("    all\t all of the following")
+                       rowf("    none\t none of the following")
                        for _, flag := range flagGroup.flags {
                                state := "disabled"
                                if *flag.value {
                                        state = "enabled"
                                }
-                               fmt.Fprintf(wr, "    %s\t %s (%v)\n", flag.name, flag.help, state)
+                               rowf("    %s\t %s (%v)", flag.name, flag.description, state)
                        }
-                       wr.Flush()
+                       finishTable()
                }
        }
        if hasFlagGroups {
-               io.WriteString(wr, "\n")
-               io.WriteString(wr, "  (Prefix a flag with \"no-\" to disable it.)\n")
-               wr.Flush()
+               rowf("")
+               rowf("  (Prefix a flag with \"no-\" to disable it.)")
+               finishTable()
        }
 }
 
 type option struct {
-       shortName      rune
-       longName       string
-       argDescription string
-       description    string
-       data           interface{}
+       shortName   rune
+       longName    string
+       argsName    string
+       description string
+       data        interface{}
 }
 
 type FlagGroup struct {
        flags []*groupFlag
 }
 
-func (fg *FlagGroup) AddFlagVar(name string, flag *bool, defval bool, help string) {
-       opt := &groupFlag{name, flag, help}
+type groupFlag struct {
+       name        string
+       value       *bool
+       description string
+}
+
+func (fg *FlagGroup) AddFlagVar(name string, flag *bool, defval bool, description string) {
+       opt := &groupFlag{name, flag, description}
        fg.flags = append(fg.flags, opt)
        *flag = defval
 }
 
-func (fg *FlagGroup) parse(optionPrefix, arg string) (err error) {
-argopt:
-       for _, argopt := range strings.Split(arg, ",") {
-               if argopt == "none" || argopt == "all" {
-                       for _, opt := range fg.flags {
-                               *opt.value = argopt == "all"
-                       }
-                       continue argopt
+func (fg *FlagGroup) parse(optionPrefix, arg string) error {
+       for _, argOpt := range strings.Split(arg, ",") {
+               err := fg.parseOpt(optionPrefix, argOpt)
+               if err != nil {
+                       return err
                }
-               for _, opt := range fg.flags {
-                       if argopt == opt.name {
-                               *opt.value = true
-                               continue argopt
-                       }
-                       if argopt == "no-"+opt.name {
-                               *opt.value = false
-                               continue argopt
-                       }
-               }
-               return optErr("unknown option: " + optionPrefix + argopt)
        }
        return nil
 }
 
-type groupFlag struct {
-       name  string
-       value *bool
-       help  string
-}
+func (fg *FlagGroup) parseOpt(optionPrefix, argOpt string) error {
 
-type optErr string
+       if argOpt == "none" || argOpt == "all" {
+               for _, opt := range fg.flags {
+                       *opt.value = argOpt == "all"
+               }
+               return nil
+       }
 
-func (err optErr) Error() string {
-       return string(err)
+       for _, opt := range fg.flags {
+               if argOpt == opt.name {
+                       *opt.value = true
+                       return nil
+               }
+               if argOpt == "no-"+opt.name {
+                       *opt.value = false
+                       return nil
+               }
+       }
+
+       return optErr("unknown option: " + optionPrefix + argOpt)
 }
+
+func optErr(str string) error { return errors.New(str) }

Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go
diff -u pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.7 pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.8
--- pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go:1.7     Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt_test.go Wed Nov  7 20:58:23 2018
@@ -2,6 +2,7 @@ package getopt
 
 import (
        "gopkg.in/check.v1"
+       "netbsd.org/pkglint/intqa"
        "strings"
        "testing"
 )
@@ -12,7 +13,7 @@ var _ = check.Suite(new(Suite))
 
 func Test(t *testing.T) { check.TestingT(t) }
 
-func (s *Suite) Test_Options_Parse_short(c *check.C) {
+func (s *Suite) Test_Options_Parse__short(c *check.C) {
        opts := NewOptions()
        var help bool
        opts.AddFlagVar('h', "help", &help, false, "prints a help page")
@@ -24,7 +25,7 @@ func (s *Suite) Test_Options_Parse_short
        c.Check(help, check.Equals, true)
 }
 
-func (s *Suite) Test_Options_Parse_unknown_short(c *check.C) {
+func (s *Suite) Test_Options_Parse__unknown_short(c *check.C) {
        opts := NewOptions()
 
        _, err := opts.Parse([]string{"progname", "-z"})
@@ -32,7 +33,7 @@ func (s *Suite) Test_Options_Parse_unkno
        c.Check(err.Error(), check.Equals, "progname: unknown option: -z")
 }
 
-func (s *Suite) Test_Options_Parse_unknown_long(c *check.C) {
+func (s *Suite) Test_Options_Parse__unknown_long(c *check.C) {
        opts := NewOptions()
 
        _, err := opts.Parse([]string{"progname", "--unknown-long"})
@@ -40,7 +41,7 @@ func (s *Suite) Test_Options_Parse_unkno
        c.Check(err.Error(), check.Equals, "progname: unknown option: --unknown-long")
 }
 
-func (s *Suite) Test_Options_Parse_unknown_flag_in_group(c *check.C) {
+func (s *Suite) Test_Options_Parse__unknown_flag_in_group(c *check.C) {
        opts := NewOptions()
        opts.AddFlagGroup('W', "warnings", "", "")
 
@@ -57,7 +58,7 @@ func (s *Suite) Test_Options_Parse_unkno
        c.Check(err.Error(), check.Equals, "progname: option requires an argument: -W")
 }
 
-func (s *Suite) Test_Options_Parse_abbreviated_long(c *check.C) {
+func (s *Suite) Test_Options_Parse__abbreviated_long(c *check.C) {
        opts := NewOptions()
        var longFlag, longerFlag bool
        opts.AddFlagVar('?', "long", &longFlag, false, "")
@@ -83,7 +84,7 @@ func (s *Suite) Test_Options_Parse_abbre
        c.Check(longerFlag, check.Equals, true)
 }
 
-func (s *Suite) Test_Options_Parse_mixed_args_and_options(c *check.C) {
+func (s *Suite) Test_Options_Parse__mixed_args_and_options(c *check.C) {
        opts := NewOptions()
        var aflag, bflag bool
        opts.AddFlagVar('a', "aflag", &aflag, false, "")
@@ -106,7 +107,7 @@ func (s *Suite) Test_Options_Parse_mixed
        c.Check(bflag, check.Equals, false)
 }
 
-func (s *Suite) Test_Options_Parse_string_list(c *check.C) {
+func (s *Suite) Test_Options_Parse__string_list(c *check.C) {
        opts := NewOptions()
        var verbose bool
        var includes []string
@@ -140,43 +141,43 @@ func (s *Suite) Test_Options_Parse_strin
 }
 
 func (s *Suite) Test_Options_Parse__long_flags(c *check.C) {
-       var aflag, bflag, cflag, dflag, eflag, fflag, gflag, hflag, iflag, jflag bool
-
-       opts := NewOptions()
-       opts.AddFlagVar('a', "aflag", &aflag, false, "")
-       opts.AddFlagVar('b', "bflag", &bflag, false, "")
-       opts.AddFlagVar('c', "cflag", &cflag, false, "")
-       opts.AddFlagVar('d', "dflag", &dflag, false, "")
-       opts.AddFlagVar('e', "eflag", &eflag, true, "")
-       opts.AddFlagVar('f', "fflag", &fflag, true, "")
-       opts.AddFlagVar('g', "gflag", &gflag, true, "")
-       opts.AddFlagVar('h', "hflag", &hflag, true, "")
-       opts.AddFlagVar('i', "iflag", &iflag, false, "")
-       opts.AddFlagVar('j', "jflag", &jflag, false, "")
+       var posFlags [5]bool
+       var negFlags [5]bool
+       var otherFlags [2]bool
+
+       opts := NewOptions()
+       opts.AddFlagVar(0, "pos0", &posFlags[0], false, "")
+       opts.AddFlagVar(0, "pos1", &posFlags[1], false, "")
+       opts.AddFlagVar(0, "pos2", &posFlags[2], false, "")
+       opts.AddFlagVar(0, "pos3", &posFlags[3], false, "")
+       opts.AddFlagVar(0, "pos4", &posFlags[4], false, "")
+       opts.AddFlagVar(0, "neg0", &negFlags[0], true, "")
+       opts.AddFlagVar(0, "neg1", &negFlags[1], true, "")
+       opts.AddFlagVar(0, "neg2", &negFlags[2], true, "")
+       opts.AddFlagVar(0, "neg3", &negFlags[3], true, "")
+       opts.AddFlagVar(0, "neg4", &negFlags[4], true, "")
+       opts.AddFlagVar(0, "other0", &otherFlags[0], false, "")
+       opts.AddFlagVar(0, "other1", &otherFlags[1], false, "")
 
        args, err := opts.Parse([]string{"progname",
-               "--aflag=true",
-               "--bflag=on",
-               "--cflag=enabled",
-               "--dflag=1",
-               "--eflag=false",
-               "--fflag=off",
-               "--gflag=disabled",
-               "--hflag=0",
-               "--iflag",
-               "--jflag=unknown"})
+               "--pos0=true",
+               "--pos1=on",
+               "--pos2=enabled",
+               "--pos3=1",
+               "--pos4=yes",
+               "--neg0=false",
+               "--neg1=off",
+               "--neg2=disabled",
+               "--neg3=0",
+               "--neg4=no",
+               "--other0",
+               "--other1=unknown"})
 
        c.Check(args, check.HasLen, 0)
-       c.Check(aflag, check.Equals, true)
-       c.Check(bflag, check.Equals, true)
-       c.Check(cflag, check.Equals, true)
-       c.Check(dflag, check.Equals, true)
-       c.Check(eflag, check.Equals, false)
-       c.Check(fflag, check.Equals, false)
-       c.Check(gflag, check.Equals, false)
-       c.Check(hflag, check.Equals, false)
-       c.Check(iflag, check.Equals, true)
-       c.Check(err, check.ErrorMatches, `^progname: invalid argument for option --jflag$`)
+       c.Check(posFlags, check.Equals, [5]bool{true, true, true, true, true})
+       c.Check(negFlags, check.Equals, [5]bool{false, false, false, false, false})
+       c.Check(otherFlags, check.Equals, [2]bool{true, false})
+       c.Check(err.Error(), check.Equals, "progname: invalid argument for option --other1")
 }
 
 func (s *Suite) Test_Options_handleLongOption__flag_group_without_argument(c *check.C) {
@@ -227,12 +228,14 @@ func (s *Suite) Test_Options_handleLongO
        opts := NewOptions()
        group := opts.AddFlagGroup('W', "warnings", "warning,...", "Print selected warnings")
        group.AddFlagVar("extra", &extra, false, "Print extra warnings")
+
+       // Intentionally damage internal structure to reach full code coverage.
        opts.options[0].data = "unexpected value"
 
        c.Check(
                func() { _, _ = opts.Parse([]string{"progname", "--warnings"}) },
                check.Panics,
-               "getopt: unknown option type")
+               "getopt: internal error: unknown option type")
 }
 
 func (s *Suite) Test_Options_parseShortOptions__flag_group_separate_argument(c *check.C) {
@@ -275,3 +278,9 @@ func (s *Suite) Test_Options_Help(c *che
                "\n"+
                "  (Prefix a flag with \"no-\" to disable it.)\n")
 }
+
+func (s *Suite) Test__test_names(c *check.C) {
+       ck := intqa.NewTestNameChecker(c)
+       ck.ShowWarnings(false)
+       ck.Check()
+}

Index: pkgsrc/pkgtools/pkglint/files/histogram/histogram.go
diff -u pkgsrc/pkgtools/pkglint/files/histogram/histogram.go:1.3 pkgsrc/pkgtools/pkglint/files/histogram/histogram.go:1.4
--- pkgsrc/pkgtools/pkglint/files/histogram/histogram.go:1.3    Sun Aug 12 16:31:56 2018
+++ pkgsrc/pkgtools/pkglint/files/histogram/histogram.go        Wed Nov  7 20:58:23 2018
@@ -18,18 +18,18 @@ func (h *Histogram) Add(s string, n int)
        h.histo[s] += n
 }
 
-func (h *Histogram) PrintStats(caption string, out io.Writer, limit int) {
-       type entry struct {
+func (h *Histogram) PrintStats(out io.Writer, caption string, limit int) {
+       type row struct {
                s     string
                count int
        }
 
-       entries := make([]entry, len(h.histo))
+       entries := make([]row, len(h.histo))
 
-       i := 0
+       n := 0
        for s, count := range h.histo {
-               entries[i] = entry{s, count}
-               i++
+               entries[n] = row{s, count}
+               n++
        }
 
        sort.SliceStable(entries, func(i, j int) bool {
@@ -39,9 +39,9 @@ func (h *Histogram) PrintStats(caption s
        })
 
        for i, entry := range entries {
-               fmt.Fprintf(out, "%s %6d %s\n", caption, entry.count, entry.s)
                if limit >= 0 && i >= limit {
                        break
                }
+               _, _ = fmt.Fprintf(out, "%s %6d %s\n", caption, entry.count, entry.s)
        }
 }

Index: pkgsrc/pkgtools/pkglint/files/licenses/licenses.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses/licenses.go:1.4 pkgsrc/pkgtools/pkglint/files/licenses/licenses.go:1.5
--- pkgsrc/pkgtools/pkglint/files/licenses/licenses.go:1.4      Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses/licenses.go  Wed Nov  7 20:58:23 2018
@@ -1,29 +1,34 @@
 package licenses
 
 import (
-       "netbsd.org/pkglint/regex"
        "netbsd.org/pkglint/textproc"
+       "strings"
 )
 
-// Condition describes a complex license condition.
-// It has either `Name` or `Paren` or `Children` set.
-// In the `Children` case, `And` and `Or` specify the operators used.
-// Malformed license conditions can have both `And` and `Or` set.
+// Condition describes the syntax of a complex license condition.
+// It has either Name or Paren or Children set.
+// In the Children case, either And or Or specify the operators used.
+// Only malformed license conditions can have both And and Or set.
 type Condition struct {
-       Name     string       `json:",omitempty"`
-       Paren    *Condition   `json:",omitempty"`
-       And      bool         `json:",omitempty"`
-       Or       bool         `json:",omitempty"`
-       Children []*Condition `json:",omitempty"`
+       Name     string       `json:",omitempty"` // A license name, such as gnu-gpl-v2
+       Paren    *Condition   `json:",omitempty"` // A parenthesized expression
+       And      bool         `json:",omitempty"` // license1 AND license2
+       Or       bool         `json:",omitempty"` // license1 OR license2
+       Children []*Condition `json:",omitempty"` // The operands of And and Or
 }
 
-func Parse(licenses string, res *regex.Registry) *Condition {
-       lexer := &licenseLexer{repl: textproc.NewPrefixReplacer(licenses, res)}
+func Parse(licenses string) *Condition {
+       lexer := &licenseLexer{lexer: textproc.NewLexer(licenses)}
        result := liyyNewParser().Parse(lexer)
-       if result == 0 && lexer.repl.EOF() {
-               return lexer.result
+       if result != 0 || !lexer.lexer.EOF() {
+               return nil
        }
-       return nil
+
+       cond := lexer.result
+       if !cond.And && !cond.Or && cond.Name == "" && len(cond.Children) == 1 {
+               cond = cond.Children[0]
+       }
+       return cond
 }
 
 func (cond *Condition) String() string {
@@ -33,15 +38,15 @@ func (cond *Condition) String() string {
        if cond.Paren != nil {
                return "(" + cond.Paren.String() + ")"
        }
-       s := ""
+       var s strings.Builder
        separator := [...]string{"", " AND ", " OR ", " MIXED "}[b2i(cond.And)+2*b2i(cond.Or)]
        for i, child := range cond.Children {
                if i != 0 {
-                       s += separator
+                       s.WriteString(separator)
                }
-               s += child.String()
+               s.WriteString(child.String())
        }
-       return s
+       return s.String()
 }
 
 func (cond *Condition) Walk(callback func(*Condition)) {
@@ -57,34 +62,37 @@ func (cond *Condition) Walk(callback fun
 //go:generate goyacc -p liyy -o licensesyacc.go -v licensesyacc.log licenses.y
 
 type licenseLexer struct {
-       repl   *textproc.PrefixReplacer
+       lexer  *textproc.Lexer
        result *Condition
        error  string
 }
 
+var licenseNameChars = textproc.NewByteSet("A-Za-z0-9---.")
+
 func (lexer *licenseLexer) Lex(llval *liyySymType) int {
-       repl := lexer.repl
-       repl.SkipHspace()
+       repl := lexer.lexer
+       repl.NextHspace()
        switch {
        case repl.EOF():
                return 0
-       case repl.AdvanceStr("("):
+       case repl.NextByte('('):
                return ltOPEN
-       case repl.AdvanceStr(")"):
+       case repl.NextByte(')'):
                return ltCLOSE
-       case repl.AdvanceRegexp(`^[\w-.]+`):
-               word := repl.Group(0)
-               switch word {
-               case "AND":
-                       return ltAND
-               case "OR":
-                       return ltOR
-               default:
-                       llval.Node = &Condition{Name: word}
-                       return ltNAME
-               }
        }
-       return -1
+
+       word := repl.NextBytesSet(licenseNameChars)
+       switch word {
+       case "AND":
+               return ltAND
+       case "OR":
+               return ltOR
+       case "":
+               return -1
+       default:
+               llval.Node = &Condition{Name: word}
+               return ltNAME
+       }
 }
 
 func (lexer *licenseLexer) Error(s string) {

Index: pkgsrc/pkgtools/pkglint/files/licenses/licenses_test.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses/licenses_test.go:1.3 pkgsrc/pkgtools/pkglint/files/licenses/licenses_test.go:1.4
--- pkgsrc/pkgtools/pkglint/files/licenses/licenses_test.go:1.3 Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/licenses/licenses_test.go     Wed Nov  7 20:58:23 2018
@@ -3,7 +3,7 @@ package licenses
 import (
        "encoding/json"
        "gopkg.in/check.v1"
-       "netbsd.org/pkglint/regex"
+       "netbsd.org/pkglint/intqa"
        "strings"
        "testing"
 )
@@ -11,17 +11,17 @@ import (
 type Suite struct{}
 
 func (s *Suite) Test_Parse(c *check.C) {
-       res := regex.NewRegistry()
        checkParse := func(cond string, expected string) {
-               c.Check(toJSON(Parse(cond, &res)), check.Equals, expected)
+               c.Check(toJSON(Parse(cond)), check.Equals, expected)
        }
        checkParseDeep := func(cond string, expected *Condition) {
-               c.Check(Parse(cond, &res), check.DeepEquals, expected)
+               c.Check(Parse(cond), check.DeepEquals, expected)
        }
 
-       checkParseDeep("gnu-gpl-v2", NewSingleton(NewName("gnu-gpl-v2")))
+       checkParseDeep("gnu-gpl-v2", NewName("gnu-gpl-v2"))
+
+       checkParse("gnu-gpl-v2", "{Name:gnu-gpl-v2}")
 
-       checkParse("gnu-gpl-v2", "{Children:[{Name:gnu-gpl-v2}]}")
        checkParse("a AND b", "{And:true,Children:[{Name:a},{Name:b}]}")
        checkParse("a OR b", "{Or:true,Children:[{Name:a},{Name:b}]}")
 
@@ -46,13 +46,13 @@ func (s *Suite) Test_Parse(c *check.C) {
                        NewParen(NewAnd(NewName("c"), NewName("d")))))
 
        checkParse("a AND b OR c AND d", "{And:true,Or:true,Children:[{Name:a},{Name:b},{Name:c},{Name:d}]}")
-       checkParse("((a AND (b AND c)))", "{Children:[{Paren:{Children:[{Paren:{And:true,Children:[{Name:a},{Paren:{And:true,Children:[{Name:b},{Name:c}]}}]}}]}}]}")
+       checkParse("((a AND (b AND c)))", "{Paren:{Children:[{Paren:{And:true,Children:[{Name:a},{Paren:{And:true,Children:[{Name:b},{Name:c}]}}]}}]}}")
 
-       c.Check(Parse("a AND b OR c AND d", &res).String(), check.Equals, "a MIXED b MIXED c MIXED d")
+       c.Check(Parse("a AND b OR c AND d").String(), check.Equals, "a MIXED b MIXED c MIXED d")
 
-       c.Check(Parse("AND artistic", &res), check.IsNil)
+       c.Check(Parse("AND artistic"), check.IsNil)
 
-       c.Check(Parse("invalid/character", &res), check.IsNil)
+       c.Check(Parse("invalid/character"), check.IsNil)
 }
 
 func (s *Suite) Test_Condition_String(c *check.C) {
@@ -83,10 +83,8 @@ func (s *Suite) Test_Condition_String(c 
        c.Check(mixed.String(), check.Equals, "a MIXED b MIXED c")
 }
 
-func (s *Suite) Test_Walk(c *check.C) {
-       res := regex.NewRegistry()
-
-       condition := Parse("(a OR b) AND (c AND d)", &res)
+func (s *Suite) Test_Condition_Walk(c *check.C) {
+       condition := Parse("(a OR b) AND (c AND d)")
 
        var out []string
        condition.Walk(func(condition *Condition) {
@@ -115,9 +113,6 @@ func NewName(name string) *Condition {
 func NewParen(child *Condition) *Condition {
        return &Condition{Paren: child}
 }
-func NewSingleton(child *Condition) *Condition {
-       return &Condition{Children: []*Condition{child}}
-}
 func NewAnd(parts ...*Condition) *Condition {
        return &Condition{Children: parts, And: true}
 }
@@ -126,11 +121,18 @@ func NewOr(parts ...*Condition) *Conditi
 }
 
 func toJSON(cond *Condition) string {
-       json, _ := json.Marshal(cond)
-       return strings.Replace(string(json), "\"", "", -1)
+       jsonStr, _ := json.Marshal(cond)
+       return strings.Replace(string(jsonStr), "\"", "", -1)
 }
 
 func Test(t *testing.T) {
        check.Suite(new(Suite))
        check.TestingT(t)
 }
+
+func (s *Suite) Test__test_names(c *check.C) {
+       ck := intqa.NewTestNameChecker(c)
+       ck.IgnoreFiles("*yacc.go")
+       ck.ShowWarnings(false)
+       ck.Check()
+}

Index: pkgsrc/pkgtools/pkglint/files/regex/regex.go
diff -u pkgsrc/pkgtools/pkglint/files/regex/regex.go:1.4 pkgsrc/pkgtools/pkglint/files/regex/regex.go:1.5
--- pkgsrc/pkgtools/pkglint/files/regex/regex.go:1.4    Wed Oct  3 22:27:54 2018
+++ pkgsrc/pkgtools/pkglint/files/regex/regex.go        Wed Nov  7 20:58:23 2018
@@ -44,6 +44,10 @@ func (r *Registry) Compile(re Pattern) *
        return cre
 }
 
+// Consider defining an alternative CompileX method that implements the
+// /x modifier to allow whitespace in the regular expression.
+// This makes the regular expressions more readable.
+
 func (r *Registry) Match(s string, re Pattern) []string {
        if !r.profiling {
                return r.Compile(re).FindStringSubmatch(s)
@@ -127,9 +131,9 @@ func (r *Registry) ReplaceFirst(s string
 
 func (r *Registry) PrintStats(out io.Writer) {
        if r.profiling {
-               r.rematch.PrintStats("rematch", out, 10)
-               r.renomatch.PrintStats("renomatch", out, 10)
-               r.retime.PrintStats("retime", out, 10)
+               r.rematch.PrintStats(out, "rematch", 10)
+               r.renomatch.PrintStats(out, "renomatch", 10)
+               r.retime.PrintStats(out, "retime", 10)
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/textproc/prefixreplacer.go
diff -u pkgsrc/pkgtools/pkglint/files/textproc/prefixreplacer.go:1.7 pkgsrc/pkgtools/pkglint/files/textproc/prefixreplacer.go:1.8
--- pkgsrc/pkgtools/pkglint/files/textproc/prefixreplacer.go:1.7        Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/textproc/prefixreplacer.go    Wed Nov  7 20:58:23 2018
@@ -3,7 +3,6 @@ package textproc
 import (
        "fmt"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/trace"
        "strings"
 )
 
@@ -24,10 +23,6 @@ func NewPrefixReplacer(s string, res *re
        return &PrefixReplacer{s, "", nil, res}
 }
 
-func (pr *PrefixReplacer) EOF() bool {
-       return pr.rest == ""
-}
-
 func (pr *PrefixReplacer) Rest() string {
        return pr.rest
 }
@@ -46,9 +41,6 @@ func (pr *PrefixReplacer) AdvanceStr(pre
        pr.m = nil
        pr.s = ""
        if strings.HasPrefix(pr.rest, prefix) {
-               if trace.Tracing {
-                       trace.Stepf("PrefixReplacer.AdvanceStr(%q, %q)", pr.rest, prefix)
-               }
                pr.s = prefix
                pr.rest = pr.rest[len(prefix):]
                return true
@@ -56,28 +48,6 @@ func (pr *PrefixReplacer) AdvanceStr(pre
        return false
 }
 
-func (pr *PrefixReplacer) AdvanceByte(b byte) bool {
-       if len(pr.rest) != 0 && pr.rest[0] == b {
-               pr.s = pr.rest[:1]
-               pr.rest = pr.rest[1:]
-               return true
-       }
-       return false
-}
-
-func (pr *PrefixReplacer) AdvanceBytesFunc(fn func(c byte) bool) bool {
-       i := 0
-       for i < len(pr.rest) && fn(pr.rest[i]) {
-               i++
-       }
-       if i != 0 {
-               pr.s = pr.rest[:i]
-               pr.rest = pr.rest[i:]
-               return true
-       }
-       return false
-}
-
 // AdvanceHspace advances over as many spaces and tabs as possible.
 func (pr *PrefixReplacer) AdvanceHspace() bool {
        i := initialHspace(pr.rest)
@@ -99,9 +69,6 @@ func (pr *PrefixReplacer) AdvanceRegexp(
                panic(fmt.Sprintf("PrefixReplacer.AdvanceRegexp: the empty string must not match the regular expression %q.", re))
        }
        if m := pr.res.Match(pr.rest, re); m != nil {
-               if trace.Tracing {
-                       trace.Stepf("PrefixReplacer.AdvanceRegexp(%q, %q, %q)", pr.rest, re, m[0])
-               }
                pr.rest = pr.rest[len(m[0]):]
                pr.m = m
                pr.s = m[0]
@@ -110,6 +77,20 @@ func (pr *PrefixReplacer) AdvanceRegexp(
        return false
 }
 
+// NextBytesFunc chops off the longest prefix (possibly empty) consisting
+// solely of bytes for which fn returns true.
+func (pr *PrefixReplacer) NextBytesFunc(fn func(b byte) bool) string {
+       i := 0
+       rest := pr.rest
+       for i < len(rest) && fn(rest[i]) {
+               i++
+       }
+       if i != 0 {
+               pr.rest = rest[i:]
+       }
+       return rest[:i]
+}
+
 func (pr *PrefixReplacer) PeekByte() int {
        rest := pr.rest
        if rest == "" {

Index: pkgsrc/pkgtools/pkglint/files/trace/tracing.go
diff -u pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.2 pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.3
--- pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.2  Wed Sep  5 17:56:22 2018
+++ pkgsrc/pkgtools/pkglint/files/trace/tracing.go      Wed Nov  7 20:58:23 2018
@@ -9,38 +9,37 @@ import (
        "strings"
 )
 
-var (
+type Tracer struct {
        Tracing bool
        Out     io.Writer
-)
-
-var traceDepth int
+       depth   int
+}
 
-func Stepf(format string, args ...interface{}) {
-       if Tracing {
+func (t *Tracer) Stepf(format string, args ...interface{}) {
+       if t.Tracing {
                msg := fmt.Sprintf(format, args...)
-               io.WriteString(Out, fmt.Sprintf("TRACE: %s  %s\n", traceIndent(), msg))
+               io.WriteString(t.Out, fmt.Sprintf("TRACE: %s  %s\n", t.traceIndent(), msg))
        }
 }
 
-func Step1(format string, arg0 string) {
-       Stepf(format, arg0)
+func (t *Tracer) Step1(format string, arg0 string) {
+       t.Stepf(format, arg0)
 }
 
-func Step2(format string, arg0, arg1 string) {
-       Stepf(format, arg0, arg1)
+func (t *Tracer) Step2(format string, arg0, arg1 string) {
+       t.Stepf(format, arg0, arg1)
 }
 
-func Call0() func() {
-       return traceCall()
+func (t *Tracer) Call0() func() {
+       return t.traceCall()
 }
 
-func Call1(arg1 string) func() {
-       return traceCall(arg1)
+func (t *Tracer) Call1(arg1 string) func() {
+       return t.traceCall(arg1)
 }
 
-func Call2(arg1, arg2 string) func() {
-       return traceCall(arg1, arg2)
+func (t *Tracer) Call2(arg1, arg2 string) func() {
+       return t.traceCall(arg1, arg2)
 }
 
 // Call records a function call in the tracing log, both when entering and
@@ -50,8 +49,8 @@ func Call2(arg1, arg2 string) func() {
 //  if trace.Tracing {
 //      defer trace.Call(arg1, arg2, trace.Result(result1), trace.Result(result2))()
 // }
-func Call(args ...interface{}) func() {
-       return traceCall(args...)
+func (t *Tracer) Call(args ...interface{}) func() {
+       return t.traceCall(args...)
 }
 
 // http://stackoverflow.com/questions/13476349/check-for-nil-and-nil-interface-in-go
@@ -77,16 +76,16 @@ func argsStr(args []interface{}) string 
        return rv
 }
 
-func traceIndent() string {
+func (t *Tracer) traceIndent() string {
        indent := ""
-       for i := 0; i < traceDepth; i++ {
+       for i := 0; i < t.depth; i++ {
                indent += fmt.Sprintf("%d ", i+1)
        }
        return indent
 }
 
-func traceCall(args ...interface{}) func() {
-       if !Tracing {
+func (t *Tracer) traceCall(args ...interface{}) func() {
+       if !t.Tracing {
                panic("Internal pkglint error: calls to trace.Call must only occur in tracing mode")
        }
 
@@ -96,13 +95,13 @@ func traceCall(args ...interface{}) func
                        funcname = strings.TrimPrefix(fn.Name(), "netbsd.org/pkglint.")
                }
        }
-       indent := traceIndent()
-       io.WriteString(Out, fmt.Sprintf("TRACE: %s+ %s(%s)\n", indent, funcname, argsStr(withoutResults(args))))
-       traceDepth++
+       indent := t.traceIndent()
+       io.WriteString(t.Out, fmt.Sprintf("TRACE: %s+ %s(%s)\n", indent, funcname, argsStr(withoutResults(args))))
+       t.depth++
 
        return func() {
-               traceDepth--
-               io.WriteString(Out, fmt.Sprintf("TRACE: %s- %s(%s)\n", indent, funcname, argsStr(withResults(args))))
+               t.depth--
+               io.WriteString(t.Out, fmt.Sprintf("TRACE: %s- %s(%s)\n", indent, funcname, argsStr(withResults(args))))
        }
 }
 
@@ -111,7 +110,7 @@ type result struct {
 }
 
 // Result marks an argument as a result and is only logged when the function returns.
-func Result(rv interface{}) result {
+func (t *Tracer) Result(rv interface{}) result {
        if reflect.ValueOf(rv).Kind() != reflect.Ptr {
                panic(fmt.Sprintf("Result must be called with a pointer to the result, not %#v.", rv))
        }
Index: pkgsrc/pkgtools/pkglint/files/trace/tracing_test.go
diff -u pkgsrc/pkgtools/pkglint/files/trace/tracing_test.go:1.2 pkgsrc/pkgtools/pkglint/files/trace/tracing_test.go:1.3
--- pkgsrc/pkgtools/pkglint/files/trace/tracing_test.go:1.2     Tue Oct  9 19:12:13 2018
+++ pkgsrc/pkgtools/pkglint/files/trace/tracing_test.go Wed Nov  7 20:58:23 2018
@@ -3,76 +3,83 @@ package trace
 import (
        "bytes"
        "gopkg.in/check.v1"
+       "netbsd.org/pkglint/intqa"
        "testing"
 )
 
-type Suite struct{}
+type Suite struct {
+       Tracer Tracer
+}
 
 var _ = check.Suite(new(Suite))
 
 func Test(t *testing.T) { check.TestingT(t) }
 
-func onlyArguments(args ...interface{}) {
-       defer Call(args...)()
-       Stepf("Running %q", "code")
+func (t *Tracer) onlyArguments(args ...interface{}) {
+       defer t.Call(args...)()
+       t.Stepf("Running %q", "code")
 }
 
-func argumentsAndResult(arg0 string, arg1 int) (result string) {
-       defer Call(arg0, arg1, Result(&result))()
-       Stepf("Running %q", "code")
+func (t *Tracer) argumentsAndResult(arg0 string, arg1 int) (result string) {
+       defer t.Call(arg0, arg1, t.Result(&result))()
+       t.Stepf("Running %q", "code")
        return "the result"
 }
 
-func argumentsAndResultWrong(arg0 string, arg1 int) (result string) {
-       defer Call(arg0, arg1, result)() // "result" is evaluated too early and only once.
-       Stepf("Running %q", "code")
+func (t *Tracer) argumentsAndResultWrong(arg0 string, arg1 int) (result string) {
+       defer t.Call(arg0, arg1, result)() // "result" is evaluated too early and only once.
+       t.Stepf("Running %q", "code")
        return "the result"
 }
 
-func (s *Suite) Test_Call__onlyArguments(c *check.C) {
+func (s *Suite) Test_Tracer_Call__only_arguments(c *check.C) {
+       tracer := &s.Tracer
 
        output := s.captureTracingOutput(func() {
-               onlyArguments("arg0", 1234)
+               tracer.onlyArguments("arg0", 1234)
        })
 
        c.Check(output, check.Equals, ""+
-               "TRACE: + netbsd.org/pkglint/trace.onlyArguments(\"arg0\", 1234)\n"+
+               "TRACE: + netbsd.org/pkglint/trace.(*Tracer).onlyArguments(\"arg0\", 1234)\n"+
                "TRACE: 1   Running \"code\"\n"+
-               "TRACE: - netbsd.org/pkglint/trace.onlyArguments(\"arg0\", 1234)\n")
+               "TRACE: - netbsd.org/pkglint/trace.(*Tracer).onlyArguments(\"arg0\", 1234)\n")
 }
 
-func (s *Suite) Test_Call__argumentsAndResult(c *check.C) {
+func (s *Suite) Test_Tracer_Call__arguments_and_result(c *check.C) {
+       tracer := &s.Tracer
 
        output := s.captureTracingOutput(func() {
-               argumentsAndResult("arg0", 1234)
+               tracer.argumentsAndResult("arg0", 1234)
        })
 
        c.Check(output, check.Equals, ""+
-               "TRACE: + netbsd.org/pkglint/trace.argumentsAndResult(\"arg0\", 1234)\n"+
+               "TRACE: + netbsd.org/pkglint/trace.(*Tracer).argumentsAndResult(\"arg0\", 1234)\n"+
                "TRACE: 1   Running \"code\"\n"+
-               "TRACE: - netbsd.org/pkglint/trace.argumentsAndResult(\"arg0\", 1234, \"=>\", \"the result\")\n")
+               "TRACE: - netbsd.org/pkglint/trace.(*Tracer).argumentsAndResult(\"arg0\", 1234, \"=>\", \"the result\")\n")
 }
 
-func (s *Suite) Test_Call__argumentsAndResultWrong(c *check.C) {
+func (s *Suite) Test_Tracer_Call__arguments_and_result_wrong(c *check.C) {
+       tracer := &s.Tracer
 
        output := s.captureTracingOutput(func() {
-               argumentsAndResultWrong("arg0", 1234)
+               tracer.argumentsAndResultWrong("arg0", 1234)
        })
 
        c.Check(output, check.Equals, ""+
-               "TRACE: + netbsd.org/pkglint/trace.argumentsAndResultWrong(\"arg0\", 1234, \"\")\n"+
+               "TRACE: + netbsd.org/pkglint/trace.(*Tracer).argumentsAndResultWrong(\"arg0\", 1234, \"\")\n"+
                "TRACE: 1   Running \"code\"\n"+
-               "TRACE: - netbsd.org/pkglint/trace.argumentsAndResultWrong(\"arg0\", 1234, \"\")\n")
+               "TRACE: - netbsd.org/pkglint/trace.(*Tracer).argumentsAndResultWrong(\"arg0\", 1234, \"\")\n")
 }
 
 func (s *Suite) Test__fixed_argument_variants(c *check.C) {
+       tracer := &s.Tracer
 
        output := s.captureTracingOutput(func() {
-               defer Call0()()
-               defer Call1("x")()
-               defer Call2("x", "y")()
-               Step1("step %s", "a")
-               Step2("step %s, %s", "a", "b")
+               defer tracer.Call0()()
+               defer tracer.Call1("x")()
+               defer tracer.Call2("x", "y")()
+               tracer.Step1("step %s", "a")
+               tracer.Step2("step %s, %s", "a", "b")
        })
 
        c.Check(output, check.Equals, ""+
@@ -87,9 +94,10 @@ func (s *Suite) Test__fixed_argument_var
 }
 
 func (s *Suite) Test__stringer_arg(c *check.C) {
+       tracer := &s.Tracer
 
        output := s.captureTracingOutput(func() {
-               defer Call(str{}, &str{})()
+               defer tracer.Call(str{}, &str{})()
        })
 
        c.Check(output, check.Equals, ""+
@@ -97,29 +105,35 @@ func (s *Suite) Test__stringer_arg(c *ch
                "TRACE: - netbsd.org/pkglint/trace.(*Suite).Test__stringer_arg.func1(It's a string, It's a string)\n")
 }
 
-func (s *Suite) Test_traceCall__panic(c *check.C) {
+func (s *Suite) Test_Tracer_traceCall__panic(c *check.C) {
+       tracer := &s.Tracer
+
        c.Check(
-               func() { traceCall() },
+               func() { tracer.traceCall() },
                check.Panics,
                "Internal pkglint error: calls to trace.Call must only occur in tracing mode")
 }
 
-func (s *Suite) Test_Result__panic(c *check.C) {
+func (s *Suite) Test_Tracer_Result__panic(c *check.C) {
+       tracer := &s.Tracer
+
        c.Check(
-               func() { Result("invalid argument") },
+               func() { tracer.Result("invalid argument") },
                check.Panics,
                "Result must be called with a pointer to the result, not \"invalid argument\".")
 }
 
 func (s *Suite) captureTracingOutput(action func()) string {
+       tracer := &s.Tracer
+
        out := bytes.Buffer{}
-       Out = &out
-       Tracing = true
+       tracer.Out = &out
+       tracer.Tracing = true
 
        action()
 
-       Tracing = false
-       Out = nil
+       tracer.Tracing = false
+       tracer.Out = nil
        return out.String()
 }
 
@@ -128,3 +142,10 @@ type str struct{}
 func (str) String() string {
        return "It's a string"
 }
+
+func (s *Suite) Test__test_names(c *check.C) {
+       ck := intqa.NewTestNameChecker(c)
+       ck.AllowCamelCaseDescriptions()
+       ck.ShowWarnings(false)
+       ck.Check()
+}

Added files:

Index: pkgsrc/pkgtools/pkglint/files/expecter_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/expecter_test.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/expecter_test.go      Wed Nov  7 20:58:23 2018
@@ -0,0 +1,36 @@
+package main
+
+import (
+       "gopkg.in/check.v1"
+)
+
+func (s *Suite) Test_Expecter_ExpectEmptyLine__beginning_of_file(c *check.C) {
+       t := s.Init(c)
+
+       lines := t.NewLines("file.txt",
+               "line 1",
+               "line 2")
+       exp := NewExpecter(lines)
+
+       exp.ExpectEmptyLine()
+
+       t.CheckOutputLines(
+               "NOTE: file.txt:1: Empty line expected before this line.")
+}
+
+func (s *Suite) Test_Expecter_ExpectEmptyLine__end_of_file(c *check.C) {
+       t := s.Init(c)
+
+       lines := t.NewLines("file.txt",
+               "line 1",
+               "line 2")
+       exp := NewExpecter(lines)
+
+       for exp.Advance() {
+       }
+
+       exp.ExpectEmptyLine()
+
+       t.CheckOutputLines(
+               "NOTE: file.txt:2: Empty line expected after this line.")
+}
Index: pkgsrc/pkgtools/pkglint/files/lines.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/lines.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/lines.go      Wed Nov  7 20:58:23 2018
@@ -0,0 +1,33 @@
+package main
+
+import "path"
+
+type Lines = *LinesImpl
+
+type LinesImpl struct {
+       FileName string
+       BaseName string
+       Lines    []Line
+}
+
+func NewLines(fileName string, lines []Line) Lines {
+       return &LinesImpl{fileName, path.Base(fileName), lines}
+}
+
+func (ls *LinesImpl) Len() int { return len(ls.Lines) }
+
+func (ls *LinesImpl) LastLine() Line { return ls.Lines[ls.Len()-1] }
+
+func (ls *LinesImpl) EOFLine() Line { return NewLine(ls.FileName, -1, "", nil) }
+
+func (ls *LinesImpl) Errorf(format string, args ...interface{}) {
+       NewLineWhole(ls.FileName).Errorf(format, args...)
+}
+
+func (ls *LinesImpl) Warnf(format string, args ...interface{}) {
+       NewLineWhole(ls.FileName).Warnf(format, args...)
+}
+
+func (ls *LinesImpl) SaveAutofixChanges() {
+       SaveAutofixChanges(ls)
+}

Index: pkgsrc/pkgtools/pkglint/files/histogram/histogram_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/histogram/histogram_test.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/histogram/histogram_test.go   Wed Nov  7 20:58:23 2018
@@ -0,0 +1,28 @@
+package histogram_test
+
+import (
+       "gopkg.in/check.v1"
+       "netbsd.org/pkglint/histogram"
+       "strings"
+       "testing"
+)
+
+type Suite struct{}
+
+var _ = check.Suite(new(Suite))
+
+func Test(t *testing.T) { check.TestingT(t) }
+
+func (s *Suite) Test_Histogram(c *check.C) {
+       hgr := histogram.New()
+       hgr.Add("one", 1)
+       hgr.Add("two", 2)
+       hgr.Add("three", 3)
+
+       var out strings.Builder
+       hgr.PrintStats(&out, "caption", 2)
+
+       c.Check(out.String(), check.Equals, ""+
+               "caption      3 three\n"+
+               "caption      2 two\n")
+}

Index: pkgsrc/pkgtools/pkglint/files/intqa/see.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/intqa/see.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/intqa/see.go  Wed Nov  7 20:58:23 2018
@@ -0,0 +1,6 @@
+package intqa
+
+// XXX: It might be nice to check all comments of the form "See XYZ"
+// to see whether XYZ actually exists. The scope should be the current type,
+// then the current package, then a package-qualified identifier.
+// As if there were a "_ = XYZ" at the beginning of the function.
Index: pkgsrc/pkgtools/pkglint/files/intqa/testnames.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/intqa/testnames.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/intqa/testnames.go    Wed Nov  7 20:58:23 2018
@@ -0,0 +1,330 @@
+// Package intqa provides quality assurance for the pkglint code.
+package intqa
+
+import (
+       "bytes"
+       "fmt"
+       "go/ast"
+       "go/parser"
+       "go/token"
+       "gopkg.in/check.v1"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "sort"
+       "strings"
+       "unicode"
+)
+
+// TestNameChecker ensures that all test names follow a common naming scheme:
+//
+// Test_${Type}_${Method}__${description_using_underscores}
+type TestNameChecker struct {
+       camelCase map[string]bool
+       ignore    []string
+       warn      bool
+       prefixes  []testeePrefix
+       c         *check.C
+       errors    []string
+       warnings  []string
+}
+
+type testeePrefix struct {
+       prefix   string
+       fileName string
+}
+
+// testeeElement is an element of the source code that can be tested.
+// It is either a type, a function or a method.
+// The test methods are also testeeElements.
+type testeeElement struct {
+       File string // The file containing the testeeElement
+       Type string // The type, e.g. MkLine
+       Func string // The function or method name, e.g. Warnf
+
+       FullName string // Type + "." + Func
+
+       // Whether the testeeElement is a test or a testee
+       Test bool
+
+       // For a test, its name without the description,
+       // otherwise the prefix (Type + "_" + Func) for the corresponding tests
+       Prefix string
+}
+
+func NewTestNameChecker(c *check.C) *TestNameChecker {
+       return &TestNameChecker{c: c, camelCase: make(map[string]bool)}
+}
+
+func (ck *TestNameChecker) IgnoreFiles(fileGlob string) {
+       ck.ignore = append(ck.ignore, fileGlob)
+}
+
+// AllowPrefix allows tests with the given prefix to appear in the test
+// file corresponding to the given source file (which doesn't even have
+// to exist).
+//
+// In all other cases, the tests may only be named after things from the
+// main code that can actually be tested.
+func (ck *TestNameChecker) AllowPrefix(prefix, sourceFileName string) {
+       ck.prefixes = append(ck.prefixes, testeePrefix{prefix, sourceFileName})
+}
+
+// AllowCamelCaseDescriptions allows the given strings to appear
+// in the description part of a test name (Test_$Type_$Method__$description).
+// In most cases the description should use snake case to allow for
+// easier reading.
+//
+// When writing tests for combinations of several functions, it is most
+// natural to mention one of these functions in the test name and the
+// other in the test description. This is a typical use case.
+func (ck *TestNameChecker) AllowCamelCaseDescriptions(descriptions ...string) {
+       for _, description := range descriptions {
+               ck.camelCase[description] = true
+       }
+}
+
+func (ck *TestNameChecker) ShowWarnings(warn bool) { ck.warn = warn }
+
+func (ck *TestNameChecker) addError(format string, args ...interface{}) {
+       ck.errors = append(ck.errors, "E: "+fmt.Sprintf(format, args...))
+}
+
+func (ck *TestNameChecker) addWarning(format string, args ...interface{}) {
+       ck.warnings = append(ck.warnings, "W: "+fmt.Sprintf(format, args...))
+}
+
+func newElement(typeName, funcName, fileName string) *testeeElement {
+       typeName = strings.TrimSuffix(typeName, "Impl")
+
+       e := &testeeElement{File: fileName, Type: typeName, Func: funcName}
+
+       e.FullName = e.Type + ifelseStr(e.Type != "" && e.Func != "", ".", "") + e.Func
+
+       e.Test = strings.HasSuffix(e.File, "_test.go") && e.Type != "" && strings.HasPrefix(e.Func, "Test")
+
+       if e.Test {
+               e.Prefix = strings.Split(strings.TrimPrefix(e.Func, "Test"), "__")[0]
+       } else {
+               e.Prefix = e.Type + ifelseStr(e.Type != "" && e.Func != "", "_", "") + e.Func
+       }
+
+       return e
+}
+
+// addElement adds a single type or function declaration
+// to the known elements.
+func (ck *TestNameChecker) addElement(elements *[]*testeeElement, decl ast.Decl, fileName string) {
+       switch decl := decl.(type) {
+
+       case *ast.GenDecl:
+               for _, spec := range decl.Specs {
+                       switch spec := spec.(type) {
+                       case *ast.TypeSpec:
+                               typeName := spec.Name.Name
+                               *elements = append(*elements, newElement(typeName, "", fileName))
+                       }
+               }
+
+       case *ast.FuncDecl:
+               typeName := ""
+               if decl.Recv != nil {
+                       typeExpr := decl.Recv.List[0].Type.(ast.Expr)
+                       if star, ok := typeExpr.(*ast.StarExpr); ok {
+                               typeName = star.X.(*ast.Ident).Name
+                       } else {
+                               typeName = typeExpr.(*ast.Ident).Name
+                       }
+               }
+               *elements = append(*elements, newElement(typeName, decl.Name.Name, fileName))
+       }
+}
+
+// fixTabs replaces literal tabs with proper escape sequences,
+// except for the indentation tabs.
+//
+// It doesn't really belong to this type (TestNameChecker) but
+// merely uses its infrastructure.
+func (ck *TestNameChecker) fixTabs(fileName string) {
+       if ck.isIgnored(fileName) {
+               return
+       }
+
+       readBytes, err := ioutil.ReadFile(fileName)
+       ck.c.Assert(err, check.IsNil)
+
+       var fixed bytes.Buffer
+       for _, line := range strings.SplitAfter(string(readBytes), "\n") {
+               rest := strings.TrimLeft(line, "\t")
+               fixed.WriteString(line[:len(line)-len(rest)])
+               fixed.WriteString(strings.Replace(rest, "\t", "\\t", -1))
+       }
+
+       if fixed.String() != string(readBytes) {
+               tmpName := fileName + ".tmp"
+               err = ioutil.WriteFile(tmpName, fixed.Bytes(), 0666)
+               ck.c.Assert(err, check.IsNil)
+               err = os.Rename(tmpName, fileName)
+               ck.c.Assert(err, check.IsNil)
+       }
+}
+
+// loadAllElements returns all type, function and method names
+// from the current package, in the form FunctionName or
+// TypeName.MethodName (omitting the * from the type name).
+func (ck *TestNameChecker) loadAllElements() []*testeeElement {
+       fileSet := token.NewFileSet()
+       pkgs, err := parser.ParseDir(fileSet, ".", func(fi os.FileInfo) bool { return true }, 0)
+       if err != nil {
+               panic(err)
+       }
+
+       var elements []*testeeElement
+       for _, pkg := range pkgs {
+               for fileName, file := range pkg.Files {
+                       for _, decl := range file.Decls {
+                               ck.addElement(&elements, decl, fileName)
+                       }
+               }
+       }
+
+       sort.Slice(elements, func(i, j int) bool {
+               ti := elements[i]
+               tj := elements[j]
+               switch {
+               case ti.Type != tj.Type:
+                       return ti.Type < tj.Type
+               case ti.Func != tj.Func:
+                       return ti.Func < tj.Func
+               default:
+                       return ti.File < tj.File
+               }
+       })
+
+       return elements
+}
+
+// collectTesteeByName generates a map containing the names of all
+// testable elements, as used in the test names. Examples:
+//
+//  Autofix
+//  Line_Warnf
+//  match5
+func (ck *TestNameChecker) collectTesteeByName(elements []*testeeElement) map[string]*testeeElement {
+       prefixes := make(map[string]*testeeElement)
+       for _, element := range elements {
+               if element.Prefix != "" {
+                       prefixes[element.Prefix] = element
+               }
+       }
+
+       for _, p := range ck.prefixes {
+               prefixes[p.prefix] = newElement(p.prefix, "", p.fileName)
+       }
+
+       return prefixes
+}
+
+func (ck *TestNameChecker) checkTestName(test *testeeElement, prefix string, descr string, testeeByName map[string]*testeeElement) {
+       testee := testeeByName[prefix]
+       if testee == nil {
+               ck.addError("Test %q for missing testee %q.", test.FullName, prefix)
+
+       } else if !strings.HasSuffix(testee.File, "_test.go") {
+               correctTestFile := strings.TrimSuffix(testee.File, ".go") + "_test.go"
+               if correctTestFile != test.File {
+                       ck.addWarning("Test %q for %q should be in %s instead of %s.",
+                               test.FullName, testee.FullName, correctTestFile, test.File)
+               }
+       }
+
+       if isCamelCase(descr) && !ck.camelCase[descr] {
+               ck.addError("%s: Test description %q must not use CamelCase.", test.FullName, descr)
+       }
+}
+
+func (ck *TestNameChecker) checkAll(elements []*testeeElement, testeeByName map[string]*testeeElement) {
+       testNames := make(map[string]bool)
+
+       for _, element := range elements {
+               if element.Test {
+                       method := element.Func
+                       switch {
+                       case strings.HasPrefix(method, "Test__"):
+                               // OK
+
+                       case strings.HasPrefix(method, "Test_"):
+                               refAndDescr := strings.SplitN(method[5:], "__", 2)
+                               descr := ""
+                               if len(refAndDescr) > 1 {
+                                       descr = refAndDescr[1]
+                               }
+                               testNames[refAndDescr[0]] = true
+                               ck.checkTestName(element, refAndDescr[0], descr, testeeByName)
+
+                       default:
+                               ck.addError("Test name %q must contain an underscore.", element.FullName)
+                       }
+               }
+       }
+
+       for _, element := range elements {
+               if !strings.HasSuffix(element.File, "_test.go") && !ck.isIgnored(element.File) {
+                       if !testNames[element.Prefix] {
+                               ck.addWarning("Missing unit test %q for %q.",
+                                       "Test_"+element.Prefix, element.FullName)
+                       }
+               }
+       }
+}
+
+func (ck *TestNameChecker) Check() {
+       elements := ck.loadAllElements()
+       testeeByName := ck.collectTesteeByName(elements)
+       ck.checkAll(elements, testeeByName)
+
+       for _, err := range ck.errors {
+               fmt.Println(err)
+       }
+       for _, warning := range ck.warnings {
+               if ck.warn {
+                       fmt.Println(warning)
+               }
+       }
+       if len(ck.errors) > 0 || (ck.warn && len(ck.warnings) > 0) {
+               fmt.Printf("%d %s and %d %s.",
+                       len(ck.errors),
+                       ifelseStr(len(ck.errors) == 1, "error", "errors"),
+                       len(ck.warnings),
+                       ifelseStr(len(ck.warnings) == 1, "warning", "warnings"))
+       }
+}
+
+func (ck *TestNameChecker) isIgnored(fileName string) bool {
+       for _, mask := range ck.ignore {
+               ok, err := filepath.Match(mask, fileName)
+               if err != nil {
+                       panic(err)
+               }
+               if ok {
+                       return true
+               }
+       }
+       return false
+}
+
+func ifelseStr(cond bool, a, b string) string {
+       if cond {
+               return a
+       }
+       return b
+}
+
+func isCamelCase(str string) bool {
+       for i := 0; i+1 < len(str); i++ {
+               if unicode.IsLower(rune(str[i])) && unicode.IsUpper(rune(str[i+1])) {
+                       return true
+               }
+       }
+       return false
+}

Index: pkgsrc/pkgtools/pkglint/files/textproc/lexer.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/textproc/lexer.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/textproc/lexer.go     Wed Nov  7 20:58:23 2018
@@ -0,0 +1,205 @@
+package textproc
+
+import "strings"
+
+// Lexer provides a flexible way of splitting a string into several parts
+// by repeatedly chopping off a prefix that matches a string, a function
+// or a set of byte values.
+type Lexer struct {
+       rest string
+}
+
+// LexerMark remembers a position in the string being parsed, to be able
+// to revert to that position, should a complex expression not match in
+// its entirety.
+type LexerMark string
+
+// ByteSet is a subset of all 256 possible byte values.
+// It is used for matching byte strings efficiently.
+//
+// It cannot match Unicode code points individually and is therefore
+// usually used with ASCII characters.
+type ByteSet struct {
+       bits [4]uint64
+}
+
+func NewLexer(text string) *Lexer {
+       return &Lexer{text}
+}
+
+// Rest returns the part of the string that has not yet been chopped off.
+func (l *Lexer) Rest() string { return l.rest }
+
+// EOF returns whether the whole input has been consumed.
+func (l *Lexer) EOF() bool { return l.rest == "" }
+
+// PeekByte returns the next byte without chopping it off, or -1 at the end.
+func (l *Lexer) PeekByte() int {
+       if l.rest != "" {
+               return int(l.rest[0])
+       }
+       return -1
+}
+
+// Skip skips the next n bytes.
+func (l *Lexer) Skip(n int) {
+       l.rest = l.rest[n:]
+}
+
+// NextString tests whether the remaining string has the given prefix,
+// and if so, chops and returns it. Otherwise, returns the empty string.
+func (l *Lexer) NextString(prefix string) string {
+       if strings.HasPrefix(l.rest, prefix) {
+               l.rest = l.rest[len(prefix):]
+               return prefix
+       }
+       return ""
+}
+
+// NextHspace chops off the longest prefix (possibly empty) consisting
+// solely of horizontal whitespace, which is the ASCII space (U+0020)
+// and tab (U+0009).
+func (l *Lexer) NextHspace() string {
+       // The same code as in NextBytesFunc, inlined here for performance reasons.
+       // As of Go 1.11, the compiler does not inline constant function arguments.
+       i := 0
+       rest := l.rest
+       for i < len(rest) && (rest[i] == ' ' || rest[i] == '\t') {
+               i++
+       }
+       if i != 0 {
+               l.rest = rest[i:]
+       }
+       return rest[:i]
+}
+
+// NextByte returns true if the remaining string starts with the given byte,
+// and in that case, chops it off.
+//
+// The return type differs from the other methods since creating a string
+// would be too much work for such a simple operation.
+func (l *Lexer) NextByte(b byte) bool {
+       if len(l.rest) > 0 && l.rest[0] == b {
+               l.rest = l.rest[1:]
+               return true
+       }
+       return false
+}
+
+// NextBytesFunc chops off the longest prefix (possibly empty) consisting
+// solely of bytes for which fn returns true.
+func (l *Lexer) NextBytesFunc(fn func(b byte) bool) string {
+       i := 0
+       rest := l.rest
+       for i < len(rest) && fn(rest[i]) {
+               i++
+       }
+       if i != 0 {
+               l.rest = rest[i:]
+       }
+       return rest[:i]
+}
+
+// NextByteSet chops off and returns the first byte if the set contains it,
+// otherwise -1.
+func (l *Lexer) NextByteSet(set *ByteSet) int {
+       rest := l.rest
+       if 0 < len(rest) && set.bits[rest[0]/64]&(1<<(rest[0]%64)) != 0 {
+               l.rest = rest[1:]
+               return int(rest[0])
+       }
+       return -1
+}
+
+// NextBytesSet chops off the longest prefix (possibly empty) consisting
+// solely of bytes from the given set.
+func (l *Lexer) NextBytesSet(bytes *ByteSet) string {
+       // The same code as in NextBytesFunc, inlined here for performance reasons.
+       // As of Go 1.11, the compiler does not inline variable function arguments.
+       i := 0
+       rest := l.rest
+       for i < len(rest) && bytes.bits[rest[i]/64]&(1<<(rest[i]%64)) != 0 {
+               i++
+       }
+       if i != 0 {
+               l.rest = rest[i:]
+       }
+       return rest[:i]
+}
+
+// Mark returns the current position of the lexer,
+// which can later be restored by calling Reset.
+func (l *Lexer) Mark() LexerMark {
+       return LexerMark(l.rest)
+}
+
+// Reset sets the lexer back to the position where
+// the corresponding Mark was called.
+func (l *Lexer) Reset(mark LexerMark) {
+       l.rest = string(mark)
+}
+
+// Since returns the text between the given mark and the current position.
+func (l *Lexer) Since(mark LexerMark) string {
+       return string(mark)[0 : len(mark)-len(l.rest)]
+}
+
+// Copy returns a copy of this lexer.
+// It can be used to try one path of parsing and then either discard the
+// result or commit it back by calling Commit.
+func (l *Lexer) Copy() *Lexer { return &Lexer{l.rest} }
+
+// Commit copies the state of the other lexer into this lexer.
+// It always returns true so that it can be used in conditions.
+func (l *Lexer) Commit(other *Lexer) bool { l.rest = other.rest; return true }
+
+// NewByteSet creates a bit mask out of a string like "0-9A-Za-z_".
+// The bit mask can be used with Lexer.NextBytesSet.
+func NewByteSet(chars string) *ByteSet {
+       var set ByteSet
+       i := 0
+
+       for i < len(chars) {
+               switch {
+               case i+2 < len(chars) && chars[i+1] == '-':
+                       min := uint(chars[i])
+                       max := uint(chars[i+2]) // inclusive
+                       for j := uint(0); j < 4; j++ {
+                               minBit := 64 * j
+                               if min < minBit+64 && minBit <= max {
+                                       loMask := ^uint64(0)
+                                       if minBit < min {
+                                               loMask <<= min - minBit
+                                       }
+
+                                       hiMask := ^uint64(0)
+                                       if minBit+63 > max {
+                                               hiMask >>= minBit + 63 - max
+                                       }
+
+                                       set.bits[j] |= loMask & hiMask
+                               }
+                       }
+                       i += 3
+               default:
+                       ch := chars[i]
+                       set.bits[ch/64] |= 1 << (ch % 64)
+                       i++
+               }
+       }
+       return &set
+}
+
+// Inverse returns a byte set that matches the inverted set of bytes.
+func (bs *ByteSet) Inverse() *ByteSet {
+       return &ByteSet{[4]uint64{^bs.bits[0], ^bs.bits[1], ^bs.bits[2], ^bs.bits[3]}}
+}
+
+// Predefined byte sets for parsing ASCII text.
+var (
+       Alnum  = NewByteSet("A-Za-z0-9")  // Alphanumerical, without underscore
+       AlnumU = NewByteSet("A-Za-z0-9_") // Alphanumerical, including underscore
+       Digit  = NewByteSet("0-9")        // The digits zero to nine
+       Space  = NewByteSet("\t\n ")      // Tab, newline, space
+       Hspace = NewByteSet("\t ")        // Tab, space
+)
Index: pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.1
--- /dev/null   Wed Nov  7 20:58:24 2018
+++ pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go        Wed Nov  7 20:58:23 2018
@@ -0,0 +1,295 @@
+package textproc
+
+import (
+       "gopkg.in/check.v1"
+       "netbsd.org/pkglint/intqa"
+       "testing"
+       "unicode"
+)
+
+type Suite struct{}
+
+var equals = check.Equals
+
+func Test(t *testing.T) {
+       check.Suite(new(Suite))
+       check.TestingT(t)
+}
+
+func (s *Suite) Test_NewLexer(c *check.C) {
+       lexer := NewLexer("text")
+
+       c.Check(lexer.rest, equals, "text")
+}
+
+func (s *Suite) Test_Lexer_Rest__initial(c *check.C) {
+       lexer := NewLexer("text")
+
+       c.Check(lexer.Rest(), equals, "text")
+}
+
+func (s *Suite) Test_Lexer_Rest__end(c *check.C) {
+       lexer := NewLexer("")
+
+       c.Check(lexer.Rest(), equals, "")
+}
+
+func (s *Suite) Test_Lexer_EOF__initial(c *check.C) {
+       lexer := NewLexer("text")
+
+       c.Check(lexer.EOF(), equals, false)
+}
+
+func (s *Suite) Test_Lexer_EOF__end(c *check.C) {
+       lexer := NewLexer("")
+
+       c.Check(lexer.EOF(), equals, true)
+}
+
+func (s *Suite) Test_Lexer_PeekByte(c *check.C) {
+       lexer := NewLexer("text")
+
+       c.Check(lexer.PeekByte(), equals, int('t'))
+
+       c.Check(lexer.NextString("text"), equals, "text")
+
+       c.Check(lexer.PeekByte(), equals, -1)
+}
+
+func (s *Suite) Test_Lexer_Skip(c *check.C) {
+       lexer := NewLexer("example text")
+
+       lexer.Skip(7)
+
+       c.Check(lexer.Rest(), equals, " text")
+
+       // Skipping a fixed number of bytes only makes sense when the
+       // lexer has examined every one of them before. Therefore no
+       // extra check is done here, and panicking here is intentional.
+       c.Check(
+               func() { lexer.Skip(6) },
+               check.PanicMatches,
+               `^runtime error: slice bounds out of range$`)
+}
+
+func (s *Suite) Test_Lexer_NextString(c *check.C) {
+       lexer := NewLexer("text")
+
+       c.Check(lexer.NextString("te"), equals, "te")
+       c.Check(lexer.NextString("st"), equals, "") // Did not match.
+       c.Check(lexer.NextString("xt"), equals, "xt")
+}
+
+func (s *Suite) Test_Lexer_NextHspace(c *check.C) {
+       lexer := NewLexer("spaces   \t \t  and tabs\n")
+
+       c.Check(lexer.NextString("spaces"), equals, "spaces")
+       c.Check(lexer.NextHspace(), equals, "   \t \t  ")
+       c.Check(lexer.NextHspace(), equals, "") // No space left.
+       c.Check(lexer.NextString("and tabs"), equals, "and tabs")
+       c.Check(lexer.NextHspace(), equals, "") // Newline is not a horizontal space.
+}
+
+func (s *Suite) Test_Lexer_NextByte(c *check.C) {
+       lexer := NewLexer("byte")
+
+       c.Check(lexer.NextByte('b'), equals, true)
+       c.Check(lexer.NextByte('b'), equals, false) // The b is already chopped off.
+       c.Check(lexer.NextByte('y'), equals, true)
+       c.Check(lexer.NextByte('t'), equals, true)
+       c.Check(lexer.NextByte('e'), equals, true)
+       c.Check(lexer.NextByte(0), equals, false) // This is not a C string.
+}
+
+func (s *Suite) Test_Lexer_NextBytesFunc(c *check.C) {
+       lexer := NewLexer("an alphanumerical string")
+
+       c.Check(lexer.NextBytesFunc(func(b byte) bool { return 'A' <= b && b <= 'Z' }), equals, "")
+       c.Check(lexer.NextBytesFunc(func(b byte) bool { return !unicode.IsSpace(rune(b)) }), equals, "an")
+       c.Check(lexer.NextHspace(), equals, " ")
+       c.Check(lexer.NextBytesFunc(func(b byte) bool { return 'a' <= b && b <= 'z' }), equals, "alphanumerical")
+       c.Check(lexer.NextBytesFunc(func(b byte) bool { return true }), equals, " string")
+}
+
+func (s *Suite) Test_Lexer_NextByteSet(c *check.C) {
+       lexer := NewLexer("an a\n")
+
+       c.Check(lexer.NextByteSet(Alnum), equals, int('a'))
+       c.Check(lexer.NextByteSet(Alnum), equals, int('n'))
+       c.Check(lexer.NextByteSet(Space), equals, int(' '))
+       c.Check(lexer.NextByteSet(Alnum), equals, int('a'))
+       c.Check(lexer.NextByteSet(Space), equals, int('\n'))
+       c.Check(lexer.NextByteSet(Alnum), equals, -1)
+}
+
+func (s *Suite) Test_Lexer_NextBytesSet(c *check.C) {
+       lexer := NewLexer("an alphanumerical 90_ \tstring\t\t \n")
+
+       c.Check(lexer.NextBytesSet(Alnum), equals, "an")
+       c.Check(lexer.NextBytesSet(Alnum), equals, "")
+       c.Check(lexer.NextBytesSet(Space), equals, " ")
+       c.Check(lexer.NextBytesSet(Alnum), equals, "alphanumerical")
+       c.Check(lexer.NextBytesSet(Space), equals, " ")
+       c.Check(lexer.NextBytesSet(AlnumU), equals, "90_")
+       c.Check(lexer.NextBytesSet(Space), equals, " \t")
+       c.Check(lexer.NextBytesSet(Alnum), equals, "string")
+       c.Check(lexer.NextBytesSet(Hspace), equals, "\t\t ")
+       c.Check(lexer.NextBytesSet(Space), equals, "\n")
+}
+
+func (s *Suite) Test_Lexer_Mark__beginning(c *check.C) {
+       lexer := NewLexer("text")
+
+       mark := lexer.Mark()
+       c.Check(lexer.NextString("text"), equals, "text")
+
+       c.Check(lexer.Rest(), equals, "")
+
+       lexer.Reset(mark)
+
+       c.Check(lexer.Rest(), equals, "text")
+}
+
+func (s *Suite) Test_Lexer_Mark__middle(c *check.C) {
+       lexer := NewLexer("text")
+       lexer.NextString("te")
+
+       mark := lexer.Mark()
+       c.Check(lexer.NextString("x"), equals, "x")
+
+       c.Check(lexer.Rest(), equals, "t")
+
+       lexer.Reset(mark)
+
+       c.Check(lexer.Rest(), equals, "xt")
+}
+
+// Demonstrates that multiple marks can be taken at the same time and that
+// the lexer can be reset to any of them in any order.
+func (s *Suite) Test_Lexer_Reset__multiple(c *check.C) {
+       lexer := NewLexer("text")
+
+       mark0 := lexer.Mark()
+       c.Check(lexer.NextString("te"), equals, "te")
+       mark2 := lexer.Mark()
+       c.Check(lexer.NextString("x"), equals, "x")
+       mark3 := lexer.Mark()
+       c.Check(lexer.NextString("t"), equals, "t")
+       mark4 := lexer.Mark()
+
+       c.Check(lexer.Rest(), equals, "")
+
+       lexer.Reset(mark0)
+
+       c.Check(lexer.Rest(), equals, "text")
+
+       lexer.Reset(mark3)
+
+       c.Check(lexer.Rest(), equals, "t")
+
+       lexer.Reset(mark2)
+
+       c.Check(lexer.Rest(), equals, "xt")
+
+       lexer.Reset(mark4)
+
+       c.Check(lexer.Rest(), equals, "")
+}
+
+func (s *Suite) Test_Lexer__NextString_then_EOF(c *check.C) {
+       lexer := NewLexer("text")
+       lexer.NextString("text")
+
+       c.Check(lexer.EOF(), equals, true)
+}
+
+func (s *Suite) Test_Lexer_Since(c *check.C) {
+       lexer := NewLexer("text")
+       mark := lexer.Mark()
+
+       c.Check(lexer.NextString("te"), equals, "te")
+       c.Check(lexer.NextString("st"), equals, "") // Did not match.
+       c.Check(lexer.Since(mark), equals, "te")
+       c.Check(lexer.NextString("xt"), equals, "xt")
+       c.Check(lexer.Since(mark), equals, "text")
+}
+
+func (s *Suite) Test_Lexer_Copy(c *check.C) {
+       lexer := NewLexer("text")
+       copied := lexer.Copy()
+
+       c.Check(copied.Rest(), equals, lexer.Rest())
+
+       copied.NextString("te")
+
+       c.Check(copied.Rest(), equals, "xt")
+       c.Check(lexer.Rest(), equals, "text") // The original is not yet affected.
+}
+
+func (s *Suite) Test_Lexer_Commit(c *check.C) {
+       lexer := NewLexer("text")
+       copied := lexer.Copy()
+       copied.NextString("te")
+
+       c.Check(lexer.Rest(), equals, "text") // The original is not yet affected.
+
+       lexer.Commit(copied)
+
+       c.Check(lexer.Rest(), equals, "xt")
+}
+
+func (s *Suite) Test_NewByteSet(c *check.C) {
+       set := NewByteSet("A-Za-z0-9_\xFC")
+
+       c.Check(set.bits, equals, [4]uint64{
+               0x03ff000000000000, // 9-0
+               0x07fffffe87fffffe, // z-a _ Z-A
+               0x0000000000000000,
+               0x1000000000000000}) // \xFC
+}
+
+// Ensures that the bit manipulations work when a range spans
+// multiple of the uint64 words.
+func (s *Suite) Test_NewByteSet__large_range(c *check.C) {
+       set := NewByteSet("\x01-\xFE")
+
+       c.Check(set.bits, equals, [4]uint64{
+               0xfffffffffffffffe,
+               0xffffffffffffffff,
+               0xffffffffffffffff,
+               0x7fffffffffffffff})
+}
+
+// Demonstrates how to specify a byte set that includes a hyphen,
+// since that is also used for byte ranges.
+// The hyphen must be written as ---, and it must be at the beginning.
+func (s *Suite) Test_NewByteSet__range_hyphen(c *check.C) {
+       set := NewByteSet("---a-z")
+
+       c.Check(set.bits, equals, [4]uint64{
+               0x0000200000000000,
+               0x07fffffe00000000,
+               0x0000000000000000,
+               0x0000000000000000})
+}
+
+func (s *Suite) Test_ByteSet_Inverse(c *check.C) {
+       set := NewByteSet("A-Za-z0-9_\xFC")
+       inverse := set.Inverse()
+
+       c.Check(inverse.bits, equals, [4]uint64{
+               0xfc00ffffffffffff,
+               0xf800000178000001,
+               0xffffffffffffffff,
+               0xefffffffffffffff})
+
+       c.Check(inverse.Inverse().bits, equals, set.bits)
+}
+
+func (s *Suite) Test__test_names(c *check.C) {
+       ck := intqa.NewTestNameChecker(c)
+       ck.AllowCamelCaseDescriptions(
+               "NextString_then_EOF")
+       ck.ShowWarnings(false)
+       ck.Check()
+}



Home | Main Index | Thread Index | Old Index