summaryrefslogtreecommitdiff
path: root/pkgtools
diff options
context:
space:
mode:
authorrillig <rillig@pkgsrc.org>2018-01-28 23:21:16 +0000
committerrillig <rillig@pkgsrc.org>2018-01-28 23:21:16 +0000
commit90ca88236b6927e3ecd596c2e4538b7d154b2d23 (patch)
treebaaf41e209e87f2382c749dfb5378976cf9b9186 /pkgtools
parent1bf85a634ceb83c7d3889697ddd61a7d403d9207 (diff)
downloadpkgsrc-90ca88236b6927e3ecd596c2e4538b7d154b2d23.tar.gz
pkgtools/pkglint: update to 5.5.3
Changes since 5.5.2: * Fixed lots of bugs regarding autofixing variable assignments in continuation lines. * Fixed checking of MESSAGE files, which also get fixed now. * In variable assignments, commented assignments are aligned too. * Fixed a crash when checking an empty patch file. * The :Q modifier is only checked on predefined variables, to prevent the --autofix mode from removing :Q from user-defined variables. * Fixed lots of bugs in PLIST autofixing: relevant lines had been removed, and the sorting was not correct.
Diffstat (limited to 'pkgtools')
-rw-r--r--pkgtools/pkglint/Makefile4
-rw-r--r--pkgtools/pkglint/files/autofix.go44
-rw-r--r--pkgtools/pkglint/files/autofix_test.go73
-rw-r--r--pkgtools/pkglint/files/category.go2
-rw-r--r--pkgtools/pkglint/files/files.go30
-rw-r--r--pkgtools/pkglint/files/files_test.go88
-rw-r--r--pkgtools/pkglint/files/globaldata.go14
-rw-r--r--pkgtools/pkglint/files/histogram/histogram.go28
-rw-r--r--pkgtools/pkglint/files/linechecker.go2
-rw-r--r--pkgtools/pkglint/files/mkline.go73
-rw-r--r--pkgtools/pkglint/files/mkline_test.go68
-rw-r--r--pkgtools/pkglint/files/mklinechecker.go2
-rw-r--r--pkgtools/pkglint/files/mklinechecker_test.go31
-rw-r--r--pkgtools/pkglint/files/mklines.go28
-rwxr-xr-xpkgtools/pkglint/files/mklines_varalign_test.go115
-rw-r--r--pkgtools/pkglint/files/patches.go5
-rw-r--r--pkgtools/pkglint/files/patches_test.go27
-rw-r--r--pkgtools/pkglint/files/pkglint.go34
-rw-r--r--pkgtools/pkglint/files/pkglint_test.go32
-rw-r--r--pkgtools/pkglint/files/plist.go55
-rw-r--r--pkgtools/pkglint/files/plist_test.go44
21 files changed, 652 insertions, 147 deletions
diff --git a/pkgtools/pkglint/Makefile b/pkgtools/pkglint/Makefile
index bbbbab8af83..ce4dfe4847d 100644
--- a/pkgtools/pkglint/Makefile
+++ b/pkgtools/pkglint/Makefile
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.527 2018/01/28 13:40:22 rillig Exp $
+# $NetBSD: Makefile,v 1.528 2018/01/28 23:21:16 rillig Exp $
-PKGNAME= pkglint-5.5.2
+PKGNAME= pkglint-5.5.3
DISTFILES= # none
CATEGORIES= pkgtools
diff --git a/pkgtools/pkglint/files/autofix.go b/pkgtools/pkglint/files/autofix.go
index 0063d5d2eb0..3604ed1ae9e 100644
--- a/pkgtools/pkglint/files/autofix.go
+++ b/pkgtools/pkglint/files/autofix.go
@@ -62,25 +62,42 @@ func (fix *Autofix) ReplaceAfter(prefix, from string, to string) {
}
}
-func (fix *Autofix) ReplaceRegex(from regex.Pattern, to string) {
+// ReplaceRegex replaces the first or all occurrences of the `from` pattern
+// with the fixed string `toText`. Placeholders like `$1` are _not_ expanded.
+// (If you know how to do the expansion correctly, feel free to implement it.)
+func (fix *Autofix) ReplaceRegex(from regex.Pattern, toText string, howOften int) {
if fix.skip() {
return
}
+ done := 0
for _, rawLine := range fix.lines {
if rawLine.Lineno != 0 {
- if replaced := regex.Compile(from).ReplaceAllString(rawLine.textnl, to); replaced != rawLine.textnl {
+ var froms []string // The strings that have actually changed
+
+ replace := func(fromText string) string {
+ if howOften >= 0 && done >= howOften {
+ return fromText
+ }
+ froms = append(froms, fromText)
+ done++
+ return toText
+ }
+
+ if replaced := regex.Compile(from).ReplaceAllStringFunc(rawLine.textnl, replace); replaced != rawLine.textnl {
if G.opts.PrintAutofix || G.opts.Autofix {
rawLine.textnl = replaced
}
- fix.Describef(rawLine.Lineno, "Replacing regular expression %q with %q.", from, to)
+ for _, fromText := range froms {
+ fix.Describef(rawLine.Lineno, "Replacing %q with %q.", fromText, toText)
+ }
}
}
}
}
func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
- if fix.skip() || !mkline.IsMultiline() || !mkline.IsVarassign() {
+ if fix.skip() || !mkline.IsMultiline() || !(mkline.IsVarassign() || mkline.IsCommentedVarassign()) {
return
}
@@ -90,19 +107,19 @@ func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
{
// Interpreting the continuation marker as variable value
// is cheating, but works well.
- m, _, _, _, valueAlign, value, _, _ := MatchVarassign(mkline.raw[0].orignl)
+ m, _, _, _, _, valueAlign, value, _, _ := MatchVarassign(mkline.raw[0].orignl)
if m && value != "\\" {
oldWidth = tabWidth(valueAlign)
}
}
for _, rawLine := range fix.lines[1:] {
- _, space := regex.Match1(rawLine.textnl, `^(\s*)`)
- width := tabWidth(space)
- if oldWidth == 0 || width < oldWidth {
+ _, comment, space := regex.Match2(rawLine.textnl, `^(#?)([ \t]*)`)
+ width := tabWidth(comment + space)
+ if (oldWidth == 0 || width < oldWidth) && width >= 8 && rawLine.textnl != "\n" {
oldWidth = width
}
- if !regex.Matches(space, `^\t*\s{0,7}`) {
+ if !regex.Matches(space, `^\t* {0,7}`) {
normalized = false
}
}
@@ -119,10 +136,11 @@ func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
}
for _, rawLine := range fix.lines[1:] {
- _, oldSpace := regex.Match1(rawLine.textnl, `^(\s*)`)
+ _, comment, oldSpace := regex.Match2(rawLine.textnl, `^(#?)([ \t]*)`)
newWidth := tabWidth(oldSpace) - oldWidth + newWidth
newSpace := strings.Repeat("\t", newWidth/8) + strings.Repeat(" ", newWidth%8)
- if replaced := strings.Replace(rawLine.textnl, oldSpace, newSpace, 1); replaced != rawLine.textnl {
+ replaced := strings.Replace(rawLine.textnl, comment+oldSpace, comment+newSpace, 1)
+ if replaced != rawLine.textnl {
if G.opts.PrintAutofix || G.opts.Autofix {
rawLine.textnl = replaced
}
@@ -131,6 +149,8 @@ func (fix *Autofix) Realign(mkline MkLine, newWidth int) {
}
}
+// InsertBefore prepends a line before the current line.
+// The newline is added internally.
func (fix *Autofix) InsertBefore(text string) {
if fix.skip() {
return
@@ -140,6 +160,8 @@ func (fix *Autofix) InsertBefore(text string) {
fix.Describef(fix.lines[0].Lineno, "Inserting a line %q before this line.", text)
}
+// InsertBefore appends a line after the current line.
+// The newline is added internally.
func (fix *Autofix) InsertAfter(text string) {
if fix.skip() {
return
diff --git a/pkgtools/pkglint/files/autofix_test.go b/pkgtools/pkglint/files/autofix_test.go
index bf9c4d08bc9..e4e20628ab4 100644
--- a/pkgtools/pkglint/files/autofix_test.go
+++ b/pkgtools/pkglint/files/autofix_test.go
@@ -13,7 +13,7 @@ func (s *Suite) Test_Autofix_ReplaceRegex(c *check.C) {
fix := lines[1].Autofix()
fix.Warnf("Something's wrong here.")
- fix.ReplaceRegex(`.`, "X")
+ fix.ReplaceRegex(`.`, "X", -1)
fix.Apply()
SaveAutofixChanges(lines)
@@ -24,7 +24,11 @@ func (s *Suite) Test_Autofix_ReplaceRegex(c *check.C) {
"line3")
t.CheckOutputLines(
"WARN: ~/Makefile:2: Something's wrong here.",
- "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".")
+ "AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"e\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"2\" with \"X\".")
}
func (s *Suite) Test_Autofix_ReplaceRegex_with_autofix(c *check.C) {
@@ -38,27 +42,32 @@ func (s *Suite) Test_Autofix_ReplaceRegex_with_autofix(c *check.C) {
fix := lines[1].Autofix()
fix.Warnf("Something's wrong here.")
- fix.ReplaceRegex(`.`, "X")
+ fix.ReplaceRegex(`.`, "X", 3)
fix.Apply()
+ t.CheckOutputLines(
+ "AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
+ "-\tline2",
+ "+\tXXXe2")
+
fix.Warnf("Use Y instead of X.")
fix.Replace("X", "Y")
fix.Apply()
+ t.CheckOutputLines(
+ "",
+ "AUTOFIX: ~/Makefile:2: Replacing \"X\" with \"Y\".",
+ "-\tline2",
+ "+\tYXXe2")
+
SaveAutofixChanges(lines)
t.CheckFileLines("Makefile",
"line1",
- "YXXXX",
+ "YXXe2",
"line3")
- t.CheckOutputLines(
- "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
- "-\tline2",
- "+\tXXXXX",
- "",
- "AUTOFIX: ~/Makefile:2: Replacing \"X\" with \"Y\".",
- "-\tline2",
- "+\tYXXXX")
}
func (s *Suite) Test_Autofix_ReplaceRegex_with_show_autofix(c *check.C) {
@@ -72,7 +81,7 @@ func (s *Suite) Test_Autofix_ReplaceRegex_with_show_autofix(c *check.C) {
fix := lines[1].Autofix()
fix.Warnf("Something's wrong here.")
- fix.ReplaceRegex(`.`, "X")
+ fix.ReplaceRegex(`.`, "X", -1)
fix.Apply()
fix.Warnf("Use Y instead of X.")
@@ -83,7 +92,11 @@ func (s *Suite) Test_Autofix_ReplaceRegex_with_show_autofix(c *check.C) {
t.CheckOutputLines(
"WARN: ~/Makefile:2: Something's wrong here.",
- "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"l\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"i\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"n\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"e\" with \"X\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"2\" with \"X\".",
"-\tline2",
"+\tXXXXX",
"",
@@ -108,17 +121,27 @@ func (s *Suite) Test_autofix_MkLines(c *check.C) {
fix := mklines.mklines[1].Autofix()
fix.Warnf("Something's wrong here.")
- fix.ReplaceRegex(`.`, "X")
+ fix.ReplaceRegex(`...`, "XXX", -1)
+ fix.Apply()
+
+ fix = mklines.mklines[2].Autofix()
+ fix.Warnf("Something's wrong here.")
+ fix.ReplaceRegex(`...`, "XXX", 1)
fix.Apply()
SaveAutofixChanges(mklines.lines)
+ t.CheckOutputLines(
+ "AUTOFIX: ~/Makefile:2: Replacing \"lin\" with \"XXX\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"e2 \" with \"XXX\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \":= \" with \"XXX\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"val\" with \"XXX\".",
+ "AUTOFIX: ~/Makefile:2: Replacing \"ue2\" with \"XXX\".",
+ "AUTOFIX: ~/Makefile:3: Replacing \"lin\" with \"XXX\".")
t.CheckFileLines("Makefile",
"line1 := value1",
"XXXXXXXXXXXXXXX",
- "line3 := value3")
- t.CheckOutputLines(
- "AUTOFIX: ~/Makefile:2: Replacing regular expression \".\" with \"X\".")
+ "XXXe3 := value3")
}
func (s *Suite) Test_Autofix_multiple_modifications(c *check.C) {
@@ -134,14 +157,14 @@ func (s *Suite) Test_Autofix_multiple_modifications(c *check.C) {
{
fix := line.Autofix()
fix.Warnf("Silent-Magic-Diagnostic")
- fix.ReplaceRegex(`(.)(.*)(.)`, "$3$2$1")
+ fix.ReplaceRegex(`(.)(.*)(.)`, "lriginao", 1) // XXX: the replacement should be "$3$2$1"
fix.Apply()
}
c.Check(line.autofix, check.NotNil)
c.Check(line.raw, check.DeepEquals, t.NewRawLines(1, "original\n", "lriginao\n"))
t.CheckOutputLines(
- "AUTOFIX: fname:1: Replacing regular expression \"(.)(.*)(.)\" with \"$3$2$1\".")
+ "AUTOFIX: fname:1: Replacing \"original\" with \"lriginao\".")
{
fix := line.Autofix()
@@ -304,7 +327,7 @@ func (s *Suite) Test_Autofix_suppress_unfixable_warnings(c *check.C) {
fix := lines[1].Autofix()
fix.Warnf("Something's wrong here.")
- fix.ReplaceRegex(`.`, "X")
+ fix.ReplaceRegex(`.`, "X", -1)
fix.Apply()
fix.Warnf("The XXX marks are usually not fixed, use TODO instead.")
@@ -315,7 +338,11 @@ func (s *Suite) Test_Autofix_suppress_unfixable_warnings(c *check.C) {
t.CheckOutputLines(
"WARN: Makefile:2: Something's wrong here.",
- "AUTOFIX: Makefile:2: Replacing regular expression \".\" with \"X\".",
+ "AUTOFIX: Makefile:2: Replacing \"l\" with \"X\".",
+ "AUTOFIX: Makefile:2: Replacing \"i\" with \"X\".",
+ "AUTOFIX: Makefile:2: Replacing \"n\" with \"X\".",
+ "AUTOFIX: Makefile:2: Replacing \"e\" with \"X\".",
+ "AUTOFIX: Makefile:2: Replacing \"2\" with \"X\".",
"-\tline2",
"+\tXXXXX",
"",
@@ -333,7 +360,7 @@ func (s *Suite) Test_Autofix_failed_replace(c *check.C) {
fix := line.Autofix()
fix.Warnf("All-uppercase words should not be used at all.")
- fix.ReplaceRegex(`\b[A-Z]{3,}\b`, "---censored---")
+ fix.ReplaceRegex(`\b[A-Z]{3,}\b`, "---censored---", -1)
fix.Apply()
// No output since there was no all-uppercase word in the text.
diff --git a/pkgtools/pkglint/files/category.go b/pkgtools/pkglint/files/category.go
index c5f7ecced45..05a27c1da65 100644
--- a/pkgtools/pkglint/files/category.go
+++ b/pkgtools/pkglint/files/category.go
@@ -41,7 +41,7 @@ func CheckdirCategory() {
// collect the SUBDIRs in the Makefile and in the file system.
fSubdirs := getSubdirs(G.CurrentDir)
- sort.Sort(sort.StringSlice(fSubdirs))
+ sort.Strings(fSubdirs)
var mSubdirs []subdir
prevSubdir := ""
diff --git a/pkgtools/pkglint/files/files.go b/pkgtools/pkglint/files/files.go
index 46d08c83ffe..7997a97b803 100644
--- a/pkgtools/pkglint/files/files.go
+++ b/pkgtools/pkglint/files/files.go
@@ -26,7 +26,7 @@ func LoadExistingLines(fname string, joinBackslashLines bool) []Line {
return lines
}
-func getLogicalLine(fname string, rawLines []*RawLine, pindex *int) Line {
+func nextLogicalLine(fname string, rawLines []*RawLine, pindex *int) Line {
{ // Handle the common case efficiently
index := *pindex
rawLine := rawLines[index]
@@ -68,26 +68,30 @@ func getLogicalLine(fname string, rawLines []*RawLine, pindex *int) Line {
}
func splitRawLine(textnl string) (leadingWhitespace, text, trailingWhitespace, cont string) {
- i, m := 0, len(textnl)
+ end := len(textnl)
- if m > i && textnl[m-1] == '\n' {
- m--
+ if end-1 >= 0 && textnl[end-1] == '\n' {
+ end--
}
- if m > i && textnl[m-1] == '\\' {
- m--
- cont = textnl[m : m+1]
+ backslashes := 0
+ for end-1 >= 0 && textnl[end-1] == '\\' {
+ end--
+ backslashes++
}
+ cont = textnl[end : end+backslashes%2]
+ end += backslashes / 2
- trailingEnd := m
- for m > i && (textnl[m-1] == ' ' || textnl[m-1] == '\t') {
- m--
+ trailingEnd := end
+ for end-1 >= 0 && (textnl[end-1] == ' ' || textnl[end-1] == '\t') {
+ end--
}
- trailingStart := m
+ trailingStart := end
trailingWhitespace = textnl[trailingStart:trailingEnd]
+ i := 0
leadingStart := i
- for i < m && (textnl[i] == ' ' || textnl[i] == '\t') {
+ for i < end && (textnl[i] == ' ' || textnl[i] == '\t') {
i++
}
leadingEnd := i
@@ -117,7 +121,7 @@ func convertToLogicalLines(fname string, rawText string, joinBackslashLines bool
var loglines []Line
if joinBackslashLines {
for lineno := 0; lineno < len(rawLines); {
- loglines = append(loglines, getLogicalLine(fname, rawLines, &lineno))
+ loglines = append(loglines, nextLogicalLine(fname, rawLines, &lineno))
}
} else {
for _, rawLine := range rawLines {
diff --git a/pkgtools/pkglint/files/files_test.go b/pkgtools/pkglint/files/files_test.go
index 6bc857c65f8..255ab2ff33a 100644
--- a/pkgtools/pkglint/files/files_test.go
+++ b/pkgtools/pkglint/files/files_test.go
@@ -1,7 +1,7 @@
package main
import (
- check "gopkg.in/check.v1"
+ "gopkg.in/check.v1"
)
func (s *Suite) Test_convertToLogicalLines_no_continuation(c *check.C) {
@@ -29,6 +29,92 @@ func (s *Suite) Test_convertToLogicalLines_continuation(c *check.C) {
c.Check(lines[1].String(), equals, "fname_cont:3: third")
}
+// In Makefiles, comment lines can also have continuations.
+// See devel/bmake/files/unit-tests/comment.mk
+func (s *Suite) Test_convertToLogicalLines__comments(c *check.C) {
+ t := s.Init(c)
+
+ lines := t.SetupFileLinesContinuation("comment.mk",
+ "# This is a comment",
+ "",
+ "#\\",
+ "\tMultiline comment",
+ "# Another escaped comment \\",
+ "that \\",
+ "goes \\",
+ "on",
+ "# This is NOT an escaped comment due to the double backslashes \\\\",
+ "VAR=\tThis is not a comment",
+ "",
+ "#\\",
+ "This is a comment",
+ "#\\\\",
+ "This is no comment",
+ "#\\\\\\",
+ "This is a comment",
+ "#\\\\\\\\",
+ "This is no comment",
+ "#\\\\\\\\\\",
+ "This is a comment",
+ "#\\\\\\\\\\\\",
+ "This is no comment")
+
+ var texts []string
+ for _, line := range lines {
+ texts = append(texts, line.Text)
+ }
+
+ c.Check(texts, deepEquals, []string{
+ "# This is a comment",
+ "",
+ "# Multiline comment",
+ "# Another escaped comment that goes on",
+ "# This is NOT an escaped comment due to the double backslashes \\",
+ "VAR=\tThis is not a comment",
+ "",
+ "# This is a comment",
+ "#\\",
+ "This is no comment",
+ "#\\ This is a comment",
+ "#\\\\",
+ "This is no comment",
+ "#\\\\ This is a comment",
+ "#\\\\\\",
+ "This is no comment"})
+
+ var rawTexts []string
+ for _, line := range lines {
+ for _, rawLine := range line.raw {
+ rawTexts = append(rawTexts, rawLine.textnl)
+ }
+ }
+
+ c.Check(rawTexts, deepEquals, []string{
+ "# This is a comment\n",
+ "\n",
+ "#\\\n",
+ "\tMultiline comment\n",
+ "# Another escaped comment \\\n",
+ "that \\\n",
+ "goes \\\n",
+ "on\n",
+ "# This is NOT an escaped comment due to the double backslashes \\\\\n",
+ "VAR=\tThis is not a comment\n",
+ "\n",
+ "#\\\n",
+ "This is a comment\n",
+ "#\\\\\n",
+ "This is no comment\n",
+ "#\\\\\\\n",
+ "This is a comment\n",
+ "#\\\\\\\\\n",
+ "This is no comment\n",
+ "#\\\\\\\\\\\n",
+ "This is a comment\n",
+ "#\\\\\\\\\\\\\n",
+ "This is no comment\n"})
+}
+
func (s *Suite) Test_convertToLogicalLines_continuationInLastLine(c *check.C) {
t := s.Init(c)
diff --git a/pkgtools/pkglint/files/globaldata.go b/pkgtools/pkglint/files/globaldata.go
index 9af8b61a1b5..4646d3700ef 100644
--- a/pkgtools/pkglint/files/globaldata.go
+++ b/pkgtools/pkglint/files/globaldata.go
@@ -108,8 +108,8 @@ func (gd *GlobalData) loadDistSites() {
name2url := make(map[string]string)
url2name := make(map[string]string)
for _, line := range lines {
- if m, varname, _, _, _, urls, _, _ := MatchVarassign(line.Text); m {
- if hasPrefix(varname, "MASTER_SITE_") && varname != "MASTER_SITE_BACKUP" {
+ if m, commented, varname, _, _, _, urls, _, _ := MatchVarassign(line.Text); m {
+ if !commented && hasPrefix(varname, "MASTER_SITE_") && varname != "MASTER_SITE_BACKUP" {
for _, url := range splitOnSpace(urls) {
if matches(url, `^(?:http://|https://|ftp://)`) {
if name2url[varname] == "" {
@@ -187,8 +187,11 @@ func (gd *GlobalData) loadTools() {
lines := LoadExistingLines(fname, true)
for _, line := range lines {
text := line.Text
+ if hasPrefix(text, "#") {
+ continue
+ }
- if m, varname, _, _, _, value, _, _ := MatchVarassign(text); m {
+ if m, _, varname, _, _, _, value, _, _ := MatchVarassign(text); m {
if varname == "USE_TOOLS" {
if trace.Tracing {
trace.Stepf("[condDepth=%d] %s", condDepth, value)
@@ -618,7 +621,10 @@ func (tr *ToolRegistry) Trace() {
}
func (tr *ToolRegistry) ParseToolLine(line Line) {
- if m, varname, _, _, _, value, _, _ := MatchVarassign(line.Text); m {
+ if m, commented, varname, _, _, _, value, _, _ := MatchVarassign(line.Text); m {
+ if commented {
+ return
+ }
if varname == "TOOLS_CREATE" && (value == "[" || matches(value, `^?[-\w.]+$`)) {
tr.Register(value)
diff --git a/pkgtools/pkglint/files/histogram/histogram.go b/pkgtools/pkglint/files/histogram/histogram.go
index a915c382daf..ad802d949a9 100644
--- a/pkgtools/pkglint/files/histogram/histogram.go
+++ b/pkgtools/pkglint/files/histogram/histogram.go
@@ -19,6 +19,11 @@ func (h *Histogram) Add(s string, n int) {
}
func (h *Histogram) PrintStats(caption string, out io.Writer, limit int) {
+ type entry struct {
+ s string
+ count int
+ }
+
entries := make([]entry, len(h.histo))
i := 0
@@ -27,7 +32,11 @@ func (h *Histogram) PrintStats(caption string, out io.Writer, limit int) {
i++
}
- sort.Sort(byCountDesc(entries))
+ sort.SliceStable(entries, func(i, j int) bool {
+ ei := entries[i]
+ ej := entries[j]
+ return ej.count < ei.count || (ei.count == ej.count && ei.s < ej.s)
+ })
for i, entry := range entries {
fmt.Fprintf(out, "%s %6d %s\n", caption, entry.count, entry.s)
@@ -36,20 +45,3 @@ func (h *Histogram) PrintStats(caption string, out io.Writer, limit int) {
}
}
}
-
-type entry struct {
- s string
- count int
-}
-
-type byCountDesc []entry
-
-func (a byCountDesc) Len() int {
- return len(a)
-}
-func (a byCountDesc) Swap(i, j int) {
- a[i], a[j] = a[j], a[i]
-}
-func (a byCountDesc) Less(i, j int) bool {
- return a[j].count < a[i].count || (a[i].count == a[j].count && a[i].s < a[j].s)
-}
diff --git a/pkgtools/pkglint/files/linechecker.go b/pkgtools/pkglint/files/linechecker.go
index 37aaa989e4f..d6c53840772 100644
--- a/pkgtools/pkglint/files/linechecker.go
+++ b/pkgtools/pkglint/files/linechecker.go
@@ -54,7 +54,7 @@ func CheckLineTrailingWhitespace(line Line) {
fix.Explain(
"When a line ends with some white-space, that space is in most cases",
"irrelevant and can be removed.")
- fix.ReplaceRegex(`\s+\n$`, "\n")
+ fix.ReplaceRegex(`[ \t\r]+\n$`, "\n", 1)
fix.Apply()
}
}
diff --git a/pkgtools/pkglint/files/mkline.go b/pkgtools/pkglint/files/mkline.go
index 61ed55c58b5..7d7a1a619a9 100644
--- a/pkgtools/pkglint/files/mkline.go
+++ b/pkgtools/pkglint/files/mkline.go
@@ -16,6 +16,7 @@ type MkLineImpl struct {
data interface{} // One of the following mkLine* types
}
type mkLineAssign struct {
+ commented bool // Whether the whole variable assignment is commented out
varname string // e.g. "HOMEPAGE", "SUBST_SED.perl"
varcanon string // e.g. "HOMEPAGE", "SUBST_SED.*"
varparam string // e.g. "", "perl"
@@ -59,7 +60,7 @@ func NewMkLine(line Line) (mkline *MkLineImpl) {
"white-space.")
}
- if m, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment := MatchVarassign(text); m {
+ if m, commented, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment := MatchVarassign(text); m {
if G.opts.WarnSpace && spaceAfterVarname != "" {
switch {
case hasSuffix(varname, "+") && op == "=":
@@ -85,6 +86,7 @@ func NewMkLine(line Line) (mkline *MkLineImpl) {
varparam := varnameParam(varname)
mkline.data = mkLineAssign{
+ commented,
varname,
varnameCanon(varname),
varparam,
@@ -148,13 +150,46 @@ func NewMkLine(line Line) (mkline *MkLineImpl) {
func (mkline *MkLineImpl) String() string {
return fmt.Sprintf("%s:%s", mkline.Filename, mkline.Linenos())
}
-func (mkline *MkLineImpl) IsVarassign() bool { _, ok := mkline.data.(mkLineAssign); return ok }
-func (mkline *MkLineImpl) IsShellcmd() bool { _, ok := mkline.data.(mkLineShell); return ok }
-func (mkline *MkLineImpl) IsComment() bool { _, ok := mkline.data.(mkLineComment); return ok }
-func (mkline *MkLineImpl) IsEmpty() bool { _, ok := mkline.data.(mkLineEmpty); return ok }
+
+func (mkline *MkLineImpl) IsVarassign() bool {
+ data, ok := mkline.data.(mkLineAssign)
+ return ok && !data.commented
+}
+
+// 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.
+func (mkline *MkLineImpl) IsCommentedVarassign() bool {
+ data, ok := mkline.data.(mkLineAssign)
+ return ok && data.commented
+}
+
+// IsShellcmd returns true for tab-indented lines that are assigned to a Make
+// target. Example:
+//
+// pre-configure: # IsDependency
+// ${ECHO} # IsShellcmd
+func (mkline *MkLineImpl) IsShellcmd() bool {
+ _, ok := mkline.data.(mkLineShell)
+ return ok
+}
+
+func (mkline *MkLineImpl) IsComment() bool {
+ _, ok := mkline.data.(mkLineComment)
+ return ok || mkline.IsCommentedVarassign()
+}
+
+func (mkline *MkLineImpl) IsEmpty() bool {
+ _, ok := mkline.data.(mkLineEmpty)
+ return ok
+}
// IsCond checks whether the line is a conditional (.if/.ifelse/.else/.if) or a loop (.for/.endfor).
-func (mkline *MkLineImpl) IsCond() bool { _, ok := mkline.data.(mkLineConditional); return ok }
+func (mkline *MkLineImpl) IsCond() bool {
+ _, ok := mkline.data.(mkLineConditional)
+ return ok
+}
+
func (mkline *MkLineImpl) IsInclude() bool {
incl, ok := mkline.data.(mkLineInclude)
return ok && !incl.sys
@@ -163,11 +198,15 @@ func (mkline *MkLineImpl) IsSysinclude() bool {
incl, ok := mkline.data.(mkLineInclude)
return ok && incl.sys
}
-func (mkline *MkLineImpl) IsDependency() bool { _, ok := mkline.data.(mkLineDependency); return ok }
-func (mkline *MkLineImpl) Varname() string { return mkline.data.(mkLineAssign).varname }
-func (mkline *MkLineImpl) Varcanon() string { return mkline.data.(mkLineAssign).varcanon }
-func (mkline *MkLineImpl) Varparam() string { return mkline.data.(mkLineAssign).varparam }
-func (mkline *MkLineImpl) Op() MkOperator { return mkline.data.(mkLineAssign).op }
+func (mkline *MkLineImpl) IsDependency() bool {
+ _, ok := mkline.data.(mkLineDependency)
+ return ok
+}
+
+func (mkline *MkLineImpl) Varname() string { return mkline.data.(mkLineAssign).varname }
+func (mkline *MkLineImpl) Varcanon() string { return mkline.data.(mkLineAssign).varcanon }
+func (mkline *MkLineImpl) Varparam() string { return mkline.data.(mkLineAssign).varparam }
+func (mkline *MkLineImpl) Op() MkOperator { return mkline.data.(mkLineAssign).op }
// For a variable assignment, the text up to and including the assignment operator, e.g. VARNAME+=\t
func (mkline *MkLineImpl) ValueAlign() string { return mkline.data.(mkLineAssign).valueAlign }
@@ -356,6 +395,9 @@ func (mkline *MkLineImpl) VariableNeedsQuoting(varname string, vartype *Vartype,
if vartype.basicType.IsEnum() || vartype.IsBasicSafe() {
if vartype.kindOfList == lkNone {
+ if vartype.guessed {
+ return nqDontKnow
+ }
return nqDoesntMatter
}
if vartype.kindOfList == lkShell && !vuc.IsWordPart {
@@ -740,11 +782,16 @@ func (ind *Indentation) Varnames() string {
return varnames
}
-func MatchVarassign(text string) (m bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) {
+func MatchVarassign(text string) (m, commented bool, varname, spaceAfterVarname, op, valueAlign, value, spaceAfterValue, comment string) {
i, n := 0, len(text)
- for i < n && text[i] == ' ' {
+ if i < n && text[i] == '#' {
+ commented = true
i++
+ } else {
+ for i < n && text[i] == ' ' {
+ i++
+ }
}
varnameStart := i
diff --git a/pkgtools/pkglint/files/mkline_test.go b/pkgtools/pkglint/files/mkline_test.go
index 02d228d941d..1f70c197e11 100644
--- a/pkgtools/pkglint/files/mkline_test.go
+++ b/pkgtools/pkglint/files/mkline_test.go
@@ -644,6 +644,36 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__tool_in_CONFIGURE_ENV(c *check
"NOTE: Makefile:3: The :Q operator isn't necessary for ${TOOLS_TAR} here.")
}
+// For some well-known directory variables like WRKDIR, PREFIX, LOCALBASE,
+// the :Q modifier can be safely removed since pkgsrc will never support
+// having special characters in these directory names.
+// For guessed variable types be cautious and don't autofix them.
+func (s *Suite) Test_MkLine_variableNeedsQuoting__only_remove_known(c *check.C) {
+ t := s.Init(c)
+
+ t.SetupCommandLine("-Wall", "--autofix")
+ G.globalData.InitVartypes()
+
+ lines := t.SetupFileLinesContinuation("Makefile",
+ MkRcsId,
+ "",
+ "demo: .PHONY",
+ "\t${ECHO} ${WRKSRC:Q}",
+ "\t${ECHO} ${FOODIR:Q}")
+ mklines := NewMkLines(lines)
+
+ mklines.Check()
+
+ t.CheckOutputLines(
+ "AUTOFIX: ~/Makefile:4: Replacing \"${WRKSRC:Q}\" with \"${WRKSRC}\".")
+ t.CheckFileLines("Makefile",
+ MkRcsId,
+ "",
+ "demo: .PHONY",
+ "\t${ECHO} ${WRKSRC}",
+ "\t${ECHO} ${FOODIR:Q}")
+}
+
func (s *Suite) Test_MkLine_Pkgmandir(c *check.C) {
t := s.Init(c)
@@ -750,38 +780,46 @@ func (s *Suite) Test_MkLine_ConditionVars(c *check.C) {
func (s *Suite) Test_MatchVarassign(c *check.C) {
s.Init(c)
- checkVarassign := func(text string, ck check.Checker, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string) {
+ checkVarassign := func(text string, commented bool, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string) {
type VarAssign struct {
+ commented bool
varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment string
}
- expected := VarAssign{varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment}
- am, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment := MatchVarassign(text)
+ expected := VarAssign{commented, varname, spaceAfterVarname, op, align, value, spaceAfterValue, comment}
+ am, acommented, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment := MatchVarassign(text)
if !am {
c.Errorf("Text %q doesn't match variable assignment", text)
return
}
- actual := VarAssign{avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment}
- c.Check(actual, ck, expected)
+ actual := VarAssign{acommented, avarname, aspaceAfterVarname, aop, aalign, avalue, aspaceAfterValue, acomment}
+ c.Check(actual, equals, expected)
}
checkNotVarassign := func(text string) {
- m, _, _, _, _, _, _, _ := MatchVarassign(text)
+ m, _, _, _, _, _, _, _, _ := MatchVarassign(text)
if m {
c.Errorf("Text %q matches variable assignment, but shouldn't.", text)
}
}
- checkVarassign("C++=c11", equals, "C+", "", "+=", "C++=", "c11", "", "")
- checkVarassign("V=v", equals, "V", "", "=", "V=", "v", "", "")
- checkVarassign("VAR=#comment", equals, "VAR", "", "=", "VAR=", "", "", "#comment")
- checkVarassign("VAR=\\#comment", equals, "VAR", "", "=", "VAR=", "#comment", "", "")
- checkVarassign("VAR=\\\\\\##comment", equals, "VAR", "", "=", "VAR=", "\\\\#", "", "#comment")
- checkVarassign("VAR=\\", equals, "VAR", "", "=", "VAR=", "\\", "", "")
- checkVarassign("VAR += value", equals, "VAR", " ", "+=", "VAR += ", "value", "", "")
- checkVarassign(" VAR=value", equals, "VAR", "", "=", " VAR=", "value", "", "")
- checkVarassign("VAR=value #comment", equals, "VAR", "", "=", "VAR=", "value", " ", "#comment")
+ checkVarassign("C++=c11", false, "C+", "", "+=", "C++=", "c11", "", "")
+ checkVarassign("V=v", false, "V", "", "=", "V=", "v", "", "")
+ checkVarassign("VAR=#comment", false, "VAR", "", "=", "VAR=", "", "", "#comment")
+ checkVarassign("VAR=\\#comment", false, "VAR", "", "=", "VAR=", "#comment", "", "")
+ checkVarassign("VAR=\\\\\\##comment", false, "VAR", "", "=", "VAR=", "\\\\#", "", "#comment")
+ checkVarassign("VAR=\\", false, "VAR", "", "=", "VAR=", "\\", "", "")
+ checkVarassign("VAR += value", false, "VAR", " ", "+=", "VAR += ", "value", "", "")
+ checkVarassign(" VAR=value", false, "VAR", "", "=", " VAR=", "value", "", "")
+ checkVarassign("VAR=value #comment", false, "VAR", "", "=", "VAR=", "value", " ", "#comment")
+ checkVarassign("#VAR=value", true, "VAR", "", "=", "#VAR=", "value", "", "")
+
checkNotVarassign("\tVAR=value")
checkNotVarassign("?=value")
checkNotVarassign("<=value")
+ checkNotVarassign("#")
+
+ // A single space is typically used for writing documentation,
+ // not for commenting out code.
+ checkNotVarassign("# VAR=value")
}
func (s *Suite) Test_Indentation(c *check.C) {
diff --git a/pkgtools/pkglint/files/mklinechecker.go b/pkgtools/pkglint/files/mklinechecker.go
index 66ff5f884c3..8ce532f81e4 100644
--- a/pkgtools/pkglint/files/mklinechecker.go
+++ b/pkgtools/pkglint/files/mklinechecker.go
@@ -191,7 +191,7 @@ func (ck MkLineChecker) checkDirectiveIndentation(expectedDepth int) {
if expected := strings.Repeat(" ", expectedDepth); indent != expected {
fix := mkline.Line.Autofix()
fix.Notef("This directive should be indented by %d spaces.", expectedDepth)
- fix.Replace("."+indent, "."+expected)
+ fix.ReplaceRegex(regex.Pattern(`^\.`+indent), "."+expected, 1)
fix.Apply()
}
}
diff --git a/pkgtools/pkglint/files/mklinechecker_test.go b/pkgtools/pkglint/files/mklinechecker_test.go
index f7134677e65..2807eb41655 100644
--- a/pkgtools/pkglint/files/mklinechecker_test.go
+++ b/pkgtools/pkglint/files/mklinechecker_test.go
@@ -322,3 +322,34 @@ func (s *Suite) Test_MkLineChecker_CheckVartype_CFLAGS(c *check.C) {
"WARN: Makefile:2: Unknown compiler flag \"-bs\".",
"WARN: Makefile:2: Compiler flag \"%s\\\\\\\"\" should start with a hyphen.")
}
+
+// Up to 2018-01-28, pkglint applied the autofix also to the continuation
+// lines, which is incorrect. It replaced the dot in "4.*" with spaces.
+func (s *Suite) Test_MkLineChecker_checkDirectiveIndentation_autofix(c *check.C) {
+ t := s.Init(c)
+
+ t.SetupCommandLine("-Wall", "--autofix")
+ G.globalData.InitVartypes()
+ lines := t.SetupFileLinesContinuation("options.mk",
+ MkRcsId,
+ ".if ${PKGNAME} == pkgname",
+ ".if \\",
+ " ${PLATFORM:MNetBSD-4.*}",
+ ".endif",
+ ".endif")
+ mklines := NewMkLines(lines)
+
+ mklines.Check()
+
+ t.CheckOutputLines(
+ "AUTOFIX: ~/options.mk:3: Replacing \".\" with \". \".",
+ "AUTOFIX: ~/options.mk:5: Replacing \".\" with \". \".")
+
+ t.CheckFileLines("options.mk",
+ MkRcsId,
+ ".if ${PKGNAME} == pkgname",
+ ". if \\",
+ " ${PLATFORM:MNetBSD-4.*}",
+ ". endif",
+ ".endif")
+}
diff --git a/pkgtools/pkglint/files/mklines.go b/pkgtools/pkglint/files/mklines.go
index 262d713ee41..a23f9c366b2 100644
--- a/pkgtools/pkglint/files/mklines.go
+++ b/pkgtools/pkglint/files/mklines.go
@@ -260,28 +260,42 @@ func (va *VaralignBlock) Check(mkline MkLine) {
case !G.opts.WarnSpace:
return
- case mkline.IsComment():
+ case mkline.IsEmpty():
+ va.Finish()
return
- case mkline.IsCond():
+ case mkline.IsCommentedVarassign():
+ break
+
+ case mkline.IsComment():
return
- case mkline.IsEmpty():
- va.Finish()
+ case mkline.IsCond():
return
case !mkline.IsVarassign():
trace.Stepf("Skipping")
va.skip = true
return
+ }
+ 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
}
@@ -289,7 +303,7 @@ func (va *VaralignBlock) Check(mkline MkLine) {
if mkline.IsMultiline() {
// Interpreting the continuation marker as variable value
// is cheating, but works well.
- m, _, _, _, _, value, _, _ := MatchVarassign(mkline.raw[0].orignl)
+ m, _, _, _, _, _, value, _, _ := MatchVarassign(mkline.raw[0].orignl)
continuation = m && value == "\\"
}
@@ -330,8 +344,8 @@ func (va *VaralignBlock) Finish() {
// 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
- secondLongest := 0
+ longest := 0 // The longest seen varnameOpWidth
+ secondLongest := 0 // The second-longest seen varnameOpWidth
for _, info := range infos {
if info.continuation {
continue
diff --git a/pkgtools/pkglint/files/mklines_varalign_test.go b/pkgtools/pkglint/files/mklines_varalign_test.go
index 2fffca84214..894b48be7cf 100755
--- a/pkgtools/pkglint/files/mklines_varalign_test.go
+++ b/pkgtools/pkglint/files/mklines_varalign_test.go
@@ -850,8 +850,8 @@ func (s *Suite) Test_Varalign__indented_continuation_line_in_paragraph(c *check.
}
// Up to 2018-01-27, it could happen that some source code was logged
-// without a corresponding diagnostic. This was unintented and confusing.
-func (s *Suite) Test_Varalign__bla(c *check.C) {
+// without a corresponding diagnostic. This was unintended and confusing.
+func (s *Suite) Test_Varalign__fix_without_diagnostic(c *check.C) {
vt := NewVaralignTester(s, c)
vt.Input(
"MESSAGE_SUBST+=\t\tRUBY_DISTNAME=${RUBY_DISTNAME}",
@@ -870,3 +870,114 @@ func (s *Suite) Test_Varalign__bla(c *check.C) {
vt.source = 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_Varalign__continuation_line_last_empty(c *check.C) {
+ vt := NewVaralignTester(s, c)
+ vt.Input(
+ "DISTFILES= \\",
+ "\ta \\",
+ "\tb \\",
+ "\tc \\",
+ "",
+ "NEXT_VAR=\tmust not be indented")
+ vt.Diagnostics(
+ "NOTE: ~/Makefile:1--5: 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= must not be indented")
+ 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.
+func (s *Suite) Test_Varalign__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.Diagnostics(
+ "NOTE: ~/Makefile:1: This variable value should be aligned to column 17.",
+ "NOTE: ~/Makefile:3--4: This variable value should be aligned with tabs, not spaces, to column 17.",
+ "NOTE: ~/Makefile:5--6: This line should be aligned 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 indentation \"\" 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()
+}
+
+func (s *Suite) Test_Varalign__realign_commented_continuation_line(c *check.C) {
+ vt := NewVaralignTester(s, c)
+ vt.Input(
+ "BEFORE=\tvalue",
+ "#COMMENTED= \\",
+ "#\tvalue1 \\",
+ "#\tvalue2 \\",
+ "#\tvalue3 \\",
+ "AFTER=\tafter") // This line continues the comment.
+ vt.Diagnostics()
+ vt.Autofixes()
+ 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.
+func (s *Suite) Test_Varalign__realign_variable_without_value(c *check.C) {
+ vt := NewVaralignTester(s, c)
+ vt.Input(
+ "COMMENT=\t\tShort description of the package",
+ "#HOMEPAGE=")
+ 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.
+func (s *Suite) Test_Varalign__realign_commented_multiline(c *check.C) {
+ vt := NewVaralignTester(s, c)
+ vt.Input(
+ "#CONF_FILES+=\t\tfile1 \\",
+ "#\t\t\tfile2")
+ vt.Diagnostics()
+ vt.Autofixes()
+ vt.Fixed(
+ "#CONF_FILES+= file1 \\",
+ "# file2")
+ vt.Run()
+}
diff --git a/pkgtools/pkglint/files/patches.go b/pkgtools/pkglint/files/patches.go
index e1b6805076c..19099827ace 100644
--- a/pkgtools/pkglint/files/patches.go
+++ b/pkgtools/pkglint/files/patches.go
@@ -37,6 +37,11 @@ func (ck *PatchChecker) Check() {
if CheckLineRcsid(ck.lines[0], ``, "") {
ck.exp.Advance()
}
+ if ck.exp.EOF() {
+ ck.lines[0].Errorf("Patch files must not be empty.")
+ return
+ }
+
ck.previousLineEmpty = ck.exp.ExpectEmptyLine(G.opts.WarnSpace)
patchedFiles := 0
diff --git a/pkgtools/pkglint/files/patches_test.go b/pkgtools/pkglint/files/patches_test.go
index 419cf843fb6..4da652b66bc 100644
--- a/pkgtools/pkglint/files/patches_test.go
+++ b/pkgtools/pkglint/files/patches_test.go
@@ -408,3 +408,30 @@ func (s *Suite) Test_ChecklinesPatch__context_lines_with_tab_instead_of_space(c
t.CheckOutputEmpty()
}
+
+// Must not panic.
+func (s *Suite) Test_ChecklinesPatch__autofix_empty_patch(c *check.C) {
+ t := s.Init(c)
+
+ t.SetupCommandLine("-Wall", "--autofix")
+ lines := t.NewLines("patch-aa",
+ RcsId)
+
+ ChecklinesPatch(lines)
+
+ t.CheckOutputEmpty()
+}
+
+// Must not panic.
+func (s *Suite) Test_ChecklinesPatch__autofix_long_empty_patch(c *check.C) {
+ t := s.Init(c)
+
+ t.SetupCommandLine("-Wall", "--autofix")
+ lines := t.NewLines("patch-aa",
+ RcsId,
+ "")
+
+ ChecklinesPatch(lines)
+
+ t.CheckOutputEmpty()
+}
diff --git a/pkgtools/pkglint/files/pkglint.go b/pkgtools/pkglint/files/pkglint.go
index 03b9c1e749a..a20b46a11da 100644
--- a/pkgtools/pkglint/files/pkglint.go
+++ b/pkgtools/pkglint/files/pkglint.go
@@ -308,37 +308,45 @@ func ChecklinesMessage(lines []Line) {
defer trace.Call1(lines[0].Filename)()
}
- explainMessage := func() {
- Explain(
- "A MESSAGE file should consist of a header line, having 75 \"=\"",
- "characters, followed by a line containing only the RCS Id, then an",
- "empty line, your text and finally the footer line, which is the",
- "same as the header line.")
- }
+ explanation := []string{
+ "A MESSAGE file should consist of a header line, having 75 \"=\"",
+ "characters, followed by a line containing only the RCS Id, then an",
+ "empty line, your text and finally the footer line, which is the",
+ "same as the header line."}
if len(lines) < 3 {
lastLine := lines[len(lines)-1]
lastLine.Warnf("File too short.")
- explainMessage()
+ Explain(explanation...)
return
}
hline := strings.Repeat("=", 75)
if line := lines[0]; line.Text != hline {
- line.Warnf("Expected a line of exactly 75 \"=\" characters.")
- explainMessage()
+ fix := line.Autofix()
+ fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
+ fix.Explain(explanation...)
+ fix.InsertBefore(hline)
+ fix.Apply()
+ CheckLineRcsid(lines[0], ``, "")
+ } else if 1 < len(lines) {
+ CheckLineRcsid(lines[1], ``, "")
}
- CheckLineRcsid(lines[1], ``, "")
for _, line := range lines {
CheckLineLength(line, 80)
CheckLineTrailingWhitespace(line)
CheckLineValidCharacters(line, `[\t -~]`)
}
if lastLine := lines[len(lines)-1]; lastLine.Text != hline {
- lastLine.Warnf("Expected a line of exactly 75 \"=\" characters.")
- explainMessage()
+ fix := lastLine.Autofix()
+ fix.Warnf("Expected a line of exactly 75 \"=\" characters.")
+ fix.Explain(explanation...)
+ fix.InsertAfter(hline)
+ fix.Apply()
}
ChecklinesTrailingEmptyLines(lines)
+
+ SaveAutofixChanges(lines)
}
func CheckfileMk(fname string) {
diff --git a/pkgtools/pkglint/files/pkglint_test.go b/pkgtools/pkglint/files/pkglint_test.go
index b12fb0bfd01..cf4e9216210 100644
--- a/pkgtools/pkglint/files/pkglint_test.go
+++ b/pkgtools/pkglint/files/pkglint_test.go
@@ -186,10 +186,40 @@ func (s *Suite) Test_ChecklinesMessage__malformed(c *check.C) {
t.CheckOutputLines(
"WARN: MESSAGE:1: Expected a line of exactly 75 \"=\" characters.",
- "ERROR: MESSAGE:2: Expected \"$"+"NetBSD$\".",
+ "ERROR: MESSAGE:1: Expected \"$"+"NetBSD$\".",
"WARN: MESSAGE:5: Expected a line of exactly 75 \"=\" characters.")
}
+func (s *Suite) Test_ChecklinesMessage__autofix(c *check.C) {
+ t := s.Init(c)
+
+ t.SetupCommandLine("-Wall", "--autofix")
+ lines := t.SetupFileLines("MESSAGE",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5")
+
+ ChecklinesMessage(lines)
+
+ t.CheckOutputLines(
+ "AUTOFIX: ~/MESSAGE:1: Inserting a line \"===================================="+
+ "=======================================\" before this line.",
+ "AUTOFIX: ~/MESSAGE:1: Inserting a line \"$NetBSD: pkglint_test.go,v 1.14 2018/01/28 23:21:16 rillig Exp $\" before this line.",
+ "AUTOFIX: ~/MESSAGE:5: Inserting a line \"===================================="+
+ "=======================================\" after this line.")
+ t.CheckFileLines("MESSAGE",
+ "===========================================================================",
+ "$NetBSD: pkglint_test.go,v 1.14 2018/01/28 23:21:16 rillig Exp $",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "===========================================================================")
+}
+
func (s *Suite) Test_GlobalData_Latest(c *check.C) {
t := s.Init(c)
diff --git a/pkgtools/pkglint/files/plist.go b/pkgtools/pkglint/files/plist.go
index 2d94fef0be2..b961ab17667 100644
--- a/pkgtools/pkglint/files/plist.go
+++ b/pkgtools/pkglint/files/plist.go
@@ -101,7 +101,7 @@ func (ck *PlistChecker) collectFilesAndDirs(plines []*PlistLine) {
first == '$',
'A' <= first && first <= 'Z',
'0' <= first && first <= '9':
- if ck.allFiles[text] == nil {
+ if prev := ck.allFiles[text]; prev == nil || pline.conditional < prev.conditional {
ck.allFiles[text] = pline
}
for dir := path.Dir(text); dir != "."; dir = path.Dir(dir) {
@@ -143,6 +143,7 @@ func (ck *PlistChecker) checkpath(pline *PlistLine) {
dirname := strings.TrimSuffix(sdirname, "/")
ck.checkSorted(pline)
+ ck.checkDuplicate(pline)
if contains(basename, "${IMAKE_MANNEWSUFFIX}") {
pline.warnImakeMannewsuffix()
@@ -211,17 +212,29 @@ func (ck *PlistChecker) checkSorted(pline *PlistLine) {
"The files in the PLIST should be sorted alphabetically.",
"To fix this, run \"pkglint -F PLIST\".")
}
- if prev := ck.allFiles[text]; prev != nil && prev != pline {
- fix := pline.line.Autofix()
- fix.Errorf("Duplicate filename %q, already appeared in %s.", text, prev.line.ReferenceFrom(pline.line))
- fix.Delete()
- fix.Apply()
- }
}
ck.lastFname = text
}
}
+func (ck *PlistChecker) checkDuplicate(pline *PlistLine) {
+ text := pline.text
+ if !hasAlnumPrefix(text) || containsVarRef(text) {
+ return
+ }
+
+ prev := ck.allFiles[text]
+ if prev == pline || prev.conditional != "" {
+ return
+ }
+
+ fix := pline.line.Autofix()
+ fix.Errorf("Duplicate filename %q, already appeared in %s.",
+ text, prev.line.ReferenceFrom(pline.line))
+ fix.Delete()
+ fix.Apply()
+}
+
func (ck *PlistChecker) checkpathBin(pline *PlistLine, dirname, basename string) {
if contains(dirname, "/") {
pline.line.Warnf("The bin/ directory should not have subdirectories.")
@@ -279,7 +292,8 @@ func (ck *PlistChecker) checkpathLib(pline *PlistLine, dirname, basename string)
if contains(basename, ".a") || contains(basename, ".so") {
if m, noext := match1(pline.text, `^(.*)(?:\.a|\.so[0-9.]*)$`); m {
if laLine := ck.allFiles[noext+".la"]; laLine != nil {
- pline.line.Warnf("Redundant library found. The libtool library is in %s.", laLine.line.ReferenceFrom(pline.line))
+ pline.line.Warnf("Redundant library found. The libtool library is in %s.",
+ laLine.line.ReferenceFrom(pline.line))
}
}
}
@@ -320,7 +334,7 @@ func (ck *PlistChecker) checkpathMan(pline *PlistLine) {
"configured by the pkgsrc user. Compression and decompression takes",
"place automatically, no matter if the .gz extension is mentioned in",
"the PLIST or not.")
- fix.ReplaceRegex(`\.gz\n`, "\n")
+ fix.ReplaceRegex(`\.gz\n`, "\n", 1)
fix.Apply()
}
}
@@ -493,17 +507,6 @@ func NewPlistLineSorter(plines []*PlistLine) *plistLineSorter {
return &plistLineSorter{header, middle, footer, unsortable, false, false}
}
-func (s *plistLineSorter) Len() int {
- return len(s.middle)
-}
-func (s *plistLineSorter) Less(i, j int) bool {
- return s.middle[i].text < s.middle[j].text
-}
-func (s *plistLineSorter) Swap(i, j int) {
- s.changed = true
- s.middle[i], s.middle[j] = s.middle[j], s.middle[i]
-}
-
func (s *plistLineSorter) Sort() {
if line := s.unsortable; line != nil {
if G.opts.PrintAutofix || G.opts.Autofix {
@@ -516,7 +519,17 @@ func (s *plistLineSorter) Sort() {
return
}
firstLine := s.middle[0].line
- sort.Stable(s)
+
+ sort.SliceStable(s.middle, func(i, j int) bool {
+ mi := s.middle[i]
+ mj := s.middle[j]
+ less := mi.text < mj.text || (mi.text == mj.text &&
+ mi.conditional < mj.conditional)
+ if (i < j) != less {
+ s.changed = true
+ }
+ return less
+ })
if !s.changed {
return
diff --git a/pkgtools/pkglint/files/plist_test.go b/pkgtools/pkglint/files/plist_test.go
index 198ab529904..19f5762b954 100644
--- a/pkgtools/pkglint/files/plist_test.go
+++ b/pkgtools/pkglint/files/plist_test.go
@@ -292,3 +292,47 @@ func (s *Suite) Test_PlistChecker__autofix(c *check.C) {
"@pkgdir etc/logrotate.d",
"@pkgdir etc/sasl2")
}
+
+// When the same entry appears both with and without a conditional,
+// the one with the conditional can be removed.
+// When the same entry appears with several different conditionals,
+// all of them must stay.
+func (s *Suite) Test_PlistChecker__remove_same_entries(c *check.C) {
+ t := s.Init(c)
+
+ t.SetupCommandLine("-Wall")
+ lines := t.SetupFileLines("PLIST",
+ PlistRcsId,
+ "${PLIST.option1}bin/true",
+ "bin/true",
+ "${PLIST.option1}bin/true",
+ "bin/true",
+ "${PLIST.option3}bin/false",
+ "${PLIST.option2}bin/false",
+ "bin/true")
+
+ ChecklinesPlist(lines)
+
+ t.CheckOutputLines(
+ "ERROR: ~/PLIST:2: Duplicate filename \"bin/true\", already appeared in line 3.",
+ "ERROR: ~/PLIST:4: Duplicate filename \"bin/true\", already appeared in line 3.",
+ "ERROR: ~/PLIST:5: Duplicate filename \"bin/true\", already appeared in line 3.",
+ "WARN: ~/PLIST:6: \"bin/false\" should be sorted before \"bin/true\".",
+ "ERROR: ~/PLIST:8: Duplicate filename \"bin/true\", already appeared in line 3.")
+
+ t.SetupCommandLine("-Wall", "--autofix")
+
+ ChecklinesPlist(lines)
+
+ t.CheckOutputLines(
+ "AUTOFIX: ~/PLIST:2: Deleting this line.",
+ "AUTOFIX: ~/PLIST:4: Deleting this line.",
+ "AUTOFIX: ~/PLIST:5: Deleting this line.",
+ "AUTOFIX: ~/PLIST:8: Deleting this line.",
+ "AUTOFIX: ~/PLIST:2: Sorting the whole file.")
+ t.CheckFileLines("PLIST",
+ PlistRcsId,
+ "${PLIST.option2}bin/false",
+ "${PLIST.option3}bin/false",
+ "bin/true")
+}