Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"fail-fast": false,
"matrix": {
"lisp": [
"sbcl-bin",
"sbcl-bin/2.5.10",
"ccl-bin"
]
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
"LISP": "sbcl-bin/2.5.10"
},
"steps": [
{
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"env": {
"OS": "ubuntu-latest",
"QUICKLISP_DIST": "quicklisp",
"LISP": "sbcl-bin"
"LISP": "sbcl-bin/2.5.10"
},
"steps": [
{
Expand Down
2 changes: 2 additions & 0 deletions docs/changelog.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"1Gb"
"DYNAMIC_SPACE_SIZE"
"HTTP"))
(0.7.0 2025-12-24
"* A new feature was added to the imports linter. Now it will warn you if you forgot to include some files into the ASDF system definition. For now it works only for package-inferred ASDF systems.")
(0.6.0 2025-05-11
"* Now imports linter will ignore packages which are not correspond to ASDF system.")
(0.5.0 2025-05-02
Expand Down
8 changes: 4 additions & 4 deletions qlfile.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
("quicklisp" .
(:class qlot/source/dist:source-dist
:initargs (:distribution "http://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
:version "2023-10-21"))
:initargs (:distribution "https://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest)
:version "2025-06-22"))
("ultralisp" .
(:class qlot/source/dist:source-dist
:initargs (:distribution "http://dist.ultralisp.org/" :%version :latest)
:version "20240709174002"))
:initargs (:distribution "https://dist.ultralisp.org/" :%version :latest)
:version "20251223054000"))
("slynk" .
(:class qlot/source/github:source-github
:initargs (:repos "svetlyak40wt/sly" :ref nil :branch "patches" :tag nil)
Expand Down
16 changes: 14 additions & 2 deletions src/ci.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
:on-pull-request t
:cache t
:jobs ((40ants-ci/jobs/linter:linter
;; Until the issue with
;; Bug in readtable iterators or concurrent access?
;; will be resolved in the named-readtables.
:lisp "sbcl-bin/2.5.10"
:asdf-systems ("40ants-linter"
"40ants-linter-docs"
"40ants-linter-tests"))))
Expand All @@ -25,7 +29,12 @@
:by-cron "0 10 * * 1"
:on-pull-request t
:cache t
:jobs ((build-docs :asdf-system "40ants-linter-docs")))
:jobs ((build-docs
;; Until the issue with
;; Bug in readtable iterators or concurrent access?
;; will be resolved in the named-readtables.
:lisp "sbcl-bin/2.5.10"
:asdf-system "40ants-linter-docs")))


(defworkflow ci
Expand All @@ -34,6 +43,9 @@
:on-pull-request t
:cache t
:jobs ((run-tests
:lisp ("sbcl-bin"
;; Until the issue with
;; Bug in readtable iterators or concurrent access?
;; will be resolved in the named-readtables.
:lisp ("sbcl-bin/2.5.10"
"ccl-bin")
:coverage t)))
118 changes: 102 additions & 16 deletions src/imports.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@
(:import-from #:alexandria
#:curry
#:with-input-from-file)
(:import-from #:serapeum
#:soft-list-of
#:->)
(:export
#:analyze-imports))
(in-package 40ants-linter/imports)


(-> sort-pathnames ((soft-list-of pathname))
(values (soft-list-of pathname) &optional))

(defun sort-pathnames (pathnames)
(sort (copy-list pathnames)
#'string<
:key #'namestring))


(defun system-files (system-name &optional (visited (make-hash-table :test 'equal)))
(let* ((system (asdf:find-system system-name))
(primary-name (asdf:primary-system-name system)))
Expand All @@ -33,9 +45,7 @@
(system-files dep visited))
:test #'equal)))

(values (sort files
#'string<
:key #'namestring)))))
(sort-pathnames files))))


(defun package-definition-p (form)
Expand Down Expand Up @@ -307,6 +317,66 @@
(package-name package)))


(-> get-package-name-from (list)
(values (or null string)))

(defun get-package-name-from (form)
"Returns a downcased package name if the form is DEFPACKAGE or UIOP:DEFINE-PACKAGE macro."
(when (and (consp form)
(symbolp (car form))
(member (car form)
'(defpackage uiop:define-package)))
(string-downcase (second form))))


(-> get-file-package (pathname)
(values (or null string) &optional))

(defun get-file-package (filename)
(loop for form in (uiop:with-safe-io-syntax ()
(handler-case (uiop:read-file-forms filename)
;; Sometimes there might be other lisp files,
;; which belong to other system. For example,
;; Systems like 40ants-linter and 40ants-linter-ci
;; can share the same folder ./src/
;; And whe
(serious-condition ()
(return-from get-file-package nil))))
for package-name = (get-package-name-from form)
thereis package-name))


(defun all-system-files (system)
(unless (typep system 'asdf:package-inferred-system)
(error "We can search for system files only for ASDF package inferred system, because they match to the file-system structure."))
(let* ((root-dir (asdf:component-pathname system))
(pathname-pattern (merge-pathnames (merge-pathnames
(make-pathname :name uiop:*wild*
:type "lisp")
uiop:*wild-inferiors*)
root-dir))
(all-files (directory pathname-pattern))
(system-name (asdf:component-name system))
(prefix-to-search (str:ensure-suffix "/" system-name)))
(sort-pathnames
(remove-if-not (lambda (filename)
(let ((file-package (get-file-package filename)))
(when file-package
(str:starts-with-p prefix-to-search
file-package))))
all-files))))


(-> make-relative-pathname (asdf:system pathname)
(values pathname &optional))

(defun make-relative-pathname (asdf-system pathname)
"Returns a pathname relative to the directory where asdf-system definition is stored."
(let ((base-dir (asdf:system-source-directory asdf-system)))
(uiop:enough-pathname pathname
base-dir)))


(defun analyze-imports (system-name &key (allow-unused-imports nil))
"Prints report about issues of IMPORT-FROM
clauses in a given package-inferred ASDF system.
Expand All @@ -331,15 +401,19 @@
(asdf:system (asdf:component-name package-or-system)))))

(if symbols
(format nil "~A (~{~S~^, ~})"
name
symbols)
(format nil "~A (for proper loading from Quicklisp)"
name)))))
(format nil "~A (~{~S~^, ~})"
name
symbols)
(format nil "~A (for proper loading from Quicklisp)"
name)))))
(loop with system = (asdf:registered-system system-name)
with system-dependencies = (asdf:system-depends-on system)
with num-problems = 0
for filename in (system-files system-name)
with all-system-files = (all-system-files system)
with used-files = (system-files system-name)
for orphaned-files = (set-difference all-system-files used-files
:test #'uiop:pathname-equal)
for filename in used-files
for (missing-imports
unused-imports
not-imported-symbols
Expand All @@ -350,12 +424,13 @@
not-found-packages
(and unused-imports
(not allow-unused-imports)))
do (format t "~2&~A:~%"
filename)
(incf num-problems
(+ (length missing-imports)
(length not-found-packages)
(if allow-unused-imports
do (format t "~2&~A:~%"
filename)
(incf num-problems
(+ (length missing-imports)
(length not-found-packages)
(length orphaned-files)
(if allow-unused-imports
0
(length unused-imports))))
when missing-imports
Expand All @@ -373,4 +448,15 @@
(mapcar #'downcased-package-name
unused-imports))
(incf num-problems (length unused-imports))
finally (return num-problems))))
finally (return
(progn
(when orphaned-files
(format t "These files are not used in the ASDF system.~%They either contain some old unwanted code or should be included in the ~A file:~%~{ - ~A~^~%~}~2%"
(make-relative-pathname system
(asdf:system-source-file system))
(mapcar (curry #'make-relative-pathname system)
(sort-pathnames orphaned-files))))

(incf num-problems (length unused-imports))

num-problems)))))
Loading