diagnostics: Add '&formatted-message'.
[jackhill/guix/guix.git] / guix / lint.scm
CommitLineData
f363c836
CB
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com>
3;;; Copyright © 2014, 2015 Eric Bavier <bavier@member.fsf.org>
f4cde9ac 4;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
f363c836
CB
5;;; Copyright © 2015, 2016 Mathieu Lirzin <mthl@gnu.org>
6;;; Copyright © 2016 Danny Milosavljevic <dannym+a@scratchpost.org>
7;;; Copyright © 2016 Hartmut Goebel <h.goebel@crazy-compilers.com>
8;;; Copyright © 2017 Alex Kost <alezost@gmail.com>
9;;; Copyright © 2017 Tobias Geerinckx-Rice <me@tobias.gr>
d95252ba 10;;; Copyright © 2017, 2018, 2020 Efraim Flashner <efraim@flashner.co.il>
f363c836 11;;; Copyright © 2018, 2019 Arun Isaac <arunisaac@systemreboot.net>
21887021 12;;; Copyright © 2020 Chris Marusich <cmmarusich@gmail.com>
f363c836
CB
13;;;
14;;; This file is part of GNU Guix.
15;;;
16;;; GNU Guix is free software; you can redistribute it and/or modify it
17;;; under the terms of the GNU General Public License as published by
18;;; the Free Software Foundation; either version 3 of the License, or (at
19;;; your option) any later version.
20;;;
21;;; GNU Guix is distributed in the hope that it will be useful, but
22;;; WITHOUT ANY WARRANTY; without even the implied warranty of
23;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24;;; GNU General Public License for more details.
25;;;
26;;; You should have received a copy of the GNU General Public License
27;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
28
29(define-module (guix lint)
f4cde9ac 30 #:use-module (guix store)
f363c836
CB
31 #:use-module (guix base32)
32 #:use-module (guix diagnostics)
33 #:use-module (guix download)
34 #:use-module (guix ftp-client)
35 #:use-module (guix http-client)
36 #:use-module (guix packages)
37 #:use-module (guix i18n)
38 #:use-module (guix licenses)
39 #:use-module (guix records)
40 #:use-module (guix grafts)
41 #:use-module (guix upstream)
42 #:use-module (guix utils)
43 #:use-module (guix memoization)
993023a2
LC
44 #:use-module (guix profiles)
45 #:use-module (guix monads)
f363c836
CB
46 #:use-module (guix scripts)
47 #:use-module ((guix ui) #:select (texi->plain-text fill-paragraph))
48 #:use-module (guix gnu-maintenance)
f363c836 49 #:use-module (guix cve)
55549c7b 50 #:use-module ((guix swh) #:hide (origin?))
6a7c4636
LC
51 #:autoload (guix git-download) (git-reference?
52 git-reference-url git-reference-commit)
f363c836
CB
53 #:use-module (ice-9 match)
54 #:use-module (ice-9 regex)
55 #:use-module (ice-9 format)
56 #:use-module (web client)
57 #:use-module (web uri)
58 #:use-module ((guix build download)
59 #:select (maybe-expand-mirrors
60 (open-connection-for-uri
f4cde9ac 61 . guix:open-connection-for-uri)))
f363c836
CB
62 #:use-module (web request)
63 #:use-module (web response)
64 #:use-module (srfi srfi-1)
65 #:use-module (srfi srfi-6) ;Unicode string ports
66 #:use-module (srfi srfi-9)
67 #:use-module (srfi srfi-11)
68 #:use-module (srfi srfi-26)
69 #:use-module (srfi srfi-34)
70 #:use-module (srfi srfi-35)
71 #:use-module (ice-9 rdelim)
72 #:export (check-description-style
73 check-inputs-should-be-native
74 check-inputs-should-not-be-an-input-at-all
75 check-patch-file-names
76 check-synopsis-style
77 check-derivation
78 check-home-page
79 check-source
80 check-source-file-name
81 check-source-unstable-tarball
82 check-mirror-url
83 check-github-url
84 check-license
85 check-vulnerabilities
86 check-for-updates
87 check-formatting
55549c7b 88 check-archival
993023a2 89 check-profile-collisions
f363c836
CB
90
91 lint-warning
92 lint-warning?
93 lint-warning-package
94 lint-warning-message
95 lint-warning-message-text
96 lint-warning-message-data
97 lint-warning-location
98
38f3176a
CB
99 %local-checkers
100 %network-dependent-checkers
101 %all-checkers
f363c836
CB
102
103 lint-checker
104 lint-checker?
105 lint-checker-name
106 lint-checker-description
3874dc5e
CB
107 lint-checker-check
108 lint-checker-requires-store?))
f363c836
CB
109
110\f
111;;;
112;;; Warnings
113;;;
114
115(define-record-type* <lint-warning>
116 lint-warning make-lint-warning
117 lint-warning?
118 (package lint-warning-package)
119 (message-text lint-warning-message-text)
120 (message-data lint-warning-message-data
121 (default '()))
122 (location lint-warning-location
123 (default #f)))
124
125(define (lint-warning-message warning)
126 (apply format #f
127 (G_ (lint-warning-message-text warning))
128 (lint-warning-message-data warning)))
129
130(define (package-file package)
131 (location-file
132 (package-location package)))
133
134(define* (%make-warning package message-text
135 #:optional (message-data '())
136 #:key field location)
137 (make-lint-warning
138 package
139 message-text
140 message-data
141 (or location
142 (package-field-location package field)
143 (package-location package))))
144
145(define-syntax make-warning
146 (syntax-rules (G_)
147 ((_ package (G_ message) rest ...)
148 (%make-warning package message rest ...))))
149
150\f
151;;;
152;;; Checkers
153;;;
154
155(define-record-type* <lint-checker>
156 lint-checker make-lint-checker
157 lint-checker?
158 ;; TODO: add a 'certainty' field that shows how confident we are in the
159 ;; checker. Then allow users to only run checkers that have a certain
160 ;; 'certainty' level.
161 (name lint-checker-name)
162 (description lint-checker-description)
3874dc5e
CB
163 (check lint-checker-check)
164 (requires-store? lint-checker-requires-store?
165 (default #f)))
f363c836
CB
166
167(define (properly-starts-sentence? s)
168 (string-match "^[(\"'`[:upper:][:digit:]]" s))
169
170(define (starts-with-abbreviation? s)
171 "Return #t if S starts with what looks like an abbreviation or acronym."
172 (string-match "^[A-Z][A-Z0-9]+\\>" s))
173
174(define %quoted-identifier-rx
175 ;; A quoted identifier, like 'this'.
176 (make-regexp "['`][[:graph:]]+'"))
177
178(define (check-description-style package)
179 ;; Emit a warning if stylistic issues are found in the description of PACKAGE.
180 (define (check-not-empty description)
181 (if (string-null? description)
182 (list
183 (make-warning package
184 (G_ "description should not be empty")
185 #:field 'description))
186 '()))
187
188 (define (check-texinfo-markup description)
189 "Check that DESCRIPTION can be parsed as a Texinfo fragment. If the
190markup is valid return a plain-text version of DESCRIPTION, otherwise #f."
191 (catch #t
192 (lambda () (texi->plain-text description))
193 (lambda (keys . args)
194 (make-warning package
195 (G_ "Texinfo markup in description is invalid")
196 #:field 'description))))
197
198 (define (check-trademarks description)
199 "Check that DESCRIPTION does not contain '™' or '®' characters. See
200http://www.gnu.org/prep/standards/html_node/Trademarks.html."
201 (match (string-index description (char-set #\™ #\®))
202 ((and (? number?) index)
203 (list
204 (make-warning package
205 (G_ "description should not contain ~
206trademark sign '~a' at ~d")
207 (list (string-ref description index) index)
208 #:field 'description)))
209 (else '())))
210
211 (define (check-quotes description)
212 "Check whether DESCRIPTION contains single quotes and suggest @code."
213 (if (regexp-exec %quoted-identifier-rx description)
214 (list
215 (make-warning package
216 ;; TRANSLATORS: '@code' is Texinfo markup and must be kept
217 ;; as is.
218 (G_ "use @code or similar ornament instead of quotes")
219 #:field 'description))
220 '()))
221
222 (define (check-proper-start description)
223 (if (or (string-null? description)
224 (properly-starts-sentence? description)
225 (string-prefix-ci? (package-name package) description))
226 '()
227 (list
228 (make-warning
229 package
230 (G_ "description should start with an upper-case letter or digit")
231 #:field 'description))))
232
233 (define (check-end-of-sentence-space description)
234 "Check that an end-of-sentence period is followed by two spaces."
235 (let ((infractions
236 (reverse (fold-matches
237 "\\. [A-Z]" description '()
238 (lambda (m r)
239 ;; Filter out matches of common abbreviations.
240 (if (find (lambda (s)
241 (string-suffix-ci? s (match:prefix m)))
242 '("i.e" "e.g" "a.k.a" "resp"))
243 r (cons (match:start m) r)))))))
244 (if (null? infractions)
245 '()
246 (list
247 (make-warning package
248 (G_ "sentences in description should be followed ~
249by two spaces; possible infraction~p at ~{~a~^, ~}")
250 (list (length infractions)
251 infractions)
252 #:field 'description)))))
253
254 (let ((description (package-description package)))
255 (if (string? description)
256 (append
257 (check-not-empty description)
258 (check-quotes description)
259 (check-trademarks description)
260 ;; Use raw description for this because Texinfo rendering
261 ;; automatically fixes end of sentence space.
262 (check-end-of-sentence-space description)
263 (match (check-texinfo-markup description)
264 ((and warning (? lint-warning?)) (list warning))
265 (plain-description
266 (check-proper-start plain-description))))
267 (list
268 (make-warning package
269 (G_ "invalid description: ~s")
270 (list description)
271 #:field 'description)))))
272
273(define (package-input-intersection inputs-to-check input-names)
274 "Return the intersection between INPUTS-TO-CHECK, the list of input tuples
275of a package, and INPUT-NAMES, a list of package specifications such as
276\"glib:bin\"."
277 (match inputs-to-check
278 (((labels packages . outputs) ...)
279 (filter-map (lambda (package output)
280 (and (package? package)
281 (let ((input (string-append
282 (package-name package)
283 (if (> (length output) 0)
284 (string-append ":" (car output))
285 ""))))
286 (and (member input input-names)
287 input))))
288 packages outputs))))
289
290(define (check-inputs-should-be-native package)
291 ;; Emit a warning if some inputs of PACKAGE are likely to belong to its
292 ;; native inputs.
d95252ba
EF
293 (let ((inputs (append (package-inputs package)
294 (package-propagated-inputs package)))
f363c836
CB
295 (input-names
296 '("pkg-config"
bbd9063a
DM
297 "autoconf"
298 "automake"
299 "bison"
f363c836 300 "cmake"
bbd9063a
DM
301 "dejagnu"
302 "desktop-file-utils"
303 "doxygen"
f363c836 304 "extra-cmake-modules"
bbd9063a
DM
305 "flex"
306 "gettext"
f363c836 307 "glib:bin"
bbd9063a
DM
308 "gobject-introspection"
309 "googletest-source"
310 "groff"
1e44ae6f 311 "gtk-doc"
bbd9063a 312 "help2man"
f363c836
CB
313 "intltool"
314 "itstool"
bbd9063a 315 "libtool"
9ac6d378 316 "m4"
f363c836 317 "qttools"
e8c6644a 318 "yasm" "nasm" "fasm"
f363c836
CB
319 "python-coverage" "python2-coverage"
320 "python-cython" "python2-cython"
321 "python-docutils" "python2-docutils"
322 "python-mock" "python2-mock"
323 "python-nose" "python2-nose"
324 "python-pbr" "python2-pbr"
325 "python-pytest" "python2-pytest"
326 "python-pytest-cov" "python2-pytest-cov"
327 "python-setuptools-scm" "python2-setuptools-scm"
bbd9063a 328 "python-sphinx" "python2-sphinx"
3e000955 329 "scdoc"
bbd9063a
DM
330 "swig"
331 "qmake"
332 "qttools"
333 "texinfo"
334 "xorg-server-for-tests"
335 "yelp-tools")))
f363c836
CB
336 (map (lambda (input)
337 (make-warning
338 package
339 (G_ "'~a' should probably be a native input")
340 (list input)
341 #:field 'inputs))
342 (package-input-intersection inputs input-names))))
343
344(define (check-inputs-should-not-be-an-input-at-all package)
345 ;; Emit a warning if some inputs of PACKAGE are likely to should not be
346 ;; an input at all.
347 (let ((input-names '("python-setuptools"
348 "python2-setuptools"
349 "python-pip"
350 "python2-pip")))
351 (map (lambda (input)
352 (make-warning
353 package
354 (G_ "'~a' should probably not be an input at all")
355 (list input)
356 #:field 'inputs))
357 (package-input-intersection (package-direct-inputs package)
358 input-names))))
359
360(define (package-name-regexp package)
361 "Return a regexp that matches PACKAGE's name as a word at the beginning of a
362line."
363 (make-regexp (string-append "^" (regexp-quote (package-name package))
364 "\\>")
365 regexp/icase))
366
367(define (check-synopsis-style package)
368 ;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE.
369 (define (check-final-period synopsis)
370 ;; Synopsis should not end with a period, except for some special cases.
371 (if (and (string-suffix? "." synopsis)
372 (not (string-suffix? "etc." synopsis)))
373 (list
374 (make-warning package
375 (G_ "no period allowed at the end of the synopsis")
376 #:field 'synopsis))
377 '()))
378
379 (define check-start-article
380 ;; Skip this check for GNU packages, as suggested by Karl Berry's reply to
381 ;; <http://lists.gnu.org/archive/html/bug-womb/2014-11/msg00000.html>.
382 (if (false-if-exception (gnu-package? package))
383 (const '())
384 (lambda (synopsis)
385 (if (or (string-prefix-ci? "A " synopsis)
386 (string-prefix-ci? "An " synopsis))
387 (list
388 (make-warning package
389 (G_ "no article allowed at the beginning of \
390the synopsis")
391 #:field 'synopsis))
392 '()))))
393
394 (define (check-synopsis-length synopsis)
395 (if (>= (string-length synopsis) 80)
396 (list
397 (make-warning package
398 (G_ "synopsis should be less than 80 characters long")
399 #:field 'synopsis))
400 '()))
401
402 (define (check-proper-start synopsis)
403 (if (properly-starts-sentence? synopsis)
404 '()
405 (list
406 (make-warning package
407 (G_ "synopsis should start with an upper-case letter or digit")
408 #:field 'synopsis))))
409
410 (define (check-start-with-package-name synopsis)
411 (if (and (regexp-exec (package-name-regexp package) synopsis)
412 (not (starts-with-abbreviation? synopsis)))
413 (list
414 (make-warning package
415 (G_ "synopsis should not start with the package name")
416 #:field 'synopsis))
417 '()))
418
419 (define (check-texinfo-markup synopsis)
420 "Check that SYNOPSIS can be parsed as a Texinfo fragment. If the
421markup is valid return a plain-text version of SYNOPSIS, otherwise #f."
422 (catch #t
423 (lambda ()
424 (texi->plain-text synopsis)
425 '())
426 (lambda (keys . args)
427 (list
428 (make-warning package
429 (G_ "Texinfo markup in synopsis is invalid")
430 #:field 'synopsis)))))
431
432 (define checks
433 (list check-proper-start
434 check-final-period
435 check-start-article
436 check-start-with-package-name
437 check-synopsis-length
438 check-texinfo-markup))
439
440 (match (package-synopsis package)
441 (""
442 (list
443 (make-warning package
444 (G_ "synopsis should not be empty")
445 #:field 'synopsis)))
446 ((? string? synopsis)
447 (append-map
448 (lambda (proc)
449 (proc synopsis))
450 checks))
451 (invalid
452 (list
453 (make-warning package
454 (G_ "invalid synopsis: ~s")
455 (list invalid)
456 #:field 'synopsis)))))
457
458(define* (probe-uri uri #:key timeout)
459 "Probe URI, a URI object, and return two values: a symbol denoting the
460probing status, such as 'http-response' when we managed to get an HTTP
461response from URI, and additional details, such as the actual HTTP response.
462
463TIMEOUT is the maximum number of seconds (possibly an inexact number) to wait
464for connections to complete; when TIMEOUT is #f, wait as long as needed."
465 (define headers
466 '((User-Agent . "GNU Guile")
467 (Accept . "*/*")))
468
469 (let loop ((uri uri)
470 (visited '()))
471 (match (uri-scheme uri)
472 ((or 'http 'https)
473 (catch #t
474 (lambda ()
475 (let ((port (guix:open-connection-for-uri
476 uri #:timeout timeout))
477 (request (build-request uri #:headers headers)))
478 (define response
479 (dynamic-wind
480 (const #f)
481 (lambda ()
482 (write-request request port)
483 (force-output port)
484 (read-response port))
485 (lambda ()
f4cde9ac 486 (close-port port))))
f363c836
CB
487
488 (case (response-code response)
489 ((302 ; found (redirection)
490 303 ; see other
491 307 ; temporary redirection
492 308) ; permanent redirection
493 (let ((location (response-location response)))
494 (if (or (not location) (member location visited))
495 (values 'http-response response)
496 (loop location (cons location visited))))) ;follow the redirect
497 ((301) ; moved permanently
498 (let ((location (response-location response)))
499 ;; Return RESPONSE, unless the final response as we follow
500 ;; redirects is not 200.
501 (if location
502 (let-values (((status response2)
503 (loop location (cons location visited))))
504 (case status
505 ((http-response)
506 (values 'http-response
507 (if (= 200 (response-code response2))
508 response
509 response2)))
510 (else
511 (values status response2))))
512 (values 'http-response response)))) ;invalid redirect
513 (else
514 (values 'http-response response)))))
515 (lambda (key . args)
516 (case key
517 ((bad-header bad-header-component)
518 ;; This can happen if the server returns an invalid HTTP header,
519 ;; as is the case with the 'Date' header at sqlite.org.
520 (values 'invalid-http-response #f))
521 ((getaddrinfo-error system-error
522 gnutls-error tls-certificate-error)
523 (values key args))
524 (else
525 (apply throw key args))))))
526 ('ftp
527 (catch #t
528 (lambda ()
529 (let ((conn (ftp-open (uri-host uri) #:timeout timeout)))
530 (define response
531 (dynamic-wind
532 (const #f)
533 (lambda ()
534 (ftp-chdir conn (dirname (uri-path uri)))
535 (ftp-size conn (basename (uri-path uri))))
536 (lambda ()
537 (ftp-close conn))))
538 (values 'ftp-response '(ok))))
539 (lambda (key . args)
540 (case key
541 ((ftp-error)
542 (values 'ftp-response `(error ,@args)))
543 ((getaddrinfo-error system-error gnutls-error)
544 (values key args))
545 (else
546 (apply throw key args))))))
547 (_
548 (values 'unknown-protocol #f)))))
549
550(define (tls-certificate-error-string args)
551 "Return a string explaining the 'tls-certificate-error' arguments ARGS."
552 (call-with-output-string
553 (lambda (port)
554 (print-exception port #f
555 'tls-certificate-error args))))
556
557(define (validate-uri uri package field)
558 "Return #t if the given URI can be reached, otherwise return a warning for
7f694149 559PACKAGE mentioning the FIELD."
f363c836
CB
560 (let-values (((status argument)
561 (probe-uri uri #:timeout 3))) ;wait at most 3 seconds
562 (case status
563 ((http-response)
564 (cond ((= 200 (response-code argument))
565 (match (response-content-length argument)
566 ((? number? length)
567 ;; As of July 2016, SourceForge returns 200 (instead of 404)
568 ;; with a small HTML page upon failure. Attempt to detect
569 ;; such malicious behavior.
570 (or (> length 1000)
571 (make-warning package
572 (G_ "URI ~a returned \
573suspiciously small file (~a bytes)")
574 (list (uri->string uri)
575 length)
576 #:field field)))
577 (_ #t)))
578 ((= 301 (response-code argument))
579 (if (response-location argument)
580 (make-warning package
581 (G_ "permanent redirect from ~a to ~a")
582 (list (uri->string uri)
583 (uri->string
584 (response-location argument)))
585 #:field field)
586 (make-warning package
587 (G_ "invalid permanent redirect \
588from ~a")
589 (list (uri->string uri))
590 #:field field)))
591 (else
592 (make-warning package
593 (G_ "URI ~a not reachable: ~a (~s)")
594 (list (uri->string uri)
595 (response-code argument)
596 (response-reason-phrase argument))
597 #:field field))))
598 ((ftp-response)
599 (match argument
600 (('ok) #t)
601 (('error port command code message)
602 (make-warning package
603 (G_ "URI ~a not reachable: ~a (~s)")
604 (list (uri->string uri)
605 code (string-trim-both message))
606 #:field field))))
607 ((getaddrinfo-error)
608 (make-warning package
609 (G_ "URI ~a domain not found: ~a")
610 (list (uri->string uri)
611 (gai-strerror (car argument)))
612 #:field field))
613 ((system-error)
614 (make-warning package
615 (G_ "URI ~a unreachable: ~a")
616 (list (uri->string uri)
617 (strerror
618 (system-error-errno
619 (cons status argument))))
620 #:field field))
621 ((tls-certificate-error)
622 (make-warning package
623 (G_ "TLS certificate error: ~a")
624 (list (tls-certificate-error-string argument))
625 #:field field))
626 ((invalid-http-response gnutls-error)
627 ;; Probably a misbehaving server; ignore.
628 #f)
629 ((unknown-protocol) ;nothing we can do
630 #f)
631 (else
632 (error "internal linter error" status)))))
633
634(define (check-home-page package)
635 "Emit a warning if PACKAGE has an invalid 'home-page' field, or if that
636'home-page' is not reachable."
637 (let ((uri (and=> (package-home-page package) string->uri)))
638 (cond
639 ((uri? uri)
640 (match (validate-uri uri package 'home-page)
641 ((and (? lint-warning? warning) warning)
642 (list warning))
643 (_ '())))
644 ((not (package-home-page package))
645 (if (or (string-contains (package-name package) "bootstrap")
646 (string=? (package-name package) "ld-wrapper"))
647 '()
648 (list
649 (make-warning package
650 (G_ "invalid value for home page")
651 #:field 'home-page))))
652 (else
653 (list
654 (make-warning package
655 (G_ "invalid home page URL: ~s")
656 (list (package-home-page package))
657 #:field 'home-page))))))
658
659(define %distro-directory
660 (mlambda ()
661 (dirname (search-path %load-path "gnu.scm"))))
662
663(define (check-patch-file-names package)
664 "Emit a warning if the patches requires by PACKAGE are badly named or if the
665patch could not be found."
666 (guard (c ((message-condition? c) ;raised by 'search-patch'
667 (list
668 ;; Use %make-warning, as condition-mesasge is already
669 ;; translated.
670 (%make-warning package (condition-message c)
671 #:field 'patch-file-names))))
672 (define patches
37c3e0bb
LC
673 (match (package-source package)
674 ((? origin? origin) (origin-patches origin))
675 (_ '())))
f363c836 676
21887021
CM
677 (define (starts-with-package-name? file-name)
678 (and=> (string-contains file-name (package-name package))
679 zero?))
680
f363c836
CB
681 (append
682 (if (every (match-lambda ;patch starts with package name?
683 ((? string? patch)
21887021
CM
684 (starts-with-package-name? (basename patch)))
685 ((? origin? patch)
db6290af 686 (starts-with-package-name? (origin-actual-file-name patch)))
21887021 687 (_ #f)) ;must be some other file-like object
f363c836
CB
688 patches)
689 '()
690 (list
691 (make-warning
692 package
693 (G_ "file names of patches should start with the package name")
694 #:field 'patch-file-names)))
695
696 ;; Check whether we're reaching tar's maximum file name length.
697 (let ((prefix (string-length (%distro-directory)))
92587f8e 698 (margin (string-length "guix-2.0.0rc3-10000-1234567890/"))
f363c836
CB
699 (max 99))
700 (filter-map (match-lambda
701 ((? string? patch)
702 (if (> (+ margin (if (string-prefix? (%distro-directory)
703 patch)
704 (- (string-length patch) prefix)
705 (string-length patch)))
706 max)
707 (make-warning
708 package
709 (G_ "~a: file name is too long")
710 (list (basename patch))
711 #:field 'patch-file-names)
712 #f))
713 (_ #f))
714 patches)))))
715
716(define (escape-quotes str)
717 "Replace any quote character in STR by an escaped quote character."
718 (list->string
719 (string-fold-right (lambda (chr result)
720 (match chr
721 (#\" (cons* #\\ #\"result))
722 (_ (cons chr result))))
723 '()
724 str)))
725
726(define official-gnu-packages*
727 (mlambda ()
728 "A memoizing version of 'official-gnu-packages' that returns the empty
729list when something goes wrong, such as a networking issue."
730 (let ((gnus (false-if-exception (official-gnu-packages))))
731 (or gnus '()))))
732
733(define (check-gnu-synopsis+description package)
734 "Make sure that, if PACKAGE is a GNU package, it uses the synopsis and
735descriptions maintained upstream."
736 (match (find (lambda (descriptor)
737 (string=? (gnu-package-name descriptor)
738 (package-name package)))
739 (official-gnu-packages*))
740 (#f ;not a GNU package, so nothing to do
741 '())
742 (descriptor ;a genuine GNU package
743 (append
744 (let ((upstream (gnu-package-doc-summary descriptor))
745 (downstream (package-synopsis package)))
746 (if (and upstream
747 (or (not (string? downstream))
748 (not (string=? upstream downstream))))
749 (list
750 (make-warning package
751 (G_ "proposed synopsis: ~s~%")
752 (list upstream)
753 #:field 'synopsis))
754 '()))
755
756 (let ((upstream (gnu-package-doc-description descriptor))
757 (downstream (package-description package)))
758 (if (and upstream
759 (or (not (string? downstream))
760 (not (string=? (fill-paragraph upstream 100)
761 (fill-paragraph downstream 100)))))
762 (list
763 (make-warning
764 package
765 (G_ "proposed description:~% \"~a\"~%")
766 (list (fill-paragraph (escape-quotes upstream) 77 7))
767 #:field 'description))
768 '()))))))
769
770(define (origin-uris origin)
771 "Return the list of URIs (strings) for ORIGIN."
772 (match (origin-uri origin)
773 ((? string? uri)
774 (list uri))
775 ((uris ...)
776 uris)))
777
778(define (check-source package)
779 "Emit a warning if PACKAGE has an invalid 'source' field, or if that
780'source' is not reachable."
781 (define (warnings-for-uris uris)
674b9df3
LC
782 (let loop ((uris uris)
783 (warnings '()))
784 (match uris
785 (()
786 (reverse warnings))
787 ((uri rest ...)
788 (match (validate-uri uri package 'source)
789 (#t
790 ;; We found a working URL, so stop right away.
791 '())
792 ((? lint-warning? warning)
793 (loop rest (cons warning warnings))))))))
f363c836
CB
794
795 (let ((origin (package-source package)))
c1052667
LC
796 (if (origin? origin)
797 (cond
798 ((eq? (origin-method origin) url-fetch)
799 (let* ((uris (append-map (cut maybe-expand-mirrors <> %mirrors)
800 (map string->uri (origin-uris origin))))
801 (warnings (warnings-for-uris uris)))
802
803 ;; Just make sure that at least one of the URIs is valid.
804 (if (= (length uris) (length warnings))
805 ;; When everything fails, report all of WARNINGS, otherwise don't
806 ;; report anything.
807 ;;
808 ;; XXX: Ideally we'd still allow warnings to be raised if *some*
809 ;; URIs are unreachable, but distinguish that from the error case
810 ;; where *all* the URIs are unreachable.
811 (cons*
812 (make-warning package
813 (G_ "all the source URIs are unreachable:")
814 #:field 'source)
815 warnings)
816 '())))
817 ((git-reference? (origin-uri origin))
818 (warnings-for-uris
819 (list (string->uri (git-reference-url (origin-uri origin))))))
820 (else
e5c00059
LC
821 '()))
822 '())))
f363c836
CB
823
824(define (check-source-file-name package)
825 "Emit a warning if PACKAGE's origin has no meaningful file name."
826 (define (origin-file-name-valid? origin)
827 ;; Return #f if the source file name contains only a version or is #f;
828 ;; indicates that the origin needs a 'file-name' field.
829 (let ((file-name (origin-actual-file-name origin))
830 (version (package-version package)))
831 (and file-name
832 ;; Common in many projects is for the filename to start
833 ;; with a "v" followed by the version,
834 ;; e.g. "v3.2.0.tar.gz".
835 (not (string-match (string-append "^v?" version) file-name)))))
836
837 (let ((origin (package-source package)))
37c3e0bb 838 (if (or (not (origin? origin)) (origin-file-name-valid? origin))
f363c836
CB
839 '()
840 (list
841 (make-warning package
842 (G_ "the source file name should contain the package name")
843 #:field 'source)))))
844
845(define (check-source-unstable-tarball package)
846 "Emit a warning if PACKAGE's source is an autogenerated tarball."
847 (define (check-source-uri uri)
848 (if (and (string=? (uri-host (string->uri uri)) "github.com")
849 (match (split-and-decode-uri-path
850 (uri-path (string->uri uri)))
851 ((_ _ "archive" _ ...) #t)
852 (_ #f)))
853 (make-warning package
854 (G_ "the source URI should not be an autogenerated tarball")
855 #:field 'source)
856 #f))
857
858 (let ((origin (package-source package)))
859 (if (and (origin? origin)
860 (eqv? (origin-method origin) url-fetch))
861 (filter-map check-source-uri
862 (origin-uris origin))
863 '())))
864
865(define (check-mirror-url package)
866 "Check whether PACKAGE uses source URLs that should be 'mirror://'."
867 (define (check-mirror-uri uri) ;XXX: could be optimized
868 (let loop ((mirrors %mirrors))
869 (match mirrors
870 (()
871 #f)
872 (((mirror-id mirror-urls ...) rest ...)
873 (match (find (cut string-prefix? <> uri) mirror-urls)
874 (#f
875 (loop rest))
876 (prefix
877 (make-warning package
878 (G_ "URL should be \
879'mirror://~a/~a'")
880 (list mirror-id
881 (string-drop uri (string-length prefix)))
882 #:field 'source)))))))
883
884 (let ((origin (package-source package)))
885 (if (and (origin? origin)
886 (eqv? (origin-method origin) url-fetch))
887 (let ((uris (origin-uris origin)))
888 (filter-map check-mirror-uri uris))
889 '())))
890
891(define* (check-github-url package #:key (timeout 3))
892 "Check whether PACKAGE uses source URLs that redirect to GitHub."
893 (define (follow-redirect url)
894 (let* ((uri (string->uri url))
895 (port (guix:open-connection-for-uri uri #:timeout timeout))
896 (response (http-head uri #:port port)))
897 (close-port port)
898 (case (response-code response)
899 ((301 302)
900 (uri->string (assoc-ref (response-headers response) 'location)))
901 (else #f))))
902
903 (define (follow-redirects-to-github uri)
904 (cond
905 ((string-prefix? "https://github.com/" uri) uri)
906 ((string-prefix? "http" uri)
907 (and=> (follow-redirect uri) follow-redirects-to-github))
908 ;; Do not attempt to follow redirects on URIs other than http and https
909 ;; (such as mirror, file)
910 (else #f)))
911
912 (let ((origin (package-source package)))
913 (if (and (origin? origin)
914 (eqv? (origin-method origin) url-fetch))
915 (filter-map
916 (lambda (uri)
917 (and=> (follow-redirects-to-github uri)
918 (lambda (github-uri)
919 (if (string=? github-uri uri)
920 #f
921 (make-warning
922 package
923 (G_ "URL should be '~a'")
924 (list github-uri)
925 #:field 'source)))))
926 (origin-uris origin))
927 '())))
928
fd4c832b
LC
929(cond-expand
930 (guile-3
931 ;; Guile 3.0.0 does not export this predicate.
932 (define exception-with-kind-and-args?
933 (exception-predicate &exception-with-kind-and-args)))
934 (else ;Guile 2
935 (define exception-with-kind-and-args?
936 (const #f))))
937
7826fbc0 938(define* (check-derivation package #:key store)
f363c836 939 "Emit a warning if we fail to compile PACKAGE to a derivation."
7826fbc0 940 (define (try store system)
fd4c832b 941 (catch #t ;TODO: Remove 'catch' when Guile 2.x is no longer supported.
f363c836
CB
942 (lambda ()
943 (guard (c ((store-protocol-error? c)
944 (make-warning package
945 (G_ "failed to create ~a derivation: ~a")
946 (list system
947 (store-protocol-error-message c))))
fd4c832b
LC
948 ((exception-with-kind-and-args? c)
949 (make-warning package
950 (G_ "failed to create ~a derivation: ~s")
951 (list system
952 (cons (exception-kind c)
953 (exception-args c)))))
f363c836
CB
954 ((message-condition? c)
955 (make-warning package
956 (G_ "failed to create ~a derivation: ~a")
957 (list system
958 (condition-message c)))))
7826fbc0
CB
959 (parameterize ((%graft? #f))
960 (package-derivation store package system #:graft? #f)
961
962 ;; If there's a replacement, make sure we can compute its
963 ;; derivation.
964 (match (package-replacement package)
965 (#f #t)
966 (replacement
967 (package-derivation store replacement system
968 #:graft? #f))))))
f363c836
CB
969 (lambda args
970 (make-warning package
971 (G_ "failed to create ~a derivation: ~s")
972 (list system args)))))
973
7826fbc0
CB
974 (define (check-with-store store)
975 (filter lint-warning?
976 (map (cut try store <>) (package-supported-systems package))))
977
978 ;; For backwards compatability, don't rely on store being set
979 (or (and=> store check-with-store)
980 (with-store store
981 (check-with-store store))))
f363c836 982
993023a2
LC
983(define* (check-profile-collisions package #:key store)
984 "Check for collisions that would occur when installing PACKAGE as a result
985of the propagated inputs it pulls in."
986 (define (do-check store)
987 (guard (c ((profile-collision-error? c)
988 (let ((first (profile-collision-error-entry c))
989 (second (profile-collision-error-conflict c)))
990 (define format
991 (if (string=? (manifest-entry-version first)
992 (manifest-entry-version second))
993 manifest-entry-item
994 (lambda (entry)
995 (string-append (manifest-entry-name entry) "@"
996 (manifest-entry-version entry)))))
997
998 (list (make-warning package
999 (G_ "propagated inputs ~a and ~a collide")
1000 (list (format first)
1001 (format second)))))))
1002 ;; Disable grafts to avoid building PACKAGE and its dependencies.
1003 (parameterize ((%graft? #f))
1004 (run-with-store store
1005 (mbegin %store-monad
1006 (check-for-collisions (packages->manifest (list package))
1007 (%current-system))
1008 (return '()))))))
1009
1010 (if store
1011 (do-check store)
1012 (with-store store
1013 (do-check store))))
1014
f363c836
CB
1015(define (check-license package)
1016 "Warn about type errors of the 'license' field of PACKAGE."
1017 (match (package-license package)
1018 ((or (? license?)
1019 ((? license?) ...))
1020 '())
1021 (x
1022 (list
1023 (make-warning package (G_ "invalid license field")
1024 #:field 'license)))))
1025
1026(define (call-with-networking-fail-safe message error-value proc)
1027 "Call PROC catching any network-related errors. Upon a networking error,
1028display a message including MESSAGE and return ERROR-VALUE."
1029 (guard (c ((http-get-error? c)
1030 (warning (G_ "~a: HTTP GET error for ~a: ~a (~s)~%")
1031 message
1032 (uri->string (http-get-error-uri c))
1033 (http-get-error-code c)
1034 (http-get-error-reason c))
1035 error-value))
1036 (catch #t
1037 proc
1038 (match-lambda*
1039 (('getaddrinfo-error errcode)
1040 (warning (G_ "~a: host lookup failure: ~a~%")
1041 message
1042 (gai-strerror errcode))
1043 error-value)
1044 (('tls-certificate-error args ...)
1045 (warning (G_ "~a: TLS certificate error: ~a")
1046 message
1047 (tls-certificate-error-string args))
1048 error-value)
900e0fbc
LC
1049 ((and ('system-error _ ...) args)
1050 (let ((errno (system-error-errno args)))
1051 (if (member errno (list ECONNRESET ECONNABORTED ECONNREFUSED))
1052 (let ((details (call-with-output-string
1053 (lambda (port)
1054 (print-exception port #f (car args)
1055 (cdr args))))))
1056 (warning (G_ "~a: ~a~%") message details)
1057 error-value)
1058 (apply throw args))))
f363c836
CB
1059 (args
1060 (apply throw args))))))
1061
1062(define-syntax-rule (with-networking-fail-safe message error-value exp ...)
1063 (call-with-networking-fail-safe message error-value
1064 (lambda () exp ...)))
1065
1066(define (current-vulnerabilities*)
1067 "Like 'current-vulnerabilities', but return the empty list upon networking
1068or HTTP errors. This allows network-less operation and makes problems with
1069the NIST server non-fatal."
1070 (with-networking-fail-safe (G_ "while retrieving CVE vulnerabilities")
1071 '()
1072 (current-vulnerabilities)))
1073
1074(define package-vulnerabilities
1075 (let ((lookup (delay (vulnerabilities->lookup-proc
1076 (current-vulnerabilities*)))))
1077 (lambda (package)
1078 "Return a list of vulnerabilities affecting PACKAGE."
1079 ;; First we retrieve the Common Platform Enumeration (CPE) name and
1080 ;; version for PACKAGE, then we can pass them to LOOKUP.
1081 (let ((name (or (assoc-ref (package-properties package)
1082 'cpe-name)
1083 (package-name package)))
1084 (version (or (assoc-ref (package-properties package)
1085 'cpe-version)
1086 (package-version package))))
1087 ((force lookup) name version)))))
1088
fcb2318e
LC
1089(define* (check-vulnerabilities package
1090 #:optional (package-vulnerabilities
1091 package-vulnerabilities))
1092 "Check for known vulnerabilities for PACKAGE. Obtain the list of
1093vulnerability records for PACKAGE by calling PACKAGE-VULNERABILITIES."
f363c836
CB
1094 (let ((package (or (package-replacement package) package)))
1095 (match (package-vulnerabilities package)
1096 (()
1097 '())
1098 ((vulnerabilities ...)
1099 (let* ((patched (package-patched-vulnerabilities package))
1100 (known-safe (or (assq-ref (package-properties package)
1101 'lint-hidden-cve)
1102 '()))
1103 (unpatched (remove (lambda (vuln)
1104 (let ((id (vulnerability-id vuln)))
1105 (or (member id patched)
1106 (member id known-safe))))
1107 vulnerabilities)))
1108 (if (null? unpatched)
1109 '()
1110 (list
1111 (make-warning
1112 package
1113 (G_ "probably vulnerable to ~a")
1114 (list (string-join (map vulnerability-id unpatched)
1115 ", "))))))))))
1116
1117(define (check-for-updates package)
1118 "Check if there is an update available for PACKAGE."
1119 (match (with-networking-fail-safe
58d5f280
LC
1120 (format #f (G_ "while retrieving upstream info for '~a'")
1121 (package-name package))
f363c836
CB
1122 #f
1123 (package-latest-release* package (force %updaters)))
1124 ((? upstream-source? source)
1125 (if (version>? (upstream-source-version source)
1126 (package-version package))
1127 (list
1128 (make-warning package
1129 (G_ "can be upgraded to ~a")
1130 (list (upstream-source-version source))
1131 #:field 'version))
1132 '()))
1133 (#f '()))) ; cannot find newer upstream release
1134
55549c7b
LC
1135
1136(define (check-archival package)
1137 "Check whether PACKAGE's source code is archived on Software Heritage. If
1138it's not, and if its source code is a VCS snapshot, then send a \"save\"
1139request to Software Heritage.
1140
1141Software Heritage imposes limits on the request rate per client IP address.
1142This checker prints a notice and stops doing anything once that limit has been
1143reached."
1144 (define (response->warning url method response)
1145 (if (request-rate-limit-reached? url method)
1146 (list (make-warning package
1147 (G_ "Software Heritage rate limit reached; \
1148try again later")
1149 #:field 'source))
1150 (list (make-warning package
1151 (G_ "'~a' returned ~a")
1152 (list url (response-code response))
1153 #:field 'source))))
1154
1155 (define skip-key (gensym "skip-archival-check"))
1156
1157 (define (skip-when-limit-reached url method)
1158 (or (not (request-rate-limit-reached? url method))
1159 (throw skip-key #t)))
1160
1161 (parameterize ((%allow-request? skip-when-limit-reached))
1162 (catch #t
1163 (lambda ()
1164 (match (and (origin? (package-source package))
1165 (package-source package))
1166 (#f ;no source
1167 '())
1168 ((= origin-uri (? git-reference? reference))
1169 (define url
1170 (git-reference-url reference))
1171 (define commit
1172 (git-reference-commit reference))
1173
1174 (match (if (commit-id? commit)
1175 (or (lookup-revision commit)
1176 (lookup-origin-revision url commit))
1177 (lookup-origin-revision url commit))
1178 ((? revision? revision)
1179 '())
1180 (#f
1181 ;; Revision is missing from the archive, attempt to save it.
1182 (catch 'swh-error
1183 (lambda ()
1184 (save-origin (git-reference-url reference) "git")
1185 (list (make-warning
1186 package
1187 ;; TRANSLATORS: "Software Heritage" is a proper noun
1188 ;; that must remain untranslated. See
1189 ;; <https://www.softwareheritage.org>.
1190 (G_ "scheduled Software Heritage archival")
1191 #:field 'source)))
1192 (lambda (key url method response . _)
1193 (cond ((= 429 (response-code response))
1194 (list (make-warning
1195 package
1196 (G_ "archival rate limit exceeded; \
1197try again later")
1198 #:field 'source)))
1199 (else
1200 (response->warning url method response))))))))
1201 ((? origin? origin)
1202 ;; Since "save" origins are not supported for non-VCS source, all
1203 ;; we can do is tell whether a given tarball is available or not.
feea1d0e
LC
1204 (if (origin-hash origin) ;XXX: for ungoogled-chromium
1205 (let ((hash (origin-hash origin)))
1206 (match (lookup-content (content-hash-value hash)
1207 (symbol->string
1208 (content-hash-algorithm hash)))
1209 (#f
1210 (list (make-warning package
1211 (G_ "source not archived on Software \
55549c7b 1212Heritage")
feea1d0e
LC
1213 #:field 'source)))
1214 ((? content?)
1215 '())))
55549c7b
LC
1216 '()))))
1217 (match-lambda*
d2fde340 1218 (('swh-error url method response)
55549c7b
LC
1219 (response->warning url method response))
1220 ((key . args)
1221 (if (eq? key skip-key)
1222 '()
c2b2c8e9
LC
1223 (with-networking-fail-safe
1224 (G_ "while connecting to Software Heritage")
1225 '()
1226 (apply throw key args))))))))
55549c7b 1227
f363c836
CB
1228\f
1229;;;
1230;;; Source code formatting.
1231;;;
1232
1233(define (report-tabulations package line line-number)
1234 "Warn about tabulations found in LINE."
1235 (match (string-index line #\tab)
7d09f2e8 1236 (#f #f)
f363c836
CB
1237 (index
1238 (make-warning package
1239 (G_ "tabulation on line ~a, column ~a")
1240 (list line-number index)
1241 #:location
1242 (location (package-file package)
1243 line-number
1244 index)))))
1245
1246(define (report-trailing-white-space package line line-number)
1247 "Warn about trailing white space in LINE."
7d09f2e8
LC
1248 (and (not (or (string=? line (string-trim-right line))
1249 (string=? line (string #\page))))
1250 (make-warning package
1251 (G_ "trailing white space on line ~a")
1252 (list line-number)
1253 #:location
1254 (location (package-file package)
1255 line-number
1256 0))))
f363c836
CB
1257
1258(define (report-long-line package line line-number)
1259 "Emit a warning if LINE is too long."
1260 ;; Note: We don't warn at 80 characters because sometimes hashes and URLs
1261 ;; make it hard to fit within that limit and we want to avoid making too
1262 ;; much noise.
7d09f2e8
LC
1263 (and (> (string-length line) 90)
1264 (make-warning package
1265 (G_ "line ~a is way too long (~a characters)")
1266 (list line-number (string-length line))
1267 #:location
1268 (location (package-file package)
1269 line-number
1270 0))))
f363c836
CB
1271
1272(define %hanging-paren-rx
1273 (make-regexp "^[[:blank:]]*[()]+[[:blank:]]*$"))
1274
1275(define (report-lone-parentheses package line line-number)
1276 "Emit a warning if LINE contains hanging parentheses."
7d09f2e8
LC
1277 (and (regexp-exec %hanging-paren-rx line)
1278 (make-warning package
1279 (G_ "parentheses feel lonely, \
f363c836 1280move to the previous or next line")
7d09f2e8
LC
1281 (list line-number)
1282 #:location
1283 (location (package-file package)
1284 line-number
1285 0))))
f363c836
CB
1286
1287(define %formatting-reporters
1288 ;; List of procedures that report formatting issues. These are not separate
1289 ;; checkers because they would need to re-read the file.
1290 (list report-tabulations
1291 report-trailing-white-space
1292 report-long-line
1293 report-lone-parentheses))
1294
1295(define* (report-formatting-issues package file starting-line
1296 #:key (reporters %formatting-reporters))
1297 "Report white-space issues in FILE starting from STARTING-LINE, and report
1298them for PACKAGE."
1299 (define (sexp-last-line port)
1300 ;; Return the last line of the sexp read from PORT or an estimate thereof.
1301 (define &failure (list 'failure))
1302
1303 (let ((start (ftell port))
1304 (start-line (port-line port))
1305 (sexp (catch 'read-error
1306 (lambda () (read port))
1307 (const &failure))))
1308 (let ((line (port-line port)))
1309 (seek port start SEEK_SET)
1310 (set-port-line! port start-line)
1311 (if (eq? sexp &failure)
1312 (+ start-line 60) ;conservative estimate
1313 line))))
1314
1315 (call-with-input-file file
1316 (lambda (port)
1317 (let loop ((line-number 1)
1318 (last-line #f)
1319 (warnings '()))
1320 (let ((line (read-line port)))
1321 (if (or (eof-object? line)
1322 (and last-line (> line-number last-line)))
1323 warnings
1324 (if (and (= line-number starting-line)
1325 (not last-line))
1326 (loop (+ 1 line-number)
1327 (+ 1 (sexp-last-line port))
1328 warnings)
1329 (loop (+ 1 line-number)
1330 last-line
1331 (append
1332 warnings
1333 (if (< line-number starting-line)
1334 '()
7d09f2e8
LC
1335 (filter-map (lambda (report)
1336 (report package line line-number))
1337 reporters)))))))))))
f363c836
CB
1338
1339(define (check-formatting package)
1340 "Check the formatting of the source code of PACKAGE."
1341 (let ((location (package-location package)))
1342 (if location
1343 (and=> (search-path %load-path (location-file location))
1344 (lambda (file)
1345 ;; Report issues starting from the line before the 'package'
1346 ;; form, which usually contains the 'define' form.
1347 (report-formatting-issues package file
1348 (- (location-line location) 1))))
1349 '())))
1350
1351\f
1352;;;
1353;;; List of checkers.
1354;;;
1355
38f3176a 1356(define %local-checkers
f363c836
CB
1357 (list
1358 (lint-checker
1359 (name 'description)
1360 (description "Validate package descriptions")
1361 (check check-description-style))
f363c836
CB
1362 (lint-checker
1363 (name 'inputs-should-be-native)
1364 (description "Identify inputs that should be native inputs")
1365 (check check-inputs-should-be-native))
1366 (lint-checker
1367 (name 'inputs-should-not-be-input)
1368 (description "Identify inputs that shouldn't be inputs at all")
1369 (check check-inputs-should-not-be-an-input-at-all))
f363c836
CB
1370 (lint-checker
1371 (name 'license)
1372 ;; TRANSLATORS: <license> is the name of a data type and must not be
1373 ;; translated.
1374 (description "Make sure the 'license' field is a <license> \
1375or a list thereof")
1376 (check check-license))
f363c836
CB
1377 (lint-checker
1378 (name 'mirror-url)
1379 (description "Suggest 'mirror://' URLs")
1380 (check check-mirror-url))
f363c836
CB
1381 (lint-checker
1382 (name 'source-file-name)
1383 (description "Validate file names of sources")
1384 (check check-source-file-name))
1385 (lint-checker
1386 (name 'source-unstable-tarball)
1387 (description "Check for autogenerated tarballs")
1388 (check check-source-unstable-tarball))
1389 (lint-checker
d84ad6a2
CB
1390 (name 'derivation)
1391 (description "Report failure to compile a package to a derivation")
1392 (check check-derivation)
1393 (requires-store? #t))
993023a2
LC
1394 (lint-checker
1395 (name 'profile-collisions)
1396 (description "Report collisions that would occur due to propagated inputs")
1397 (check check-profile-collisions)
1398 (requires-store? #t))
38f3176a
CB
1399 (lint-checker
1400 (name 'patch-file-names)
1401 (description "Validate file names and availability of patches")
1402 (check check-patch-file-names))
1403 (lint-checker
1404 (name 'formatting)
1405 (description "Look for formatting issues in the source")
1406 (check check-formatting))))
1407
1408(define %network-dependent-checkers
1409 (list
f363c836
CB
1410 (lint-checker
1411 (name 'synopsis)
1412 (description "Validate package synopses")
1413 (check check-synopsis-style))
38f3176a
CB
1414 (lint-checker
1415 (name 'gnu-description)
1416 (description "Validate synopsis & description of GNU packages")
1417 (check check-gnu-synopsis+description))
1418 (lint-checker
1419 (name 'home-page)
1420 (description "Validate home-page URLs")
1421 (check check-home-page))
1422 (lint-checker
1423 (name 'source)
1424 (description "Validate source URLs")
1425 (check check-source))
1426 (lint-checker
1427 (name 'github-url)
1428 (description "Suggest GitHub URLs")
1429 (check check-github-url))
9efa2c28
LC
1430 (lint-checker
1431 (name 'cve)
1432 (description "Check the Common Vulnerabilities and Exposures\
1433 (CVE) database")
1434 (check check-vulnerabilities))
f363c836
CB
1435 (lint-checker
1436 (name 'refresh)
1437 (description "Check the package for new upstream releases")
55549c7b
LC
1438 (check check-for-updates))
1439 (lint-checker
1440 (name 'archival)
1441 (description "Ensure source code archival on Software Heritage")
1442 (check check-archival))))
38f3176a
CB
1443
1444(define %all-checkers
1445 (append %local-checkers
1446 %network-dependent-checkers))