This is a complete rewrite of my Emacs Lisp package checker, formerly called =emacs-package-checker= or =melpa-check=. It is still currently at an alpha stage.
** Table of contents :PROPERTIES: :TOC: siblings :END:
** Project goals This project aims at the following goals:
Linting (the list mostly extracted from [[https://github.com/alphapapa/makem.sh][makem.sh]] + inspiration from [[https://github.com/gonewest818/elisp-lint][elisp-lint]]):
Building packages:
Testing:
Target project types:
Sources:
Configuration:
Integration:
Reporting:
GitHub Actions:
bash <(curl -L https://nixos.org/nix/install)
=elinter= can be installed using Nix:
nix-env -if https://github.com/akirak/elinter/archive/v4.tar.gz
Alternatively, you can clone this repository and run
nix-env -if .
Optionally, it is recommended to install [[https://github.com/cachix/cachix][cachix]] and enable cached Emacs binaries:
cachix use emacs-ci
Optionally, you can use [[https://github.com/xzfc/cached-nix-shell][cached-nix-shell]] for improving =nix-shell= startup time. Install the program in =PATH=, and =elinter= will automatically detect it. ** Usage *** Use case #1: Local source + in-repository recipes First create package recipes in =.recipes= directory. The recipe format is [[https://github.com/melpa/melpa/#recipe-format][the same as you would create for MELPA]], and each file should define exactly one package.
You can use =elinter.el=, which is included in this project, to copy existing recipes from your local copy of MELPA. First set =elinter-recipes-dir= to the =recipes= directory inside MELPA, and then use =elinter= interactive function. It scans source files in the repository, import package recipes, and run lint on source files.
The below describes command line usage but also applies to =elinter= command inside Emacs.
Without arguments, it lints and compiles source files in the repository:
elinter
To enable experimental checks by [[https://github.com/riscy/melpazoid/][melpazoid]] which you would receive on MELPA PRs, add =--experimental= flag:
elinter --experimental
With =--buttercup= or =--ert-runner= flag, it also runs tests:
elinter --buttercup
To only lint packages and prevent from byte-compiling, add =-l= flag:
elinter -l
To only byte-compile source files, add =-b= flag:
elinter -b
To only run tests, run it with =-t= along with the =--buttercup=, =--ert-runner=, etc.:
elinter -t --buttercup
See [[https://github.com/akirak/elinter-demo/blob/master/.github/workflows/test.yml][elinter-demo]] and [[https://github.com/akirak/elinter-demo/actions?query=workflow%3ADemo][its status page]] for concrete examples. *** Use case #2: Local recipes + remote sources This is like the use case 1, but it clones the remote repository specified in the recipe rather than work on source files in the working directory.
=elinter= command accepts recipe files as arguments. When =-r= flag is given, it clones remote Git repositories according to the recipes. This can be easily integrated into the MELPA PR workflow:
cd ~/your-melpa-fork elinter -r recipes/your-package --experimental
The same flags as #1 applies. *** Use case #3: GitHub workflow for package repositories This repository also provides a GitHub action for checking individual packages on GitHub.
The following is an example workflow. Create a file in =.github/workflows=. Here is [[https://github.com/akirak/elinter/actions?query=workflow%3A%22Action+CI%22][an example output]].
name: CI on: push: jobs: ci: runs-on: ubuntu-latest steps:
*** Use case #4: GitHub workflow for batch-linting your packages on MELPA Since =elinter= command can take recipe files as arguments and fetch remote repositories, it is possible to add a linting workflow to your copy of [[https://github.com/melpa/melpa][MELPA]].
Here is [[https://github.com/akirak/melpa/blob/internal/.github/workflows/akirak.yml][an example workflow definition]] and [[https://github.com/akirak/melpa/actions?query=workflow%3ACI][output]]. *** Use case #5: Git pre-commit hook This repository also provides a script that can be integrated into Git =pre-commit= hook.
If you run =elinter= with =-g= argument at a repository root, it installs a =pre-commit= hook that performs static checks based on the recipe(s).
It uses [[https://pre-commit.com/][pre-commit]], and =.pre-commit-config.yaml= is created at the repository root.
Note that byte-compilation is not performed by the hook.
Alternatively, you can use [[cachix/pre-commit-hooks.nix][cachix/pre-commit-hooks.nix]] to configure the hook for multiple languages in Nix. The following is an example:
with builtins;
with (import
elinter = import (fetchTarball "https://github.com/akirak/elinter/archive/v4.tar.gz") { };
pre-commit-check = pre-commit-hooks.run {
src = gitignoreSource ../.;
excludes = [ "^nix/sources\.nix$" ];
hooks = {
shellcheck.enable = true;
nix-linter.enable = true;
nixpkgs-fmt.enable = true;
elinter = {
enable = true;
name = "elinter";
description = "Lint Emacs Lisp files";
entry = "${elinter.file-linter}/bin/elinter-lint-files";
files = "\\.el$";
};
};
};
in mkShell { shellHook = pre-commit-check.shellHook; }
That is, =file-linter= Nix attribute of this repository provides =elinter-lint-files= executable which performs static checks on given files, so you can integrate it using any Git hooks manager:
nix-env -if . -A file-linter
elinter-lint-files hello.el hello-utils.el
** Technical details *** elinter command The default Nix derivation provides =elinter= executable. It takes recipe files as command line arguments.
It can also take package names and it refers to source files linked from the sandbox (described below).
If no recipe file or package name is given as an argument, it looks for ones in =.recipes= directory in the working directory. *** Emacs version syntax =elinter= command supports =-e= option that takes an Emacs version, e.g. =26.3= for Emacs 26.3 and =snapshot= for the latest snapshot. You can use any single version available in nix-emacs-ci.
It also supports the following abstract version specs:
elinter -u RECIPES
*** Sandboxing =elinter= creates symbolic links in a cache directory and operates on them, rather than lint and compile source files directly in the repository. This is useful both for simplification and isolation. Once symbolic links are created, they are reused across different runs for performance. Since they are symbolic links, file modifications are reflected, but file additions/deletions are not applied. After you create/delete a source file in the repository, you have to run =elinter= with =-c= arguments to recreate the sandbox:
elinter -c
You can use =-c= with other arguments:
elinter -c -l --experimental
** Credits, inspiration sources, and alternatives =elinter= was influenced by or depends on the following projects:
=elinter= is an improvement upon the previous version, on which I gained help by [[https://github.com/ericdallo][Eric Dallo]] and [[https://github.com/terlar][Terje Larsen]].
The current version (v4) was contributed by the following people: