Emacs Lisp Static Analyzer
(Your favourite princess now in Emacs!)
Elsa is a tool that analyses your code without loading or running it. It can track types and provide helpful hints when things don't match up before you even try to run the code.
Table of Contents
We are currently in a very early ALPHA phase. API is somewhat stable but the type system and annotations are under constant development. Things might break at any point.
Here comes a non-exhaustive list of some more interesting features.
The error highlightings in the screenshots are provided by Elsa Flycheck extension.
Everything you see here actually works, this is not just for show!
Elsa can be run with makem.sh or with Cask.
Using
makem.sh, simply run this command from the project root directory, which installs and runs Elsa in a temporary sandbox:
./makem.sh --sandbox lint-elsa
To use a non-temporary sandbox directory named
.sandboxand avoid installing Elsa on each run:
./makem.sh -s.sandbox --install-deps --install-linters.
./makem.sh -s.sandbox lint-elsa.
See
makem.sh's documentation for more information.
This method uses Cask and installs Elsa from MELPA.
(depends-on "elsa")to
Caskfile of your project.
cask install.
cask exec elsa FILE-TO-ANALYSE [ANOTHER-FILE...]to analyse the file.
To use the development version of Elsa, you can clone the repository and use the
cask linkfeature to use the code from the clone.
git clone https://github.com/emacs-elsa/Elsa.gitsomewhere to your computer.
(depends-on "elsa")to
Caskfile of your project.
cask link elsa.
cask exec elsa FILE-TO-ANALYSE [ANOTHER-FILE...]to analyse the file.
If you use flycheck you can use the flycheck-elsa package which integrates Elsa with Flycheck.
By default Elsa core comes with very little built-in logic, only understanding the elisp special forms.
However, we ship a large number of extensions for popular packages such as
eieio,
cl,
dashor even
elsaitself.
You can configure Elsa by adding an
Elsafile.elto your project. The
Elsafile.elshould be located next to the
Caskfile.
There are multiple ways to extend the capabilities of Elsa.
One is by providing special analysis rules for more forms and functions where we can exploit the knowledge of how the function behaves to narrow the analysis down more.
For example, we can say that if the input of
notis
t, the return value is always
nil. This encodes our domain knowledge in form of an analysis rule.
All the rules are added in form of extensions. Elsa has few core extensions for most common built-in functions such as list manipulation (
car,
nth...), predicates (
stringp,
atomp...), logical functions (
not, ...) and so on. These are automatically loaded because the functions are so common virtually every project is going to use them.
Additional extensions are provided for popular external packages such as dash.el. To use them, add to your
Elsafile.elthe
register-extensionsform, like so
(register-extensions dash ;; more extensions here )
After analysis of the forms is done we have all the type information and the AST ready to be further processed by various checks and rules.
These can be (non-exhaustive list):
lisp-casefor naming instead of
snake_case.
ifwith a useless
progn.
ifdoes not always evaluate to
non-nil(in which case the
ifform is useless).
Elsa provides some built-in rulesets and more can also be used by loading extensions.
To register a ruleset, add the following form to
Elsafile.el
(register-ruleset dead-code style ;; more rulesets here )
In Elisp users are not required to provide type annotations to their code. While at many places the types can be inferred there are places, especially in user-defined functions, where we can not guess the correct type (we can only infer what we see during runtime).
Users can annotate their
defundefinitions like this:
;; (elsa-pluralize :: String -> Int -> String) (defun elsa-pluralize (word n) "Return singular or plural of WORD based on N." (if (= n 1) word (concat word "s")))
The
(elsa-pluralise :: ...)inside a comment form provides additional information to the Elsa analysis. Here we say that the function following such a comment takes two arguments, string and int, and returns a string.
The syntax of the type annotation is somewhat modeled after Haskell but there are some special constructs available to Elsa
Here are general guidelines on how the types are constructed.
por
-psuffix and PascalCase to get the type:
stringp→
String
integerp→
Integer(
Intis also accepted)
markerp→
Marker
hash-table-p→
HashTable
Mixed. It accepts anything and is always nullable. This is the default type for when we lack type information.
|syntax, so
String | Integeris a type accepting both strings or integers.
carand
cdrtypes with a
Consconstructor, so
Cons Int Intis a type where the
caris an int and
cdris also an int, for example
(1 . 3).
[]constructor, so
[Int]is a list of integers and
[String | Int]is a list of items where each item is either a string or an integer. A type constructor
Listis also supported.
->token.
&restkeyword) add three dots
...after the type, so
String... -> Stringis a function taking any number of strings and returning a string, such as
concat. Note: a variadic type is internally just a list of the same base type but it has a flag that allows the function be of variable arity. A
Variadictype constructor is also available to construct complex types.
?to the end of it, so that
Int?accepts any integer and also a
nil. A
Maybetype constructor is also available to construct complex types.
Some type constructors have optional arguments, for example writing just
Conswill assume the
carand
cdrare of type
Mixed.
Open an issue if you want to work on something (not necessarily listed below in the roadmap) so we won't duplicate work. Or just give us feedback or helpful tips.
You can provide type definitions for built-in functions by extending
elsa-typed-builtin.el. There is plenty to go. Some of the types necessary to express what we want might not exist or be supported yet, open an issue so we can discuss how to model things.
See the discussion.
After calling
(require 'elsa-font-lock)there is a function
elsa-setup-font-lockwhich can be called from
emacs-lisp-mode-hookto set up some additional font-locking for Elsa types.