flycheck-guile.el 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. ;;; flycheck-guile.el --- A Flycheck checker for GNU Guile -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2019 Ricardo Wurmus <rekado@elephly.net>
  3. ;; Copyright (C) 2020, 2022, 2023 Free Software Foundation, Inc
  4. ;; Author: Ricardo Wurmus <rekado@elephly.net>
  5. ;; Maintainer: Andrew Whatson <whatson@tailcall.au>
  6. ;; Version: 0.5
  7. ;; URL: https://notabug.org/flatwhatson/flycheck-guile
  8. ;; Package-Requires: ((emacs "25.1") (flycheck "0.22") (geiser "0.20"))
  9. ;; This file is not part of GNU Emacs.
  10. ;; This program is free software; you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation, either version 3 of the License, or
  13. ;; (at your option) any later version.
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. ;;; Commentary:
  21. ;; GNU Guile syntax checking support for Flycheck.
  22. ;;; Code:
  23. (require 'flycheck)
  24. (defvar geiser-guile-load-path)
  25. (defvar geiser-repl-current-project-function)
  26. (defvar geiser-repl-add-project-paths)
  27. (defgroup flycheck-guile nil
  28. "GNU Guile support for Flycheck."
  29. :prefix "flycheck-guile-"
  30. :group 'flycheck
  31. :link '(url-link :tag "Homepage" "https://notabug.org/flatwhatson/flycheck-guile"))
  32. (defconst flycheck-guile--warning-specs
  33. ;; current warnings for GNU Guile 3.0.9-16-ge334e5958
  34. '(("unsupported-warning" nil "warn about unknown warning types")
  35. ("unused-variable" nil "report unused variables")
  36. ("unused-toplevel" nil "report unused local top-level variables")
  37. ("unused-module" nil "report unused modules")
  38. ("shadowed-toplevel" nil "report shadowed top-level variables")
  39. ("unbound-variable" t "report possibly unbound variables")
  40. ("macro-use-before-definition" t "report possibly mis-use of macros before they are defined")
  41. ("use-before-definition" t "report uses of top-levels before they are defined")
  42. ("non-idempotent-definition" t "report names that can refer to imports on first load, but module definitions on second load")
  43. ("arity-mismatch" t "report procedure arity mismatches (wrong number of arguments)")
  44. ("duplicate-case-datum" t "report a duplicate datum in a case expression")
  45. ("bad-case-datum" t "report a case datum that cannot be meaningfully compared using `eqv?'")
  46. ("format" t "report wrong number of arguments to `format'")))
  47. (defcustom flycheck-guile-warnings
  48. ;; default warnings are marked T above
  49. (mapcar #'car (seq-filter #'cadr flycheck-guile--warning-specs))
  50. "A list of warnings to enable for `guild compile'.
  51. The value of this variable is a list of strings, where each
  52. string names a supported warning type.
  53. The list of supported warning types can be found by running
  54. `guild compile -W help'."
  55. :type (let* ((max-length
  56. (seq-max (mapcar (lambda (spec)
  57. (length (car spec)))
  58. flycheck-guile--warning-specs)))
  59. (options
  60. (mapcar (lambda (spec)
  61. (let* ((name (car spec))
  62. (desc (caddr spec))
  63. (pad (make-string (- max-length (length name)) ?\s)))
  64. `(const
  65. :tag ,(format "%s%s ; %s" name pad desc)
  66. ,name)))
  67. flycheck-guile--warning-specs)))
  68. `(choice
  69. (list :tag "Level 0 (no warnings)" (const "0"))
  70. (list :tag "Level 1 (default)" (const "1"))
  71. (list :tag "Level 2" (const "2"))
  72. (list :tag "Level 3" (const "3"))
  73. (set :tag "Select warnings" ,@options)
  74. (repeat :tag "Specify warnings" string)))
  75. :group 'flycheck-guile)
  76. (flycheck-def-args-var flycheck-guile-args guile)
  77. (defun flycheck-guile--load-path-args ()
  78. "Build the load-path arguments for `guild compile'."
  79. (mapcan (lambda (p)
  80. (list "-L" p))
  81. (append (flycheck-guile--project-path)
  82. geiser-guile-load-path)))
  83. (defun flycheck-guile--project-path ()
  84. "Determine project paths from geiser configuration."
  85. ;; see `geiser-repl--set-up-load-path'
  86. (if-let ((geiser-repl-add-project-paths)
  87. (root (funcall geiser-repl-current-project-function)))
  88. (mapcar (lambda (p)
  89. (expand-file-name p root))
  90. (cond ((eq t geiser-repl-add-project-paths)
  91. '("."))
  92. ((listp geiser-repl-add-project-paths)
  93. geiser-repl-add-project-paths)))
  94. nil))
  95. (defun flycheck-guile--filter-errors (errors)
  96. "Fix up ERRORS before passing them to flycheck."
  97. (seq-do (lambda (err)
  98. ;; errors without a line are on line 0
  99. ;; see `flycheck-fill-empty-line-numbers'
  100. (unless (flycheck-error-line err)
  101. (setf (flycheck-error-line err) 0))
  102. ;; flycheck wants 1-based columns, guile gives 0-based
  103. ;; see `flycheck-increment-error-columns'
  104. (when (flycheck-error-column err)
  105. (cl-incf (flycheck-error-column err) 1))
  106. (when (flycheck-error-end-column err)
  107. (cl-incf (flycheck-error-end-column err) 1)))
  108. errors)
  109. errors)
  110. (flycheck-define-checker guile
  111. "A GNU Guile syntax checker using `guild compile'."
  112. :command ("guild" "compile" "-O0"
  113. (eval flycheck-guile-args)
  114. (option-list "-W" flycheck-guile-warnings)
  115. (eval (flycheck-guile--load-path-args))
  116. source)
  117. :predicate
  118. (lambda ()
  119. (and (bound-and-true-p geiser-mode)
  120. (eq (bound-and-true-p geiser-impl--implementation)
  121. 'guile)))
  122. :verify
  123. (lambda (_checker)
  124. (let ((geiser-impl (and (bound-and-true-p geiser-mode)
  125. (bound-and-true-p geiser-impl--implementation))))
  126. (list
  127. (flycheck-verification-result-new
  128. :label "Geiser Implementation"
  129. :message (cond
  130. ((eq geiser-impl 'guile) "Guile")
  131. (geiser-impl (format "Other: %s" geiser-impl))
  132. (t "Geiser not active"))
  133. :face (cond
  134. ((or (eq geiser-impl 'guile)) 'success)
  135. (t '(bold error)))))))
  136. :error-filter flycheck-guile--filter-errors
  137. :error-patterns
  138. ((warning
  139. line-start
  140. (file-name) ":" line ":" column ": warning:" (message) line-end)
  141. (warning
  142. line-start
  143. "<unknown-location>: warning:" (message) line-end)
  144. (error
  145. line-start
  146. "ice-9/boot-9.scm:" (+ digit) ":" (+ digit) ":" (+ (any space "\n"))
  147. "In procedure raise-exception:" (+ (any space "\n"))
  148. "In procedure " (id (+ (not (any ":")))) ":" (+ (any space "\n"))
  149. (file-name) ":" line ":" column ":" (message (+? anything)) (* space) string-end)
  150. (error
  151. line-start
  152. "ice-9/boot-9.scm:" (+ digit) ":" (+ digit) ":" (+ (any space "\n"))
  153. "In procedure raise-exception:" (+ (any space "\n"))
  154. (id (+ (not (any ":")))) ":" (+ (any space "\n"))
  155. (file-name) ":" line ":" column ":" (message (+? anything)) (* space) string-end)
  156. (error
  157. line-start
  158. "ice-9/boot-9.scm:" (+ digit) ":" (+ digit) ":" (+ (any space "\n"))
  159. "In procedure raise-exception:" (+ (any space "\n"))
  160. (message (+? anything)) (* space) string-end)
  161. (error
  162. line-start
  163. (file-name) ":" line ":" column ":" (+ (any space "\n"))
  164. "In procedure raise-exception:" (+ (any space "\n"))
  165. (message (+? anything)) (* space) string-end)
  166. (error
  167. line-start
  168. (file-name) ":" line ":" column ":" (+ (any space "\n"))
  169. (message (+? anything)) (* space) string-end))
  170. :modes (scheme-mode geiser-mode))
  171. (add-to-list 'flycheck-checkers 'guile)
  172. (provide 'flycheck-guile)
  173. ;;; flycheck-guile.el ends here