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:           Sun Jul 14 21:25:47 UTC 2019

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile PLIST
        pkgsrc/pkgtools/pkglint/files: alternatives.go alternatives_test.go
            autofix.go autofix_test.go buildlink3.go category.go
            category_test.go check_test.go distinfo_test.go files_test.go
            licenses.go line.go line_test.go linelexer_test.go logging.go
            logging_test.go mkline.go mkline_test.go mklinechecker.go
            mklinechecker_test.go mklines.go mklines_test.go mkparser.go
            mkparser_test.go mkshparser_test.go mkshwalker_test.go
            mktokenslexer_test.go mktypes.go mktypes_test.go package.go
            package_test.go paragraph.go pkglint.go pkglint_test.go pkgsrc.go
            pkgsrc_test.go plist.go plist_test.go redundantscope_test.go
            shell.go shell_test.go shtokenizer_test.go shtypes_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
            var_test.go vardefs_test.go vartype.go vartype_test.go
            vartypecheck.go vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/textproc: lexer_test.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: varalignblock.go varalignblock_test.go
Removed Files:
        pkgsrc/pkgtools/pkglint/files: mklines_varalign_test.go

Log Message:
pkgtools/pkglint: update to 5.7.16

Changes since 5.7.15:

* Completely rewrote the code for aligning multiple variable assignment
  lines. It works on the actual lines of the file now instead of the
  parsed lines. This provides more exact diagnostics and also makes the
  handling of these lines easier so that future requirements can be
  implemented more easily.

* Added support for exotic conditions in .if clauses. These conditions
  are not seen in the wild though.

* Fixed wrong diagnostics for ALTERNATIVES files that appear
  conditionally in the PLIST.

* As always: lots of refactorings and newly added tests.


To generate a diff of this commit:
cvs rdiff -u -r1.587 -r1.588 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/PLIST
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/alternatives.go \
    pkgsrc/pkgtools/pkglint/files/mktypes_test.go
cvs rdiff -u -r1.15 -r1.16 pkgsrc/pkgtools/pkglint/files/alternatives_test.go \
    pkgsrc/pkgtools/pkglint/files/mkshparser_test.go \
    pkgsrc/pkgtools/pkglint/files/mktypes.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/autofix.go \
    pkgsrc/pkgtools/pkglint/files/autofix_test.go \
    pkgsrc/pkgtools/pkglint/files/files_test.go \
    pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go \
    pkgsrc/pkgtools/pkglint/files/substcontext.go
cvs rdiff -u -r1.23 -r1.24 pkgsrc/pkgtools/pkglint/files/buildlink3.go \
    pkgsrc/pkgtools/pkglint/files/category_test.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/category.go \
    pkgsrc/pkgtools/pkglint/files/vartype_test.go
cvs rdiff -u -r1.44 -r1.45 pkgsrc/pkgtools/pkglint/files/check_test.go
cvs rdiff -u -r1.31 -r1.32 pkgsrc/pkgtools/pkglint/files/distinfo_test.go \
    pkgsrc/pkgtools/pkglint/files/mkparser.go \
    pkgsrc/pkgtools/pkglint/files/util_test.go
cvs rdiff -u -r1.25 -r1.26 pkgsrc/pkgtools/pkglint/files/licenses.go
cvs rdiff -u -r1.37 -r1.38 pkgsrc/pkgtools/pkglint/files/line.go \
    pkgsrc/pkgtools/pkglint/files/plist_test.go
cvs rdiff -u -r1.18 -r1.19 pkgsrc/pkgtools/pkglint/files/line_test.go \
    pkgsrc/pkgtools/pkglint/files/tools_test.go \
    pkgsrc/pkgtools/pkglint/files/toplevel_test.go
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/files/linelexer_test.go \
    pkgsrc/pkgtools/pkglint/files/mktokenslexer_test.go \
    pkgsrc/pkgtools/pkglint/files/paragraph.go
cvs rdiff -u -r1.24 -r1.25 pkgsrc/pkgtools/pkglint/files/logging.go
cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/logging_test.go \
    pkgsrc/pkgtools/pkglint/files/tools.go \
    pkgsrc/pkgtools/pkglint/files/vardefs_test.go
cvs rdiff -u -r1.54 -r1.55 pkgsrc/pkgtools/pkglint/files/mkline.go
cvs rdiff -u -r1.60 -r1.61 pkgsrc/pkgtools/pkglint/files/mkline_test.go
cvs rdiff -u -r1.42 -r1.43 pkgsrc/pkgtools/pkglint/files/mklinechecker.go \
    pkgsrc/pkgtools/pkglint/files/plist.go
cvs rdiff -u -r1.38 -r1.39 \
    pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
cvs rdiff -u -r1.52 -r1.53 pkgsrc/pkgtools/pkglint/files/mklines.go
cvs rdiff -u -r1.46 -r1.47 pkgsrc/pkgtools/pkglint/files/mklines_test.go
cvs rdiff -u -r1.12 -r0 \
    pkgsrc/pkgtools/pkglint/files/mklines_varalign_test.go
cvs rdiff -u -r1.30 -r1.31 pkgsrc/pkgtools/pkglint/files/mkparser_test.go \
    pkgsrc/pkgtools/pkglint/files/pkgsrc.go
cvs rdiff -u -r1.9 -r1.10 pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go
cvs rdiff -u -r1.58 -r1.59 pkgsrc/pkgtools/pkglint/files/package.go \
    pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.49 -r1.50 pkgsrc/pkgtools/pkglint/files/package_test.go \
    pkgsrc/pkgtools/pkglint/files/shell_test.go
cvs rdiff -u -r1.57 -r1.58 pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.45 -r1.46 pkgsrc/pkgtools/pkglint/files/pkglint_test.go
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/redundantscope_test.go \
    pkgsrc/pkgtools/pkglint/files/testnames_test.go \
    pkgsrc/pkgtools/pkglint/files/var_test.go
cvs rdiff -u -r1.43 -r1.44 pkgsrc/pkgtools/pkglint/files/shell.go
cvs rdiff -u -r1.16 -r1.17 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go
cvs rdiff -u -r1.8 -r1.9 pkgsrc/pkgtools/pkglint/files/shtypes_test.go
cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/substcontext_test.go
cvs rdiff -u -r1.19 -r1.20 pkgsrc/pkgtools/pkglint/files/toplevel.go
cvs rdiff -u -r1.48 -r1.49 pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/varalignblock.go \
    pkgsrc/pkgtools/pkglint/files/varalignblock_test.go
cvs rdiff -u -r1.34 -r1.35 pkgsrc/pkgtools/pkglint/files/vartype.go
cvs rdiff -u -r1.51 -r1.52 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.4 -r1.5 pkgsrc/pkgtools/pkglint/files/textproc/lexer_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.587 pkgsrc/pkgtools/pkglint/Makefile:1.588
--- pkgsrc/pkgtools/pkglint/Makefile:1.587      Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/Makefile    Sun Jul 14 21:25:47 2019
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.587 2019/07/01 22:25:52 rillig Exp $
+# $NetBSD: Makefile,v 1.588 2019/07/14 21:25:47 rillig Exp $
 
-PKGNAME=       pkglint-5.7.15
+PKGNAME=       pkglint-5.7.16
 CATEGORIES=    pkgtools
 DISTNAME=      tools
 MASTER_SITES=  ${MASTER_SITE_GITHUB:=golang/}

Index: pkgsrc/pkgtools/pkglint/PLIST
diff -u pkgsrc/pkgtools/pkglint/PLIST:1.12 pkgsrc/pkgtools/pkglint/PLIST:1.13
--- pkgsrc/pkgtools/pkglint/PLIST:1.12  Mon Jun 10 19:51:57 2019
+++ pkgsrc/pkgtools/pkglint/PLIST       Sun Jul 14 21:25:47 2019
@@ -1,4 +1,4 @@
-@comment $NetBSD: PLIST,v 1.12 2019/06/10 19:51:57 rillig Exp $
+@comment $NetBSD: PLIST,v 1.13 2019/07/14 21:25:47 rillig Exp $
 bin/pkglint
 gopkg/pkg/${GO_PLATFORM}/netbsd.org/pkglint.a
 gopkg/pkg/${GO_PLATFORM}/netbsd.org/pkglint/getopt.a
@@ -54,7 +54,6 @@ gopkg/src/netbsd.org/pkglint/mklinecheck
 gopkg/src/netbsd.org/pkglint/mklinechecker_test.go
 gopkg/src/netbsd.org/pkglint/mklines.go
 gopkg/src/netbsd.org/pkglint/mklines_test.go
-gopkg/src/netbsd.org/pkglint/mklines_varalign_test.go
 gopkg/src/netbsd.org/pkglint/mkparser.go
 gopkg/src/netbsd.org/pkglint/mkparser_test.go
 gopkg/src/netbsd.org/pkglint/mkshparser.go
@@ -113,6 +112,8 @@ gopkg/src/netbsd.org/pkglint/util.go
 gopkg/src/netbsd.org/pkglint/util_test.go
 gopkg/src/netbsd.org/pkglint/var.go
 gopkg/src/netbsd.org/pkglint/var_test.go
+gopkg/src/netbsd.org/pkglint/varalignblock.go
+gopkg/src/netbsd.org/pkglint/varalignblock_test.go
 gopkg/src/netbsd.org/pkglint/vardefs.go
 gopkg/src/netbsd.org/pkglint/vardefs_test.go
 gopkg/src/netbsd.org/pkglint/vartype.go

Index: pkgsrc/pkgtools/pkglint/files/alternatives.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives.go:1.12 pkgsrc/pkgtools/pkglint/files/alternatives.go:1.13
--- pkgsrc/pkgtools/pkglint/files/alternatives.go:1.12  Sun Jun 30 20:56:18 2019
+++ pkgsrc/pkgtools/pkglint/files/alternatives.go       Sun Jul 14 21:25:47 2019
@@ -17,7 +17,7 @@ func CheckFileAlternatives(filename stri
        }
 
        checkPlistWrapper := func(line *Line, wrapper string) {
-               if plist.Files[wrapper] {
+               if plist.Files[wrapper] != nil {
                        line.Errorf("Alternative wrapper %q must not appear in the PLIST.", wrapper)
                }
        }
@@ -25,7 +25,7 @@ func CheckFileAlternatives(filename stri
        checkPlistAlternative := func(line *Line, alternative string) {
                relImplementation := strings.Replace(alternative, "@PREFIX@/", "", 1)
                plistName := replaceAll(relImplementation, `@(\w+)@`, "${$1}")
-               if plist.Files[plistName] || G.Pkg.vars.Defined("ALTERNATIVES_SRC") {
+               if plist.Files[plistName] != nil || G.Pkg.vars.Defined("ALTERNATIVES_SRC") {
                        return
                }
 
Index: pkgsrc/pkgtools/pkglint/files/mktypes_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.12 pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.13
--- pkgsrc/pkgtools/pkglint/files/mktypes_test.go:1.12  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mktypes_test.go       Sun Jul 14 21:25:47 2019
@@ -17,9 +17,9 @@ func (s *Suite) Test_MkVarUse_Mod(c *che
 
        test := func(varUseText string, mod string) {
                line := t.NewLine("filename.mk", 123, "")
-               varUse := NewMkParser(line, varUseText, true).VarUse()
+               varUse := NewMkParser(line, varUseText).VarUse()
                t.CheckOutputEmpty()
-               c.Check(varUse.Mod(), equals, mod)
+               t.CheckEquals(varUse.Mod(), mod)
        }
 
        test("${varname:Q}", ":Q")
@@ -41,7 +41,7 @@ func (s *Suite) Test_MkVarUseModifier_Ch
 
        test := func(modifier string, changes bool) {
                mod := MkVarUseModifier{modifier}
-               t.Check(mod.ChangesWords(), equals, changes)
+               t.CheckEquals(mod.ChangesWords(), changes)
        }
 
        test("E", false)
@@ -73,31 +73,35 @@ func (s *Suite) Test_MkVarUseModifier_Ch
        })
 
        t.CheckOutputEmpty()
-       t.Check(n, equals, 100)
+       t.CheckEquals(n, 100)
 }
 
 func (s *Suite) Test_MkVarUseModifier_MatchSubst(c *check.C) {
+       t := s.Init(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")
+       t.CheckEquals(ok, true)
+       t.CheckEquals(regex, false)
+       t.CheckEquals(from, "from")
+       t.CheckEquals(to, "to")
+       t.CheckEquals(options, "1g")
 }
 
 func (s *Suite) Test_MkVarUseModifier_MatchSubst__backslash(c *check.C) {
+       t := s.Init(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, "")
+       t.CheckEquals(ok, true)
+       t.CheckEquals(regex, false)
+       t.CheckEquals(from, "\\/")
+       t.CheckEquals(to, "\\:")
+       t.CheckEquals(options, "")
 }
 
 // Some pkgsrc users really explore the darkest corners of bmake by using
@@ -107,37 +111,43 @@ func (s *Suite) Test_MkVarUseModifier_Ma
 // Using the backslash as separator means that it cannot be used for anything
 // else, not even for escaping other characters.
 func (s *Suite) Test_MkVarUseModifier_MatchSubst__backslash_as_separator(c *check.C) {
+       t := s.Init(c)
+
        mod := MkVarUseModifier{"S\\.post1\\\\1"}
 
        ok, regex, from, to, options := mod.MatchSubst()
 
-       c.Check(ok, equals, true)
-       c.Check(regex, equals, false)
-       c.Check(from, equals, ".post1")
-       c.Check(to, equals, "")
-       c.Check(options, equals, "1")
+       t.CheckEquals(ok, true)
+       t.CheckEquals(regex, false)
+       t.CheckEquals(from, ".post1")
+       t.CheckEquals(to, "")
+       t.CheckEquals(options, "1")
 }
 
 // As of 2019-03-24, pkglint doesn't know how to handle complicated
 // :C modifiers.
 func (s *Suite) Test_MkVarUseModifier_Subst__regexp(c *check.C) {
+       t := s.Init(c)
+
        mod := MkVarUseModifier{"C,.*,,"}
 
        empty, ok := mod.Subst("anything")
 
-       c.Check(ok, equals, false)
-       c.Check(empty, equals, "")
+       t.CheckEquals(ok, false)
+       t.CheckEquals(empty, "")
 }
 
 // When given a modifier that is not actually a :S or :C, Subst
 // doesn't do anything.
 func (s *Suite) Test_MkVarUseModifier_Subst__invalid_argument(c *check.C) {
+       t := s.Init(c)
+
        mod := MkVarUseModifier{"Mpattern"}
 
        empty, ok := mod.Subst("anything")
 
-       c.Check(ok, equals, false)
-       c.Check(empty, equals, "")
+       t.CheckEquals(ok, false)
+       t.CheckEquals(empty, "")
 }
 
 func (s *Suite) Test_MkVarUseModifier_Subst__no_tracing(c *check.C) {
@@ -148,8 +158,8 @@ func (s *Suite) Test_MkVarUseModifier_Su
 
        result, ok := mod.Subst("from a to b")
 
-       c.Check(ok, equals, true)
-       c.Check(result, equals, "to a to b")
+       t.CheckEquals(ok, true)
+       t.CheckEquals(result, "to a to b")
 }
 
 // Since the replacement text is not a simple string, the :C modifier
@@ -157,10 +167,12 @@ func (s *Suite) Test_MkVarUseModifier_Su
 // one of the special characters that would need to be escaped in the
 // replacement text.
 func (s *Suite) Test_MkVarUseModifier_Subst__C_with_complex_replacement(c *check.C) {
+       t := s.Init(c)
+
        mod := MkVarUseModifier{"C,from,${VAR},"}
 
        result, ok := mod.Subst("from a to b")
 
-       c.Check(ok, equals, false)
-       c.Check(result, equals, "")
+       t.CheckEquals(ok, false)
+       t.CheckEquals(result, "")
 }

Index: pkgsrc/pkgtools/pkglint/files/alternatives_test.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.15 pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.16
--- pkgsrc/pkgtools/pkglint/files/alternatives_test.go:1.15     Sun Jun 30 20:56:18 2019
+++ pkgsrc/pkgtools/pkglint/files/alternatives_test.go  Sun Jul 14 21:25:47 2019
@@ -47,6 +47,32 @@ func (s *Suite) Test_CheckFileAlternativ
                "AUTOFIX: ALTERNATIVES:4: Replacing \"bin/vim\" with \"@PREFIX@/bin/vim\".")
 }
 
+// A file that is mentioned in the ALTERNATIVES file must appear
+// in the package's PLIST files. It may appear there conditionally,
+// assuming that manual testing will reveal inconsistencies. Or
+// that this scenario is an edge case anyway.
+func (s *Suite) Test_CheckFileAlternatives__PLIST_conditional(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package")
+       t.Chdir("category/package")
+       t.CreateFileLines("ALTERNATIVES",
+               "bin/wrapper1 @PREFIX@/bin/always-exists",
+               "bin/wrapper2 @PREFIX@/bin/conditional",
+               "bin/wrapper3 @PREFIX@/bin/not-found")
+       t.CreateFileLines("PLIST",
+               PlistCvsID,
+               "bin/always-exists",
+               "${PLIST.cond}bin/conditional")
+       t.FinishSetUp()
+
+       G.Check(".")
+
+       t.CheckOutputLines(
+               "ERROR: ALTERNATIVES:3: Alternative implementation \"@PREFIX@/bin/not-found\" " +
+                       "must appear in the PLIST as \"bin/not-found\".")
+}
+
 func (s *Suite) Test_CheckFileAlternatives__empty(c *check.C) {
        t := s.Init(c)
 
Index: pkgsrc/pkgtools/pkglint/files/mkshparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.15 pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.16
--- pkgsrc/pkgtools/pkglint/files/mkshparser_test.go:1.15       Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mkshparser_test.go    Sun Jul 14 21:25:47 2019
@@ -16,10 +16,10 @@ func (s *Suite) Test_parseShellProgram__
                        program, err := parseShellProgram(mkline.Line, text)
 
                        if err == nil {
-                               c.Check(err, equals, expError)
+                               t.CheckEquals(err, expError)
                        } else {
-                               c.Check(err, deepEquals, expError)
-                               c.Check(program, deepEquals, expProgram)
+                               t.CheckDeepEquals(err, expError)
+                               t.CheckDeepEquals(program, expProgram)
                        }
 
                        t.CheckOutput(expDiagnostics)
@@ -51,6 +51,7 @@ func (s *Suite) Test_parseShellProgram__
 }
 
 type ShSuite struct {
+       t *Tester
        c *check.C
 }
 
@@ -538,12 +539,15 @@ func (s *ShSuite) Test_ShellParser__io_h
 
 func (s *ShSuite) init(c *check.C) *MkShBuilder {
        s.c = c
+       s.t = &Tester{c: c, testName: c.TestName()}
        return NewMkShBuilder()
 }
 
 func (s *ShSuite) test(program string, expected *MkShList) {
+       t := s.t
+
        tokens, rest := splitIntoShellTokens(dummyLine, program)
-       s.c.Check(rest, equals, "")
+       t.CheckEquals(rest, "")
        lexer := ShellLexer{
                current:        "",
                remaining:      tokens,
@@ -555,72 +559,75 @@ func (s *ShSuite) test(program string, e
 
        c := s.c
 
-       if c.Check(succeeded, equals, 0) && c.Check(lexer.error, equals, "") {
-               if !c.Check(lexer.result, deepEquals, expected) {
+       if t.CheckEquals(succeeded, 0) && t.CheckEquals(lexer.error, "") {
+               if !t.CheckDeepEquals(lexer.result, expected) {
                        actualJSON, actualErr := json.MarshalIndent(lexer.result, "", "  ")
                        expectedJSON, expectedErr := json.MarshalIndent(expected, "", "  ")
                        if c.Check(actualErr, check.IsNil) && c.Check(expectedErr, check.IsNil) {
-                               c.Check(string(actualJSON), deepEquals, string(expectedJSON))
+                               t.CheckDeepEquals(string(actualJSON), string(expectedJSON))
                        }
                }
        } else {
-               c.Check(lexer.remaining, deepEquals, []string{})
+               t.CheckDeepEquals(lexer.remaining, []string{})
        }
 }
 
 func (s *ShSuite) Test_ShellLexer_Lex__redirects(c *check.C) {
+       t := s.t
+
        tokens, rest := splitIntoShellTokens(dummyLine, "2>&1 <& <>file >>file <<EOF <<-EOF > /dev/stderr")
 
-       c.Check(tokens, deepEquals, []string{"2>&", "1", "<&", "<>", "file", ">>", "file", "<<", "EOF", "<<-", "EOF", ">", "/dev/stderr"})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(tokens, []string{"2>&", "1", "<&", "<>", "file", ">>", "file", "<<", "EOF", "<<-", "EOF", ">", "/dev/stderr"})
+       t.CheckEquals(rest, "")
 
        lexer := NewShellLexer(tokens, rest)
        var llval shyySymType
 
-       c.Check(lexer.Lex(&llval), equals, tkIO_NUMBER)
-       c.Check(llval.IONum, equals, 2)
+       t.CheckEquals(lexer.Lex(&llval), tkIO_NUMBER)
+       t.CheckEquals(llval.IONum, 2)
 
-       c.Check(lexer.Lex(&llval), equals, tkGTAND)
+       t.CheckEquals(lexer.Lex(&llval), tkGTAND)
 
-       c.Check(lexer.Lex(&llval), equals, tkWORD)
-       c.Check(llval.Word.MkText, equals, "1")
+       t.CheckEquals(lexer.Lex(&llval), tkWORD)
+       t.CheckEquals(llval.Word.MkText, "1")
 
-       c.Check(lexer.Lex(&llval), equals, tkLTAND)
+       t.CheckEquals(lexer.Lex(&llval), tkLTAND)
 
-       c.Check(lexer.Lex(&llval), equals, tkLTGT)
+       t.CheckEquals(lexer.Lex(&llval), tkLTGT)
 
-       c.Check(lexer.Lex(&llval), equals, tkWORD)
-       c.Check(llval.Word.MkText, equals, "file")
+       t.CheckEquals(lexer.Lex(&llval), tkWORD)
+       t.CheckEquals(llval.Word.MkText, "file")
 
-       c.Check(lexer.Lex(&llval), equals, tkGTGT)
+       t.CheckEquals(lexer.Lex(&llval), tkGTGT)
 
-       c.Check(lexer.Lex(&llval), equals, tkWORD)
-       c.Check(llval.Word.MkText, equals, "file")
+       t.CheckEquals(lexer.Lex(&llval), tkWORD)
+       t.CheckEquals(llval.Word.MkText, "file")
 
-       c.Check(lexer.Lex(&llval), equals, tkLTLT)
+       t.CheckEquals(lexer.Lex(&llval), tkLTLT)
 
-       c.Check(lexer.Lex(&llval), equals, tkWORD)
-       c.Check(llval.Word.MkText, equals, "EOF")
+       t.CheckEquals(lexer.Lex(&llval), tkWORD)
+       t.CheckEquals(llval.Word.MkText, "EOF")
 
-       c.Check(lexer.Lex(&llval), equals, tkLTLTDASH)
+       t.CheckEquals(lexer.Lex(&llval), tkLTLTDASH)
 
-       c.Check(lexer.Lex(&llval), equals, tkWORD)
-       c.Check(llval.Word.MkText, equals, "EOF")
+       t.CheckEquals(lexer.Lex(&llval), tkWORD)
+       t.CheckEquals(llval.Word.MkText, "EOF")
 
-       c.Check(lexer.Lex(&llval), equals, tkGT)
+       t.CheckEquals(lexer.Lex(&llval), tkGT)
 
-       c.Check(lexer.Lex(&llval), equals, tkWORD)
-       c.Check(llval.Word.MkText, equals, "/dev/stderr")
+       t.CheckEquals(lexer.Lex(&llval), tkWORD)
+       t.CheckEquals(llval.Word.MkText, "/dev/stderr")
 
-       c.Check(lexer.Lex(&llval), equals, 0)
+       t.CheckEquals(lexer.Lex(&llval), 0)
 }
 
 func (s *ShSuite) Test_ShellLexer_Lex__keywords(c *check.C) {
        b := s.init(c)
+       t := s.t
 
        testErr := func(program, error, remaining string) {
                tokens, rest := splitIntoShellTokens(dummyLine, program)
-               s.c.Check(rest, equals, "")
+               t.CheckEquals(rest, "")
 
                lexer := ShellLexer{
                        current:        "",
@@ -631,9 +638,9 @@ func (s *ShSuite) Test_ShellLexer_Lex__k
 
                succeeded := parser.Parse(&lexer)
 
-               c.Check(succeeded, equals, 1)
-               c.Check(lexer.error, equals, error)
-               c.Check(joinSkipEmpty(" ", append([]string{lexer.current}, lexer.remaining...)...), equals, remaining)
+               t.CheckEquals(succeeded, 1)
+               t.CheckEquals(lexer.error, error)
+               t.CheckEquals(joinSkipEmpty(" ", append([]string{lexer.current}, lexer.remaining...)...), remaining)
        }
 
        s.test(
Index: pkgsrc/pkgtools/pkglint/files/mktypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes.go:1.15 pkgsrc/pkgtools/pkglint/files/mktypes.go:1.16
--- pkgsrc/pkgtools/pkglint/files/mktypes.go:1.15       Mon Jun 10 19:51:57 2019
+++ pkgsrc/pkgtools/pkglint/files/mktypes.go    Sun Jul 14 21:25:47 2019
@@ -43,7 +43,7 @@ func (m MkVarUseModifier) IsSuffixSubst(
 }
 
 func (m MkVarUseModifier) MatchSubst() (ok bool, regex bool, from string, to string, options string) {
-       p := NewMkParser(nil, m.Text, false)
+       p := NewMkParser(nil, m.Text)
        return p.varUseModifierSubst('}')
 }
 

Index: pkgsrc/pkgtools/pkglint/files/autofix.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix.go:1.26 pkgsrc/pkgtools/pkglint/files/autofix.go:1.27
--- pkgsrc/pkgtools/pkglint/files/autofix.go:1.26       Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/autofix.go    Sun Jul 14 21:25:47 2019
@@ -125,6 +125,41 @@ func (fix *Autofix) ReplaceAfter(prefix,
        }
 }
 
+// ReplaceAt replaces the text "from" with "to", a single time.
+// But only if the text at the given position is indeed "from".
+func (fix *Autofix) ReplaceAt(rawIndex int, column int, from string, to string) {
+       assert(from != to)
+       fix.assertRealLine()
+
+       if fix.skip() {
+               return
+       }
+
+       rawLine := fix.line.raw[rawIndex]
+       if column >= len(rawLine.textnl) || !hasPrefix(rawLine.textnl[column:], from) {
+               return
+       }
+
+       replaced := rawLine.textnl[:column] + to + rawLine.textnl[column+len(from):]
+
+       if G.Logger.IsAutofix() {
+               rawLine.textnl = replaced
+
+               // Fix the parsed text as well.
+               // This is only approximate and won't work in some edge cases
+               // that involve escaped comments or replacements across line breaks.
+               //
+               // TODO: Do this properly by parsing the whole line again,
+               //  and ideally everything that depends on the parsed line.
+               //  This probably requires a generic notification mechanism.
+               if strings.Count(fix.line.Text, from) == 1 {
+                       fix.line.Text = strings.Replace(fix.line.Text, from, to, 1)
+               }
+       }
+       fix.Describef(rawLine.Lineno, "Replacing %q with %q.", from, to)
+       return
+}
+
 // ReplaceRegex replaces the first howOften or all occurrences (if negative)
 // of the `from` pattern with the fixed string `toText`.
 //
@@ -313,11 +348,12 @@ func (fix *Autofix) Apply() {
        logFix := G.Logger.IsAutofix()
 
        if logDiagnostic {
+               linenos := fix.affectedLinenos()
                msg := sprintf(fix.diagFormat, fix.diagArgs...)
-               if !logFix && G.Logger.FirstTime(line.Filename, line.Linenos(), msg) {
-                       line.showSource(G.Logger.out)
+               if !logFix && G.Logger.FirstTime(line.Filename, linenos, msg) {
+                       G.Logger.showSource(line)
                }
-               G.Logger.Logf(fix.level, line.Filename, line.Linenos(), fix.diagFormat, msg)
+               G.Logger.Logf(fix.level, line.Filename, linenos, fix.diagFormat, msg)
        }
 
        if logFix {
@@ -332,7 +368,7 @@ func (fix *Autofix) Apply() {
 
        if logDiagnostic || logFix {
                if logFix {
-                       line.showSource(G.Logger.out)
+                       G.Logger.showSource(line)
                }
                if logDiagnostic && len(fix.explanation) > 0 {
                        line.Explain(fix.explanation...)
@@ -347,66 +383,31 @@ func (fix *Autofix) Apply() {
        reset()
 }
 
-func (fix *Autofix) Realign(mkline *MkLine, newWidth int) {
-
-       // XXX: Check whether this method can be implemented as Custom fix.
-       // This complicated code should not be in the Autofix type.
-
-       fix.assertRealLine()
-       assert(mkline.IsMultiline())
-       assert(mkline.IsVarassign() || mkline.IsCommentedVarassign())
-
-       if fix.skip() {
-               return
+func (fix *Autofix) affectedLinenos() string {
+       if len(fix.actions) == 0 {
+               return fix.line.Linenos()
        }
 
-       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")
-               data := MkLineParser{}.split(nil, text)
-               _, a := MkLineParser{}.MatchVarassign(mkline.Line, text, data)
-               if a.value != "\\" {
-                       oldWidth = tabWidth(a.valueAlign)
+       var first, last int
+       for _, action := range fix.actions {
+               if action.lineno == 0 {
+                       continue
                }
-       }
 
-       for _, rawLine := range fix.line.raw[1:] {
-               _, comment, space := match2(rawLine.textnl, `^(#?)([ \t]*)`)
-               width := tabWidth(comment + space)
-               if (oldWidth == 0 || width < oldWidth) && width >= 8 {
-                       oldWidth = width
+               if last == 0 || action.lineno < first {
+                       first = action.lineno
                }
-               if !matches(space, `^\t* {0,7}$`) {
-                       normalized = false
+               if last == 0 || action.lineno > last {
+                       last = action.lineno
                }
        }
 
-       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]*)`)
-               newLineWidth := tabWidth(oldSpace) - oldWidth + newWidth
-               newSpace := strings.Repeat("\t", newLineWidth/8) + strings.Repeat(" ", newLineWidth%8)
-               replaced := strings.Replace(rawLine.textnl, comment+oldSpace, comment+newSpace, 1)
-               if replaced != rawLine.textnl {
-                       if G.Logger.IsAutofix() {
-                               rawLine.textnl = replaced
-                       }
-                       fix.Describef(rawLine.Lineno, "Replacing indentation %q with %q.", oldSpace, newSpace)
-               }
+       if last == 0 {
+               return fix.line.Linenos()
+       } else if first < last {
+               return sprintf("%d--%d", first, last)
+       } else {
+               return strconv.Itoa(first)
        }
 }
 
@@ -425,6 +426,8 @@ func (fix *Autofix) setDiag(level *LogLe
        fix.diagArgs = args
 }
 
+// skip returns whether this autofix should be skipped because
+// its message is matched by one of the --only command line options.
 func (fix *Autofix) skip() bool {
        assert(fix.diagFormat != "") // The diagnostic must be given before the action.
 
Index: pkgsrc/pkgtools/pkglint/files/autofix_test.go
diff -u pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.26 pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/autofix_test.go:1.26  Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/autofix_test.go       Sun Jul 14 21:25:47 2019
@@ -35,14 +35,14 @@ func (s *Suite) Test_Autofix__default_le
        fix.Delete()
        fix.Apply()
 
-       c.Check(fix.RawText(), equals, ""+
+       t.CheckEquals(fix.RawText(), ""+
                "# 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)
+       t.CheckEquals(fix.modified, true)
 }
 
 func (s *Suite) Test_Autofix__show_autofix_modifies_line(c *check.C) {
@@ -63,7 +63,7 @@ func (s *Suite) Test_Autofix__show_autof
        fix.Delete()
        fix.Apply()
 
-       c.Check(fix.RawText(), equals, ""+
+       t.CheckEquals(fix.RawText(), ""+
                "above\n"+
                "below\n")
        t.CheckOutputLines(
@@ -78,7 +78,7 @@ func (s *Suite) Test_Autofix__show_autof
                "-\t# row 1 \\",
                "-\tcontinuation of row 1",
                "+\tbelow")
-       c.Check(fix.modified, equals, true)
+       t.CheckEquals(fix.modified, true)
 }
 
 func (s *Suite) Test_Autofix_ReplaceAfter__autofix_in_continuation_line(c *check.C) {
@@ -153,7 +153,7 @@ func (s *Suite) Test_Autofix_ReplaceRege
        fix.Apply()
        SaveAutofixChanges(lines)
 
-       c.Check(lines.Lines[1].raw[0].textnl, equals, "XXXXX\n")
+       t.CheckEquals(lines.Lines[1].raw[0].textnl, "XXXXX\n")
        t.CheckFileLines("Makefile",
                "line1",
                "line2",
@@ -271,6 +271,82 @@ func (s *Suite) Test_Autofix_ReplaceAfte
                "AUTOFIX: filename:5: Replacing \"i\" with \"I\".")
 }
 
+func (s *Suite) Test_Autofix_ReplaceAt(c *check.C) {
+       t := s.Init(c)
+
+       lines := func(lines ...string) []string { return lines }
+       diagnostics := lines
+       autofixes := lines
+       test := func(texts []string, rawIndex int, column int, from, to string, diagnostics []string, autofixes []string) {
+
+               mainPart := func() {
+                       mklines := t.NewMkLines("filename.mk", texts...)
+                       assert(len(mklines.mklines) == 1)
+                       mkline := mklines.mklines[0]
+
+                       fix := mkline.Autofix()
+                       fix.Notef("Should be appended instead of assigned.")
+                       fix.ReplaceAt(rawIndex, column, from, to)
+                       fix.Anyway()
+                       fix.Apply()
+               }
+
+               t.SetUpCommandLine("-Wall")
+               mainPart()
+               t.CheckOutput(diagnostics)
+
+               t.SetUpCommandLine("-Wall", "--autofix")
+               mainPart()
+               t.CheckOutput(autofixes)
+       }
+
+       test(
+               lines(
+                       "VAR=value1 \\",
+                       "\tvalue2"),
+               0, 3, "=", "+=",
+               diagnostics(
+                       "NOTE: filename.mk:1: Should be appended instead of assigned."),
+               autofixes(
+                       "AUTOFIX: filename.mk:1: Replacing \"=\" with \"+=\"."))
+
+       // If the text at the precisely given position does not match,
+       // the note is still printed because of the fix.Anyway(), but
+       // nothing is replaced automatically.
+       test(
+               lines(
+                       "VAR=value1 \\",
+                       "\tvalue2"),
+               0, 3, "?", "+=",
+               diagnostics(
+                       "NOTE: filename.mk:1--2: Should be appended instead of assigned."),
+               autofixes(
+                       nil...))
+
+       // Getting the line number wrong is a strange programming error, and
+       // there does not need to be any code checking for this in the main code.
+       t.ExpectPanicMatches(
+               func() { test(lines("VAR=value"), 10, 3, "from", "to", nil, nil) },
+               `runtime error: index out of range.*`)
+
+       // It is a programming error to replace a string with itself, since that
+       // would produce confusing diagnostics.
+       t.ExpectAssert(
+               func() { test(lines("VAR=value"), 0, 4, "value", "value", nil, nil) })
+
+       // Getting the column number wrong may happen when a previous replacement
+       // has made the string shorter than before, therefore no panic in this case.
+       test(
+               lines(
+                       "VAR=value1 \\",
+                       "\tvalue2"),
+               0, 20, "?", "+=",
+               diagnostics(
+                       "NOTE: filename.mk:1--2: Should be appended instead of assigned."),
+               autofixes(
+                       nil...))
+}
+
 func (s *Suite) Test_SaveAutofixChanges(c *check.C) {
        t := s.Init(c)
 
@@ -325,7 +401,7 @@ func (s *Suite) Test_Autofix__multiple_f
        line := t.NewLine("filename", 1, "original")
 
        c.Check(line.autofix, check.IsNil)
-       c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n"))
+       t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n"))
 
        {
                fix := line.Autofix()
@@ -335,7 +411,7 @@ func (s *Suite) Test_Autofix__multiple_f
        }
 
        c.Check(line.autofix, check.NotNil)
-       c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lriginao\n"))
+       t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n", "lriginao\n"))
        t.CheckOutputLines(
                "AUTOFIX: filename:1: Replacing \"original\" with \"lriginao\".")
 
@@ -347,8 +423,8 @@ func (s *Suite) Test_Autofix__multiple_f
        }
 
        c.Check(line.autofix, check.NotNil)
-       c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lruginao\n"))
-       c.Check(line.raw[0].textnl, equals, "lruginao\n")
+       t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n", "lruginao\n"))
+       t.CheckEquals(line.raw[0].textnl, "lruginao\n")
        t.CheckOutputLines(
                "AUTOFIX: filename:1: Replacing \"ig\" with \"ug\".")
 
@@ -360,12 +436,12 @@ func (s *Suite) Test_Autofix__multiple_f
        }
 
        c.Check(line.autofix, check.NotNil)
-       c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "middle\n"))
-       c.Check(line.raw[0].textnl, equals, "middle\n")
+       t.CheckDeepEquals(line.raw, t.NewRawLines(1, "original\n", "middle\n"))
+       t.CheckEquals(line.raw[0].textnl, "middle\n")
        t.CheckOutputLines(
                "AUTOFIX: filename:1: Replacing \"lruginao\" with \"middle\".")
 
-       c.Check(line.raw[0].textnl, equals, "middle\n")
+       t.CheckEquals(line.raw[0].textnl, "middle\n")
        t.CheckOutputEmpty()
 
        {
@@ -375,7 +451,7 @@ func (s *Suite) Test_Autofix__multiple_f
                fix.Apply()
        }
 
-       c.Check(line.Autofix().RawText(), equals, "")
+       t.CheckEquals(line.Autofix().RawText(), "")
        t.CheckOutputLines(
                "AUTOFIX: filename:1: Deleting this line.")
 }
@@ -393,7 +469,7 @@ func (s *Suite) Test_Autofix_Explain__wi
 
        t.CheckOutputLines(
                "WARN: Makefile:74: Please write row instead of line.")
-       c.Check(G.Logger.explanationsAvailable, equals, true)
+       t.CheckEquals(G.Logger.explanationsAvailable, true)
 }
 
 func (s *Suite) Test_Autofix_Explain__default(c *check.C) {
@@ -413,7 +489,7 @@ func (s *Suite) Test_Autofix_Explain__de
                "",
                "\tExplanation",
                "")
-       c.Check(G.Logger.explanationsAvailable, equals, true)
+       t.CheckEquals(G.Logger.explanationsAvailable, true)
 }
 
 func (s *Suite) Test_Autofix_Explain__show_autofix(c *check.C) {
@@ -434,7 +510,7 @@ func (s *Suite) Test_Autofix_Explain__sh
                "",
                "\tExplanation",
                "")
-       c.Check(G.Logger.explanationsAvailable, equals, true)
+       t.CheckEquals(G.Logger.explanationsAvailable, true)
 }
 
 func (s *Suite) Test_Autofix_Explain__autofix(c *check.C) {
@@ -451,7 +527,7 @@ func (s *Suite) Test_Autofix_Explain__au
 
        t.CheckOutputLines(
                "AUTOFIX: Makefile:74: Replacing \"line\" with \"row\".")
-       c.Check(G.Logger.explanationsAvailable, equals, false) // Not necessary.
+       t.CheckEquals(G.Logger.explanationsAvailable, false) // Not necessary.
 }
 
 func (s *Suite) Test_Autofix_Explain__SilentAutofixFormat(c *check.C) {
@@ -491,7 +567,7 @@ func (s *Suite) Test_Autofix_Explain__si
                "\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")
+       t.CheckEquals(fix.RawText(), "Text\n")
 }
 
 func (s *Suite) Test_Autofix__show_autofix_and_source_continuation_line(c *check.C) {
@@ -516,7 +592,7 @@ func (s *Suite) Test_Autofix__show_autof
        // make some of the lines appear misaligned in the pkglint output although
        // they are correct in the Makefiles.
        t.CheckOutputLines(
-               "WARN: ~/Makefile:2--4: Using \"old\" is deprecated.",
+               "WARN: ~/Makefile:3: Using \"old\" is deprecated.",
                "AUTOFIX: ~/Makefile:3: Replacing \"old\" with \"new\".",
                "\t# before \\",
                "-\tThe old song \\",
@@ -768,7 +844,7 @@ func (s *Suite) Test_Autofix_Custom__in_
        t.CheckOutputLines(
                "WARN: Makefile:2: Please write in ALL-UPPERCASE.",
                "AUTOFIX: Makefile:2: Converting to uppercase")
-       c.Check(lines.Lines[1].Text, equals, "LINE2")
+       t.CheckEquals(lines.Lines[1].Text, "LINE2")
 
        t.SetUpCommandLine("--autofix")
 
@@ -776,7 +852,7 @@ func (s *Suite) Test_Autofix_Custom__in_
 
        t.CheckOutputLines(
                "AUTOFIX: Makefile:3: Converting to uppercase")
-       c.Check(lines.Lines[2].Text, equals, "LINE3")
+       t.CheckEquals(lines.Lines[2].Text, "LINE3")
 }
 
 // Since the diagnostic doesn't contain the string "few", nothing happens.
@@ -798,12 +874,12 @@ func (s *Suite) Test_Autofix_skip(c *che
        // None of the following actions has any effect because of the --only option above.
        fix.Replace("111", "___")
        fix.ReplaceAfter(" ", "222", "___")
+       fix.ReplaceAt(0, 0, "VAR", "NEW")
        fix.ReplaceRegex(`\d+`, "___", 1)
        fix.InsertBefore("before")
        fix.InsertAfter("after")
        fix.Delete()
        fix.Custom(func(showAutofix, autofix bool) {})
-       fix.Realign(mklines.mklines[0], 32)
 
        fix.Apply()
 
@@ -813,7 +889,7 @@ func (s *Suite) Test_Autofix_skip(c *che
        t.CheckFileLines("filename",
                "VAR=\t111 222 333 444 555 \\",
                "666")
-       c.Check(fix.RawText(), equals, ""+
+       t.CheckEquals(fix.RawText(), ""+
                "VAR=\t111 222 333 444 555 \\\n"+
                "666\n")
 }
@@ -984,7 +1060,7 @@ func (s *Suite) Test_Autofix_Apply__anyw
 
        mklines.SaveAutofixChanges()
 
-       t.Check(G.Logger.errors, equals, 0)
+       t.CheckEquals(G.Logger.errors, 0)
        t.CheckOutputEmpty()
 }
 
@@ -1059,11 +1135,11 @@ func (s *Suite) Test_Autofix_Apply__text
        t.CheckOutputLines(
                "AUTOFIX: filename.mk:123: Replacing \"value\" with \"new value\".")
 
-       t.Check(mkline.raw[0].textnl, equals, "VAR=\tnew value\n")
-       t.Check(mkline.raw[0].orignl, equals, "VAR=\tvalue\n")
-       t.Check(mkline.Text, equals, "VAR=\tnew value")
-       // FIXME: should be updated as well.
-       t.Check(mkline.Value(), equals, "value")
+       t.CheckEquals(mkline.raw[0].textnl, "VAR=\tnew value\n")
+       t.CheckEquals(mkline.raw[0].orignl, "VAR=\tvalue\n")
+       t.CheckEquals(mkline.Text, "VAR=\tnew value")
+       // TODO: should be updated as well.
+       t.CheckEquals(mkline.Value(), "value")
 }
 
 // After fixing part of a line, the whole line needs to be parsed again.
@@ -1083,82 +1159,11 @@ func (s *Suite) Test_Autofix_Apply__text
        t.CheckOutputLines(
                "AUTOFIX: filename.mk:123: Replacing \"value\" with \"new value\".")
 
-       t.Check(mkline.raw[0].textnl, equals, "VAR=\tnew value\n")
-       t.Check(mkline.raw[0].orignl, equals, "VAR=\tvalue\n")
-       t.Check(mkline.Text, equals, "VAR=\tnew value")
-       // FIXME: should be updated as well.
-       t.Check(mkline.Value(), equals, "value")
-}
-
-func (s *Suite) Test_Autofix_Realign__wrong_line_type(c *check.C) {
-       t := s.Init(c)
-
-       mklines := t.NewMkLines("file.mk",
-               MkCvsID,
-               ".if \\",
-               "${PKGSRC_RUN_TESTS}")
-
-       mkline := mklines.mklines[1]
-       fix := mkline.Autofix()
-
-       t.ExpectAssert(func() { fix.Realign(mkline, 16) })
-}
-
-func (s *Suite) Test_Autofix_Realign__short_continuation_line(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpCommandLine("--autofix")
-       mklines := t.SetUpFileMkLines("file.mk",
-               MkCvsID,
-               "BUILD_DIRS= \\",
-               "\tdir \\",
-               "")
-       mkline := mklines.mklines[1]
-       fix := mkline.Autofix()
-       fix.Warnf("Line should be realigned.")
-
-       // In this case realigning has no effect since the oldWidth == 8,
-       // which counts as "sufficiently intentional not to be modified".
-       fix.Realign(mkline, 16)
-
-       mklines.SaveAutofixChanges()
-
-       t.CheckOutputEmpty()
-       t.CheckFileLines("file.mk",
-               MkCvsID,
-               "BUILD_DIRS= \\",
-               "\tdir \\",
-               "")
-}
-
-func (s *Suite) Test_Autofix_Realign__multiline_indented_with_spaces(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpCommandLine("--autofix")
-       mklines := t.SetUpFileMkLines("file.mk",
-               MkCvsID,
-               "BUILD_DIRS= \\",
-               "\t        dir1 \\",
-               "\t\tdir2 \\",
-               "  ") // Trailing whitespace is not fixed by Autofix.Realign.
-
-       mkline := mklines.mklines[1]
-
-       fix := mkline.Autofix()
-       fix.Warnf("Warning.")
-       fix.Realign(mkline, 16)
-       fix.Apply()
-
-       mklines.SaveAutofixChanges()
-
-       t.CheckOutputLines(
-               "AUTOFIX: ~/file.mk:3: Replacing indentation \"\\t        \" with \"\\t\\t\".")
-       t.CheckFileLines("file.mk",
-               MkCvsID,
-               "BUILD_DIRS= \\",
-               "\t\tdir1 \\",
-               "\t\tdir2 \\",
-               "  ")
+       t.CheckEquals(mkline.raw[0].textnl, "VAR=\tnew value\n")
+       t.CheckEquals(mkline.raw[0].orignl, "VAR=\tvalue\n")
+       t.CheckEquals(mkline.Text, "VAR=\tnew value")
+       // TODO: should be updated as well.
+       t.CheckEquals(mkline.Value(), "value")
 }
 
 // Just for branch coverage.
Index: pkgsrc/pkgtools/pkglint/files/files_test.go
diff -u pkgsrc/pkgtools/pkglint/files/files_test.go:1.26 pkgsrc/pkgtools/pkglint/files/files_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/files_test.go:1.26    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/files_test.go Sun Jul 14 21:25:47 2019
@@ -5,18 +5,22 @@ import (
 )
 
 func (s *Suite) Test_convertToLogicalLines__no_continuation(c *check.C) {
+       t := s.Init(c)
+
        rawText := "" +
                "first line\n" +
                "second line\n"
 
        lines := convertToLogicalLines("filename", rawText, false)
 
-       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")
+       t.CheckEquals(lines.Len(), 2)
+       t.CheckEquals(lines.Lines[0].String(), "filename:1: first line")
+       t.CheckEquals(lines.Lines[1].String(), "filename:2: second line")
 }
 
 func (s *Suite) Test_convertToLogicalLines__continuation(c *check.C) {
+       t := s.Init(c)
+
        rawText := "" +
                "first line, \\\n" +
                "still first line\n" +
@@ -24,9 +28,9 @@ func (s *Suite) Test_convertToLogicalLin
 
        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")
+       t.CheckEquals(lines.Len(), 2)
+       t.CheckEquals(lines.Lines[0].String(), "filename:1--2: first line, still first line")
+       t.CheckEquals(lines.Lines[1].String(), "filename:3: second line")
 }
 
 func (s *Suite) Test_convertToLogicalLines__continuation_in_last_line(c *check.C) {
@@ -37,8 +41,8 @@ func (s *Suite) Test_convertToLogicalLin
 
        lines := convertToLogicalLines("filename", rawText, true)
 
-       c.Check(lines.Len(), equals, 1)
-       c.Check(lines.Lines[0].String(), equals, "filename:1: last line\\")
+       t.CheckEquals(lines.Len(), 1)
+       t.CheckEquals(lines.Lines[0].String(), "filename:1: last line\\")
        t.CheckOutputEmpty()
 }
 
@@ -77,7 +81,7 @@ func (s *Suite) Test_convertToLogicalLin
                texts = append(texts, line.Text)
        }
 
-       c.Check(texts, deepEquals, []string{
+       t.CheckDeepEquals(texts, []string{
                "# This is a comment",
                "",
                "# Multiline comment",
@@ -108,8 +112,8 @@ func (s *Suite) Test_nextLogicalLine__co
        mkline := mklines.mklines[0]
 
        // The leading comments are stripped from the continuation lines as well.
-       t.Check(mkline.Value(), equals, "continuation 1 \tcontinuation 2")
-       t.Check(mkline.VarassignComment(), equals, "")
+       t.CheckEquals(mkline.Value(), "continuation 1 \tcontinuation 2")
+       t.CheckEquals(mkline.VarassignComment(), "")
 }
 
 func (s *Suite) Test_convertToLogicalLines__missing_newline_at_eof(c *check.C) {
@@ -121,8 +125,8 @@ func (s *Suite) Test_convertToLogicalLin
 
        lines := convertToLogicalLines("DESCR", rawText, true)
 
-       c.Check(lines.Len(), equals, 2)
-       c.Check(lines.Lines[1].String(), equals, "DESCR:2: takes 2 lines")
+       t.CheckEquals(lines.Len(), 2)
+       t.CheckEquals(lines.Lines[1].String(), "DESCR:2: takes 2 lines")
        t.CheckOutputLines(
                "ERROR: DESCR:2: File must end with a newline.")
 }
@@ -135,8 +139,8 @@ func (s *Suite) Test_convertToLogicalLin
 
        lines := convertToLogicalLines("filename", rawText, true)
 
-       c.Check(lines.Len(), equals, 1)
-       c.Check(lines.Lines[0].String(), equals, "filename:1: last line\\")
+       t.CheckEquals(lines.Len(), 1)
+       t.CheckEquals(lines.Lines[0].String(), "filename:1: last line\\")
        t.CheckOutputLines(
                "ERROR: filename:1: File must end with a newline.")
 }
@@ -150,27 +154,29 @@ func (s *Suite) Test_convertToLogicalLin
 
        lines := convertToLogicalLines("filename", rawText, true)
 
-       c.Check(lines.Len(), equals, 1)
-       c.Check(lines.Lines[0].String(), equals, "filename:1: last line\\")
+       t.CheckEquals(lines.Len(), 1)
+       t.CheckEquals(lines.Lines[0].String(), "filename:1: last line\\")
        t.CheckOutputLines(
                ">\tlast line\\",
                "ERROR: filename:1: File must end with a newline.")
 }
 
 func (s *Suite) Test_matchContinuationLine(c *check.C) {
+       t := s.Init(c)
+
        leadingWhitespace, text, trailingWhitespace, continuation := matchContinuationLine("\n")
 
-       c.Check(leadingWhitespace, equals, "")
-       c.Check(text, equals, "")
-       c.Check(trailingWhitespace, equals, "")
-       c.Check(continuation, equals, "")
+       t.CheckEquals(leadingWhitespace, "")
+       t.CheckEquals(text, "")
+       t.CheckEquals(trailingWhitespace, "")
+       t.CheckEquals(continuation, "")
 
        leadingWhitespace, text, trailingWhitespace, continuation = matchContinuationLine("\tword   \\\n")
 
-       c.Check(leadingWhitespace, equals, "\t")
-       c.Check(text, equals, "word")
-       c.Check(trailingWhitespace, equals, "   ")
-       c.Check(continuation, equals, "\\")
+       t.CheckEquals(leadingWhitespace, "\t")
+       t.CheckEquals(text, "word")
+       t.CheckEquals(trailingWhitespace, "   ")
+       t.CheckEquals(continuation, "\\")
 }
 
 func (s *Suite) Test_Load(c *check.C) {
@@ -183,26 +189,26 @@ func (s *Suite) Test_Load(c *check.C) {
 
        t.Check(Load(nonexistent, 0), check.IsNil)
        t.Check(Load(empty, 0).Lines, check.HasLen, 0)
-       t.Check(Load(oneLiner, 0).Lines[0].Text, equals, "hello, world")
+       t.CheckEquals(Load(oneLiner, 0).Lines[0].Text, "hello, world")
 
        t.CheckOutputEmpty()
 
        t.Check(Load(nonexistent, LogErrors), check.IsNil)
        t.Check(Load(empty, LogErrors).Lines, check.HasLen, 0)
-       t.Check(Load(oneLiner, LogErrors).Lines[0].Text, equals, "hello, world")
+       t.CheckEquals(Load(oneLiner, LogErrors).Lines[0].Text, "hello, world")
 
        t.CheckOutputLines(
                "ERROR: ~/nonexistent: Cannot be read.")
 
        t.Check(Load(nonexistent, NotEmpty), check.IsNil)
        t.Check(Load(empty, NotEmpty), check.IsNil)
-       t.Check(Load(oneLiner, NotEmpty).Lines[0].Text, equals, "hello, world")
+       t.CheckEquals(Load(oneLiner, NotEmpty).Lines[0].Text, "hello, world")
 
        t.CheckOutputEmpty()
 
        t.Check(Load(nonexistent, NotEmpty|LogErrors), check.IsNil)
        t.Check(Load(empty, NotEmpty|LogErrors), check.IsNil)
-       t.Check(Load(oneLiner, NotEmpty|LogErrors).Lines[0].Text, equals, "hello, world")
+       t.CheckEquals(Load(oneLiner, NotEmpty|LogErrors).Lines[0].Text, "hello, world")
 
        t.CheckOutputLines(
                "ERROR: ~/nonexistent: Cannot be read.",
Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.26 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.27
--- pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.26   Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go        Sun Jul 14 21:25:47 2019
@@ -24,17 +24,17 @@ func (s *Suite) Test_Pkgsrc_loadMasterSi
 
        G.Pkgsrc.loadMasterSites()
 
-       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://example.org/distfiles/";], equals, "MASTER_SITE_A")
-       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://b.example.org/distfiles/";], equals, "MASTER_SITE_B")
-       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://b2.example.org/distfiles/";], equals, "MASTER_SITE_B")
-       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://a.example.org/distfiles/";], equals, "MASTER_SITE_A")
-       c.Check(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_A"], equals, "https://example.org/distfiles/";)
-       c.Check(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_B"], equals, "https://b.example.org/distfiles/";)
+       t.CheckEquals(G.Pkgsrc.MasterSiteURLToVar["https://example.org/distfiles/";], "MASTER_SITE_A")
+       t.CheckEquals(G.Pkgsrc.MasterSiteURLToVar["https://b.example.org/distfiles/";], "MASTER_SITE_B")
+       t.CheckEquals(G.Pkgsrc.MasterSiteURLToVar["https://b2.example.org/distfiles/";], "MASTER_SITE_B")
+       t.CheckEquals(G.Pkgsrc.MasterSiteURLToVar["https://a.example.org/distfiles/";], "MASTER_SITE_A")
+       t.CheckEquals(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_A"], "https://example.org/distfiles/";)
+       t.CheckEquals(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_B"], "https://b.example.org/distfiles/";)
 
        // Ignored entries:
-       c.Check(G.Pkgsrc.MasterSiteURLToVar["${other}"], equals, "")
-       c.Check(G.Pkgsrc.MasterSiteURLToVar["https://backup.example.org/";], equals, "")
-       c.Check(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_BACKUP"], equals, "")
+       t.CheckEquals(G.Pkgsrc.MasterSiteURLToVar["${other}"], "")
+       t.CheckEquals(G.Pkgsrc.MasterSiteURLToVar["https://backup.example.org/";], "")
+       t.CheckEquals(G.Pkgsrc.MasterSiteVarToURL["MASTER_SITE_BACKUP"], "")
 }
 
 func (s *Suite) Test_Pkgsrc_parseSuggestedUpdates(c *check.C) {
@@ -53,7 +53,7 @@ func (s *Suite) Test_Pkgsrc_parseSuggest
 
        todo := G.Pkgsrc.parseSuggestedUpdates(lines)
 
-       c.Check(todo, check.DeepEquals, []SuggestedUpdate{
+       t.CheckDeepEquals(todo, []SuggestedUpdate{
                {lines.Lines[5].Location, "CSP", "0.34", ""},
                {lines.Lines[6].Location, "freeciv-client", "2.5.0", "(urgent)"}})
 }
@@ -90,7 +90,7 @@ func (s *Suite) Test_Pkgsrc_checkTopleve
                "WARN: ~/category/package2/Makefile:11: License file ~/licenses/missing does not exist.",
                "WARN: ~/licenses/gnu-gpl-v2: This license seems to be unused.", // Added by Tester.SetUpPkgsrc
                "WARN: ~/licenses/gnu-gpl-v3: This license seems to be unused.",
-               "0 errors and 3 warnings found.")
+               "3 warnings found.")
 }
 
 func (s *Suite) Test_Pkgsrc_loadUntypedVars(c *check.C) {
@@ -236,8 +236,8 @@ func (s *Suite) Test_Pkgsrc_loadTools__B
 
        G.Check(pkg)
 
-       c.Check(G.Pkgsrc.IsBuildDef("PKG_SYSCONFDIR"), equals, true)
-       c.Check(G.Pkgsrc.IsBuildDef("VARBASE"), equals, false)
+       t.CheckEquals(G.Pkgsrc.IsBuildDef("PKG_SYSCONFDIR"), true)
+       t.CheckEquals(G.Pkgsrc.IsBuildDef("VARBASE"), false)
 
        t.CheckOutputLines(
                "WARN: ~/category/package/Makefile:21: " +
@@ -256,14 +256,13 @@ func (s *Suite) Test_Pkgsrc_loadDocChang
                "\tMoved pkgpath to category/new-pkg [author 2018-03-01]")
        t.FinishSetUp()
 
-       t.Check(G.Pkgsrc.LastChange["pkgpath"].Action, equals, Moved)
+       t.CheckEquals(G.Pkgsrc.LastChange["pkgpath"].Action, Moved)
 }
 
 func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze(c *check.C) {
        t := s.Init(c)
 
        t.SetUpCommandLine("-Wall", "--source")
-       t.SetUpPkgsrc()
        t.CreateFileLines("doc/CHANGES-2019",
                CvsID,
                "",
@@ -273,7 +272,9 @@ func (s *Suite) Test_Pkgsrc_checkRemoved
                "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
                "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
                "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
-               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]")
+               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
+               "\tUpdated category/still-there to 1.0 [updater 2019-07-04]")
+       t.SetUpPackage("category/still-there")
        t.FinishSetUp()
 
        // It doesn't matter whether the last visible package change was before
@@ -298,6 +299,31 @@ func (s *Suite) Test_Pkgsrc_checkRemoved
                        "must either exist or be marked as removed.")
 }
 
+func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze__wip(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("wip/package")
+       t.CreateFileLines("doc/CHANGES-2019",
+               CvsID,
+               "",
+               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
+               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
+               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
+               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
+               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
+               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
+               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]")
+
+       t.Main("-Wall", "--source", "wip/package")
+
+       // Since the first argument is in pkgsrc-wip, the check for doc/CHANGES
+       // is skipped. It may well be that a pkgsrc-wip developer doesn't have
+       // write access to main pkgsrc, and therefore cannot fix doc/CHANGES.
+
+       t.CheckOutputLines(
+               "Looks fine.")
+}
+
 func (s *Suite) Test_Pkgsrc_loadDocChanges__not_found(c *check.C) {
        t := s.Init(c)
 
@@ -327,24 +353,29 @@ func (s *Suite) Test_Pkgsrc_loadDocChang
                "\ttoo few fields",
                "\ttoo many many many many many fields",
                "\tmissing brackets around author",
-               "\tAdded another [new package]")
+               "\tAdded another [new package]",
+               "",
+               "\tmk/bsd.pkg.mk: freeze ended for branch pkgsrc-2019Q2", // missing date
+               "\tmk/bsd.pkg.mk: freeze ended for branch pkgsrc-2019Q2 [thawer 2019-07-01]",
+               "",
+               "Normal paragraph.")
 
        changes := G.Pkgsrc.loadDocChangesFromFile(t.File("doc/CHANGES-2018"))
 
-       c.Assert(len(changes), equals, 7)
-       c.Check(*changes[0], equals, Change{changes[0].Location,
+       c.Assert(changes, check.HasLen, 7) // TODO: refactor to CheckDeepEquals
+       t.CheckEquals(*changes[0], Change{changes[0].Location,
                Added, "category/package", "1.0", "author1", "2015-01-01"})
-       c.Check(*changes[1], equals, Change{changes[1].Location,
+       t.CheckEquals(*changes[1], Change{changes[1].Location,
                Updated, "category/package", "1.5", "author2", "2018-01-02"})
-       c.Check(*changes[2], equals, Change{changes[2].Location,
+       t.CheckEquals(*changes[2], Change{changes[2].Location,
                Renamed, "category/package", "category/pkg", "author3", "2018-01-03"})
-       c.Check(*changes[3], equals, Change{changes[3].Location,
+       t.CheckEquals(*changes[3], Change{changes[3].Location,
                Moved, "category/package", "other/package", "author4", "2018-01-04"})
-       c.Check(*changes[4], equals, Change{changes[4].Location,
+       t.CheckEquals(*changes[4], Change{changes[4].Location,
                Removed, "category/package", "", "author5", "2018-01-09"})
-       c.Check(*changes[5], equals, Change{changes[5].Location,
+       t.CheckEquals(*changes[5], Change{changes[5].Location,
                Removed, "category/package", "category/package2", "author6", "2018-01-06"})
-       c.Check(*changes[6], equals, Change{changes[6].Location,
+       t.CheckEquals(*changes[6], Change{changes[6].Location,
                Downgraded, "category/package", "1.2", "author7", "2018-01-07"})
 
        t.CheckOutputLines(
@@ -402,7 +433,7 @@ func (s *Suite) Test_Pkgsrc_loadDocChang
        t.CheckOutputLines(
                "WARN: ~/doc/CHANGES-2018:5: Package changes should be indented using a single tab, not \"        \".",
                "WARN: ~/doc/CHANGES-2018:6: Package changes should be indented using a single tab, not \"    \\t\".",
-               "0 errors and 2 warnings found.",
+               "2 warnings found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -642,11 +673,11 @@ func (s *Suite) Test_Pkgsrc__caching(c *
 
        latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest, equals, "../../lang/python27")
+       t.CheckEquals(latest, "../../lang/python27")
 
        cached := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(cached, equals, "../../lang/python27")
+       t.CheckEquals(cached, "../../lang/python27")
 }
 
 func (s *Suite) Test_Pkgsrc_Latest__multiple_candidates(c *check.C) {
@@ -658,7 +689,7 @@ func (s *Suite) Test_Pkgsrc_Latest__mult
 
        latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest, equals, "../../lang/python35")
+       t.CheckEquals(latest, "../../lang/python35")
 }
 
 func (s *Suite) Test_Pkgsrc_Latest__not_found(c *check.C) {
@@ -668,7 +699,7 @@ func (s *Suite) Test_Pkgsrc_Latest__not_
 
        latest := G.Pkgsrc.Latest("lang", `^python[0-9]+$`, "../../lang/$0")
 
-       c.Check(latest, equals, "")
+       t.CheckEquals(latest, "")
 
        t.CheckOutputLines(
                "ERROR: Cannot find package versions of \"^python[0-9]+$\" in \"~/lang\".")
@@ -688,7 +719,7 @@ func (s *Suite) Test_Pkgsrc_ListVersions
 
        versions := G.Pkgsrc.ListVersions("databases", `^postgresql[0-9]+$`, "$0", true)
 
-       c.Check(versions, check.DeepEquals, []string{
+       t.CheckDeepEquals(versions, []string{
                "postgresql95",
                "postgresql97",
                "postgresql10",
@@ -696,6 +727,8 @@ func (s *Suite) Test_Pkgsrc_ListVersions
 }
 
 func (s *Suite) Test_Pkgsrc_ListVersions__ensure_transitive(c *check.C) {
+       t := s.Init(c)
+
        names := []string{
                "base",
                "base0",
@@ -725,10 +758,9 @@ func (s *Suite) Test_Pkgsrc_ListVersions
                actual := less(names[i], names[j])
                expected := i < j
                if actual != expected {
-                       c.Check(
-                               []interface{}{names[i], ifelseStr(actual, "<", "!<"), names[j]},
-                               check.DeepEquals,
-                               []interface{}{names[i], ifelseStr(expected, "<", "!<"), names[j]})
+                       t.CheckDeepEquals(
+                               []interface{}{names[i], condStr(actual, "<", "!<"), names[j]},
+                               []interface{}{names[i], condStr(expected, "<", "!<"), names[j]})
                }
        }
 
@@ -749,7 +781,7 @@ func (s *Suite) Test_Pkgsrc_ListVersions
 
        versions := G.Pkgsrc.ListVersions("emulators", `^suse_(\d+).*$`, "$1", true)
 
-       c.Check(versions, deepEquals, []string{
+       t.CheckDeepEquals(versions, []string{
                "131",
                "131",
                "131",
@@ -766,7 +798,7 @@ func (s *Suite) Test_Pkgsrc_ListVersions
 
        versionsUpTo2 := G.Pkgsrc.ListVersions("lang", `^go[0-9]+$`, "$0", true)
 
-       c.Check(versionsUpTo2, deepEquals, []string{"go14", "go19", "go111", "go2"})
+       t.CheckDeepEquals(versionsUpTo2, []string{"go14", "go19", "go111", "go2"})
 
        t.CreateFileLines("lang/go37/Makefile")
 
@@ -777,7 +809,7 @@ func (s *Suite) Test_Pkgsrc_ListVersions
 
        versionsUpTo37 := G.Pkgsrc.ListVersions("lang", `^go[0-9]+$`, "$0", true)
 
-       c.Check(versionsUpTo37, deepEquals, []string{"go14", "go19", "go111", "go2", "go37"})
+       t.CheckDeepEquals(versionsUpTo37, []string{"go14", "go19", "go111", "go2", "go37"})
 }
 
 func (s *Suite) Test_Pkgsrc_ListVersions__invalid_argument(c *check.C) {
@@ -843,7 +875,7 @@ func (s *Suite) Test_Pkgsrc_VariableType
                        c.Check(actualType, check.IsNil)
                } else {
                        if c.Check(actualType, check.NotNil) {
-                               c.Check(actualType.String(), equals, vartype)
+                               t.CheckEquals(actualType.String(), vartype)
                        }
                }
        }
@@ -872,12 +904,12 @@ func (s *Suite) Test_Pkgsrc_VariableType
        t1 := G.Pkgsrc.VariableType(nil, "FONT_DIRS")
 
        c.Assert(t1, check.NotNil)
-       c.Check(t1.String(), equals, "PathMask (list, guessed)")
+       t.CheckEquals(t1.String(), "PathMask (list, guessed)")
 
        t2 := G.Pkgsrc.VariableType(nil, "FONT_DIRS.ttf")
 
        c.Assert(t2, check.NotNil)
-       c.Check(t2.String(), equals, "PathMask (list, guessed)")
+       t.CheckEquals(t2.String(), "PathMask (list, guessed)")
 }
 
 // Guessing the variable type also works for variables that are
@@ -905,15 +937,15 @@ func (s *Suite) Test_Pkgsrc_VariableType
        t.Main("-Wall", pkg)
 
        if typ := G.Pkgsrc.VariableType(nil, "PKGSRC_MAKE_ENV"); c.Check(typ, check.NotNil) {
-               c.Check(typ.String(), equals, "ShellWord (list, guessed)")
+               t.CheckEquals(typ.String(), "ShellWord (list, guessed)")
        }
 
        if typ := G.Pkgsrc.VariableType(nil, "CPPPATH"); c.Check(typ, check.NotNil) {
-               c.Check(typ.String(), equals, "Pathlist (guessed)")
+               t.CheckEquals(typ.String(), "Pathlist (guessed)")
        }
 
        if typ := G.Pkgsrc.VariableType(nil, "OSNAME.Other"); c.Check(typ, check.NotNil) {
-               c.Check(typ.String(), equals, "Unknown")
+               t.CheckEquals(typ.String(), "Unknown")
        }
 
        // No warnings about "defined but not used" or "used but not defined"
@@ -923,7 +955,7 @@ func (s *Suite) Test_Pkgsrc_VariableType
        t.CheckOutputLines(
                "WARN: ~/category/package/Makefile:21: PKGSRC_UNKNOWN_ENV is defined but not used.",
                "WARN: ~/category/package/Makefile:21: ABCPATH is used but not defined.",
-               "0 errors and 2 warnings found.",
+               "2 warnings found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -940,8 +972,8 @@ func (s *Suite) Test_Pkgsrc_guessVariabl
        mklines.Check()
 
        vartype := G.Pkgsrc.VariableType(mklines, "MY_CHECK_SKIP")
-       t.Check(vartype.Guessed(), equals, true)
-       t.Check(vartype.EffectivePermissions("filename.mk"), equals, aclpAllRuntime)
+       t.CheckEquals(vartype.Guessed(), true)
+       t.CheckEquals(vartype.EffectivePermissions("filename.mk"), aclpAllRuntime)
 
        // The permissions for MY_CHECK_SKIP say aclpAllRuntime, which excludes
        // aclpUseLoadtime. Therefore there should be a warning about the VarUse in
@@ -964,7 +996,7 @@ func (s *Suite) Test_Pkgsrc__frozen(c *c
                "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2018Q2 branch [freezer 2018-03-25]")
        t.FinishSetUp()
 
-       t.Check(G.Pkgsrc.LastFreezeStart, equals, "2018-03-25")
+       t.CheckEquals(G.Pkgsrc.LastFreezeStart, "2018-03-25")
 }
 
 func (s *Suite) Test_Pkgsrc__not_frozen(c *check.C) {
@@ -976,8 +1008,8 @@ func (s *Suite) Test_Pkgsrc__not_frozen(
                "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2018Q2 branch [freezer 2018-03-27]")
        t.FinishSetUp()
 
-       t.Check(G.Pkgsrc.LastFreezeStart, equals, "2018-03-25")
-       t.Check(G.Pkgsrc.LastFreezeEnd, equals, "2018-03-27")
+       t.CheckEquals(G.Pkgsrc.LastFreezeStart, "2018-03-25")
+       t.CheckEquals(G.Pkgsrc.LastFreezeEnd, "2018-03-27")
 }
 
 func (s *Suite) Test_Pkgsrc__frozen_with_typo(c *check.C) {
@@ -989,7 +1021,7 @@ func (s *Suite) Test_Pkgsrc__frozen_with
                "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2018Q2 branch [freezer 2018-03-25")
        t.FinishSetUp()
 
-       t.Check(G.Pkgsrc.LastFreezeStart, equals, "")
+       t.CheckEquals(G.Pkgsrc.LastFreezeStart, "")
 }
 
 func (s *Suite) Test_Change_Version(c *check.C) {
@@ -1001,9 +1033,9 @@ func (s *Suite) Test_Change_Version(c *c
        downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
        removed := Change{loc, Removed, "category/path", "1.0", "author", "2019-01-01"}
 
-       t.Check(added.Version(), equals, "1.0")
-       t.Check(updated.Version(), equals, "1.0")
-       t.Check(downgraded.Version(), equals, "1.0")
+       t.CheckEquals(added.Version(), "1.0")
+       t.CheckEquals(updated.Version(), "1.0")
+       t.CheckEquals(downgraded.Version(), "1.0")
        t.ExpectAssert(func() { removed.Version() })
 }
 
@@ -1015,8 +1047,8 @@ func (s *Suite) Test_Change_Target(c *ch
        moved := Change{loc, Moved, "category/path", "category/other", "author", "2019-01-01"}
        downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
 
-       t.Check(renamed.Target(), equals, "category/other")
-       t.Check(moved.Target(), equals, "category/other")
+       t.CheckEquals(renamed.Target(), "category/other")
+       t.CheckEquals(moved.Target(), "category/other")
        t.ExpectAssert(func() { downgraded.Target() })
 }
 
@@ -1028,16 +1060,41 @@ func (s *Suite) Test_Change_Successor(c 
        removedSucc := Change{loc, Removed, "category/path", "category/successor", "author", "2019-01-01"}
        downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
 
-       t.Check(removed.Successor(), equals, "")
-       t.Check(removedSucc.Successor(), equals, "category/successor")
+       t.CheckEquals(removed.Successor(), "")
+       t.CheckEquals(removedSucc.Successor(), "category/successor")
        t.ExpectAssert(func() { downgraded.Successor() })
 }
 
+func (s *Suite) Test_Change_Above(c *check.C) {
+       t := s.Init(c)
+
+       var changes = []*Change{
+               {Location{"", 1, 1}, 0, "", "", "", "2011-07-01"},
+               {Location{"", 2, 2}, 0, "", "", "", "2011-07-01"},
+               {Location{"", 1, 1}, 0, "", "", "", "2011-07-02"}}
+
+       test := func(i int, chi *Change, j int, chj *Change) {
+               actual := chi.Above(chj)
+               expected := i < j
+               if actual != expected {
+                       t.CheckDeepEquals(
+                               []interface{}{i, *chi, j, *chj, actual},
+                               []interface{}{i, *chi, j, *chj, expected})
+               }
+       }
+
+       for i, chi := range changes {
+               for j, chj := range changes {
+                       test(i, chi, j, chj)
+               }
+       }
+}
+
 func (s *Suite) Test_ChangeAction_String(c *check.C) {
        t := s.Init(c)
 
-       t.Check(Added.String(), equals, "Added")
-       t.Check(Removed.String(), equals, "Removed")
+       t.CheckEquals(Added.String(), "Added")
+       t.CheckEquals(Removed.String(), "Removed")
 }
 
 func (s *Suite) Test_Pkgsrc_ReadDir(c *check.C) {
@@ -1057,5 +1114,5 @@ func (s *Suite) Test_Pkgsrc_ReadDir(c *c
                names = append(names, info.Name())
        }
 
-       t.Check(names, deepEquals, []string{"aaa-subdir", "file", "subdir"})
+       t.CheckDeepEquals(names, []string{"aaa-subdir", "file", "subdir"})
 }
Index: pkgsrc/pkgtools/pkglint/files/substcontext.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext.go:1.26 pkgsrc/pkgtools/pkglint/files/substcontext.go:1.27
--- pkgsrc/pkgtools/pkglint/files/substcontext.go:1.26  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/substcontext.go       Sun Jul 14 21:25:47 2019
@@ -258,8 +258,8 @@ func (ctx *SubstContext) suggestSubstVar
 
                varop := sprintf("SUBST_VARS.%s%s%s",
                        ctx.id,
-                       ifelseStr(hasSuffix(ctx.id, "+"), " ", ""),
-                       ifelseStr(ctx.curr.seenVars, "+=", "="))
+                       condStr(hasSuffix(ctx.id, "+"), " ", ""),
+                       condStr(ctx.curr.seenVars, "+=", "="))
 
                fix := mkline.Autofix()
                fix.Notef("The substitution command %q can be replaced with \"%s %s\".",
@@ -280,7 +280,7 @@ func (ctx *SubstContext) suggestSubstVar
 // extractVarname extracts the variable name from a sed command of the form
 // s,@VARNAME@,${VARNAME}, and some related variants thereof.
 func (*SubstContext) extractVarname(token string) string {
-       parser := NewMkParser(nil, token, false)
+       parser := NewMkParser(nil, token)
        lexer := parser.lexer
        if !lexer.SkipByte('s') {
                return ""

Index: pkgsrc/pkgtools/pkglint/files/buildlink3.go
diff -u pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.23 pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.24
--- pkgsrc/pkgtools/pkglint/files/buildlink3.go:1.23    Sun Jun 30 20:56:18 2019
+++ pkgsrc/pkgtools/pkglint/files/buildlink3.go Sun Jul 14 21:25:47 2019
@@ -188,7 +188,7 @@ func (ck *Buildlink3Checker) checkVarass
 
        if varname == "BUILDLINK_ABI_DEPENDS."+pkgbase {
                ck.abiLine = mkline
-               parser := NewMkParser(nil, value, false)
+               parser := NewMkParser(nil, value)
                if dp := parser.Dependency(); dp != nil && parser.EOF() {
                        ck.abi = dp
                }
@@ -197,7 +197,7 @@ func (ck *Buildlink3Checker) checkVarass
 
        if varname == "BUILDLINK_API_DEPENDS."+pkgbase {
                ck.apiLine = mkline
-               parser := NewMkParser(nil, value, false)
+               parser := NewMkParser(nil, value)
                if dp := parser.Dependency(); dp != nil && parser.EOF() {
                        ck.api = dp
                }
Index: pkgsrc/pkgtools/pkglint/files/category_test.go
diff -u pkgsrc/pkgtools/pkglint/files/category_test.go:1.23 pkgsrc/pkgtools/pkglint/files/category_test.go:1.24
--- pkgsrc/pkgtools/pkglint/files/category_test.go:1.23 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/category_test.go      Sun Jul 14 21:25:47 2019
@@ -208,18 +208,12 @@ func (s *Suite) Test_CheckdirCategory__r
        // It is only removed in Pkglint.Main, therefore it stays there even
        // after the call to CheckdirCategory. This is a bit unrealistic,
        // but close enough for this test.
-       t.Check(
-               G.Todo,
-               deepEquals,
-               []string{"."})
+       t.CheckDeepEquals(G.Todo, []string{"."})
 
        CheckdirCategory(".")
 
        t.CheckOutputEmpty()
-       t.Check(
-               G.Todo,
-               deepEquals,
-               []string{"./package", "."})
+       t.CheckDeepEquals(G.Todo, []string{"./package", "."})
 }
 
 // Ensures that a directory in the file system can be added at the very

Index: pkgsrc/pkgtools/pkglint/files/category.go
diff -u pkgsrc/pkgtools/pkglint/files/category.go:1.20 pkgsrc/pkgtools/pkglint/files/category.go:1.21
--- pkgsrc/pkgtools/pkglint/files/category.go:1.20      Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/category.go   Sun Jul 14 21:25:47 2019
@@ -64,7 +64,7 @@ func CheckdirCategory(dir string) {
        for !mlex.EOF() {
                mkline := mlex.CurrentMkLine()
 
-               if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) && mkline.Varname() == "SUBDIR" {
+               if (mkline.IsVarassignMaybeCommented()) && mkline.Varname() == "SUBDIR" {
                        mlex.Skip()
 
                        name := mkline.Value()
Index: pkgsrc/pkgtools/pkglint/files/vartype_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.20 pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.21
--- pkgsrc/pkgtools/pkglint/files/vartype_test.go:1.20  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/vartype_test.go       Sun Jul 14 21:25:47 2019
@@ -10,20 +10,21 @@ func (s *Suite) Test_Vartype_EffectivePe
        t.SetUpVartypes()
 
        if typ := G.Pkgsrc.vartypes.Canon("PREFIX"); c.Check(typ, check.NotNil) {
-               c.Check(typ.basicType.name, equals, "Pathname")
-               c.Check(typ.aclEntries, deepEquals, []ACLEntry{NewACLEntry("*", aclpUse)})
-               c.Check(typ.EffectivePermissions("Makefile"), equals, aclpUse)
-               c.Check(typ.EffectivePermissions("buildlink3.mk"), equals, aclpUse)
+               t.CheckEquals(typ.basicType.name, "Pathname")
+               t.CheckDeepEquals(typ.aclEntries, []ACLEntry{NewACLEntry("*", aclpUse)})
+               t.CheckEquals(typ.EffectivePermissions("Makefile"), aclpUse)
+               t.CheckEquals(typ.EffectivePermissions("buildlink3.mk"), aclpUse)
        }
 
        if typ := G.Pkgsrc.vartypes.Canon("EXTRACT_OPTS"); c.Check(typ, check.NotNil) {
-               c.Check(typ.basicType.name, equals, "ShellWord")
-               c.Check(typ.EffectivePermissions("Makefile"), equals, aclpAllWrite|aclpUse)
-               c.Check(typ.EffectivePermissions("options.mk"), equals, aclpAllWrite|aclpUse)
+               t.CheckEquals(typ.basicType.name, "ShellWord")
+               t.CheckEquals(typ.EffectivePermissions("Makefile"), aclpAllWrite|aclpUse)
+               t.CheckEquals(typ.EffectivePermissions("options.mk"), aclpAllWrite|aclpUse)
        }
 }
 
 func (s *Suite) Test_Vartype_AlternativeFiles(c *check.C) {
+       t := s.Init(c)
 
        // test generates the files description for the "set" permission.
        test := func(rules []string, alternatives string) {
@@ -32,7 +33,7 @@ func (s *Suite) Test_Vartype_Alternative
 
                alternativeFiles := vartype.AlternativeFiles(aclpSet)
 
-               c.Check(alternativeFiles, equals, alternatives)
+               t.CheckEquals(alternativeFiles, alternatives)
        }
 
        // rules parses the given permission rules.
@@ -122,42 +123,50 @@ func (s *Suite) Test_Vartype_String(c *c
        t.SetUpVartypes()
 
        vartype := G.Pkgsrc.VariableType(nil, "PKG_DEBUG_LEVEL")
-       t.Check(vartype.String(), equals, "Integer (command-line-provided)")
+       t.CheckEquals(vartype.String(), "Integer (command-line-provided)")
 }
 
 func (s *Suite) Test_BasicType_HasEnum(c *check.C) {
+       t := s.Init(c)
+
        vc := enum("start middle end")
 
-       c.Check(vc.HasEnum("start"), equals, true)
-       c.Check(vc.HasEnum("middle"), equals, true)
-       c.Check(vc.HasEnum("end"), equals, true)
-
-       c.Check(vc.HasEnum("star"), equals, false)
-       c.Check(vc.HasEnum("mid"), equals, false)
-       c.Check(vc.HasEnum("nd"), equals, false)
-       c.Check(vc.HasEnum("start middle"), equals, false)
+       t.CheckEquals(vc.HasEnum("start"), true)
+       t.CheckEquals(vc.HasEnum("middle"), true)
+       t.CheckEquals(vc.HasEnum("end"), true)
+
+       t.CheckEquals(vc.HasEnum("star"), false)
+       t.CheckEquals(vc.HasEnum("mid"), false)
+       t.CheckEquals(vc.HasEnum("nd"), false)
+       t.CheckEquals(vc.HasEnum("start middle"), false)
 }
 
 func (s *Suite) Test_ACLPermissions_Contains(c *check.C) {
+       t := s.Init(c)
+
        perms := aclpAllRuntime
 
-       c.Check(perms.Contains(aclpAllRuntime), equals, true)
-       c.Check(perms.Contains(aclpUse), equals, true)
-       c.Check(perms.Contains(aclpUseLoadtime), equals, false)
+       t.CheckEquals(perms.Contains(aclpAllRuntime), true)
+       t.CheckEquals(perms.Contains(aclpUse), true)
+       t.CheckEquals(perms.Contains(aclpUseLoadtime), false)
 }
 
 func (s *Suite) Test_ACLPermissions_String(c *check.C) {
-       c.Check(ACLPermissions(0).String(), equals, "none")
-       c.Check(aclpAll.String(), equals, "set, set-default, append, use-loadtime, use")
+       t := s.Init(c)
+
+       t.CheckEquals(ACLPermissions(0).String(), "none")
+       t.CheckEquals(aclpAll.String(), "set, set-default, append, use-loadtime, use")
 }
 
 func (s *Suite) Test_ACLPermissions_HumanString(c *check.C) {
+       t := s.Init(c)
 
-       c.Check(ACLPermissions(0).HumanString(),
-               equals, "") // Doesn't happen in practice
+       // Doesn't happen in practice
+       t.CheckEquals(ACLPermissions(0).HumanString(), "")
 
-       c.Check(aclpAll.HumanString(),
-               equals, "set, given a default value, appended to, used at load time, or used")
+       t.CheckEquals(
+               aclpAll.HumanString(),
+               "set, given a default value, appended to, used at load time, or used")
 }
 
 func (s *Suite) Test_Vartype_MayBeAppendedTo(c *check.C) {
@@ -165,8 +174,8 @@ func (s *Suite) Test_Vartype_MayBeAppend
 
        t.SetUpVartypes()
 
-       c.Check(G.Pkgsrc.VariableType(nil, "COMMENT").MayBeAppendedTo(), equals, true)
-       c.Check(G.Pkgsrc.VariableType(nil, "DEPENDS").MayBeAppendedTo(), equals, true)
-       c.Check(G.Pkgsrc.VariableType(nil, "PKG_FAIL_REASON").MayBeAppendedTo(), equals, true)
-       c.Check(G.Pkgsrc.VariableType(nil, "CONF_FILES").MayBeAppendedTo(), equals, true)
+       t.CheckEquals(G.Pkgsrc.VariableType(nil, "COMMENT").MayBeAppendedTo(), true)
+       t.CheckEquals(G.Pkgsrc.VariableType(nil, "DEPENDS").MayBeAppendedTo(), true)
+       t.CheckEquals(G.Pkgsrc.VariableType(nil, "PKG_FAIL_REASON").MayBeAppendedTo(), true)
+       t.CheckEquals(G.Pkgsrc.VariableType(nil, "CONF_FILES").MayBeAppendedTo(), true)
 }

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.44 pkgsrc/pkgtools/pkglint/files/check_test.go:1.45
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.44    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sun Jul 14 21:25:47 2019
@@ -16,9 +16,6 @@ import (
        "gopkg.in/check.v1"
 )
 
-var equals = check.Equals
-var deepEquals = check.DeepEquals
-
 const CvsID = "$" + "NetBSD$"
 const MkCvsID = "# $" + "NetBSD$"
 const PlistCvsID = "@comment $" + "NetBSD$"
@@ -104,7 +101,7 @@ func (s *Suite) TearDownTest(c *check.C)
                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, "\t%q%s\n", line, condStr(i == len(lines)-1, ")", ","))
                }
                _, _ = fmt.Fprintf(&msg, "\n")
                _, _ = os.Stderr.WriteString(msg.String())
@@ -705,6 +702,14 @@ func (t *Tester) Check(obj interface{}, 
        return t.c.Check(obj, checker, args...)
 }
 
+func (t *Tester) CheckEquals(actual interface{}, expected interface{}) bool {
+       return t.c.Check(actual, check.Equals, expected)
+}
+
+func (t *Tester) CheckDeepEquals(actual interface{}, expected interface{}) bool {
+       return t.c.Check(actual, check.DeepEquals, expected)
+}
+
 func (t *Tester) Errorf(format string, args ...interface{}) {
        _, _ = fmt.Fprintf(os.Stderr, "In %s: %s\n", t.testName, sprintf(format, args...))
 }
@@ -756,16 +761,22 @@ func (t *Tester) ExpectFatalMatches(acti
 }
 
 // ExpectPanic runs the given action and expects that this action calls
-// assertf or uses some other way to panic.
+// assert or assertf, or uses some other way to panic.
 //
 // Usage:
 //  t.ExpectPanic(
 //      func() { /* do something that panics */ },
-//      "FATAL: ~/Makefile:1: Must not be empty")
+//      "runtime error: path not found")
 func (t *Tester) ExpectPanic(action func(), expectedMessage string) {
        t.Check(action, check.Panics, expectedMessage)
 }
 
+// ExpectPanicMatches runs the given action and expects that this action
+// calls assert or assertf, or uses some other way to panic.
+func (t *Tester) ExpectPanicMatches(action func(), expectedMessage string) {
+       t.Check(action, check.PanicMatches, expectedMessage)
+}
+
 // ExpectAssert runs the given action and expects that this action calls assert.
 //
 // Usage:
@@ -918,7 +929,7 @@ func (t *Tester) CheckOutputLinesMatchin
                        actualLines = append(actualLines, line)
                }
        }
-       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(expectedLines))
+       t.CheckDeepEquals(emptyToNil(actualLines), emptyToNil(expectedLines))
 }
 
 // CheckOutputLinesIgnoreSpace checks that the output up to now equals the given lines.
@@ -937,7 +948,7 @@ func (t *Tester) CheckOutputLinesIgnoreS
        _ = t.Output() // Just to consume the output
 
        actual, expected := t.compareOutputIgnoreSpace(rawOutput, expectedLines, t.tmpdir)
-       t.Check(actual, deepEquals, expected)
+       t.CheckDeepEquals(actual, expected)
 }
 
 func (t *Tester) compareOutputIgnoreSpace(rawOutput string, expectedLines []string, tmpdir string) ([]string, []string) {
@@ -976,7 +987,7 @@ func (s *Suite) Test_Tester_compareOutpu
        lines := func(lines ...string) []string { return lines }
        test := func(rawOutput string, expectedLines []string, tmpdir string, eq bool) {
                actual, expected := t.compareOutputIgnoreSpace(rawOutput, expectedLines, tmpdir)
-               t.Check(actual == nil && expected == nil, equals, eq)
+               t.CheckEquals(actual == nil && expected == nil, eq)
        }
 
        test("", lines(), "/tmp", true)
@@ -1029,7 +1040,7 @@ func (t *Tester) CheckOutputMatches(expe
                }
        }
 
-       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(patterns))
+       t.CheckDeepEquals(emptyToNil(actualLines), emptyToNil(patterns))
 }
 
 // CheckOutput checks that the output up to now equals the given lines.
@@ -1045,7 +1056,7 @@ func (t *Tester) CheckOutput(expectedLin
        output := t.Output()
        actualLines := strings.Split(output, "\n")
        actualLines = actualLines[:len(actualLines)-1]
-       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(expectedLines))
+       t.CheckDeepEquals(emptyToNil(actualLines), emptyToNil(expectedLines))
 }
 
 // EnableTracing logs the tracing output to os.Stdout instead of silently discarding it.
@@ -1099,7 +1110,7 @@ func (t *Tester) CheckFileLines(relative
        t.c.Assert(err, check.IsNil)
        actualLines := strings.Split(string(content), "\n")
        actualLines = actualLines[:len(actualLines)-1]
-       t.Check(emptyToNil(actualLines), deepEquals, emptyToNil(lines))
+       t.CheckDeepEquals(emptyToNil(actualLines), emptyToNil(lines))
 }
 
 // CheckFileLinesDetab loads the lines from the temporary file and checks
@@ -1114,7 +1125,7 @@ func (t *Tester) CheckFileLinesDetab(rel
                detabbedLines = append(detabbedLines, detab(line.Text))
        }
 
-       t.Check(detabbedLines, deepEquals, lines)
+       t.CheckDeepEquals(detabbedLines, lines)
 }
 
 // Use marks all passed functions as used for the Go compiler.

Index: pkgsrc/pkgtools/pkglint/files/distinfo_test.go
diff -u pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.31 pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.32
--- pkgsrc/pkgtools/pkglint/files/distinfo_test.go:1.31 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/distinfo_test.go      Sun Jul 14 21:25:47 2019
@@ -204,8 +204,8 @@ func (s *Suite) Test_distinfoLinesChecke
                "(Run \"pkglint -e\" to show explanations.)")
 
        // Ensure that hex.DecodeString does not waste memory here.
-       t.Check(len(G.InterPackage.hashes["SHA512:distfile-1.0.tar.gz"].hash), equals, 8)
-       t.Check(cap(G.InterPackage.hashes["SHA512:distfile-1.0.tar.gz"].hash), equals, 8)
+       t.CheckEquals(len(G.InterPackage.hashes["SHA512:distfile-1.0.tar.gz"].hash), 8)
+       t.CheckEquals(cap(G.InterPackage.hashes["SHA512:distfile-1.0.tar.gz"].hash), 8)
 }
 
 func (s *Suite) Test_distinfoLinesChecker_checkAlgorithms__missing_patch_with_distfile_checksums(c *check.C) {
Index: pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.31 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.32
--- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.31      Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/mkparser.go   Sun Jul 14 21:25:47 2019
@@ -23,14 +23,15 @@ func (p *MkParser) Rest() string {
 }
 
 // NewMkParser creates a new parser for the given text.
-// If emitWarnings is false, line may be nil.
+//
+// If line is given, it is used for reporting parse errors and warnings.
+// Otherwise parsing is silent.
 //
 // The text argument is assumed to be after unescaping the # character,
 // which means the # is a normal character and does not introduce a Makefile comment.
 // For VarUse, this distinction is irrelevant.
-func NewMkParser(line *Line, text string, emitWarnings bool) *MkParser {
-       assert((line != nil) == emitWarnings) // line must be given iff emitWarnings is set
-       return &MkParser{line, textproc.NewLexer(text), emitWarnings}
+func NewMkParser(line *Line, text string) *MkParser {
+       return &MkParser{line, textproc.NewLexer(text), line != nil}
 }
 
 // MkTokens splits a text like in the following example:
@@ -251,7 +252,7 @@ func (p *MkParser) varUseModifier(varnam
 
        case '=', 'D', 'M', 'N', 'U':
                lexer.Skip(1)
-               re := regcomp(regex.Pattern(ifelseStr(closing == '}', `^([^$:\\}]|\$\$|\\.)+`, `^([^$:\\)]|\$\$|\\.)+`)))
+               re := regcomp(regex.Pattern(condStr(closing == '}', `^([^$:\\}]|\$\$|\\.)+`, `^([^$:\\)]|\$\$|\\.)+`)))
                for p.VarUse() != nil || lexer.SkipRegexp(re) {
                }
                arg := lexer.Since(mark)
@@ -283,7 +284,7 @@ func (p *MkParser) varUseModifier(varnam
 
        lexer.Reset(mark)
 
-       re := regcomp(regex.Pattern(ifelseStr(closing == '}', `^([^:$}]|\$\$)+`, `^([^:$)]|\$\$)+`)))
+       re := regcomp(regex.Pattern(condStr(closing == '}', `^([^:$}]|\$\$)+`, `^([^:$)]|\$\$)+`)))
        for p.VarUse() != nil || lexer.SkipRegexp(re) {
        }
        modifier := lexer.Since(mark)
@@ -309,7 +310,7 @@ func (p *MkParser) varUseModifier(varnam
 func (p *MkParser) varUseText(closing byte) string {
        lexer := p.lexer
        start := lexer.Mark()
-       re := regcomp(regex.Pattern(ifelseStr(closing == '}', `^([^$:}]|\$\$)+`, `^([^$:)]|\$\$)+`)))
+       re := regcomp(regex.Pattern(condStr(closing == '}', `^([^$:}]|\$\$)+`, `^([^$:)]|\$\$)+`)))
        for p.VarUse() != nil || lexer.SkipRegexp(re) {
        }
        return lexer.Since(start)
@@ -432,7 +433,7 @@ func (p *MkParser) MkCond() *MkCond {
 }
 
 func (p *MkParser) mkCondAnd() *MkCond {
-       atom := p.mkCondAtom()
+       atom := p.mkCondCompare()
        if atom == nil {
                return nil
        }
@@ -444,7 +445,7 @@ func (p *MkParser) mkCondAnd() *MkCond {
                if p.lexer.NextString("&&") == "" {
                        break
                }
-               next := p.mkCondAtom()
+               next := p.mkCondCompare()
                if next == nil {
                        p.lexer.Reset(mark)
                        break
@@ -457,7 +458,7 @@ func (p *MkParser) mkCondAnd() *MkCond {
        return &MkCond{And: atoms}
 }
 
-func (p *MkParser) mkCondAtom() *MkCond {
+func (p *MkParser) mkCondCompare() *MkCond {
        if trace.Tracing {
                defer trace.Call1(p.Rest())()
        }
@@ -467,10 +468,13 @@ func (p *MkParser) mkCondAtom() *MkCond 
        lexer.SkipHspace()
        switch {
        case lexer.SkipByte('!'):
-               cond := p.mkCondAtom()
+               notMark := lexer.Mark()
+               cond := p.mkCondCompare()
                if cond != nil {
                        return &MkCond{Not: cond}
                }
+               lexer.Reset(notMark)
+               return nil
 
        case lexer.SkipByte('('):
                cond := p.MkCond()
@@ -480,90 +484,108 @@ func (p *MkParser) mkCondAtom() *MkCond 
                                return cond
                        }
                }
+               lexer.Reset(mark)
+               return nil
 
-       case lexer.TestByteSet(textproc.Lower):
+       case lexer.TestByteSet(textproc.Alpha):
+               // This can only be a function name, not a string literal like in
+               // amd64 == ${MACHINE_ARCH}, since bmake interprets it in the same
+               // way, reporting a malformed conditional.
                return p.mkCondFunc()
+       }
 
-       default:
-               lhs := p.VarUse()
-               mark := lexer.Mark()
-               if lhs == nil && lexer.SkipByte('"') {
-                       if quotedLHS := p.VarUse(); quotedLHS != nil && lexer.SkipByte('"') {
-                               lhs = quotedLHS
-                       } else {
-                               lexer.Reset(mark)
-                       }
+       lhs := p.mkCondTerm()
+
+       if lhs != nil {
+               lexer.SkipHspace()
+
+               if m := lexer.NextRegexp(regcomp(`^(<|<=|==|!=|>=|>)[\t ]*(0x[0-9A-Fa-f]+|\d+(?:\.\d+)?)`)); m != nil {
+                       return &MkCond{Compare: &MkCondCompare{*lhs, m[1], MkCondTerm{Num: m[2]}}}
                }
 
-               if lhs != nil {
-                       lexer.SkipHspace()
+               m := lexer.NextRegexp(regcomp(`^(?:<|<=|==|!=|>=|>)`))
+               if len(m) == 0 {
+                       // See devel/bmake/files/cond.c:/\* For \.if \$/
+                       return &MkCond{Term: lhs}
+               }
+               lexer.SkipHspace()
 
-                       if m := lexer.NextRegexp(regcomp(`^(<|<=|==|!=|>=|>)[\t ]*(0x[0-9A-Fa-f]+|\d+(?:\.\d+)?)`)); m != nil {
-                               return &MkCond{CompareVarNum: &MkCondCompareVarNum{lhs, m[1], m[2]}}
+               op := m[0]
+               if op == "==" || op == "!=" {
+                       if mrhs := lexer.NextRegexp(regcomp(`^"([^"\$\\]*)"`)); mrhs != nil {
+                               return &MkCond{Compare: &MkCondCompare{*lhs, op, MkCondTerm{Str: mrhs[1]}}}
                        }
+               }
 
-                       m := lexer.NextRegexp(regcomp(`^(?:<|<=|==|!=|>=|>)`))
-                       if m == nil {
-                               return &MkCond{Var: lhs} // See devel/bmake/files/cond.c:/\* For \.if \$/
-                       }
-                       lexer.SkipHspace()
+               rhs := p.mkCondTerm()
+               if rhs != nil {
+                       return &MkCond{Compare: &MkCondCompare{*lhs, op, *rhs}}
+               }
 
-                       op := m[0]
-                       if op == "==" || op == "!=" {
-                               if mrhs := lexer.NextRegexp(regcomp(`^"([^"\$\\]*)"`)); mrhs != nil {
-                                       return &MkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, mrhs[1]}}
-                               }
-                       }
+               if str := lexer.NextBytesSet(textproc.AlnumU); str != "" {
+                       return &MkCond{Compare: &MkCondCompare{*lhs, op, MkCondTerm{Str: str}}}
+               }
+       }
 
-                       if str := lexer.NextBytesSet(textproc.AlnumU); str != "" {
-                               return &MkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, str}}
-                       }
+       // See devel/bmake/files/cond.c:/^CondCvtArg
+       if m := lexer.NextRegexp(regcomp(`^(?:0x[0-9A-Fa-f]+|\d+(?:\.\d+)?)`)); m != nil {
+               return &MkCond{Term: &MkCondTerm{Num: m[0]}}
+       }
 
-                       if rhs := p.VarUse(); rhs != nil {
-                               return &MkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, rhs}}
-                       }
+       lexer.Reset(mark)
+       return nil
+}
 
-                       if lexer.PeekByte() == '"' {
-                               mark := lexer.Mark()
-                               lexer.Skip(1)
-                               if quotedRHS := p.VarUse(); quotedRHS != nil {
-                                       if lexer.SkipByte('"') {
-                                               return &MkCond{CompareVarVar: &MkCondCompareVarVar{lhs, op, quotedRHS}}
-                                       }
-                               }
-                               lexer.Reset(mark)
+// mkCondTerm parses the following:
+//  ${VAR}
+//  "${VAR}"
+//  "text${VAR}text"
+//  "text"
+// It does not parse unquoted string literals since these are only allowed
+// at the right-hand side of a comparison expression.
+func (p *MkParser) mkCondTerm() *MkCondTerm {
+       lexer := p.lexer
 
-                               lexer.Skip(1)
-                               var rhsText strings.Builder
-                       loop:
-                               for {
-                                       m := lexer.Mark()
-                                       switch {
-                                       case p.VarUse() != nil,
-                                               lexer.NextBytesSet(textproc.Alnum) != "",
-                                               lexer.NextBytesFunc(func(b byte) bool { return b != '"' && b != '\\' }) != "":
-                                               rhsText.WriteString(lexer.Since(m))
-
-                                       case lexer.SkipString("\\\""),
-                                               lexer.SkipString("\\\\"):
-                                               rhsText.WriteByte(lexer.Since(m)[1])
-
-                                       case lexer.SkipByte('"'):
-                                               return &MkCond{CompareVarStr: &MkCondCompareVarStr{lhs, op, rhsText.String()}}
-                                       default:
-                                               break loop
-                                       }
-                               }
-                               lexer.Reset(mark)
-                       }
+       if rhs := p.VarUse(); rhs != nil {
+               return &MkCondTerm{Var: rhs}
+       }
+
+       if lexer.PeekByte() != '"' {
+               return nil
+       }
+
+       mark := lexer.Mark()
+       lexer.Skip(1)
+       if quotedRHS := p.VarUse(); quotedRHS != nil {
+               if lexer.SkipByte('"') {
+                       return &MkCondTerm{Var: quotedRHS}
                }
+       }
+       lexer.Reset(mark)
 
-               // See devel/bmake/files/cond.c:/^CondCvtArg
-               if m := lexer.NextRegexp(regcomp(`^(?:0x[0-9A-Fa-f]+|\d+(?:\.\d+)?)`)); m != nil {
-                       return &MkCond{Num: m[0]}
+       lexer.Skip(1)
+       var rhsText strings.Builder
+loop:
+       for {
+               m := lexer.Mark()
+               switch {
+               case p.VarUse() != nil,
+                       lexer.NextBytesSet(textproc.Alnum) != "",
+                       lexer.NextBytesFunc(func(b byte) bool { return b != '"' && b != '\\' }) != "":
+                       rhsText.WriteString(lexer.Since(m))
+
+               case lexer.SkipString("\\\""),
+                       lexer.SkipString("\\\\"):
+                       rhsText.WriteByte(lexer.Since(m)[1])
+
+               case lexer.SkipByte('"'):
+                       return &MkCondTerm{Str: rhsText.String()}
+               default:
+                       break loop
                }
        }
        lexer.Reset(mark)
+
        return nil
 }
 
@@ -574,6 +596,7 @@ func (p *MkParser) mkCondFunc() *MkCond 
        funcName := lexer.NextBytesSet(textproc.Lower)
        lexer.SkipHspace()
        if !lexer.SkipByte('(') {
+               lexer.Reset(mark)
                return nil
        }
 
@@ -625,6 +648,23 @@ func (p *MkParser) Varname() string {
        return lexer.Since(mark)
 }
 
+func (p *MkParser) Op() (bool, MkOperator) {
+       lexer := p.lexer
+       switch {
+       case lexer.SkipString("!="):
+               return true, opAssignShell
+       case lexer.SkipString(":="):
+               return true, opAssignEval
+       case lexer.SkipString("+="):
+               return true, opAssignAppend
+       case lexer.SkipString("?="):
+               return true, opAssignDefault
+       case lexer.SkipString("="):
+               return true, opAssign
+       }
+       return false, 0
+}
+
 // isPkgbasePart returns whether str, when following a hyphen,
 // continues the package base (as in "mysql-client"), or whether it
 // starts the version (as in "mysql-1.0").
@@ -637,7 +677,7 @@ func (*MkParser) isPkgbasePart(str strin
                return true
        }
 
-       varUse := NewMkParser(nil, lexer.Rest(), false).VarUse()
+       varUse := NewMkParser(nil, lexer.Rest()).VarUse()
        if varUse != nil {
                return !contains(varUse.varname, "VER") && len(varUse.modifiers) == 0
        }
@@ -772,7 +812,7 @@ func (p *MkParser) Dependency() *Depende
 // if there is a parse error or some trailing text.
 // Parse errors are silently ignored.
 func ToVarUse(str string) *MkVarUse {
-       p := NewMkParser(nil, str, false)
+       p := NewMkParser(nil, str)
        varUse := p.VarUse()
        if varUse == nil || !p.EOF() {
                return nil
@@ -791,45 +831,48 @@ type MkCond struct {
        And []*MkCond
        Not *MkCond
 
-       Defined       string
-       Empty         *MkVarUse
-       Var           *MkVarUse
-       CompareVarNum *MkCondCompareVarNum
-       CompareVarStr *MkCondCompareVarStr
-       CompareVarVar *MkCondCompareVarVar
-       Call          *MkCondCall
-       Num           string
+       Defined string
+       Empty   *MkVarUse
+       Term    *MkCondTerm
+       Compare *MkCondCompare
+       Call    *MkCondCall
+}
+type MkCondCompare struct {
+       Left MkCondTerm
+       // For numeric comparison: one of <, <=, ==, !=, >=, >.
+       //
+       // For string comparison: one of ==, !=.
+       //
+       // For not-empty test: "".
+       Op    string
+       Right MkCondTerm
 }
-type MkCondCompareVarNum struct {
-       Var *MkVarUse
-       Op  string // One of <, <=, ==, !=, >=, >.
+type MkCondTerm struct {
+       Str string
        Num string
-}
-type MkCondCompareVarStr struct {
        Var *MkVarUse
-       Op  string // One of ==, !=.
-       Str string
-}
-type MkCondCompareVarVar struct {
-       Left  *MkVarUse
-       Op    string // One of <, <=, ==, !=, >=, >.
-       Right *MkVarUse
 }
 type MkCondCall struct {
        Name string
        Arg  string
 }
 
+// MkCondCallback defines the actions for walking a Makefile condition
+// using MkCondWalker.Walk.
 type MkCondCallback struct {
-       Not           func(cond *MkCond)
-       Defined       func(varname string)
-       Empty         func(empty *MkVarUse)
-       CompareVarNum func(varuse *MkVarUse, op string, num string)
-       CompareVarStr func(varuse *MkVarUse, op string, str string)
-       CompareVarVar func(left *MkVarUse, op string, right *MkVarUse)
-       Call          func(name string, arg string)
-       Var           func(varuse *MkVarUse)
-       VarUse        func(varuse *MkVarUse)
+       Not     func(cond *MkCond)
+       Defined func(varname string)
+       Empty   func(empty *MkVarUse)
+       Compare func(left *MkCondTerm, op string, right *MkCondTerm)
+       Call    func(name string, arg string)
+
+       // Var is called for every atomic expression that consists solely
+       // of a variable use, possibly enclosed in double quotes, but without
+       // any surrounding string literal parts.
+       Var func(varuse *MkVarUse)
+
+       // VarUse is called for each variable that is used in some expression.
+       VarUse func(varuse *MkVarUse)
 }
 
 func (cond *MkCond) Walk(callback *MkCondCallback) {
@@ -866,14 +909,17 @@ func (w *MkCondWalker) Walk(cond *MkCond
                        callback.VarUse(&MkVarUse{cond.Defined, nil})
                }
 
-       case cond.Var != nil:
+       case cond.Term != nil && cond.Term.Var != nil:
                if callback.Var != nil {
-                       callback.Var(cond.Var)
+                       callback.Var(cond.Term.Var)
                }
                if callback.VarUse != nil {
-                       callback.VarUse(cond.Var)
+                       callback.VarUse(cond.Term.Var)
                }
 
+       case cond.Term != nil && cond.Term.Str != "":
+               w.walkStr(cond.Term.Str, callback)
+
        case cond.Empty != nil:
                if callback.Empty != nil {
                        callback.Empty(cond.Empty)
@@ -882,35 +928,13 @@ func (w *MkCondWalker) Walk(cond *MkCond
                        callback.VarUse(cond.Empty)
                }
 
-       case cond.CompareVarVar != nil:
-               if callback.CompareVarVar != nil {
-                       cvv := cond.CompareVarVar
-                       callback.CompareVarVar(cvv.Left, cvv.Op, cvv.Right)
-               }
-               if callback.VarUse != nil {
-                       cvv := cond.CompareVarVar
-                       callback.VarUse(cvv.Left)
-                       callback.VarUse(cvv.Right)
-               }
-
-       case cond.CompareVarStr != nil:
-               if callback.CompareVarStr != nil {
-                       cvs := cond.CompareVarStr
-                       callback.CompareVarStr(cvs.Var, cvs.Op, cvs.Str)
-               }
-               if callback.VarUse != nil {
-                       callback.VarUse(cond.CompareVarStr.Var)
-               }
-               w.walkStr(cond.CompareVarStr.Str, callback)
-
-       case cond.CompareVarNum != nil:
-               if callback.CompareVarNum != nil {
-                       cvn := cond.CompareVarNum
-                       callback.CompareVarNum(cvn.Var, cvn.Op, cvn.Num)
-               }
-               if callback.VarUse != nil {
-                       callback.VarUse(cond.CompareVarNum.Var)
+       case cond.Compare != nil:
+               cmp := cond.Compare
+               if callback.Compare != nil {
+                       callback.Compare(&cmp.Left, cmp.Op, &cmp.Right)
                }
+               w.walkAtom(&cmp.Left, callback)
+               w.walkAtom(&cmp.Right, callback)
 
        case cond.Call != nil:
                if callback.Call != nil {
@@ -921,9 +945,22 @@ func (w *MkCondWalker) Walk(cond *MkCond
        }
 }
 
+func (w *MkCondWalker) walkAtom(atom *MkCondTerm, callback *MkCondCallback) {
+       switch {
+       case atom.Var != nil:
+               if callback.VarUse != nil {
+                       callback.VarUse(atom.Var)
+               }
+       case atom.Num != "":
+               break
+       default:
+               w.walkStr(atom.Str, callback)
+       }
+}
+
 func (w *MkCondWalker) walkStr(str string, callback *MkCondCallback) {
        if callback.VarUse != nil {
-               tokens := NewMkParser(nil, str, false).MkTokens()
+               tokens := NewMkParser(nil, str).MkTokens()
                for _, token := range tokens {
                        if token.Varuse != nil {
                                callback.VarUse(token.Varuse)
Index: pkgsrc/pkgtools/pkglint/files/util_test.go
diff -u pkgsrc/pkgtools/pkglint/files/util_test.go:1.31 pkgsrc/pkgtools/pkglint/files/util_test.go:1.32
--- pkgsrc/pkgtools/pkglint/files/util_test.go:1.31     Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/util_test.go  Sun Jul 14 21:25:47 2019
@@ -32,64 +32,84 @@ func (s *Suite) Test_assertNotNil(c *che
 }
 
 func (s *Suite) Test_YesNoUnknown_String(c *check.C) {
-       c.Check(yes.String(), equals, "yes")
-       c.Check(no.String(), equals, "no")
-       c.Check(unknown.String(), equals, "unknown")
+       t := s.Init(c)
+
+       t.CheckEquals(yes.String(), "yes")
+       t.CheckEquals(no.String(), "no")
+       t.CheckEquals(unknown.String(), "unknown")
 }
 
 func (s *Suite) Test_mkopSubst__middle(c *check.C) {
-       c.Check(mkopSubst("pkgname", false, "kgna", false, "ri", ""), equals, "prime")
-       c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), equals, "replacement")
-       c.Check(mkopSubst("aaaaaaa", false, "a", false, "b", ""), equals, "baaaaaa")
+       t := s.Init(c)
+
+       t.CheckEquals(mkopSubst("pkgname", false, "kgna", false, "ri", ""), "prime")
+       t.CheckEquals(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), "replacement")
+       t.CheckEquals(mkopSubst("aaaaaaa", false, "a", false, "b", ""), "baaaaaa")
 }
 
 func (s *Suite) Test_mkopSubst__left(c *check.C) {
-       c.Check(mkopSubst("pkgname", true, "kgna", false, "ri", ""), equals, "pkgname")
-       c.Check(mkopSubst("pkgname", true, "pkgname", false, "replacement", ""), equals, "replacement")
+       t := s.Init(c)
+
+       t.CheckEquals(mkopSubst("pkgname", true, "kgna", false, "ri", ""), "pkgname")
+       t.CheckEquals(mkopSubst("pkgname", true, "pkgname", false, "replacement", ""), "replacement")
 }
 
 func (s *Suite) Test_mkopSubst__right(c *check.C) {
-       c.Check(mkopSubst("pkgname", false, "kgna", true, "ri", ""), equals, "pkgname")
-       c.Check(mkopSubst("pkgname", false, "pkgname", true, "replacement", ""), equals, "replacement")
+       t := s.Init(c)
+
+       t.CheckEquals(mkopSubst("pkgname", false, "kgna", true, "ri", ""), "pkgname")
+       t.CheckEquals(mkopSubst("pkgname", false, "pkgname", true, "replacement", ""), "replacement")
 }
 
 func (s *Suite) Test_mkopSubst__left_and_right(c *check.C) {
-       c.Check(mkopSubst("pkgname", true, "kgna", true, "ri", ""), equals, "pkgname")
-       c.Check(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), equals, "replacement")
+       t := s.Init(c)
+
+       t.CheckEquals(mkopSubst("pkgname", true, "kgna", true, "ri", ""), "pkgname")
+       t.CheckEquals(mkopSubst("pkgname", false, "pkgname", false, "replacement", ""), "replacement")
 }
 
 func (s *Suite) Test_mkopSubst__gflag(c *check.C) {
-       c.Check(mkopSubst("aaaaa", false, "a", false, "b", "g"), equals, "bbbbb")
-       c.Check(mkopSubst("aaaaa", true, "a", false, "b", "g"), equals, "baaaa")
-       c.Check(mkopSubst("aaaaa", false, "a", true, "b", "g"), equals, "aaaab")
-       c.Check(mkopSubst("aaaaa", true, "a", true, "b", "g"), equals, "aaaaa")
+       t := s.Init(c)
+
+       t.CheckEquals(mkopSubst("aaaaa", false, "a", false, "b", "g"), "bbbbb")
+       t.CheckEquals(mkopSubst("aaaaa", true, "a", false, "b", "g"), "baaaa")
+       t.CheckEquals(mkopSubst("aaaaa", false, "a", true, "b", "g"), "aaaab")
+       t.CheckEquals(mkopSubst("aaaaa", true, "a", true, "b", "g"), "aaaaa")
 }
 
 func (s *Suite) Test__regex_ReplaceFirst(c *check.C) {
+       t := s.Init(c)
+
        m, rest := G.res.ReplaceFirst("a+b+c+d", `(\w)(.)(\w)`, "X")
 
        c.Assert(m, check.NotNil)
-       c.Check(m, check.DeepEquals, []string{"a+b", "a", "+", "b"})
-       c.Check(rest, equals, "X+c+d")
+       t.CheckDeepEquals(m, []string{"a+b", "a", "+", "b"})
+       t.CheckEquals(rest, "X+c+d")
 }
 
 func (s *Suite) Test_shorten(c *check.C) {
-       c.Check(shorten("aaaaa", 3), equals, "aaa...")
-       c.Check(shorten("aaaaa", 5), equals, "aaaaa")
-       c.Check(shorten("aaa", 5), equals, "aaa")
+       t := s.Init(c)
+
+       t.CheckEquals(shorten("aaaaa", 3), "aaa...")
+       t.CheckEquals(shorten("aaaaa", 5), "aaaaa")
+       t.CheckEquals(shorten("aaa", 5), "aaa")
 }
 
 func (s *Suite) Test_tabWidth(c *check.C) {
-       c.Check(tabWidth("12345"), equals, 5)
-       c.Check(tabWidth("\t"), equals, 8)
-       c.Check(tabWidth("123\t"), equals, 8)
-       c.Check(tabWidth("1234567\t"), equals, 8)
-       c.Check(tabWidth("12345678\t"), equals, 16)
+       t := s.Init(c)
+
+       t.CheckEquals(tabWidth("12345"), 5)
+       t.CheckEquals(tabWidth("\t"), 8)
+       t.CheckEquals(tabWidth("123\t"), 8)
+       t.CheckEquals(tabWidth("1234567\t"), 8)
+       t.CheckEquals(tabWidth("12345678\t"), 16)
 }
 
 func (s *Suite) Test_cleanpath(c *check.C) {
+       t := s.Init(c)
+
        test := func(from, to string) {
-               c.Check(cleanpath(from), equals, to)
+               t.CheckEquals(cleanpath(from), to)
        }
 
        test("simple/path", "simple/path")
@@ -139,10 +159,10 @@ func (s *Suite) Test_relpath(c *check.C)
        t := s.Init(c)
 
        t.Chdir(".")
-       t.Check(G.Pkgsrc.topdir, equals, t.tmpdir)
+       t.CheckEquals(G.Pkgsrc.topdir, t.tmpdir)
 
        test := func(from, to, result string) {
-               c.Check(relpath(from, to), equals, result)
+               t.CheckEquals(relpath(from, to), result)
        }
 
        test("some/dir", "some/directory", "../../some/directory")
@@ -175,9 +195,10 @@ func (s *Suite) Test_relpath(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) {
+       t := s.Init(c)
 
        test := func(from, to, result string) {
-               c.Check(relpath(from, to), equals, result)
+               t.CheckEquals(relpath(from, to), result)
        }
 
        test("some/dir", "some/dir/../..", "../..")
@@ -193,14 +214,14 @@ func (s *Suite) Test_pathContains(c *che
 
        test := func(haystack, needle string, expected bool) {
                actual := pathContains(haystack, needle)
-               t.Check(actual, equals, expected)
+               t.CheckEquals(actual, expected)
        }
 
        testPanic := func(haystack, needle string) {
                t.c.Check(
                        func() { _ = pathContains(haystack, needle) },
                        check.PanicMatches,
-                       `runtime error: index out of range`)
+                       `runtime error: index out of range.*`)
        }
 
        testPanic("", "")
@@ -230,14 +251,14 @@ func (s *Suite) Test_pathContainsDir(c *
 
        test := func(haystack, needle string, expected bool) {
                actual := pathContainsDir(haystack, needle)
-               t.Check(actual, equals, expected)
+               t.CheckEquals(actual, expected)
        }
 
        testPanic := func(haystack, needle string) {
                t.c.Check(
                        func() { _ = pathContainsDir(haystack, needle) },
                        check.PanicMatches,
-                       `runtime error: index out of range`)
+                       `^runtime error: index out of range.*`)
        }
 
        testPanic("", "")
@@ -267,10 +288,10 @@ func (s *Suite) Test_fileExists(c *check
 
        t.CreateFileLines("dir/file")
 
-       t.Check(fileExists(t.File("nonexistent")), equals, false)
-       t.Check(fileExists(t.File("dir")), equals, false)
-       t.Check(fileExists(t.File("dir/nonexistent")), equals, false)
-       t.Check(fileExists(t.File("dir/file")), equals, true)
+       t.CheckEquals(fileExists(t.File("nonexistent")), false)
+       t.CheckEquals(fileExists(t.File("dir")), false)
+       t.CheckEquals(fileExists(t.File("dir/nonexistent")), false)
+       t.CheckEquals(fileExists(t.File("dir/file")), true)
 }
 
 func (s *Suite) Test_dirExists(c *check.C) {
@@ -278,10 +299,10 @@ func (s *Suite) Test_dirExists(c *check.
 
        t.CreateFileLines("dir/file")
 
-       t.Check(dirExists(t.File("nonexistent")), equals, false)
-       t.Check(dirExists(t.File("dir")), equals, true)
-       t.Check(dirExists(t.File("dir/nonexistent")), equals, false)
-       t.Check(dirExists(t.File("dir/file")), equals, false)
+       t.CheckEquals(dirExists(t.File("nonexistent")), false)
+       t.CheckEquals(dirExists(t.File("dir")), true)
+       t.CheckEquals(dirExists(t.File("dir/nonexistent")), false)
+       t.CheckEquals(dirExists(t.File("dir/file")), false)
 }
 
 func (s *Suite) Test_isEmptyDir__and_getSubdirs(c *check.C) {
@@ -291,17 +312,17 @@ func (s *Suite) Test_isEmptyDir__and_get
                "dummy")
 
        if dir := t.File("."); true {
-               c.Check(isEmptyDir(dir), equals, true)
-               c.Check(getSubdirs(dir), check.DeepEquals, []string(nil))
+               t.CheckEquals(isEmptyDir(dir), true)
+               t.CheckDeepEquals(getSubdirs(dir), []string(nil))
 
                t.CreateFileLines("somedir/file")
 
-               c.Check(isEmptyDir(dir), equals, false)
-               c.Check(getSubdirs(dir), check.DeepEquals, []string{"somedir"})
+               t.CheckEquals(isEmptyDir(dir), false)
+               t.CheckDeepEquals(getSubdirs(dir), []string{"somedir"})
        }
 
        if absent := t.File("nonexistent"); true {
-               c.Check(isEmptyDir(absent), equals, true) // Counts as empty.
+               t.CheckEquals(isEmptyDir(absent), true) // Counts as empty.
 
                // The last group from the error message is localized, therefore the matching.
                t.ExpectFatalMatches(
@@ -318,8 +339,8 @@ func (s *Suite) Test_isEmptyDir(c *check
        t.CreateFileLines("subdir/CVS/Entries",
                "dummy")
 
-       c.Check(isEmptyDir(t.File(".")), equals, true)
-       c.Check(isEmptyDir(t.File("CVS")), equals, true)
+       t.CheckEquals(isEmptyDir(t.File(".")), true)
+       t.CheckEquals(isEmptyDir(t.File("CVS")), true)
 }
 
 func (s *Suite) Test_getSubdirs(c *check.C) {
@@ -329,22 +350,24 @@ func (s *Suite) Test_getSubdirs(c *check
        t.CreateFileLines("empty/file")
        c.Check(os.Remove(t.File("empty/file")), check.IsNil)
 
-       c.Check(getSubdirs(t.File(".")), deepEquals, []string{"subdir"})
+       t.CheckDeepEquals(getSubdirs(t.File(".")), []string{"subdir"})
 }
 
 func (s *Suite) Test_detab(c *check.C) {
-       c.Check(detab(""), equals, "")
-       c.Check(detab("\t"), equals, "        ")
-       c.Check(detab("1234\t9"), equals, "1234    9")
-       c.Check(detab("1234567\t"), equals, "1234567 ")
-       c.Check(detab("12345678\t"), equals, "12345678        ")
+       t := s.Init(c)
+
+       t.CheckEquals(detab(""), "")
+       t.CheckEquals(detab("\t"), "        ")
+       t.CheckEquals(detab("1234\t9"), "1234    9")
+       t.CheckEquals(detab("1234567\t"), "1234567 ")
+       t.CheckEquals(detab("12345678\t"), "12345678        ")
 }
 
 func (s *Suite) Test_alignWith(c *check.C) {
        t := s.Init(c)
 
        test := func(str, other, expected string) {
-               t.Check(alignWith(str, other), equals, expected)
+               t.CheckEquals(alignWith(str, other), expected)
        }
 
        // At least one tab is _always_ added.
@@ -426,10 +449,10 @@ func emptyToNil(slice []string) []string
 func (s *Suite) Test_trimHspace(c *check.C) {
        t := s.Init(c)
 
-       t.Check(trimHspace("a b"), equals, "a b")
-       t.Check(trimHspace(" a b "), equals, "a b")
-       t.Check(trimHspace("\ta b\t"), equals, "a b")
-       t.Check(trimHspace(" \t a b\t \t"), equals, "a b")
+       t.CheckEquals(trimHspace("a b"), "a b")
+       t.CheckEquals(trimHspace(" a b "), "a b")
+       t.CheckEquals(trimHspace("\ta b\t"), "a b")
+       t.CheckEquals(trimHspace(" \t a b\t \t"), "a b")
 }
 
 func (s *Suite) Test_trimCommon(c *check.C) {
@@ -437,8 +460,8 @@ func (s *Suite) Test_trimCommon(c *check
 
        test := func(a, b, trimmedA, trimmedB string) {
                ta, tb := trimCommon(a, b)
-               t.Check(ta, equals, trimmedA)
-               t.Check(tb, equals, trimmedB)
+               t.CheckEquals(ta, trimmedA)
+               t.CheckEquals(tb, trimmedB)
        }
 
        test("", "",
@@ -463,6 +486,50 @@ func (s *Suite) Test_trimCommon(c *check
                "a", "")
 }
 
+func (s *Suite) Test_indent(c *check.C) {
+       t := s.Init(c)
+
+       test := func(width int, ind string) {
+               actual := indent(width)
+
+               t.CheckEquals(actual, ind)
+       }
+
+       test(0, "")
+       test(1, " ")
+       test(7, "       ")
+       test(8, "\t")
+       test(15, "\t       ")
+       test(16, "\t\t")
+       test(72, "\t\t\t\t\t\t\t\t\t")
+}
+
+func (s *Suite) Test_alignmentAfter(c *check.C) {
+       t := s.Init(c)
+
+       test := func(prefix string, width int, ind string) {
+               actual := alignmentAfter(prefix, width)
+
+               t.CheckEquals(actual, ind)
+       }
+
+       test("", 0, "")
+       test("", 15, "\t       ")
+
+       test("  ", 5, "   ")
+       test("      ", 10, "\t  ")
+
+       test("\t", 15, "       ")
+       test(" \t", 15, "       ")
+       test("       \t", 15, "       ")
+       test("\t    ", 15, "   ")
+
+       test("    ", 16, "\t\t")
+
+       // The desired width must be at least the width of the prefix.
+       t.ExpectAssert(func() { test("\t", 7, "") })
+}
+
 func (s *Suite) Test_isLocallyModified(c *check.C) {
        t := s.Init(c)
 
@@ -476,7 +543,7 @@ func (s *Suite) Test_isLocallyModified(c
        c.Check(err, check.IsNil)
 
        // Make sure that the file system has second precision and accuracy.
-       c.Check(st.ModTime().UTC(), check.DeepEquals, modTime)
+       t.CheckDeepEquals(st.ModTime().UTC(), modTime)
 
        modified := t.CreateFileLines("modified")
 
@@ -485,15 +552,15 @@ func (s *Suite) Test_isLocallyModified(c
                "/modified//"+modTime.Format(time.ANSIC)+"//",
                "/enoent//"+modTime.Format(time.ANSIC)+"//")
 
-       c.Check(isLocallyModified(unmodified), equals, false)
-       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)
+       t.CheckEquals(isLocallyModified(unmodified), false)
+       t.CheckEquals(isLocallyModified(modified), true)
+       t.CheckEquals(isLocallyModified(t.File("enoent")), true)
+       t.CheckEquals(isLocallyModified(t.File("not_mentioned")), false)
+       t.CheckEquals(isLocallyModified(t.File("subdir/file")), false)
 
        t.DisableTracing()
 
-       c.Check(isLocallyModified(t.File("unmodified")), equals, false)
+       t.CheckEquals(isLocallyModified(t.File("unmodified")), false)
 }
 
 func (s *Suite) Test_Scope_Define(c *check.C) {
@@ -502,16 +569,16 @@ func (s *Suite) Test_Scope_Define(c *che
        scope := NewScope()
        scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 121, "BUILD_DIRS=\tone two three"))
 
-       c.Check(scope.LastValue("BUILD_DIRS"), equals, "one two three")
+       t.CheckEquals(scope.LastValue("BUILD_DIRS"), "one two three")
 
        scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 123, "BUILD_DIRS+=\tfour"))
 
-       c.Check(scope.LastValue("BUILD_DIRS"), equals, "one two three four")
+       t.CheckEquals(scope.LastValue("BUILD_DIRS"), "one two three four")
 
        // Later default assignments do not have an effect.
        scope.Define("BUILD_DIRS", t.NewMkLine("file.mk", 123, "BUILD_DIRS?=\tdefault"))
 
-       c.Check(scope.LastValue("BUILD_DIRS"), equals, "one two three four")
+       t.CheckEquals(scope.LastValue("BUILD_DIRS"), "one two three four")
 }
 
 func (s *Suite) Test_Scope_Defined(c *check.C) {
@@ -520,13 +587,13 @@ func (s *Suite) Test_Scope_Defined(c *ch
        scope := NewScope()
        scope.Define("VAR.param", t.NewMkLine("file.mk", 1, "VAR.param=value"))
 
-       c.Check(scope.Defined("VAR.param"), equals, true)
-       c.Check(scope.Defined("VAR.other"), equals, false)
-       c.Check(scope.Defined("VARIABLE.*"), equals, false)
-
-       c.Check(scope.DefinedSimilar("VAR.param"), equals, true)
-       c.Check(scope.DefinedSimilar("VAR.other"), equals, true)
-       c.Check(scope.DefinedSimilar("VARIABLE.*"), equals, false)
+       t.CheckEquals(scope.Defined("VAR.param"), true)
+       t.CheckEquals(scope.Defined("VAR.other"), false)
+       t.CheckEquals(scope.Defined("VARIABLE.*"), false)
+
+       t.CheckEquals(scope.DefinedSimilar("VAR.param"), true)
+       t.CheckEquals(scope.DefinedSimilar("VAR.other"), true)
+       t.CheckEquals(scope.DefinedSimilar("VARIABLE.*"), false)
 }
 
 func (s *Suite) Test_Scope_Used(c *check.C) {
@@ -536,13 +603,13 @@ func (s *Suite) Test_Scope_Used(c *check
        mkline := t.NewMkLine("file.mk", 1, "\techo ${VAR.param}")
        scope.Use("VAR.param", mkline, VucRunTime)
 
-       c.Check(scope.Used("VAR.param"), equals, true)
-       c.Check(scope.Used("VAR.other"), equals, false)
-       c.Check(scope.Used("VARIABLE.*"), equals, false)
-
-       c.Check(scope.UsedSimilar("VAR.param"), equals, true)
-       c.Check(scope.UsedSimilar("VAR.other"), equals, true)
-       c.Check(scope.UsedSimilar("VARIABLE.*"), equals, false)
+       t.CheckEquals(scope.Used("VAR.param"), true)
+       t.CheckEquals(scope.Used("VAR.other"), false)
+       t.CheckEquals(scope.Used("VARIABLE.*"), false)
+
+       t.CheckEquals(scope.UsedSimilar("VAR.param"), true)
+       t.CheckEquals(scope.UsedSimilar("VAR.other"), true)
+       t.CheckEquals(scope.UsedSimilar("VARIABLE.*"), false)
 }
 
 func (s *Suite) Test_Scope_DefineAll(c *check.C) {
@@ -560,7 +627,7 @@ func (s *Suite) Test_Scope_DefineAll(c *
        src.Define("VAR", t.NewMkLine("file.mk", 1, "VAR=value"))
        dst.DefineAll(src)
 
-       c.Check(dst.Defined("VAR"), equals, true)
+       t.CheckEquals(dst.Defined("VAR"), true)
 }
 
 func (s *Suite) Test_Scope_FirstDefinition(c *check.C) {
@@ -573,7 +640,7 @@ func (s *Suite) Test_Scope_FirstDefiniti
        scope.Define("VAR", mkline1)
        scope.Define("SNEAKY", mkline2)
 
-       t.Check(scope.FirstDefinition("VAR"), equals, mkline1)
+       t.CheckEquals(scope.FirstDefinition("VAR"), mkline1)
 
        // This call returns nil because it's not a variable assignment
        // and the calling code typically assumes a variable definition.
@@ -595,7 +662,7 @@ func (s *Suite) Test_Scope_LastValue(c *
 
        mklines.Check()
 
-       t.Check(mklines.vars.LastValue("VAR"), equals, "third (conditional)")
+       t.CheckEquals(mklines.vars.LastValue("VAR"), "third (conditional)")
 
        t.CheckOutputLines(
                "WARN: file.mk:2: VAR is defined but not used.")
@@ -608,9 +675,9 @@ func (s *Suite) Test_Scope__no_tracing(c
        scope.Define("VAR.param", t.NewMkLine("fname.mk", 3, "VAR.param=\tvalue"))
        t.DisableTracing()
 
-       t.Check(scope.DefinedSimilar("VAR.param"), equals, true)
-       t.Check(scope.DefinedSimilar("VAR.other"), equals, true)
-       t.Check(scope.DefinedSimilar("OTHER"), equals, false)
+       t.CheckEquals(scope.DefinedSimilar("VAR.param"), true)
+       t.CheckEquals(scope.DefinedSimilar("VAR.other"), true)
+       t.CheckEquals(scope.DefinedSimilar("OTHER"), false)
 }
 
 func (s *Suite) Test_Scope__commented_varassign(c *check.C) {
@@ -620,16 +687,16 @@ func (s *Suite) Test_Scope__commented_va
        scope := NewScope()
        scope.Define("VAR", mkline)
 
-       t.Check(scope.Defined("VAR"), equals, false)
+       t.CheckEquals(scope.Defined("VAR"), false)
        t.Check(scope.FirstDefinition("VAR"), check.IsNil)
        t.Check(scope.LastDefinition("VAR"), check.IsNil)
 
-       t.Check(scope.Mentioned("VAR"), equals, mkline)
-       t.Check(scope.Commented("VAR"), equals, mkline)
+       t.CheckEquals(scope.Mentioned("VAR"), mkline)
+       t.CheckEquals(scope.Commented("VAR"), mkline)
 
        value, found := scope.LastValueFound("VAR")
-       t.Check(value, equals, "")
-       t.Check(found, equals, false)
+       t.CheckEquals(value, "")
+       t.CheckEquals(found, false)
 }
 
 func (s *Suite) Test_Scope_Commented(c *check.C) {
@@ -645,7 +712,7 @@ func (s *Suite) Test_Scope_Commented(c *
        scope.Define("DOCUMENTED", documented)
 
        t.Check(scope.Commented("VAR"), check.IsNil)
-       t.Check(scope.Commented("COMMENTED"), equals, commented)
+       t.CheckEquals(scope.Commented("COMMENTED"), commented)
        t.Check(scope.Commented("DOCUMENTED"), check.IsNil)
        t.Check(scope.Commented("UNKNOWN"), check.IsNil)
 }
@@ -662,57 +729,60 @@ func (s *Suite) Test_Scope_Mentioned(c *
        scope.Define("COMMENTED", commented)
        scope.Define("DOCUMENTED", documented)
 
-       t.Check(scope.Mentioned("VAR"), equals, assigned)
-       t.Check(scope.Mentioned("COMMENTED"), equals, commented)
-       t.Check(scope.Mentioned("DOCUMENTED"), equals, documented)
+       t.CheckEquals(scope.Mentioned("VAR"), assigned)
+       t.CheckEquals(scope.Mentioned("COMMENTED"), commented)
+       t.CheckEquals(scope.Mentioned("DOCUMENTED"), documented)
        t.Check(scope.Mentioned("UNKNOWN"), check.IsNil)
 }
 
 func (s *Suite) Test_naturalLess(c *check.C) {
-       c.Check(naturalLess("", "a"), equals, true)
-       c.Check(naturalLess("a", ""), equals, false)
-
-       c.Check(naturalLess("a", "b"), equals, true)
-       c.Check(naturalLess("b", "a"), equals, false)
-
-       // Numbers are always considered smaller than other characters.
-       c.Check(naturalLess("0", "!"), equals, true)
-       c.Check(naturalLess("!", "0"), equals, false)
-
-       c.Check(naturalLess("0", "a"), equals, true)
-       c.Check(naturalLess("a", "0"), equals, false)
-
-       c.Check(naturalLess("5", "12"), equals, true)
-       c.Check(naturalLess("12", "5"), equals, false)
-
-       c.Check(naturalLess("5", "7"), equals, true)
-       c.Check(naturalLess("7", "5"), equals, false)
+       t := s.Init(c)
 
-       c.Check(naturalLess("000", "0000"), equals, true)
-       c.Check(naturalLess("0000", "000"), equals, false)
+       var elements = []string{
+               "",
+               // Numbers are always considered smaller than other characters.
+               "0", "000", "0000", "5", "7", "00011", "12", "00012", "000111",
+               "!", "a", "a0", "a ", "aa", "ab", "b"}
 
-       c.Check(naturalLess("000", "000"), equals, false)
+       test := func(i int, ie string, j int, je string) {
+               actual := naturalLess(ie, je)
+               expected := i < j
+               if actual != expected {
+                       t.CheckDeepEquals(
+                               []interface{}{i, ie, j, je, actual},
+                               []interface{}{i, ie, j, je, expected})
+               }
+       }
 
-       c.Check(naturalLess("00011", "000111"), equals, true)
-       c.Check(naturalLess("00011", "00012"), equals, true)
+       for i, ie := range elements {
+               for j, je := range elements {
+                       test(i, ie, j, je)
+               }
+       }
 }
 
 func (s *Suite) Test_varnameBase(c *check.C) {
-       c.Check(varnameBase("VAR"), equals, "VAR")
-       c.Check(varnameBase("VAR.param"), equals, "VAR")
-       c.Check(varnameBase(".CURDIR"), equals, ".CURDIR")
+       t := s.Init(c)
+
+       t.CheckEquals(varnameBase("VAR"), "VAR")
+       t.CheckEquals(varnameBase("VAR.param"), "VAR")
+       t.CheckEquals(varnameBase(".CURDIR"), ".CURDIR")
 }
 
 func (s *Suite) Test_varnameParam(c *check.C) {
-       c.Check(varnameParam("VAR"), equals, "")
-       c.Check(varnameParam("VAR.param"), equals, "param")
-       c.Check(varnameParam(".CURDIR"), equals, "")
+       t := s.Init(c)
+
+       t.CheckEquals(varnameParam("VAR"), "")
+       t.CheckEquals(varnameParam("VAR.param"), "param")
+       t.CheckEquals(varnameParam(".CURDIR"), "")
 }
 
 func (s *Suite) Test_varnameCanon(c *check.C) {
-       c.Check(varnameCanon("VAR"), equals, "VAR")
-       c.Check(varnameCanon("VAR.param"), equals, "VAR.*")
-       c.Check(varnameCanon(".CURDIR"), equals, ".CURDIR")
+       t := s.Init(c)
+
+       t.CheckEquals(varnameCanon("VAR"), "VAR")
+       t.CheckEquals(varnameCanon("VAR.param"), "VAR.*")
+       t.CheckEquals(varnameCanon(".CURDIR"), ".CURDIR")
 }
 
 func (s *Suite) Test_FileCache(c *check.C) {
@@ -727,22 +797,22 @@ func (s *Suite) Test_FileCache(c *check.
                "# line 2")
 
        c.Check(cache.Get("Makefile", 0), check.IsNil)
-       c.Check(cache.hits, equals, 0)
-       c.Check(cache.misses, equals, 1)
+       t.CheckEquals(cache.hits, 0)
+       t.CheckEquals(cache.misses, 1)
 
        cache.Put("Makefile", 0, lines)
        c.Check(cache.Get("Makefile", MustSucceed|LogErrors), check.IsNil) // Wrong LoadOptions.
 
        linesFromCache := cache.Get("Makefile", 0)
-       c.Check(linesFromCache.Filename, equals, "Makefile")
+       t.CheckEquals(linesFromCache.Filename, "Makefile")
        c.Check(linesFromCache.Lines, check.HasLen, 2)
-       c.Check(linesFromCache.Lines[0].Filename, equals, "Makefile")
+       t.CheckEquals(linesFromCache.Lines[0].Filename, "Makefile")
 
        // Cache keys are normalized using path.Clean.
        linesFromCache2 := cache.Get("./Makefile", 0)
-       c.Check(linesFromCache2.Filename, equals, "./Makefile")
+       t.CheckEquals(linesFromCache2.Filename, "./Makefile")
        c.Check(linesFromCache2.Lines, check.HasLen, 2)
-       c.Check(linesFromCache2.Lines[0].Filename, equals, "./Makefile")
+       t.CheckEquals(linesFromCache2.Lines[0].Filename, "./Makefile")
 
        cache.Put("file1.mk", 0, lines)
        cache.Put("file2.mk", 0, lines)
@@ -767,8 +837,8 @@ func (s *Suite) Test_FileCache(c *check.
        c.Check(cache.Get("Makefile", 0), check.IsNil)
        c.Check(cache.table, check.HasLen, 1)
        c.Check(cache.mapping, check.HasLen, 1)
-       c.Check(cache.hits, equals, 7)
-       c.Check(cache.misses, equals, 5)
+       t.CheckEquals(cache.hits, 7)
+       t.CheckEquals(cache.misses, 5)
 
        t.CheckOutputLines(
                "TRACE:   FileCache \"Makefile\" with count 4.",
@@ -858,25 +928,29 @@ func (s *Suite) Test_FileCache_Evict__so
 }
 
 func (s *Suite) Test_makeHelp(c *check.C) {
-       c.Check(makeHelp("subst"), equals, confMake+" help topic=subst")
+       t := s.Init(c)
+
+       t.CheckEquals(makeHelp("subst"), confMake+" help topic=subst")
 }
 
 func (s *Suite) Test_hasAlnumPrefix(c *check.C) {
        t := s.Init(c)
 
-       t.Check(hasAlnumPrefix(""), equals, false)
-       t.Check(hasAlnumPrefix("A"), equals, true)
-       t.Check(hasAlnumPrefix(","), equals, false)
+       t.CheckEquals(hasAlnumPrefix(""), false)
+       t.CheckEquals(hasAlnumPrefix("A"), true)
+       t.CheckEquals(hasAlnumPrefix(","), false)
 }
 
 func (s *Suite) Test_Once(c *check.C) {
+       t := s.Init(c)
+
        var once Once
 
-       c.Check(once.FirstTime("str"), equals, true)
-       c.Check(once.FirstTime("str"), equals, false)
-       c.Check(once.FirstTimeSlice("str"), equals, false)
-       c.Check(once.FirstTimeSlice("str", "str2"), equals, true)
-       c.Check(once.FirstTimeSlice("str", "str2"), equals, false)
+       t.CheckEquals(once.FirstTime("str"), true)
+       t.CheckEquals(once.FirstTime("str"), false)
+       t.CheckEquals(once.FirstTimeSlice("str"), false)
+       t.CheckEquals(once.FirstTimeSlice("str", "str2"), true)
+       t.CheckEquals(once.FirstTimeSlice("str", "str2"), false)
 }
 
 func (s *Suite) Test_Once__trace(c *check.C) {
@@ -885,11 +959,11 @@ func (s *Suite) Test_Once__trace(c *chec
        var once Once
        once.Trace = true
 
-       c.Check(once.FirstTime("str"), equals, true)
-       c.Check(once.FirstTime("str"), equals, false)
-       c.Check(once.FirstTimeSlice("str"), equals, false)
-       c.Check(once.FirstTimeSlice("str", "str2"), equals, true)
-       c.Check(once.FirstTimeSlice("str", "str2"), equals, false)
+       t.CheckEquals(once.FirstTime("str"), true)
+       t.CheckEquals(once.FirstTime("str"), false)
+       t.CheckEquals(once.FirstTimeSlice("str"), false)
+       t.CheckEquals(once.FirstTimeSlice("str", "str2"), true)
+       t.CheckEquals(once.FirstTimeSlice("str", "str2"), false)
 
        t.CheckOutputLines(
                "FirstTime: str",
@@ -897,6 +971,7 @@ func (s *Suite) Test_Once__trace(c *chec
 }
 
 func (s *Suite) Test_wrap(c *check.C) {
+       t := s.Init(c)
 
        wrapped := wrap(20,
                "See the pkgsrc guide, section \"Package components, Makefile\":",
@@ -951,18 +1026,22 @@ func (s *Suite) Test_wrap(c *check.C) {
                "A\tB\tC\tD E",
                "veryVeryVeryVeryVeryVeryVeryVeryLong"}
 
-       c.Check(wrapped, deepEquals, expected)
+       t.CheckDeepEquals(wrapped, expected)
 }
 
 func (s *Suite) Test_escapePrintable(c *check.C) {
-       c.Check(escapePrintable(""), equals, "")
-       c.Check(escapePrintable("ASCII only~\n\t"), equals, "ASCII only~\n\t")
-       c.Check(escapePrintable("Beep \u0007 control \u001F"), equals, "Beep <U+0007> control <U+001F>")
-       c.Check(escapePrintable("Bad \xFF character"), equals, "Bad <0xFF> character")
-       c.Check(escapePrintable("Unicode \uFFFD replacement"), equals, "Unicode <U+FFFD> replacement")
+       t := s.Init(c)
+
+       t.CheckEquals(escapePrintable(""), "")
+       t.CheckEquals(escapePrintable("ASCII only~\n\t"), "ASCII only~\n\t")
+       t.CheckEquals(escapePrintable("Beep \u0007 control \u001F"), "Beep <U+0007> control <U+001F>")
+       t.CheckEquals(escapePrintable("Bad \xFF character"), "Bad <0xFF> character")
+       t.CheckEquals(escapePrintable("Unicode \uFFFD replacement"), "Unicode <U+FFFD> replacement")
 }
 
 func (s *Suite) Test_stringSliceLess(c *check.C) {
+       t := s.Init(c)
+
        var elements = [][][]string{
                {nil, {}},
                {{"a"}},
@@ -975,9 +1054,8 @@ func (s *Suite) Test_stringSliceLess(c *
                actual := stringSliceLess(iElement, jElement)
                expected := i < j
                if actual != expected {
-                       c.Check(
+                       t.CheckDeepEquals(
                                []interface{}{i, iElement, j, jElement, actual},
-                               check.DeepEquals,
                                []interface{}{i, iElement, j, jElement, expected})
                }
        }
@@ -996,32 +1074,28 @@ func (s *Suite) Test_stringSliceLess(c *
 func (s *Suite) Test_joinSkipEmpty(c *check.C) {
        t := s.Init(c)
 
-       t.Check(
+       t.CheckDeepEquals(
                joinSkipEmpty(", ", "", "one", "", "", "two", "", "three"),
-               deepEquals,
                "one, two, three")
 }
 
 func (s *Suite) Test_joinSkipEmptyCambridge(c *check.C) {
        t := s.Init(c)
 
-       t.Check(
+       t.CheckDeepEquals(
                joinSkipEmptyCambridge("and", "", "one", "", "", "two", "", "three"),
-               deepEquals,
                "one, two and three")
 
-       t.Check(
+       t.CheckDeepEquals(
                joinSkipEmptyCambridge("and", "", "one", "", ""),
-               deepEquals,
                "one")
 }
 
 func (s *Suite) Test_joinSkipEmptyOxford(c *check.C) {
        t := s.Init(c)
 
-       t.Check(
+       t.CheckDeepEquals(
                joinSkipEmptyOxford("and", "", "one", "", "", "two", "", "three"),
-               deepEquals,
                "one, two, and three")
 }
 
@@ -1029,7 +1103,7 @@ func (s *Suite) Test_newPathMatcher(c *c
        t := s.Init(c)
 
        test := func(pattern string, matchType pathMatchType, matchPattern string) {
-               c.Check(*newPathMatcher(pattern), equals, pathMatcher{matchType, matchPattern, pattern})
+               t.CheckEquals(*newPathMatcher(pattern), pathMatcher{matchType, matchPattern, pattern})
        }
 
        testPanic := func(pattern string) {
@@ -1053,10 +1127,11 @@ func (s *Suite) Test_newPathMatcher(c *c
 }
 
 func (s *Suite) Test_pathMatcher_matches(c *check.C) {
+       t := s.Init(c)
 
        test := func(pattern string, subject string, expected bool) {
                matcher := newPathMatcher(pattern)
-               c.Check(matcher.matches(subject), equals, expected)
+               t.CheckEquals(matcher.matches(subject), expected)
        }
 
        test("", "", true)
@@ -1080,8 +1155,8 @@ func (s *Suite) Test_StringInterner(c *c
 
        si := NewStringInterner()
 
-       t.Check(si.Intern(""), equals, "")
-       t.Check(si.Intern("Hello"), equals, "Hello")
-       t.Check(si.Intern("Hello, world"), equals, "Hello, world")
-       t.Check(si.Intern("Hello, world"[0:5]), equals, "Hello")
+       t.CheckEquals(si.Intern(""), "")
+       t.CheckEquals(si.Intern("Hello"), "Hello")
+       t.CheckEquals(si.Intern("Hello, world"), "Hello, world")
+       t.CheckEquals(si.Intern("Hello, world"[0:5]), "Hello")
 }

Index: pkgsrc/pkgtools/pkglint/files/licenses.go
diff -u pkgsrc/pkgtools/pkglint/files/licenses.go:1.25 pkgsrc/pkgtools/pkglint/files/licenses.go:1.26
--- pkgsrc/pkgtools/pkglint/files/licenses.go:1.25      Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/licenses.go   Sun Jul 14 21:25:47 2019
@@ -9,7 +9,7 @@ type LicenseChecker struct {
 
 func (lc *LicenseChecker) Check(value string, op MkOperator) {
        expanded := resolveVariableRefs(lc.MkLines, value) // For ${PERL5_LICENSE}
-       cond := licenses.Parse(ifelseStr(op == opAssignAppend, "append-placeholder ", "") + expanded)
+       cond := licenses.Parse(condStr(op == opAssignAppend, "append-placeholder ", "") + expanded)
 
        if cond == nil {
                if op == opAssign {

Index: pkgsrc/pkgtools/pkglint/files/line.go
diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.37 pkgsrc/pkgtools/pkglint/files/line.go:1.38
--- pkgsrc/pkgtools/pkglint/files/line.go:1.37  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/line.go       Sun Jul 14 21:25:47 2019
@@ -43,6 +43,10 @@ type Location struct {
        lastLine  int32  // usually the same as firstLine, may differ in Makefiles
 }
 
+func (loc *Location) String() string {
+       return loc.Filename + ":" + loc.Linenos()
+}
+
 func NewLocation(filename string, firstLine, lastLine int) Location {
        return Location{filename, int32(firstLine), int32(lastLine)}
 }
@@ -74,7 +78,7 @@ type Line struct {
 
        raw     []*RawLine // contains the original text including trailing newline
        autofix *Autofix   // any changes that pkglint would like to apply to the line
-       Once
+       once    Once
 
        // XXX: Filename and Basename could be replaced with a pointer to a Lines object.
 }
@@ -128,52 +132,6 @@ func (line *Line) IsCvsID(prefixRe regex
        return m, exp != ""
 }
 
-func (line *Line) showSource(out *SeparatorWriter) {
-       if !G.Logger.Opts.ShowSource {
-               return
-       }
-
-       writeLine := func(prefix, line string) {
-               out.Write(prefix)
-               out.Write(escapePrintable(line))
-               if !hasSuffix(line, "\n") {
-                       out.Write("\n")
-               }
-       }
-
-       printDiff := func(rawLines []*RawLine) {
-               prefix := ">\t"
-               for _, rawLine := range rawLines {
-                       if rawLine.textnl != rawLine.orignl {
-                               prefix = "\t" // Make it look like an actual diff
-                       }
-               }
-
-               for _, rawLine := range rawLines {
-                       if rawLine.textnl != rawLine.orignl {
-                               writeLine("-\t", rawLine.orignl)
-                               if rawLine.textnl != "" {
-                                       writeLine("+\t", rawLine.textnl)
-                               }
-                       } else {
-                               writeLine(prefix, rawLine.orignl)
-                       }
-               }
-       }
-
-       if line.autofix != nil {
-               for _, before := range line.autofix.linesBefore {
-                       writeLine("+\t", before)
-               }
-               printDiff(line.raw)
-               for _, after := range line.autofix.linesAfter {
-                       writeLine("+\t", after)
-               }
-       } else {
-               printDiff(line.raw)
-       }
-}
-
 func (line *Line) Fatalf(format string, args ...interface{}) {
        if trace.Tracing {
                trace.Stepf("Fatalf: %q, %v", format, args)
Index: pkgsrc/pkgtools/pkglint/files/plist_test.go
diff -u pkgsrc/pkgtools/pkglint/files/plist_test.go:1.37 pkgsrc/pkgtools/pkglint/files/plist_test.go:1.38
--- pkgsrc/pkgtools/pkglint/files/plist_test.go:1.37    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/plist_test.go Sun Jul 14 21:25:47 2019
@@ -182,7 +182,7 @@ func (s *Suite) Test_plistLineSorter_Sor
        plines := ck.NewLines(lines)
 
        sorter1 := NewPlistLineSorter(plines)
-       c.Check(sorter1.unsortable, equals, lines.Lines[5])
+       t.CheckEquals(sorter1.unsortable, lines.Lines[5])
 
        cleanedLines := append(append(lines.Lines[0:5], lines.Lines[6:8]...), lines.Lines[9:]...) // Remove ${UNKNOWN} and @exec
 

Index: pkgsrc/pkgtools/pkglint/files/line_test.go
diff -u pkgsrc/pkgtools/pkglint/files/line_test.go:1.18 pkgsrc/pkgtools/pkglint/files/line_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/line_test.go:1.18     Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/line_test.go  Sun Jul 14 21:25:47 2019
@@ -9,7 +9,7 @@ func (s *Suite) Test_RawLine_String(c *c
 
        line := t.NewLine("filename", 123, "text")
 
-       c.Check(line.raw[0].String(), equals, "123:text\n")
+       t.CheckEquals(line.raw[0].String(), "123:text\n")
 }
 
 func (s *Suite) Test_NewLine__assertion(c *check.C) {
@@ -21,10 +21,10 @@ func (s *Suite) Test_NewLine__assertion(
 func (s *Suite) Test_Line_IsMultiline(c *check.C) {
        t := s.Init(c)
 
-       t.Check(t.NewLine("filename", 123, "text").IsMultiline(), equals, false)
-       t.Check(NewLineEOF("filename").IsMultiline(), equals, false)
+       t.CheckEquals(t.NewLine("filename", 123, "text").IsMultiline(), false)
+       t.CheckEquals(NewLineEOF("filename").IsMultiline(), false)
 
-       t.Check(NewLineMulti("filename", 123, 125, "text", nil).IsMultiline(), equals, true)
+       t.CheckEquals(NewLineMulti("filename", 123, 125, "text", nil).IsMultiline(), true)
 }
 
 // In case of a fatal error, pkglint quits in a controlled manner,
Index: pkgsrc/pkgtools/pkglint/files/tools_test.go
diff -u pkgsrc/pkgtools/pkglint/files/tools_test.go:1.18 pkgsrc/pkgtools/pkglint/files/tools_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/tools_test.go:1.18    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/tools_test.go Sun Jul 14 21:25:47 2019
@@ -3,30 +3,32 @@ package pkglint
 import "gopkg.in/check.v1"
 
 func (s *Suite) Test_Tool_UsableAtLoadTime(c *check.C) {
+       t := s.Init(c)
 
        nowhere := Tool{"nowhere", "", false, Nowhere, nil}
-       c.Check(nowhere.UsableAtLoadTime(false), equals, false)
-       c.Check(nowhere.UsableAtLoadTime(true), equals, false)
+       t.CheckEquals(nowhere.UsableAtLoadTime(false), false)
+       t.CheckEquals(nowhere.UsableAtLoadTime(true), false)
 
        load := Tool{"load", "", false, AfterPrefsMk, nil}
-       c.Check(load.UsableAtLoadTime(false), equals, false)
-       c.Check(load.UsableAtLoadTime(true), equals, true)
+       t.CheckEquals(load.UsableAtLoadTime(false), false)
+       t.CheckEquals(load.UsableAtLoadTime(true), true)
 
        run := Tool{"run", "", false, AtRunTime, nil}
-       c.Check(run.UsableAtLoadTime(false), equals, false)
-       c.Check(run.UsableAtLoadTime(true), equals, false)
+       t.CheckEquals(run.UsableAtLoadTime(false), false)
+       t.CheckEquals(run.UsableAtLoadTime(true), false)
 }
 
 func (s *Suite) Test_Tool_UsableAtRunTime(c *check.C) {
+       t := s.Init(c)
 
        nowhere := Tool{"nowhere", "", false, Nowhere, nil}
-       c.Check(nowhere.UsableAtRunTime(), equals, false)
+       t.CheckEquals(nowhere.UsableAtRunTime(), false)
 
        load := Tool{"load", "", false, AfterPrefsMk, nil}
-       c.Check(load.UsableAtRunTime(), equals, true)
+       t.CheckEquals(load.UsableAtRunTime(), true)
 
        run := Tool{"run", "", false, AtRunTime, nil}
-       c.Check(run.UsableAtRunTime(), equals, true)
+       t.CheckEquals(run.UsableAtRunTime(), true)
 }
 
 // USE_TOOLS is an operating-system-dependent variable.
@@ -66,9 +68,9 @@ func (s *Suite) Test_Tools_ParseToolLine
                "_TOOLS.${t}=\t${t}",
                ".endfor")
 
-       mklines.collectDefinedVariables()
+       mklines.collectVariables()
        t.Check(mklines.Tools.byName, check.HasLen, 1)
-       t.Check(mklines.Tools.ByName("tool").String(), equals, "tool:::Nowhere:abc")
+       t.CheckEquals(mklines.Tools.ByName("tool").String(), "tool:::Nowhere:abc")
 
        t.CheckOutputEmpty()
 }
@@ -141,7 +143,7 @@ func (s *Suite) Test_Tools__USE_TOOLS_pr
        t.CheckOutputLines(
                "WARN: ~/module.mk:5: Unknown shell command \"${AWK}\".",
                "WARN: ~/module.mk:5: AWK is used but not defined.",
-               "0 errors and 2 warnings found.",
+               "2 warnings found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -155,14 +157,14 @@ func (s *Suite) Test_Tools__add_varname_
        tools := NewTools()
        tool := tools.Define("tool", "", mkline)
 
-       c.Check(tool.Name, equals, "tool")
-       c.Check(tool.Varname, equals, "")
+       t.CheckEquals(tool.Name, "tool")
+       t.CheckEquals(tool.Varname, "")
 
        // Updates the existing tool definition.
        tools.Define("tool", "TOOL", mkline)
 
-       c.Check(tool.Name, equals, "tool")
-       c.Check(tool.Varname, equals, "TOOL")
+       t.CheckEquals(tool.Name, "tool")
+       t.CheckEquals(tool.Varname, "TOOL")
 }
 
 func (s *Suite) Test_Tools__load_from_infrastructure(c *check.C) {
@@ -191,9 +193,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.String(), equals, "load:::AtRunTime")
-       c.Check(run.String(), equals, "run:::AtRunTime")
-       c.Check(nowhere.String(), equals, "nowhere:::AtRunTime")
+       t.CheckEquals(load.String(), "load:::AtRunTime")
+       t.CheckEquals(run.String(), "run:::AtRunTime")
+       t.CheckEquals(nowhere.String(), "nowhere:::AtRunTime")
 
        // The variable name RUN is reserved by pkgsrc, therefore RUN_CMD.
        varnamesMklines := t.NewMkLines("varnames.mk",
@@ -208,29 +210,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.String(), equals, "load:LOAD::AtRunTime")
-       c.Check(run.String(), equals, "run:RUN_CMD::AtRunTime")
-       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::AtRunTime")
-       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.String(), equals, "load:LOAD::AtRunTime")
-       c.Check(run.String(), equals, "run:RUN_CMD::AtRunTime")
-       c.Check(nowhere.String(), equals, "nowhere:NOWHERE::AtRunTime")
+       t.CheckEquals(load.String(), "load:LOAD::AtRunTime")
+       t.CheckEquals(run.String(), "run:RUN_CMD::AtRunTime")
+       t.CheckEquals(nowhere.String(), "nowhere:NOWHERE::AtRunTime")
+       t.CheckEquals(tools.ByVarname("LOAD"), load)
+       t.CheckEquals(tools.ByVarname("RUN_CMD"), run)
+       t.CheckEquals(tools.ByVarname("NOWHERE"), nowhere)
+       t.CheckEquals(load.String(), "load:LOAD::AtRunTime")
+       t.CheckEquals(run.String(), "run:RUN_CMD::AtRunTime")
+       t.CheckEquals(nowhere.String(), "nowhere:NOWHERE::AtRunTime")
 
        tools.ParseToolLine(dummyMklines, 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.String(), equals, "load:LOAD::AfterPrefsMk")
+       t.CheckEquals(load.String(), "load:LOAD::AfterPrefsMk")
 
        tools.ParseToolLine(dummyMklines, 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.String(), equals, "run:RUN_CMD::AtRunTime")
+       t.CheckEquals(run.String(), "run:RUN_CMD::AtRunTime")
 
        // That's all for parsing tool definitions from the pkgsrc infrastructure.
        // See Test_Tools__package_Makefile for a continuation.
@@ -266,9 +268,9 @@ func (s *Suite) Test_Tools__package_Make
        before := tools.ByName("pkg-before-prefs")
        after := tools.ByName("pkg-after-prefs")
 
-       c.Check(load.UsableAtRunTime(), equals, true)
-       c.Check(run.UsableAtRunTime(), equals, true)
-       c.Check(nowhere.UsableAtRunTime(), equals, true)
+       t.CheckEquals(load.UsableAtRunTime(), true)
+       t.CheckEquals(run.UsableAtRunTime(), true)
+       t.CheckEquals(nowhere.UsableAtRunTime(), true)
 
        // The seenPrefs variable is only relevant for the package Makefile.
        // All other files must not use the tools at load time.
@@ -279,16 +281,16 @@ func (s *Suite) Test_Tools__package_Make
 
        tools.ParseToolLine(dummyMklines, t.NewMkLine("Makefile", 2, "USE_TOOLS+=     pkg-before-prefs"), false, true)
 
-       c.Check(before.Validity, equals, AfterPrefsMk)
-       c.Check(tools.SeenPrefs, equals, false)
+       t.CheckEquals(before.Validity, AfterPrefsMk)
+       t.CheckEquals(tools.SeenPrefs, false)
 
        tools.ParseToolLine(dummyMklines, t.NewMkLine("Makefile", 3, ".include \"../../mk/bsd.prefs.mk\""), false, true)
 
-       c.Check(tools.SeenPrefs, equals, true)
+       t.CheckEquals(tools.SeenPrefs, true)
 
        tools.ParseToolLine(dummyMklines, t.NewMkLine("Makefile", 4, "USE_TOOLS+=     pkg-after-prefs"), false, true)
 
-       c.Check(after.Validity, equals, AtRunTime)
+       t.CheckEquals(after.Validity, AtRunTime)
 }
 
 func (s *Suite) Test_Tools__builtin_mk(c *check.C) {
@@ -359,7 +361,7 @@ func (s *Suite) Test_Tools__implicit_def
        // In other words, this test is only there for the code coverage.
        t.FinishSetUp()
 
-       c.Check(G.Pkgsrc.Tools.ByName("run").String(), equals, "run:::AtRunTime")
+       t.CheckEquals(G.Pkgsrc.Tools.ByName("run").String(), "run:::AtRunTime")
 }
 
 func (s *Suite) Test_Tools__both_prefs_and_pkg_mk(c *check.C) {
@@ -378,7 +380,7 @@ func (s *Suite) Test_Tools__both_prefs_a
        // grants more use cases (load time + run time), therefore it wins.
        t.FinishSetUp()
 
-       c.Check(G.Pkgsrc.Tools.ByName("both").Validity, equals, AfterPrefsMk)
+       t.CheckEquals(G.Pkgsrc.Tools.ByName("both").Validity, AfterPrefsMk)
 }
 
 func (s *Suite) Test_Tools__tools_having_the_same_variable_name(c *check.C) {
@@ -396,10 +398,10 @@ func (s *Suite) Test_Tools__tools_having
 
        t.FinishSetUp()
 
-       c.Check(G.Pkgsrc.Tools.ByName("awk").Validity, equals, AfterPrefsMk)
-       c.Check(G.Pkgsrc.Tools.ByName("sed").Validity, equals, AfterPrefsMk)
-       c.Check(G.Pkgsrc.Tools.ByName("gawk").Validity, equals, Nowhere)
-       c.Check(G.Pkgsrc.Tools.ByName("gsed").Validity, equals, Nowhere)
+       t.CheckEquals(G.Pkgsrc.Tools.ByName("awk").Validity, AfterPrefsMk)
+       t.CheckEquals(G.Pkgsrc.Tools.ByName("sed").Validity, AfterPrefsMk)
+       t.CheckEquals(G.Pkgsrc.Tools.ByName("gawk").Validity, Nowhere)
+       t.CheckEquals(G.Pkgsrc.Tools.ByName("gsed").Validity, Nowhere)
 
        t.EnableTracingToLog()
        G.Pkgsrc.Tools.Trace()
@@ -442,8 +444,10 @@ func (s *Suite) Test_Tools__tools_having
 }
 
 func (s *Suite) Test_ToolTime_String(c *check.C) {
-       c.Check(LoadTime.String(), equals, "LoadTime")
-       c.Check(RunTime.String(), equals, "RunTime")
+       t := s.Init(c)
+
+       t.CheckEquals(LoadTime.String(), "LoadTime")
+       t.CheckEquals(RunTime.String(), "RunTime")
 }
 
 func (s *Suite) Test_Tools__var(c *check.C) {
@@ -476,6 +480,8 @@ 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) {
+       t := s.Init(c)
+
        nonGnu := NewTools()
        nonGnu.def("sed", "SED", false, AfterPrefsMk, nil)
 
@@ -486,21 +492,21 @@ func (s *Suite) Test_Tools_Fallback__too
        local1.def("sed", "SED", false, AfterPrefsMk, nil)
        local1.Fallback(gnu)
 
-       c.Check(local1.ByName("sed").Validity, equals, AfterPrefsMk)
-       c.Check(local1.ByName("gsed").Validity, equals, Nowhere)
+       t.CheckEquals(local1.ByName("sed").Validity, AfterPrefsMk)
+       t.CheckEquals(local1.ByName("gsed").Validity, Nowhere)
 
        local2 := NewTools()
        local2.def("gsed", "SED", false, Nowhere, nil)
        local2.Fallback(nonGnu)
 
-       c.Check(local2.ByName("sed").Validity, equals, AfterPrefsMk)
-       c.Check(local2.ByName("gsed").Validity, equals, Nowhere)
+       t.CheckEquals(local2.ByName("sed").Validity, AfterPrefsMk)
+       t.CheckEquals(local2.ByName("gsed").Validity, Nowhere)
 
        // No matter in which order the tool definitions are encountered,
        // the non-GNU version is always chosen since the GNU version is
        // not available at all.
-       c.Check(local1.ByVarname("SED").String(), equals, "sed:SED::AfterPrefsMk")
-       c.Check(local2.ByVarname("SED").String(), equals, "sed:SED::AfterPrefsMk")
+       t.CheckEquals(local1.ByVarname("SED").String(), "sed:SED::AfterPrefsMk")
+       t.CheckEquals(local2.ByVarname("SED").String(), "sed:SED::AfterPrefsMk")
 }
 
 // Demonstrates how the Tools type handles tools that share the same
@@ -511,6 +517,7 @@ 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) {
+       t := s.Init(c)
 
        // This simulates a tool defined in the tools framework but not added
        // to USE_TOOLS, neither by bsd.prefs.mk nor by bsd.pkg.mk.
@@ -530,18 +537,18 @@ func (s *Suite) Test_Tools_Fallback__too
        local1.def("sed", "SED", false, Nowhere, nil)
        local1.Fallback(gnu)
 
-       c.Check(local1.ByName("sed").Validity, equals, Nowhere)
-       c.Check(local1.ByName("gsed").Validity, equals, AfterPrefsMk)
+       t.CheckEquals(local1.ByName("sed").Validity, Nowhere)
+       t.CheckEquals(local1.ByName("gsed").Validity, AfterPrefsMk)
 
        local2 := NewTools()
        local2.def("gsed", "SED", false, AfterPrefsMk, []string{"sed"})
        local2.Fallback(nonGnu)
 
-       c.Check(local2.ByName("sed").Validity, equals, AfterPrefsMk)
-       c.Check(local2.ByName("gsed").Validity, equals, AfterPrefsMk)
+       t.CheckEquals(local2.ByName("sed").Validity, AfterPrefsMk)
+       t.CheckEquals(local2.ByName("gsed").Validity, AfterPrefsMk)
 
-       c.Check(local1.ByVarname("SED").String(), equals, "sed:SED::AfterPrefsMk")
-       c.Check(local2.ByVarname("SED").String(), equals, "sed:SED::AfterPrefsMk")
+       t.CheckEquals(local1.ByVarname("SED").String(), "sed:SED::AfterPrefsMk")
+       t.CheckEquals(local2.ByVarname("SED").String(), "sed:SED::AfterPrefsMk")
 }
 
 func (s *Suite) Test_Tools_Fallback__called_twice(c *check.C) {
@@ -572,21 +579,21 @@ func (s *Suite) Test_Tools__aliases(c *c
                infraTools.ParseToolLine(mklines, mkline, false, false)
        })
 
-       c.Check(infraTools.ByName("sed").String(), equals, "sed:::AtRunTime")
-       c.Check(infraTools.ByName("gsed").String(), equals, "gsed:::AtRunTime:sed")
+       t.CheckEquals(infraTools.ByName("sed").String(), "sed:::AtRunTime")
+       t.CheckEquals(infraTools.ByName("gsed").String(), "gsed:::AtRunTime:sed")
 
        pkgTools := NewTools()
        pkgTools.Fallback(infraTools)
 
-       c.Check(pkgTools.ByName("sed").String(), equals, "sed:::AtRunTime")
-       c.Check(pkgTools.ByName("gsed").String(), equals, "gsed:::AtRunTime:sed")
+       t.CheckEquals(pkgTools.ByName("sed").String(), "sed:::AtRunTime")
+       t.CheckEquals(pkgTools.ByName("gsed").String(), "gsed:::AtRunTime:sed")
 
        mkline := t.NewMkLine("Makefile", 123, "USE_TOOLS+=\tgsed")
        pkgTools.ParseToolLine(mklines, mkline, false, false)
 
        // Since sed is an alias of gsed, it gets the same validity.
-       c.Check(pkgTools.ByName("sed").String(), equals, "sed:::AfterPrefsMk")
-       c.Check(pkgTools.ByName("gsed").String(), equals, "gsed:::AfterPrefsMk:sed")
+       t.CheckEquals(pkgTools.ByName("sed").String(), "sed:::AfterPrefsMk")
+       t.CheckEquals(pkgTools.ByName("gsed").String(), "gsed:::AfterPrefsMk:sed")
 }
 
 func (s *Suite) Test_Tools__aliases_in_for_loop(c *check.C) {
@@ -600,10 +607,11 @@ func (s *Suite) Test_Tools__aliases_in_f
                "TOOLS_ALIASES.ggrep+=\t${t}",
                ".endfor")
 
-       mklines.collectDefinedVariables() // calls ParseToolLine internally
+       mklines.collectVariables() // calls ParseToolLine internally
 
-       c.Check(mklines.Tools.ByName("ggrep").Aliases,
-               deepEquals, []string{"grep", "egrep", "fgrep"})
+       t.CheckDeepEquals(
+               mklines.Tools.ByName("ggrep").Aliases,
+               []string{"grep", "egrep", "fgrep"})
 }
 
 // The cmake tool is included conditionally. The condition is so simple that
Index: pkgsrc/pkgtools/pkglint/files/toplevel_test.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.18 pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/toplevel_test.go:1.18 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/toplevel_test.go      Sun Jul 14 21:25:47 2019
@@ -25,7 +25,6 @@ func (s *Suite) Test_CheckdirToplevel(c 
        CheckdirToplevel(t.File("."))
 
        t.CheckOutputLines(
-               "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.",
@@ -101,7 +100,7 @@ func (s *Suite) Test_CheckdirToplevel__r
 
        t.CheckOutputLines(
                "WARN: ~/category/package/Makefile:20: UNKNOWN is defined but not used.",
-               "0 errors and 1 warning found.",
+               "1 warning found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -130,6 +129,28 @@ func (s *Suite) Test_CheckdirToplevel__r
        t.CheckOutputLines(
                "WARN: ~/category/package/Makefile:20: UNKNOWN is defined but not used.",
                "WARN: ~/licenses/gnu-gpl-v2: This license seems to be unused.",
-               "0 errors and 2 warnings found.",
+               "2 warnings found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
+
+func (s *Suite) Test_CheckdirToplevel__indentation(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPkgsrc()
+       t.CreateFileLines("category1/Makefile")
+       t.CreateFileLines("category2/Makefile")
+       t.CreateFileLines("Makefile",
+               MkCvsID,
+               "",
+               "SUBDIR+=\tcategory1",
+               "SUBDIR+=\t\tcategory2")
+
+       t.Main("-Wall", ".")
+
+       t.CheckOutputLines(
+               "NOTE: ~/Makefile:4: This variable value should be aligned to column 17.",
+               "Looks fine.",
+               "(Run \"pkglint -e\" to show explanations.)",
+               "(Run \"pkglint -fs\" to show what can be fixed automatically.)",
+               "(Run \"pkglint -F\" to automatically fix some issues.)")
+}

Index: pkgsrc/pkgtools/pkglint/files/linelexer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/linelexer_test.go:1.2 pkgsrc/pkgtools/pkglint/files/linelexer_test.go:1.3
--- pkgsrc/pkgtools/pkglint/files/linelexer_test.go:1.2 Thu Feb 21 22:49:03 2019
+++ pkgsrc/pkgtools/pkglint/files/linelexer_test.go     Sun Jul 14 21:25:47 2019
@@ -43,9 +43,9 @@ func (s *Suite) Test_LinesLexer_SkipPref
                "line 2")
        llex := NewLinesLexer(lines)
 
-       t.Check(llex.SkipPrefix("line 1"), equals, true)
-       t.Check(llex.SkipPrefix("line 1"), equals, false)
-       t.Check(llex.SkipPrefix("line 2"), equals, true)
-       t.Check(llex.SkipPrefix("line 2"), equals, false)
-       t.Check(llex.SkipPrefix(""), equals, false)
+       t.CheckEquals(llex.SkipPrefix("line 1"), true)
+       t.CheckEquals(llex.SkipPrefix("line 1"), false)
+       t.CheckEquals(llex.SkipPrefix("line 2"), true)
+       t.CheckEquals(llex.SkipPrefix("line 2"), false)
+       t.CheckEquals(llex.SkipPrefix(""), false)
 }
Index: pkgsrc/pkgtools/pkglint/files/mktokenslexer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mktokenslexer_test.go:1.2 pkgsrc/pkgtools/pkglint/files/mktokenslexer_test.go:1.3
--- pkgsrc/pkgtools/pkglint/files/mktokenslexer_test.go:1.2     Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mktokenslexer_test.go Sun Jul 14 21:25:47 2019
@@ -10,7 +10,7 @@ func (s *Suite) Test_MkTokensLexer__empt
 
        lexer := NewMkTokensLexer(nil)
 
-       t.Check(lexer.EOF(), equals, true)
+       t.CheckEquals(lexer.EOF(), true)
 }
 
 // A slice of a single token behaves like textproc.Lexer.
@@ -19,13 +19,13 @@ func (s *Suite) Test_MkTokensLexer__sing
 
        lexer := NewMkTokensLexer([]*MkToken{{"\\# $$ [#] $V", nil}})
 
-       t.Check(lexer.SkipByte('\\'), equals, true)
-       t.Check(lexer.Rest(), equals, "# $$ [#] $V")
-       t.Check(lexer.SkipByte('#'), equals, true)
-       t.Check(lexer.NextHspace(), equals, " ")
-       t.Check(lexer.NextBytesSet(textproc.Space.Inverse()), equals, "$$")
-       t.Check(lexer.Skip(len(lexer.Rest())), equals, true)
-       t.Check(lexer.EOF(), equals, true)
+       t.CheckEquals(lexer.SkipByte('\\'), true)
+       t.CheckEquals(lexer.Rest(), "# $$ [#] $V")
+       t.CheckEquals(lexer.SkipByte('#'), true)
+       t.CheckEquals(lexer.NextHspace(), " ")
+       t.CheckEquals(lexer.NextBytesSet(textproc.Space.Inverse()), "$$")
+       t.CheckEquals(lexer.Skip(len(lexer.Rest())), true)
+       t.CheckEquals(lexer.EOF(), true)
 }
 
 // If the first element of the slice is a variable use, none of the plain
@@ -39,9 +39,9 @@ func (s *Suite) Test_MkTokensLexer__sing
 
        lexer := NewMkTokensLexer([]*MkToken{{"${VAR:Mpattern}", NewMkVarUse("VAR", "Mpattern")}})
 
-       t.Check(lexer.EOF(), equals, false)
-       t.Check(lexer.PeekByte(), equals, -1)
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR", "Mpattern"))
+       t.CheckEquals(lexer.EOF(), false)
+       t.CheckEquals(lexer.PeekByte(), -1)
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR", "Mpattern"))
 }
 
 func (s *Suite) Test_MkTokensLexer__plain_then_varuse(c *check.C) {
@@ -51,9 +51,9 @@ func (s *Suite) Test_MkTokensLexer__plai
                {"plain text", nil},
                {"${VAR:Mpattern}", NewMkVarUse("VAR", "Mpattern")}})
 
-       t.Check(lexer.NextBytesSet(textproc.Digit.Inverse()), equals, "plain text")
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR", "Mpattern"))
-       t.Check(lexer.EOF(), equals, true)
+       t.CheckEquals(lexer.NextBytesSet(textproc.Digit.Inverse()), "plain text")
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR", "Mpattern"))
+       t.CheckEquals(lexer.EOF(), true)
 }
 
 func (s *Suite) Test_MkTokensLexer__varuse_varuse_varuse(c *check.C) {
@@ -64,9 +64,9 @@ func (s *Suite) Test_MkTokensLexer__varu
                {"${VAR:Mpattern}", NewMkVarUse("VAR", "Mpattern")},
                {"${.TARGET}", NewMkVarUse(".TARGET")}})
 
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("dirs", "O", "u"))
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR", "Mpattern"))
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse(".TARGET"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("dirs", "O", "u"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR", "Mpattern"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse(".TARGET"))
        t.Check(lexer.NextVarUse(), check.IsNil)
 }
 
@@ -79,13 +79,13 @@ func (s *Suite) Test_MkTokensLexer__mark
                {"${.TARGET}", NewMkVarUse(".TARGET")}})
 
        start := lexer.Mark()
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("dirs", "O", "u"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("dirs", "O", "u"))
        middle := lexer.Mark()
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}${.TARGET}")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}${.TARGET}")
        lexer.Reset(start)
-       t.Check(lexer.Rest(), equals, "${dirs:O:u}${VAR:Mpattern}${.TARGET}")
+       t.CheckEquals(lexer.Rest(), "${dirs:O:u}${VAR:Mpattern}${.TARGET}")
        lexer.Reset(middle)
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}${.TARGET}")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}${.TARGET}")
 }
 
 func (s *Suite) Test_MkTokensLexer__mark_reset_since_inside_plain_text(c *check.C) {
@@ -97,13 +97,13 @@ func (s *Suite) Test_MkTokensLexer__mark
                {"rest", nil}})
 
        start := lexer.Mark()
-       t.Check(lexer.NextBytesSet(textproc.Alpha), equals, "plain")
+       t.CheckEquals(lexer.NextBytesSet(textproc.Alpha), "plain")
        middle := lexer.Mark()
-       t.Check(lexer.Rest(), equals, " text${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), " text${VAR:Mpattern}rest")
        lexer.Reset(start)
-       t.Check(lexer.Rest(), equals, "plain text${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "plain text${VAR:Mpattern}rest")
        lexer.Reset(middle)
-       t.Check(lexer.Rest(), equals, " text${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), " text${VAR:Mpattern}rest")
 }
 
 func (s *Suite) Test_MkTokensLexer__mark_reset_since_after_plain_text(c *check.C) {
@@ -115,13 +115,13 @@ func (s *Suite) Test_MkTokensLexer__mark
                {"rest", nil}})
 
        start := lexer.Mark()
-       t.Check(lexer.SkipString("plain text"), equals, true)
+       t.CheckEquals(lexer.SkipString("plain text"), true)
        end := lexer.Mark()
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}rest")
        lexer.Reset(start)
-       t.Check(lexer.Rest(), equals, "plain text${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "plain text${VAR:Mpattern}rest")
        lexer.Reset(end)
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}rest")
 }
 
 func (s *Suite) Test_MkTokensLexer__mark_reset_since_after_varuse(c *check.C) {
@@ -132,13 +132,13 @@ func (s *Suite) Test_MkTokensLexer__mark
                {"rest", nil}})
 
        start := lexer.Mark()
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR", "Mpattern"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR", "Mpattern"))
        end := lexer.Mark()
-       t.Check(lexer.Rest(), equals, "rest")
+       t.CheckEquals(lexer.Rest(), "rest")
        lexer.Reset(start)
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}rest")
        lexer.Reset(end)
-       t.Check(lexer.Rest(), equals, "rest")
+       t.CheckEquals(lexer.Rest(), "rest")
 }
 
 func (s *Suite) Test_MkTokensLexer__multiple_marks_in_same_plain_text(c *check.C) {
@@ -150,17 +150,17 @@ func (s *Suite) Test_MkTokensLexer__mult
                {"rest", nil}})
 
        start := lexer.Mark()
-       t.Check(lexer.NextString("plain "), equals, "plain ")
+       t.CheckEquals(lexer.NextString("plain "), "plain ")
        middle := lexer.Mark()
-       t.Check(lexer.NextString("text"), equals, "text")
+       t.CheckEquals(lexer.NextString("text"), "text")
        end := lexer.Mark()
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}rest")
        lexer.Reset(start)
-       t.Check(lexer.Rest(), equals, "plain text${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "plain text${VAR:Mpattern}rest")
        lexer.Reset(middle)
-       t.Check(lexer.Rest(), equals, "text${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "text${VAR:Mpattern}rest")
        lexer.Reset(end)
-       t.Check(lexer.Rest(), equals, "${VAR:Mpattern}rest")
+       t.CheckEquals(lexer.Rest(), "${VAR:Mpattern}rest")
 }
 
 func (s *Suite) Test_MkTokensLexer__multiple_marks_in_varuse(c *check.C) {
@@ -172,21 +172,21 @@ func (s *Suite) Test_MkTokensLexer__mult
                {"${VAR3}", NewMkVarUse("VAR3")}})
 
        start := lexer.Mark()
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR1"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR1"))
        middle := lexer.Mark()
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR2"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR2"))
        further := lexer.Mark()
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR3"))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR3"))
        end := lexer.Mark()
-       t.Check(lexer.Rest(), equals, "")
+       t.CheckEquals(lexer.Rest(), "")
        lexer.Reset(middle)
-       t.Check(lexer.Rest(), equals, "${VAR2}${VAR3}")
+       t.CheckEquals(lexer.Rest(), "${VAR2}${VAR3}")
        lexer.Reset(further)
-       t.Check(lexer.Rest(), equals, "${VAR3}")
+       t.CheckEquals(lexer.Rest(), "${VAR3}")
        lexer.Reset(start)
-       t.Check(lexer.Rest(), equals, "${VAR1}${VAR2}${VAR3}")
+       t.CheckEquals(lexer.Rest(), "${VAR1}${VAR2}${VAR3}")
        lexer.Reset(end)
-       t.Check(lexer.Rest(), equals, "")
+       t.CheckEquals(lexer.Rest(), "")
 }
 
 func (s *Suite) Test_MkTokensLexer__EOF_before_plain_text(c *check.C) {
@@ -194,7 +194,7 @@ func (s *Suite) Test_MkTokensLexer__EOF_
 
        lexer := NewMkTokensLexer([]*MkToken{{"rest", nil}})
 
-       t.Check(lexer.EOF(), equals, false)
+       t.CheckEquals(lexer.EOF(), false)
 }
 
 func (s *Suite) Test_MkTokensLexer__EOF_before_varuse(c *check.C) {
@@ -202,7 +202,7 @@ func (s *Suite) Test_MkTokensLexer__EOF_
 
        lexer := NewMkTokensLexer([]*MkToken{{"${VAR}", NewMkVarUse("VAR")}})
 
-       t.Check(lexer.EOF(), equals, false)
+       t.CheckEquals(lexer.EOF(), false)
 }
 
 // When the MkTokensLexer is constructed, it gets a copy of the tokens array.
@@ -221,12 +221,12 @@ func (s *Suite) Test_MkTokensLexer__cons
        tokens := []*MkToken{{"${VAR}", NewMkVarUse("VAR")}}
        lexer := NewMkTokensLexer(tokens)
 
-       t.Check(lexer.Rest(), equals, "${VAR}")
+       t.CheckEquals(lexer.Rest(), "${VAR}")
 
        tokens[0].Text = "modified text"
        tokens[0].Varuse = NewMkVarUse("MODIFIED", "Mpattern")
 
-       t.Check(lexer.Rest(), equals, "modified text")
+       t.CheckEquals(lexer.Rest(), "modified text")
 }
 
 func (s *Suite) Test_MkTokensLexer__peek_after_varuse(c *check.C) {
@@ -237,11 +237,11 @@ func (s *Suite) Test_MkTokensLexer__peek
                {"${VAR}", NewMkVarUse("VAR")},
                {"text", nil}})
 
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR"))
-       t.Check(lexer.PeekByte(), equals, -1)
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR"))
+       t.CheckEquals(lexer.PeekByte(), -1)
 
-       t.Check(lexer.NextVarUse(), deepEquals, NewMkVarUse("VAR"))
-       t.Check(lexer.PeekByte(), equals, int('t'))
+       t.CheckDeepEquals(lexer.NextVarUse(), NewMkVarUse("VAR"))
+       t.CheckEquals(lexer.PeekByte(), int('t'))
 }
 
 func (s *Suite) Test_MkTokensLexer__varuse_when_plain_text(c *check.C) {
@@ -250,9 +250,9 @@ func (s *Suite) Test_MkTokensLexer__varu
        lexer := NewMkTokensLexer([]*MkToken{{"text", nil}})
 
        t.Check(lexer.NextVarUse(), check.IsNil)
-       t.Check(lexer.NextString("te"), equals, "te")
+       t.CheckEquals(lexer.NextString("te"), "te")
        t.Check(lexer.NextVarUse(), check.IsNil)
-       t.Check(lexer.NextString("xt"), equals, "xt")
+       t.CheckEquals(lexer.NextString("xt"), "xt")
        t.Check(lexer.NextVarUse(), check.IsNil)
 }
 
@@ -268,9 +268,9 @@ func (s *Suite) Test_MkTokensLexer__adja
                {"text2", nil}})
 
        // Returns false since the string is distributed over two separate tokens.
-       t.Check(lexer.SkipString("text1text2"), equals, false)
+       t.CheckEquals(lexer.SkipString("text1text2"), false)
 
-       t.Check(lexer.SkipString("text1"), equals, true)
+       t.CheckEquals(lexer.SkipString("text1"), true)
 
        // This returns false since the internal lexer is not advanced to the
        // next text token. To do that, all methods from the internal lexer
@@ -279,12 +279,12 @@ func (s *Suite) Test_MkTokensLexer__adja
        //
        // Since this situation doesn't occur in practice, there's no point in
        // implementing it.
-       t.Check(lexer.SkipString("text2"), equals, false)
+       t.CheckEquals(lexer.SkipString("text2"), false)
 
        // Just for covering the "Varuse != nil" branch in MkTokensLexer.NextVarUse.
        t.Check(lexer.NextVarUse(), check.IsNil)
 
        // The string is still not found since the next token is only consumed
        // by the NextVarUse above if it is indeed a VarUse.
-       t.Check(lexer.SkipString("text2"), equals, false)
+       t.CheckEquals(lexer.SkipString("text2"), false)
 }
Index: pkgsrc/pkgtools/pkglint/files/paragraph.go
diff -u pkgsrc/pkgtools/pkglint/files/paragraph.go:1.2 pkgsrc/pkgtools/pkglint/files/paragraph.go:1.3
--- pkgsrc/pkgtools/pkglint/files/paragraph.go:1.2      Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/paragraph.go  Sun Jul 14 21:25:47 2019
@@ -62,8 +62,8 @@ func (p *Paragraph) AlignTo(column int) 
                        return
                }
 
-               trimmed := strings.TrimRightFunc(align, isHspaceRune)
-               newSpace := strings.Repeat("\t", (7+column-tabWidth(trimmed))/8)
+               trimmed := rtrimHspace(align)
+               newSpace := alignmentAfter(trimmed, column)
 
                fix := mkline.Autofix()
                fix.Notef(SilentAutofixFormat)

Index: pkgsrc/pkgtools/pkglint/files/logging.go
diff -u pkgsrc/pkgtools/pkglint/files/logging.go:1.24 pkgsrc/pkgtools/pkglint/files/logging.go:1.25
--- pkgsrc/pkgtools/pkglint/files/logging.go:1.24       Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/logging.go    Sun Jul 14 21:25:47 2019
@@ -23,6 +23,7 @@ type Logger struct {
 
        errors                int
        warnings              int
+       notes                 int
        explanationsAvailable bool
        autofixAvailable      bool
 }
@@ -114,9 +115,21 @@ func (l *Logger) ShowSummary() {
        }
 
        if l.errors != 0 || l.warnings != 0 {
-               l.out.Write(sprintf("%d %s and %d %s found.\n",
-                       l.errors, ifelseStr(l.errors == 1, "error", "errors"),
-                       l.warnings, ifelseStr(l.warnings == 1, "warning", "warnings")))
+               num := func(n int, singular, plural string) string {
+                       if n == 0 {
+                               return ""
+                       } else if n == 1 {
+                               return sprintf("%d %s", n, singular)
+                       } else {
+                               return sprintf("%d %s", n, plural)
+                       }
+               }
+
+               l.out.Write(sprintf("%s found.\n",
+                       joinSkipEmptyCambridge("and",
+                               num(l.errors, "error", "errors"),
+                               num(l.warnings, "warning", "warnings"),
+                               num(l.notes, "note", "notes"))))
        } else {
                l.out.WriteLine("Looks fine.")
        }
@@ -174,7 +187,7 @@ func (l *Logger) Diag(line *Line, level 
        }
 
        if l.Opts.ShowSource {
-               line.showSource(l.out)
+               l.showSource(line)
                l.Logf(level, filename, linenos, format, msg)
                l.out.Separate()
        } else {
@@ -182,6 +195,53 @@ func (l *Logger) Diag(line *Line, level 
        }
 }
 
+func (l *Logger) showSource(line *Line) {
+       if !G.Logger.Opts.ShowSource {
+               return
+       }
+
+       out := l.out
+       writeLine := func(prefix, line string) {
+               out.Write(prefix)
+               out.Write(escapePrintable(line))
+               if !hasSuffix(line, "\n") {
+                       out.Write("\n")
+               }
+       }
+
+       printDiff := func(rawLines []*RawLine) {
+               prefix := ">\t"
+               for _, rawLine := range rawLines {
+                       if rawLine.textnl != rawLine.orignl {
+                               prefix = "\t" // Make it look like an actual diff
+                       }
+               }
+
+               for _, rawLine := range rawLines {
+                       if rawLine.textnl != rawLine.orignl {
+                               writeLine("-\t", rawLine.orignl)
+                               if rawLine.textnl != "" {
+                                       writeLine("+\t", rawLine.textnl)
+                               }
+                       } else {
+                               writeLine(prefix, rawLine.orignl)
+                       }
+               }
+       }
+
+       if line.autofix != nil {
+               for _, before := range line.autofix.linesBefore {
+                       writeLine("+\t", before)
+               }
+               printDiff(line.raw)
+               for _, after := range line.autofix.linesAfter {
+                       writeLine("+\t", after)
+               }
+       } else {
+               printDiff(line.raw)
+       }
+}
+
 func (l *Logger) Logf(level *LogLevel, filename, lineno, format, msg string) {
        if l.suppressDiag {
                l.suppressDiag = false
@@ -213,9 +273,9 @@ func (l *Logger) Logf(level *LogLevel, f
                out = l.err
        }
 
-       filenameSep := ifelseStr(filename != "", ": ", "")
-       effLineno := ifelseStr(filename != "", lineno, "")
-       linenoSep := ifelseStr(effLineno != "", ":", "")
+       filenameSep := condStr(filename != "", ": ", "")
+       effLineno := condStr(filename != "", lineno, "")
+       linenoSep := condStr(effLineno != "", ":", "")
        var diag string
        if l.Opts.GccOutput {
                diag = sprintf("%s%s%s%s%s: %s\n", filename, linenoSep, effLineno, filenameSep, level.GccName, msg)
@@ -231,6 +291,8 @@ func (l *Logger) Logf(level *LogLevel, f
                l.errors++
        case Warn:
                l.warnings++
+       case Note:
+               l.notes++
        }
 }
 

Index: pkgsrc/pkgtools/pkglint/files/logging_test.go
diff -u pkgsrc/pkgtools/pkglint/files/logging_test.go:1.17 pkgsrc/pkgtools/pkglint/files/logging_test.go:1.18
--- pkgsrc/pkgtools/pkglint/files/logging_test.go:1.17  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/logging_test.go       Sun Jul 14 21:25:47 2019
@@ -10,24 +10,28 @@ import (
 // Suppressing duplicate messages or filtering messages happens
 // in other methods of the Logger, namely Relevant, FirstTime, Diag.
 func (s *Suite) Test_Logger_Logf(c *check.C) {
+       t := s.Init(c)
+
        var sw strings.Builder
        logger := Logger{out: NewSeparatorWriter(&sw)}
 
        logger.Logf(Error, "filename", "3", "Blue should be %s.", "Blue should be orange.")
 
-       c.Check(sw.String(), equals, ""+
+       t.CheckEquals(sw.String(), ""+
                "ERROR: filename:3: Blue should be orange.\n")
 }
 
 // Logf doesn't filter duplicates, but Diag does.
 func (s *Suite) Test_Logger_Logf__duplicates(c *check.C) {
+       t := s.Init(c)
+
        var sw strings.Builder
        logger := Logger{out: NewSeparatorWriter(&sw)}
 
        logger.Logf(Error, "filename", "3", "Blue should be %s.", "Blue should be orange.")
        logger.Logf(Error, "filename", "3", "Blue should be %s.", "Blue should be orange.")
 
-       c.Check(sw.String(), equals, ""+
+       t.CheckEquals(sw.String(), ""+
                "ERROR: filename:3: Blue should be orange.\n"+
                "ERROR: filename:3: Blue should be orange.\n")
 }
@@ -46,7 +50,7 @@ func (s *Suite) Test_Logger_Logf__mixed_
        logger.Diag(line, Error, "Diag %s.", "1") // Duplicate, therefore suppressed
        logger.Logf(Error, "filename", "3", "Logf output 3.", "Logf output 3.")
 
-       c.Check(sw.String(), equals, ""+
+       t.CheckEquals(sw.String(), ""+
                "ERROR: filename:3: Logf output 1.\n"+
                "ERROR: filename:3: Diag 1.\n"+
                "ERROR: filename:3: Logf output 2.\n"+
@@ -54,6 +58,8 @@ func (s *Suite) Test_Logger_Logf__mixed_
 }
 
 func (s *Suite) Test_Logger_Logf__production(c *check.C) {
+       t := s.Init(c)
+
        var sw strings.Builder
        logger := Logger{out: NewSeparatorWriter(&sw)}
 
@@ -63,7 +69,7 @@ func (s *Suite) Test_Logger_Logf__produc
        G.Testing = false
        logger.Logf(Error, "filename", "3", "diagnostic", "message")
 
-       c.Check(sw.String(), equals, ""+
+       t.CheckEquals(sw.String(), ""+
                "ERROR: filename:3: message\n")
 }
 
@@ -125,7 +131,7 @@ func (s *Suite) Test_Logger_Diag__duplic
        logger.Diag(line, Error, "Blue should be %s.", "orange")
        logger.Diag(line, Error, "Blue should be %s.", "orange")
 
-       c.Check(sw.String(), equals, ""+
+       t.CheckEquals(sw.String(), ""+
                "ERROR: filename:3: Blue should be orange.\n")
 }
 
@@ -153,7 +159,7 @@ func (s *Suite) Test_Logger_Diag__explan
        logger.Explain(
                "The colors have further changed.")
 
-       c.Check(sw.String(), equals, ""+
+       t.CheckEquals(sw.String(), ""+
                "ERROR: filename:3: Blue should be orange.\n"+
                "\n"+
                "\tThe colors have changed.\n"+
@@ -453,7 +459,7 @@ func (s *Suite) Test_Logger_ShowSummary_
        // adding --explain to the options would not show any explanation.
        // Therefore, "Run \"pkglint -e\"" is not advertised in this case,
        // but see below.
-       c.Check(G.Logger.explanationsAvailable, equals, false)
+       t.CheckEquals(G.Logger.explanationsAvailable, false)
        t.CheckOutputLines(
                "Looks fine.")
 
@@ -461,10 +467,10 @@ func (s *Suite) Test_Logger_ShowSummary_
        line.Explain("This explanation is available.")
        G.Logger.ShowSummary()
 
-       c.Check(G.Logger.explanationsAvailable, equals, true)
+       t.CheckEquals(G.Logger.explanationsAvailable, true)
        t.CheckOutputLines(
                "WARN: Makefile:27: This warning is interesting.",
-               "0 errors and 1 warning found.",
+               "1 warning found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -553,7 +559,7 @@ func (s *Suite) Test_Logger_ShowSummary_
 
        t.CheckOutputLines(
                "ERROR: .",
-               "1 error and 0 warnings found.",
+               "1 error found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -572,7 +578,7 @@ func (s *Suite) Test_Logger_ShowSummary_
 
        t.CheckOutputLines(
                "ERROR: .",
-               "1 error and 0 warnings found.")
+               "1 error found.")
 }
 
 func (s *Suite) Test_Logger_ShowSummary__autofix_available(c *check.C) {
@@ -802,7 +808,7 @@ func (s *Suite) Test_Logger_Diag__source
                        "Patch \"../../category/dependency/patches/patch-aa\" is not recorded. "+
                        "Run \""+confMake+" makepatchsum\".",
                "",
-               "3 errors and 0 warnings found.",
+               "3 errors found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -811,17 +817,17 @@ func (s *Suite) Test_Logger_shallBeLogge
 
        t.SetUpCommandLine( /* none */ )
 
-       c.Check(G.Logger.shallBeLogged("Options should not contain whitespace."), equals, true)
+       t.CheckEquals(G.Logger.shallBeLogged("Options should not contain whitespace."), true)
 
        t.SetUpCommandLine("--only", "whitespace")
 
-       c.Check(G.Logger.shallBeLogged("Options should not contain whitespace."), equals, true)
-       c.Check(G.Logger.shallBeLogged("Options should not contain space."), equals, false)
+       t.CheckEquals(G.Logger.shallBeLogged("Options should not contain whitespace."), true)
+       t.CheckEquals(G.Logger.shallBeLogged("Options should not contain space."), false)
 
        t.SetUpCommandLine( /* none again */ )
 
-       c.Check(G.Logger.shallBeLogged("Options should not contain whitespace."), equals, true)
-       c.Check(G.Logger.shallBeLogged("Options should not contain space."), equals, true)
+       t.CheckEquals(G.Logger.shallBeLogged("Options should not contain whitespace."), true)
+       t.CheckEquals(G.Logger.shallBeLogged("Options should not contain space."), true)
 }
 
 // Even if verbose logging is disabled, the "Replacing" diagnostics
@@ -852,57 +858,63 @@ func (s *Suite) Test_Logger_Logf__panic(
 }
 
 func (s *Suite) Test_SeparatorWriter(c *check.C) {
+       t := s.Init(c)
+
        var sb strings.Builder
        wr := NewSeparatorWriter(&sb)
 
        wr.WriteLine("a")
        wr.WriteLine("b")
 
-       c.Check(sb.String(), equals, "a\nb\n")
+       t.CheckEquals(sb.String(), "a\nb\n")
 
        wr.Separate()
 
-       c.Check(sb.String(), equals, "a\nb\n")
+       t.CheckEquals(sb.String(), "a\nb\n")
 
        wr.WriteLine("c")
 
-       c.Check(sb.String(), equals, "a\nb\n\nc\n")
+       t.CheckEquals(sb.String(), "a\nb\n\nc\n")
 }
 
 func (s *Suite) Test_SeparatorWriter_Flush(c *check.C) {
+       t := s.Init(c)
+
        var sb strings.Builder
        wr := NewSeparatorWriter(&sb)
 
        wr.Write("a")
        wr.Write("b")
 
-       c.Check(sb.String(), equals, "")
+       t.CheckEquals(sb.String(), "")
 
        wr.Flush()
 
-       c.Check(sb.String(), equals, "ab")
+       t.CheckEquals(sb.String(), "ab")
 
        wr.Separate()
 
        // The current line is terminated immediately by the above Separate(),
        // but the empty line for separating two paragraphs is kept in mind.
        // It will be added later, before the next non-newline character.
-       c.Check(sb.String(), equals, "ab\n")
+       t.CheckEquals(sb.String(), "ab\n")
 
        wr.Write("c")
        wr.Flush()
 
-       c.Check(sb.String(), equals, "ab\n\nc")
+       t.CheckEquals(sb.String(), "ab\n\nc")
 }
 
 func (s *Suite) Test_SeparatorWriter_Separate(c *check.C) {
+       t := s.Init(c)
+
        var sb strings.Builder
        wr := NewSeparatorWriter(&sb)
 
        wr.WriteLine("a")
        wr.Separate()
 
-       c.Check(sb.String(), equals, "a\n")
+       t.CheckEquals(sb.String(), "a\n")
 
        // The call to Separate had requested an empty line. That empty line
        // can either be given explicitly (like here), or it will be written
@@ -910,10 +922,10 @@ func (s *Suite) Test_SeparatorWriter_Sep
        wr.WriteLine("")
        wr.Separate()
 
-       c.Check(sb.String(), equals, "a\n\n")
+       t.CheckEquals(sb.String(), "a\n\n")
 
        wr.WriteLine("c")
        wr.Separate()
 
-       c.Check(sb.String(), equals, "a\n\nc\n")
+       t.CheckEquals(sb.String(), "a\n\nc\n")
 }
Index: pkgsrc/pkgtools/pkglint/files/tools.go
diff -u pkgsrc/pkgtools/pkglint/files/tools.go:1.17 pkgsrc/pkgtools/pkglint/files/tools.go:1.18
--- pkgsrc/pkgtools/pkglint/files/tools.go:1.17 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/tools.go      Sun Jul 14 21:25:47 2019
@@ -41,7 +41,7 @@ func (tool *Tool) String() string {
                aliases = ":" + strings.Join(tool.Aliases, ",")
        }
 
-       varForm := ifelseStr(tool.MustUseVarForm, "var", "")
+       varForm := condStr(tool.MustUseVarForm, "var", "")
 
        return sprintf("%s:%s:%s:%s%s",
                tool.Name, tool.Varname, varForm, tool.Validity, aliases)
Index: pkgsrc/pkgtools/pkglint/files/vardefs_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.17 pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.18
--- pkgsrc/pkgtools/pkglint/files/vardefs_test.go:1.17  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/vardefs_test.go       Sun Jul 14 21:25:47 2019
@@ -8,8 +8,8 @@ func (s *Suite) Test_VarTypeRegistry_Ini
        src := NewPkgsrc(t.File("."))
        src.vartypes.Init(&src)
 
-       c.Check(src.vartypes.Canon("BSD_MAKE_ENV").basicType.name, equals, "ShellWord")
-       c.Check(src.vartypes.Canon("USE_BUILTIN.*").basicType.name, equals, "YesNoIndirectly")
+       t.CheckEquals(src.vartypes.Canon("BSD_MAKE_ENV").basicType.name, "ShellWord")
+       t.CheckEquals(src.vartypes.Canon("USE_BUILTIN.*").basicType.name, "YesNoIndirectly")
 }
 
 func (s *Suite) Test_VarTypeRegistry_enumFrom(c *check.C) {
@@ -49,7 +49,7 @@ func (s *Suite) Test_VarTypeRegistry_enu
 
        test := func(varname, values string) {
                vartype := G.Pkgsrc.VariableType(nil, varname).String()
-               c.Check(vartype, equals, values)
+               t.CheckEquals(vartype, values)
        }
 
        test("EMACS_VERSIONS_ACCEPTED", "enum: emacs29 emacs31  (list, package-settable)")
@@ -72,9 +72,9 @@ func (s *Suite) Test_VarTypeRegistry_enu
        noAssignmentsType := reg.enumFrom(&G.Pkgsrc, "mk/existing.mk", "defval", "OTHER_VAR")
        nonexistentType := reg.enumFrom(&G.Pkgsrc, "mk/nonexistent.mk", "defval", "VAR")
 
-       t.Check(existingType.AllowedEnums(), equals, "first second")
-       t.Check(noAssignmentsType.AllowedEnums(), equals, "defval")
-       t.Check(nonexistentType.AllowedEnums(), equals, "defval")
+       t.CheckEquals(existingType.AllowedEnums(), "first second")
+       t.CheckEquals(noAssignmentsType.AllowedEnums(), "defval")
+       t.CheckEquals(nonexistentType.AllowedEnums(), "defval")
 }
 
 func (s *Suite) Test_VarTypeRegistry_enumFromDirs(c *check.C) {
@@ -89,7 +89,7 @@ func (s *Suite) Test_VarTypeRegistry_enu
 
        test := func(varname, values string) {
                vartype := G.Pkgsrc.VariableType(nil, varname).String()
-               c.Check(vartype, equals, values)
+               t.CheckEquals(vartype, values)
        }
 
        test("PYPKGPREFIX", "enum: py28 py33  (system-provided)")
@@ -107,7 +107,7 @@ func (s *Suite) Test_VarTypeRegistry_enu
 
        test := func(varname, values string) {
                vartype := G.Pkgsrc.VariableType(nil, varname).String()
-               c.Check(vartype, equals, values)
+               t.CheckEquals(vartype, values)
        }
 
        test("OPSYS", "enum: NetBSD SunOS  (system-provided)")
@@ -201,5 +201,5 @@ func (s *Suite) Test_VarTypeRegistry_Ini
        t.SetUpVartypes()
 
        vartype := G.Pkgsrc.VariableType(nil, "MASTER_SITE_GITHUB")
-       t.Check(vartype.String(), equals, "FetchURL (list, system-provided)")
+       t.CheckEquals(vartype.String(), "FetchURL (list, system-provided)")
 }

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.54 pkgsrc/pkgtools/pkglint/files/mkline.go:1.55
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.54        Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sun Jul 14 21:25:47 2019
@@ -86,15 +86,22 @@ func (p MkLineParser) Parse(line *Line) 
                        "Otherwise remove the leading whitespace.")
        }
 
-       data := p.split(line, text)
-
        // Check for shell commands first because these cannot have comments
        // at the end of the line.
        if hasPrefix(text, "\t") {
+               lex := textproc.NewLexer(text)
+               for lex.SkipByte('\t') {
+               }
+
+               // Just for the side effects of the warnings.
+               _ = p.split(line, lex.Rest())
+
                return p.parseShellcmd(line)
        }
 
-       if mkline := p.parseVarassign(line, data); mkline != nil {
+       data := p.split(line, text)
+
+       if mkline := p.parseVarassign(line); mkline != nil {
                return mkline
        }
        if mkline := p.parseCommentOrEmpty(line); mkline != nil {
@@ -121,8 +128,8 @@ func (p MkLineParser) Parse(line *Line) 
        return &MkLine{line, nil}
 }
 
-func (p MkLineParser) parseVarassign(line *Line, data mkLineSplitResult) *MkLine {
-       m, a := p.MatchVarassign(line, line.Text, data)
+func (p MkLineParser) parseVarassign(line *Line) *MkLine {
+       m, a := p.MatchVarassign(line, line.Text)
        if !m {
                return nil
        }
@@ -233,11 +240,33 @@ func (mkline *MkLine) IsVarassign() bool
 // IsCommentedVarassign returns true for commented-out variable assignments.
 // In most cases these are treated as ordinary comments, but in some others
 // they are treated like variable assignments, just inactive ones.
+//
+// To qualify as a commented variable assignment, there must be no
+// space between the # and the variable name.
+//
+// Example:
+//  #VAR=   value
+// Counterexample:
+//  # VAR=  value
 func (mkline *MkLine) IsCommentedVarassign() bool {
        data, ok := mkline.data.(*mkLineAssign)
        return ok && data.commented
 }
 
+// IsVarassignMaybeCommented returns true for variable assignments of the
+// form VAR=value, no matter if they are commented out like #VAR=value or
+// not. To qualify as a commented variable assignment, there must be no
+// space between the # and the variable name.
+//
+// Example:
+//  #VAR=   value
+// Counterexample:
+//  # VAR=  value
+func (mkline *MkLine) IsVarassignMaybeCommented() bool {
+       _, ok := mkline.data.(*mkLineAssign)
+       return ok
+}
+
 // IsShellCommand returns true for tab-indented lines that are assigned to a Make
 // target. Example:
 //
@@ -337,13 +366,12 @@ func (mkline *MkLine) VarassignComment()
 //  NO_VALUE_IN_FIRST_LINE= \
 //          value starts in second line
 func (mkline *MkLine) FirstLineContainsValue() bool {
-       assert(mkline.IsVarassign() || mkline.IsCommentedVarassign())
+       assert(mkline.IsVarassignMaybeCommented())
        assert(mkline.IsMultiline())
 
        // Parsing the continuation marker as variable value is cheating but works well.
        text := strings.TrimSuffix(mkline.raw[0].orignl, "\n")
-       data := MkLineParser{}.split(nil, text)
-       _, a := MkLineParser{}.MatchVarassign(mkline.Line, text, data)
+       _, a := MkLineParser{}.MatchVarassign(mkline.Line, text)
        return a.value != "\\"
 }
 
@@ -372,7 +400,7 @@ func (mkline *MkLine) Args() string { re
 func (mkline *MkLine) Cond() *MkCond {
        cond := mkline.data.(*mkLineDirective).cond
        if cond == nil {
-               cond = NewMkParser(mkline.Line, mkline.Args(), true).MkCond()
+               cond = NewMkParser(mkline.Line, mkline.Args()).MkCond()
                mkline.data.(*mkLineDirective).cond = cond
        }
        return cond
@@ -431,10 +459,10 @@ func (mkline *MkLine) Tokenize(text stri
 
        var tokens []*MkToken
        var rest string
-       if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) && text == mkline.Value() {
+       if mkline.IsVarassignMaybeCommented() && text == mkline.Value() {
                tokens, rest = mkline.ValueTokens()
        } else {
-               p := NewMkParser(mkline.Line, text, true)
+               p := NewMkParser(mkline.Line, text)
                tokens = p.MkTokens()
                rest = p.Rest()
        }
@@ -565,7 +593,7 @@ func (mkline *MkLine) ValueTokens() ([]*
 
        // No error checking here since all this has already been done when the
        // whole line was parsed in MkLineParser.Parse.
-       p := NewMkParser(nil, value, false)
+       p := NewMkParser(nil, value)
        assign.valueMk = p.MkTokens()
        assign.valueMkRest = p.Rest()
        return assign.valueMk, assign.valueMkRest
@@ -575,7 +603,7 @@ func (mkline *MkLine) ValueTokens() ([]*
 // For variable assignments, it returns the right-hand side, properly split into words.
 // For .for loops, it returns all arguments (including variable names), properly split into words.
 func (mkline *MkLine) Fields() []string {
-       if mkline.IsVarassign() || mkline.IsCommentedVarassign() {
+       if mkline.IsVarassignMaybeCommented() {
                value := mkline.Value()
                if value == "" {
                        return nil
@@ -608,7 +636,7 @@ func (mkline *MkLine) Fields() []string 
 
 func (*MkLine) WithoutMakeVariables(value string) string {
        var valueNovar strings.Builder
-       for _, token := range NewMkParser(nil, value, false).MkTokens() {
+       for _, token := range NewMkParser(nil, value).MkTokens() {
                if token.Varuse == nil {
                        valueNovar.WriteString(token.Text)
                }
@@ -710,7 +738,7 @@ func (mkline *MkLine) RefTo(other *MkLin
 var (
        LowerDash                  = textproc.NewByteSet("a-z---")
        AlnumDot                   = textproc.NewByteSet("A-Za-z0-9_.")
-       unescapeMkCommentSafeChars = textproc.NewByteSet("\\#[$").Inverse()
+       unescapeMkCommentSafeChars = textproc.NewByteSet("\\#[\n").Inverse()
 )
 
 // unescapeComment takes a Makefile line, as written in a file, and splits
@@ -724,7 +752,7 @@ var (
 //
 // The comment is returned including the leading "#", if any. If the line has
 // no comment, it is an empty string.
-func (p MkLineParser) unescapeComment(text string) (main, comment string) {
+func (MkLineParser) unescapeComment(text string) (main, comment string) {
        var sb strings.Builder
 
        lexer := textproc.NewLexer(text)
@@ -736,9 +764,6 @@ again:
        }
 
        switch {
-       case lexer.SkipByte('$'):
-               sb.WriteByte('$')
-
        case lexer.SkipString("\\#"):
                sb.WriteByte('#')
 
@@ -770,6 +795,8 @@ again:
 }
 
 type mkLineSplitResult struct {
+       // The text of the line, without the comment at the end of the line,
+       // and with # signs unescaped.
        main               string
        tokens             []*MkToken
        spaceBeforeComment string
@@ -777,18 +804,23 @@ type mkLineSplitResult struct {
        comment            string
 }
 
-// splitMkLine parses a logical line from a Makefile (that is, after joining
+// split parses a logical line from a Makefile (that is, after joining
 // the lines that end in a backslash) into two parts: the main part and the
 // comment.
 //
 // This applies to all line types except those starting with a tab, which
 // contain the shell commands to be associated with make targets. These cannot
 // have comments.
-func (p MkLineParser) split(line *Line, text string) mkLineSplitResult {
+//
+// If line is given, it is used for logging parse errors and warnings
+// about round parentheses instead of curly braces, as well as ambiguous
+// variables of the form $v instead of ${v}.
+func (MkLineParser) split(line *Line, text string) mkLineSplitResult {
+       assert(!hasPrefix(text, "\t"))
 
-       mainWithSpaces, comment := p.unescapeComment(text)
+       mainWithSpaces, comment := MkLineParser{}.unescapeComment(text)
 
-       parser := NewMkParser(line, mainWithSpaces, line != nil)
+       parser := NewMkParser(line, mainWithSpaces)
        lexer := parser.lexer
 
        parseOther := func() string {
@@ -1015,7 +1047,7 @@ func (mkline *MkLine) ForEachUsed(action
                        return
                }
 
-               for _, token := range NewMkParser(nil, text, false).MkTokens() {
+               for _, token := range NewMkParser(nil, text).MkTokens() {
                        if token.Varuse != nil {
                                searchInVarUse(token.Varuse, time)
                        }
@@ -1031,7 +1063,7 @@ func (mkline *MkLine) ForEachUsed(action
        case mkline.IsDirective() && mkline.Directive() == "for":
                searchIn(mkline.Args(), VucLoadTime)
 
-       case mkline.IsDirective() && mkline.Cond() != nil:
+       case mkline.IsDirective() && (mkline.Directive() == "if" || mkline.Directive() == "elif") && mkline.Cond() != nil:
                mkline.Cond().Walk(&MkCondCallback{
                        VarUse: func(varuse *MkVarUse) {
                                searchInVarUse(varuse, VucLoadTime)
@@ -1474,7 +1506,7 @@ var (
        VarparamBytes = textproc.NewByteSet("A-Za-z_0-9#*+---./[")
 )
 
-func (p MkLineParser) MatchVarassign(line *Line, text string, asdfData mkLineSplitResult) (m bool, assignment *mkLineAssign) {
+func (p MkLineParser) MatchVarassign(line *Line, text string) (bool, *mkLineAssign) {
 
        // A commented variable assignment does not have leading whitespace.
        // Otherwise line 1 of almost every Makefile fragment would need to
@@ -1511,7 +1543,7 @@ func (p MkLineParser) MatchVarassign(lin
        varname := lexer.Since(varnameStart)
 
        if varname == "" {
-               return
+               return false, nil
        }
 
        spaceAfterVarname := lexer.NextHspace()
@@ -1522,7 +1554,7 @@ func (p MkLineParser) MatchVarassign(lin
                lexer.Skip(1)
        }
        if !lexer.SkipByte('=') {
-               return
+               return false, nil
        }
        op := NewMkOperator(lexer.Since(opStart))
 
@@ -1534,7 +1566,8 @@ func (p MkLineParser) MatchVarassign(lin
        lexer.SkipHspace()
 
        value := trimHspace(lexer.Rest())
-       valueAlign := ifelseStr(commented, "#", "") + lexer.Since(mainStart)
+       parsedValueAlign := condStr(commented, "#", "") + lexer.Since(mainStart)
+       valueAlign := p.getRawValueAlign(line.raw[0].orignl, parsedValueAlign)
        spaceBeforeComment := data.spaceBeforeComment
        if value == "" {
                valueAlign += spaceBeforeComment
@@ -1554,10 +1587,38 @@ func (p MkLineParser) MatchVarassign(lin
                valueMkRest:       "",  // filled in lazily
                fields:            nil, // filled in lazily
                spaceAfterValue:   spaceBeforeComment,
-               comment:           ifelseStr(data.hasComment, "#", "") + data.comment,
+               comment:           condStr(data.hasComment, "#", "") + data.comment,
        }
 }
 
+func (*MkLineParser) getRawValueAlign(raw, parsed string) string {
+       r := textproc.NewLexer(raw)
+       p := textproc.NewLexer(parsed)
+       mark := r.Mark()
+
+       for !p.EOF() {
+               pch := p.PeekByte()
+               rch := r.PeekByte()
+
+               switch {
+               case pch == rch:
+                       p.Skip(1)
+                       r.Skip(1)
+
+               case pch == ' ', pch == '\t':
+                       p.SkipHspace()
+                       r.SkipHspace()
+
+               default:
+                       assert(pch == '#')
+                       assert(r.SkipString("\\#"))
+                       p.Skip(1)
+               }
+       }
+
+       return r.Since(mark)
+}
+
 func MatchMkInclude(text string) (m bool, indentation, directive, filename string) {
        lexer := textproc.NewLexer(text)
        if lexer.SkipByte('.') {

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.60 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.61
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.60   Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sun Jul 14 21:25:47 2019
@@ -11,13 +11,43 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                "VARNAME.param?=value # varassign comment")
 
-       c.Check(mkline.IsVarassign(), equals, true)
-       c.Check(mkline.Varname(), equals, "VARNAME.param")
-       c.Check(mkline.Varcanon(), equals, "VARNAME.*")
-       c.Check(mkline.Varparam(), equals, "param")
-       c.Check(mkline.Op(), equals, opAssignDefault)
-       c.Check(mkline.Value(), equals, "value")
-       c.Check(mkline.VarassignComment(), equals, "# varassign comment")
+       t.CheckEquals(mkline.IsVarassign(), true)
+       t.CheckEquals(mkline.Varname(), "VARNAME.param")
+       t.CheckEquals(mkline.Varcanon(), "VARNAME.*")
+       t.CheckEquals(mkline.Varparam(), "param")
+       t.CheckEquals(mkline.Op(), opAssignDefault)
+       t.CheckEquals(mkline.Value(), "value")
+       t.CheckEquals(mkline.VarassignComment(), "# varassign comment")
+}
+
+func (s *Suite) Test_MkLineParser_Parse__varassign_empty_multiline(c *check.C) {
+       t := s.Init(c)
+
+       mklines := t.NewMkLines("test.mk",
+               "VAR=\t\\",
+               "\t\\",
+               "\t\\",
+               "\t# nothing",
+               "",
+               "VAR=\t1\\",
+               "\t\\",
+               "\t\\",
+               "\t# a single letter")
+
+       // Bmake and pkglint agree that the variable value is an empty string.
+       // They don't agree on the exact whitespace in the line, though,
+       // but this doesn't matter in practice. To see the difference, run:
+       //  bmake -dA 2>&1 | grep 'ParseReadLine.*VAR'
+       // See devel/bmake/files/parse.c:/non-comment, non-blank line/
+       t.CheckEquals(mklines.mklines[0].Text, "VAR=   # nothing")
+       t.CheckEquals(mklines.mklines[2].Text, "VAR=\t1   # a single letter")
+
+       mkline := mklines.mklines[0]
+       t.CheckEquals(mkline.IsVarassign(), true)
+       t.CheckEquals(mkline.Varname(), "VAR")
+       t.CheckEquals(mkline.Op(), opAssign)
+       t.CheckEquals(mkline.Value(), "")
+       t.CheckEquals(mkline.VarassignComment(), "# nothing")
 }
 
 func (s *Suite) Test_MkLineParser_Parse__varassign_space_around_operator(c *check.C) {
@@ -40,8 +70,8 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                "\tshell command # shell comment")
 
-       c.Check(mkline.IsShellCommand(), equals, true)
-       c.Check(mkline.ShellCommand(), equals, "shell command # shell comment")
+       t.CheckEquals(mkline.IsShellCommand(), true)
+       t.CheckEquals(mkline.ShellCommand(), "shell command # shell comment")
 }
 
 func (s *Suite) Test_MkLineParser_Parse__comment(c *check.C) {
@@ -50,7 +80,7 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                "# whole line comment")
 
-       c.Check(mkline.IsComment(), equals, true)
+       t.CheckEquals(mkline.IsComment(), true)
 }
 
 func (s *Suite) Test_MkLineParser_Parse__empty(c *check.C) {
@@ -58,7 +88,7 @@ func (s *Suite) Test_MkLineParser_Parse_
 
        mkline := t.NewMkLine("test.mk", 101, "")
 
-       c.Check(mkline.IsEmpty(), equals, true)
+       t.CheckEquals(mkline.IsEmpty(), true)
 }
 
 func (s *Suite) Test_MkLineParser_Parse__directive(c *check.C) {
@@ -67,11 +97,11 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                ".  if !empty(PKGNAME:M*-*) && ${RUBY_RAILS_SUPPORTED:[\\#]} == 1 # directive comment")
 
-       c.Check(mkline.IsDirective(), equals, true)
-       c.Check(mkline.Indent(), equals, "  ")
-       c.Check(mkline.Directive(), equals, "if")
-       c.Check(mkline.Args(), equals, "!empty(PKGNAME:M*-*) && ${RUBY_RAILS_SUPPORTED:[#]} == 1")
-       c.Check(mkline.DirectiveComment(), equals, "directive comment")
+       t.CheckEquals(mkline.IsDirective(), true)
+       t.CheckEquals(mkline.Indent(), "  ")
+       t.CheckEquals(mkline.Directive(), "if")
+       t.CheckEquals(mkline.Args(), "!empty(PKGNAME:M*-*) && ${RUBY_RAILS_SUPPORTED:[#]} == 1")
+       t.CheckEquals(mkline.DirectiveComment(), "directive comment")
 }
 
 func (s *Suite) Test_MkLineParser_Parse__include(c *check.C) {
@@ -80,12 +110,12 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                ".    include \"../../mk/bsd.prefs.mk\" # include comment")
 
-       c.Check(mkline.IsInclude(), equals, true)
-       c.Check(mkline.Indent(), equals, "    ")
-       c.Check(mkline.MustExist(), equals, true)
-       c.Check(mkline.IncludedFile(), equals, "../../mk/bsd.prefs.mk")
+       t.CheckEquals(mkline.IsInclude(), true)
+       t.CheckEquals(mkline.Indent(), "    ")
+       t.CheckEquals(mkline.MustExist(), true)
+       t.CheckEquals(mkline.IncludedFile(), "../../mk/bsd.prefs.mk")
 
-       c.Check(mkline.IsSysinclude(), equals, false)
+       t.CheckEquals(mkline.IsSysinclude(), false)
 }
 
 func (s *Suite) Test_MkLineParser_Parse__sysinclude(c *check.C) {
@@ -94,12 +124,12 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                ".    include <subdir.mk> # sysinclude comment")
 
-       c.Check(mkline.IsSysinclude(), equals, true)
-       c.Check(mkline.Indent(), equals, "    ")
-       c.Check(mkline.MustExist(), equals, true)
-       c.Check(mkline.IncludedFile(), equals, "subdir.mk")
+       t.CheckEquals(mkline.IsSysinclude(), true)
+       t.CheckEquals(mkline.Indent(), "    ")
+       t.CheckEquals(mkline.MustExist(), true)
+       t.CheckEquals(mkline.IncludedFile(), "subdir.mk")
 
-       c.Check(mkline.IsInclude(), equals, false)
+       t.CheckEquals(mkline.IsInclude(), false)
 }
 
 func (s *Suite) Test_MkLineParser_Parse__dependency(c *check.C) {
@@ -108,9 +138,9 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                "target1 target2: source1 source2")
 
-       c.Check(mkline.IsDependency(), equals, true)
-       c.Check(mkline.Targets(), equals, "target1 target2")
-       c.Check(mkline.Sources(), equals, "source1 source2")
+       t.CheckEquals(mkline.IsDependency(), true)
+       t.CheckEquals(mkline.Targets(), "target1 target2")
+       t.CheckEquals(mkline.Sources(), "source1 source2")
 }
 
 func (s *Suite) Test_MkLineParser_Parse__dependency_space(c *check.C) {
@@ -119,8 +149,8 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                "target : source")
 
-       c.Check(mkline.Targets(), equals, "target")
-       c.Check(mkline.Sources(), equals, "source")
+       t.CheckEquals(mkline.Targets(), "target")
+       t.CheckEquals(mkline.Sources(), "source")
        t.CheckOutputLines(
                "NOTE: test.mk:101: Space before colon in dependency line.")
 }
@@ -131,10 +161,10 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("test.mk", 101,
                "VARNAME+=value")
 
-       c.Check(mkline.IsVarassign(), equals, true)
-       c.Check(mkline.Varname(), equals, "VARNAME")
-       c.Check(mkline.Varcanon(), equals, "VARNAME")
-       c.Check(mkline.Varparam(), equals, "")
+       t.CheckEquals(mkline.IsVarassign(), true)
+       t.CheckEquals(mkline.Varname(), "VARNAME")
+       t.CheckEquals(mkline.Varcanon(), "VARNAME")
+       t.CheckEquals(mkline.Varparam(), "")
 }
 
 func (s *Suite) Test_MkLineParser_Parse__merge_conflict(c *check.C) {
@@ -144,14 +174,14 @@ func (s *Suite) Test_MkLineParser_Parse_
                "<<<<<<<<<<<<<<<<<")
 
        // Merge conflicts are of neither type.
-       c.Check(mkline.IsVarassign(), equals, false)
-       c.Check(mkline.IsDirective(), equals, false)
-       c.Check(mkline.IsInclude(), equals, false)
-       c.Check(mkline.IsEmpty(), equals, false)
-       c.Check(mkline.IsComment(), equals, false)
-       c.Check(mkline.IsDependency(), equals, false)
-       c.Check(mkline.IsShellCommand(), equals, false)
-       c.Check(mkline.IsSysinclude(), equals, false)
+       t.CheckEquals(mkline.IsVarassign(), false)
+       t.CheckEquals(mkline.IsDirective(), false)
+       t.CheckEquals(mkline.IsInclude(), false)
+       t.CheckEquals(mkline.IsEmpty(), false)
+       t.CheckEquals(mkline.IsComment(), false)
+       t.CheckEquals(mkline.IsDependency(), false)
+       t.CheckEquals(mkline.IsShellCommand(), false)
+       t.CheckEquals(mkline.IsSysinclude(), false)
 }
 
 func (s *Suite) Test_MkLineParser_Parse__autofix_space_after_varname(c *check.C) {
@@ -196,12 +226,12 @@ func (s *Suite) Test_MkLineParser_Parse_
        mkline := t.NewMkLine("Makefile", 123, "VARNAME.#=\tvalue")
 
        // Parse error because the # starts a comment.
-       c.Check(mkline.IsVarassign(), equals, false)
+       t.CheckEquals(mkline.IsVarassign(), false)
 
        mkline2 := t.NewMkLine("Makefile", 124, "VARNAME.\\#=\tvalue")
 
-       c.Check(mkline2.IsVarassign(), equals, true)
-       c.Check(mkline2.Varname(), equals, "VARNAME.#")
+       t.CheckEquals(mkline2.IsVarassign(), true)
+       t.CheckEquals(mkline2.Varname(), "VARNAME.#")
 
        t.CheckOutputLines(
                "ERROR: Makefile:123: Unknown Makefile line format: \"VARNAME.#=\\tvalue\".")
@@ -227,11 +257,11 @@ func (s *Suite) Test_MkLineParser_Parse_
                ".endfor")
        parsed := mklines.mklines
 
-       c.Check(parsed[0].Value(), equals, "value")
-       c.Check(parsed[1].Value(), equals, "value#")
-       c.Check(parsed[2].Value(), equals, "value\\\\")
-       c.Check(parsed[3].Value(), equals, "value\\\\#")
-       c.Check(parsed[4].Value(), equals, "value\\\\\\\\")
+       t.CheckEquals(parsed[0].Value(), "value")
+       t.CheckEquals(parsed[1].Value(), "value#")
+       t.CheckEquals(parsed[2].Value(), "value\\\\")
+       t.CheckEquals(parsed[3].Value(), "value\\\\#")
+       t.CheckEquals(parsed[4].Value(), "value\\\\\\\\")
 
        t.CheckOutputLines(
                "WARN: ~/Makefile:1: The # character starts a Makefile comment.",
@@ -246,7 +276,7 @@ func (s *Suite) Test_MkLine_Varparam(c *
 
        varparam := mkline.Varparam()
 
-       c.Check(varparam, equals, "${param}")
+       t.CheckEquals(varparam, "${param}")
 }
 
 func (s *Suite) Test_MkLine_ValueAlign__commented(c *check.C) {
@@ -256,8 +286,8 @@ func (s *Suite) Test_MkLine_ValueAlign__
 
        valueAlign := mkline.ValueAlign()
 
-       c.Check(mkline.IsCommentedVarassign(), equals, true)
-       c.Check(valueAlign, equals, "#SUBST_SED.${param}=\t")
+       t.CheckEquals(mkline.IsCommentedVarassign(), true)
+       t.CheckEquals(valueAlign, "#SUBST_SED.${param}=\t")
 }
 
 func (s *Suite) Test_MkLine_FirstLineContainsValue(c *check.C) {
@@ -279,10 +309,171 @@ func (s *Suite) Test_MkLine_FirstLineCon
 
        t.ExpectAssert(func() { mklines.mklines[1].FirstLineContainsValue() })
 
-       t.Check(mklines.mklines[2].FirstLineContainsValue(), equals, true)
-       t.Check(mklines.mklines[3].FirstLineContainsValue(), equals, false)
-       t.Check(mklines.mklines[4].FirstLineContainsValue(), equals, true)
-       t.Check(mklines.mklines[5].FirstLineContainsValue(), equals, false)
+       t.CheckEquals(mklines.mklines[2].FirstLineContainsValue(), true)
+       t.CheckEquals(mklines.mklines[3].FirstLineContainsValue(), false)
+       t.CheckEquals(mklines.mklines[4].FirstLineContainsValue(), true)
+       t.CheckEquals(mklines.mklines[5].FirstLineContainsValue(), false)
+}
+
+// Up to July 2019, there was a method MkLine.IsMultiAligned, which has
+// been replaced by VaralignBlock. The test cases were still useful,
+// therefore they were kept.
+func (s *Suite) Test_MkLine__aligned(c *check.C) {
+       t := s.Init(c)
+
+       test := func(data ...interface{}) {
+               var lineTexts []string
+               for _, text := range data[:len(data)-1] {
+                       lineTexts = append(lineTexts, text.(string))
+               }
+               expected := data[len(data)-1].(bool)
+
+               mklines := t.NewMkLines("filename.mk",
+                       lineTexts...)
+               assert(len(mklines.mklines) == 1)
+
+               var varalign VaralignBlock
+               varalign.Process(mklines.mklines[0])
+               varalign.Finish()
+
+               output := t.Output()
+               t.CheckEquals(output == "", expected)
+       }
+
+       // The first line uses a space for indentation, which is typical of
+       // the outlier line in VaralignBlock.
+       //
+       // The second line starts in column 0, which is too far to the left.
+       // For a human reader the second line looks like a variable assignment
+       // of its own.
+       test(
+               "CONFIGURE_ENV+= \\",
+               "AWK=${AWK:Q}",
+               false)
+
+       // The second line is indented and therefore visually distinct from
+       // a Makefile assignment line. Everything's fine.
+       test(
+               "CONFIGURE_ENV+= \\",
+               "\tAWK=${AWK:Q}",
+               true)
+
+       // The first line may also use a tab instead of a space for indentation.
+       // This is typical of variable assignments whose name is short enough
+       // to be aligned with the other lines.
+       test(
+               "CONFIGURE_ENV+=\t\\",
+               "AWK=${AWK:Q}",
+               false)
+       test(
+               "CONFIGURE_ENV+=\t\\",
+               "\tAWK=${AWK:Q}",
+               true)
+
+       // The first line contains a value, and the second line has the same
+       // indentation as the first line. This looks nicely aligned.
+       test(
+               "CONFIGURE_ENV+=\tAWK=${AWK:Q} \\",
+               "\t\tSED=${SED:Q}",
+               true)
+
+       // The second line is indented less than the first line. This looks
+       // confusing to the human reader because the actual values do not
+       // appear in a rectangular shape in the source code.
+       //
+       // There are several cases though where the follow-up lines are quite
+       // long, therefore it is allowed to indent them with a single tab.
+       test(
+               "CONFIGURE_ENV+=\tAWK=${AWK:Q} \\",
+               "\tSED=${SED:Q}",
+               true)
+
+       // Having the continuation line in column 0 looks even more confusing.
+       test(
+               "CONFIGURE_ENV+=\tAWK=${AWK:Q} \\",
+               "SED=${SED:Q}",
+               false)
+
+       // Longer continuation lines may use internal indentation to represent
+       // AWK or shell code.
+       test(
+               "GENERATE_PLIST+=\t/pattern/ {\\",
+               "\t\t\t  action(); \\",
+               "\t\t\t}",
+               true)
+
+       // If any of the continuation lines is indented less than the first
+       // line, it looks confusing.
+       test(
+               "GENERATE_PLIST+=\t/pattern/ {\\",
+               "\t  action(); \\",
+               "\t}",
+               false)
+
+       // If the first line is empty, the indentation may start in column 8,
+       // and the continuation lines have to be indented as least as far to
+       // the right as the second line.
+       test(
+               "GENERATE_PLIST+= \\",
+               "\t/pattern/ {\\",
+               "\t  action(); \\",
+               "\t}",
+               true)
+
+       // The very last line is indented at column 0, therefore the whole
+       // line is not indented properly.
+       test(
+               "GENERATE_PLIST+= \\",
+               "\t/pattern/ {\\",
+               "\t  action(); \\",
+               "}",
+               false)
+
+       // If there is no visible variable value at all, pkglint must not crash.
+       // This case doesn't occur in practice since the code is usually
+       // succinct enough to avoid these useless lines.
+       //
+       // The first line is empty, the second line is indented to column 8 and
+       // the remaining lines are all indented by at least 8, therefore the
+       // alignment is correct.
+       //
+       // A theoretical use case might be to have a long explaining comment
+       // in the continuation lines, but that is not possible syntactically.
+       // In the line "VAR= value \# comment", the \# is interpreted as
+       // an escaped number sign, and not as a continuation marker followed
+       // by a comment. In the line "VAR= value \ # comment", the backslash
+       // is not a continuation marker as well, since it is not the very
+       // last character of the line.
+       test(
+               "CONFIGURE_ENV+= \\",
+               "\t\\",
+               "\t\\",
+               "\t# nothing",
+               true)
+
+       // Commented variable assignments can also be tested for alignment.
+       test(
+               "#CONFIGURE_ENV+= \\",
+               "\tvalue",
+               true)
+
+       // In commented multilines, the continuation lines may or may not start
+       // with a comment character. Bmake doesn't care, but for human readers
+       // it is confusing to omit the leading comment character.
+       //
+       // For determining whether a multiline is aligned, the initial comment
+       // character is ignored.
+       test(
+               "#CONFIGURE_ENV+= \\",
+               "#\tvalue",
+               true)
+
+       // The indentation of the continuation line is neither 8 nor the
+       // indentation of the first line. Therefore the line is not aligned.
+       test(
+               "#CONFIGURE_ENV+= value1 \\",
+               "#\t\tvalue2",
+               false)
 }
 
 // Demonstrates how a simple condition is structured internally.
@@ -294,9 +485,9 @@ func (s *Suite) Test_MkLine_Cond(c *chec
 
        cond := mkline.Cond()
 
-       c.Check(cond.CompareVarStr.Var.varname, equals, "VAR")
-       c.Check(cond.CompareVarStr.Str, equals, "Value")
-       c.Check(mkline.Cond(), equals, cond)
+       t.CheckEquals(cond.Compare.Left.Var.varname, "VAR")
+       t.CheckEquals(cond.Compare.Right.Str, "Value")
+       t.CheckEquals(mkline.Cond(), cond)
 }
 
 func (s *Suite) Test_VarUseContext_String(c *check.C) {
@@ -306,7 +497,7 @@ func (s *Suite) Test_VarUseContext_Strin
        vartype := G.Pkgsrc.VariableType(nil, "PKGNAME")
        vuc := VarUseContext{vartype, VucUnknownTime, VucQuotBackt, false}
 
-       c.Check(vuc.String(), equals, "(Pkgname (package-settable) time:unknown quoting:backt wordpart:false)")
+       t.CheckEquals(vuc.String(), "(Pkgname (package-settable) time:unknown quoting:backt wordpart:false)")
 }
 
 // In variable assignments, a plain '#' introduces a line comment, unless
@@ -317,22 +508,22 @@ func (s *Suite) Test_MkLineParser_Parse_
 
        mklineVarassignEscaped := t.NewMkLine("filename.mk", 1, "SED_CMD=\t's,\\#,hash,g'")
 
-       c.Check(mklineVarassignEscaped.Varname(), equals, "SED_CMD")
-       c.Check(mklineVarassignEscaped.Value(), equals, "'s,#,hash,g'")
+       t.CheckEquals(mklineVarassignEscaped.Varname(), "SED_CMD")
+       t.CheckEquals(mklineVarassignEscaped.Value(), "'s,#,hash,g'")
 
        mklineCommandEscaped := t.NewMkLine("filename.mk", 1, "\tsed -e 's,\\#,hash,g'")
 
-       c.Check(mklineCommandEscaped.ShellCommand(), equals, "sed -e 's,\\#,hash,g'")
+       t.CheckEquals(mklineCommandEscaped.ShellCommand(), "sed -e 's,\\#,hash,g'")
 
        // From shells/zsh/Makefile.common, rev. 1.78
        mklineCommandUnescaped := t.NewMkLine("filename.mk", 1, "\t# $ sha1 patches/patch-ac")
 
-       c.Check(mklineCommandUnescaped.ShellCommand(), equals, "# $ sha1 patches/patch-ac")
+       t.CheckEquals(mklineCommandUnescaped.ShellCommand(), "# $ sha1 patches/patch-ac")
        t.CheckOutputEmpty() // No warning about parsing the lonely dollar sign.
 
        mklineVarassignUnescaped := t.NewMkLine("filename.mk", 1, "SED_CMD=\t's,#,hash,'")
 
-       c.Check(mklineVarassignUnescaped.Value(), equals, "'s,")
+       t.CheckEquals(mklineVarassignUnescaped.Value(), "'s,")
        t.CheckOutputLines(
                "WARN: filename.mk:1: The # character starts a Makefile comment.")
 }
@@ -370,9 +561,9 @@ func (s *Suite) Test_MkLineParser_Parse_
                ".elifnmake target2", // Neither is this.
                ".endif")
 
-       c.Check(mklines.mklines[1].Varcanon(), equals, "USE_BUILTIN.*")
-       c.Check(mklines.mklines[2].Directive(), equals, "error")
-       c.Check(mklines.mklines[3].Directive(), equals, "export")
+       t.CheckEquals(mklines.mklines[1].Varcanon(), "USE_BUILTIN.*")
+       t.CheckEquals(mklines.mklines[2].Directive(), "error")
+       t.CheckEquals(mklines.mklines[3].Directive(), "export")
 
        t.CheckOutputLines(
                "WARN: infra.mk:2: Makefile lines should not start with space characters.",
@@ -398,7 +589,7 @@ func (s *Suite) Test_MkLine_VariableNeed
        vuc := VarUseContext{G.Pkgsrc.VariableType(nil, "PKGNAME"), VucLoadTime, VucQuotUnknown, false}
        nq := mkline.VariableNeedsQuoting(nil, &MkVarUse{"UNKNOWN", nil}, nil, &vuc)
 
-       c.Check(nq, equals, unknown)
+       t.CheckEquals(nq, unknown)
 }
 
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__append_URL_to_list_of_URLs(c *check.C) {
@@ -414,7 +605,7 @@ func (s *Suite) Test_MkLine_VariableNeed
        vuc := VarUseContext{G.Pkgsrc.vartypes.Canon("MASTER_SITES"), VucRunTime, VucQuotPlain, false}
        nq := mkline.VariableNeedsQuoting(nil, &MkVarUse{"HOMEPAGE", nil}, G.Pkgsrc.vartypes.Canon("HOMEPAGE"), &vuc)
 
-       c.Check(nq, equals, no)
+       t.CheckEquals(nq, no)
 
        MkLineChecker{mklines, mkline}.checkVarassign()
 
@@ -476,7 +667,7 @@ func (s *Suite) Test_MkLine_VariableNeed
                MkCvsID,
                "GENERATE_PLIST= cd ${DESTDIR}${PREFIX}; ${FIND} * \\( -type f -or -type l \\) | ${SORT};")
 
-       mklines.collectDefinedVariables()
+       mklines.collectVariables()
        MkLineChecker{mklines, mklines.mklines[1]}.Check()
 
        t.CheckOutputLines(
@@ -966,7 +1157,7 @@ func (s *Suite) Test_MkLine_ConditionalV
 
        mkline.SetConditionalVars([]string{"OPSYS"})
 
-       c.Check(mkline.ConditionalVars(), deepEquals, []string{"OPSYS"})
+       t.CheckDeepEquals(mkline.ConditionalVars(), []string{"OPSYS"})
 }
 
 func (s *Suite) Test_MkLine_ValueSplit(c *check.C) {
@@ -975,7 +1166,7 @@ func (s *Suite) Test_MkLine_ValueSplit(c
        test := func(value string, expected ...string) {
                mkline := t.NewMkLine("Makefile", 1, "PATH=\t"+value)
                split := mkline.ValueSplit(value, ":")
-               c.Check(split, deepEquals, expected)
+               t.CheckDeepEquals(split, expected)
        }
 
        test("Platform-independent C# compiler #5",
@@ -1023,12 +1214,12 @@ func (s *Suite) Test_MkLine_Fields__vara
        test := func(value string, expected ...string) {
                mkline := t.NewMkLine("Makefile", 1, "PATH=\t"+value)
                fields := mkline.Fields()
-               c.Check(fields, deepEquals, expected)
+               t.CheckDeepEquals(fields, expected)
 
                // Repeated calls get the cached value.
                if len(fields) > 0 {
                        cached := mkline.Fields()
-                       c.Check(&cached[0], equals, &fields[0])
+                       t.CheckEquals(&cached[0], &fields[0])
                }
        }
 
@@ -1050,12 +1241,12 @@ func (s *Suite) Test_MkLine_Fields__for(
        test := func(value string, expected ...string) {
                mkline := t.NewMkLine("Makefile", 1, ".for "+value)
                fields := mkline.Fields()
-               c.Check(fields, deepEquals, expected)
+               t.CheckDeepEquals(fields, expected)
 
                // Repeated calls get the cached value.
                if len(fields) > 0 {
                        cached := mkline.Fields()
-                       c.Check(&cached[0], equals, &fields[0])
+                       t.CheckEquals(&cached[0], &fields[0])
                }
        }
 
@@ -1082,7 +1273,7 @@ func (s *Suite) Test_MkLine_Fields__semi
        mkline := t.NewMkLine("filename.mk", 123, "VAR=\tword1 word2;;;")
        words := mkline.Fields()
 
-       c.Check(words, deepEquals, []string{"word1", "word2;;;"})
+       t.CheckDeepEquals(words, []string{"word1", "word2;;;"})
 }
 
 func (s *Suite) Test_MkLine_Fields__varuse_with_embedded_space(c *check.C) {
@@ -1092,7 +1283,7 @@ func (s *Suite) Test_MkLine_Fields__varu
 
        words := mkline.Fields()
 
-       c.Check(words, deepEquals, []string{"${VAR:S/ /_/g}"})
+       t.CheckDeepEquals(words, []string{"${VAR:S/ /_/g}"})
 }
 
 func (s *Suite) Test_MkLine_ValueFields(c *check.C) {
@@ -1101,7 +1292,7 @@ func (s *Suite) Test_MkLine_ValueFields(
        test := func(value string, expected ...string) {
                mkline := t.NewMkLine("Makefile", 1, "VAR=\t"+value)
                split := mkline.ValueFields(value)
-               c.Check(split, deepEquals, expected)
+               t.CheckDeepEquals(split, expected)
        }
 
        test("one   two\t\t${THREE:Uthree:Nsome \tspaces}",
@@ -1124,7 +1315,7 @@ func (s *Suite) Test_MkLine_ValueFields_
        test := func(value string, expected ...string) {
                mkline := t.NewMkLine("Makefile", 1, "")
                split := mkline.ValueFields(value)
-               c.Check(split, deepEquals, expected)
+               t.CheckDeepEquals(split, expected)
        }
 
        test("\t; ${RM} ${WRKSRC}",
@@ -1140,21 +1331,21 @@ func (s *Suite) Test_MkLine_ValueFields_
 
        words, rest := splitIntoShellTokens(dummyLine, url) // Doesn't really make sense
 
-       c.Check(words, check.DeepEquals, []string{
+       t.CheckDeepEquals(words, []string{
                "http://registry.gimp.org/file/fix-ca.c?action=download";,
                "&",
                "id=9884",
                "&",
                "file="})
-       c.Check(rest, equals, "")
+       t.CheckEquals(rest, "")
 
        words = mkline.ValueFields(url)
 
-       c.Check(words, check.DeepEquals, []string{url})
+       t.CheckDeepEquals(words, []string{url})
 
        words = mkline.ValueFields("a b \"c  c  c\" d;;d;; \"e\"''`` 'rest")
 
-       c.Check(words, check.DeepEquals, []string{"a", "b", "\"c  c  c\"", "d;;d;;", "\"e\"''``"})
+       t.CheckDeepEquals(words, []string{"a", "b", "\"c  c  c\"", "d;;d;;", "\"e\"''``"})
        // TODO: c.Check(rest, equals, "'rest")
 }
 
@@ -1169,7 +1360,7 @@ func (s *Suite) Test_MkLine_ValueTokens(
        test := func(value string, expected []*MkToken, diagnostics ...string) {
                mkline := t.NewMkLine("Makefile", 1, "PATH=\t"+value)
                actualTokens, _ := mkline.ValueTokens()
-               c.Check(actualTokens, deepEquals, expected)
+               t.CheckDeepEquals(actualTokens, expected)
                t.CheckOutput(diagnostics)
        }
 
@@ -1207,13 +1398,13 @@ func (s *Suite) Test_MkLine_ValueTokens_
        tokens, rest := mkline.ValueTokens()
 
        t.Check(tokens, check.IsNil)
-       t.Check(rest, equals, "$")
+       t.CheckEquals(rest, "$")
 
        // Returns the same values, this time from the cache.
        tokens, rest = mkline.ValueTokens()
 
        t.Check(tokens, check.IsNil)
-       t.Check(rest, equals, "$")
+       t.CheckEquals(rest, "$")
 }
 
 func (s *Suite) Test_MkLine_ValueTokens__caching(c *check.C) {
@@ -1224,19 +1415,19 @@ func (s *Suite) Test_MkLine_ValueTokens_
        mkline := t.NewMkLine("Makefile", 1, "PATH=\tvalue ${UNFINISHED")
        valueTokens, rest := mkline.ValueTokens()
 
-       c.Check(valueTokens, deepEquals,
+       t.CheckDeepEquals(valueTokens,
                tokens(
                        &MkToken{"value ", nil},
                        &MkToken{"${UNFINISHED", NewMkVarUse("UNFINISHED")}))
-       c.Check(rest, equals, "")
+       t.CheckEquals(rest, "")
        t.CheckOutputLines(
                "WARN: Makefile:1: Missing closing \"}\" for \"UNFINISHED\".")
 
        // This time the slice is taken from the cache.
        tokens2, rest2 := mkline.ValueTokens()
 
-       c.Check(&tokens2[0], equals, &valueTokens[0])
-       c.Check(rest2, equals, rest)
+       t.CheckEquals(&tokens2[0], &valueTokens[0])
+       t.CheckEquals(rest2, rest)
 }
 
 func (s *Suite) Test_MkLine_ValueTokens__caching_parse_error(c *check.C) {
@@ -1250,16 +1441,16 @@ func (s *Suite) Test_MkLine_ValueTokens_
        mkline := t.NewMkLine("Makefile", 1, "PATH=\t${UNFINISHED")
        valueTokens, rest := mkline.ValueTokens()
 
-       c.Check(valueTokens, deepEquals, tokens(varuseText("${UNFINISHED", "UNFINISHED")))
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(valueTokens, tokens(varuseText("${UNFINISHED", "UNFINISHED")))
+       t.CheckEquals(rest, "")
        t.CheckOutputLines(
                "WARN: Makefile:1: Missing closing \"}\" for \"UNFINISHED\".")
 
        // This time the slice is taken from the cache.
        tokens2, rest2 := mkline.ValueTokens()
 
-       c.Check(&tokens2[0], equals, &valueTokens[0])
-       c.Check(rest2, equals, rest)
+       t.CheckEquals(&tokens2[0], &valueTokens[0])
+       t.CheckEquals(rest2, rest)
 }
 
 func (s *Suite) Test_MkLine_ValueTokens__warnings(c *check.C) {
@@ -1296,7 +1487,7 @@ func (s *Suite) Test_MkLine_ResolveVarsI
        mkline := mklines.mklines[0]
 
        test := func(before string, after string) {
-               c.Check(mkline.ResolveVarsInRelativePath(before), equals, after)
+               t.CheckEquals(mkline.ResolveVarsInRelativePath(before), after)
        }
 
        test("", ".")
@@ -1355,15 +1546,12 @@ func (s *Suite) Test_MkLine_ResolveVarsI
 func (s *Suite) Test_MkLineParser_MatchVarassign(c *check.C) {
        t := s.Init(c)
 
-       test := func(text string, commented bool, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string, diagnostics ...string) {
-               line := t.NewLine("filename.mk", 123, text)
-               data := MkLineParser{}.split(line, text)
-               m, actual := MkLineParser{}.MatchVarassign(line, text, data)
-               if !m {
-                       c.Errorf("Text %q doesn't match variable assignment", text)
-                       return
-               }
+       testLine := func(line *Line, commented bool, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string, diagnostics ...string) {
+               text := line.Text
+
+               m, actual := MkLineParser{}.MatchVarassign(line, text)
 
+               assert(m)
                expected := mkLineAssign{
                        commented:         commented,
                        varname:           varname,
@@ -1379,20 +1567,30 @@ func (s *Suite) Test_MkLineParser_MatchV
                        spaceAfterValue:   spaceAfterValue,
                        comment:           comment,
                }
-               c.Check(*actual, deepEquals, expected)
+               t.CheckDeepEquals(*actual, expected)
                t.CheckOutput(diagnostics)
        }
 
+       test := func(text string, commented bool, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string, diagnostics ...string) {
+               line := t.NewLine("filename.mk", 123, text)
+               testLine(line, commented, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment, diagnostics...)
+       }
+
        testInvalid := func(text string, diagnostics ...string) {
                line := t.NewLine("filename.mk", 123, text)
-               data := MkLineParser{}.split(nil, text)
-               m, _ := MkLineParser{}.MatchVarassign(line, text, data)
+               m, _ := MkLineParser{}.MatchVarassign(line, text)
                if m {
                        c.Errorf("Text %q matches variable assignment but shouldn't.", text)
                }
                t.CheckOutput(diagnostics)
        }
 
+       lines := func(text ...string) *Line {
+               mklines := t.NewMkLines("filename.mk",
+                       text...)
+               return mklines.mklines[0].Line
+       }
+
        test("C++=c11", false, "C+", "", "+=", "C++=", "c11", "", "")
        test("V=v", false, "V", "", "=", "V=", "v", "", "")
        test("VAR=#comment", false, "VAR", "", "=", "VAR=", "", "", "#comment")
@@ -1427,7 +1625,7 @@ func (s *Suite) Test_MkLineParser_MatchV
                "",
                "")
 
-       testInvalid("\tVAR=value")
+       t.ExpectAssert(func() { testInvalid("\tVAR=value") })
        testInvalid("?=value")
        testInvalid("<=value")
        testInvalid("#")
@@ -1493,14 +1691,7 @@ func (s *Suite) Test_MkLineParser_MatchV
                "EGDIRS=\t",
                "${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d",
                "",
-               "",
-
-               "WARN: filename.mk:123: Missing closing \"}\" for \"EGDIR/pam.d\".",
-               "WARN: filename.mk:123: Invalid part \"/pam.d\" after variable name \"EGDIR\".",
-               "WARN: filename.mk:123: Missing closing \"}\" for \"EGDIR/dbus-1/system.d ${EGDIR/pam.d\".",
-               "WARN: filename.mk:123: Invalid part \"/dbus-1/system.d ${EGDIR/pam.d\" after variable name \"EGDIR\".",
-               "WARN: filename.mk:123: Missing closing \"}\" for \"EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d\".",
-               "WARN: filename.mk:123: Invalid part \"/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d\" after variable name \"EGDIR\".")
+               "")
 
        test("VAR:=\t${VAR:M-*:[\\#]}",
                false,
@@ -1518,11 +1709,39 @@ func (s *Suite) Test_MkLineParser_MatchV
        testInvalid("# VAR=value")
        testInvalid("#\tVAR=value")
        testInvalid(MkCvsID)
+
+       testLine(
+               lines(
+                       "VAR=\t\t\t\\",
+                       "\tvalue"),
+               false,
+               "VAR",
+               "",
+               "=",
+               "VAR=\t\t\t",
+               "value",
+               "",
+               "")
+
+       testLine(
+               lines(
+                       "#VAR=\t\t\t\\",
+                       "#\tvalue"),
+               true,
+               "VAR",
+               "",
+               "=",
+               "#VAR=\t\t\t",
+               "value",
+               "",
+               "")
 }
 
 func (s *Suite) Test_NewMkOperator(c *check.C) {
-       c.Check(NewMkOperator(":="), equals, opAssignEval)
-       c.Check(NewMkOperator("="), equals, opAssign)
+       t := s.Init(c)
+
+       t.CheckEquals(NewMkOperator(":="), opAssignEval)
+       t.CheckEquals(NewMkOperator("="), opAssign)
 
        c.Check(func() { NewMkOperator("???") }, check.Panics, "Invalid operator: ???")
 }
@@ -1534,41 +1753,41 @@ func (s *Suite) Test_Indentation(c *chec
 
        mkline := t.NewMkLine("dummy.mk", 5, ".if 0")
 
-       c.Check(ind.Depth("if"), equals, 0)
-       c.Check(ind.DependsOn("VARNAME"), equals, false)
+       t.CheckEquals(ind.Depth("if"), 0)
+       t.CheckEquals(ind.DependsOn("VARNAME"), false)
 
        ind.Push(mkline, 2, "")
 
-       c.Check(ind.Depth("if"), equals, 2)
-       c.Check(ind.Depth("endfor"), equals, 0)
+       t.CheckEquals(ind.Depth("if"), 2)
+       t.CheckEquals(ind.Depth("endfor"), 0)
 
        ind.AddVar("LEVEL1.VAR1")
 
-       c.Check(ind.Varnames(), deepEquals, []string{"LEVEL1.VAR1"})
+       t.CheckDeepEquals(ind.Varnames(), []string{"LEVEL1.VAR1"})
 
        ind.AddVar("LEVEL1.VAR2")
 
-       c.Check(ind.Varnames(), deepEquals, []string{"LEVEL1.VAR1", "LEVEL1.VAR2"})
-       c.Check(ind.DependsOn("LEVEL1.VAR1"), equals, true)
-       c.Check(ind.DependsOn("OTHER_VAR"), equals, false)
+       t.CheckDeepEquals(ind.Varnames(), []string{"LEVEL1.VAR1", "LEVEL1.VAR2"})
+       t.CheckEquals(ind.DependsOn("LEVEL1.VAR1"), true)
+       t.CheckEquals(ind.DependsOn("OTHER_VAR"), false)
 
        ind.Push(mkline, 2, "")
 
        ind.AddVar("LEVEL2.VAR")
 
-       c.Check(ind.Varnames(), deepEquals, []string{"LEVEL1.VAR1", "LEVEL1.VAR2", "LEVEL2.VAR"})
-       c.Check(ind.String(), equals, "[2 (LEVEL1.VAR1 LEVEL1.VAR2) 2 (LEVEL2.VAR)]")
+       t.CheckDeepEquals(ind.Varnames(), []string{"LEVEL1.VAR1", "LEVEL1.VAR2", "LEVEL2.VAR"})
+       t.CheckEquals(ind.String(), "[2 (LEVEL1.VAR1 LEVEL1.VAR2) 2 (LEVEL2.VAR)]")
 
        ind.Pop()
 
-       c.Check(ind.Varnames(), deepEquals, []string{"LEVEL1.VAR1", "LEVEL1.VAR2"})
-       c.Check(ind.IsConditional(), equals, true)
+       t.CheckDeepEquals(ind.Varnames(), []string{"LEVEL1.VAR1", "LEVEL1.VAR2"})
+       t.CheckEquals(ind.IsConditional(), true)
 
        ind.Pop()
 
        c.Check(ind.Varnames(), check.HasLen, 0)
-       c.Check(ind.IsConditional(), equals, false)
-       c.Check(ind.String(), equals, "[]")
+       t.CheckEquals(ind.IsConditional(), false)
+       t.CheckEquals(ind.String(), "[]")
 }
 
 func (s *Suite) Test_Indentation__realistic(c *check.C) {
@@ -1643,7 +1862,7 @@ func (s *Suite) Test_Indentation_Remembe
        ind.RememberUsedVariables(mkline.Cond())
 
        t.CheckOutputEmpty()
-       c.Check(ind.Varnames(), deepEquals, []string{"PKGREVISION"})
+       t.CheckDeepEquals(ind.Varnames(), []string{"PKGREVISION"})
 }
 
 func (s *Suite) Test_Indentation_TrackAfter__checked_files(c *check.C) {
@@ -1736,7 +1955,7 @@ func (s *Suite) Test_MkLine_ForEachUsed(
                })
        }
 
-       c.Check(varnames, deepEquals, []string{
+       t.CheckDeepEquals(varnames, []string{
                "run VALUE",
                "load OPSYS",
                "load endianness",
@@ -1767,7 +1986,7 @@ func (s *Suite) Test_MkLine_UnquoteShell
 
        test := func(input, output string) {
                unquoted := (*MkLine).UnquoteShell(nil, input)
-               t.Check(unquoted, equals, output)
+               t.CheckEquals(unquoted, output)
        }
 
        test("", "")
@@ -1796,9 +2015,8 @@ func (s *Suite) Test_MkLineParser_unesca
 
        test := func(text string, main, comment string) {
                aMain, aComment := MkLineParser{}.unescapeComment(text)
-               t.Check(
+               t.CheckDeepEquals(
                        []interface{}{text, aMain, aComment},
-                       deepEquals,
                        []interface{}{text, main, comment})
        }
 
@@ -1940,7 +2158,7 @@ func (s *Suite) Test_MkLineParser_split(
                actualData := MkLineParser{}.split(line, text)
 
                t.CheckOutput(diagnostics)
-               t.Check([]interface{}{text, actualData}, deepEquals, []interface{}{text, data})
+               t.CheckDeepEquals([]interface{}{text, actualData}, []interface{}{text, data})
        }
 
        t.Use(text, varuse, varuseText, tokens)
@@ -2220,6 +2438,56 @@ func (s *Suite) Test_MkLineParser_split(
                        hasComment:         true,
                        comment:            " comment after spaces",
                })
+
+       // FIXME: This theoretical edge case is interpreted differently
+       //  between bmake and pkglint. Pkglint treats the # as a comment,
+       //  while bmake interprets it as a regular character.
+       test("\\[#",
+               mkLineSplitResult{
+                       main:       "\\[",
+                       tokens:     tokens(text("\\[")),
+                       hasComment: true,
+               })
+
+       test("\\\\[#",
+               mkLineSplitResult{
+                       main:   "\\\\[#",
+                       tokens: tokens(text("\\\\[#")),
+               })
+}
+
+func (s *Suite) Test_MkLineParser_split__unclosed_varuse(c *check.C) {
+       t := s.Init(c)
+
+       test := func(text string, expected mkLineSplitResult, diagnostics ...string) {
+               line := t.NewLine("filename.mk", 123, text)
+
+               data := MkLineParser{}.split(line, text)
+
+               t.CheckDeepEquals(data, expected)
+               t.CheckOutput(diagnostics)
+       }
+
+       test(
+               "EGDIRS=\t${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d",
+
+               mkLineSplitResult{
+                       "EGDIRS=\t${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d",
+                       []*MkToken{
+                               {"EGDIRS=\t", nil},
+                               {"${EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d",
+                                       NewMkVarUse("EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d")}},
+                       "",
+                       false,
+                       "",
+               },
+
+               "WARN: filename.mk:123: Missing closing \"}\" for \"EGDIR/pam.d\".",
+               "WARN: filename.mk:123: Invalid part \"/pam.d\" after variable name \"EGDIR\".",
+               "WARN: filename.mk:123: Missing closing \"}\" for \"EGDIR/dbus-1/system.d ${EGDIR/pam.d\".",
+               "WARN: filename.mk:123: Invalid part \"/dbus-1/system.d ${EGDIR/pam.d\" after variable name \"EGDIR\".",
+               "WARN: filename.mk:123: Missing closing \"}\" for \"EGDIR/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d\".",
+               "WARN: filename.mk:123: Invalid part \"/apparmor.d ${EGDIR/dbus-1/system.d ${EGDIR/pam.d\" after variable name \"EGDIR\".")
 }
 
 func (s *Suite) Test_MkLineParser_parseDirective(c *check.C) {
@@ -2233,9 +2501,8 @@ func (s *Suite) Test_MkLineParser_parseD
                        return
                }
 
-               c.Check(
+               t.CheckDeepEquals(
                        []interface{}{mkline.Indent(), mkline.Directive(), mkline.Args(), mkline.DirectiveComment()},
-                       deepEquals,
                        []interface{}{expectedIndent, expectedDirective, expectedArgs, expectedComment})
                t.CheckOutput(diagnostics)
        }
@@ -2265,9 +2532,8 @@ func (s *Suite) Test_MatchMkInclude(c *c
 
        test := func(input, expectedIndent, expectedDirective, expectedFilename string) {
                m, indent, directive, args := MatchMkInclude(input)
-               c.Check(
+               t.CheckDeepEquals(
                        []interface{}{m, indent, directive, args},
-                       deepEquals,
                        []interface{}{true, expectedIndent, expectedDirective, expectedFilename})
        }
 

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.42 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.43
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.42 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Sun Jul 14 21:25:47 2019
@@ -162,7 +162,7 @@ func (ck MkLineChecker) checkDirective(f
 
        case directive == "ifdef" || directive == "ifndef":
                mkline.Warnf("The \".%s\" directive is deprecated. Please use \".if %sdefined(%s)\" instead.",
-                       directive, ifelseStr(directive == "ifdef", "", "!"), args)
+                       directive, condStr(directive == "ifdef", "", "!"), args)
 
        case directive == "for":
                ck.checkDirectiveFor(forVars, ind)
@@ -421,7 +421,7 @@ func (ck MkLineChecker) checkVarassignLe
        }
 
        needsRationale := func(mkline *MkLine) bool {
-               if !mkline.IsVarassign() && !mkline.IsCommentedVarassign() {
+               if !mkline.IsVarassignMaybeCommented() {
                        return false
                }
                vartype := G.Pkgsrc.VariableType(ck.MkLines, mkline.Varname())
@@ -862,7 +862,7 @@ func (ck MkLineChecker) checkVarUseQuoti
        } else if needsQuoting == yes {
                modNoQ := strings.TrimSuffix(mod, ":Q")
                modNoM := strings.TrimSuffix(modNoQ, ":M*")
-               correctMod := modNoM + ifelseStr(needMstar, ":M*:Q", ":Q")
+               correctMod := modNoM + condStr(needMstar, ":M*:Q", ":Q")
                if correctMod == mod+":Q" && vuc.IsWordPart && !vartype.IsShell() {
 
                        isSingleWordConstant := func() bool {
@@ -1199,7 +1199,7 @@ func (ck MkLineChecker) checkTextVarUse(
                defer trace.Call(vartype, time)()
        }
 
-       tokens := NewMkParser(nil, text, false).MkTokens()
+       tokens := NewMkParser(nil, text).MkTokens()
        for i, token := range tokens {
                if token.Varuse != nil {
                        spaceLeft := i-1 < 0 || matches(tokens[i-1].Text, `[\t ]$`)
@@ -1484,7 +1484,7 @@ func (ck MkLineChecker) checkDirectiveCo
                defer trace.Call1(mkline.Args())()
        }
 
-       p := NewMkParser(nil, mkline.Args(), false) // No emitWarnings here, see the code below.
+       p := NewMkParser(nil, mkline.Args()) // No emitWarnings here, see the code below.
        cond := p.MkCond()
        if !p.EOF() {
                mkline.Warnf("Invalid condition, unrecognized part: %q.", p.Rest())
@@ -1507,8 +1507,8 @@ func (ck MkLineChecker) checkDirectiveCo
                        done[empty] = true
                }
 
-               varUse := not.Var
-               if varUse != nil {
+               if not.Term != nil && not.Term.Var != nil {
+                       varUse := not.Term.Var
                        ck.checkDirectiveCondEmpty(varUse, false, false, not == cond.Not)
                        done[varUse] = true
                }
@@ -1522,16 +1522,16 @@ func (ck MkLineChecker) checkDirectiveCo
 
        checkVar := func(varUse *MkVarUse) {
                if !done[varUse] {
-                       ck.checkDirectiveCondEmpty(varUse, false, true, varUse == cond.Var)
+                       ck.checkDirectiveCondEmpty(varUse, false, true, cond.Term != nil)
                }
        }
 
        cond.Walk(&MkCondCallback{
-               Not:           checkNotEmpty,
-               Empty:         checkEmpty,
-               Var:           checkVar,
-               CompareVarStr: ck.checkDirectiveCondCompareVarStr,
-               VarUse:        checkVarUse})
+               Not:     checkNotEmpty,
+               Empty:   checkEmpty,
+               Var:     checkVar,
+               Compare: ck.checkDirectiveCondCompare,
+               VarUse:  checkVarUse})
 }
 
 // checkDirectiveCondEmpty checks a condition of the form empty(VAR),
@@ -1573,15 +1573,15 @@ func (ck MkLineChecker) simplifyConditio
        // Before putting any cases involving special characters into
        // production, there need to be more tests for the edge cases.
        replace := func(varname string, m bool, pattern string) (string, string) {
-               op := ifelseStr(notEmpty == m, "==", "!=")
+               op := condStr(notEmpty == m, "==", "!=")
 
                from := "" +
-                       ifelseStr(notEmpty != fromEmpty, "", "!") +
-                       ifelseStr(fromEmpty, "empty(", "${") +
+                       condStr(notEmpty != fromEmpty, "", "!") +
+                       condStr(fromEmpty, "empty(", "${") +
                        varname +
-                       ifelseStr(m, ":M", ":N") +
+                       condStr(m, ":M", ":N") +
                        pattern +
-                       ifelseStr(fromEmpty, ")", "}")
+                       condStr(fromEmpty, ")", "}")
 
                to := "${" + varname + "} " + op + " " + pattern
 
@@ -1607,7 +1607,7 @@ func (ck MkLineChecker) simplifyConditio
 
                                fix := ck.MkLine.Autofix()
                                fix.Notef("%s should be compared using %s instead of matching against %q.",
-                                       varname, ifelseStr(positive == notEmpty, "==", "!="), ":"+modifier.Text)
+                                       varname, condStr(positive == notEmpty, "==", "!="), ":"+modifier.Text)
                                fix.Explain(
                                        "This variable has a single value, not a list of values.",
                                        "Therefore it feels strange to apply list operators like :M and :N onto it.",
@@ -1627,13 +1627,20 @@ func (ck MkLineChecker) checkCompareVarS
        ck.checkVartype(varname, opUseCompare, value, "")
 
        if varname == "PKGSRC_COMPILER" {
-               ck.MkLine.Warnf("Use ${PKGSRC_COMPILER:%s%s} instead of the %s operator.", ifelseStr(op == "==", "M", "N"), value, op)
+               ck.MkLine.Warnf("Use ${PKGSRC_COMPILER:%s%s} instead of the %s operator.", condStr(op == "==", "M", "N"), value, op)
                ck.MkLine.Explain(
                        "The PKGSRC_COMPILER can be a list of chained compilers, e.g. \"ccache distcc clang\".",
                        "Therefore, comparing it using == or != leads to wrong results in these cases.")
        }
 }
 
+func (ck MkLineChecker) checkDirectiveCondCompare(left *MkCondTerm, op string, right *MkCondTerm) {
+       switch {
+       case left.Var != nil && right.Var == nil && right.Num == "":
+               ck.checkDirectiveCondCompareVarStr(left.Var, op, right.Str)
+       }
+}
+
 func (ck MkLineChecker) checkDirectiveCondCompareVarStr(varuse *MkVarUse, op string, str string) {
        varname := varuse.varname
        varmods := varuse.modifiers
Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.42 pkgsrc/pkgtools/pkglint/files/plist.go:1.43
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.42 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Sun Jul 14 21:25:47 2019
@@ -54,17 +54,23 @@ type PlistLine struct {
        text       string   // Line.Text without any conditions of the form ${PLIST.cond}
 }
 
-func (ck *PlistChecker) Check(plainLines *Lines) {
-       plines := ck.NewLines(plainLines)
+func (ck *PlistChecker) Load(lines *Lines) []*PlistLine {
+       plines := ck.NewLines(lines)
        ck.collectFilesAndDirs(plines)
 
-       if plines[0].Basename == "PLIST.common_end" {
-               commonLines := Load(strings.TrimSuffix(plines[0].Filename, "_end"), NotEmpty)
+       if lines.BaseName == "PLIST.common_end" {
+               commonLines := Load(strings.TrimSuffix(lines.Filename, "_end"), NotEmpty)
                if commonLines != nil {
                        ck.collectFilesAndDirs(ck.NewLines(commonLines))
                }
        }
 
+       return plines
+}
+
+func (ck *PlistChecker) Check(plainLines *Lines) {
+       plines := ck.Load(plainLines)
+
        for _, pline := range plines {
                ck.checkLine(pline)
                pline.CheckTrailingWhitespace()

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.38 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.39
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.38    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Sun Jul 14 21:25:47 2019
@@ -603,9 +603,9 @@ func (s *Suite) Test_MkLineChecker_check
        vartype := G.Pkgsrc.VariableType(nil, "COMMENT")
 
        c.Assert(vartype, check.NotNil)
-       c.Check(vartype.basicType.name, equals, "Comment")
-       c.Check(vartype.Guessed(), equals, false)
-       c.Check(vartype.List(), equals, false)
+       t.CheckEquals(vartype.basicType.name, "Comment")
+       t.CheckEquals(vartype.Guessed(), false)
+       t.CheckEquals(vartype.List(), false)
 
        mklines := t.NewMkLines("Makefile",
                MkCvsID,
@@ -779,37 +779,106 @@ func (s *Suite) Test_MkLineChecker_check
                        "} for MACHINE_ARCH.",
                "NOTE: filename.mk:1: MACHINE_ARCH should be compared using == instead of matching against \":Mx86\".")
 
+       // Doesn't occur in practice since it is surprising that the ! applies
+       // to the comparison operator, and not to one of its arguments.
+       test(".if !${VAR} == value",
+               "WARN: filename.mk:1: VAR is used but not defined.")
+
+       // Doesn't occur in practice since this string can never be empty.
+       test(".if !\"${VAR}str\"",
+               "WARN: filename.mk:1: VAR is used but not defined.")
+
+       // Doesn't occur in practice since !${VAR} && !${VAR2} is more idiomatic.
+       test(".if !\"${VAR}${VAR2}\"",
+               "WARN: filename.mk:1: VAR is used but not defined.",
+               "WARN: filename.mk:1: VAR2 is used but not defined.")
+
+       // Just for code coverage; always evaluates to true.
+       test(".if \"string\"",
+               nil...)
+
+       // Code coverage for checkVar.
+       test(".if ${OPSYS} || ${MACHINE_ARCH}",
+               nil...)
+
+       test(".if ${VAR}",
+               "WARN: filename.mk:1: VAR is used but not defined.")
+
+       test(".if ${VAR} == 3",
+               "WARN: filename.mk:1: VAR is used but not defined.")
+
+       test(".if \"value\" == ${VAR}",
+               "WARN: filename.mk:1: VAR is used but not defined.")
+
        test(".if ${MASTER_SITES:Mftp://*} == \"ftp://netbsd.org/\"";,
                "WARN: filename.mk:1: Invalid variable modifier \"//*\" for \"MASTER_SITES\".",
                "WARN: filename.mk:1: \"ftp\" is not a valid URL.",
                "WARN: filename.mk:1: MASTER_SITES should not be used at load time in any file.",
                "WARN: filename.mk:1: Invalid variable modifier \"//*\" for \"MASTER_SITES\".")
+}
+
+func (s *Suite) Test_MkLineChecker_checkDirectiveCondCompare(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpVartypes()
+
+       test := func(cond string, output ...string) {
+               mklines := t.NewMkLines("filename.mk",
+                       cond)
+               mklines.ForEach(func(mkline *MkLine) {
+                       MkLineChecker{mklines, mkline}.checkDirectiveCond()
+               })
+               t.CheckOutput(output)
+       }
+
+       // As of July 2019, pkglint doesn't have specific checks for comparing
+       // variables to numbers.
+       test(".if ${VAR} > 0",
+               "WARN: filename.mk:1: VAR is used but not defined.")
+
+       // For string comparisons, the checks from vartypecheck.go are
+       // performed.
+       test(".if ${DISTNAME} == \"<>\"",
+               "WARN: filename.mk:1: The filename \"<>\" contains the invalid characters \"<>\".",
+               "WARN: filename.mk:1: DISTNAME should not be used at load time in any file.")
+
+       // This type of comparison doesn't occur in practice since it is
+       // overly verbose.
+       test(".if \"${BUILD_DIRS}str\" == \"str\"",
+               // TODO: why should it not be used? In a .for loop it sounds pretty normal.
+               "WARN: filename.mk:1: BUILD_DIRS should not be used at load time in any file.")
+
+       // This is a shorthand for defined(VAR), but it is not used in practice.
+       test(".if VAR",
+               "WARN: filename.mk:1: Invalid condition, unrecognized part: \"VAR\".")
+
+       // Calling a function with braces instead of parentheses is syntactically
+       // invalid. Pkglint is stricter than bmake in this situation.
+       //
+       // Bmake reads the "empty{VAR}" as a variable name. It then checks whether
+       // this variable is defined. It is not, of course, therefore the expression
+       // is false. The ! in front of it negates this false, which makes the whole
+       // condition true.
+       //
+       // See https://mail-index.netbsd.org/tech-pkg/2019/07/07/msg021539.html
+       test(".if !empty{VAR}",
+               "WARN: filename.mk:1: Invalid condition, unrecognized part: \"empty{VAR}\".")
+}
+
+func (s *Suite) Test_MkLineChecker_checkDirectiveCond__tracing(c *check.C) {
+       t := s.Init(c)
 
-       // The only interesting line from the below tracing output is the one
-       // containing "checkCompareVarStr".
        t.EnableTracingToLog()
-       test(".if ${VAR:Mpattern1:Mpattern2} == comparison",
-               "TRACE:   Indentation before line 1: []",
-               "TRACE: + MkLineChecker.checkDirectiveCond(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
-               "TRACE: 1 + (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
-               "TRACE: 1 - (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
+       mklines := t.NewMkLines("filename.mk",
+               ".if ${VAR:Mpattern1:Mpattern2} == comparison")
+
+       mklines.ForEach(func(mkline *MkLine) {
+               MkLineChecker{mklines, mkline}.checkDirectiveCond()
+       })
+
+       t.CheckOutputLinesMatching(`^WARN|checkCompare`,
                "TRACE: 1   checkCompareVarStr ${VAR:Mpattern1:Mpattern2} == comparison",
-               "TRACE: 1 + MkLineChecker.CheckVaruse(filename.mk:1, ${VAR:Mpattern1:Mpattern2}, (no-type time:load quoting:plain wordpart:false))",
-               "TRACE: 1 2 + (*Pkgsrc).VariableType(\"VAR\")",
-               "TRACE: 1 2 3   No type definition found for \"VAR\".",
-               "TRACE: 1 2 - (*Pkgsrc).VariableType(\"VAR\", \"=>\", (*pkglint.Vartype)(nil))",
-               "WARN: filename.mk:1: VAR is used but not defined.",
-               "TRACE: 1 2 + MkLineChecker.checkVarusePermissions(\"VAR\", (no-type time:load quoting:plain wordpart:false))",
-               "TRACE: 1 2 3   No type definition found for \"VAR\".",
-               "TRACE: 1 2 - MkLineChecker.checkVarusePermissions(\"VAR\", (no-type time:load quoting:plain wordpart:false))",
-               "TRACE: 1 2 + (*MkLine).VariableNeedsQuoting(${VAR:Mpattern1:Mpattern2}, (*pkglint.Vartype)(nil), (no-type time:load quoting:plain wordpart:false))",
-               "TRACE: 1 2 - (*MkLine).VariableNeedsQuoting(${VAR:Mpattern1:Mpattern2}, (*pkglint.Vartype)(nil), (no-type time:load quoting:plain wordpart:false), \"=>\", unknown)",
-               "TRACE: 1 - MkLineChecker.CheckVaruse(filename.mk:1, ${VAR:Mpattern1:Mpattern2}, (no-type time:load quoting:plain wordpart:false))",
-               "TRACE: - MkLineChecker.checkDirectiveCond(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
-               "TRACE: + (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
-               "TRACE: - (*MkParser).mkCondAtom(\"${VAR:Mpattern1:Mpattern2} == comparison\")",
-               "TRACE:   Indentation after line 1: [2 (VAR)]")
-       t.EnableSilentTracing()
+               "WARN: filename.mk:1: VAR is used but not defined.")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVarassign(c *check.C) {
@@ -1513,15 +1582,15 @@ func (s *Suite) Test_MkLineChecker_warnV
        mklines.Check()
 
        toolDependsType := G.Pkgsrc.VariableType(nil, "TOOL_DEPENDS")
-       t.Check(toolDependsType.String(), equals, "DependencyWithPath (list, package-settable)")
-       t.Check(toolDependsType.AlternativeFiles(aclpAppend), equals, "Makefile, Makefile.* or *.mk")
-       t.Check(toolDependsType.AlternativeFiles(aclpUse), equals, "Makefile, Makefile.* or *.mk")
-       t.Check(toolDependsType.AlternativeFiles(aclpUseLoadtime), equals, "")
+       t.CheckEquals(toolDependsType.String(), "DependencyWithPath (list, package-settable)")
+       t.CheckEquals(toolDependsType.AlternativeFiles(aclpAppend), "Makefile, Makefile.* or *.mk")
+       t.CheckEquals(toolDependsType.AlternativeFiles(aclpUse), "Makefile, Makefile.* or *.mk")
+       t.CheckEquals(toolDependsType.AlternativeFiles(aclpUseLoadtime), "")
 
        apiDependsType := G.Pkgsrc.VariableType(nil, "BUILDLINK_API_DEPENDS.*")
-       t.Check(apiDependsType.String(), equals, "Dependency (list, package-settable)")
-       t.Check(apiDependsType.AlternativeFiles(aclpUse), equals, "")
-       t.Check(apiDependsType.AlternativeFiles(aclpUseLoadtime), equals, "buildlink3.mk or builtin.mk only")
+       t.CheckEquals(apiDependsType.String(), "Dependency (list, package-settable)")
+       t.CheckEquals(apiDependsType.AlternativeFiles(aclpUse), "")
+       t.CheckEquals(apiDependsType.AlternativeFiles(aclpUseLoadtime), "buildlink3.mk or builtin.mk only")
 
        t.CheckOutputLines(
                "WARN: mk-c.mk:7: BUILDLINK_API_DEPENDS.mk-c should not be used in any file.",
@@ -1785,7 +1854,7 @@ func (s *Suite) Test_MkLineChecker_check
                        after := diagnosticsAndAfter[diagLen-1]
 
                        t.CheckOutput(diagnostics)
-                       t.Check(afterMklines.mklines[1].Text, equals, after)
+                       t.CheckEquals(afterMklines.mklines[1].Text, after)
                } else {
                        t.CheckOutputEmpty()
                }
@@ -2020,7 +2089,7 @@ func (s *Suite) Test_MkLineChecker_check
 
        words := mkline.Fields()
 
-       c.Check(words, deepEquals, []string{"`pkg-config pidgin --cflags`"})
+       t.CheckDeepEquals(words, []string{"`pkg-config pidgin --cflags`"})
 
        ck := MkLineChecker{mklines, mklines.mklines[1]}
        ck.checkVartype("CFLAGS", opAssignAppend, "`pkg-config pidgin --cflags`", "")
@@ -2714,7 +2783,7 @@ func (s *Suite) Test_MkLineChecker_Check
 func (s *Suite) Test_MkLineChecker_CheckRelativePath__absolute_path(c *check.C) {
        t := s.Init(c)
 
-       absDir := ifelseStr(runtime.GOOS == "windows", "C:/", "/")
+       absDir := condStr(runtime.GOOS == "windows", "C:/", "/")
        // Just a random UUID, to really guarantee that the file does not exist.
        absPath := absDir + "0f5c2d56-8a7a-4c9d-9caa-859b52bbc8c7"
 

Index: pkgsrc/pkgtools/pkglint/files/mklines.go
diff -u pkgsrc/pkgtools/pkglint/files/mklines.go:1.52 pkgsrc/pkgtools/pkglint/files/mklines.go:1.53
--- pkgsrc/pkgtools/pkglint/files/mklines.go:1.52       Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/mklines.go    Sun Jul 14 21:25:47 2019
@@ -87,7 +87,7 @@ func (mklines *MkLines) Check() {
        // In the first pass, all additions to BUILD_DEFS and USE_TOOLS
        // are collected to make the order of the definitions irrelevant.
        mklines.collectUsedVariables()
-       mklines.collectDefinedVariables()
+       mklines.collectVariables()
        mklines.collectPlistVars()
        mklines.collectElse()
 
@@ -289,10 +289,7 @@ func (mklines *MkLines) ExpandLoopVar(va
        return nil
 }
 
-func (mklines *MkLines) collectDefinedVariables() {
-       // FIXME: This method has a wrong name. It collects not only the defined
-       //  variables but also the used ones.
-
+func (mklines *MkLines) collectVariables() {
        if trace.Tracing {
                defer trace.Call0()()
        }
@@ -300,7 +297,7 @@ func (mklines *MkLines) collectDefinedVa
        mklines.ForEach(func(mkline *MkLine) {
                mklines.Tools.ParseToolLine(mklines, mkline, false, true)
 
-               if !mkline.IsVarassign() && !mkline.IsCommentedVarassign() {
+               if !mkline.IsVarassignMaybeCommented() {
                        return
                }
 
@@ -445,7 +442,7 @@ func (mklines *MkLines) collectDocumente
 
                        commentLines++
 
-                       parser := NewMkParser(nil, words[1], false)
+                       parser := NewMkParser(nil, words[1])
                        varname := parser.Varname()
                        if len(varname) < 3 {
                                break
@@ -573,248 +570,3 @@ func (mklines *MkLines) SaveAutofixChang
 func (mklines *MkLines) EOFLine() *MkLine {
        return MkLineParser{}.Parse(mklines.lines.EOFLine())
 }
-
-// VaralignBlock checks that all variable assignments from a paragraph
-// use the same indentation depth for their values.
-// It also checks that the indentation uses tabs instead of spaces.
-//
-// 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-file-name.tar.gz variable
-// between HOMEPAGE and DISTFILES.
-type VaralignBlock struct {
-       infos []*varalignBlockInfo
-       skip  bool
-}
-
-type varalignBlockInfo struct {
-       mkline         *MkLine
-       varnameOp      string // Variable name + assignment operator
-       varnameOpWidth int    // Screen width of varnameOp
-       space          string // Whitespace between varnameOp and the variable value
-       totalWidth     int    // Screen width of varnameOp + space
-       continuation   bool   // A continuation line with no value in the first line.
-}
-
-func (va *VaralignBlock) Process(mkline *MkLine) {
-       switch {
-       case !G.Opts.WarnSpace:
-               return
-
-       case mkline.IsEmpty():
-               va.Finish()
-               return
-
-       case mkline.IsVarassign(), mkline.IsCommentedVarassign():
-               va.processVarassign(mkline)
-
-       case mkline.IsComment(), mkline.IsDirective():
-               return
-
-       default:
-               trace.Stepf("Skipping")
-               va.skip = true
-               return
-       }
-}
-
-func (va *VaralignBlock) processVarassign(mkline *MkLine) {
-       switch {
-       case mkline.Op() == opAssignEval && matches(mkline.Varname(), `^[a-z]`):
-               // Arguments to procedures do not take part in block alignment.
-               //
-               // Example:
-               // pkgpath := ${PKGPATH}
-               return
-
-       case mkline.Value() == "" && mkline.VarassignComment() == "":
-               // Multiple-inclusion guards usually appear in a block of
-               // their own and therefore do not need alignment.
-               //
-               // Example:
-               // .if !defined(INCLUSION_GUARD_MK)
-               // INCLUSION_GUARD_MK:=
-               // # ...
-               // .endif
-               return
-       }
-
-       continuation := false
-       if mkline.IsMultiline() {
-               // Parsing the continuation marker as variable value is cheating but works well.
-               text := strings.TrimSuffix(mkline.raw[0].orignl, "\n")
-               data := MkLineParser{}.split(nil, text)
-               m, a := MkLineParser{}.MatchVarassign(mkline.Line, text, data)
-               assert(m)
-               continuation = a.value == "\\"
-       }
-
-       valueAlign := mkline.ValueAlign()
-       varnameOp := strings.TrimRight(valueAlign, " \t")
-       space := valueAlign[len(varnameOp):]
-
-       width := tabWidth(valueAlign)
-       va.infos = append(va.infos, &varalignBlockInfo{mkline, varnameOp, tabWidth(varnameOp), space, width, continuation})
-}
-
-func (va *VaralignBlock) Finish() {
-       infos := va.infos
-       skip := va.skip
-       *va = VaralignBlock{}
-
-       if len(infos) == 0 || skip {
-               return
-       }
-
-       if trace.Tracing {
-               defer trace.Call(infos[0].mkline.Line)()
-       }
-
-       newWidth := va.optimalWidth(infos)
-       if newWidth == 0 {
-               return
-       }
-
-       for _, info := range infos {
-               va.realign(info.mkline, info.varnameOp, info.space, info.continuation, newWidth)
-       }
-}
-
-// optimalWidth computes the minimum necessary screen width for the
-// variable assignment lines. There may be a single line sticking out
-// from the others (called outlier). This is to prevent a single SITES.*
-// variable from forcing the rest of the paragraph to be indented too
-// far to the right.
-func (va *VaralignBlock) optimalWidth(infos []*varalignBlockInfo) int {
-       longest := 0       // The longest seen varnameOpWidth
-       secondLongest := 0 // The second-longest seen varnameOpWidth
-       for _, info := range infos {
-               if info.continuation {
-                       continue
-               }
-
-               width := info.varnameOpWidth
-               if width >= longest {
-                       secondLongest = longest
-                       longest = width
-               } else if width > secondLongest {
-                       secondLongest = width
-               }
-       }
-
-       // Minimum required width of varnameOp, without the trailing whitespace.
-       minVarnameOpWidth := longest
-       outlier := 0
-       if secondLongest != 0 && secondLongest/8+1 < longest/8 {
-               minVarnameOpWidth = secondLongest
-               outlier = longest
-       }
-
-       // Widths of the current indentation (including whitespace)
-       minTotalWidth := 0
-       maxTotalWidth := 0
-       for _, info := range infos {
-               if info.continuation {
-                       continue
-               }
-
-               if width := info.totalWidth; info.varnameOpWidth != outlier {
-                       if minTotalWidth == 0 || width < minTotalWidth {
-                               minTotalWidth = width
-                       }
-                       maxTotalWidth = imax(maxTotalWidth, width)
-               }
-       }
-
-       if trace.Tracing {
-               trace.Stepf("Indentation including whitespace is between %d and %d.",
-                       minTotalWidth, maxTotalWidth)
-               trace.Stepf("Minimum required indentation is %d + 1.", minVarnameOpWidth)
-               if outlier != 0 {
-                       trace.Stepf("The outlier is at indentation %d.", outlier)
-               }
-       }
-
-       if minTotalWidth > minVarnameOpWidth && minTotalWidth == maxTotalWidth && minTotalWidth%8 == 0 {
-               // The whole paragraph is already indented to the same width.
-               return minTotalWidth
-       }
-
-       if minVarnameOpWidth == 0 {
-               // Only continuation lines in this paragraph.
-               return 0
-       }
-
-       return (minVarnameOpWidth & -8) + 8
-}
-
-func (va *VaralignBlock) realign(mkline *MkLine, varnameOp, oldSpace string, continuation bool, newWidth int) {
-       hasSpace := contains(oldSpace, " ")
-
-       newSpace := ""
-       for tabWidth(varnameOp+newSpace) < newWidth {
-               newSpace += "\t"
-       }
-       // Indent the outlier with a space instead of a tab to keep the line short.
-       if newSpace == "" {
-               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.
-                       newSpace = oldSpace
-               } else {
-                       newSpace = " "
-               }
-       }
-
-       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)
-       case hasSpace && oldSpace != newSpace:
-               fix.Notef("Variable values should be aligned with tabs, not spaces.")
-       case wrongColumn:
-               fix.Notef("This variable value should be aligned to column %d.", newWidth+1)
-       default:
-               return
-       }
-
-       if wrongColumn {
-               fix.Explain(
-                       "Normally, all variable values in a block should start at the same column.",
-                       "This provides orientation, especially for sequences",
-                       "of variables that often appear in the same order.",
-                       "For these it suffices to look at the variable values only.",
-                       "",
-                       "There are some exceptions to this rule:",
-                       "",
-                       "Definitions for long variable names may be indented with a single space instead of tabs,",
-                       "but only if they appear in a block that is otherwise indented using tabs.",
-                       "",
-                       "Variable definitions that span multiple lines are not checked for alignment at all.",
-                       "",
-                       "When the block contains something else than variable definitions",
-                       "and directives like .if or .for, it is not checked at all.")
-       }
-
-       fix.ReplaceAfter(varnameOp, oldSpace, newSpace)
-       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.46 pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.47
--- pkgsrc/pkgtools/pkglint/files/mklines_test.go:1.46  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mklines_test.go       Sun Jul 14 21:25:47 2019
@@ -100,7 +100,7 @@ func (s *Suite) Test_MkLines__varuse_sh_
                vars2 = append(vars2, varUse.varname)
        })
 
-       c.Check(vars2, deepEquals, []string{"SED"})
+       t.CheckDeepEquals(vars2, []string{"SED"})
 
        var vars3 []string
        mklines.mklines[2].ForEachUsed(func(varUse *MkVarUse, time VucTime) {
@@ -108,7 +108,7 @@ func (s *Suite) Test_MkLines__varuse_sh_
        })
 
        // qore-version, despite its unusual name, is a pretty normal Make variable.
-       c.Check(vars3, deepEquals, []string{"qore-version"})
+       t.CheckDeepEquals(vars3, []string{"qore-version"})
 
        mklines.Check()
 
@@ -315,7 +315,7 @@ func (s *Suite) Test_MkLines_CheckUsedBy
                        "WARN: Makefile.common:4: Please add a line \"# used by category/package\" here.",
                        "AUTOFIX: Makefile.common:4: Inserting a line \"# used by category/package\" after this line."))
 
-       c.Check(G.Logger.autofixAvailable, equals, true)
+       t.CheckEquals(G.Logger.autofixAvailable, true)
 }
 
 func (s *Suite) Test_MkLines_CheckUsedBy(c *check.C) {
@@ -383,7 +383,33 @@ func (s *Suite) Test_MkLines_CheckUsedBy
                        "WARN: Makefile.common:4: There should only be a single \"used by\" paragraph per file.",
                        "WARN: Makefile.common:2: Please add a line \"# used by category/package\" here."))
 
-       c.Check(G.Logger.autofixAvailable, equals, true)
+       // Code coverage for hasOther being true and conflict being non-nil.
+       // Ensures that the warning is printed in the first wrong line.
+       test("category/package",
+               lines(
+                       MkCvsID,
+                       "",
+                       "# Unrelated comment.",
+                       "# used by category/package1",
+                       "# used by category/package2"),
+               diagnostics(
+                       "WARN: Makefile.common:4: The \"used by\" lines should be in a separate paragraph.",
+                       "WARN: Makefile.common:1: Please add a line \"# used by category/package\" here."))
+
+       // Code coverage for hasUsedBy being true and conflict being non-nil.
+       // Ensures that the warning is printed in the first wrong line.
+       test("category/package",
+               lines(
+                       MkCvsID,
+                       "",
+                       "# used by category/package1",
+                       "# Unrelated comment.",
+                       "# Unrelated comment 2."),
+               diagnostics(
+                       "WARN: Makefile.common:4: The \"used by\" lines should be in a separate paragraph.",
+                       "WARN: Makefile.common:1: Please add a line \"# used by category/package\" here."))
+
+       t.CheckEquals(G.Logger.autofixAvailable, true)
 }
 
 func (s *Suite) Test_MkLines_CheckUsedBy__separate_paragraph(c *check.C) {
@@ -424,8 +450,8 @@ func (s *Suite) Test_MkLines_ExpandLoopV
                }
        })
 
-       t.Check(files, deepEquals, strings.Split("abcdefgh", ""))
-       t.Check(ranks, deepEquals, strings.Split("12345678", ""))
+       t.CheckDeepEquals(files, strings.Split("abcdefgh", ""))
+       t.CheckDeepEquals(ranks, strings.Split("12345678", ""))
        t.Check(diagonals, check.HasLen, 0)
 }
 
@@ -475,7 +501,7 @@ func (s *Suite) Test_MkLines_ExpandLoopV
        t.Check(values, check.HasLen, 0)
 }
 
-func (s *Suite) Test_MkLines_collectDefinedVariables(c *check.C) {
+func (s *Suite) Test_MkLines_collectVariables(c *check.C) {
        t := s.Init(c)
 
        t.SetUpCommandLine("-Wall,no-space")
@@ -512,7 +538,7 @@ func (s *Suite) Test_MkLines_collectDefi
        t.CheckOutputEmpty()
 }
 
-func (s *Suite) Test_MkLines_collectDefinedVariables__BUILTIN_FIND_FILES_VAR(c *check.C) {
+func (s *Suite) Test_MkLines_collectVariables__BUILTIN_FIND_FILES_VAR(c *check.C) {
        t := s.Init(c)
 
        t.SetUpCommandLine("-Wall,no-space")
@@ -537,7 +563,7 @@ func (s *Suite) Test_MkLines_collectDefi
                "WARN: ~/category/package/builtin.mk:8: H_UNDEF is used but not defined.")
 }
 
-func (s *Suite) Test_MkLines_collectDefinedVariables__no_tracing(c *check.C) {
+func (s *Suite) Test_MkLines_collectVariables__no_tracing(c *check.C) {
        t := s.Init(c)
 
        mklines := t.SetUpFileMkLines("filename.mk",
@@ -548,7 +574,7 @@ func (s *Suite) Test_MkLines_collectDefi
                "SUBST_VARS.id+=\tVAR3")
        t.DisableTracing()
 
-       mklines.collectDefinedVariables()
+       mklines.collectVariables()
 
        t.CheckOutputEmpty()
 }
@@ -562,8 +588,8 @@ func (s *Suite) Test_MkLines_collectUsed
 
        mklines.collectUsedVariables()
 
-       c.Check(mklines.vars.used, deepEquals, map[string]*MkLine{"VAR": mkline})
-       c.Check(mklines.vars.FirstUse("VAR"), equals, mkline)
+       t.CheckDeepEquals(mklines.vars.used, map[string]*MkLine{"VAR": mkline})
+       t.CheckEquals(mklines.vars.FirstUse("VAR"), mkline)
 }
 
 func (s *Suite) Test_MkLines_collectUsedVariables__nested(c *check.C) {
@@ -581,12 +607,12 @@ func (s *Suite) Test_MkLines_collectUsed
 
        mklines.collectUsedVariables()
 
-       c.Check(len(mklines.vars.used), equals, 5)
-       c.Check(mklines.vars.FirstUse("lparam"), equals, assignMkline)
-       c.Check(mklines.vars.FirstUse("rparam"), equals, assignMkline)
-       c.Check(mklines.vars.FirstUse("inner"), equals, shellMkline)
-       c.Check(mklines.vars.FirstUse("outer.*"), equals, shellMkline)
-       c.Check(mklines.vars.FirstUse("outer.${inner}"), equals, shellMkline)
+       t.CheckEquals(len(mklines.vars.used), 5)
+       t.CheckEquals(mklines.vars.FirstUse("lparam"), assignMkline)
+       t.CheckEquals(mklines.vars.FirstUse("rparam"), assignMkline)
+       t.CheckEquals(mklines.vars.FirstUse("inner"), shellMkline)
+       t.CheckEquals(mklines.vars.FirstUse("outer.*"), shellMkline)
+       t.CheckEquals(mklines.vars.FirstUse("outer.${inner}"), shellMkline)
 }
 
 func (s *Suite) Test_MkLines__private_tool_undefined(c *check.C) {
@@ -860,7 +886,7 @@ func (s *Suite) Test_MkLines_collectDocu
                "VARBASE1.* (line 27)",
                "VARBASE2.* (line 28)",
                "VARBASE3.* (line 29)"}
-       c.Check(varnames, deepEquals, expected)
+       t.CheckDeepEquals(varnames, expected)
 }
 
 func (s *Suite) Test_MkLines__shell_command_indentation(c *check.C) {
@@ -1021,9 +1047,9 @@ func (s *Suite) Test_MkLines_collectElse
 
        mklines.collectElse()
 
-       c.Check(mklines.mklines[2].HasElseBranch(), equals, false)
-       c.Check(mklines.mklines[5].HasElseBranch(), equals, true)
-       c.Check(mklines.mklines[9].HasElseBranch(), equals, false)
+       t.CheckEquals(mklines.mklines[2].HasElseBranch(), false)
+       t.CheckEquals(mklines.mklines[5].HasElseBranch(), true)
+       t.CheckEquals(mklines.mklines[9].HasElseBranch(), false)
 }
 
 func (s *Suite) Test_MkLines_Check__defined_and_used_variables(c *check.C) {
@@ -1225,7 +1251,7 @@ func (s *Suite) Test_MkLines_SplitToPara
                        exp = append(exp, NewParagraph(mklines, r.from, r.to))
                }
 
-               t.Check(paras, deepEquals, exp)
+               t.CheckDeepEquals(paras, exp)
        }
 
        para := func(from, to int) lineRange { return lineRange{from, to} }
@@ -1250,6 +1276,31 @@ func (s *Suite) Test_MkLines_SplitToPara
                t.NewMkLines("filename.mk",
                        ""),
                nil...)
+
+       // Test coverage for i == 0.
+       test(
+               t.NewMkLines("filename.mk",
+                       "#"),
+               nil...)
+
+       // The empty comment line is not a paragraph separator. To be a
+       // separator, it would have to be enclosed by comment lines.
+       test(
+               t.NewMkLines("filename.mk",
+                       "VAR=\tvalue",
+                       "#"),
+               para(0, 2))
+
+       // The empty comment line is not a paragraph separator because
+       // below it there is no comment. This is a typical way of separating
+       // a multi-line comment from a variable definition.
+       test(
+               t.NewMkLines("filename.mk",
+                       "# This comment spans",
+                       "# multiple lines.",
+                       "#",
+                       "VAR=\tvalue"),
+               para(0, 4))
 }
 
 // Ensures that during MkLines.ForEach, the conditional variables in
@@ -1277,17 +1328,17 @@ func (s *Suite) Test_MkLines_ForEach__co
                if mkline.IsVarassign() {
                        switch mkline.Varname() {
                        case "DEVELOPER":
-                               c.Check(mklines.indentation.IsConditional(), equals, true)
+                               t.CheckEquals(mklines.indentation.IsConditional(), true)
                                seenDeveloper = true
                        case "USES_GETTEXT":
-                               c.Check(mklines.indentation.IsConditional(), equals, true)
+                               t.CheckEquals(mklines.indentation.IsConditional(), true)
                                seenUsesGettext = true
                        }
                }
        })
 
-       c.Check(seenDeveloper, equals, true)
-       c.Check(seenUsesGettext, equals, true)
+       t.CheckEquals(seenDeveloper, true)
+       t.CheckEquals(seenUsesGettext, true)
 }
 
 // At 2018-12-02, pkglint had resolved ${MY_PLIST_VARS} into a single word,
@@ -1310,119 +1361,3 @@ func (s *Suite) Test_MkLines_checkVarass
 
        t.CheckOutputEmpty()
 }
-
-func (s *Suite) Test_VaralignBlock_Process__autofix(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpCommandLine("-Wspace", "--show-autofix")
-
-       mklines := t.NewMkLines("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.
-
-       var varalign VaralignBlock
-       for _, line := range mklines.mklines {
-               varalign.Process(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\".")
-}
-
-// When the lines of a paragraph are inconsistently aligned,
-// they are realigned to the minimum required width.
-func (s *Suite) Test_VaralignBlock_Process__reduce_indentation(c *check.C) {
-       t := s.Init(c)
-
-       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")
-
-       var varalign VaralignBlock
-       for _, mkline := range mklines.mklines {
-               varalign.Process(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.")
-}
-
-// For every variable assignment, there is at least one space or tab between the variable
-// name and the value. Even if it is the longest line, and even if the value would start
-// exactly at a tab stop.
-func (s *Suite) Test_VaralignBlock_Process__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.aaaaaa=cat")
-
-       var varalign VaralignBlock
-       for _, mkline := range mklines.mklines {
-               varalign.Process(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_Process__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")
-
-       var varalign VaralignBlock
-       for _, mkline := range mklines.mklines {
-               varalign.Process(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/mkparser_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.30 pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.31
--- pkgsrc/pkgtools/pkglint/files/mkparser_test.go:1.30 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mkparser_test.go      Sun Jul 14 21:25:47 2019
@@ -5,30 +5,21 @@ import (
        "strings"
 )
 
-func (s *Suite) Test_NewMkParser__invalid_arguments(c *check.C) {
-       t := s.Init(c)
-
-       line := t.NewLine("filename.mk", 123, "")
-
-       t.ExpectAssert(func() { NewMkParser(line, "", false) })
-       t.ExpectAssert(func() { NewMkParser(nil, "", true) })
-}
-
 func (s *Suite) Test_MkParser_MkTokens(c *check.C) {
        t := s.Init(c)
 
        testRest := func(input string, expectedTokens []*MkToken, expectedRest string) {
                line := t.NewLines("Test_MkParser_MkTokens.mk", input).Lines[0]
-               p := NewMkParser(line, input, true)
+               p := NewMkParser(line, input)
                actualTokens := p.MkTokens()
-               c.Check(actualTokens, deepEquals, expectedTokens)
+               t.CheckDeepEquals(actualTokens, expectedTokens)
                for i, expectedToken := range expectedTokens {
                        if i < len(actualTokens) {
-                               c.Check(*actualTokens[i], deepEquals, *expectedToken)
-                               c.Check(actualTokens[i].Varuse, deepEquals, expectedToken.Varuse)
+                               t.CheckDeepEquals(*actualTokens[i], *expectedToken)
+                               t.CheckDeepEquals(actualTokens[i].Varuse, expectedToken.Varuse)
                        }
                }
-               c.Check(p.Rest(), equals, expectedRest)
+               t.CheckEquals(p.Rest(), expectedRest)
        }
        test := func(input string, expectedToken *MkToken) {
                testRest(input, []*MkToken{expectedToken}, "")
@@ -97,18 +88,18 @@ func (s *Suite) Test_MkParser_VarUse(c *
 
        testRest := func(input string, expectedTokens []*MkToken, expectedRest string, diagnostics ...string) {
                line := t.NewLines("Test_MkParser_VarUse.mk", input).Lines[0]
-               p := NewMkParser(line, input, true)
+               p := NewMkParser(line, input)
 
                actualTokens := p.MkTokens()
 
-               c.Check(actualTokens, deepEquals, expectedTokens)
+               t.CheckDeepEquals(actualTokens, expectedTokens)
                for i, expectedToken := range expectedTokens {
                        if i < len(actualTokens) {
-                               c.Check(*actualTokens[i], deepEquals, *expectedToken)
-                               c.Check(actualTokens[i].Varuse, deepEquals, expectedToken.Varuse)
+                               t.CheckDeepEquals(*actualTokens[i], *expectedToken)
+                               t.CheckDeepEquals(actualTokens[i].Varuse, expectedToken.Varuse)
                        }
                }
-               c.Check(p.Rest(), equals, expectedRest)
+               t.CheckEquals(p.Rest(), expectedRest)
                t.CheckOutput(diagnostics)
        }
        tokens := func(tokens ...*MkToken) []*MkToken { return tokens }
@@ -370,6 +361,13 @@ func (s *Suite) Test_MkParser_VarUse(c *
                "WARN: Test_MkParser_VarUse.mk:1: Modifier ${PLIST_SUBST_VARS:@var@...@} is missing the final \"@\".",
                "WARN: Test_MkParser_VarUse.mk:1: Missing closing \"}\" for \"PLIST_SUBST_VARS\".")
 
+       // The replacement text may include closing braces, which is useful
+       // for AWK programs.
+       test("${PLIST_SUBST_VARS:@var@{${var}}@}",
+               varuseText("${PLIST_SUBST_VARS:@var@{${var}}@}",
+                       "PLIST_SUBST_VARS", "@var@{${var}}@"),
+               nil...)
+
        // Unfinished variable use
        test("${",
                varuseText("${", ""),
@@ -387,12 +385,12 @@ func (s *Suite) Test_MkParser_varUseModi
 
        t.SetUpCommandLine("-Wall", "--explain")
        line := t.NewLine("filename.mk", 123, "${VAR:tsabc}")
-       p := NewMkParser(line, "tsabc}", true)
+       p := NewMkParser(line, "tsabc}")
 
        modifier := p.varUseModifier("VAR", '}')
 
-       t.Check(modifier, equals, "tsabc")
-       t.Check(p.Rest(), equals, "}")
+       t.CheckEquals(modifier, "tsabc")
+       t.CheckEquals(p.Rest(), "}")
        t.CheckOutputLines(
                "WARN: filename.mk:123: Invalid separator \"abc\" for :ts modifier of \"VAR\".",
                "",
@@ -405,24 +403,24 @@ func (s *Suite) Test_MkParser_varUseModi
 func (s *Suite) Test_MkParser_varUseModifier__invalid_ts_modifier_without_warning(c *check.C) {
        t := s.Init(c)
 
-       p := NewMkParser(nil, "tsabc}", false)
+       p := NewMkParser(nil, "tsabc}")
 
        modifier := p.varUseModifier("VAR", '}')
 
-       t.Check(modifier, equals, "tsabc")
-       t.Check(p.Rest(), equals, "}")
+       t.CheckEquals(modifier, "tsabc")
+       t.CheckEquals(p.Rest(), "}")
 }
 
 func (s *Suite) Test_MkParser_varUseModifier__square_bracket(c *check.C) {
        t := s.Init(c)
 
        line := t.NewLine("filename.mk", 123, "\t${VAR:[asdf]}")
-       p := NewMkParser(line, "[asdf]", true)
+       p := NewMkParser(line, "[asdf]")
 
        modifier := p.varUseModifier("VAR", '}')
 
-       t.Check(modifier, equals, "")
-       t.Check(p.Rest(), equals, "")
+       t.CheckEquals(modifier, "")
+       t.CheckEquals(p.Rest(), "")
 
        t.CheckOutputLines(
                "WARN: filename.mk:123: Invalid variable modifier \"[asdf]\" for \"VAR\".")
@@ -432,14 +430,14 @@ func (s *Suite) Test_MkParser_varUseModi
        t := s.Init(c)
 
        line := t.NewLine("filename.mk", 123, "${${VAR}:?yes:no}${${VAR}:?yes}")
-       p := NewMkParser(line, line.Text, true)
+       p := NewMkParser(line, line.Text)
 
        varUse1 := p.VarUse()
        varUse2 := p.VarUse()
 
-       t.Check(varUse1, deepEquals, NewMkVarUse("${VAR}", "?yes:no"))
-       t.Check(varUse2, deepEquals, NewMkVarUse("${VAR}"))
-       t.Check(p.Rest(), equals, "")
+       t.CheckDeepEquals(varUse1, NewMkVarUse("${VAR}", "?yes:no"))
+       t.CheckDeepEquals(varUse2, NewMkVarUse("${VAR}"))
+       t.CheckEquals(p.Rest(), "")
 
        t.CheckOutputLines(
                "WARN: filename.mk:123: Invalid variable modifier \"?yes\" for \"${VAR}\".")
@@ -449,12 +447,12 @@ func (s *Suite) Test_MkParser_varUseModi
        t := s.Init(c)
 
        line := t.NewLine("filename.mk", 123, "$(${VAR}:?yes)")
-       p := NewMkParser(line, line.Text, true)
+       p := NewMkParser(line, line.Text)
 
        varUse := p.VarUse()
 
-       t.Check(varUse, deepEquals, NewMkVarUse("${VAR}"))
-       t.Check(p.Rest(), equals, "")
+       t.CheckDeepEquals(varUse, NewMkVarUse("${VAR}"))
+       t.CheckEquals(p.Rest(), "")
 
        t.CheckOutputLines(
                "WARN: filename.mk:123: Invalid variable modifier \"?yes\" for \"${VAR}\".",
@@ -465,12 +463,12 @@ func (s *Suite) Test_MkParser_varUseModi
        t := s.Init(c)
 
        line := t.NewLine("filename.mk", 123, "${${VAR}:?yes${INNER}}")
-       p := NewMkParser(line, line.Text, true)
+       p := NewMkParser(line, line.Text)
 
        varUse := p.VarUse()
 
-       t.Check(varUse, deepEquals, NewMkVarUse("${VAR}"))
-       t.Check(p.Rest(), equals, "")
+       t.CheckDeepEquals(varUse, NewMkVarUse("${VAR}"))
+       t.CheckEquals(p.Rest(), "")
 
        t.CheckOutputLines(
                "WARN: filename.mk:123: Invalid variable modifier \"?yes${INNER}\" for \"${VAR}\".")
@@ -480,12 +478,12 @@ func (s *Suite) Test_MkParser_varUseModi
        t := s.Init(c)
 
        line := t.NewLine("filename.mk", 123, "${VAR:@varname}")
-       p := NewMkParser(line, line.Text, true)
+       p := NewMkParser(line, line.Text)
 
        varUse := p.VarUse()
 
-       t.Check(varUse, deepEquals, NewMkVarUse("VAR"))
-       t.Check(p.Rest(), equals, "")
+       t.CheckDeepEquals(varUse, NewMkVarUse("VAR"))
+       t.CheckEquals(p.Rest(), "")
        t.CheckOutputLines(
                "WARN: filename.mk:123: Invalid variable modifier \"@varname\" for \"VAR\".")
 }
@@ -494,27 +492,24 @@ func (s *Suite) Test_MkParser_varUseModi
        t := s.Init(c)
 
        line := t.NewLine("filename.mk", 123, "${VAR:@var@$$var@}")
-       p := NewMkParser(line, line.Text, true)
+       p := NewMkParser(line, line.Text)
 
        varUse := p.VarUse()
 
-       t.Check(varUse, deepEquals, NewMkVarUse("VAR", "@var@$$var@"))
-       t.Check(p.Rest(), equals, "")
+       t.CheckDeepEquals(varUse, NewMkVarUse("VAR", "@var@$$var@"))
+       t.CheckEquals(p.Rest(), "")
        t.CheckOutputEmpty()
 }
 
 func (s *Suite) Test_MkParser_varUseModifierAt__incomplete_without_warning(c *check.C) {
        t := s.Init(c)
 
-       p := NewMkParser(nil, "${VAR:@var@$$var}rest", false)
+       p := NewMkParser(nil, "${VAR:@var@$$var}rest")
 
        varUse := p.VarUse()
 
-       // TODO: It's inconsistent that this syntax error still produces a
-       //  variable modifier, while most other syntax errors don't.
-       // FIXME: The } must not be part of the variable modifier.
-       t.Check(varUse, deepEquals, NewMkVarUse("VAR", "@var@$$var}rest"))
-       t.Check(p.Rest(), equals, "")
+       t.CheckDeepEquals(varUse, NewMkVarUse("VAR", "@var@$$var}rest"))
+       t.CheckEquals(p.Rest(), "")
        t.CheckOutputEmpty()
 }
 
@@ -524,10 +519,10 @@ func (s *Suite) Test_MkParser_VarUse__am
        t.SetUpCommandLine("--explain")
 
        line := t.NewLine("module.mk", 123, "\t$Varname $X")
-       p := NewMkParser(line, line.Text[1:], true)
+       p := NewMkParser(line, line.Text[1:])
 
        tokens := p.MkTokens()
-       c.Check(tokens, deepEquals, []*MkToken{
+       t.CheckDeepEquals(tokens, []*MkToken{
                {"$V", NewMkVarUse("V")},
                {"arname ", nil},
                {"$X", NewMkVarUse("X")}})
@@ -552,61 +547,68 @@ func (s *Suite) Test_MkParser_MkCond(c *
        t := s.Init(c)
 
        testRest := func(input string, expectedTree *MkCond, expectedRest string) {
-               p := NewMkParser(nil, input, false)
+               // As of July 2019 p.MkCond does not emit warnings;
+               // this is left to MkLineChecker) checkDirectiveCond.
+               line := t.NewLine("filename.mk", 1, ".if "+input)
+               p := NewMkParser(line, input)
                actualTree := p.MkCond()
-               c.Check(actualTree, deepEquals, expectedTree)
-               c.Check(p.Rest(), equals, expectedRest)
+               t.CheckDeepEquals(actualTree, expectedTree)
+               t.CheckEquals(p.Rest(), expectedRest)
        }
        test := func(input string, expectedTree *MkCond) {
                testRest(input, expectedTree, "")
        }
-       varuse := NewMkVarUse
+       varUse := func(name string, modifiers ...string) MkCondTerm {
+               return MkCondTerm{Var: NewMkVarUse(name, modifiers...)}
+       }
+       str := func(s string) MkCondTerm { return MkCondTerm{Str: s} }
+       num := func(s string) MkCondTerm { return MkCondTerm{Num: s} }
 
-       t.Use(testRest, test, varuse)
+       t.Use(testRest, test, varUse)
 
        test("${OPSYS:MNetBSD}",
-               &MkCond{Var: varuse("OPSYS", "MNetBSD")})
+               &MkCond{Term: &MkCondTerm{Var: NewMkVarUse("OPSYS", "MNetBSD")}})
 
        test("defined(VARNAME)",
                &MkCond{Defined: "VARNAME"})
 
        test("empty(VARNAME)",
-               &MkCond{Empty: varuse("VARNAME")})
+               &MkCond{Empty: NewMkVarUse("VARNAME")})
 
        test("!empty(VARNAME)",
-               &MkCond{Not: &MkCond{Empty: varuse("VARNAME")}})
+               &MkCond{Not: &MkCond{Empty: NewMkVarUse("VARNAME")}})
 
        test("!empty(VARNAME:M[yY][eE][sS])",
-               &MkCond{Not: &MkCond{Empty: varuse("VARNAME", "M[yY][eE][sS]")}})
+               &MkCond{Not: &MkCond{Empty: NewMkVarUse("VARNAME", "M[yY][eE][sS]")}})
 
        // Colons are unescaped at this point because they cannot be mistaken for separators anymore.
        test("!empty(USE_TOOLS:Mautoconf\\:run)",
-               &MkCond{Not: &MkCond{Empty: varuse("USE_TOOLS", "Mautoconf:run")}})
+               &MkCond{Not: &MkCond{Empty: NewMkVarUse("USE_TOOLS", "Mautoconf:run")}})
 
        test("${VARNAME} != \"Value\"",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME"), "!=", "Value"}})
+               &MkCond{Compare: &MkCondCompare{varUse("VARNAME"), "!=", str("Value")}})
 
        test("${VARNAME:Mi386} != \"Value\"",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME", "Mi386"), "!=", "Value"}})
+               &MkCond{Compare: &MkCondCompare{varUse("VARNAME", "Mi386"), "!=", str("Value")}})
 
        test("${VARNAME} != Value",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME"), "!=", "Value"}})
+               &MkCond{Compare: &MkCondCompare{varUse("VARNAME"), "!=", str("Value")}})
 
        test("\"${VARNAME}\" != Value",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VARNAME"), "!=", "Value"}})
+               &MkCond{Compare: &MkCondCompare{varUse("VARNAME"), "!=", str("Value")}})
 
        test("${pkg} == \"${name}\"",
-               &MkCond{CompareVarVar: &MkCondCompareVarVar{varuse("pkg"), "==", varuse("name")}})
+               &MkCond{Compare: &MkCondCompare{varUse("pkg"), "==", varUse("name")}})
 
        test("\"${pkg}\" == \"${name}\"",
-               &MkCond{CompareVarVar: &MkCondCompareVarVar{varuse("pkg"), "==", varuse("name")}})
+               &MkCond{Compare: &MkCondCompare{varUse("pkg"), "==", varUse("name")}})
 
        // The right-hand side is not analyzed further to keep the data types simple.
        test("${ABC} == \"${A}B${C}\"",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("ABC"), "==", "${A}B${C}"}})
+               &MkCond{Compare: &MkCondCompare{varUse("ABC"), "==", str("${A}B${C}")}})
 
        test("${ABC} == \"${A}\\\"${B}\\\\${C}$${shellvar}${D}\"",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("ABC"), "==", "${A}\"${B}\\${C}$${shellvar}${D}"}})
+               &MkCond{Compare: &MkCondCompare{varUse("ABC"), "==", str("${A}\"${B}\\${C}$${shellvar}${D}")}})
 
        test("exists(/etc/hosts)",
                &MkCond{Call: &MkCondCall{"exists", "/etc/hosts"}})
@@ -616,13 +618,13 @@ func (s *Suite) Test_MkParser_MkCond(c *
 
        test("${OPSYS} == \"NetBSD\" || ${OPSYS} == \"OpenBSD\"",
                &MkCond{Or: []*MkCond{
-                       {CompareVarStr: &MkCondCompareVarStr{varuse("OPSYS"), "==", "NetBSD"}},
-                       {CompareVarStr: &MkCondCompareVarStr{varuse("OPSYS"), "==", "OpenBSD"}}}})
+                       {Compare: &MkCondCompare{varUse("OPSYS"), "==", str("NetBSD")}},
+                       {Compare: &MkCondCompare{varUse("OPSYS"), "==", str("OpenBSD")}}}})
 
        test("${OPSYS} == \"NetBSD\" && ${MACHINE_ARCH} == \"i386\"",
                &MkCond{And: []*MkCond{
-                       {CompareVarStr: &MkCondCompareVarStr{varuse("OPSYS"), "==", "NetBSD"}},
-                       {CompareVarStr: &MkCondCompareVarStr{varuse("MACHINE_ARCH"), "==", "i386"}}}})
+                       {Compare: &MkCondCompare{varUse("OPSYS"), "==", str("NetBSD")}},
+                       {Compare: &MkCondCompare{varUse("MACHINE_ARCH"), "==", str("i386")}}}})
 
        test("defined(A) && defined(B) || defined(C) && defined(D)",
                &MkCond{Or: []*MkCond{
@@ -635,52 +637,52 @@ func (s *Suite) Test_MkParser_MkCond(c *
 
        test("${MACHINE_ARCH:Mi386} || ${MACHINE_OPSYS:MNetBSD}",
                &MkCond{Or: []*MkCond{
-                       {Var: varuse("MACHINE_ARCH", "Mi386")},
-                       {Var: varuse("MACHINE_OPSYS", "MNetBSD")}}})
+                       {Term: &MkCondTerm{Var: NewMkVarUse("MACHINE_ARCH", "Mi386")}},
+                       {Term: &MkCondTerm{Var: NewMkVarUse("MACHINE_OPSYS", "MNetBSD")}}}})
 
        test("${VAR} == \"${VAR}suffix\"",
-               &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VAR"), "==", "${VAR}suffix"}})
+               &MkCond{Compare: &MkCondCompare{varUse("VAR"), "==", str("${VAR}suffix")}})
 
        // Exotic cases
 
        // ".if 0" can be used to skip over a block of code.
        test("0",
-               &MkCond{Num: "0"})
+               &MkCond{Term: &MkCondTerm{Num: "0"}})
 
        test("0xCAFEBABE",
-               &MkCond{Num: "0xCAFEBABE"})
+               &MkCond{Term: &MkCondTerm{Num: "0xCAFEBABE"}})
 
        test("${VAR} == 0xCAFEBABE",
                &MkCond{
-                       CompareVarNum: &MkCondCompareVarNum{
-                               Var: varuse("VAR"),
-                               Op:  "==",
-                               Num: "0xCAFEBABE"}})
+                       Compare: &MkCondCompare{
+                               varUse("VAR"),
+                               "==",
+                               num("0xCAFEBABE")}})
 
        test("! ( defined(A)  && empty(VARNAME) )",
                &MkCond{Not: &MkCond{
                        And: []*MkCond{
                                {Defined: "A"},
-                               {Empty: varuse("VARNAME")}}}})
+                               {Empty: NewMkVarUse("VARNAME")}}}})
 
        test("${REQD_MAJOR} > ${MAJOR}",
-               &MkCond{CompareVarVar: &MkCondCompareVarVar{varuse("REQD_MAJOR"), ">", varuse("MAJOR")}})
+               &MkCond{Compare: &MkCondCompare{varUse("REQD_MAJOR"), ">", varUse("MAJOR")}})
 
        test("${OS_VERSION} >= 6.5",
-               &MkCond{CompareVarNum: &MkCondCompareVarNum{varuse("OS_VERSION"), ">=", "6.5"}})
+               &MkCond{Compare: &MkCondCompare{varUse("OS_VERSION"), ">=", num("6.5")}})
 
        test("${OS_VERSION} == 5.3",
-               &MkCond{CompareVarNum: &MkCondCompareVarNum{varuse("OS_VERSION"), "==", "5.3"}})
+               &MkCond{Compare: &MkCondCompare{varUse("OS_VERSION"), "==", num("5.3")}})
 
        test("!empty(${OS_VARIANT:MIllumos})", // Probably not intended
-               &MkCond{Not: &MkCond{Empty: varuse("${OS_VARIANT:MIllumos}")}})
+               &MkCond{Not: &MkCond{Empty: NewMkVarUse("${OS_VARIANT:MIllumos}")}})
 
        // There may be whitespace before the parenthesis; see devel/bmake/files/cond.c:^compare_function.
        test("defined (VARNAME)",
                &MkCond{Defined: "VARNAME"})
 
        test("${\"${PKG_OPTIONS:Moption}\":?--enable-option:--disable-option}",
-               &MkCond{Var: varuse("\"${PKG_OPTIONS:Moption}\"", "?--enable-option:--disable-option")})
+               &MkCond{Term: &MkCondTerm{Var: NewMkVarUse("\"${PKG_OPTIONS:Moption}\"", "?--enable-option:--disable-option")}})
 
        // Contrary to most other programming languages, the == operator binds
        // more tightly that the ! operator.
@@ -688,7 +690,14 @@ func (s *Suite) Test_MkParser_MkCond(c *
        // TODO: Since this operator precedence is surprising there should be a warning,
        //  suggesting to replace "!${VAR} == value" with "${VAR} != value".
        test("!${VAR} == value",
-               &MkCond{Not: &MkCond{CompareVarStr: &MkCondCompareVarStr{varuse("VAR"), "==", "value"}}})
+               &MkCond{Not: &MkCond{Compare: &MkCondCompare{varUse("VAR"), "==", str("value")}}})
+
+       // The left-hand side of the comparison can be a quoted string.
+       test("\"${VAR}suffix\" == value",
+               &MkCond{Compare: &MkCondCompare{MkCondTerm{Str: "${VAR}suffix"}, "==", MkCondTerm{Str: "value"}}})
+
+       test("\"${VAR}str\"",
+               &MkCond{Term: &MkCondTerm{Str: "${VAR}str"}})
 
        // Errors
 
@@ -717,11 +726,11 @@ func (s *Suite) Test_MkParser_MkCond(c *
                "exists(/unfinished")
 
        testRest("!empty(PKG_OPTIONS:Msndfile) || defined(PKG_OPTIONS:Msamplerate)",
-               &MkCond{Not: &MkCond{Empty: varuse("PKG_OPTIONS", "Msndfile")}},
+               &MkCond{Not: &MkCond{Empty: NewMkVarUse("PKG_OPTIONS", "Msndfile")}},
                "|| defined(PKG_OPTIONS:Msamplerate)")
 
        testRest("${LEFT} &&",
-               &MkCond{Var: varuse("LEFT")},
+               &MkCond{Term: &MkCondTerm{Var: NewMkVarUse("LEFT")}},
                "&&")
 
        testRest("\"unfinished string literal",
@@ -744,7 +753,7 @@ func (s *Suite) Test_MkParser_MkCond(c *
        // A logical not must always be followed by an expression.
        testRest("!<",
                nil,
-               "!<")
+               "<")
 
        // Empty parentheses are a syntax error.
        testRest("()",
@@ -756,11 +765,25 @@ func (s *Suite) Test_MkParser_MkCond(c *
                nil,
                "(${VAR}")
 
-       // The left-hand side of the comparison can only be a variable.
-       // FIXME: bmake accepts this, and so should pkglint.
-       testRest("\"${VAR}suffix\" == value",
+       // Too many closing parentheses are a syntax error.
+       testRest("(${VAR}))",
+               &MkCond{Term: &MkCondTerm{Var: NewMkVarUse("VAR")}},
+               ")")
+
+       // The left-hand side of the comparison cannot be an unquoted string literal.
+       // These would be rejected by bmake as well.
+       testRest("value == \"${VAR}suffix\"",
                nil,
-               "\"${VAR}suffix\" == value")
+               "value == \"${VAR}suffix\"")
+
+       // Function calls need round parentheses instead of curly braces.
+       // As of July 2019, bmake silently accepts this wrong expression
+       // and interprets it as !defined(empty{USE_CROSS_COMPILE:M[yY][eE][sS]}),
+       // which is always true, except if a variable of this strange name
+       // were actually defined.
+       testRest("!empty{USE_CROSS_COMPILE:M[yY][eE][sS]}",
+               nil,
+               "empty{USE_CROSS_COMPILE:M[yY][eE][sS]}")
 }
 
 func (s *Suite) Test_MkParser_Varname(c *check.C) {
@@ -768,22 +791,22 @@ func (s *Suite) Test_MkParser_Varname(c 
 
        test := func(text string) {
                line := t.NewLine("filename.mk", 1, text)
-               p := NewMkParser(line, text, true)
+               p := NewMkParser(line, text)
 
                varname := p.Varname()
 
-               t.Check(varname, equals, text)
-               t.Check(p.Rest(), equals, "")
+               t.CheckEquals(varname, text)
+               t.CheckEquals(p.Rest(), "")
        }
 
        testRest := func(text string, expectedVarname string, expectedRest string) {
                line := t.NewLine("filename.mk", 1, text)
-               p := NewMkParser(line, text, true)
+               p := NewMkParser(line, text)
 
                varname := p.Varname()
 
-               t.Check(varname, equals, expectedVarname)
-               t.Check(p.Rest(), equals, expectedRest)
+               t.CheckEquals(varname, expectedVarname)
+               t.CheckEquals(p.Rest(), expectedRest)
        }
 
        test("VARNAME")
@@ -829,12 +852,12 @@ func (s *Suite) Test_MkParser_VarUseModi
        varUse := NewMkVarUse
        test := func(text string, varUse *MkVarUse, diagnostics ...string) {
                line := t.NewLine("Makefile", 20, "\t"+text)
-               p := NewMkParser(line, text, true)
+               p := NewMkParser(line, text)
 
                actual := p.VarUse()
 
-               t.Check(actual, deepEquals, varUse)
-               t.Check(p.Rest(), equals, "")
+               t.CheckDeepEquals(actual, varUse)
+               t.CheckEquals(p.Rest(), "")
                t.CheckOutput(diagnostics)
        }
 
@@ -867,12 +890,12 @@ func (s *Suite) Test_MkParser_varUseModi
        varUse := NewMkVarUse
        test := func(text string, varUse *MkVarUse, rest string, diagnostics ...string) {
                line := t.NewLine("Makefile", 20, "\t"+text)
-               p := NewMkParser(line, text, true)
+               p := NewMkParser(line, text)
 
                actual := p.VarUse()
 
-               t.Check(actual, deepEquals, varUse)
-               t.Check(p.Rest(), equals, rest)
+               t.CheckDeepEquals(actual, varUse)
+               t.CheckEquals(p.Rest(), rest)
                t.CheckOutput(diagnostics)
        }
 
@@ -925,12 +948,12 @@ func (s *Suite) Test_MkParser_varUseModi
        varUse := NewMkVarUse
        test := func(text string, varUse *MkVarUse, rest string, diagnostics ...string) {
                line := t.NewLine("Makefile", 20, "\t"+text)
-               p := NewMkParser(line, text, true)
+               p := NewMkParser(line, text)
 
                actual := p.VarUse()
 
-               t.Check(actual, deepEquals, varUse)
-               t.Check(p.Rest(), equals, rest)
+               t.CheckDeepEquals(actual, varUse)
+               t.CheckEquals(p.Rest(), rest)
                t.CheckOutput(diagnostics)
        }
 
@@ -952,11 +975,12 @@ func (s *Suite) Test_MkParser_varUseModi
 }
 
 func (s *Suite) Test_MkParser_isPkgbasePart(c *check.C) {
+       t := s.Init(c)
 
        test := func(str string, expected bool) {
                actual := (*MkParser)(nil).isPkgbasePart(str)
 
-               c.Check(actual, equals, expected)
+               t.CheckEquals(actual, expected)
        }
 
        test("X11", true)
@@ -975,14 +999,15 @@ func (s *Suite) Test_MkParser_isPkgbaseP
 }
 
 func (s *Suite) Test_MkParser_PkgbasePattern(c *check.C) {
+       t := s.Init(c)
 
        test := func(pattern, expected, rest string) {
-               parser := NewMkParser(nil, pattern, false)
+               parser := NewMkParser(nil, pattern)
 
                actual := parser.PkgbasePattern()
 
-               c.Check(actual, equals, expected)
-               c.Check(parser.Rest(), equals, rest)
+               t.CheckEquals(actual, expected)
+               t.CheckEquals(parser.Rest(), rest)
        }
 
        test("fltk", "fltk", "")
@@ -1020,21 +1045,22 @@ func (s *Suite) Test_MkParser_PkgbasePat
 }
 
 func (s *Suite) Test_MkParser_Dependency(c *check.C) {
+       t := s.Init(c)
 
        testRest := func(pattern string, expected DependencyPattern, rest string) {
-               parser := NewMkParser(nil, pattern, false)
+               parser := NewMkParser(nil, pattern)
                dp := parser.Dependency()
                if c.Check(dp, check.NotNil) {
-                       c.Check(*dp, equals, expected)
-                       c.Check(parser.Rest(), equals, rest)
+                       t.CheckEquals(*dp, expected)
+                       t.CheckEquals(parser.Rest(), rest)
                }
        }
 
        testNil := func(pattern string) {
-               parser := NewMkParser(nil, pattern, false)
+               parser := NewMkParser(nil, pattern)
                dp := parser.Dependency()
                if c.Check(dp, check.IsNil) {
-                       c.Check(parser.Rest(), equals, pattern)
+                       t.CheckEquals(parser.Rest(), pattern)
                }
        }
 
@@ -1160,14 +1186,16 @@ func (s *Suite) Test_MkCondWalker_Walk(c
                Empty: func(varuse *MkVarUse) {
                        addEvent("empty", varuseStr(varuse))
                },
-               CompareVarNum: func(varuse *MkVarUse, op string, num string) {
-                       addEvent("compareVarNum", varuseStr(varuse), num)
-               },
-               CompareVarStr: func(varuse *MkVarUse, op string, str string) {
-                       addEvent("compareVarStr", varuseStr(varuse), str)
-               },
-               CompareVarVar: func(left *MkVarUse, op string, right *MkVarUse) {
-                       addEvent("compareVarVar", varuseStr(left), varuseStr(right))
+               Compare: func(left *MkCondTerm, op string, right *MkCondTerm) {
+                       assert(left.Var != nil)
+                       switch {
+                       case right.Var != nil:
+                               addEvent("compareVarVar", varuseStr(left.Var), varuseStr(right.Var))
+                       case right.Num != "":
+                               addEvent("compareVarNum", varuseStr(left.Var), right.Num)
+                       default:
+                               addEvent("compareVarStr", varuseStr(left.Var), right.Str)
+                       }
                },
                Call: func(name string, arg string) {
                        addEvent("call", name, arg)
@@ -1179,7 +1207,7 @@ func (s *Suite) Test_MkCondWalker_Walk(c
                        addEvent("varUse", varuseStr(varuse))
                }})
 
-       c.Check(events, deepEquals, []string{
+       t.CheckDeepEquals(events, []string{
                " compareVarVar  VAR:Mmatch, OTHER",
                "        varUse  VAR:Mmatch",
                "        varUse  OTHER",
Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.30 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.31
--- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.30        Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Sun Jul 14 21:25:47 2019
@@ -372,7 +372,7 @@ func (src *Pkgsrc) loadUntypedVars() {
 
        handleMkFile := func(path string) {
                mklines := LoadMk(path, MustSucceed)
-               mklines.collectDefinedVariables()
+               mklines.collectVariables()
                mklines.collectUsedVariables()
                for varname, mkline := range mklines.vars.firstDef {
                        define(varnameCanon(varname), mkline)
@@ -484,7 +484,7 @@ func (*Pkgsrc) parseDocChange(line *Line
                        Location: line.Location,
                        Action:   action,
                        Pkgpath:  intern(pkgpath),
-                       target:   intern(ifelseStr(n == 6, f[3], "")),
+                       target:   intern(condStr(n == 6, f[3], "")),
                        Author:   intern(author),
                        Date:     intern(date),
                }
@@ -627,13 +627,7 @@ func (src *Pkgsrc) checkRemovedAfterLast
                }
        }
 
-       sort.Slice(wrong, func(i, j int) bool {
-               ei, ej := wrong[i], wrong[j]
-               if ei.Date != ej.Date {
-                       return ei.Date < ej.Date
-               }
-               return ei.Location.firstLine < ej.Location.firstLine
-       })
+       sort.Slice(wrong, func(i, j int) bool { return wrong[i].Above(wrong[j]) })
 
        for _, change := range wrong {
                // It's a bit cheated to construct a Line from only a Location,
@@ -648,7 +642,7 @@ func (src *Pkgsrc) loadUserDefinedVars()
        mklines := src.LoadMk("mk/defaults/mk.conf", MustSucceed|NotEmpty)
 
        for _, mkline := range mklines.mklines {
-               if mkline.IsVarassign() || mkline.IsCommentedVarassign() {
+               if mkline.IsVarassignMaybeCommented() {
                        src.UserDefinedVars.Define(mkline.Varname(), mkline)
                }
        }
@@ -1070,6 +1064,13 @@ func (ch *Change) Successor() string {
        return ch.target
 }
 
+func (ch *Change) Above(other *Change) bool {
+       if ch.Date != other.Date {
+               return ch.Date < other.Date
+       }
+       return ch.Location.firstLine < other.Location.firstLine
+}
+
 type ChangeAction uint8
 
 const (

Index: pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go:1.9 pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go:1.10
--- pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go:1.9        Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/mkshwalker_test.go    Sun Jul 14 21:25:47 2019
@@ -3,6 +3,7 @@ package pkglint
 import "gopkg.in/check.v1"
 
 func (s *Suite) Test_MkShWalker_Walk(c *check.C) {
+       t := s.Init(c)
 
        pathFor := map[string]bool{}
 
@@ -67,13 +68,13 @@ func (s *Suite) Test_MkShWalker_Walk(c *
                //    Case with 1 item(s)
                //      ...
 
-               c.Check(commands, deepEquals, output)
+               t.CheckDeepEquals(commands, output)
 
                // After parsing, there is not a single level of indentation,
                // therefore even Parent(0) returns nil.
                //
                // This ensures that the w.push/w.pop calls are balanced.
-               c.Check(walker.Parent(0), equals, nil)
+               t.CheckEquals(walker.Parent(0), nil)
        }
 
        outputPathFor("SimpleCommand")
@@ -240,6 +241,7 @@ func (s *Suite) Test_MkShWalker_Walk(c *
 }
 
 func (s *Suite) Test_MkShWalker_Walk__empty_callback(c *check.C) {
+       t := s.Init(c)
 
        test := func(program string) {
                list, err := parseShellProgram(dummyLine, program)
@@ -248,7 +250,7 @@ func (s *Suite) Test_MkShWalker_Walk__em
                walker := NewMkShWalker()
                walker.Walk(list)
 
-               c.Check(walker.Parent(0), equals, nil)
+               t.CheckEquals(walker.Parent(0), nil)
        }
 
        test("" +

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.58 pkgsrc/pkgtools/pkglint/files/package.go:1.59
--- pkgsrc/pkgtools/pkglint/files/package.go:1.58       Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sun Jul 14 21:25:47 2019
@@ -666,8 +666,8 @@ func (pkg *Package) checkfilePackageMake
 
        pkg.checkUpdate()
 
-       allLines.collectDefinedVariables() // To get the tool definitions
-       mklines.Tools = allLines.Tools     // TODO: also copy the other collected data
+       allLines.collectVariables()    // To get the tool definitions
+       mklines.Tools = allLines.Tools // TODO: also copy the other collected data
        mklines.Check()
 
        pkg.CheckVarorder(mklines)
@@ -847,7 +847,7 @@ func (pkg *Package) determineEffectivePk
 }
 
 func (pkg *Package) pkgnameFromDistname(pkgname, distname string) (string, bool) {
-       tokens := NewMkParser(nil, pkgname, false).MkTokens()
+       tokens := NewMkParser(nil, pkgname).MkTokens()
 
        // TODO: Make this resolving of variable references available to all other variables as well.
 
@@ -1007,7 +1007,7 @@ func (pkg *Package) CheckVarorder(mkline
                firstIrrelevant := -1
                for i, mkline := range mklines.mklines {
                        switch {
-                       case mkline.IsVarassign(), mkline.IsCommentedVarassign():
+                       case mkline.IsVarassignMaybeCommented():
                                varcanon := mkline.Varcanon()
                                if relevantVars[varcanon] {
                                        if firstRelevant == -1 {
@@ -1105,7 +1105,7 @@ func (pkg *Package) CheckVarorder(mkline
 
                        found := false
                        for _, mkline := range relevantLines {
-                               if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) &&
+                               if mkline.IsVarassignMaybeCommented() &&
                                        mkline.Varcanon() == variable.Name {
 
                                        canonical = append(canonical, mkline.Varname())
@@ -1202,15 +1202,14 @@ func (pkg *Package) checkOwnerMaintainer
                line.Warnf("Don't commit changes to this file without asking the OWNER, %s.", owner)
                line.Explain(
                        seeGuide("Package components, Makefile", "components.Makefile"))
+               return
        }
 
-       if maintainer != "" {
-               line := NewLineWhole(filename)
-               line.Notef("Please only commit changes that %s would approve.", maintainer)
-               line.Explain(
-                       "See the pkgsrc guide, section \"Package components\",",
-                       "keyword \"maintainer\", for more information.")
-       }
+       line := NewLineWhole(filename)
+       line.Notef("Please only commit changes that %s would approve.", maintainer)
+       line.Explain(
+               "See the pkgsrc guide, section \"Package components\",",
+               "keyword \"maintainer\", for more information.")
 }
 
 func (pkg *Package) checkFreeze(filename string) {
@@ -1263,15 +1262,20 @@ func (pkg *Package) checkIncludeConditio
 
 func (pkg *Package) loadPlistDirs(plistFilename string) {
        lines := Load(plistFilename, MustSucceed)
-       for _, line := range lines.Lines {
-               text := line.Text
-               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.Plist.Dirs[dir] = true
-                       }
-               }
+       ck := PlistChecker{
+               pkg,
+               make(map[string]*PlistLine),
+               make(map[string]*PlistLine),
+               "",
+               Once{},
+               false}
+       ck.Load(lines)
+
+       for filename, pline := range ck.allFiles {
+               pkg.Plist.Files[filename] = pline
+       }
+       for dirname, pline := range ck.allDirs {
+               pkg.Plist.Dirs[dirname] = pline
        }
 }
 
@@ -1335,13 +1339,21 @@ func (pkg *Package) checkUseLanguagesCom
        })
 }
 
+// PlistContent lists the directories and files that appear in the
+// package's PLIST files. It serves two purposes:
+//
+// 1. Decide whether AUTO_MKDIRS can be used instead of listing
+// the INSTALLATION_DIRS redundantly.
+//
+// 2. Ensure that the entries mentioned in the ALTERNATIVES file
+// also appear in the PLIST files.
 type PlistContent struct {
-       Dirs  map[string]bool
-       Files map[string]bool
+       Dirs  map[string]*PlistLine
+       Files map[string]*PlistLine
 }
 
 func NewPlistContent() PlistContent {
        return PlistContent{
-               make(map[string]bool),
-               make(map[string]bool)}
+               make(map[string]*PlistLine),
+               make(map[string]*PlistLine)}
 }
Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.58 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.59
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.58  Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sun Jul 14 21:25:47 2019
@@ -297,7 +297,7 @@ func (cv *VartypeCheck) ConfFiles() {
 func (cv *VartypeCheck) Dependency() {
        value := cv.Value
 
-       parser := NewMkParser(nil, value, false)
+       parser := NewMkParser(nil, value)
        deppat := parser.Dependency()
        rest := parser.Rest()
 
@@ -544,7 +544,7 @@ func (cv *VartypeCheck) FetchURL() {
 //
 // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_169
 func (cv *VartypeCheck) Filename() {
-       valid := regex.Pattern(ifelseStr(
+       valid := regex.Pattern(condStr(
                cv.Op == opUseMatch,
                `[%*+,\-.0-9?@A-Z\[\]_a-z~]`,
                `[%+,\-.0-9@A-Z_a-z~]`))
@@ -555,11 +555,11 @@ func (cv *VartypeCheck) Filename() {
        }
 
        cv.Warnf(
-               ifelseStr(cv.Op == opUseMatch,
+               condStr(cv.Op == opUseMatch,
                        "The filename pattern %q contains the invalid character%s %q.",
                        "The filename %q contains the invalid character%s %q."),
                cv.Value,
-               ifelseStr(len(invalid) > 1, "s", ""),
+               condStr(len(invalid) > 1, "s", ""),
                invalid)
 }
 
@@ -574,7 +574,7 @@ func (cv *VartypeCheck) FileMask() {
 
        cv.Warnf("The filename pattern %q contains the invalid character%s %q.",
                cv.Value,
-               ifelseStr(len(invalid) > 1, "s", ""),
+               condStr(len(invalid) > 1, "s", ""),
                invalid)
 }
 
@@ -858,7 +858,7 @@ func (cv *VartypeCheck) PathMask() {
 
        cv.Warnf("The pathname pattern %q contains the invalid character%s %q.",
                cv.Value,
-               ifelseStr(len(invalid) > 1, "s", ""),
+               condStr(len(invalid) > 1, "s", ""),
                invalid)
 }
 
@@ -868,7 +868,7 @@ func (cv *VartypeCheck) PathMask() {
 //
 // See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_266
 func (cv *VartypeCheck) Pathname() {
-       valid := regex.Pattern(ifelseStr(
+       valid := regex.Pattern(condStr(
                cv.Op == opUseMatch,
                `[%*+,\-./0-9?@A-Z\[\]_a-z~]`,
                `[%+,\-./0-9@A-Z_a-z~]`))
@@ -878,11 +878,11 @@ func (cv *VartypeCheck) Pathname() {
        }
 
        cv.Warnf(
-               ifelseStr(cv.Op == opUseMatch,
+               condStr(cv.Op == opUseMatch,
                        "The pathname pattern %q contains the invalid character%s %q.",
                        "The pathname %q contains the invalid character%s %q."),
                cv.Value,
-               ifelseStr(len(invalid) > 1, "s", ""),
+               condStr(len(invalid) > 1, "s", ""),
                invalid)
 }
 

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.49 pkgsrc/pkgtools/pkglint/files/package_test.go:1.50
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.49  Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sun Jul 14 21:25:47 2019
@@ -3,6 +3,7 @@ package pkglint
 import (
        "gopkg.in/check.v1"
        "os"
+       "sort"
        "strings"
 )
 
@@ -78,7 +79,7 @@ func (s *Suite) Test_Package_pkgnameFrom
                pkg := NewPackage(t.File("category/package"))
                pkg.loadPackageMakefile()
                pkg.determineEffectivePkgVars()
-               t.Check(pkg.EffectivePkgname, equals, expectedPkgname)
+               t.CheckEquals(pkg.EffectivePkgname, expectedPkgname)
                t.CheckOutput(diagnostics)
        }
 
@@ -563,12 +564,12 @@ func (s *Suite) Test_Package_nbPart(c *c
        pkg := NewPackage(t.File("category/pkgbase"))
        pkg.vars.Define("PKGREVISION", t.NewMkLine("Makefile", 1, "PKGREVISION=14"))
 
-       c.Check(pkg.nbPart(), equals, "nb14")
+       t.CheckEquals(pkg.nbPart(), "nb14")
 
        pkg.vars = NewScope()
        pkg.vars.Define("PKGREVISION", t.NewMkLine("Makefile", 1, "PKGREVISION=asdf"))
 
-       c.Check(pkg.nbPart(), equals, "")
+       t.CheckEquals(pkg.nbPart(), "")
 }
 
 // PKGNAME is stronger than DISTNAME.
@@ -586,9 +587,9 @@ func (s *Suite) Test_Package_determineEf
 
        pkg.determineEffectivePkgVars()
 
-       c.Check(pkg.EffectivePkgbase, equals, "pkgname")
-       c.Check(pkg.EffectivePkgname, equals, "pkgname-1.0nb13")
-       c.Check(pkg.EffectivePkgversion, equals, "1.0")
+       t.CheckEquals(pkg.EffectivePkgbase, "pkgname")
+       t.CheckEquals(pkg.EffectivePkgname, "pkgname-1.0nb13")
+       t.CheckEquals(pkg.EffectivePkgversion, "1.0")
 }
 
 func (s *Suite) Test_Package_determineEffectivePkgVars__same(c *check.C) {
@@ -674,7 +675,7 @@ func (s *Suite) Test_Package_determineEf
 
        pkg.check(files, mklines, allLines)
 
-       t.Check(pkg.EffectivePkgname, equals, "p5-gtk2-1.0")
+       t.CheckEquals(pkg.EffectivePkgname, "p5-gtk2-1.0")
 }
 
 // In some cases the PKGNAME is derived from DISTNAME, and it seems as
@@ -693,7 +694,7 @@ func (s *Suite) Test_Package_determineEf
 
        pkg.check(files, mklines, allLines)
 
-       t.Check(pkg.EffectivePkgname, equals, "distname-1.0")
+       t.CheckEquals(pkg.EffectivePkgname, "distname-1.0")
        t.CheckOutputEmpty()
 }
 
@@ -736,7 +737,7 @@ func (s *Suite) Test_Package_checkPossib
        pkg.determineEffectivePkgVars()
        pkg.checkPossibleDowngrade()
 
-       t.Check(G.Pkgsrc.LastChange["category/pkgbase"].Action, equals, Moved)
+       t.CheckEquals(G.Pkgsrc.LastChange["category/pkgbase"].Action, Moved)
        // No warning because the latest action is not Updated.
        t.CheckOutputEmpty()
 }
@@ -1101,7 +1102,7 @@ func (s *Suite) Test_Package_check__patc
        t.CheckOutputLines(
                "WARN: ~/category/package/patches/Makefile: Patch files should be "+
                        "named \"patch-\", followed by letters, '-', '_', '.', and digits only.",
-               "0 errors and 1 warning found.")
+               "1 warning found.")
 }
 
 func (s *Suite) Test_Package_checkDirent__errors(c *check.C) {
@@ -1380,7 +1381,7 @@ func (s *Suite) Test_Package_checkUpdate
                "NOTE: category/pkg1/Makefile:4: The update request to 1.0 from doc/TODO has been done.",
                "WARN: category/pkg2/Makefile:4: This package should be updated to 2.0 ([nice new features]).",
                "NOTE: category/pkg3/Makefile:4: This package is newer than the update request to 3.0 ([security update]).",
-               "0 errors and 4 warnings found.",
+               "4 warnings and 2 notes found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -1739,9 +1740,34 @@ func (s *Suite) Test_Package_loadPlistDi
        pkg := NewPackage(t.File("category/package"))
        pkg.load()
 
-       // FIXME: dir/subdir should also be included in pkg.Plist.Dirs.
-       t.Check(pkg.Plist.Dirs, deepEquals, map[string]bool{
-               "bin": true})
+       var dirs []string
+       for dir := range pkg.Plist.Dirs {
+               dirs = append(dirs, dir)
+       }
+       sort.Strings(dirs)
+
+       t.CheckDeepEquals(dirs, []string{"bin", "dir", "dir/subdir"})
+}
+
+// Just ensure that pkglint doesn't crash.
+func (s *Suite) Test_Package_loadPlistDirs__empty(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package")
+       t.CreateFileLines("category/package/PLIST.common",
+               nil...)
+       t.FinishSetUp()
+
+       pkg := NewPackage(t.File("category/package"))
+       pkg.load()
+
+       var dirs []string
+       for dir := range pkg.Plist.Dirs {
+               dirs = append(dirs, dir)
+       }
+       sort.Strings(dirs)
+
+       t.CheckDeepEquals(dirs, []string{"bin"})
 }
 
 func (s *Suite) Test_Package_checkUseLanguagesCompilerMk__too_late(c *check.C) {
@@ -1993,7 +2019,7 @@ func (s *Suite) Test_Package_parse__skip
                }
        }
 
-       c.Check(relevant, deepEquals, []string{
+       t.CheckDeepEquals(relevant, []string{
                "TRACE: 1 2 3 4   ~/category/package/Makefile:20: " +
                        "Skipping unresolvable include file \"${MYSQL_PKGSRCDIR:S/-client$/-server/}/buildlink3.mk\"."})
 }
@@ -2166,7 +2192,7 @@ func (s *Suite) Test_Package_collectSeen
        pkg := NewPackage(t.File("category/package"))
        pkg.load()
 
-       t.Check(pkg.seenInclude, equals, true)
+       t.CheckEquals(pkg.seenInclude, true)
 }
 
 func (s *Suite) Test_Package_diveInto(c *check.C) {
@@ -2174,7 +2200,7 @@ func (s *Suite) Test_Package_diveInto(c 
 
        test := func(including, included string, expected bool) {
                actual := (*Package)(nil).diveInto(including, included)
-               t.Check(actual, equals, expected)
+               t.CheckEquals(actual, expected)
        }
 
        // The variables that appear in these files are largely modeled by
@@ -2383,11 +2409,8 @@ func (s *Suite) Test_Package_checkOwnerM
        G.Check(t.File("category/package"))
 
        t.CheckOutputLines(
-               "WARN: ~/category/package/Makefile: "+
-                       "Don't commit changes to this file without asking the OWNER, owner%example.org@localhost.",
-               // FIXME: OWNER is stronger than MAINTAINER, therefore this note should disappear.
-               "NOTE: ~/category/package/Makefile: "+
-                       "Please only commit changes that maintainer%example.org@localhost would approve.")
+               "WARN: ~/category/package/Makefile: " +
+                       "Don't commit changes to this file without asking the OWNER, owner%example.org@localhost.")
 }
 
 // Just for code coverage.
@@ -2457,6 +2480,23 @@ func (s *Suite) Test_Package_checkFreeze
                "")
 }
 
+func (s *Suite) Test_Package_checkFreeze__freeze_ended(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpCommandLine("-Wall", "--explain")
+       pkg := t.SetUpPackage("category/package")
+       t.CreateFileLines("category/package/CVS/Entries",
+               "/Makefile//modified//")
+       t.CreateFileLines("doc/CHANGES-2018",
+               "\tmk/bsd.pkg.mk: started freeze for 2018Q2 [freezer 2018-03-20]",
+               "\tmk/bsd.pkg.mk: freeze ended for 2018Q2 [freezer 2018-03-27]")
+       t.FinishSetUp()
+
+       G.Check(pkg)
+
+       t.CheckOutputEmpty()
+}
+
 // In practice the distinfo file can always be autofixed since it has
 // just been read successfully and the corresponding patch file could
 // also be autofixed right before.
Index: pkgsrc/pkgtools/pkglint/files/shell_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shell_test.go:1.49 pkgsrc/pkgtools/pkglint/files/shell_test.go:1.50
--- pkgsrc/pkgtools/pkglint/files/shell_test.go:1.49    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/shell_test.go Sun Jul 14 21:25:47 2019
@@ -10,60 +10,72 @@ func (s *Suite) Test_splitIntoShellToken
 
        words, rest := splitIntoShellTokens(dummyLine, "if true; then \\")
 
-       c.Check(words, check.DeepEquals, []string{"if", "true", ";", "then"})
-       c.Check(rest, equals, "\\")
+       t.CheckDeepEquals(words, []string{"if", "true", ";", "then"})
+       t.CheckEquals(rest, "\\")
 
        t.CheckOutputLines(
                "WARN: Internal pkglint error in ShTokenizer.ShAtom at \"\\\\\" (quoting=plain).")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__dollar_slash(c *check.C) {
+       t := s.Init(c)
+
        words, rest := splitIntoShellTokens(dummyLine, "pax -s /.*~$$//g")
 
-       c.Check(words, check.DeepEquals, []string{"pax", "-s", "/.*~$$//g"})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"pax", "-s", "/.*~$$//g"})
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__dollar_subshell(c *check.C) {
+       t := s.Init(c)
+
        words, rest := splitIntoShellTokens(dummyLine, "id=$$(${AWK} '{print}' < ${WRKSRC}/idfile) && echo \"$$id\"")
 
-       c.Check(words, deepEquals, []string{"id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)", "&&", "echo", "\"$$id\""})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"id=$$(${AWK} '{print}' < ${WRKSRC}/idfile)", "&&", "echo", "\"$$id\""})
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__semicolons(c *check.C) {
+       t := s.Init(c)
+
        words, rest := splitIntoShellTokens(dummyLine, "word1 word2;;;")
 
-       c.Check(words, deepEquals, []string{"word1", "word2", ";;", ";"})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"word1", "word2", ";;", ";"})
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__whitespace(c *check.C) {
+       t := s.Init(c)
+
        text := "\t${RUN} cd ${WRKSRC}&&(${ECHO} ${PERL5:Q};${ECHO})|${BASH} ./install"
        words, rest := splitIntoShellTokens(dummyLine, text)
 
-       c.Check(words, deepEquals, []string{
+       t.CheckDeepEquals(words, []string{
                "${RUN}",
                "cd", "${WRKSRC}",
                "&&", "(", "${ECHO}", "${PERL5:Q}", ";", "${ECHO}", ")",
                "|", "${BASH}", "./install"})
-       c.Check(rest, equals, "")
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__finished_dquot(c *check.C) {
+       t := s.Init(c)
+
        text := "\"\""
        words, rest := splitIntoShellTokens(dummyLine, text)
 
-       c.Check(words, deepEquals, []string{"\"\""})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"\"\""})
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__unfinished_dquot(c *check.C) {
+       t := s.Init(c)
+
        text := "\t\""
        words, rest := splitIntoShellTokens(dummyLine, text)
 
        c.Check(words, check.IsNil)
-       c.Check(rest, equals, "\"")
+       t.CheckEquals(rest, "\"")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__unescaped_dollar_in_dquot(c *check.C) {
@@ -72,41 +84,49 @@ func (s *Suite) Test_splitIntoShellToken
        text := "echo \"$$\""
        words, rest := splitIntoShellTokens(dummyLine, text)
 
-       c.Check(words, deepEquals, []string{"echo", "\"$$\""})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"echo", "\"$$\""})
+       t.CheckEquals(rest, "")
 
        t.CheckOutputEmpty()
 }
 
 func (s *Suite) Test_splitIntoShellTokens__varuse_with_embedded_space_and_other_vars(c *check.C) {
+       t := s.Init(c)
+
        varuseWord := "${GCONF_SCHEMAS:@.s.@${INSTALL_DATA} ${WRKSRC}/src/common/dbus/${.s.} ${DESTDIR}${GCONF_SCHEMAS_DIR}/@}"
        words, rest := splitIntoShellTokens(dummyLine, varuseWord)
 
-       c.Check(words, deepEquals, []string{varuseWord})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{varuseWord})
+       t.CheckEquals(rest, "")
 }
 
 // Two shell variables, next to each other,
 // are two separate atoms but count as a single token.
 func (s *Suite) Test_splitIntoShellTokens__two_shell_variables(c *check.C) {
+       t := s.Init(c)
+
        code := "echo $$i$$j"
        words, rest := splitIntoShellTokens(dummyLine, code)
 
-       c.Check(words, deepEquals, []string{"echo", "$$i$$j"})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"echo", "$$i$$j"})
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__varuse_with_embedded_space(c *check.C) {
+       t := s.Init(c)
+
        words, rest := splitIntoShellTokens(dummyLine, "${VAR:S/ /_/g}")
 
-       c.Check(words, deepEquals, []string{"${VAR:S/ /_/g}"})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(words, []string{"${VAR:S/ /_/g}"})
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_splitIntoShellTokens__redirect(c *check.C) {
+       t := s.Init(c)
+
        words, rest := splitIntoShellTokens(dummyLine, "echo 1>output 2>>append 3>|clobber 4>&5 6<input >>append")
 
-       c.Check(words, deepEquals, []string{
+       t.CheckDeepEquals(words, []string{
                "echo",
                "1>", "output",
                "2>>", "append",
@@ -114,11 +134,11 @@ func (s *Suite) Test_splitIntoShellToken
                "4>&", "5",
                "6<", "input",
                ">>", "append"})
-       c.Check(rest, equals, "")
+       t.CheckEquals(rest, "")
 
        words, rest = splitIntoShellTokens(dummyLine, "echo 1> output 2>> append 3>| clobber 4>& 5 6< input >> append")
 
-       c.Check(words, deepEquals, []string{
+       t.CheckDeepEquals(words, []string{
                "echo",
                "1>", "output",
                "2>>", "append",
@@ -126,7 +146,7 @@ func (s *Suite) Test_splitIntoShellToken
                "4>&", "5",
                "6<", "input",
                ">>", "append"})
-       c.Check(rest, equals, "")
+       t.CheckEquals(rest, "")
 }
 
 func (s *Suite) Test_ShellLineChecker_CheckShellCommandLine(c *check.C) {
@@ -232,7 +252,10 @@ func (s *Suite) Test_ShellLineChecker_Ch
                "WARN: filename.mk:1: Using a leading \"-\" to suppress errors is deprecated.")
 
        G.Pkg = NewPackage(t.File("category/pkgbase"))
-       G.Pkg.Plist.Dirs["share/pkgbase"] = true
+       G.Pkg.Plist.Dirs["share/pkgbase"] = &PlistLine{
+               t.NewLine("PLIST", 123, "share/pkgbase/file"),
+               nil,
+               "share/pkgbase/file"}
 
        // A directory that is found in the PLIST.
        // TODO: Add a test for using this command inside a conditional;
@@ -385,8 +408,8 @@ func (s *Suite) Test_ShellLineChecker_Ch
 
        tokens, rest := splitIntoShellTokens(dummyLine, text)
 
-       c.Check(tokens, deepEquals, []string{text})
-       c.Check(rest, equals, "")
+       t.CheckDeepEquals(tokens, []string{text})
+       t.CheckEquals(rest, "")
 
        mklines.ForEach(func(mkline *MkLine) { ck.CheckWord(text, false, RunTime) })
 
@@ -598,9 +621,10 @@ func (s *Suite) Test_ShellLineChecker_un
 }
 
 func (s *Suite) Test_ShellLineChecker_variableNeedsQuoting(c *check.C) {
+       t := s.Init(c)
 
        test := func(shVarname string, expected bool) {
-               c.Check((*ShellLineChecker).variableNeedsQuoting(nil, shVarname), equals, expected)
+               t.CheckEquals((*ShellLineChecker).variableNeedsQuoting(nil, shVarname), expected)
        }
 
        test("#", false) // A length is always an integer.
@@ -927,7 +951,7 @@ func (s *Suite) Test_ShellLineChecker_un
                        q = atoms[0].Quoting
                        atoms = atoms[1:]
                }
-               c.Check(tok.Rest(), equals, "")
+               t.CheckEquals(tok.Rest(), "")
 
                backtCommand := ck.unescapeBackticks(&atoms, q)
 
@@ -936,8 +960,8 @@ func (s *Suite) Test_ShellLineChecker_un
                        actualRest.WriteString(atom.MkText)
                }
 
-               c.Check(backtCommand, equals, expectedOutput)
-               c.Check(actualRest.String(), equals, expectedRest)
+               t.CheckEquals(backtCommand, expectedOutput)
+               t.CheckEquals(actualRest.String(), expectedRest)
                t.CheckOutput(diagnostics)
        }
 
@@ -1385,6 +1409,7 @@ func (s *Suite) Test_SimpleCommandChecke
        t := s.Init(c)
 
        t.SetUpVartypes()
+       // TODO: Check whether these tools are actually necessary for this test.
        t.SetUpTool("awk", "AWK", AtRunTime)
        t.SetUpTool("cp", "CP", AtRunTime)
        t.SetUpTool("echo", "", AtRunTime)
@@ -1413,7 +1438,10 @@ func (s *Suite) Test_SimpleCommandChecke
                "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= second\" instead of \"${INSTALL} -d\".")
 
        G.Pkg = NewPackage(t.File("category/pkgbase"))
-       G.Pkg.Plist.Dirs["share/pkgbase"] = true
+       G.Pkg.Plist.Dirs["share/pkgbase"] = &PlistLine{
+               t.NewLine("PLIST", 123, "share/pkgbase/file"),
+               nil,
+               "share/pkgbase/file"}
 
        // A directory that is found in the PLIST.
        // TODO: Add a test for using this command inside a conditional;
@@ -1434,6 +1462,43 @@ func (s *Suite) Test_SimpleCommandChecke
                "NOTE: filename.mk:1: You can use \"INSTALLATION_DIRS+= share/other\" instead of \"${INSTALL_DATA_DIR}\".")
 }
 
+// The AUTO_MKDIRS code in mk/install/install.mk (install-dirs-from-PLIST)
+// skips conditional directories, as well as directories with placeholders.
+func (s *Suite) Test_SimpleCommandChecker_checkAutoMkdirs__conditional_PLIST(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package",
+               "LIB_SUBDIR=\tsubdir",
+               "",
+               "do-install:",
+               "\t${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/libexec/always",
+               "\t${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/libexec/conditional",
+               "\t${RUN} ${INSTALL_DATA_DIR} ${PREFIX}/${LIB_SUBDIR}",
+       )
+       t.Chdir("category/package")
+       t.CreateFileLines("PLIST",
+               PlistCvsID,
+               "libexec/always/always",
+               "${LIB_SUBDIR}/file",
+               "${PLIST.cond}libexec/conditional/conditional")
+       t.FinishSetUp()
+
+       G.checkdirPackage(".")
+
+       // As libexec/conditional will not be created automatically,
+       // AUTO_MKDIRS must not be suggested in that line.
+       t.CheckOutputLines(
+               "NOTE: Makefile:23: You can use AUTO_MKDIRS=yes "+
+                       "or \"INSTALLATION_DIRS+= libexec/always\" "+
+                       "instead of \"${INSTALL_DATA_DIR}\".",
+               "NOTE: Makefile:24: You can use "+
+                       "\"INSTALLATION_DIRS+= libexec/conditional\" "+
+                       "instead of \"${INSTALL_DATA_DIR}\".",
+               "NOTE: Makefile:25: You can use "+
+                       "\"INSTALLATION_DIRS+= ${LIB_SUBDIR}\" "+
+                       "instead of \"${INSTALL_DATA_DIR}\".")
+}
+
 func (s *Suite) Test_ShellProgramChecker_checkSetE__simple_commands(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.57 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.58
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.57       Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sun Jul 14 21:25:47 2019
@@ -503,7 +503,7 @@ func CheckLinesDescr(lines *Lines) {
                ck.CheckValidCharacters()
 
                if contains(line.Text, "${") {
-                       for _, token := range NewMkParser(nil, line.Text, false).MkTokens() {
+                       for _, token := range NewMkParser(nil, line.Text).MkTokens() {
                                if token.Varuse != nil && G.Pkgsrc.VariableType(nil, token.Varuse.varname) != nil {
                                        line.Notef("Variables are not expanded in the DESCR file.")
                                }

Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.45 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.46
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.45  Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sun Jul 14 21:25:47 2019
@@ -16,7 +16,7 @@ func (s *Suite) Test_Pkglint_Main__help(
 
        exitCode := t.Main("-h")
 
-       c.Check(exitCode, equals, 0)
+       t.CheckEquals(exitCode, 0)
        t.CheckOutputLines(
                "usage: pkglint [options] dir...",
                "",
@@ -60,7 +60,7 @@ func (s *Suite) Test_Pkglint_Main__versi
 
        exitcode := t.Main("--version")
 
-       c.Check(exitcode, equals, 0)
+       t.CheckEquals(exitcode, 0)
        t.CheckOutputLines(
                confVersion)
 }
@@ -71,7 +71,7 @@ func (s *Suite) Test_Pkglint_Main__no_ar
        exitcode := t.Main()
 
        // The "." from the error message is the implicit argument added in Pkglint.Main.
-       c.Check(exitcode, equals, 1)
+       t.CheckEquals(exitcode, 1)
        t.CheckOutputLines(
                "FATAL: \".\" must be inside a pkgsrc tree.")
 }
@@ -82,9 +82,9 @@ func (s *Suite) Test_Pkglint_ParseComman
        exitcode := G.ParseCommandLine([]string{"pkglint", "-Wall", "--only", ":Q", "--version"})
 
        if exitcode != -1 {
-               c.Check(exitcode, equals, 0)
+               t.CheckEquals(exitcode, 0)
        }
-       c.Check(G.Opts.LogOnly, deepEquals, []string{":Q"})
+       t.CheckDeepEquals(G.Opts.LogOnly, []string{":Q"})
        t.CheckOutputLines(
                confVersion)
 }
@@ -94,7 +94,7 @@ func (s *Suite) Test_Pkglint_Main__unkno
 
        exitcode := t.Main("--unknown-option")
 
-       c.Check(exitcode, equals, 1)
+       t.CheckEquals(exitcode, 1)
        c.Check(t.Output(), check.Matches,
                `\Qpkglint: unknown option: --unknown-option\E\n`+
                        `\Q\E\n`+
@@ -230,7 +230,7 @@ func (s *Suite) Test_Pkglint_Main__compl
                        "(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.",
+               "4 errors, 2 warnings and 1 note found.",
                "(Run \"pkglint -e\" to show explanations.)",
                "(Run \"pkglint -fs\" to show what can be fixed automatically.)",
                "(Run \"pkglint -F\" to automatically fix some issues.)")
@@ -247,7 +247,7 @@ func (s *Suite) Test_Pkglint_Main__autof
 
        t.CheckOutputLines(
                "AUTOFIX: ~/filename.mk:1: Inserting a line \"" + MkCvsID + "\" before this line.")
-       t.Check(exitcode, equals, 0)
+       t.CheckEquals(exitcode, 0)
 }
 
 // Run pkglint in a realistic environment.
@@ -441,7 +441,7 @@ func (s *Suite) Test_resolveVariableRefs
 
        // TODO: The ${VAR} after "b:" should also be expanded since there
        //  is no recursion.
-       c.Check(resolved, equals, "the a:1:${VAR}+ 2:${VAR} b:${VAR}")
+       t.CheckEquals(resolved, "the a:1:${VAR}+ 2:${VAR} b:${VAR}")
 }
 
 func (s *Suite) Test_resolveVariableRefs__multilevel(c *check.C) {
@@ -460,7 +460,7 @@ func (s *Suite) Test_resolveVariableRefs
        //  in such a case.
        resolved := resolveVariableRefs(nil, "you ${FIRST}")
 
-       c.Check(resolved, equals, "you got it")
+       t.CheckEquals(resolved, "you got it")
 }
 
 // Usually, a dot in a variable name means a parameterized form.
@@ -475,7 +475,7 @@ func (s *Suite) Test_resolveVariableRefs
 
        resolved := resolveVariableRefs(nil, "gst-plugins0.10-${GST_PLUGINS0.10_TYPE}/distinfo")
 
-       c.Check(resolved, equals, "gst-plugins0.10-x11/distinfo")
+       t.CheckEquals(resolved, "gst-plugins0.10-x11/distinfo")
 }
 
 func (s *Suite) Test_CheckLinesDescr(c *check.C) {
@@ -610,7 +610,7 @@ func (s *Suite) Test_Pkglint_checkReg__a
 
        t.CheckOutputLines(
                "ERROR: ~/category/package/ALTERNATIVES:1: Alternative implementation \"bin/gnu-tar\" must be an absolute path.",
-               "1 error and 0 warnings found.",
+               "1 error found.",
                "(Run \"pkglint -e\" to show explanations.)")
 }
 
@@ -658,7 +658,7 @@ func (s *Suite) Test_Pkglint__profiling(
 
        // Pkglint always writes the profiling data into the current directory.
        // TODO: Make the location of the profiling log a mandatory parameter.
-       c.Check(fileExists("pkglint.pprof"), equals, true)
+       t.CheckEquals(fileExists("pkglint.pprof"), true)
 
        err := os.Remove("pkglint.pprof")
        c.Check(err, check.IsNil)
@@ -667,7 +667,7 @@ func (s *Suite) Test_Pkglint__profiling(
        // or not interesting enough, since that info includes the exact timing
        // that the top time-consuming regular expressions took.
        firstOutput := strings.Split(t.Output(), "\n")[0]
-       c.Check(firstOutput, equals, "ERROR: Makefile: Cannot be read.")
+       t.CheckEquals(firstOutput, "ERROR: Makefile: Cannot be read.")
 }
 
 func (s *Suite) Test_Pkglint__profiling_error(c *check.C) {
@@ -678,7 +678,7 @@ func (s *Suite) Test_Pkglint__profiling_
 
        exitcode := t.Main("--profiling")
 
-       c.Check(exitcode, equals, 1)
+       t.CheckEquals(exitcode, 1)
        t.CheckOutputMatches(
                `FATAL: Cannot create profiling file: open pkglint\.pprof: .*`)
 }
@@ -694,7 +694,7 @@ func (s *Suite) Test_Pkglint_checkReg__i
 
        t.CheckOutputLines(
                "WARN: log: Unexpected file found.",
-               "0 errors and 1 warning found.")
+               "1 warning found.")
 }
 
 func (s *Suite) Test_Pkglint_Tool__prefer_mk_over_pkgsrc(c *check.C) {
@@ -711,10 +711,10 @@ func (s *Suite) Test_Pkglint_Tool__prefe
        loadTimeTool, loadTimeUsable := G.Tool(mklines, "tool", LoadTime)
        runTimeTool, runTimeUsable := G.Tool(mklines, "tool", RunTime)
 
-       c.Check(loadTimeTool, equals, local)
-       c.Check(loadTimeUsable, equals, false)
-       c.Check(runTimeTool, equals, local)
-       c.Check(runTimeUsable, equals, true)
+       t.CheckEquals(loadTimeTool, local)
+       t.CheckEquals(loadTimeUsable, false)
+       t.CheckEquals(runTimeTool, local)
+       t.CheckEquals(runTimeUsable, true)
 }
 
 func (s *Suite) Test_Pkglint_Tool__lookup_by_name_fallback(c *check.C) {
@@ -729,10 +729,10 @@ func (s *Suite) Test_Pkglint_Tool__looku
        // The tool is returned even though it may not be used at the moment.
        // The calling code must explicitly check for usability.
 
-       c.Check(loadTimeTool.String(), equals, "tool:::Nowhere")
-       c.Check(loadTimeUsable, equals, false)
-       c.Check(runTimeTool.String(), equals, "tool:::Nowhere")
-       c.Check(runTimeUsable, equals, false)
+       t.CheckEquals(loadTimeTool.String(), "tool:::Nowhere")
+       t.CheckEquals(loadTimeUsable, false)
+       t.CheckEquals(runTimeTool.String(), "tool:::Nowhere")
+       t.CheckEquals(runTimeUsable, false)
 }
 
 // TODO: Document the purpose of this test.
@@ -750,10 +750,10 @@ func (s *Suite) Test_Pkglint_Tool__looku
        loadTimeTool, loadTimeUsable := G.Tool(mklines, "${TOOL}", LoadTime)
        runTimeTool, runTimeUsable := G.Tool(mklines, "${TOOL}", RunTime)
 
-       c.Check(loadTimeTool, equals, local)
-       c.Check(loadTimeUsable, equals, false)
-       c.Check(runTimeTool, equals, local)
-       c.Check(runTimeUsable, equals, true)
+       t.CheckEquals(loadTimeTool, local)
+       t.CheckEquals(loadTimeUsable, false)
+       t.CheckEquals(runTimeTool, local)
+       t.CheckEquals(runTimeUsable, true)
 }
 
 // TODO: Document the purpose of this test.
@@ -766,10 +766,10 @@ func (s *Suite) Test_Pkglint_Tool__looku
        loadTimeTool, loadTimeUsable := G.Tool(mklines, "${TOOL}", LoadTime)
        runTimeTool, runTimeUsable := G.Tool(mklines, "${TOOL}", RunTime)
 
-       c.Check(loadTimeTool.String(), equals, "tool:TOOL::Nowhere")
-       c.Check(loadTimeUsable, equals, false)
-       c.Check(runTimeTool.String(), equals, "tool:TOOL::Nowhere")
-       c.Check(runTimeUsable, equals, false)
+       t.CheckEquals(loadTimeTool.String(), "tool:TOOL::Nowhere")
+       t.CheckEquals(loadTimeUsable, false)
+       t.CheckEquals(runTimeTool.String(), "tool:TOOL::Nowhere")
+       t.CheckEquals(runTimeUsable, false)
 }
 
 // TODO: Document the purpose of this test.
@@ -782,10 +782,10 @@ func (s *Suite) Test_Pkglint_Tool__looku
        loadTimeTool, loadTimeUsable := G.Tool(mklines, "${TOOL}", LoadTime)
        runTimeTool, runTimeUsable := G.Tool(mklines, "${TOOL}", RunTime)
 
-       c.Check(loadTimeTool.String(), equals, "tool:TOOL::AtRunTime")
-       c.Check(loadTimeUsable, equals, false)
-       c.Check(runTimeTool.String(), equals, "tool:TOOL::AtRunTime")
-       c.Check(runTimeUsable, equals, true)
+       t.CheckEquals(loadTimeTool.String(), "tool:TOOL::AtRunTime")
+       t.CheckEquals(loadTimeUsable, false)
+       t.CheckEquals(runTimeTool.String(), "tool:TOOL::AtRunTime")
+       t.CheckEquals(runTimeUsable, true)
 }
 
 func (s *Suite) Test_Pkglint_ToolByVarname__prefer_mk_over_pkgsrc(c *check.C) {
@@ -799,7 +799,7 @@ func (s *Suite) Test_Pkglint_ToolByVarna
        global.Validity = Nowhere
        local.Validity = AtRunTime
 
-       c.Check(G.ToolByVarname(mklines, "TOOL"), equals, local)
+       t.CheckEquals(G.ToolByVarname(mklines, "TOOL"), local)
 }
 
 func (s *Suite) Test_Pkglint_ToolByVarname(c *check.C) {
@@ -808,7 +808,7 @@ func (s *Suite) Test_Pkglint_ToolByVarna
        mklines := t.NewMkLines("Makefile", MkCvsID)
        G.Pkgsrc.Tools.def("tool", "TOOL", false, AtRunTime, nil)
 
-       c.Check(G.ToolByVarname(mklines, "TOOL").String(), equals, "tool:TOOL::AtRunTime")
+       t.CheckEquals(G.ToolByVarname(mklines, "TOOL").String(), "tool:TOOL::AtRunTime")
 }
 
 func (s *Suite) Test_Pkglint_checkReg__other(c *check.C) {
@@ -899,7 +899,7 @@ func (s *Suite) Test_Pkglint_checkReg__r
        t.CheckOutputLines(
                "ERROR: category/package/README: Packages in main pkgsrc must not have a README file.",
                "ERROR: category/package/TODO: Packages in main pkgsrc must not have a TODO file.",
-               "2 errors and 0 warnings found.")
+               "2 errors found.")
 
        t.Main("--import", "category/package", "wip/package")
 
@@ -908,7 +908,7 @@ func (s *Suite) Test_Pkglint_checkReg__r
                "ERROR: category/package/TODO: Packages in main pkgsrc must not have a TODO file.",
                "ERROR: wip/package/README: Must be cleaned up before committing the package.",
                "ERROR: wip/package/TODO: Must be cleaned up before committing the package.",
-               "4 errors and 0 warnings found.")
+               "4 errors found.")
 }
 
 func (s *Suite) Test_Pkglint_checkReg__unknown_file_in_patches(c *check.C) {
@@ -1152,7 +1152,7 @@ func (s *Suite) Test_Pkglint_checkExecut
        // file is readonly or not.
        st, err := os.Lstat(filename)
        if t.Check(err, check.IsNil) {
-               t.Check(st.Mode()&0111, equals, os.FileMode(0))
+               t.CheckEquals(st.Mode()&0111, os.FileMode(0))
        }
 }
 
@@ -1202,7 +1202,7 @@ func (s *Suite) Test_Pkglint_Main(c *che
 
        runMain := func(out *os.File, commandLine ...string) {
                exitCode := G.Main(out, out, commandLine)
-               c.Check(exitCode, equals, 0)
+               t.CheckEquals(exitCode, 0)
        }
 
        runMain(out, "pkglint", ".")
@@ -1246,7 +1246,7 @@ func (s *Suite) Test_Pkglint_loadCvsEntr
                "must be silently ignored",
                "/name/revision/timestamp/options/tagdate")
 
-       t.Check(isCommitted(t.File("name")), equals, true)
+       t.CheckEquals(isCommitted(t.File("name")), true)
 
        t.CheckOutputLines(
                "ERROR: ~/CVS/Entries:1: Invalid line: /invalid/")
@@ -1268,9 +1268,9 @@ func (s *Suite) Test_Pkglint_loadCvsEntr
                "R /invalid/",
                "R /removed//modified//")
 
-       t.Check(isCommitted(t.File("name")), equals, true)
-       t.Check(isCommitted(t.File("added")), equals, true)
-       t.Check(isCommitted(t.File("removed")), equals, false)
+       t.CheckEquals(isCommitted(t.File("name")), true)
+       t.CheckEquals(isCommitted(t.File("added")), true)
+       t.CheckEquals(isCommitted(t.File("removed")), false)
 
        t.CheckOutputLines(
                "ERROR: ~/CVS/Entries:1: Invalid line: /invalid/",

Index: pkgsrc/pkgtools/pkglint/files/redundantscope_test.go
diff -u pkgsrc/pkgtools/pkglint/files/redundantscope_test.go:1.5 pkgsrc/pkgtools/pkglint/files/redundantscope_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/redundantscope_test.go:1.5    Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/redundantscope_test.go        Sun Jul 14 21:25:47 2019
@@ -1498,9 +1498,8 @@ func (s *Suite) Test_RedundantScope_hand
        scope.Check(mklines)
        writeLocations := scope.get("VAR").vari.WriteLocations()
 
-       t.Check(
+       t.CheckDeepEquals(
                writeLocations,
-               deepEquals,
                []*MkLine{mklines.mklines[0], mklines.mklines[2]})
 }
 
@@ -1537,15 +1536,15 @@ func (s *Suite) Test_includePath_include
                mo  = path("Makefile", "other.mk")
        )
 
-       t.Check(m.includes(m), equals, false)
+       t.CheckEquals(m.includes(m), false)
 
-       t.Check(m.includes(mc), equals, true)
-       t.Check(m.includes(mco), equals, true)
-       t.Check(mc.includes(mco), equals, true)
-
-       t.Check(mc.includes(m), equals, false)
-       t.Check(mc.includes(mo), equals, false)
-       t.Check(mo.includes(mc), equals, false)
+       t.CheckEquals(m.includes(mc), true)
+       t.CheckEquals(m.includes(mco), true)
+       t.CheckEquals(mc.includes(mco), true)
+
+       t.CheckEquals(mc.includes(m), false)
+       t.CheckEquals(mc.includes(mo), false)
+       t.CheckEquals(mo.includes(mc), false)
 }
 
 func (s *Suite) Test_includePath_equals(c *check.C) {
@@ -1562,13 +1561,13 @@ func (s *Suite) Test_includePath_equals(
                mo  = path("Makefile", "other.mk")
        )
 
-       t.Check(m.equals(m), equals, true)
+       t.CheckEquals(m.equals(m), true)
 
-       t.Check(m.equals(mc), equals, false)
-       t.Check(m.equals(mco), equals, false)
-       t.Check(mc.equals(mco), equals, false)
-
-       t.Check(mc.equals(m), equals, false)
-       t.Check(mc.equals(mo), equals, false)
-       t.Check(mo.equals(mc), equals, false)
+       t.CheckEquals(m.equals(mc), false)
+       t.CheckEquals(m.equals(mco), false)
+       t.CheckEquals(mc.equals(mco), false)
+
+       t.CheckEquals(mc.equals(m), false)
+       t.CheckEquals(mc.equals(mo), false)
+       t.CheckEquals(mo.equals(mc), false)
 }
Index: pkgsrc/pkgtools/pkglint/files/testnames_test.go
diff -u pkgsrc/pkgtools/pkglint/files/testnames_test.go:1.5 pkgsrc/pkgtools/pkglint/files/testnames_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/testnames_test.go:1.5 Sat Apr 20 17:43:25 2019
+++ pkgsrc/pkgtools/pkglint/files/testnames_test.go     Sun Jul 14 21:25:47 2019
@@ -11,7 +11,6 @@ import (
 func (s *Suite) Test__test_names(c *check.C) {
        ck := intqa.NewTestNameChecker(c)
        ck.IgnoreFiles("*yacc.go")
-       ck.AllowPrefix("Varalign", "mklines_varalign.go")
        ck.AllowPrefix("ShellParser", "mkshparser.go")
        ck.AllowCamelCaseDescriptions(
                "compared_to_splitIntoShellTokens",
Index: pkgsrc/pkgtools/pkglint/files/var_test.go
diff -u pkgsrc/pkgtools/pkglint/files/var_test.go:1.5 pkgsrc/pkgtools/pkglint/files/var_test.go:1.6
--- pkgsrc/pkgtools/pkglint/files/var_test.go:1.5       Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/var_test.go   Sun Jul 14 21:25:47 2019
@@ -9,11 +9,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME=\toverwritten"), false)
 
-       t.Check(v.ConstantValue(), equals, "overwritten")
+       t.CheckEquals(v.ConstantValue(), "overwritten")
 }
 
 // Variables that reference other variable are considered constants.
@@ -27,11 +27,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME=\t${OTHER}"), false)
 
-       t.Check(v.Constant(), equals, true)
+       t.CheckEquals(v.Constant(), true)
 }
 
 func (s *Suite) Test_Var_ConstantValue__assign_eval_reference(c *check.C) {
@@ -41,7 +41,7 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME:=\t${OTHER}"), false)
 
@@ -51,7 +51,7 @@ func (s *Suite) Test_Var_ConstantValue__
        //
        // As of March 2019 this is not implemented, therefore pkglint
        // doesn't treat the variable as constant, to prevent wrong warnings.
-       t.Check(v.Constant(), equals, false)
+       t.CheckEquals(v.Constant(), false)
 }
 
 func (s *Suite) Test_Var_ConstantValue__assign_conditional(c *check.C) {
@@ -63,7 +63,7 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tconditional"), true, "OPSYS")
 
-       t.Check(v.Constant(), equals, false)
+       t.CheckEquals(v.Constant(), false)
 }
 
 func (s *Suite) Test_Var_ConstantValue__default(c *check.C) {
@@ -73,11 +73,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME?=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME?=\tignored"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 }
 
 func (s *Suite) Test_Var_ConstantValue__eval_then_default(c *check.C) {
@@ -87,11 +87,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("buildlink3.mk", 123, "VARNAME:=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("builtin.mk", 124, "VARNAME?=\tignored"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 }
 
 func (s *Suite) Test_Var_ConstantValue__append(c *check.C) {
@@ -101,11 +101,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME+=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, " value")
+       t.CheckEquals(v.ConstantValue(), " value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME+=\tappended"), false)
 
-       t.Check(v.ConstantValue(), equals, " value appended")
+       t.CheckEquals(v.ConstantValue(), " value appended")
 }
 
 func (s *Suite) Test_Var_ConstantValue__eval(c *check.C) {
@@ -115,11 +115,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME:=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME:=\toverwritten"), false)
 
-       t.Check(v.ConstantValue(), equals, "overwritten")
+       t.CheckEquals(v.ConstantValue(), "overwritten")
 }
 
 // Variables that are based on running shell commands are never constant.
@@ -130,11 +130,11 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME!=\techo hello"), false)
 
-       t.Check(v.Constant(), equals, false)
+       t.CheckEquals(v.Constant(), false)
 }
 
 func (s *Suite) Test_Var_ConstantValue__referenced_before(c *check.C) {
@@ -148,11 +148,11 @@ func (s *Suite) Test_Var_ConstantValue__
        // condition.
        v.Read(t.NewMkLine("readwrite.mk", 123, "OTHER=\t${VARNAME}"))
 
-       t.Check(v.Constant(), equals, false)
+       t.CheckEquals(v.Constant(), false)
 
        v.Write(t.NewMkLine("readwrite.mk", 124, "VARNAME=\tvalue"), false)
 
-       t.Check(v.Constant(), equals, false)
+       t.CheckEquals(v.Constant(), false)
 }
 
 func (s *Suite) Test_Var_ConstantValue__referenced_in_between(c *check.C) {
@@ -162,7 +162,7 @@ func (s *Suite) Test_Var_ConstantValue__
 
        v.Write(t.NewMkLine("readwrite.mk", 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        // Since the value of VARNAME escapes here, the value is not
        // guaranteed to be the same in all evaluations of ${VARNAME}.
@@ -170,11 +170,11 @@ func (s *Suite) Test_Var_ConstantValue__
        // condition.
        v.Read(t.NewMkLine("readwrite.mk", 124, "OTHER=\t${VARNAME}"))
 
-       t.Check(v.ConstantValue(), equals, "value")
+       t.CheckEquals(v.ConstantValue(), "value")
 
        v.Write(t.NewMkLine("write.mk", 125, "VARNAME=\toverwritten"), false)
 
-       t.Check(v.Constant(), equals, false)
+       t.CheckEquals(v.Constant(), false)
 }
 
 func (s *Suite) Test_Var_ConditionalVars(c *check.C) {
@@ -182,19 +182,19 @@ func (s *Suite) Test_Var_ConditionalVars
 
        v := NewVar("VARNAME")
 
-       t.Check(v.Conditional(), equals, false)
+       t.CheckEquals(v.Conditional(), false)
        t.Check(v.ConditionalVars(), check.IsNil)
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tconditional"), true, "OPSYS")
 
-       t.Check(v.Constant(), equals, false)
-       t.Check(v.Conditional(), equals, true)
-       t.Check(v.ConditionalVars(), deepEquals, []string{"OPSYS"})
+       t.CheckEquals(v.Constant(), false)
+       t.CheckEquals(v.Conditional(), true)
+       t.CheckDeepEquals(v.ConditionalVars(), []string{"OPSYS"})
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME=\tconditional"), true, "OPSYS")
 
-       t.Check(v.Conditional(), equals, true)
-       t.Check(v.ConditionalVars(), deepEquals, []string{"OPSYS"})
+       t.CheckEquals(v.Conditional(), true)
+       t.CheckDeepEquals(v.ConditionalVars(), []string{"OPSYS"})
 }
 
 func (s *Suite) Test_Var_Value__initial_conditional_write(c *check.C) {
@@ -207,9 +207,9 @@ func (s *Suite) Test_Var_Value__initial_
        // Since there is no previous value, the simplest choice is to just
        // take the first seen value, no matter if that value is conditional
        // or not.
-       t.Check(v.Conditional(), equals, true)
-       t.Check(v.Constant(), equals, false)
-       t.Check(v.Value(), equals, "overwritten conditionally")
+       t.CheckEquals(v.Conditional(), true)
+       t.CheckEquals(v.Constant(), false)
+       t.CheckEquals(v.Value(), "overwritten conditionally")
 }
 
 func (s *Suite) Test_Var_Write__conditional_without_variables(c *check.C) {
@@ -224,13 +224,13 @@ func (s *Suite) Test_Var_Write__conditio
        scope := NewRedundantScope()
        mklines.ForEach(func(mkline *MkLine) {
                if mkline.IsVarassign() {
-                       t.Check(scope.get("VAR").vari.Conditional(), equals, false)
+                       t.CheckEquals(scope.get("VAR").vari.Conditional(), false)
                }
 
                scope.checkLine(mklines, mkline)
 
                if mkline.IsVarassign() {
-                       t.Check(scope.get("VAR").vari.Conditional(), equals, true)
+                       t.CheckEquals(scope.get("VAR").vari.Conditional(), true)
                }
        })
 }
@@ -250,11 +250,11 @@ func (s *Suite) Test_Var_Value__conditio
 
        v.Write(t.NewMkLine("write.mk", 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.Value(), equals, "value")
+       t.CheckEquals(v.Value(), "value")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME+=\tappended"), false)
 
-       t.Check(v.Value(), equals, "value appended")
+       t.CheckEquals(v.Value(), "value appended")
 
        v.Write(t.NewMkLine("write.mk", 124, "VARNAME:=\toverwritten conditionally"), true, "OPSYS")
 
@@ -267,9 +267,9 @@ func (s *Suite) Test_Var_Value__conditio
        //  .endif
        // The value stays the same, still it is marked as conditional and therefore
        // not constant anymore.
-       t.Check(v.Conditional(), equals, true)
-       t.Check(v.Constant(), equals, false)
-       t.Check(v.Value(), equals, "value appended")
+       t.CheckEquals(v.Conditional(), true)
+       t.CheckEquals(v.Constant(), false)
+       t.CheckEquals(v.Value(), "value appended")
 }
 
 func (s *Suite) Test_Var_Value__infrastructure(c *check.C) {
@@ -279,15 +279,15 @@ func (s *Suite) Test_Var_Value__infrastr
 
        v.Write(t.NewMkLine(t.File("write.mk"), 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.Value(), equals, "value")
+       t.CheckEquals(v.Value(), "value")
 
        v.Write(t.NewMkLine(t.File("mk/write.mk"), 123, "VARNAME=\tinfra"), false)
 
-       t.Check(v.Value(), equals, "value")
+       t.CheckEquals(v.Value(), "value")
 
        v.Write(t.NewMkLine(t.File("wip/mk/write.mk"), 123, "VARNAME=\twip infra"), false)
 
-       t.Check(v.Value(), equals, "value")
+       t.CheckEquals(v.Value(), "value")
 }
 
 func (s *Suite) Test_Var_ValueInfra(c *check.C) {
@@ -297,15 +297,15 @@ func (s *Suite) Test_Var_ValueInfra(c *c
 
        v.Write(t.NewMkLine(t.File("write.mk"), 123, "VARNAME=\tvalue"), false)
 
-       t.Check(v.ValueInfra(), equals, "value")
+       t.CheckEquals(v.ValueInfra(), "value")
 
        v.Write(t.NewMkLine(t.File("mk/write.mk"), 123, "VARNAME=\tinfra"), false)
 
-       t.Check(v.ValueInfra(), equals, "infra")
+       t.CheckEquals(v.ValueInfra(), "infra")
 
        v.Write(t.NewMkLine(t.File("wip/mk/write.mk"), 123, "VARNAME=\twip infra"), false)
 
-       t.Check(v.ValueInfra(), equals, "wip infra")
+       t.CheckEquals(v.ValueInfra(), "wip infra")
 }
 
 func (s *Suite) Test_Var_ReadLocations(c *check.C) {
@@ -318,7 +318,7 @@ func (s *Suite) Test_Var_ReadLocations(c
        mkline123 := t.NewMkLine("read.mk", 123, "OTHER=\t${VAR}")
        v.Read(mkline123)
 
-       t.Check(v.ReadLocations(), deepEquals, []*MkLine{mkline123})
+       t.CheckDeepEquals(v.ReadLocations(), []*MkLine{mkline123})
 
        mkline124 := t.NewMkLine("read.mk", 124, "OTHER=\t${VAR} ${VAR}")
        v.Read(mkline124)
@@ -326,7 +326,7 @@ func (s *Suite) Test_Var_ReadLocations(c
 
        // For now, count every read of the variable. I'm not yet sure
        // whether that's the best way or whether to make the lines unique.
-       t.Check(v.ReadLocations(), deepEquals, []*MkLine{mkline123, mkline124, mkline124})
+       t.CheckDeepEquals(v.ReadLocations(), []*MkLine{mkline123, mkline124, mkline124})
 }
 
 func (s *Suite) Test_Var_WriteLocations(c *check.C) {
@@ -339,7 +339,7 @@ func (s *Suite) Test_Var_WriteLocations(
        mkline123 := t.NewMkLine("write.mk", 123, "VAR=\tvalue")
        v.Write(mkline123, false)
 
-       t.Check(v.WriteLocations(), deepEquals, []*MkLine{mkline123})
+       t.CheckDeepEquals(v.WriteLocations(), []*MkLine{mkline123})
 
        // Multiple writes from the same line may happen because of a .for loop.
        mkline125 := t.NewMkLine("write.mk", 125, "VAR+=\t${var}")
@@ -348,7 +348,7 @@ func (s *Suite) Test_Var_WriteLocations(
 
        // For now, count every write of the variable. I'm not yet sure
        // whether that's the best way or whether to make the lines unique.
-       t.Check(v.WriteLocations(), deepEquals, []*MkLine{mkline123, mkline125, mkline125})
+       t.CheckDeepEquals(v.WriteLocations(), []*MkLine{mkline123, mkline125, mkline125})
 }
 
 func (s *Suite) Test_Var_Refs(c *check.C) {
@@ -364,5 +364,5 @@ func (s *Suite) Test_Var_Refs(c *check.C
 
        v.AddRef("FOR")
 
-       t.Check(v.Refs(), deepEquals, []string{"OTHER", "OPSYS", "THEN", "ELSE", "COND", "FOR"})
+       t.CheckDeepEquals(v.Refs(), []string{"OTHER", "OPSYS", "THEN", "ELSE", "COND", "FOR"})
 }

Index: pkgsrc/pkgtools/pkglint/files/shell.go
diff -u pkgsrc/pkgtools/pkglint/files/shell.go:1.43 pkgsrc/pkgtools/pkglint/files/shell.go:1.44
--- pkgsrc/pkgtools/pkglint/files/shell.go:1.43 Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/shell.go      Sun Jul 14 21:25:47 2019
@@ -556,7 +556,7 @@ func (scc *SimpleCommandChecker) handleC
        }
 
        shellword := scc.strcmd.Name
-       varuse := NewMkParser(nil, shellword, false).VarUse()
+       varuse := NewMkParser(nil, shellword).VarUse()
        if varuse == nil {
                return false
        }
@@ -701,7 +701,15 @@ func (scc *SimpleCommandChecker) checkAu
                // TODO: Replace regex with proper VarUse.
                if !contains(arg, "$$") && !matches(arg, `\$\{[_.]*[a-z]`) {
                        if m, dirname := match1(arg, `^(?:\$\{DESTDIR\})?\$\{PREFIX(?:|:Q)\}/(.*)`); m {
-                               if G.Pkg != nil && G.Pkg.Plist.Dirs[dirname] {
+                               autoMkdirs := false
+                               if G.Pkg != nil {
+                                       plistLine := G.Pkg.Plist.Dirs[dirname]
+                                       if plistLine != nil && !containsVarRef(plistLine.Text) {
+                                               autoMkdirs = true
+                                       }
+                               }
+
+                               if autoMkdirs {
                                        scc.Notef("You can use AUTO_MKDIRS=yes or \"INSTALLATION_DIRS+= %s\" instead of %q.", dirname, cmdname)
                                        scc.Explain(
                                                "Many packages include a list of all needed directories in their",
@@ -978,7 +986,7 @@ func (spc *ShellProgramChecker) checkSet
        }
 
        line := spc.mkline.Line
-       if !line.FirstTime("switch to set -e") {
+       if !line.once.FirstTime("switch to set -e") {
                return
        }
 

Index: pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.16 pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.17
--- pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go:1.16      Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/shtokenizer_test.go   Sun Jul 14 21:25:47 2019
@@ -15,14 +15,14 @@ func (s *Suite) Test_ShTokenizer_ShAtom(
 
                actualAtoms := p.ShAtoms()
 
-               t.Check(p.Rest(), equals, expectedRest)
-               c.Check(len(actualAtoms), equals, len(expectedAtoms))
+               t.CheckEquals(p.Rest(), expectedRest)
+               t.CheckEquals(len(actualAtoms), len(expectedAtoms))
 
                for i, actualAtom := range actualAtoms {
                        if i < len(expectedAtoms) {
-                               c.Check(actualAtom, deepEquals, expectedAtoms[i])
+                               t.CheckDeepEquals(actualAtom, expectedAtoms[i])
                        } else {
-                               c.Check(actualAtom, deepEquals, nil)
+                               t.CheckDeepEquals(actualAtom, nil)
                        }
                }
        }
@@ -415,6 +415,8 @@ func (s *Suite) Test_ShTokenizer_ShAtom(
 }
 
 func (s *Suite) Test_ShTokenizer_ShAtom__quoting(c *check.C) {
+       t := s.Init(c)
+
        test := func(input, expectedOutput string) {
                p := NewShTokenizer(dummyLine, input, false)
                q := shqPlain
@@ -430,8 +432,8 @@ func (s *Suite) Test_ShTokenizer_ShAtom_
                                result += "[" + q.String() + "]"
                        }
                }
-               c.Check(result, equals, expectedOutput)
-               c.Check(p.Rest(), equals, "")
+               t.CheckEquals(result, expectedOutput)
+               t.CheckEquals(p.Rest(), "")
        }
 
        test("hello, world", "hello, world")
@@ -456,7 +458,7 @@ func (s *Suite) Test_ShTokenizer_ShToken
        testRest := func(str string, expected ...string) string {
                p := NewShTokenizer(dummyLine, str, false)
                for _, exp := range expected {
-                       c.Check(p.ShToken().MkText, equals, exp)
+                       t.CheckEquals(p.ShToken().MkText, exp)
                }
                return p.Rest()
        }
@@ -464,16 +466,16 @@ func (s *Suite) Test_ShTokenizer_ShToken
        test := func(str string, expected ...string) {
                p := NewShTokenizer(dummyLine, str, false)
                for _, exp := range expected {
-                       c.Check(p.ShToken().MkText, equals, exp)
+                       t.CheckEquals(p.ShToken().MkText, exp)
                }
-               c.Check(p.Rest(), equals, "")
+               t.CheckEquals(p.Rest(), "")
                t.CheckOutputEmpty()
        }
 
        testNil := func(str string) {
                p := NewShTokenizer(dummyLine, str, false)
                c.Check(p.ShToken(), check.IsNil)
-               c.Check(p.Rest(), equals, "")
+               t.CheckEquals(p.Rest(), "")
                t.CheckOutputEmpty()
        }
 
@@ -481,7 +483,7 @@ func (s *Suite) Test_ShTokenizer_ShToken
        testNil(" ")
        rest := testRest("\t\t\t\n\n\n\n\t ",
                "\n\n\n\n")
-       c.Check(rest, equals, "\t ")
+       t.CheckEquals(rest, "\t ")
 
        test("echo",
                "echo")
@@ -522,13 +524,14 @@ func (s *Suite) Test_ShTokenizer_ShToken
 }
 
 func (s *Suite) Test_ShTokenizer_shVarUse(c *check.C) {
+       t := s.Init(c)
 
        test := func(input string, output *ShAtom, rest string) {
                tok := NewShTokenizer(nil, input, false)
                actual := tok.shVarUse(shqPlain)
 
-               c.Check(actual, deepEquals, output)
-               c.Check(tok.Rest(), equals, rest)
+               t.CheckDeepEquals(actual, output)
+               t.CheckEquals(tok.Rest(), rest)
        }
 
        shvar := func(text, varname string) *ShAtom {

Index: pkgsrc/pkgtools/pkglint/files/shtypes_test.go
diff -u pkgsrc/pkgtools/pkglint/files/shtypes_test.go:1.8 pkgsrc/pkgtools/pkglint/files/shtypes_test.go:1.9
--- pkgsrc/pkgtools/pkglint/files/shtypes_test.go:1.8   Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/shtypes_test.go       Sun Jul 14 21:25:47 2019
@@ -9,24 +9,30 @@ func NewShAtom(typ ShAtomType, text stri
 }
 
 func (s *Suite) Test_ShAtomType_String(c *check.C) {
-       c.Check(shtComment.String(), equals, "comment")
+       t := s.Init(c)
+
+       t.CheckEquals(shtComment.String(), "comment")
 }
 
 func (s *Suite) Test_ShAtom_String(c *check.C) {
+       t := s.Init(c)
+
        tokenizer := NewShTokenizer(dummyLine, "${ECHO} \"hello, world\"", false)
 
        atoms := tokenizer.ShAtoms()
 
-       c.Check(len(atoms), equals, 5)
-       c.Check(atoms[0].String(), equals, "varuse(\"ECHO\")")
-       c.Check(atoms[1].String(), equals, "ShAtom(space, \" \", plain)")
-       c.Check(atoms[2].String(), equals, "ShAtom(text, \"\\\"\", d)")
-       c.Check(atoms[3].String(), equals, "ShAtom(text, \"hello, world\", d)")
-       c.Check(atoms[4].String(), equals, "\"\\\"\"")
+       t.CheckEquals(len(atoms), 5)
+       t.CheckEquals(atoms[0].String(), "varuse(\"ECHO\")")
+       t.CheckEquals(atoms[1].String(), "ShAtom(space, \" \", plain)")
+       t.CheckEquals(atoms[2].String(), "ShAtom(text, \"\\\"\", d)")
+       t.CheckEquals(atoms[3].String(), "ShAtom(text, \"hello, world\", d)")
+       t.CheckEquals(atoms[4].String(), "\"\\\"\"")
 }
 
 func (s *Suite) Test_ShQuoting_String(c *check.C) {
-       c.Check(shqDquotBacktSquot.String(), equals, "dbs")
+       t := s.Init(c)
+
+       t.CheckEquals(shqDquotBacktSquot.String(), "dbs")
 }
 
 func (s *Suite) Test_NewShToken__no_atoms(c *check.C) {
@@ -37,8 +43,10 @@ func (s *Suite) Test_NewShToken__no_atom
 }
 
 func (s *Suite) Test_ShToken_String(c *check.C) {
+       t := s.Init(c)
+
        tokenizer := NewShTokenizer(dummyLine, "${ECHO} \"hello, world\"", false)
 
-       c.Check(tokenizer.ShToken().String(), equals, "ShToken([varuse(\"ECHO\")])")
-       c.Check(tokenizer.ShToken().String(), equals, "ShToken([ShAtom(text, \"\\\"\", d) ShAtom(text, \"hello, world\", d) \"\\\"\"])")
+       t.CheckEquals(tokenizer.ShToken().String(), "ShToken([varuse(\"ECHO\")])")
+       t.CheckEquals(tokenizer.ShToken().String(), "ShToken([ShAtom(text, \"\\\"\", d) ShAtom(text, \"hello, world\", d) \"\\\"\"])")
 }

Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.28 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.29
--- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.28     Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go  Sun Jul 14 21:25:47 2019
@@ -13,19 +13,19 @@ func (s *Suite) Test_SubstContext__incom
 
        ctx.Varassign(t.NewMkLine("Makefile", 10, "PKGNAME=pkgname-1.0"))
 
-       c.Check(ctx.id, equals, "")
+       t.CheckEquals(ctx.id, "")
 
        ctx.Varassign(t.NewMkLine("Makefile", 11, "SUBST_CLASSES+=interp"))
 
-       c.Check(ctx.id, equals, "interp")
+       t.CheckEquals(ctx.id, "interp")
 
        ctx.Varassign(t.NewMkLine("Makefile", 12, "SUBST_FILES.interp=Makefile"))
 
-       c.Check(ctx.IsComplete(), equals, false)
+       t.CheckEquals(ctx.IsComplete(), false)
 
        ctx.Varassign(t.NewMkLine("Makefile", 13, "SUBST_SED.interp=s,@PREFIX@,${PREFIX},g"))
 
-       c.Check(ctx.IsComplete(), equals, false)
+       t.CheckEquals(ctx.IsComplete(), false)
 
        ctx.Finish(t.NewMkLine("Makefile", 14, ""))
 
@@ -46,11 +46,11 @@ func (s *Suite) Test_SubstContext__compl
        ctx.Varassign(t.NewMkLine("Makefile", 12, "SUBST_FILES.p=Makefile"))
        ctx.Varassign(t.NewMkLine("Makefile", 13, "SUBST_SED.p=s,@PREFIX@,${PREFIX},g"))
 
-       c.Check(ctx.IsComplete(), equals, false)
+       t.CheckEquals(ctx.IsComplete(), false)
 
        ctx.Varassign(t.NewMkLine("Makefile", 14, "SUBST_STAGE.p=post-configure"))
 
-       c.Check(ctx.IsComplete(), equals, true)
+       t.CheckEquals(ctx.IsComplete(), true)
 
        ctx.Finish(t.NewMkLine("Makefile", 15, ""))
 
@@ -72,7 +72,7 @@ func (s *Suite) Test_SubstContext__OPSYS
        ctx.Varassign(t.NewMkLine("Makefile", 14, "SUBST_SED.prefix=s,@PREFIX@,${PREFIX},g"))
        ctx.Varassign(t.NewMkLine("Makefile", 15, "SUBST_STAGE.prefix=post-configure"))
 
-       c.Check(ctx.IsComplete(), equals, true)
+       t.CheckEquals(ctx.IsComplete(), true)
 
        ctx.Finish(t.NewMkLine("Makefile", 15, ""))
 
@@ -814,7 +814,7 @@ func (s *Suite) Test_SubstContext_extrac
        t := s.Init(c)
 
        test := func(input, expected string) {
-               t.Check((*SubstContext).extractVarname(nil, input), equals, expected)
+               t.CheckEquals((*SubstContext).extractVarname(nil, input), expected)
        }
 
        // A simple variable name.
@@ -890,7 +890,7 @@ func simulateSubstLines(t *Tester, texts
                assertNil(err, "")
 
                if lineno != 0 {
-                       t.Check(curr, equals, lineno)
+                       t.CheckEquals(curr, lineno)
                }
 
                text := lineText[4:]

Index: pkgsrc/pkgtools/pkglint/files/toplevel.go
diff -u pkgsrc/pkgtools/pkglint/files/toplevel.go:1.19 pkgsrc/pkgtools/pkglint/files/toplevel.go:1.20
--- pkgsrc/pkgtools/pkglint/files/toplevel.go:1.19      Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/toplevel.go   Sun Jul 14 21:25:47 2019
@@ -20,7 +20,7 @@ func CheckdirToplevel(dir string) {
        }
 
        for _, mkline := range mklines.mklines {
-               if (mkline.IsVarassign() || mkline.IsCommentedVarassign()) && mkline.Varname() == "SUBDIR" {
+               if mkline.IsVarassignMaybeCommented() && mkline.Varname() == "SUBDIR" {
                        ctx.checkSubdir(mkline)
                }
        }
@@ -38,15 +38,14 @@ func CheckdirToplevel(dir string) {
 func (ctx *Toplevel) checkSubdir(mkline *MkLine) {
        subdir := mkline.Value()
 
-       if mkline.IsCommentedVarassign() && (mkline.VarassignComment() == "#" || mkline.VarassignComment() == "") {
-               mkline.Warnf("%q commented out without giving a reason.", subdir)
-       }
-
-       if !hasSuffix(mkline.ValueAlign(), "=\t") {
-               mkline.Warnf("Indentation should be a single tab character.")
+       if mkline.IsCommentedVarassign() {
+               comment := mkline.VarassignComment()
+               if comment == "" || comment == "#" {
+                       mkline.Warnf("%q commented out without giving a reason.", subdir)
+               }
        }
 
-       if contains(subdir, "$") || !fileExists(ctx.dir+"/"+subdir+"/Makefile") {
+       if containsVarRef(subdir) || !fileExists(ctx.dir+"/"+subdir+"/Makefile") {
                return
        }
 

Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.48 pkgsrc/pkgtools/pkglint/files/util.go:1.49
--- pkgsrc/pkgtools/pkglint/files/util.go:1.48  Mon Jul  1 22:25:52 2019
+++ pkgsrc/pkgtools/pkglint/files/util.go       Sun Jul 14 21:25:47 2019
@@ -126,17 +126,20 @@ func isHspace(ch byte) bool {
        return ch == ' ' || ch == '\t'
 }
 
-func isHspaceRune(r rune) bool {
-       return r == ' ' || r == '\t'
-}
-
-func ifelseStr(cond bool, a, b string) string {
+func condStr(cond bool, a, b string) string {
        if cond {
                return a
        }
        return b
 }
 
+func condInt(cond bool, trueValue, falseValue int) int {
+       if cond {
+               return trueValue
+       }
+       return falseValue
+}
+
 func keysJoined(m map[string]bool) string {
        var keys []string
        for key := range m {
@@ -146,6 +149,13 @@ func keysJoined(m map[string]bool) strin
        return strings.Join(keys, " ")
 }
 
+func imin(a, b int) int {
+       if a < b {
+               return a
+       }
+       return b
+}
+
 func imax(a, b int) int {
        if a > b {
                return a
@@ -313,7 +323,7 @@ func tabWidth(s string) int {
        length := 0
        for _, r := range s {
                if r == '\t' {
-                       length = length - length%8 + 8
+                       length = length&-8 + 8
                } else {
                        length++
                }
@@ -325,7 +335,7 @@ func detab(s string) string {
        var detabbed strings.Builder
        for _, r := range s {
                if r == '\t' {
-                       detabbed.WriteString("        "[:8-detabbed.Len()%8])
+                       detabbed.WriteString("        "[:8-detabbed.Len()&7])
                } else {
                        detabbed.WriteRune(r)
                }
@@ -342,6 +352,18 @@ func alignWith(str, other string) string
        return str + strings.Repeat("\t", tabsNeeded)
 }
 
+func indent(width int) string {
+       return strings.Repeat("\t", width>>3) + "       "[:width&7]
+}
+
+// alignmentAfter returns the indentation that is necessary to get
+// from the given prefix to the desired width.
+func alignmentAfter(prefix string, width int) string {
+       pw := tabWidth(prefix)
+       assert(width >= pw)
+       return indent(width - condInt(pw&-8 != width&-8, pw&-8, pw))
+}
+
 func shorten(s string, maxChars int) string {
        codePoints := 0
        for i := range s {
@@ -396,7 +418,7 @@ func toInt(s string, def int) int {
 
 // mkopSubst evaluates make(1)'s :S substitution operator.
 func mkopSubst(s string, left bool, from string, right bool, to string, flags string) string {
-       re := regex.Pattern(ifelseStr(left, "^", "") + regexp.QuoteMeta(from) + ifelseStr(right, "$", ""))
+       re := regex.Pattern(condStr(left, "^", "") + regexp.QuoteMeta(from) + condStr(right, "$", ""))
        done := false
        gflag := contains(flags, "g")
        return replaceAllFunc(s, re, func(match string) string {

Index: pkgsrc/pkgtools/pkglint/files/vartype.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.34 pkgsrc/pkgtools/pkglint/files/vartype.go:1.35
--- pkgsrc/pkgtools/pkglint/files/vartype.go:1.34       Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/vartype.go    Sun Jul 14 21:25:47 2019
@@ -78,20 +78,20 @@ func (perms ACLPermissions) String() str
                return "none"
        }
        return joinSkipEmpty(", ",
-               ifelseStr(perms.Contains(aclpSet), "set", ""),
-               ifelseStr(perms.Contains(aclpSetDefault), "set-default", ""),
-               ifelseStr(perms.Contains(aclpAppend), "append", ""),
-               ifelseStr(perms.Contains(aclpUseLoadtime), "use-loadtime", ""),
-               ifelseStr(perms.Contains(aclpUse), "use", ""))
+               condStr(perms.Contains(aclpSet), "set", ""),
+               condStr(perms.Contains(aclpSetDefault), "set-default", ""),
+               condStr(perms.Contains(aclpAppend), "append", ""),
+               condStr(perms.Contains(aclpUseLoadtime), "use-loadtime", ""),
+               condStr(perms.Contains(aclpUse), "use", ""))
 }
 
 func (perms ACLPermissions) HumanString() string {
        return joinSkipEmptyOxford("or",
-               ifelseStr(perms.Contains(aclpSet), "set", ""),
-               ifelseStr(perms.Contains(aclpSetDefault), "given a default value", ""),
-               ifelseStr(perms.Contains(aclpAppend), "appended to", ""),
-               ifelseStr(perms.Contains(aclpUseLoadtime), "used at load time", ""),
-               ifelseStr(perms.Contains(aclpUse), "used", ""))
+               condStr(perms.Contains(aclpSet), "set", ""),
+               condStr(perms.Contains(aclpSetDefault), "given a default value", ""),
+               condStr(perms.Contains(aclpAppend), "appended to", ""),
+               condStr(perms.Contains(aclpUseLoadtime), "used at load time", ""),
+               condStr(perms.Contains(aclpUse), "used", ""))
 }
 
 func (vt *Vartype) List() bool                { return vt.options&List != 0 }

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.51 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.52
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.51     Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sun Jul 14 21:25:47 2019
@@ -689,6 +689,18 @@ func (s *Suite) Test_VartypeCheck_Homepa
        vt.Output(
                "WARN: filename.mk:31: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
 
+       delete(G.Pkg.vars.firstDef, "MASTER_SITES")
+       delete(G.Pkg.vars.lastDef, "MASTER_SITES")
+       G.Pkg.vars.Define("MASTER_SITES", t.NewMkLine(G.Pkg.File("Makefile"), 5,
+               "MASTER_SITES=\t# none"))
+
+       vt.Values(
+               "${MASTER_SITES}")
+
+       // When MASTER_SITES is empty, pkglint cannot extract the first of the URLs
+       // for using it in the HOMEPAGE.
+       vt.Output(
+               "WARN: filename.mk:41: HOMEPAGE should not be defined in terms of MASTER_SITEs.")
 }
 
 func (s *Suite) Test_VartypeCheck_Identifier(c *check.C) {
@@ -791,7 +803,7 @@ func (s *Suite) Test_VartypeCheck_Licens
                MkCvsID,
                "PERL5_LICENSE= gnu-gpl-v2 OR artistic")
        // Also registers the PERL5_LICENSE variable in the package.
-       mklines.collectDefinedVariables()
+       mklines.collectVariables()
 
        vt := NewVartypeCheckTester(t, (*VartypeCheck).License)
 
@@ -1610,7 +1622,7 @@ func (vt *VartypeCheckTester) Values(val
                        panic("Invalid operator: " + opStr)
                }
 
-               space := ifelseStr(hasSuffix(varname, "+") && opStr == "=", " ", "")
+               space := condStr(hasSuffix(varname, "+") && opStr == "=", " ", "")
                return varname + space + opStr + value
        }
 

Index: pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go
diff -u pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.4 pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.5
--- pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go:1.4    Thu Feb 21 22:49:04 2019
+++ pkgsrc/pkgtools/pkglint/files/textproc/lexer_test.go        Sun Jul 14 21:25:47 2019
@@ -88,7 +88,7 @@ func (s *Suite) Test_Lexer_Skip(c *check
        c.Check(
                func() { lexer.Skip(6) },
                check.PanicMatches,
-               `^runtime error: slice bounds out of range$`)
+               `runtime error: slice bounds out of range.*`)
 }
 
 func (s *Suite) Test_Lexer_NextString(c *check.C) {

Added files:

Index: pkgsrc/pkgtools/pkglint/files/varalignblock.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/varalignblock.go:1.1
--- /dev/null   Sun Jul 14 21:25:48 2019
+++ pkgsrc/pkgtools/pkglint/files/varalignblock.go      Sun Jul 14 21:25:47 2019
@@ -0,0 +1,631 @@
+package pkglint
+
+import (
+       "netbsd.org/pkglint/textproc"
+       "strings"
+)
+
+// VaralignBlock checks that all variable assignments from a paragraph
+// use the same indentation depth for their values.
+// It also checks that the indentation uses tabs instead of spaces.
+//
+// In general, all values should be aligned using tabs.
+// As an exception, a single very long line (called an outlier) may be
+// aligned with a single space.
+// A typical example is a SITES.very-long-file-name.tar.gz variable
+// between HOMEPAGE and DISTFILES.
+//
+// Continuation lines are also aligned to the single-line assignments.
+// There are two types of continuation lines. The first type is an
+// empty multiline:
+//
+//  MULTI_EMPTY_LINE= \
+//          The value starts in the second line.
+//
+// The backslash in the first line is usually aligned to the other variables
+// in the same paragraph. If the variable name is so long that it is an
+// outlier, it may be indented with a single space, just like a single-line
+// variable. In multi-line shell commands or AWK programs, the backslash is
+// often indented to column 73, as are the backslashes from the follow-up
+// lines, to act as a visual guideline.
+//
+// Since this type is often used for URLs or other long values, the first
+// follow-up line may be indented with a single tab, even if the other
+// variables in the paragraph are aligned further to the right. If the
+// indentation is not a single tab, it must match the indentation of the
+// other lines in the paragraph.
+//
+//  INITIAL_LINE=   The value starts in the first line \
+//                  and continues in the second line.
+//
+// In lists or plain text, like in the INITIAL_LINE above, all values are
+// aligned in the same column. Some variables also contain code, and in
+// these variables, the line containing the first word defines how deep
+// the follow-up lines must be indented at least.
+//
+//  SHELL_CMD=                                                              \
+//          if ${PKG_ADMIN} pmatch ${PKGNAME} ${dependency}; then           \
+//                  ${ECHO} yes;                                            \
+//          else                                                            \
+//                  ${ECHO} no;                                             \
+//          fi
+//
+// In the continuation lines, each follow-up line is indented with at least
+// one tab, to avoid confusing them with regular single-lines. This is
+// especially true for CONFIGURE_ENV, since the environment variables are
+// typically uppercase as well.
+//
+// TODO: An initial line has this form:
+//  comment? varname+op space? value? space? comment? space? backslash?
+//
+// TODO: A follow-up line has the form:
+//  comment? space? value? space? comment? space? backslash?
+//
+// TODO: The alignment checks are performed on the raw lines instead of
+//  the logical lines, since this check is about the visual appearance
+//  as opposed to the meaning of the variable assignment.
+//
+// FIXME: Implement each requirement from the above documentation.
+type VaralignBlock struct {
+       infos []*varalignLine
+       skip  bool
+
+       // When the indentation of the initial line of a multiline is
+       // changed, all its follow-up lines are shifted by the same
+       // amount and in the same direction. Typical examples are
+       // SUBST_SED, shell programs and AWK programs like in
+       // GENERATE_PLIST.
+       indentDiffSet bool
+       // The amount by which the follow-up lines are shifted.
+       // Positive values mean shifting to the right, negative values
+       // mean shifting to the left.
+       indentDiff int
+}
+
+type varalignLine struct {
+       mkline   *MkLine
+       rawIndex int
+
+       // Is true for multilines that don't have a value in their first
+       // physical line.
+       //
+       // The follow-up lines of these lines may be indented with as few
+       // as a single tab. Example:
+       //  VAR= \
+       //          value1 \
+       //          value2
+       // In all other lines, the indentation must be at least the indentation
+       // of the first value found.
+       multiEmpty bool
+
+       parts varalignSplitResult
+}
+
+type varalignSplitResult struct {
+       leadingComment    string // either the # or some rarely used U+0020 spaces
+       varnameOp         string // empty iff it is a follow-up line
+       spaceBeforeValue  string // for follow-up lines, this is the indentation
+       value             string
+       spaceAfterValue   string
+       trailingComment   string
+       spaceAfterComment string
+       continuation      string
+}
+
+func (va *VaralignBlock) Process(mkline *MkLine) {
+       switch {
+       case !G.Opts.WarnSpace:
+
+       case mkline.IsEmpty():
+               va.Finish()
+
+       case mkline.IsVarassignMaybeCommented():
+               va.processVarassign(mkline)
+
+       case mkline.IsComment(), mkline.IsDirective():
+
+       default:
+               trace.Stepf("Skipping varalign block because of line %s", &mkline.Location)
+               va.skip = true
+       }
+}
+
+func (va *VaralignBlock) processVarassign(mkline *MkLine) {
+       switch {
+       case mkline.Op() == opAssignEval && matches(mkline.Varname(), `^[a-z]`):
+               // Arguments to procedures do not take part in block alignment.
+               //
+               // Example:
+               // pkgpath := ${PKGPATH}
+               // .include "../../mk/pkg-build-options.mk"
+               return
+
+       case mkline.Value() == "" && mkline.VarassignComment() == "":
+               // Multiple-inclusion guards usually appear in a block of
+               // their own and therefore do not need alignment.
+               //
+               // Example:
+               // .if !defined(INCLUSION_GUARD_MK)
+               // INCLUSION_GUARD_MK:=
+               // # ...
+               // .endif
+               return
+       }
+
+       follow := false
+       for i, raw := range mkline.raw {
+               info := varalignLine{mkline, i, follow, va.split(raw.textnl, i == 0)}
+
+               if i == 0 && info.parts.value == "" && info.continuation() {
+                       follow = true
+                       info.multiEmpty = true
+               }
+
+               va.infos = append(va.infos, &info)
+       }
+}
+
+func (*VaralignBlock) split(textnl string, initial bool) varalignSplitResult {
+
+       // See MkLineParser.unescapeComment for very similar code.
+
+       p := NewMkParser(nil, textnl)
+       lexer := p.lexer
+
+       parseLeadingComment := func() string {
+               mark := lexer.Mark()
+
+               if !lexer.SkipByte('#') && initial && lexer.SkipByte(' ') {
+                       lexer.SkipHspace()
+               }
+
+               return lexer.Since(mark)
+       }
+
+       parseVarnameOp := func() string {
+               if !initial {
+                       return ""
+               }
+
+               mark := lexer.Mark()
+               _ = p.Varname()
+               lexer.SkipHspace()
+               ok, _ := p.Op()
+               assert(ok)
+               return lexer.Since(mark)
+       }
+
+       parseValue := func() (string, string) {
+               mark := lexer.Mark()
+
+               for !lexer.EOF() &&
+                       lexer.PeekByte() != '#' &&
+                       lexer.PeekByte() != '\n' &&
+                       !hasPrefix(lexer.Rest(), "\\\n") {
+
+                       if lexer.NextBytesSet(unescapeMkCommentSafeChars) != "" ||
+                               lexer.SkipString("[#") ||
+                               lexer.SkipByte('[') {
+                               continue
+                       }
+
+                       assert(lexer.SkipByte('\\'))
+                       if !lexer.EOF() {
+                               lexer.Skip(1)
+                       }
+               }
+
+               valueSpace := lexer.Since(mark)
+               value := rtrimHspace(valueSpace)
+               space := valueSpace[len(value):]
+               return value, space
+       }
+
+       parseComment := func() (string, string, string) {
+               rest := lexer.Rest()
+
+               newline := len(rest)
+               for newline > 0 && rest[newline-1] == '\n' {
+                       newline--
+               }
+
+               backslash := newline
+               for backslash > 0 && rest[backslash-1] == '\\' {
+                       backslash--
+               }
+
+               if (newline-backslash)%2 == 1 {
+                       continuation := rest[backslash:]
+                       commentSpace := rest[:backslash]
+                       comment := rtrimHspace(commentSpace)
+                       space := commentSpace[len(comment):]
+                       return comment, space, continuation
+               }
+
+               return rest[:newline], "", rest[newline:]
+       }
+
+       leadingComment := parseLeadingComment()
+       varnameOp := parseVarnameOp()
+       spaceBeforeValue := lexer.NextHspace()
+       value, spaceAfterValue := parseValue()
+       trailingComment, spaceAfterComment, continuation := parseComment()
+
+       return varalignSplitResult{
+               leadingComment,
+               varnameOp,
+               spaceBeforeValue,
+               value,
+               spaceAfterValue,
+               trailingComment,
+               spaceAfterComment,
+               continuation,
+       }
+}
+
+func (va *VaralignBlock) Finish() {
+       infos := va.infos
+       skip := va.skip
+       *va = VaralignBlock{} // overwrites infos and skip
+
+       if len(infos) == 0 || skip {
+               return
+       }
+
+       if trace.Tracing {
+               defer trace.Call(infos[0].mkline.Line)()
+       }
+
+       newWidth := va.optimalWidth(infos)
+
+       multiEmpty := false
+       for _, info := range infos {
+               if info.rawIndex == 0 {
+                       va.indentDiffSet = false
+                       va.indentDiff = 0
+                       multiEmpty = info.multiEmpty
+               }
+
+               if newWidth > 0 || multiEmpty && info.rawIndex > 0 {
+                       va.realign(info, newWidth)
+               }
+       }
+}
+
+// optimalWidth computes the desired screen width for the variable assignment
+// lines. If the paragraph is already indented consistently, it is kept as-is.
+//
+// There may be a single line sticking out from the others (called outlier).
+// This is to prevent a single SITES.* variable from forcing the rest of the
+// paragraph to be indented too far to the right.
+func (*VaralignBlock) optimalWidth(infos []*varalignLine) int {
+       longest := 0 // The longest seen varnameOpWidth
+       var longestLine *MkLine
+       secondLongest := 0 // The second-longest seen varnameOpWidth
+       for _, info := range infos {
+               if info.multiEmpty || info.rawIndex > 0 {
+                       continue
+               }
+
+               width := info.varnameOpWidth()
+               if width >= longest {
+                       secondLongest = longest
+                       longest = width
+                       longestLine = info.mkline
+               } else if width > secondLongest {
+                       secondLongest = width
+               }
+       }
+
+       haveOutlier := secondLongest != 0 &&
+               longest/8 >= secondLongest/8+2 &&
+               !longestLine.IsMultiline()
+
+       // Minimum required width of varnameOp, without the trailing whitespace.
+       minVarnameOpWidth := condInt(haveOutlier, secondLongest, longest)
+       outlier := condInt(haveOutlier, longest, 0)
+
+       // Widths of the current indentation (including whitespace)
+       minTotalWidth := 0
+       maxTotalWidth := 0
+       for _, info := range infos {
+               if info.multiEmpty || info.rawIndex > 0 || (outlier > 0 && info.varnameOpWidth() == outlier) {
+                       continue
+               }
+
+               width := info.varnameOpSpaceWidth()
+               if minTotalWidth == 0 || width < minTotalWidth {
+                       minTotalWidth = width
+               }
+               maxTotalWidth = imax(maxTotalWidth, width)
+       }
+
+       if trace.Tracing {
+               trace.Stepf("Indentation including whitespace is between %d and %d.",
+                       minTotalWidth, maxTotalWidth)
+               trace.Stepf("Minimum required indentation is %d + 1.", minVarnameOpWidth)
+               if outlier != 0 {
+                       trace.Stepf("The outlier is at indentation %d.", outlier)
+               }
+       }
+
+       if minTotalWidth > minVarnameOpWidth && minTotalWidth == maxTotalWidth && minTotalWidth%8 == 0 {
+               // The whole paragraph is already indented to the same width.
+               return minTotalWidth
+       }
+
+       if minVarnameOpWidth == 0 {
+               // Only continuation lines in this paragraph.
+               return 0
+       }
+
+       return (minVarnameOpWidth & -8) + 8
+}
+
+func (va *VaralignBlock) realign(info *varalignLine, newWidth int) {
+       if info.multiEmpty {
+               if info.rawIndex == 0 {
+                       va.realignMultiEmptyInitial(info, newWidth)
+               } else {
+                       va.realignMultiEmptyFollow(info, newWidth)
+               }
+       } else if info.rawIndex == 0 && info.continuation() {
+               va.realignMultiInitial(info, newWidth)
+       } else if info.rawIndex > 0 {
+               va.realignMultiFollow(info, newWidth)
+       } else {
+               va.realignSingle(info, newWidth)
+       }
+}
+
+func (va *VaralignBlock) realignMultiEmptyInitial(info *varalignLine, newWidth int) {
+       leadingComment := info.parts.leadingComment
+       varnameOp := info.parts.varnameOp
+       oldSpace := info.parts.spaceBeforeValue
+
+       // Indent the outlier and any other lines that stick out
+       // with a space instead of a tab to keep the line short.
+       newSpace := " "
+       if info.varnameOpSpaceWidth() <= newWidth {
+               newSpace = alignmentAfter(leadingComment+varnameOp, newWidth)
+       }
+
+       if newSpace == oldSpace {
+               return
+       }
+
+       if newSpace == " " && oldSpace != "" && oldSpace == strings.Repeat("\t", len(oldSpace)) {
+               return
+       }
+
+       hasSpace := strings.IndexByte(oldSpace, ' ') != -1
+       oldColumn := tabWidth(leadingComment + varnameOp + oldSpace)
+       column := tabWidth(leadingComment + varnameOp + newSpace)
+
+       assert(column >= oldColumn || column > info.varnameOpWidth())
+
+       // TODO: explicitly mention "single space", "tabs to the newWidth", "tabs to column 72"
+
+       fix := info.mkline.Autofix()
+       if hasSpace && column != oldColumn {
+               fix.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", column+1)
+       } else if column != oldColumn {
+               fix.Notef("This variable value should be aligned to column %d.", column+1)
+       } else {
+               fix.Notef("Variable values should be aligned with tabs, not spaces.")
+       }
+       fix.ReplaceAt(info.rawIndex, info.spaceBeforeValueIndex(), oldSpace, newSpace)
+       fix.Apply()
+}
+
+func (va *VaralignBlock) realignMultiEmptyFollow(info *varalignLine, newWidth int) {
+       oldSpace := info.parts.spaceBeforeValue
+       oldWidth := tabWidth(oldSpace)
+
+       if !va.indentDiffSet {
+               va.indentDiffSet = true
+               va.indentDiff = condInt(newWidth != 0, newWidth-oldWidth, 0)
+               if va.indentDiff > 0 && !info.commentedOut() {
+                       va.indentDiff = 0
+               }
+       }
+
+       newWidth = oldWidth + va.indentDiff
+       if newWidth < 8 {
+               newWidth = oldWidth & -8
+               if newWidth < 8 {
+                       newWidth = 8
+               }
+       }
+
+       newSpace := indent(newWidth)
+       if newSpace == oldSpace {
+               return
+       }
+
+       // Below a continuation marker, there may be a completely empty line.
+       // This is confusing to the human readers, but technically allowed.
+       if info.parts.value == "" && info.parts.trailingComment == "" && !info.continuation() {
+               return
+       }
+
+       fix := info.mkline.Autofix()
+       fix.Notef("This continuation line should be indented with %q.", newSpace)
+       fix.ReplaceAt(info.rawIndex, info.spaceBeforeValueIndex(), oldSpace, newSpace)
+       fix.Apply()
+}
+
+func (va *VaralignBlock) realignMultiInitial(info *varalignLine, newWidth int) {
+       leadingComment := info.parts.leadingComment
+       varnameOp := info.parts.varnameOp
+       oldSpace := info.parts.spaceBeforeValue
+
+       va.indentDiffSet = true
+       oldWidth := info.varnameOpSpaceWidth()
+       va.indentDiff = newWidth - oldWidth
+
+       newSpace := alignmentAfter(leadingComment+varnameOp, newWidth)
+       if newSpace == oldSpace {
+               return
+       }
+
+       hasSpace := strings.IndexByte(oldSpace, ' ') != -1
+       width := tabWidth(leadingComment + varnameOp + newSpace)
+
+       fix := info.mkline.Autofix()
+       if hasSpace && width != oldWidth {
+               fix.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", width+1)
+       } else if width != oldWidth {
+               fix.Notef("This variable value should be aligned to column %d.", width+1)
+       } else {
+               fix.Notef("Variable values should be aligned with tabs, not spaces.")
+       }
+       fix.ReplaceAt(info.rawIndex, info.spaceBeforeValueIndex(), oldSpace, newSpace)
+       fix.Apply()
+}
+
+func (va *VaralignBlock) realignMultiFollow(info *varalignLine, newWidth int) {
+       assert(va.indentDiffSet)
+
+       oldSpace := info.parts.spaceBeforeValue
+       newSpace := indent(tabWidth(oldSpace) + va.indentDiff)
+       if tabWidth(newSpace) < newWidth {
+               newSpace = indent(newWidth)
+       }
+       if newSpace == oldSpace || oldSpace == "\t" {
+               return
+       }
+
+       fix := info.mkline.Autofix()
+       fix.Notef("This continuation line should be indented with %q.", indent(newWidth))
+       fix.ReplaceAt(info.rawIndex, info.spaceBeforeValueIndex(), oldSpace, newSpace)
+       fix.Apply()
+}
+
+func (va *VaralignBlock) realignSingle(info *varalignLine, newWidth int) {
+       assert(!va.indentDiffSet)
+
+       leadingComment := info.parts.leadingComment
+       varnameOp := info.parts.varnameOp
+       oldSpace := info.parts.spaceBeforeValue
+
+       newSpace := ""
+       for tabWidth(leadingComment+varnameOp+newSpace) < newWidth {
+               newSpace += "\t"
+       }
+
+       // Indent the outlier with a space instead of a tab to keep the line short.
+       if newSpace == "" && info.canonicalInitial(newWidth) {
+               return
+       }
+       if newSpace == "" {
+               newSpace = " "
+       }
+
+       if newSpace == oldSpace {
+               return
+       }
+
+       hasSpace := strings.IndexByte(oldSpace, ' ') != -1
+       oldColumn := tabWidth(leadingComment + varnameOp + oldSpace)
+       column := tabWidth(leadingComment + varnameOp + newSpace)
+
+       if info.parts.value == "" && info.parts.trailingComment == "" && !info.continuation() {
+               return
+       }
+
+       fix := info.mkline.Autofix()
+       if hasSpace && column != oldColumn {
+               fix.Notef("This variable value should be aligned with tabs, not spaces, to column %d.", column+1)
+               va.explainWrongColumn(fix)
+       } else if column != oldColumn {
+               fix.Notef("This variable value should be aligned to column %d.", column+1)
+               va.explainWrongColumn(fix)
+       } else {
+               fix.Notef("Variable values should be aligned with tabs, not spaces.")
+       }
+       fix.ReplaceAt(info.rawIndex, info.spaceBeforeValueIndex(), oldSpace, newSpace)
+       fix.Apply()
+}
+
+func (va *VaralignBlock) explainWrongColumn(fix *Autofix) {
+       fix.Explain(
+               "Normally, all variable values in a block should start at the same column.",
+               "This provides orientation, especially for sequences",
+               "of variables that often appear in the same order.",
+               "For these it suffices to look at the variable values only.",
+               "",
+               "There are some exceptions to this rule:",
+               "",
+               "Definitions for long variable names may be indented with a single space instead of tabs,",
+               "but only if they appear in a block that is otherwise indented with tabs.",
+               "",
+               "Variable definitions that span multiple lines are not checked for alignment at all.",
+               "",
+               "When the block contains something else than variable definitions",
+               "and directives like .if or .for, it is not checked at all.")
+}
+
+func (l *varalignLine) varnameOpWidth() int {
+       return tabWidth(l.parts.leadingComment + l.parts.varnameOp)
+}
+
+func (l *varalignLine) varnameOpSpaceWidth() int {
+       return tabWidth(l.parts.leadingComment + l.parts.varnameOp + l.parts.spaceBeforeValue)
+}
+
+// spaceBeforeValueIndex returns the string index at which the space before the value starts.
+// It's the same as the end of the assignment operator. Example:
+//  #VAR=   value
+// The index is 5.
+func (l *varalignLine) spaceBeforeValueIndex() int {
+       return len(l.parts.leadingComment) + len(l.parts.varnameOp)
+}
+
+// continuation returns whether this line ends with a backslash.
+func (l *varalignLine) continuation() bool {
+       return hasPrefix(l.parts.continuation, "\\")
+}
+
+func (l *varalignLine) commentedOut() bool {
+       return hasPrefix(l.parts.leadingComment, "#")
+}
+
+func (l *varalignLine) outlier(width int) bool {
+       assert(width == width&-8)
+       return l.varnameOpSpaceWidth() > width+8
+}
+
+// canonicalInitial returns whether the space between the assignment
+// operator and the value has its canonical form, which is either
+// at least one tab, or a single space, but only for lines that stick out.
+func (l *varalignLine) canonicalInitial(width int) bool {
+       space := l.parts.spaceBeforeValue
+       if space == "" {
+               return false
+       }
+
+       if space == " " && l.varnameOpSpaceWidth() > width {
+               return true
+       }
+
+       return strings.TrimLeft(space, "\t") == ""
+}
+
+// canonicalFollow returns whether the space before the value has its
+// canonical form, which is at least one tab, followed by up to 7 spaces.
+func (l *varalignLine) canonicalFollow() bool {
+       lexer := textproc.NewLexer(l.parts.spaceBeforeValue)
+
+       tabs := 0
+       for lexer.SkipByte('\t') {
+               tabs++
+       }
+
+       spaces := 0
+       for lexer.SkipByte(' ') {
+               spaces++
+       }
+
+       return tabs >= 1 && spaces <= 7
+}
Index: pkgsrc/pkgtools/pkglint/files/varalignblock_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.1
--- /dev/null   Sun Jul 14 21:25:48 2019
+++ pkgsrc/pkgtools/pkglint/files/varalignblock_test.go Sun Jul 14 21:25:47 2019
@@ -0,0 +1,2566 @@
+package pkglint
+
+import "gopkg.in/check.v1"
+
+// VaralignTester reduces the amount of test code for aligning variable
+// assignments in Makefiles.
+//
+// The most interesting breakpoint for looking at these tests is
+// VaralignBlock.optimalWidth.
+type VaralignTester struct {
+       suite       *Suite
+       tester      *Tester
+       input       []string // The actual input lines
+       internals   []string // The expected internal state, the varalignBlockInfos
+       diagnostics []string // The expected diagnostics in default mode
+       autofixes   []string // The expected diagnostics in --autofix mode
+       fixed       []string // The expected fixed lines, with spaces instead of tabs
+       ShowSource  bool     // The --show-source command line option
+}
+
+func NewVaralignTester(s *Suite, c *check.C) *VaralignTester {
+       t := s.Init(c)
+
+       return &VaralignTester{suite: s, tester: t}
+}
+
+// Input remembers the input lines that are checked and possibly realigned.
+func (vt *VaralignTester) Input(lines ...string) { vt.input = lines }
+
+// Internals remembers the expected internal state of the varalignBlockInfos,
+// to better trace down at which points the decisions are made.
+func (vt *VaralignTester) Internals(lines ...string) { vt.internals = lines }
+
+// Diagnostics remembers the expected diagnostics.
+func (vt *VaralignTester) Diagnostics(diagnostics ...string) { vt.diagnostics = diagnostics }
+
+// Autofixes remembers the expected diagnostics when pkglint is
+// run with the --autofix option.
+func (vt *VaralignTester) Autofixes(autofixes ...string) { vt.autofixes = autofixes }
+
+// Fixed remembers the expected fixed lines. To make the layout changes
+// clearly visible, the lines given here use spaces instead of tabs.
+// The fixed lines that have been written to the file are still using tabs.
+func (vt *VaralignTester) Fixed(lines ...string) { vt.fixed = lines }
+
+// Run is called after setting up the data and runs the varalign checks twice.
+// Once for getting the diagnostics and once for automatically fixing them.
+func (vt *VaralignTester) Run() {
+       vt.run(false)
+       vt.run(true)
+}
+
+func (vt *VaralignTester) run(autofix bool) {
+       t := vt.tester
+
+       cmdline := []string{"-Wall"}
+       if autofix {
+               cmdline = append(cmdline, "--autofix")
+       }
+       if vt.ShowSource {
+               cmdline = append(cmdline, "--source")
+       }
+       t.SetUpCommandLine(cmdline...)
+
+       mklines := t.SetUpFileMkLines("Makefile", vt.input...)
+
+       var varalign VaralignBlock
+       for _, mkline := range mklines.mklines {
+               // This standard test only covers a single paragraph.
+               // Testing multiple paragraphs is done as a side-effect
+               // by the various other pkglint tests.
+               assert(!mkline.IsEmpty())
+
+               varalign.Process(mkline)
+       }
+       infos := varalign.infos // since they are overwritten by Finish
+       varalign.Finish()
+
+       var actual []string
+       for _, info := range infos {
+               minWidth := condStr(info.rawIndex == 0, sprintf("%02d", info.varnameOpWidth()), "  ")
+               infoStr := sprintf("%s %02d", minWidth, info.varnameOpSpaceWidth())
+               actual = append(actual, infoStr)
+       }
+       t.CheckDeepEquals(actual, vt.internals)
+
+       if autofix {
+               t.CheckOutput(vt.autofixes)
+
+               SaveAutofixChanges(mklines.lines)
+               t.CheckFileLinesDetab("Makefile", vt.fixed...)
+       } else {
+               t.CheckOutput(vt.diagnostics)
+       }
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_simple_none(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=value")
+       vt.Internals(
+               "20 20")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_simple_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= value")
+       vt.Internals(
+               "20 21")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_simple_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue")
+       vt.Internals(
+               "20 24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_simple_sss(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=   value")
+       vt.Internals(
+               "20 23")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"   \" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_simple_ttt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\t\t\tvalue")
+       vt.Internals(
+               "20 40")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=                    value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_simple_tsts(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\t \t value")
+       vt.Internals(
+               "20 33")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t \\t \" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_none(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\\",
+               "\tvalue")
+       vt.Internals(
+               "20 20",
+               "   08")
+       vt.Diagnostics(
+               // TODO: There should be a space to the left of the backslash.
+               nil...)
+       vt.Autofixes(
+               nil...)
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=\\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "\tvalue")
+       vt.Internals(
+               "20 21",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\t\\",
+               "\tvalue")
+       vt.Internals(
+               "20 24",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_sss(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=   \\",
+               "\tvalue")
+       vt.Internals(
+               "20 23",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=   \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_ttt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\t\t\t\\",
+               "\tvalue")
+       vt.Internals(
+               "20 40",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=                    \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_tab72(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\t\t\t\t\t\t\t\\",
+               "\tvalue")
+       vt.Internals(
+               "20 72",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=                                                    \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_none(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "value")
+       vt.Internals(
+               "20 21",
+               "   00")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               " value")
+       vt.Internals(
+               "20 21",
+               "   01")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "\tvalue")
+       vt.Internals(
+               "20 21",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_sss(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "   value")
+       vt.Internals(
+               "20 21",
+               "   03")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"   \" with \"\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_tt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "\t\tvalue")
+       vt.Internals(
+               "20 21",
+               "   16")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "                value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_ttt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "\t\t\tvalue")
+       vt.Internals(
+               "20 21",
+               "   24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_follow_indent_tsts(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "\t \t value")
+       vt.Internals(
+               "20 21",
+               "   17")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t \".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t \\t \" with \"\\t\\t \".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV= \\",
+               "                 value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_none(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=value \\",
+               "\t\t    value")
+       vt.Internals(
+               "20 20",
+               "   20")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 25.",
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\\t    \" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV= value \\",
+               "                     value")
+       vt.Internals(
+               "20 21",
+               "   21")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"                     \" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\t\t\tvalue")
+       vt.Internals(
+               "20 24",
+               "   24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_sss(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=   value \\",
+               "                       value")
+       vt.Internals(
+               "20 23",
+               "   23")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"   \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"                       \" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_ttt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\t\t\tvalue \\",
+               "\t\t\t\t\tvalue")
+       vt.Internals(
+               "20 40",
+               "   40")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=                    value \\",
+               "                                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_tab64(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue\t\t\t\t\t\\",
+               "\t\t\tvalue")
+       vt.Internals(
+               "20 24",
+               "   24")
+       vt.Diagnostics(
+               // FIXME: backslash indentation must be space, tab or at column 73.
+               nil...)
+       vt.Autofixes(
+               // FIXME: replace many tabs with a single space, since there are
+               //  no more backslashes in this logical line.
+               nil...)
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value                                   \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_tab72(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue\t\t\t\t\t\t\\",
+               "\t\t\tvalue")
+       vt.Internals(
+               "20 24",
+               "   24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value                                           \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_none(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "value")
+       vt.Internals(
+               "20 24",
+               "   00")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               " value")
+       vt.Internals(
+               "20 24",
+               "   01")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\tvalue")
+       vt.Internals(
+               "20 24",
+               "   08")
+       vt.Diagnostics(
+               nil...)
+       vt.Autofixes(
+               nil...)
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_sss(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "   value")
+       vt.Internals(
+               "20 24",
+               "   03")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"   \" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_tt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\t\tvalue")
+       vt.Internals(
+               "20 24",
+               "   16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\\t\" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_ttt(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\t\t\tvalue")
+       vt.Internals(
+               "20 24",
+               "   24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_tsts(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\t \t value")
+       vt.Internals(
+               "20 24",
+               "   17")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t \\t \" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                        value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_plus_sss(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\t\t\t   value")
+       vt.Internals(
+               "20 24",
+               "   27")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                           value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__one_line_initial_indent_plus_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VVVVVVVVVVVVVVVVVVV=\tvalue \\",
+               "\t\t\t\tvalue")
+       vt.Internals(
+               "20 24",
+               "   32")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VVVVVVVVVVVVVVVVVVV=    value \\",
+               "                                value")
+       vt.Run()
+}
+
+// Commented lines are visually equivalent to uncommented lines.
+// The alignment algorithm must treat them the same. The only difference
+// is when follow-up lines start with a comment. This comment character
+// precedes the indentation, but at the same time it is part of its width.
+// Since the follow-up lines in their canonical form are always indented
+// using tabs, the single comment character doesn't change the width.
+
+// TODO: add systematic tests for commented lines
+
+// Generally, the value in variable assignments is aligned
+// at the next tab.
+func (s *Suite) Test_VaralignBlock__one_var_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR=\tone tab")
+       vt.Internals(
+               "04 08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VAR=    one tab")
+       vt.Run()
+}
+
+// Having more tabs than necessary is allowed. This can be for aesthetic
+// reasons to align this paragraph with the others in the same file.
+func (s *Suite) Test_VaralignBlock__one_var_tabs(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR=\t\t\tseveral tabs")
+       vt.Internals(
+               "04 24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VAR=                    several tabs")
+       vt.Run()
+}
+
+// Indentations with a single space are only allowed in some very few
+// places, such as continuation lines or very long variable names.
+// In a single paragraph of its own, indentation with a single space
+// doesn't make sense, therefore it is replaced with a tab.
+func (s *Suite) Test_VaralignBlock__one_var_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR= indented with one space")
+       vt.Internals(
+               "04 05")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 9.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "VAR=    indented with one space")
+       vt.Run()
+}
+
+// While indentation with a single space is allowed in a few cases,
+// indentation with several spaces is never allowed and is replaced
+// with tabs.
+func (s *Suite) Test_VaralignBlock__one_var_spaces(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR=   several spaces")
+       vt.Internals(
+               "04 07")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 9.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"   \" with \"\\t\".")
+       vt.Fixed(
+               "VAR=    several spaces")
+       vt.Run()
+}
+
+// Inconsistently aligned lines for variables of the same length are
+// replaced with tabs, so that they align nicely.
+func (s *Suite) Test_VaralignBlock__two_vars__spaces(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR= indented with one space",
+               "VAR=  indented with two spaces")
+       vt.Internals(
+               "04 05",
+               "04 06")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 9.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 9.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"  \" with \"\\t\".")
+       vt.Fixed(
+               "VAR=    indented with one space",
+               "VAR=    indented with two spaces")
+       vt.Run()
+}
+
+// All variables in a block are aligned to the same depth.
+func (s *Suite) Test_VaralignBlock__several_vars__spaces(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "GRP_A= value",
+               "GRP_AA= value",
+               "GRP_AAA= value",
+               "GRP_AAAA= value")
+       vt.Internals(
+               "06 07",
+               "07 08",
+               "08 09",
+               "09 10")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:4: This variable value should be aligned with tabs, not spaces, to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "GRP_A=          value",
+               "GRP_AA=         value",
+               "GRP_AAA=        value",
+               "GRP_AAAA=       value")
+       vt.Run()
+}
+
+// Lines that are continued may be indented with a single space
+// if the first line of the variable definition has no value.
+func (s *Suite) Test_VaralignBlock__continuation(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR= \\",
+               "\tvalue")
+       vt.Internals(
+               "04 05",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VAR= \\",
+               "        value")
+       vt.Run()
+}
+
+// To align these two lines, the first line needs one more tab.
+// The second line is further to the right but doesn't count as
+// an outlier since it is not far enough.
+// Adding one more tab to the indentation is generally considered ok.
+func (s *Suite) Test_VaralignBlock__short_tab__long_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "BLOCK=\tindented with tab",
+               "BLOCK_LONGVAR= indented with space")
+       vt.Internals(
+               "06 08",
+               "14 15")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "BLOCK=          indented with tab",
+               "BLOCK_LONGVAR=  indented with space")
+       vt.Run()
+}
+
+// When the indentation differs, the indentation is adjusted to the
+// minimum necessary.
+func (s *Suite) Test_VaralignBlock__short_long__tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "BLOCK=\tshort",
+               "BLOCK_LONGVAR=\t\t\t\tlong")
+       vt.Internals(
+               "06 08",
+               "14 40")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\\t\\t\\t\" with \"\\t\".")
+       vt.Fixed(
+               "BLOCK=          short",
+               "BLOCK_LONGVAR=  long")
+       vt.Run()
+}
+
+// For differing indentation, it doesn't matter whether the indentation
+// is done with tabs or with spaces. It is aligned to the minimum
+// necessary depth.
+func (s *Suite) Test_VaralignBlock__space_and_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR=    space",
+               "VAR=\ttab ${VAR}")
+       vt.Internals(
+               "04 08",
+               "04 08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: Variable values should be aligned with tabs, not spaces.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"    \" with \"\\t\".")
+       vt.Fixed(
+               "VAR=    space",
+               "VAR=    tab ${VAR}")
+       vt.Run()
+}
+
+// There must always be a visible space between the assignment operator
+// and the value.
+func (s *Suite) Test_VaralignBlock__no_space_at_all(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "PKG_FAIL_REASON+=\"Message\"")
+       vt.Internals(
+               "17 17")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\" with \"\\t\".")
+       vt.Fixed(
+               "PKG_FAIL_REASON+=       \"Message\"")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__empty_continuation_in_column_1(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR= \\",
+               "no indentation")
+       vt.Internals(
+               "04 05",
+               "   00")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\" with \"\\t\".")
+       vt.Fixed(
+               "VAR= \\",
+               "        no indentation")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__empty_continuation_in_column_9(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR= \\",
+               "\tminimum indentation")
+       vt.Internals(
+               "04 05",
+               "   08")
+       vt.Diagnostics(
+               nil...)
+       vt.Autofixes(
+               nil...)
+       vt.Fixed(
+               "VAR= \\",
+               "        minimum indentation")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__empty_continuation_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "REF=\tvalue",
+               "VAR= \\",
+               "\tminimum indentation")
+       vt.Internals(
+               "04 08",
+               "04 05",
+               "   08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 9.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "REF=    value",
+               "VAR=    \\",
+               "        minimum indentation")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__empty_continuation_properly_indented(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "REF=\tvalue",
+               "VAR=\t\\",
+               "\tminimum indentation")
+       vt.Internals(
+               "04 08",
+               "04 08",
+               "   08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "REF=    value",
+               "VAR=    \\",
+               "        minimum indentation")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__empty_continuation_too_narrow(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "LONG_VARIABLE=\tvalue",
+               "VAR=\t\\",
+               "\tminimum indentation")
+       vt.Internals(
+               "14 16",
+               "04 08",
+               "   08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "LONG_VARIABLE=  value",
+               "VAR=            \\",
+               // TODO: This continuation line looks misplaced since there
+               //  is plenty of space to the right.
+               "        minimum indentation")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__empty_continuation_too_wide(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "LONG_VARIABLE=\tvalue",
+               "REALLY_LONG_VARIABLE=\t\\",
+               "\tminimum indentation")
+       vt.Internals(
+               "14 16",
+               "21 24",
+               "   08")
+       vt.Diagnostics(
+               nil...)
+       vt.Autofixes()
+       vt.Fixed(
+               "LONG_VARIABLE=  value",
+               "REALLY_LONG_VARIABLE=   \\",
+               "        minimum indentation")
+       vt.Run()
+}
+
+// Line 1 is currently indented to column 25.
+// Line 2 is a continuation line with a very long variable name.
+// Line 2 is indented to column 38, which is much larger than 25.
+// Therefore line 2 is the outlier in this paragraph.
+// The initial line of the continuation line is empty.
+// It only contains a backslash, without the usual space to the left.
+// This space should be inserted to the left of the backslash.
+// Everything else is fine.
+func (s *Suite) Test_VaralignBlock__outlier_in_follow_continuation(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "PATCHFILES+=\t\temacs20-jumbo-patch-20170723.gz",
+               "SITES.emacs20-jumbo-patch-20170723.gz=\\",
+               "\t\t\thttp://www.NetBSD.org/~dholland/patchkits/emacs20/";)
+       vt.Internals(
+               "12 24",
+               "38 38",
+               "   24")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 40.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \"\" with \" \".")
+       vt.Fixed(
+               "PATCHFILES+=            emacs20-jumbo-patch-20170723.gz",
+               "SITES.emacs20-jumbo-patch-20170723.gz= \\",
+               "                        http://www.NetBSD.org/~dholland/patchkits/emacs20/";)
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__continuation_lines(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "DISTFILES+=\tvalue",
+               "DISTFILES+= \\", // The continuation backslash must be aligned.
+               "\t\t\tvalue",    // The value is aligned deeper than necessary.
+               "DISTFILES+=\t\t\tvalue",
+               "DISTFILES+= value")
+       vt.Internals(
+               "11 16",
+               "11 12",
+               "   24",
+               "11 32",
+               "11 12")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:3: This continuation line should be indented with \"\\t\\t\".",
+               "NOTE: ~/Makefile:4: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:5: This variable value should be aligned with tabs, not spaces, to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \"\\t\\t\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \"\\t\\t\\t\" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:5: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "DISTFILES+=     value",
+               "DISTFILES+=     \\",
+               "                value",
+               "DISTFILES+=     value",
+               "DISTFILES+=     value")
+       vt.Run()
+}
+
+// Line 1 is currently aligned at column 17. It could be shortened to column 9.
+//
+// Line 2--3 consists of multiple raw lines. The first of the raw lines
+// contains a value, therefore it doesn't count as a pure continuation line
+// for the purpose of realigning the paragraph. Pure continuation lines would
+// be skipped completely; this line still takes place in realigning.
+//
+// Line 2 needs an indentation of at least 24. This is more than one tab away
+// from the minimum required indentation of line 1, which is at column 9.
+// By this reasoning, line 2--3 would be an outlier.
+//
+// In line 2--3, the first line and the continuation are aligned in the same
+// column. Their relative indentation is 0, and that should be kept as-is.
+// This one logical line looks like two separate lines, and because their
+// indentation is the same, this logical line doesn't count as an outlier.
+//
+// Because line 2--3 is not an outlier, line 1 is realigned to column 25.
+func (s *Suite) Test_VaralignBlock__continuation_line_one_tab_ahead(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR=\t\tvalue",
+               "MASTER_SITE_NEDIT=\thttps://example.org \\",
+               "\t\t\thttps://example.org";)
+       vt.Internals(
+               "04 16",
+               "18 24",
+               "   24")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\\t\" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "VAR=                    value",
+               "MASTER_SITE_NEDIT=      https://example.org \\",
+               "                        https://example.org";)
+       vt.Run()
+}
+
+// As of June 2019, the long variable name doesn't count as an outlier
+// because it only needs one more tab than the second-longest variable.
+// This contradicts the visual impression, in which the variable names
+// differ largely in their length.
+//
+// As soon as the V2 value would be properly indented with a tab, the
+// visual difference would not be as much, therefore the current
+// behavior is appropriate.
+func (s *Suite) Test_VaralignBlock__outlier_more_than_8_spaces(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V2=\tvalue",
+               "V0000000000014=\tvalue")
+       vt.Internals(
+               "03 08",
+               "15 16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "V2=             value",
+               "V0000000000014= value")
+       vt.Run()
+}
+
+// Ensures that a wrong warning introduced in ccb56a5 is not logged.
+// The warning was about continuation lines that should be reindented.
+// In this case though, everything is already perfectly aligned.
+func (s *Suite) Test_VaralignBlock__aligned_continuation(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "USE_TOOLS+=\t[ awk \\",
+               "\t\tsed")
+       vt.Internals(
+               "11 16",
+               "   16")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "USE_TOOLS+=     [ awk \\",
+               "                sed")
+       vt.Run()
+}
+
+// Shell commands in continuation lines are assumed to be already nicely indented.
+// This particular example is not, but pkglint cannot decide this as of
+// version 5.7.14 (July 2019).
+func (s *Suite) Test_VaralignBlock__shell_command(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "USE_BUILTIN.Xfixes=\tyes",
+               "USE_BUILTIN.Xfixes!=\t\t\t\t\t\t\t\\",
+               "\tif ${PKG_ADMIN} pmatch ...; then\t\t\t\t\\",
+               "\t\t:; else :; fi")
+       vt.Internals(
+               "19 24",
+               "20 72",
+               "   08",
+               "   16")
+       vt.Diagnostics(
+               nil...)
+       vt.Autofixes(
+               nil...)
+       vt.Fixed(
+               "USE_BUILTIN.Xfixes=     yes",
+               "USE_BUILTIN.Xfixes!=                                                    \\",
+               "        if ${PKG_ADMIN} pmatch ...; then                                \\",
+               "                :; else :; fi")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__escaped_varname(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.${v:S,\\#,,g}=\tvalue",
+               "V2345678123456781234=\tvalue")
+       vt.Internals(
+               "15 16", // 15, since the number sign is not escaped when computing the indentation
+               "21 24")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "V.${v:S,\\#,,g}=         value", // looks misaligned because of the backslash
+               "V2345678123456781234=   value")
+       vt.Run()
+}
+
+// The most common pattern for laying out continuation lines is to have all
+// values in the continuation lines, one value per line, all indented to the same depth.
+// The depth is either a single tab (see the test below) or aligns with the other
+// variables in the paragraph (this test).
+func (s *Suite) Test_VaralignBlock__continuation_value_starts_in_second_line(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "WRKSRC=\t${WRKDIR}",
+               "DISTFILES=\tdistfile-1.0.0.tar.gz",
+               "SITES.distfile-1.0.0.tar.gz= \\",
+               "\t\t\t${MASTER_SITES_SOURCEFORGE} \\",
+               "\t\t\t${MASTER_SITES_GITHUB}")
+       vt.Internals(
+               "07 08",
+               "10 16",
+               "28 29",
+               "   24",
+               "   24")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:4: This continuation line should be indented with \"\\t\\t\".",
+               "NOTE: ~/Makefile:5: This continuation line should be indented with \"\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \"\\t\\t\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:5: Replacing \"\\t\\t\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "WRKSRC=         ${WRKDIR}",
+               "DISTFILES=      distfile-1.0.0.tar.gz",
+               "SITES.distfile-1.0.0.tar.gz= \\",
+               "                ${MASTER_SITES_SOURCEFORGE} \\",
+               "                ${MASTER_SITES_GITHUB}")
+       vt.Run()
+}
+
+// The most common pattern for laying out continuation lines is to have all
+// values in the continuation lines, one value per line, all indented to the same depth.
+// The depth is either a single tab (this test) or aligns with the other
+// variables in the paragraph (see the test above).
+func (s *Suite) Test_VaralignBlock__continuation_value_starts_in_second_line_with_single_tab(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "WRKSRC=\t${WRKDIR}",
+               "DISTFILES=\tdistfile-1.0.0.tar.gz",
+               "SITES.distfile-1.0.0.tar.gz= \\",
+               "\t${MASTER_SITES_SOURCEFORGE} \\",
+               "\t${MASTER_SITES_GITHUB}")
+       vt.Internals(
+               "07 08",
+               "10 16",
+               "28 29",
+               "   08",
+               "   08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "WRKSRC=         ${WRKDIR}",
+               "DISTFILES=      distfile-1.0.0.tar.gz",
+               "SITES.distfile-1.0.0.tar.gz= \\",
+               "        ${MASTER_SITES_SOURCEFORGE} \\",
+               "        ${MASTER_SITES_GITHUB}")
+       vt.Run()
+}
+
+// Another common pattern is to write the first value in the first line and
+// subsequent values indented to the same depth as the value in the first
+// line.
+//
+// If the SITES line had only a single line containing a value, it would count
+// as an outlier. But like this, the two variable expressions look massive
+// enough so that the other variables should be aligned to them.
+//
+// The whole paragraph could be indented less by making the SITES line a
+// pure continuation line, having only a backslash in its first line. Then
+// everything could be aligned to column 17.
+func (s *Suite) Test_VaralignBlock__continuation_value_starts_in_first_line(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "WRKSRC=\t${WRKDIR}",
+               "DISTFILES=\tdistfile-1.0.0.tar.gz",
+               "SITES.distfile-1.0.0.tar.gz=\t${MASTER_SITES_SOURCEFORGE} \\",
+               "\t\t\t\t${MASTER_SITES_GITHUB}")
+       vt.Internals(
+               "07 08",
+               "10 16",
+               "28 32",
+               "   32")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 33.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 33.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\\t\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\" with \"\\t\\t\\t\".")
+       vt.Fixed(
+               "WRKSRC=                         ${WRKDIR}",
+               "DISTFILES=                      distfile-1.0.0.tar.gz",
+               "SITES.distfile-1.0.0.tar.gz=    ${MASTER_SITES_SOURCEFORGE} \\",
+               "                                ${MASTER_SITES_GITHUB}")
+       vt.Run()
+}
+
+// Continued lines that have mixed indentation are probably on purpose.
+// Their minimum indentation should be aligned to the indentation of the
+// other lines. The lines that are indented further should keep their
+// relative indentation depth, no matter if that is done with spaces or
+// with tabs.
+func (s *Suite) Test_VaralignBlock__continuation_mixed_indentation_in_second_line(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "WRKSRC=\t${WRKDIR}",
+               "DISTFILES=\tdistfile-1.0.0.tar.gz",
+               "AWK_PROGRAM+= \\",
+               "\t\t\t\t  /search/ { \\",
+               "\t\t\t\t    action(); \\",
+               "\t\t\t\t  }")
+       vt.Internals(
+               "07 08",
+               "10 16",
+               "13 14",
+               "   34",
+               "   36",
+               "   34")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:4: This continuation line should be indented with \"\\t\\t\".",
+               "NOTE: ~/Makefile:5: This continuation line should be indented with \"\\t\\t  \".",
+               "NOTE: ~/Makefile:6: This continuation line should be indented with \"\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \"\\t\\t\\t\\t  \" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:5: Replacing \"\\t\\t\\t\\t    \" with \"\\t\\t  \".",
+               "AUTOFIX: ~/Makefile:6: Replacing \"\\t\\t\\t\\t  \" with \"\\t\\t\".")
+       vt.Fixed(
+               "WRKSRC=         ${WRKDIR}",
+               "DISTFILES=      distfile-1.0.0.tar.gz",
+               "AWK_PROGRAM+=   \\",
+               "                /search/ { \\",
+               "                  action(); \\",
+               "                }")
+       vt.Run()
+}
+
+// Continuation lines may also start their values in the first line.
+//
+// The indentation of the continuation line is adjusted, preserving
+// the relative indentation among its raw lines.
+func (s *Suite) Test_VaralignBlock__continuation_mixed_indentation_in_first_line(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "WRKSRC=\t${WRKDIR}",
+               "DISTFILES=\tdistfile-1.0.0.tar.gz",
+               "AWK_PROGRAM+=\t\t\t  /search/ { \\",
+               "\t\t\t\t    action(); \\",
+               "\t\t\t\t  }")
+       vt.Internals(
+               "07 08",
+               "10 16",
+               "13 34",
+               "   36",
+               "   34")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:4: This continuation line should be indented with \"\\t\\t\".",
+               "NOTE: ~/Makefile:5: This continuation line should be indented with \"\\t\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \"\\t\\t\\t  \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \"\\t\\t\\t\\t    \" with \"\\t\\t  \".",
+               "AUTOFIX: ~/Makefile:5: Replacing \"\\t\\t\\t\\t  \" with \"\\t\\t\".")
+       vt.Fixed(
+               "WRKSRC=         ${WRKDIR}",
+               "DISTFILES=      distfile-1.0.0.tar.gz",
+               "AWK_PROGRAM+=   /search/ { \\",
+               "                  action(); \\",
+               "                }")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__follow_up_indented_with_spaces(c *check.C) {
+       // FIXME: warn about the misleading empty line 6,
+       //  but not in this test
+
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "DISTFILES= \\",
+               " one space \\",
+               "   three spaces \\",
+               "        eight spaces \\",
+               "\tand a tab \\",
+               "   ") // trailing whitespace
+       vt.Internals(
+               "10 11",
+               "   01",
+               "   03",
+               "   08",
+               "   08",
+               "   03")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:2: This continuation line should be indented with \"\\t\".",
+               "NOTE: ~/Makefile:3: This continuation line should be indented with \"\\t\".",
+               "NOTE: ~/Makefile:4: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \"   \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \"        \" with \"\\t\".")
+       vt.Fixed(
+               "DISTFILES= \\",
+               "        one space \\",
+               "        three spaces \\",
+               "        eight spaces \\",
+               "        and a tab \\",
+               "   ")
+       vt.Run()
+}
+
+// When there is an outlier, no matter whether indented with space or tab,
+// fix the whole block to use the indentation of the second-longest line.
+// In this case, all of the remaining lines have the same indentation
+// (as there is only 1 line at all).
+// Therefore this existing indentation is used instead of the minimum necessary, which would only be a single tab.
+func (s *Suite) Test_VaralignBlock__tab_outlier(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "DISTFILES=\t\tvery-very-very-very-long-distfile-name",
+               "SITES.very-very-very-very-long-distfile-name=\t${MASTER_SITE_LOCAL}")
+       vt.Internals(
+               "10 24",
+               "45 48")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "DISTFILES=              very-very-very-very-long-distfile-name",
+               "SITES.very-very-very-very-long-distfile-name=   ${MASTER_SITE_LOCAL}")
+       vt.Run()
+}
+
+// The SITES.* definition is indented less than the other lines,
+// therefore the whole paragraph will be realigned to that depth.
+func (s *Suite) Test_VaralignBlock__multiline(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "DIST_SUBDIR=            asc",
+               "DISTFILES=              ${DISTNAME}${EXTRACT_SUFX} frontiers.mp3 \\",
+               "                        machine_wars.mp3 time_to_strike.mp3",
+               ".for file in frontiers.mp3 machine_wars.mp3 time_to_strike.mp3",
+               "SITES.${file}=  http://asc-hq.org/";,
+               ".endfor",
+               "WRKSRC=                 ${WRKDIR}/${PKGNAME_NOREV}")
+       vt.Internals(
+               "12 24",
+               "10 24",
+               "   24",
+               "14 16",
+               "07 24")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:3: This continuation line should be indented with \"\\t\\t\".",
+               "NOTE: ~/Makefile:5: Variable values should be aligned with tabs, not spaces.",
+               "NOTE: ~/Makefile:7: This variable value should be aligned with tabs, not spaces, to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"            \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"              \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \"                        \" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:5: Replacing \"  \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:7: Replacing \"                 \" with \"\\t\\t\".")
+       vt.Fixed(
+               "DIST_SUBDIR=    asc",
+               "DISTFILES=      ${DISTNAME}${EXTRACT_SUFX} frontiers.mp3 \\",
+               "                machine_wars.mp3 time_to_strike.mp3",
+               ".for file in frontiers.mp3 machine_wars.mp3 time_to_strike.mp3",
+               "SITES.${file}=  http://asc-hq.org/";,
+               ".endfor",
+               "WRKSRC=         ${WRKDIR}/${PKGNAME_NOREV}")
+       vt.Run()
+}
+
+// The CDROM variables align exactly at a tab position, therefore they must
+// be indented by at least one more space. Since that one space is not
+// enough to count as an outlier, everything is indented by one more tab.
+func (s *Suite) Test_VaralignBlock__single_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "RESTRICTED=\tDo not sell, do not rent",
+               "NO_BIN_ON_CDROM= ${RESTRICTED}",
+               "NO_BIN_ON_FTP=\t${RESTRICTED}",
+               "NO_SRC_ON_CDROM= ${RESTRICTED}",
+               "NO_SRC_ON_FTP=\t${RESTRICTED}")
+       vt.Internals(
+               "11 16",
+               "16 17",
+               "14 16",
+               "16 17",
+               "14 16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 25.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:3: This variable value should be aligned to column 25.",
+               "NOTE: ~/Makefile:4: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:5: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:4: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:5: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "RESTRICTED=             Do not sell, do not rent",
+               "NO_BIN_ON_CDROM=        ${RESTRICTED}",
+               "NO_BIN_ON_FTP=          ${RESTRICTED}",
+               "NO_SRC_ON_CDROM=        ${RESTRICTED}",
+               "NO_SRC_ON_FTP=          ${RESTRICTED}")
+       vt.Run()
+}
+
+// These variables all look nicely aligned, but they use spaces instead of tabs for alignment.
+// The spaces are replaced with tabs, which makes the indentation 4 spaces deeper in the first paragraph.
+// In the second paragraph it's even 7 additional spaces.
+// This is ok though since it is the prevailing indentation style in pkgsrc.
+func (s *Suite) Test_VaralignBlock__only_space(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "REPLACE_PYTHON+= *.py",
+               "REPLACE_PYTHON+= lib/*.py",
+               "REPLACE_PYTHON+= src/*.py")
+       vt.Internals(
+               "16 17",
+               "16 17",
+               "16 17")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "REPLACE_PYTHON+=        *.py",
+               "REPLACE_PYTHON+=        lib/*.py",
+               "REPLACE_PYTHON+=        src/*.py")
+       vt.Run()
+}
+
+// The indentation is deeper than necessary, but all lines agree on the same column.
+// Therefore this indentation depth is kept. It looks good and is probably due to
+// some other paragraphs in the file that are indented equally deep.
+//
+// As of December 2018, pkglint only looks at a single paragraph at a time,
+// therefore it cannot reliably decide whether this deep indentation is necessary.
+func (s *Suite) Test_VaralignBlock__mixed_tabs_and_spaces_same_column(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "DISTFILES+=             space",
+               "DISTFILES+=\t\ttab")
+       vt.Internals(
+               "11 24",
+               "11 24")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: Variable values should be aligned with tabs, not spaces.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"             \" with \"\\t\\t\".")
+       vt.Fixed(
+               "DISTFILES+=             space",
+               "DISTFILES+=             tab")
+       vt.Run()
+}
+
+// Both lines are indented to the same column. Therefore none of them is considered an outlier.
+func (s *Suite) Test_VaralignBlock__outlier_1(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V= value",
+               "V=\tvalue")
+       vt.Internals(
+               "02 03",
+               "02 08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 9.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "V=      value",
+               "V=      value")
+       vt.Run()
+}
+
+// A single space that ends at the same depth as a tab is replaced with a tab, for consistency.
+func (s *Suite) Test_VaralignBlock__outlier_2(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.0008= value",
+               "V=\tvalue")
+       vt.Internals(
+               "07 08",
+               "02 08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: Variable values should be aligned with tabs, not spaces.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "V.0008= value",
+               "V=      value")
+       vt.Run()
+}
+
+// A short line that is indented with a tab is aligned to a longer line
+// that is indented with a space. This is because space-indented lines are
+// only allowed when their indentation is much deeper than the tab-indented
+// ones (so-called outliers), or as the first line of a continuation line.
+func (s *Suite) Test_VaralignBlock__outlier_3(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.00009= value",
+               "V=\tvalue")
+       vt.Internals(
+               "08 09",
+               "02 08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "V.00009=        value",
+               "V=              value")
+       vt.Run()
+}
+
+// This space-indented line doesn't count as an outlier yet because it
+// is only a single tab away. The limit is two tabs.
+// Therefore both lines are indented with tabs.
+func (s *Suite) Test_VaralignBlock__outlier_4(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.000000000016= value",
+               "V=\tvalue")
+       vt.Internals(
+               "15 16",
+               "02 08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: Variable values should be aligned with tabs, not spaces.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "V.000000000016= value",
+               "V=              value")
+       vt.Run()
+}
+
+// This space-indented line is an outlier since it is far enough from the
+// tab-indented line. The latter would require 2 tabs to align to the former.
+// Therefore the short line is not indented to the long line, in order to
+// keep the indentation reasonably short for a large amount of the lines.
+func (s *Suite) Test_VaralignBlock__outlier_5(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.0000000000017= value",
+               "V=\tvalue")
+       vt.Internals(
+               "16 17",
+               "02 08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "V.0000000000017= value",
+               "V=      value")
+       vt.Run()
+}
+
+// Short space-indented lines do not count as outliers. They are are aligned to the longer tab-indented line.
+func (s *Suite) Test_VaralignBlock__outlier_6(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V= value",
+               "V.000010=\tvalue")
+       vt.Internals(
+               "02 03",
+               "09 16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\\t\".")
+       vt.Fixed(
+               "V=              value",
+               "V.000010=       value")
+       vt.Run()
+}
+
+// The long line is not an outlier but very close. One more space, and it would count.
+func (s *Suite) Test_VaralignBlock__outlier_10(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.0000000000000000023= value", // Adjust from 23 to 24 (+ 1 tab)
+               "V.000010=\tvalue")             // Adjust from 16 to 24 (+ 1 tab)
+       vt.Internals(
+               "22 23",
+               "09 16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "V.0000000000000000023=  value",
+               "V.000010=               value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__outlier_11(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.00000000000000000024= value", // Keep at 24 (space to tab)
+               "V.000010=\tvalue")              // Adjust from 16 to 24 (+ 1 tab)
+       vt.Internals(
+               "23 24",
+               "09 16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: Variable values should be aligned with tabs, not spaces.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "V.00000000000000000024= value",
+               "V.000010=               value")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__outlier_12(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.000000000000000000025= value", // Keep at 25 (outlier)
+               "V.000010=\tvalue")               // Keep at 16 (would require + 2 tabs)
+       vt.Internals(
+               "24 25",
+               "09 16")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "V.000000000000000000025= value",
+               "V.000010=       value")
+       vt.Run()
+}
+
+// When the lines are indented inconsistently, the indentation is reduced
+// to the required minimum.
+func (s *Suite) Test_VaralignBlock__outlier_14(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "V.00008=\t\tvalue",     // Adjust from 24 to 16 (removes 1 tab)
+               "V.00008=\t\t\t\tvalue") // Adjust from 40 to 16 (removes 3 tabs)
+       vt.Internals(
+               "08 24",
+               "08 40")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:2: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\\t\" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:2: Replacing \"\\t\\t\\t\\t\" with \"\\t\".")
+       vt.Fixed(
+               "V.00008=        value",
+               "V.00008=        value")
+       vt.Run()
+}
+
+// The INSTALLATION_DIRS line is so long that it is considered an outlier,
+// since compared to the DIST line, it is at least two tabs away.
+// Pkglint before 2018-01-26 suggested that it "should be aligned to column 9",
+// which is not possible since the variable name is already longer.
+func (s *Suite) Test_VaralignBlock__long_short(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "INSTALLATION_DIRS=\tbin",
+               "DIST=\t${WRKSRC}/dist")
+       vt.Internals(
+               "18 24",
+               "05 08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "INSTALLATION_DIRS=      bin",
+               "DIST=   ${WRKSRC}/dist")
+       vt.Run()
+}
+
+// Before 2018-01-26, pkglint wanted to replace the tab in the outlier with
+// a space. After this change, the space-indented line would not look like an
+// outlier anymore because the other values are aligned very close to the
+// outlier value. To fix this case, the indentation of the other lines needs
+// to be adjusted to the minimum required.
+//
+// FIXME: The definition of an outlier should be based on the actual indentation,
+//  not on the minimum indentation. Or maybe even better on the corrected indentation.
+//  In the below paragraph, the outlier is not indented enough to qualify as a visual outlier.
+func (s *Suite) Test_VaralignBlock__tabbed_outlier(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               ".if !empty(PKG_OPTIONS:Minspircd-sqloper)",
+               "INSPIRCD_STORAGE_DRIVER?=\tmysql",
+               "MODULES+=\t\tm_sqloper.cpp m_sqlutils.cpp",
+               "HEADERS+=\t\tm_sqlutils.h",
+               ".endif")
+       vt.Internals(
+               "25 32",
+               "09 24",
+               "09 24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               ".if !empty(PKG_OPTIONS:Minspircd-sqloper)",
+               "INSPIRCD_STORAGE_DRIVER?=       mysql",
+               "MODULES+=               m_sqloper.cpp m_sqlutils.cpp",
+               "HEADERS+=               m_sqlutils.h",
+               ".endif")
+       vt.Run()
+}
+
+// When all continuation lines are indented exactly one tab more than the
+// initial line, this is intentional.
+//
+// TODO: Make this rule more general: if the indentation of the continuation
+//  lines is more than the initial line, it is intentional.
+func (s *Suite) Test_VaralignBlock__indented_continuation_line(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "CONF_FILES_PERMS=\tsource \\",
+               "\t\t\t\tdestination \\",
+               "\t\t\t\tuser group 0644")
+       vt.Internals(
+               "17 24",
+               "   32",
+               "   32")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "CONF_FILES_PERMS=       source \\",
+               "                                destination \\",
+               "                                user group 0644")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__indented_continuation_line_in_paragraph(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "SUBST_CLASSES+=\t\tfix",
+               "SUBST_STAGE.fix=\tpost-patch",
+               "SUBST_SED.fix= \\",
+               "\t-e 's,1,one,g' \\",
+               "\t-e 's,2,two,g' \\",
+               "\t-e 's,3,three,g'")
+       vt.Internals(
+               "15 24",
+               "16 24",
+               "14 15",
+               "   08",
+               "   08",
+               "   08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:3: Replacing \" \" with \"\\t\\t\".")
+       vt.Fixed(
+               "SUBST_CLASSES+=         fix",
+               "SUBST_STAGE.fix=        post-patch",
+               "SUBST_SED.fix=          \\",
+               "        -e 's,1,one,g' \\",
+               "        -e 's,2,two,g' \\",
+               "        -e 's,3,three,g'")
+       vt.Run()
+}
+
+// Up to 2018-01-27, it could happen that some source code was logged
+// without a corresponding diagnostic. This was unintended and confusing.
+func (s *Suite) Test_VaralignBlock__fix_without_diagnostic(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "MESSAGE_SUBST+=\t\tRUBY_DISTNAME=${RUBY_DISTNAME}",
+               "PLIST_SUBST+=\t\tRUBY_SHLIBVER=${RUBY_SHLIBVER:Q} \\",
+               "\t\t\tRUBY_SHLIBMAJOR=${RUBY_SHLIBMAJOR:Q} \\",
+               "\t\t\tRUBY_NOSHLIBMAJOR=${RUBY_NOSHLIBMAJOR} \\",
+               "\t\t\tRUBY_NAME=${RUBY_NAME:Q}")
+       vt.Internals(
+               "15 24",
+               "13 24",
+               "   24",
+               "   24",
+               "   24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "MESSAGE_SUBST+=         RUBY_DISTNAME=${RUBY_DISTNAME}",
+               "PLIST_SUBST+=           RUBY_SHLIBVER=${RUBY_SHLIBVER:Q} \\",
+               "                        RUBY_SHLIBMAJOR=${RUBY_SHLIBMAJOR:Q} \\",
+               "                        RUBY_NOSHLIBMAJOR=${RUBY_NOSHLIBMAJOR} \\",
+               "                        RUBY_NAME=${RUBY_NAME:Q}")
+       vt.ShowSource = true
+       vt.Run()
+}
+
+// The two variables look like they were in two separate paragraphs, but
+// they aren't. This is because the continuation line from the DISTFILES
+// eats up the empty line that would otherwise separate the paragraphs.
+func (s *Suite) Test_VaralignBlock__continuation_line_last_empty(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               // FIXME: Add a test for MkParser to warn about this apparently empty line.
+               "DISTFILES= \\",
+               "\ta \\",
+               "\tb \\",
+               "\tc \\",
+               "", // This is the final line of the variable assignment.
+               "NEXT_VAR=\tsecond line")
+       vt.Internals(
+               "10 11",
+               "   08",
+               "   08",
+               "   08",
+               "   00",
+               "09 16")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "DISTFILES=      \\",
+               "        a \\",
+               "        b \\",
+               "        c \\",
+               "",
+               "NEXT_VAR=       second line")
+       vt.Run()
+}
+
+// Commented-out variables take part in the realignment.
+// The TZ=UTC below is part of the two-line comment since make(1) interprets it in the same way.
+//
+// This is one of the few cases where commented variable assignments are treated specially.
+// See MkLine.IsCommentedVarassign.
+func (s *Suite) Test_VaralignBlock__realign_commented_single_lines(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "SHORT=\tvalue",
+               "#DISTFILES=\tdistfile-1.0.0.tar.gz",
+               "#CONTINUATION= \\",
+               "#\t\tcontinued",
+               "#CONFIGURE_ENV+= \\",
+               "#TZ=UTC",
+               "SHORT=\tvalue")
+       vt.Internals(
+               "06 08",
+               "11 16",
+               "14 15",
+               "   16",
+               "16 17",
+               "   01",
+               "06 08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+               "NOTE: ~/Makefile:3: This variable value should be aligned with tabs, not spaces, to column 17.",
+               "NOTE: ~/Makefile:6: This continuation line should be indented with \"\\t\\t\".",
+               "NOTE: ~/Makefile:7: This variable value should be aligned to column 17.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \"\\t\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:3: Replacing \" \" with \"\\t\".",
+               "AUTOFIX: ~/Makefile:6: Replacing \"\" with \"\\t\\t\".",
+               "AUTOFIX: ~/Makefile:7: Replacing \"\\t\" with \"\\t\\t\".")
+       vt.Fixed(
+               "SHORT=          value",
+               "#DISTFILES=     distfile-1.0.0.tar.gz",
+               "#CONTINUATION=  \\",
+               "#               continued",
+               "#CONFIGURE_ENV+= \\",
+               "#               TZ=UTC",
+               "SHORT=          value")
+       vt.Run()
+}
+
+// Commented variable assignments are realigned, too.
+// In this case, the BEFORE and COMMENTED variables are already aligned properly.
+// The line starting with "AFTER" is part of the commented variable assignment,
+// and since these are checked as well, it is realigned.
+func (s *Suite) Test_VaralignBlock__realign_commented_continuation_line(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "BEFORE=\tvalue",
+               "#COMMENTED= \\",
+               "#\tvalue1 \\",
+               "#\tvalue2 \\",
+               "#\tvalue3 \\",
+               "AFTER=\tafter")
+       vt.Internals(
+               "07 08",
+               "11 12",
+               "   08",
+               "   08",
+               "   08",
+               "   00")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:6: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:6: Replacing \"\" with \"\\t\".")
+       vt.Fixed(
+               "BEFORE= value",
+               "#COMMENTED= \\",
+               "#       value1 \\",
+               "#       value2 \\",
+               "#       value3 \\",
+               "        AFTER=  after")
+       vt.Run()
+}
+
+// The HOMEPAGE is completely ignored. Since its value is empty it doesn't
+// need any alignment. Whether it is commented out doesn't matter.
+//
+// If the HOMEPAGE were taken into account, the alignment would differ and
+// the COMMENT line would be realigned to column 17, reducing the indentation by one tab.
+func (s *Suite) Test_VaralignBlock__realign_variable_without_value(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "COMMENT=\t\tShort description of the package",
+               "#HOMEPAGE=")
+       vt.Internals(
+               // The HOMEPAGE line is ignored completely since it has neither
+               // variable value nor comment and therefore there's nothing
+               // that could be aligned to other variables.
+               "08 24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "COMMENT=                Short description of the package",
+               "#HOMEPAGE=")
+       vt.Run()
+}
+
+// This commented multiline variable is already perfectly aligned.
+// Nothing needs to be fixed.
+// This is a simple case since a paragraph containing only one line
+// is always aligned properly, except when the indentation uses spaces instead of tabs.
+func (s *Suite) Test_VaralignBlock__realign_commented_multiline(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "#CONF_FILES+=\t\tfile1 \\",
+               "#\t\t\tfile2")
+       vt.Internals(
+               "13 24",
+               "   24")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "#CONF_FILES+=           file1 \\",
+               "#                       file2")
+       vt.Run()
+}
+
+// The VAR2 line is a continuation line that starts in column 9, just like
+// the VAR1 line. Therefore the alignment is correct.
+//
+// Its follow-up line is indented with effectively tab-tab-space, and
+// this relative indentation compared to the VAR2 line is preserved since
+// it is often used for indenting AWK or shell programs.
+//
+// In this particular case, using a one-space indentation looks wrong,
+// but as of July 2019, that's not something pkglint will know in the
+// near future.
+func (s *Suite) Test_VaralignBlock__mixed_indentation(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR1=\tvalue1",
+               "VAR2=\tvalue2 \\",
+               " \t \t value2 continued")
+       vt.Internals(
+               "05 08",
+               "05 08",
+               "   17")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:3: This continuation line should be indented with \"\\t\".")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:3: Replacing \" \\t \\t \" with \"\\t\\t \".")
+       vt.Fixed(
+               "VAR1=   value1",
+               "VAR2=   value2 \\",
+               "                 value2 continued")
+       vt.Run()
+}
+
+// Ensure that the end-of-line comment is properly aligned
+// to the variable values.
+//
+// This case may seem obvious, but in all other contexts, the whitespace
+// before the comment is ignored. Therefore the end of the line would be
+// after the "=" in these cases, and the alignment must take care to
+// include the whitespace.
+func (s *Suite) Test_VaralignBlock__eol_comment(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "VAR1=\tdefined",
+               "VAR2=\t# defined",
+               "VAR3=\t#empty")
+       vt.Internals(
+               "05 08",
+               "05 08",
+               "05 08")
+       vt.Diagnostics()
+       vt.Autofixes()
+       vt.Fixed(
+               "VAR1=   defined",
+               "VAR2=   # defined",
+               "VAR3=   #empty")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__follow_up_indentation(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "EGDIR=\t\t\t${PREFIX}/share/examples/rtunes",
+               "CONF_FILES=\t\t${EGDIR}/wrksrc.conf \\",
+               "\t\t\t\t${PKG_SYSCONFDIR}/installed.conf",
+               "EGDIR=\t\t\t${PREFIX}/share/examples/rtunes")
+       vt.Internals(
+               "06 24",
+               "11 24",
+               "   32",
+               "06 24")
+       vt.Diagnostics(
+               nil...)
+       vt.Autofixes(
+               nil...)
+       vt.Fixed(
+               "EGDIR=                  ${PREFIX}/share/examples/rtunes",
+               "CONF_FILES=             ${EGDIR}/wrksrc.conf \\",
+               "                                ${PKG_SYSCONFDIR}/installed.conf",
+               "EGDIR=                  ${PREFIX}/share/examples/rtunes")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock__staircase(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "JAM_COMMAND=\t\\",
+               "\tcd ${WRKSRC} &&\t\t\t\t\t\t\t\\",
+               "\t\t${SETENV} ${MY_ENV}\t\t\t\t\t\\",
+               "\t\t\t${PREFIX}/bin/my-cmd\t\t\t\t\\",
+               "\t\t\t\t-options arg...")
+       vt.Internals(
+               "12 16",
+               "   08",
+               "   16",
+               "   24",
+               "   32")
+       vt.Diagnostics(
+               nil...)
+       vt.Autofixes(
+               nil...)
+       vt.Fixed(
+               "JAM_COMMAND=    \\",
+               "        cd ${WRKSRC} &&                                                 \\",
+               "                ${SETENV} ${MY_ENV}                                     \\",
+               "                        ${PREFIX}/bin/my-cmd                            \\",
+               "                                -options arg...")
+       vt.Run()
+}
+
+// The follow-up lines may always start in column 9.
+// This is used for long variable values, to prevent wrapping them
+// into multiple lines.
+func (s *Suite) Test_VaralignBlock__command_with_arguments(c *check.C) {
+       vt := NewVaralignTester(s, c)
+       vt.Input(
+               "SED_REPLACEMENT_CMD= ${SED} -n \\",
+               "\t-e s,a,b, \\",
+               "\t-e s,a,b, \\",
+               "\t-e s,a,b,")
+       vt.Internals(
+               "20 21",
+               "   08",
+               "   08",
+               "   08")
+       vt.Diagnostics(
+               "NOTE: ~/Makefile:1: This variable value should be aligned with tabs, not spaces, to column 25.")
+       vt.Autofixes(
+               "AUTOFIX: ~/Makefile:1: Replacing \" \" with \"\\t\".")
+       vt.Fixed(
+               "SED_REPLACEMENT_CMD=    ${SED} -n \\",
+               "        -e s,a,b, \\",
+               "        -e s,a,b, \\",
+               "        -e s,a,b,")
+       vt.Run()
+}
+
+func (s *Suite) Test_VaralignBlock_Process__autofix(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpCommandLine("-Wspace", "--show-autofix")
+
+       mklines := t.NewMkLines("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.
+
+       var varalign VaralignBlock
+       for _, line := range mklines.mklines {
+               varalign.Process(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\".")
+}
+
+// When the lines of a paragraph are inconsistently aligned,
+// they are realigned to the minimum required width.
+func (s *Suite) Test_VaralignBlock_Process__reduce_indentation(c *check.C) {
+       t := s.Init(c)
+
+       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")
+
+       var varalign VaralignBlock
+       for _, mkline := range mklines.mklines {
+               varalign.Process(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.")
+}
+
+// For every variable assignment, there is at least one space or tab between the variable
+// name and the value. Even if it is the longest line, and even if the value would start
+// exactly at a tab stop.
+func (s *Suite) Test_VaralignBlock_Process__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.aaaaaa=cat")
+
+       var varalign VaralignBlock
+       for _, mkline := range mklines.mklines {
+               varalign.Process(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_Process__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")
+
+       var varalign VaralignBlock
+       for _, mkline := range mklines.mklines {
+               varalign.Process(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_VaralignBlock_split(c *check.C) {
+       t := s.Init(c)
+
+       test := func(textnl string, initial bool, expected varalignSplitResult) {
+               actual := (&VaralignBlock{}).split(textnl, initial)
+
+               t.CheckEquals(actual, expected)
+               t.CheckEquals(
+                       actual.leadingComment+actual.varnameOp+
+                               actual.spaceBeforeValue+actual.value+actual.spaceAfterValue+
+                               actual.trailingComment+actual.spaceAfterComment+actual.continuation,
+                       textnl)
+       }
+
+       test("VAR=value\n", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "\n",
+               })
+
+       test("#VAR=value\n", true,
+               varalignSplitResult{
+                       leadingComment:    "#",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "\n",
+               })
+
+       test("#VAR = value # comment \\\n", true,
+               varalignSplitResult{
+                       leadingComment:    "#",
+                       varnameOp:         "VAR =",
+                       spaceBeforeValue:  " ",
+                       value:             "value",
+                       spaceAfterValue:   " ",
+                       trailingComment:   "# comment",
+                       spaceAfterComment: " ",
+                       continuation:      "\\\n",
+               })
+
+       test("VAR=value \\\n", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   " ",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "\\\n",
+               })
+
+       test("VAR=value # comment \\\n", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   " ",
+                       trailingComment:   "# comment",
+                       spaceAfterComment: " ",
+                       continuation:      "\\\n",
+               })
+
+       test("VAR=value # comment \\\\\n", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   " ",
+                       trailingComment:   "# comment \\\\",
+                       spaceAfterComment: "",
+                       continuation:      "\n",
+               })
+
+       test("VAR=\\# a [#] b # comment \\\\\n", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "\\# a [#] b",
+                       spaceAfterValue:   " ",
+                       trailingComment:   "# comment \\\\",
+                       spaceAfterComment: "",
+                       continuation:      "\n",
+               })
+
+       test("VAR.${param:[#]}=\tvalue\n", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR.${param:[#]}=",
+                       spaceBeforeValue:  "\t",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "\n",
+               })
+
+       test("VAR=value", true,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "",
+               })
+
+       // Since this is a follow-up line, the text ends up in the variable
+       // value, and varnameOp is necessarily empty.
+       test("VAR=value", false,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "",
+                       spaceBeforeValue:  "",
+                       value:             "VAR=value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "",
+               })
+
+       // In some edge cases the variable name is indented with ordinary spaces.
+       // This must not lead to a panic.
+       test("   VAR=value", true,
+               varalignSplitResult{
+                       leadingComment:    "   ",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "",
+               })
+
+       // And in really edgy cases, the leading space may even be followed by tabs.
+       // This should not happen in practice since it is really confusing.
+       test(" \t VAR=value", true,
+               varalignSplitResult{
+                       leadingComment:    " \t ",
+                       varnameOp:         "VAR=",
+                       spaceBeforeValue:  "",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "",
+               })
+
+       test("    value", false,
+               varalignSplitResult{
+                       leadingComment:    "",
+                       varnameOp:         "",
+                       spaceBeforeValue:  "    ",
+                       value:             "value",
+                       spaceAfterValue:   "",
+                       trailingComment:   "",
+                       spaceAfterComment: "",
+                       continuation:      "",
+               })
+
+       // Commented variable assignments are only valid if they
+       // directly follow the comment sign.
+       //
+       // It is a programming error if such a line is ever added to
+       // the VaralignBlock.
+       t.ExpectAssert(
+               func() { test("#  VAR=    value", true, varalignSplitResult{}) })
+}
+
+func (s *Suite) Test_varalignLine_canonicalFollow(c *check.C) {
+       t := s.Init(c)
+
+       test := func(comment, space string, expected bool) {
+               l := varalignLine{
+                       parts: varalignSplitResult{
+                               leadingComment:   comment,
+                               spaceBeforeValue: space}}
+
+               actual := l.canonicalFollow()
+
+               t.CheckEquals(actual, expected)
+       }
+
+       // Follow-up lines should always be indented.
+       test("", "", false)
+
+       // Follow-up lines should be indented by tabs, not by spaces.
+       test("", " ", false)
+
+       // A tab is always canonical.
+       test("", "\t", true)
+
+       // A tab followed by up to 7 spaces is canonical.
+       test("", "\t       ", true)
+
+       // A tab followed by 8 spaces is not canonical, the spaces should be
+       // replaced with a tab.
+       test("", "\t        ", false)
+
+       // There may be arbitrary many tabs.
+       test("", "\t\t\t\t\t\t\t\t", true)
+
+       // In commented follow-up lines, the value should be indented in the
+       // same way as in uncommented lines.
+       test("#", "", false)
+       test("#", " ", false)
+       test("#", "\t", true)
+}



Home | Main Index | Thread Index | Old Index