pkgtools/pkglint: update to 19.4.7 Changes since 19.4.6: HOMEPAGE definitions that use http no longer get a warning that they should migrate to https. Those that could be migrated have been migrated, and the remaining homepages are not yet ready, so there's no benefit in having this warning by default. Only in --network mode and when the https site is indeed reachable, the warning is shown. In long continued lines, the continuation backslash should be preceded by a single space. Pkglint had wrongly removed that space in a few cases before.diff -r1.629 -r1.630 pkgsrc/pkgtools/pkglint/Makefile
(rillig)
@@ -1,17 +1,16 @@ | @@ -1,17 +1,16 @@ | |||
1 | # $NetBSD: Makefile,v 1.629 2020/02/02 14:19:09 bsiegert Exp $ | 1 | # $NetBSD: Makefile,v 1.630 2020/02/05 04:09:00 rillig Exp $ | |
2 | 2 | |||
3 | PKGNAME= pkglint-19.4.6 | 3 | PKGNAME= pkglint-19.4.7 | |
4 | PKGREVISION= 1 | |||
5 | CATEGORIES= pkgtools | 4 | CATEGORIES= pkgtools | |
6 | DISTNAME= tools | 5 | DISTNAME= tools | |
7 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | 6 | MASTER_SITES= ${MASTER_SITE_GITHUB:=golang/} | |
8 | GITHUB_PROJECT= tools | 7 | GITHUB_PROJECT= tools | |
9 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | 8 | GITHUB_TAG= 92d8274bd7b8a4c65f24bafe401a029e58392704 | |
10 | 9 | |||
11 | MAINTAINER= rillig@NetBSD.org | 10 | MAINTAINER= rillig@NetBSD.org | |
12 | HOMEPAGE= https://github.com/rillig/pkglint | 11 | HOMEPAGE= https://github.com/rillig/pkglint | |
13 | COMMENT= Verifier for NetBSD packages | 12 | COMMENT= Verifier for NetBSD packages | |
14 | LICENSE= 2-clause-bsd | 13 | LICENSE= 2-clause-bsd | |
15 | CONFLICTS+= pkglint4-[0-9]* | 14 | CONFLICTS+= pkglint4-[0-9]* | |
16 | 15 | |||
17 | USE_TOOLS+= pax | 16 | USE_TOOLS+= pax |
@@ -30,30 +30,35 @@ import ( | @@ -30,30 +30,35 @@ import ( | |||
30 | // - https://sf.net/projects/$project/ | 30 | // - https://sf.net/projects/$project/ | |
31 | // - http://sf.net/projects/$project/ | 31 | // - http://sf.net/projects/$project/ | |
32 | // - https://sf.net/p/$project/ | 32 | // - https://sf.net/p/$project/ | |
33 | // - http://sf.net/p/$project/ | 33 | // - http://sf.net/p/$project/ | |
34 | // | 34 | // | |
35 | // TODO: implement complete homepage migration for SourceForge. | 35 | // TODO: implement complete homepage migration for SourceForge. | |
36 | // TODO: allow to suppress the automatic migration for SourceForge, | 36 | // TODO: allow to suppress the automatic migration for SourceForge, | |
37 | // even if it is not about https vs. http. | 37 | // even if it is not about https vs. http. | |
38 | type HomepageChecker struct { | 38 | type HomepageChecker struct { | |
39 | Value string | 39 | Value string | |
40 | ValueNoVar string | 40 | ValueNoVar string | |
41 | MkLine *MkLine | 41 | MkLine *MkLine | |
42 | MkLines *MkLines | 42 | MkLines *MkLines | |
43 | ||||
44 | // Can be mocked for the tests. | |||
45 | isReachable func(url string) YesNoUnknown | |||
43 | } | 46 | } | |
44 | 47 | |||
45 | func NewHomepageChecker(value string, valueNoVar string, mkline *MkLine, mklines *MkLines) *HomepageChecker { | 48 | func NewHomepageChecker(value string, valueNoVar string, mkline *MkLine, mklines *MkLines) *HomepageChecker { | |
46 | return &HomepageChecker{value, valueNoVar, mkline, mklines} | 49 | ck := HomepageChecker{value, valueNoVar, mkline, mklines, nil} | |
50 | ck.isReachable = ck.isReachableOnline | |||
51 | return &ck | |||
47 | } | 52 | } | |
48 | 53 | |||
49 | func (ck *HomepageChecker) Check() { | 54 | func (ck *HomepageChecker) Check() { | |
50 | ck.checkBasedOnMasterSites() | 55 | ck.checkBasedOnMasterSites() | |
51 | ck.checkFtp() | 56 | ck.checkFtp() | |
52 | ck.checkHttp() | 57 | ck.checkHttp() | |
53 | ck.checkBadUrls() | 58 | ck.checkBadUrls() | |
54 | ck.checkReachable() | 59 | ck.checkReachable() | |
55 | } | 60 | } | |
56 | 61 | |||
57 | func (ck *HomepageChecker) checkBasedOnMasterSites() { | 62 | func (ck *HomepageChecker) checkBasedOnMasterSites() { | |
58 | m, wrong, sitename, subdir := match3(ck.Value, `^(\$\{(MASTER_SITE\w+)(?::=([\w\-/]+))?\})`) | 63 | m, wrong, sitename, subdir := match3(ck.Value, `^(\$\{(MASTER_SITE\w+)(?::=([\w\-/]+))?\})`) | |
59 | if !m { | 64 | if !m { | |
@@ -109,133 +114,127 @@ func (ck *HomepageChecker) checkFtp() { | @@ -109,133 +114,127 @@ func (ck *HomepageChecker) checkFtp() { | |||
109 | mkline.Explain( | 114 | mkline.Explain( | |
110 | "This homepage URL has probably been generated by url2pkg", | 115 | "This homepage URL has probably been generated by url2pkg", | |
111 | "and not been reviewed by the package author.", | 116 | "and not been reviewed by the package author.", | |
112 | "", | 117 | "", | |
113 | "In most cases there exists a more welcoming URL,", | 118 | "In most cases there exists a more welcoming URL,", | |
114 | "which is usually served via HTTP.") | 119 | "which is usually served via HTTP.") | |
115 | } | 120 | } | |
116 | 121 | |||
117 | func (ck *HomepageChecker) checkHttp() { | 122 | func (ck *HomepageChecker) checkHttp() { | |
118 | if ck.MkLine.HasRationale("http", "https") { | 123 | if ck.MkLine.HasRationale("http", "https") { | |
119 | return | 124 | return | |
120 | } | 125 | } | |
121 | 126 | |||
122 | shouldAutofix, from, to := ck.toHttps(ck.Value) | 127 | migrate, from, to := ck.migrate(ck.Value) | |
123 | if from == "" { | 128 | if !migrate { | |
124 | return | 129 | return | |
125 | } | 130 | } | |
126 | 131 | |||
127 | fix := ck.MkLine.Autofix() | 132 | fix := ck.MkLine.Autofix() | |
128 | fix.Warnf("HOMEPAGE should migrate from %s to %s.", from, to) | 133 | fix.Warnf("HOMEPAGE should migrate from %s to %s.", from, to) | |
129 | if shouldAutofix { | 134 | fix.Replace(from, to) | |
130 | fix.Replace(from, to) | 135 | if from == "http" && to == "https" { | |
136 | fix.Explain( | |||
137 | "To provide secure communication by default,", | |||
138 | "the HOMEPAGE URL should use the https protocol if available.", | |||
139 | "", | |||
140 | "If the HOMEPAGE really does not support https,", | |||
141 | "add a comment near the HOMEPAGE variable stating this clearly.") | |||
131 | } | 142 | } | |
132 | fix.Explain( | |||
133 | "To provide secure communication by default,", | |||
134 | "the HOMEPAGE URL should use the https protocol if available.", | |||
135 | "", | |||
136 | "If the HOMEPAGE really does not support https,", | |||
137 | "add a comment near the HOMEPAGE variable stating this clearly.") | |||
138 | fix.Apply() | 143 | fix.Apply() | |
139 | } | 144 | } | |
140 | 145 | |||
141 | // toHttps checks whether the homepage should be migrated from http to https | 146 | // migrate checks whether the homepage should be migrated from http to https | |
142 | // and which part of the homepage URL needs to be modified for that. | 147 | // and which part of the homepage URL needs to be modified for that. | |
143 | // | 148 | // | |
144 | // If for some reason the https URL should not be reachable but the | 149 | // If for some reason the https URL should not be reachable but the | |
145 | // corresponding http URL is, the homepage is changed back to http. | 150 | // corresponding http URL is, the homepage is changed back to http. | |
146 | func (ck *HomepageChecker) toHttps(url string) (bool, string, string) { | 151 | func (ck *HomepageChecker) migrate(url string) (bool, string, string) { | |
147 | m, scheme, host, port := match3(url, `(https?)://([A-Za-z0-9-.]+)(:[0-9]+)?`) | 152 | m, scheme, host := match2(url, `(https?)://([A-Za-z0-9-.]+)(?:/|$)?`) | |
148 | if !m { | 153 | if !m || containsVarRefLong(url) { | |
149 | return false, "", "" | 154 | return false, "", "" | |
150 | } | 155 | } | |
151 | 156 | |||
152 | if ck.hasAnySuffix(host, | 157 | if scheme == "http" && ck.hasAnySuffix(host, | |
153 | "www.gnustep.org", // 2020-01-18 | 158 | "www.gnustep.org", // no https as of 2020-01-18 | |
154 | "aspell.net", // 2020-01-18 | 159 | "aspell.net", // no https as of 2020-01-18 | |
155 | "downloads.sourceforge.net", // gets another warning already | 160 | "downloads.sourceforge.net", // gets another warning already | |
156 | ".dl.sourceforge.net", // gets another warning already | 161 | "dl.sourceforge.net", // gets another warning already | |
157 | ) { | 162 | ) { | |
158 | return false, "", "" | 163 | return false, "", "" | |
159 | } | 164 | } | |
160 | 165 | |||
161 | if scheme == "http" && ck.hasAnySuffix(host, | 166 | if scheme == "http" && ck.hasAnySuffix(host, | |
162 | "apache.org", | 167 | "apache.org", | |
163 | "archive.org", | 168 | "archive.org", | |
164 | "ctan.org", | 169 | "ctan.org", | |
165 | "freedesktop.org", | 170 | "freedesktop.org", | |
166 | "github.com", | 171 | "github.com", | |
167 | "github.io", | 172 | "github.io", | |
168 | "gnome.org", | 173 | "gnome.org", | |
169 | "gnu.org", | 174 | "gnu.org", | |
170 | "kde.org", | 175 | "kde.org", | |
171 | "kldp.net", | 176 | "kldp.net", | |
172 | "linuxfoundation.org", | 177 | "linuxfoundation.org", | |
173 | "NetBSD.org", | 178 | "NetBSD.org", | |
174 | "nongnu.org", | 179 | "nongnu.org", | |
175 | "tryton.org", | 180 | "tryton.org", | |
176 | "tug.org") { | 181 | "tug.org") { | |
177 | return port == "", "http", "https" | 182 | return true, "http", "https" | |
178 | } | 183 | } | |
179 | 184 | |||
180 | if scheme == "http" && host == "sf.net" { | 185 | if host == "sf.net" { | |
181 | return port == "", "http://sf.net", "https://sourceforge.net" | 186 | // sf.net redirects to sourceforge.net | |
187 | return true, scheme + "://sf.net", "https://sourceforge.net" | |||
182 | } | 188 | } | |
183 | 189 | |||
184 | from := scheme | 190 | from := scheme | |
185 | to := "https" | 191 | to := "https" | |
186 | toReachable := unknown | |||
187 | 192 | |||
188 | // SourceForge projects use either http://project.sourceforge.net or | 193 | // SourceForge projects use either http://project.sourceforge.net or | |
189 | // https://project.sourceforge.io (not net). | 194 | // https://project.sourceforge.io (not net). | |
190 | if m, project := match1(host, `^([\w-]+)\.(?:sf|sourceforge)\.net$`); m { | 195 | if m, project, domain := match2(host, `^([\w-]+)\.((?:sf|sourceforge)\.net)$`); m { | |
196 | ||||
191 | if scheme == "http" { | 197 | if scheme == "http" { | |
192 | from = scheme + "://" + host | |||
193 | // See https://sourceforge.net/p/forge/documentation/Custom%20VHOSTs | 198 | // See https://sourceforge.net/p/forge/documentation/Custom%20VHOSTs | |
199 | from = "http://" + host | |||
194 | to = "https://" + project + ".sourceforge.io" | 200 | to = "https://" + project + ".sourceforge.io" | |
195 | } else { | 201 | } else { | |
196 | from = "sourceforge.net" | 202 | from = domain | |
197 | to = "sourceforge.io" | 203 | to = "sourceforge.io" | |
198 | 204 | |||
199 | // Roll back wrong https SourceForge homepages generated by: | 205 | // Roll back wrong https SourceForge homepages generated by: | |
200 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | 206 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | |
201 | if port == "" && G.Opts.Network { | 207 | _, migrated := replaceOnce(url, from, to) | |
202 | _, migrated := replaceOnce(url, from, to) | 208 | if ck.isReachable(migrated) == no { | |
203 | if ck.isReachable(migrated) == no { | 209 | _, httpOnly := replaceOnce(url, "https://", "http://") | |
204 | ok, httpOnly := replaceOnce(url, "https://", "http://") | 210 | if ck.isReachable(httpOnly) == yes && ck.isReachable(url) == no { | |
205 | if ok && ck.isReachable(httpOnly) == yes && ck.isReachable(url) == no { | 211 | return true, "https", "http" | |
206 | from = "https" | |||
207 | to = "http" | |||
208 | toReachable = yes | |||
209 | } | |||
210 | } | 212 | } | |
211 | } | 213 | } | |
212 | } | 214 | } | |
213 | } | 215 | } | |
214 | 216 | |||
215 | if from == to { | 217 | if from == to { | |
216 | return false, "", "" | 218 | return false, "", "" | |
217 | } | 219 | } | |
218 | 220 | |||
219 | shouldAutofix := toReachable == yes | 221 | _, migrated := replaceOnce(url, from, to) | |
220 | if port == "" && G.Opts.Network && toReachable == unknown { | 222 | migrate := ck.isReachable(migrated) | |
221 | _, migrated := replaceOnce(url, from, to) | 223 | if migrate == yes { | |
222 | if ck.isReachable(migrated) == yes { | 224 | return true, from, to | |
223 | shouldAutofix = true | |||
224 | } else { | |||
225 | return false, "", "" | |||
226 | } | |||
227 | } | 225 | } | |
228 | return shouldAutofix, from, to | 226 | ||
227 | return false, "", "" | |||
229 | } | 228 | } | |
230 | 229 | |||
231 | func (ck *HomepageChecker) checkBadUrls() { | 230 | func (ck *HomepageChecker) checkBadUrls() { | |
232 | m, host := match1(ck.Value, `https?://([A-Za-z0-9-.]+)`) | 231 | m, host := match1(ck.Value, `https?://([A-Za-z0-9-.]+)`) | |
233 | if !m { | 232 | if !m { | |
234 | return | 233 | return | |
235 | } | 234 | } | |
236 | 235 | |||
237 | if !ck.hasAnySuffix(host, | 236 | if !ck.hasAnySuffix(host, | |
238 | ".dl.sourceforge.net", | 237 | ".dl.sourceforge.net", | |
239 | "downloads.sourceforge.net") { | 238 | "downloads.sourceforge.net") { | |
240 | return | 239 | return | |
241 | } | 240 | } | |
@@ -282,27 +281,27 @@ func (ck *HomepageChecker) checkReachabl | @@ -282,27 +281,27 @@ func (ck *HomepageChecker) checkReachabl | |||
282 | 281 | |||
283 | location, err := response.Location() | 282 | location, err := response.Location() | |
284 | if err == nil { | 283 | if err == nil { | |
285 | mkline.Warnf("Homepage %q redirects to %q.", url, location.String()) | 284 | mkline.Warnf("Homepage %q redirects to %q.", url, location.String()) | |
286 | return | 285 | return | |
287 | } | 286 | } | |
288 | 287 | |||
289 | if response.StatusCode != 200 { | 288 | if response.StatusCode != 200 { | |
290 | mkline.Warnf("Homepage %q returns HTTP status %q.", url, response.Status) | 289 | mkline.Warnf("Homepage %q returns HTTP status %q.", url, response.Status) | |
291 | return | 290 | return | |
292 | } | 291 | } | |
293 | } | 292 | } | |
294 | 293 | |||
295 | func (*HomepageChecker) isReachable(url string) YesNoUnknown { | 294 | func (*HomepageChecker) isReachableOnline(url string) YesNoUnknown { | |
296 | switch { | 295 | switch { | |
297 | case !G.Opts.Network, | 296 | case !G.Opts.Network, | |
298 | containsVarRefLong(url), | 297 | containsVarRefLong(url), | |
299 | !matches(url, `^https?://[A-Za-z0-9-.]+(?::[0-9]+)?/[!-~]*$`): | 298 | !matches(url, `^https?://[A-Za-z0-9-.]+(?::[0-9]+)?/[!-~]*$`): | |
300 | return unknown | 299 | return unknown | |
301 | } | 300 | } | |
302 | 301 | |||
303 | var client http.Client | 302 | var client http.Client | |
304 | client.Timeout = 3 * time.Second | 303 | client.Timeout = 3 * time.Second | |
305 | client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | 304 | client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |
306 | return http.ErrUseLastResponse | 305 | return http.ErrUseLastResponse | |
307 | } | 306 | } | |
308 | 307 |
@@ -122,88 +122,163 @@ func (s *Suite) Test_HomepageChecker_che | @@ -122,88 +122,163 @@ func (s *Suite) Test_HomepageChecker_che | |||
122 | 122 | |||
123 | vt.Varname("HOMEPAGE") | 123 | vt.Varname("HOMEPAGE") | |
124 | vt.Values( | 124 | vt.Values( | |
125 | "http://www.gnustep.org/", | 125 | "http://www.gnustep.org/", | |
126 | "http://www.pkgsrc.org/", | 126 | "http://www.pkgsrc.org/", | |
127 | "http://project.sourceforge.net/", | 127 | "http://project.sourceforge.net/", | |
128 | "http://sf.net/p/project/", | 128 | "http://sf.net/p/project/", | |
129 | "http://sourceforge.net/p/project/", | 129 | "http://sourceforge.net/p/project/", | |
130 | "http://example.org/ # doesn't support https", | 130 | "http://example.org/ # doesn't support https", | |
131 | "http://example.org/ # only supports http", | 131 | "http://example.org/ # only supports http", | |
132 | "http://asf.net/") | 132 | "http://asf.net/") | |
133 | 133 | |||
134 | vt.Output( | 134 | vt.Output( | |
135 | "WARN: filename.mk:2: HOMEPAGE should migrate from http to https.", | 135 | "WARN: filename.mk:4: HOMEPAGE should migrate " + | |
136 | "WARN: filename.mk:3: HOMEPAGE should migrate "+ | 136 | "from http://sf.net to https://sourceforge.net.") | |
137 | "from http://project.sourceforge.net "+ | |||
138 | "to https://project.sourceforge.io.", | |||
139 | "WARN: filename.mk:4: HOMEPAGE should migrate "+ | |||
140 | "from http://sf.net to https://sourceforge.net.", | |||
141 | "WARN: filename.mk:5: HOMEPAGE should migrate from http to https.", | |||
142 | "WARN: filename.mk:8: HOMEPAGE should migrate from http to https.") | |||
143 | 137 | |||
144 | t.SetUpCommandLine("--autofix") | 138 | t.SetUpCommandLine("--autofix") | |
145 | vt.Values( | 139 | vt.Values( | |
146 | "http://www.gnustep.org/", | 140 | "http://www.gnustep.org/", | |
147 | "http://www.pkgsrc.org/", | 141 | "http://www.pkgsrc.org/", | |
148 | "http://project.sourceforge.net/", | 142 | "http://project.sourceforge.net/", | |
149 | "http://sf.net/p/project/", | 143 | "http://sf.net/p/project/", | |
150 | "http://sourceforge.net/p/project/", | 144 | "http://sourceforge.net/p/project/", | |
151 | "http://example.org/ # doesn't support https", | 145 | "http://example.org/ # doesn't support https", | |
152 | "http://example.org/ # only supports http", | 146 | "http://example.org/ # only supports http", | |
153 | "http://kde.org/", | 147 | "http://kde.org/", | |
154 | "http://asf.net/") | 148 | "http://asf.net/") | |
155 | 149 | |||
156 | // www.gnustep.org does not support https at all. | 150 | // www.gnustep.org does not support https at all. | |
157 | // www.pkgsrc.org is not in the (short) list of known https domains, | 151 | // www.pkgsrc.org is not in the (short) list of known https domains, | |
158 | // therefore pkglint does not dare to change it automatically. | 152 | // therefore pkglint does not dare to change it automatically. | |
159 | vt.Output( | 153 | vt.Output( | |
160 | "AUTOFIX: filename.mk:14: Replacing \"http://sf.net\" "+ | 154 | "AUTOFIX: filename.mk:14: Replacing \"http://sf.net\" "+ | |
161 | "with \"https://sourceforge.net\".", | 155 | "with \"https://sourceforge.net\".", | |
162 | "AUTOFIX: filename.mk:18: Replacing \"http\" with \"https\".") | 156 | "AUTOFIX: filename.mk:18: Replacing \"http\" with \"https\".") | |
163 | } | 157 | } | |
164 | 158 | |||
165 | func (s *Suite) Test_HomepageChecker_toHttps(c *check.C) { | 159 | func (s *Suite) Test_HomepageChecker_migrate(c *check.C) { | |
166 | t := s.Init(c) | 160 | t := s.Init(c) | |
167 | 161 | |||
168 | test := func(url string, shouldAutofix bool, from, to string) { | 162 | reachable := map[string]YesNoUnknown{} | |
169 | toHttps := (*HomepageChecker).toHttps | 163 | used := map[string]bool{} | |
170 | actualShouldAutofix, actualFrom, actualTo := toHttps(nil, url) | 164 | ||
165 | isReachable := func(url string) YesNoUnknown { | |||
166 | if r, ok := reachable[url]; ok { | |||
167 | used[url] = true | |||
168 | return r | |||
169 | } | |||
170 | panic(url) | |||
171 | } | |||
172 | ||||
173 | test := func(url string, migrate bool, from, to string) { | |||
174 | ck := NewHomepageChecker(url, url, nil, nil) | |||
175 | ck.isReachable = isReachable | |||
176 | ||||
177 | actualMigrate, actualFrom, actualTo := ck.migrate(url) | |||
178 | ||||
171 | t.CheckDeepEquals( | 179 | t.CheckDeepEquals( | |
172 | []interface{}{actualShouldAutofix, actualFrom, actualTo}, | 180 | []interface{}{actualMigrate, actualFrom, actualTo}, | |
173 | []interface{}{shouldAutofix, from, to}) | 181 | []interface{}{migrate, from, to}) | |
182 | ||||
183 | for key, _ := range reachable { | |||
184 | assertf(used[key], "Reachability of %q was not used.", key) | |||
185 | delete(reachable, key) | |||
186 | } | |||
174 | } | 187 | } | |
175 | 188 | |||
176 | test("http://localhost/", false, "http", "https") | 189 | reachable["https://localhost/"] = unknown | |
190 | test( | |||
191 | "http://localhost/", | |||
192 | false, | |||
193 | "", | |||
194 | "") | |||
195 | ||||
196 | reachable["https://localhost/"] = yes | |||
197 | test( | |||
198 | "http://localhost/", | |||
199 | true, | |||
200 | "http", | |||
201 | "https") | |||
177 | 202 | |||
203 | reachable["https://project.sourceforge.io/"] = unknown | |||
178 | test( | 204 | test( | |
179 | "http://project.sourceforge.net/", | 205 | "http://project.sourceforge.net/", | |
180 | false, | 206 | false, | |
207 | "", | |||
208 | "") | |||
209 | ||||
210 | reachable["https://project.sourceforge.io/"] = yes | |||
211 | test( | |||
212 | "http://project.sourceforge.net/", | |||
213 | true, | |||
181 | "http://project.sourceforge.net", | 214 | "http://project.sourceforge.net", | |
182 | "https://project.sourceforge.io") | 215 | "https://project.sourceforge.io") | |
183 | 216 | |||
184 | // To clean up the wrong autofix from 2020-01-18: | 217 | // To clean up the wrong autofix from 2020-01-18: | |
185 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | 218 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | |
219 | reachable["https://project.sourceforge.io/"] = yes | |||
186 | test( | 220 | test( | |
187 | "https://project.sourceforge.net/", | 221 | "https://project.sourceforge.net/", | |
188 | false, | 222 | true, | |
189 | "sourceforge.net", | 223 | "sourceforge.net", | |
190 | "sourceforge.io") | 224 | "sourceforge.io") | |
191 | 225 | |||
226 | // To clean up the wrong autofix from 2020-01-18: | |||
227 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | |||
228 | // | |||
229 | // If neither of the https URLs is reachable, the homepage | |||
230 | // is rolled back to the http URL. | |||
231 | reachable["https://project.sourceforge.io/"] = no | |||
232 | reachable["http://project.sourceforge.net/"] = yes | |||
233 | reachable["https://project.sourceforge.net/"] = no | |||
234 | test( | |||
235 | "https://project.sourceforge.net/", | |||
236 | true, | |||
237 | "https", | |||
238 | "http") | |||
239 | ||||
240 | // To clean up the wrong autofix from 2020-01-18: | |||
241 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | |||
242 | // | |||
243 | // If the https URL of sourceforge.net is reachable, | |||
244 | // it is preferred over the http URL, | |||
245 | // even though it is not mentioned in the SourceForge documentation. | |||
246 | reachable["https://project.sourceforge.io/"] = no | |||
247 | reachable["http://project.sourceforge.net/"] = yes | |||
248 | reachable["https://project.sourceforge.net/"] = yes | |||
249 | test( | |||
250 | "https://project.sourceforge.net/", | |||
251 | false, | |||
252 | "", | |||
253 | "") | |||
254 | ||||
255 | // To clean up the wrong autofix from 2020-01-18: | |||
256 | // https://mail-index.netbsd.org/pkgsrc-changes/2020/01/18/msg205146.html | |||
257 | reachable["https://project.sourceforge.io/"] = no | |||
258 | reachable["http://project.sourceforge.net/"] = no | |||
259 | test( | |||
260 | "https://project.sourceforge.net/", | |||
261 | false, | |||
262 | "", | |||
263 | "") | |||
264 | ||||
265 | // Since the URL contains a variable, it cannot be resolved. | |||
266 | // Therefore it is skipped without any HTTP request being sent. | |||
192 | test( | 267 | test( | |
193 | "http://godoc.org/${GO_SRCPATH}", | 268 | "http://godoc.org/${GO_SRCPATH}", | |
194 | false, | 269 | false, | |
195 | "http", | 270 | "", | |
196 | "https") | 271 | "") | |
197 | } | 272 | } | |
198 | 273 | |||
199 | func (s *Suite) Test_HomepageChecker_checkBadUrls(c *check.C) { | 274 | func (s *Suite) Test_HomepageChecker_checkBadUrls(c *check.C) { | |
200 | t := s.Init(c) | 275 | t := s.Init(c) | |
201 | vt := NewVartypeCheckTester(t, BtHomepage) | 276 | vt := NewVartypeCheckTester(t, BtHomepage) | |
202 | 277 | |||
203 | vt.Varname("HOMEPAGE") | 278 | vt.Varname("HOMEPAGE") | |
204 | vt.Values( | 279 | vt.Values( | |
205 | "http://garr.dl.sourceforge.net/project/name/dir/subdir/", | 280 | "http://garr.dl.sourceforge.net/project/name/dir/subdir/", | |
206 | "https://downloads.sourceforge.net/project/name/dir/subdir/") | 281 | "https://downloads.sourceforge.net/project/name/dir/subdir/") | |
207 | 282 | |||
208 | vt.Output( | 283 | vt.Output( | |
209 | "WARN: filename.mk:1: A direct download URL is not a user-friendly homepage.", | 284 | "WARN: filename.mk:1: A direct download URL is not a user-friendly homepage.", | |
@@ -249,80 +324,77 @@ func (s *Suite) Test_HomepageChecker_che | @@ -249,80 +324,77 @@ func (s *Suite) Test_HomepageChecker_che | |||
249 | <-shutdown | 324 | <-shutdown | |
250 | }() | 325 | }() | |
251 | 326 | |||
252 | vt.Varname("HOMEPAGE") | 327 | vt.Varname("HOMEPAGE") | |
253 | vt.Values( | 328 | vt.Values( | |
254 | "http://localhost:28780/status/200", | 329 | "http://localhost:28780/status/200", | |
255 | "http://localhost:28780/status/301?location=/redirect301", | 330 | "http://localhost:28780/status/301?location=/redirect301", | |
256 | "http://localhost:28780/status/302?location=/redirect302", | 331 | "http://localhost:28780/status/302?location=/redirect302", | |
257 | "http://localhost:28780/status/307?location=/redirect307", | 332 | "http://localhost:28780/status/307?location=/redirect307", | |
258 | "http://localhost:28780/status/404", | 333 | "http://localhost:28780/status/404", | |
259 | "http://localhost:28780/status/500") | 334 | "http://localhost:28780/status/500") | |
260 | 335 | |||
261 | vt.Output( | 336 | vt.Output( | |
262 | "WARN: filename.mk:1: HOMEPAGE should migrate from http to https.", | |||
263 | "WARN: filename.mk:2: HOMEPAGE should migrate from http to https.", | |||
264 | "WARN: filename.mk:2: Homepage "+ | 337 | "WARN: filename.mk:2: Homepage "+ | |
265 | "\"http://localhost:28780/status/301?location=/redirect301\" "+ | 338 | "\"http://localhost:28780/status/301?location=/redirect301\" "+ | |
266 | "redirects to \"http://localhost:28780/redirect301\".", | 339 | "redirects to \"http://localhost:28780/redirect301\".", | |
267 | "WARN: filename.mk:3: HOMEPAGE should migrate from http to https.", | |||
268 | "WARN: filename.mk:3: Homepage "+ | 340 | "WARN: filename.mk:3: Homepage "+ | |
269 | "\"http://localhost:28780/status/302?location=/redirect302\" "+ | 341 | "\"http://localhost:28780/status/302?location=/redirect302\" "+ | |
270 | "redirects to \"http://localhost:28780/redirect302\".", | 342 | "redirects to \"http://localhost:28780/redirect302\".", | |
271 | "WARN: filename.mk:4: HOMEPAGE should migrate from http to https.", | |||
272 | "WARN: filename.mk:4: Homepage "+ | 343 | "WARN: filename.mk:4: Homepage "+ | |
273 | "\"http://localhost:28780/status/307?location=/redirect307\" "+ | 344 | "\"http://localhost:28780/status/307?location=/redirect307\" "+ | |
274 | "redirects to \"http://localhost:28780/redirect307\".", | 345 | "redirects to \"http://localhost:28780/redirect307\".", | |
275 | "WARN: filename.mk:5: HOMEPAGE should migrate from http to https.", | |||
276 | "WARN: filename.mk:5: Homepage \"http://localhost:28780/status/404\" "+ | 346 | "WARN: filename.mk:5: Homepage \"http://localhost:28780/status/404\" "+ | |
277 | "returns HTTP status \"404 Not Found\".", | 347 | "returns HTTP status \"404 Not Found\".", | |
278 | "WARN: filename.mk:6: HOMEPAGE should migrate from http to https.", | |||
279 | "WARN: filename.mk:6: Homepage \"http://localhost:28780/status/500\" "+ | 348 | "WARN: filename.mk:6: Homepage \"http://localhost:28780/status/500\" "+ | |
280 | "returns HTTP status \"500 Internal Server Error\".") | 349 | "returns HTTP status \"500 Internal Server Error\".") | |
281 | 350 | |||
282 | vt.Values( | 351 | vt.Values( | |
283 | "http://localhost:28780/timeout") | 352 | "http://localhost:28780/timeout") | |
284 | 353 | |||
285 | vt.Output( | 354 | vt.Output( | |
286 | "WARN: filename.mk:11: HOMEPAGE should migrate from http to https.", | 355 | "WARN: filename.mk:11: Homepage \"http://localhost:28780/timeout\" " + | |
287 | "WARN: filename.mk:11: Homepage \"http://localhost:28780/timeout\" "+ | |||
288 | "cannot be checked: timeout") | 356 | "cannot be checked: timeout") | |
289 | 357 | |||
290 | vt.Values( | 358 | vt.Values( | |
291 | "http://localhost:28780/%invalid") | 359 | "http://localhost:28780/%invalid") | |
292 | 360 | |||
293 | vt.Output( | 361 | vt.Output( | |
294 | "WARN: filename.mk:21: HOMEPAGE should migrate from http to https.", | |||
295 | "ERROR: filename.mk:21: Invalid URL \"http://localhost:28780/%invalid\".") | 362 | "ERROR: filename.mk:21: Invalid URL \"http://localhost:28780/%invalid\".") | |
296 | 363 | |||
297 | vt.Values( | 364 | vt.Values( | |
298 | "http://localhost:28781/") | 365 | "http://localhost:28781/") | |
299 | 366 | |||
300 | // The "unknown network error" is for compatibility with Go < 1.13. | 367 | // The "unknown network error" is for compatibility with Go < 1.13. | |
301 | t.CheckOutputMatches( | 368 | t.CheckOutputMatches( | |
302 | "WARN: filename.mk:31: HOMEPAGE should migrate from http to https.", | 369 | `^WARN: filename\.mk:31: Homepage "http://localhost:28781/" ` + | |
303 | `^WARN: filename\.mk:31: Homepage "http://localhost:28781/" `+ | |||
304 | `cannot be checked: (connection refused|unknown network error:.*)$`) | 370 | `cannot be checked: (connection refused|unknown network error:.*)$`) | |
305 | 371 | |||
306 | vt.Values( | 372 | vt.Values( | |
307 | "https://no-such-name.example.org/") | 373 | "https://no-such-name.example.org/") | |
308 | 374 | |||
309 | // The "unknown network error" is for compatibility with Go < 1.13. | 375 | // The "unknown network error" is for compatibility with Go < 1.13. | |
310 | t.CheckOutputMatches( | 376 | t.CheckOutputMatches( | |
311 | `^WARN: filename\.mk:41: Homepage "https://no-such-name.example.org/" ` + | 377 | `^WARN: filename\.mk:41: Homepage "https://no-such-name.example.org/" ` + | |
312 | `cannot be checked: (name not found|unknown network error:.*)$`) | 378 | `cannot be checked: (name not found|unknown network error:.*)$`) | |
379 | ||||
380 | vt.Values( | |||
381 | "https://!!!invalid/") | |||
382 | ||||
383 | t.CheckOutputLines( | |||
384 | "WARN: filename.mk:51: \"https://!!!invalid/\" is not a valid URL.") | |||
313 | } | 385 | } | |
314 | 386 | |||
315 | func (s *Suite) Test_HomepageChecker_isReachable(c *check.C) { | 387 | func (s *Suite) Test_HomepageChecker_isReachableOnline(c *check.C) { | |
316 | t := s.Init(c) | 388 | t := s.Init(c) | |
317 | 389 | |||
318 | t.SetUpCommandLine("--network") | 390 | t.SetUpCommandLine("--network") | |
319 | 391 | |||
320 | mux := http.NewServeMux() | 392 | mux := http.NewServeMux() | |
321 | mux.HandleFunc("/status/", func(writer http.ResponseWriter, request *http.Request) { | 393 | mux.HandleFunc("/status/", func(writer http.ResponseWriter, request *http.Request) { | |
322 | location := request.URL.Query().Get("location") | 394 | location := request.URL.Query().Get("location") | |
323 | if location != "" { | 395 | if location != "" { | |
324 | writer.Header().Set("Location", location) | 396 | writer.Header().Set("Location", location) | |
325 | } | 397 | } | |
326 | 398 | |||
327 | status, err := strconv.Atoi(request.URL.Path[len("/status/"):]) | 399 | status, err := strconv.Atoi(request.URL.Path[len("/status/"):]) | |
328 | assertNil(err, "") | 400 | assertNil(err, "") | |
@@ -342,27 +414,28 @@ func (s *Suite) Test_HomepageChecker_isR | @@ -342,27 +414,28 @@ func (s *Suite) Test_HomepageChecker_isR | |||
342 | go func() { | 414 | go func() { | |
343 | err := srv.Serve(listener) | 415 | err := srv.Serve(listener) | |
344 | assertf(err == http.ErrServerClosed, "%s", err) | 416 | assertf(err == http.ErrServerClosed, "%s", err) | |
345 | shutdown <- true | 417 | shutdown <- true | |
346 | }() | 418 | }() | |
347 | 419 | |||
348 | defer func() { | 420 | defer func() { | |
349 | err := srv.Shutdown(context.Background()) | 421 | err := srv.Shutdown(context.Background()) | |
350 | assertNil(err, "") | 422 | assertNil(err, "") | |
351 | <-shutdown | 423 | <-shutdown | |
352 | }() | 424 | }() | |
353 | 425 | |||
354 | test := func(url string, reachable YesNoUnknown) { | 426 | test := func(url string, reachable YesNoUnknown) { | |
355 | actual := (*HomepageChecker).isReachable(nil, url) | 427 | ck := NewHomepageChecker(url, url, nil, nil) | |
428 | actual := ck.isReachableOnline(url) | |||
356 | 429 | |||
357 | t.CheckEquals(actual, reachable) | 430 | t.CheckEquals(actual, reachable) | |
358 | } | 431 | } | |
359 | 432 | |||
360 | test("http://localhost:28780/status/200", yes) | 433 | test("http://localhost:28780/status/200", yes) | |
361 | test("http://localhost:28780/status/301?location=/", no) | 434 | test("http://localhost:28780/status/301?location=/", no) | |
362 | test("http://localhost:28780/status/404", no) | 435 | test("http://localhost:28780/status/404", no) | |
363 | test("http://localhost:28780/status/500", no) | 436 | test("http://localhost:28780/status/500", no) | |
364 | test("http://localhost:28780/timeout", no) | 437 | test("http://localhost:28780/timeout", no) | |
365 | test("http://localhost:28780/ok/${VAR}", unknown) | 438 | test("http://localhost:28780/ok/${VAR}", unknown) | |
366 | test("http://localhost:28780/ invalid", unknown) | 439 | test("http://localhost:28780/ invalid", unknown) | |
367 | test("http://localhost:28780/%invalid", no) | 440 | test("http://localhost:28780/%invalid", no) | |
368 | test("http://localhost:28781/", no) | 441 | test("http://localhost:28781/", no) |
@@ -134,26 +134,29 @@ func (line *Line) RelLocation(other Loca | @@ -134,26 +134,29 @@ func (line *Line) RelLocation(other Loca | |||
134 | func (line *Line) Rel(other CurrPath) RelPath { | 134 | func (line *Line) Rel(other CurrPath) RelPath { | |
135 | return G.Pkgsrc.Relpath(line.Filename.DirNoClean(), other) | 135 | return G.Pkgsrc.Relpath(line.Filename.DirNoClean(), other) | |
136 | } | 136 | } | |
137 | 137 | |||
138 | func (line *Line) IsMultiline() bool { | 138 | func (line *Line) IsMultiline() bool { | |
139 | return line.firstLine > 0 && line.firstLine != line.lastLine | 139 | return line.firstLine > 0 && line.firstLine != line.lastLine | |
140 | } | 140 | } | |
141 | 141 | |||
142 | func (line *Line) IsCvsID(prefixRe regex.Pattern) (found bool, expanded bool) { | 142 | func (line *Line) IsCvsID(prefixRe regex.Pattern) (found bool, expanded bool) { | |
143 | m, exp := match1(line.Text, `^`+prefixRe+`\$`+`NetBSD(:[^\$]+)?\$$`) | 143 | m, exp := match1(line.Text, `^`+prefixRe+`\$`+`NetBSD(:[^\$]+)?\$$`) | |
144 | return m, exp != "" | 144 | return m, exp != "" | |
145 | } | 145 | } | |
146 | 146 | |||
147 | // FIXME: By definition there cannot be fatal diagnostics. | |||
148 | // Having these was a misconception from the beginning, | |||
149 | // and they must be re-classified as fatal technical errors. | |||
147 | func (line *Line) Fatalf(format string, args ...interface{}) { | 150 | func (line *Line) Fatalf(format string, args ...interface{}) { | |
148 | if trace.Tracing { | 151 | if trace.Tracing { | |
149 | trace.Stepf("Fatalf: %q, %v", format, args) | 152 | trace.Stepf("Fatalf: %q, %v", format, args) | |
150 | } | 153 | } | |
151 | G.Logger.Diag(line, Fatal, format, args...) | 154 | G.Logger.Diag(line, Fatal, format, args...) | |
152 | } | 155 | } | |
153 | 156 | |||
154 | func (line *Line) Errorf(format string, args ...interface{}) { | 157 | func (line *Line) Errorf(format string, args ...interface{}) { | |
155 | G.Logger.Diag(line, Error, format, args...) | 158 | G.Logger.Diag(line, Error, format, args...) | |
156 | } | 159 | } | |
157 | 160 | |||
158 | func (line *Line) Warnf(format string, args ...interface{}) { | 161 | func (line *Line) Warnf(format string, args ...interface{}) { | |
159 | G.Logger.Diag(line, Warn, format, args...) | 162 | G.Logger.Diag(line, Warn, format, args...) |
@@ -48,26 +48,29 @@ type LoggerOpts struct { | @@ -48,26 +48,29 @@ type LoggerOpts struct { | |||
48 | ShowSource, | 48 | ShowSource, | |
49 | GccOutput, | 49 | GccOutput, | |
50 | Quiet bool | 50 | Quiet bool | |
51 | 51 | |||
52 | Only []string | 52 | Only []string | |
53 | } | 53 | } | |
54 | 54 | |||
55 | type LogLevel struct { | 55 | type LogLevel struct { | |
56 | TraditionalName string | 56 | TraditionalName string | |
57 | GccName string | 57 | GccName string | |
58 | } | 58 | } | |
59 | 59 | |||
60 | var ( | 60 | var ( | |
61 | // FIXME: By definition there cannot be fatal diagnostics. | |||
62 | // Having these was a misconception from the beginning, | |||
63 | // and they must be re-classified as fatal technical errors. | |||
61 | Fatal = &LogLevel{"FATAL", "fatal"} | 64 | Fatal = &LogLevel{"FATAL", "fatal"} | |
62 | Error = &LogLevel{"ERROR", "error"} | 65 | Error = &LogLevel{"ERROR", "error"} | |
63 | Warn = &LogLevel{"WARN", "warning"} | 66 | Warn = &LogLevel{"WARN", "warning"} | |
64 | Note = &LogLevel{"NOTE", "note"} | 67 | Note = &LogLevel{"NOTE", "note"} | |
65 | AutofixLogLevel = &LogLevel{"AUTOFIX", "autofix"} | 68 | AutofixLogLevel = &LogLevel{"AUTOFIX", "autofix"} | |
66 | ) | 69 | ) | |
67 | 70 | |||
68 | // Explain outputs an explanation for the preceding diagnostic | 71 | // Explain outputs an explanation for the preceding diagnostic | |
69 | // if the --explain option is given. Otherwise it just records | 72 | // if the --explain option is given. Otherwise it just records | |
70 | // that an explanation is available. | 73 | // that an explanation is available. | |
71 | func (l *Logger) Explain(explanation ...string) { | 74 | func (l *Logger) Explain(explanation ...string) { | |
72 | if l.suppressExpl { | 75 | if l.suppressExpl { | |
73 | return | 76 | return |
@@ -1435,30 +1435,27 @@ func (s *Suite) Test_RedundantScope__is_ | @@ -1435,30 +1435,27 @@ func (s *Suite) Test_RedundantScope__is_ | |||
1435 | "PKG_OPTIONS=\t# empty", | 1435 | "PKG_OPTIONS=\t# empty", | |
1436 | "PKG_OPTIONS=\toverwritten") | 1436 | "PKG_OPTIONS=\toverwritten") | |
1437 | t.CreateFileLines("options.mk", | 1437 | t.CreateFileLines("options.mk", | |
1438 | "OUTSIDE:=\t# empty", | 1438 | "OUTSIDE:=\t# empty", | |
1439 | "OUTSIDE=\t# empty", | 1439 | "OUTSIDE=\t# empty", | |
1440 | "OUTSIDE=\toverwritten", | 1440 | "OUTSIDE=\toverwritten", | |
1441 | ".include \"mk/bsd.options.mk\"") | 1441 | ".include \"mk/bsd.options.mk\"") | |
1442 | 1442 | |||
1443 | test := func(diagnostics ...string) { | 1443 | test := func(diagnostics ...string) { | |
1444 | mklines := t.LoadMkInclude("options.mk") | 1444 | mklines := t.LoadMkInclude("options.mk") | |
1445 | scope := NewRedundantScope() | 1445 | scope := NewRedundantScope() | |
1446 | scope.IsRelevant = func(mkline *MkLine) bool { | 1446 | scope.IsRelevant = func(mkline *MkLine) bool { | |
1447 | // See checkfilePackageMakefile. | 1447 | // See checkfilePackageMakefile. | |
1448 | if !G.Infrastructure && !G.Opts.CheckGlobal { | 1448 | return G.Opts.CheckGlobal || !G.Pkgsrc.IsInfra(mkline.Filename) | |
1449 | return !G.Pkgsrc.IsInfra(mkline.Filename) | |||
1450 | } | |||
1451 | return true | |||
1452 | } | 1449 | } | |
1453 | 1450 | |||
1454 | scope.Check(mklines) | 1451 | scope.Check(mklines) | |
1455 | 1452 | |||
1456 | // No note about the redundant variable assignment in bsd.options.mk | 1453 | // No note about the redundant variable assignment in bsd.options.mk | |
1457 | // because it is part of the infrastructure, which is filtered out. | 1454 | // because it is part of the infrastructure, which is filtered out. | |
1458 | t.CheckOutput(diagnostics) | 1455 | t.CheckOutput(diagnostics) | |
1459 | } | 1456 | } | |
1460 | 1457 | |||
1461 | test( | 1458 | test( | |
1462 | "NOTE: ~/options.mk:2: "+ | 1459 | "NOTE: ~/options.mk:2: "+ | |
1463 | "Definition of OUTSIDE is redundant because of line 1.", | 1460 | "Definition of OUTSIDE is redundant because of line 1.", | |
1464 | "WARN: ~/options.mk:2: "+ | 1461 | "WARN: ~/options.mk:2: "+ |
@@ -645,26 +645,29 @@ func (info *varalignLine) alignContinuat | @@ -645,26 +645,29 @@ func (info *varalignLine) alignContinuat | |||
645 | } | 645 | } | |
646 | if column == 72 || column == rightMarginColumn || column <= valueColumn { | 646 | if column == 72 || column == rightMarginColumn || column <= valueColumn { | |
647 | return | 647 | return | |
648 | } | 648 | } | |
649 | 649 | |||
650 | newSpace := " " | 650 | newSpace := " " | |
651 | fix := info.fixer.Autofix() | 651 | fix := info.fixer.Autofix() | |
652 | if oldSpace == "" || rightMarginColumn == 0 { | 652 | if oldSpace == "" || rightMarginColumn == 0 { | |
653 | fix.Notef("The continuation backslash should be preceded by a single space or tab.") | 653 | fix.Notef("The continuation backslash should be preceded by a single space or tab.") | |
654 | } else if info.isTooLongFor(valueColumn) { | 654 | } else if info.isTooLongFor(valueColumn) { | |
655 | fix.Notef("The continuation backslash should be preceded by a single space.") | 655 | fix.Notef("The continuation backslash should be preceded by a single space.") | |
656 | } else { | 656 | } else { | |
657 | newSpace = alignmentToWidths(info.uptoValueWidth(), rightMarginColumn) | 657 | newSpace = alignmentToWidths(info.uptoValueWidth(), rightMarginColumn) | |
658 | if newSpace == "" { | |||
659 | newSpace = " " | |||
660 | } | |||
658 | fix.Notef( | 661 | fix.Notef( | |
659 | "The continuation backslash should be in column %d, not %d.", | 662 | "The continuation backslash should be in column %d, not %d.", | |
660 | rightMarginColumn+1, column+1) | 663 | rightMarginColumn+1, column+1) | |
661 | } | 664 | } | |
662 | index := info.continuationIndex() - len(oldSpace) | 665 | index := info.continuationIndex() - len(oldSpace) | |
663 | fix.ReplaceAt(info.rawIndex, index, oldSpace, newSpace) | 666 | fix.ReplaceAt(info.rawIndex, index, oldSpace, newSpace) | |
664 | info.setSpaceBeforeContinuation(newSpace) | 667 | info.setSpaceBeforeContinuation(newSpace) | |
665 | fix.Apply() | 668 | fix.Apply() | |
666 | } | 669 | } | |
667 | 670 | |||
668 | func (info *varalignLine) replaceSpaceBeforeValue(fix *Autofix, newSpace string) { | 671 | func (info *varalignLine) replaceSpaceBeforeValue(fix *Autofix, newSpace string) { | |
669 | index := info.spaceBeforeValueIndex() | 672 | index := info.spaceBeforeValueIndex() | |
670 | fix.ReplaceAt(info.rawIndex, index, info.spaceBeforeValue, newSpace) | 673 | fix.ReplaceAt(info.rawIndex, index, info.spaceBeforeValue, newSpace) | |
@@ -842,19 +845,22 @@ func (p *varalignParts) isCanonicalIniti | @@ -842,19 +845,22 @@ func (p *varalignParts) isCanonicalIniti | |||
842 | } | 845 | } | |
843 | 846 | |||
844 | return strings.TrimLeft(space, "\t") == "" | 847 | return strings.TrimLeft(space, "\t") == "" | |
845 | } | 848 | } | |
846 | 849 | |||
847 | // isCanonicalFollow returns whether the space before the value has its | 850 | // isCanonicalFollow returns whether the space before the value has its | |
848 | // canonical form, which is at least one tab, followed by up to 7 spaces. | 851 | // canonical form, which is at least one tab, followed by up to 7 spaces. | |
849 | func (p *varalignParts) isCanonicalFollow() bool { | 852 | func (p *varalignParts) isCanonicalFollow() bool { | |
850 | column := p.valueColumn() | 853 | column := p.valueColumn() | |
851 | return column >= 8 && p.spaceBeforeValue == indent(column) | 854 | return column >= 8 && p.spaceBeforeValue == indent(column) | |
852 | } | 855 | } | |
853 | 856 | |||
854 | func (p *varalignParts) isTooLongFor(valueColumn int) bool { | 857 | func (p *varalignParts) isTooLongFor(valueColumn int) bool { | |
858 | // FIXME: As of 2020-01-27, this method is called from | |||
859 | // Test_VaralignBlock__right_margin with valueColumn == 0, | |||
860 | // which doesn't make sense. It should at least be 8. | |||
855 | column := tabWidthAppend(valueColumn, p.value) | 861 | column := tabWidthAppend(valueColumn, p.value) | |
856 | if p.isContinuation() { | 862 | if p.isContinuation() { | |
857 | column += 2 | 863 | column += 2 | |
858 | } | 864 | } | |
859 | return column > 73 | 865 | return column > 73 | |
860 | } | 866 | } |
@@ -208,27 +208,27 @@ func (vt *VaralignTester) checkTestName( | @@ -208,27 +208,27 @@ func (vt *VaralignTester) checkTestName( | |||
208 | m, name, widthStr := match2(part, `^([a-z]+)(\d*)$`) | 208 | m, name, widthStr := match2(part, `^([a-z]+)(\d*)$`) | |
209 | assert(m) | 209 | assert(m) | |
210 | width, err := strconv.Atoi(widthStr) | 210 | width, err := strconv.Atoi(widthStr) | |
211 | if err != nil || width == 0 { | 211 | if err != nil || width == 0 { | |
212 | width = 0 | 212 | width = 0 | |
213 | if i < len(actual) && name == actual[i].name { | 213 | if i < len(actual) && name == actual[i].name { | |
214 | width = actual[i].width | 214 | width = actual[i].width | |
215 | } | 215 | } | |
216 | } | 216 | } | |
217 | expected = append(expected, descriptor{name, width}) | 217 | expected = append(expected, descriptor{name, width}) | |
218 | } | 218 | } | |
219 | 219 | |||
220 | vt.tester.CheckDeepEquals(descriptorsString(actual), descriptorsString(expected)) | 220 | vt.tester.CheckDeepEquals(descriptorsString(actual), descriptorsString(expected)) | |
221 | vt.tester.CheckDeepEquals(actual, expected) | 221 | vt.tester.CheckDeepEquals(expected, actual) | |
222 | } | 222 | } | |
223 | 223 | |||
224 | func (s *Suite) Test_VaralignBlock__var_none_value(c *check.C) { | 224 | func (s *Suite) Test_VaralignBlock__var_none_value(c *check.C) { | |
225 | vt := NewVaralignTester(s, c) | 225 | vt := NewVaralignTester(s, c) | |
226 | vt.Input( | 226 | vt.Input( | |
227 | "VVVVVVVVVVVVVVVVVVV=value") | 227 | "VVVVVVVVVVVVVVVVVVV=value") | |
228 | vt.Internals( | 228 | vt.Internals( | |
229 | "20 20") | 229 | "20 20") | |
230 | vt.Diagnostics( | 230 | vt.Diagnostics( | |
231 | "NOTE: Makefile:1: This variable value should be aligned to column 25 instead of 21.") | 231 | "NOTE: Makefile:1: This variable value should be aligned to column 25 instead of 21.") | |
232 | vt.Autofixes( | 232 | vt.Autofixes( | |
233 | "AUTOFIX: Makefile:1: Replacing \"\" with \"\\t\".") | 233 | "AUTOFIX: Makefile:1: Replacing \"\" with \"\\t\".") | |
234 | vt.Fixed( | 234 | vt.Fixed( | |
@@ -2053,26 +2053,49 @@ func (s *Suite) Test_VaralignBlock__var_ | @@ -2053,26 +2053,49 @@ func (s *Suite) Test_VaralignBlock__var_ | |||
2053 | "indented with \"\\t\\t\".", | 2053 | "indented with \"\\t\\t\".", | |
2054 | "NOTE: Makefile:3: This continuation line should be "+ | 2054 | "NOTE: Makefile:3: This continuation line should be "+ | |
2055 | "indented with \"\\t\\t\".") | 2055 | "indented with \"\\t\\t\".") | |
2056 | vt.Autofixes( | 2056 | vt.Autofixes( | |
2057 | "AUTOFIX: Makefile:2: Replacing \"\\t\" with \"\\t\\t\".", | 2057 | "AUTOFIX: Makefile:2: Replacing \"\\t\" with \"\\t\\t\".", | |
2058 | "AUTOFIX: Makefile:3: Replacing \"\\t\" with \"\\t\\t\".") | 2058 | "AUTOFIX: Makefile:3: Replacing \"\\t\" with \"\\t\\t\".") | |
2059 | vt.Fixed( | 2059 | vt.Fixed( | |
2060 | "PROGFILES= 67890 234567890 234567890 234567890 234567890 2 \\", | 2060 | "PROGFILES= 67890 234567890 234567890 234567890 234567890 2 \\", | |
2061 | " 890 234567890 234567890 234567890 234567890 234567890 234567890 \\", | 2061 | " 890 234567890 234567890 234567890 234567890 234567890 234567890 \\", | |
2062 | " value") | 2062 | " value") | |
2063 | vt.Run() | 2063 | vt.Run() | |
2064 | } | 2064 | } | |
2065 | 2065 | |||
2066 | // Up to 2020-01-27, pkglint removed all spaces before the backslash, | |||
2067 | // which was against the rule of having at least one space. | |||
2068 | func (s *Suite) Test_VaralignBlock__right_margin(c *check.C) { | |||
2069 | vt := NewVaralignTester(s, c) | |||
2070 | vt.Input( | |||
2071 | "CONFIGURE_ARGS+=\t\\", | |||
2072 | "\t.....................................................................79\t\\", | |||
2073 | "\t............................................................70 \t\t\\", | |||
2074 | "\t........................................................66") | |||
2075 | vt.Diagnostics( | |||
2076 | "NOTE: Makefile:2: The continuation backslash should be in column 73, not 81.", | |||
2077 | "NOTE: Makefile:3: The continuation backslash should be in column 73, not 81.") | |||
2078 | vt.Autofixes( | |||
2079 | "AUTOFIX: Makefile:2: Replacing \"\\t\" with \" \".", | |||
2080 | "AUTOFIX: Makefile:3: Replacing \" \\t\\t\" with \"\\t\".") | |||
2081 | vt.Fixed( | |||
2082 | "CONFIGURE_ARGS+= \\", | |||
2083 | " .....................................................................79 \\", | |||
2084 | " ............................................................70 \\", | |||
2085 | " ........................................................66") | |||
2086 | vt.Run() | |||
2087 | } | |||
2088 | ||||
2066 | // Up to 2018-01-27, it could happen that some source code was logged | 2089 | // Up to 2018-01-27, it could happen that some source code was logged | |
2067 | // without a corresponding diagnostic. This was unintended and confusing. | 2090 | // without a corresponding diagnostic. This was unintended and confusing. | |
2068 | func (s *Suite) Test_VaralignBlock__fix_without_diagnostic(c *check.C) { | 2091 | func (s *Suite) Test_VaralignBlock__fix_without_diagnostic(c *check.C) { | |
2069 | vt := NewVaralignTester(s, c) | 2092 | vt := NewVaralignTester(s, c) | |
2070 | vt.Input( | 2093 | vt.Input( | |
2071 | "MESSAGE_SUBST+=\t\tRUBY_DISTNAME=${RUBY_DISTNAME}", | 2094 | "MESSAGE_SUBST+=\t\tRUBY_DISTNAME=${RUBY_DISTNAME}", | |
2072 | "PLIST_SUBST+=\t\tRUBY_SHLIBVER=${RUBY_SHLIBVER:Q} \\", | 2095 | "PLIST_SUBST+=\t\tRUBY_SHLIBVER=${RUBY_SHLIBVER:Q} \\", | |
2073 | "\t\t\tRUBY_SHLIBMAJOR=${RUBY_SHLIBMAJOR:Q} \\", | 2096 | "\t\t\tRUBY_SHLIBMAJOR=${RUBY_SHLIBMAJOR:Q} \\", | |
2074 | "\t\t\tRUBY_NOSHLIBMAJOR=${RUBY_NOSHLIBMAJOR} \\", | 2097 | "\t\t\tRUBY_NOSHLIBMAJOR=${RUBY_NOSHLIBMAJOR} \\", | |
2075 | "\t\t\tRUBY_NAME=${RUBY_NAME:Q}") | 2098 | "\t\t\tRUBY_NAME=${RUBY_NAME:Q}") | |
2076 | vt.Internals( | 2099 | vt.Internals( | |
2077 | "15 24", | 2100 | "15 24", | |
2078 | "13 24 57", | 2101 | "13 24 57", |
@@ -907,27 +907,26 @@ func (s *Suite) Test_VartypeCheck_GccReq | @@ -907,27 +907,26 @@ func (s *Suite) Test_VartypeCheck_GccReq | |||
907 | } | 907 | } | |
908 | 908 | |||
909 | func (s *Suite) Test_VartypeCheck_Homepage(c *check.C) { | 909 | func (s *Suite) Test_VartypeCheck_Homepage(c *check.C) { | |
910 | t := s.Init(c) | 910 | t := s.Init(c) | |
911 | vt := NewVartypeCheckTester(t, BtHomepage) | 911 | vt := NewVartypeCheckTester(t, BtHomepage) | |
912 | 912 | |||
913 | vt.Varname("HOMEPAGE") | 913 | vt.Varname("HOMEPAGE") | |
914 | vt.Values( | 914 | vt.Values( | |
915 | "http://www.pkgsrc.org/", | 915 | "http://www.pkgsrc.org/", | |
916 | "https://www.pkgsrc.org/", | 916 | "https://www.pkgsrc.org/", | |
917 | "${MASTER_SITES}") | 917 | "${MASTER_SITES}") | |
918 | 918 | |||
919 | vt.Output( | 919 | vt.Output( | |
920 | "WARN: filename.mk:1: HOMEPAGE should migrate from http to https.", | |||
921 | "WARN: filename.mk:3: HOMEPAGE should not be defined in terms of MASTER_SITEs.") | 920 | "WARN: filename.mk:3: HOMEPAGE should not be defined in terms of MASTER_SITEs.") | |
922 | 921 | |||
923 | // For more tests, see HomepageChecker. | 922 | // For more tests, see HomepageChecker. | |
924 | } | 923 | } | |
925 | 924 | |||
926 | func (s *Suite) Test_VartypeCheck_IdentifierDirect(c *check.C) { | 925 | func (s *Suite) Test_VartypeCheck_IdentifierDirect(c *check.C) { | |
927 | t := s.Init(c) | 926 | t := s.Init(c) | |
928 | vt := NewVartypeCheckTester(t, BtIdentifierDirect) | 927 | vt := NewVartypeCheckTester(t, BtIdentifierDirect) | |
929 | 928 | |||
930 | vt.Varname("PKGBASE") | 929 | vt.Varname("PKGBASE") | |
931 | vt.Values( | 930 | vt.Values( | |
932 | "${OTHER_VAR}", | 931 | "${OTHER_VAR}", | |
933 | "identifiers cannot contain spaces", | 932 | "identifiers cannot contain spaces", |
@@ -75,26 +75,42 @@ func (s *Suite) Test_newVersion(c *check | @@ -75,26 +75,42 @@ func (s *Suite) Test_newVersion(c *check | |||
75 | &version{[]int{1, 0, 0, -3, 3}, 0}) | 75 | &version{[]int{1, 0, 0, -3, 3}, 0}) | |
76 | c.Check(newVersion("1_0alpha3"), check.DeepEquals, | 76 | c.Check(newVersion("1_0alpha3"), check.DeepEquals, | |
77 | &version{[]int{1, 0, 0, -3, 3}, 0}) | 77 | &version{[]int{1, 0, 0, -3, 3}, 0}) | |
78 | c.Check(newVersion("2.5beta"), check.DeepEquals, | 78 | c.Check(newVersion("2.5beta"), check.DeepEquals, | |
79 | &version{[]int{2, 0, 5, -2}, 0}) | 79 | &version{[]int{2, 0, 5, -2}, 0}) | |
80 | c.Check(newVersion("20151110"), check.DeepEquals, | 80 | c.Check(newVersion("20151110"), check.DeepEquals, | |
81 | &version{[]int{20151110}, 0}) | 81 | &version{[]int{20151110}, 0}) | |
82 | c.Check(newVersion("0"), check.DeepEquals, | 82 | c.Check(newVersion("0"), check.DeepEquals, | |
83 | &version{[]int{0}, 0}) | 83 | &version{[]int{0}, 0}) | |
84 | c.Check(newVersion("nb1"), check.DeepEquals, | 84 | c.Check(newVersion("nb1"), check.DeepEquals, | |
85 | &version{nil, 1}) | 85 | &version{nil, 1}) | |
86 | c.Check(newVersion("1.0.1a"), check.DeepEquals, | 86 | c.Check(newVersion("1.0.1a"), check.DeepEquals, | |
87 | &version{[]int{1, 0, 0, 0, 1, 1}, 0}) | 87 | &version{[]int{1, 0, 0, 0, 1, 1}, 0}) | |
88 | c.Check(newVersion("1.1.1dnb2"), check.DeepEquals, | |||
89 | &version{[]int{1, 0, 1, 0, 1, 4}, 2}) | |||
88 | c.Check(newVersion("1.0.1z"), check.DeepEquals, | 90 | c.Check(newVersion("1.0.1z"), check.DeepEquals, | |
89 | &version{[]int{1, 0, 0, 0, 1, 26}, 0}) | 91 | &version{[]int{1, 0, 0, 0, 1, 26}, 0}) | |
90 | c.Check(newVersion("0pre20160620"), check.DeepEquals, | 92 | c.Check(newVersion("0pre20160620"), check.DeepEquals, | |
91 | &version{[]int{0, -1, 20160620}, 0}) | 93 | &version{[]int{0, -1, 20160620}, 0}) | |
92 | c.Check(newVersion("3.5.DEV1710"), check.DeepEquals, | 94 | c.Check(newVersion("3.5.DEV1710"), check.DeepEquals, | |
93 | &version{[]int{3, 0, 5, 0, 4, 5, 22, 1710}, 0}) | 95 | &version{[]int{3, 0, 5, 0, 4, 5, 22, 1710}, 0}) | |
96 | ||||
97 | // In the following edge case, the "nb" and "beta" overlap. | |||
98 | // All the digits after the "nb" (which in this case are none at all) | |||
99 | // end up in the nb version part, and parsing continues with the next | |||
100 | // letter. | |||
101 | // | |||
102 | // Luckily this will not happen in practice since most version numbers | |||
103 | // are completely numeric, and those that aren't might have suffixes | |||
104 | // like "alpha", "beta", "public beta", "GA" (general availability), | |||
105 | // "final", "snapshot". The word "nonbeta" is purely hypothetical, and | |||
106 | // I didn't find any other word that would contain "nbeta". Commit | |||
107 | // hashes are also safe since their hex encoding cannot contain "n". | |||
108 | c.Check(newVersion("1.0nonbeta"), check.DeepEquals, | |||
109 | &version{[]int{1, 0, 0, 14, 15, 5, 20, 1}, 0}) | |||
94 | } | 110 | } | |
95 | 111 | |||
96 | func (s *Suite) Test__qa(c *check.C) { | 112 | func (s *Suite) Test__qa(c *check.C) { | |
97 | ck := intqa.NewQAChecker(c.Errorf) | 113 | ck := intqa.NewQAChecker(c.Errorf) | |
98 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | 114 | ck.Configure("*", "*", "*", -intqa.EMissingTest) | |
99 | ck.Check() | 115 | ck.Check() | |
100 | } | 116 | } |