Elsa - Emacs Lisp Static Analyser 
(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.
State of the project
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.
How do I run it
Currently we only support running Elsa with Cask.
git clone https://github.com/Fuco1/Elsa.gitsomewhere to your computer.- Add
(depends-on "elsa")toCaskfile of your project - Run
cask link elsa <path-to-elsa-repo> cask exec elsa <file-to-analyse>to analyse the file. Currently only one file at a time can be analysed.
Flycheck integration
If you use flycheck you can use the flycheck-elsa package which integrates Elsa with Flycheck.
Configuration
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, dash or even elsa itself.
You can configure Elsa by adding an Elsafile.el to your project.
The Elsafile.el should be located next to the Cask file.
There are multiple ways to extend the capabilities of Elsa.
Analysis extension
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 not is 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
manupulation (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.el the register-extensions form, like so
(register-extensions
dash
;; more extensions here
)Rulesets
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):
- Stylistic, such as checking that a variable uses
lisp-casefor naming instead ofsnake_case. - Syntactic, such as checking we are not wrapping the else branch of
ifwith a uselessprogn. - Semantic, such as checking that the condition of
ifdoes not always evaluate tonon-nil(in which case theifform 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
)Type annotations
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 defun definitions 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.
- For built-in types with test predicates, drop the
por-psuffix and PascalCase to get the type:stringp→Stringintegerp→Integer(Intis also accepted)markerp→Markerhash-table-p→HashTable
- A type for everything is called
Mixed. It accepts anything and is always nullable. This is the default type for when we lack type information. - Sum types can be specified with
|syntax, soString | Integeris a type accepting both strings or integers. - Cons types are specified by prefixing wrapping the
carandcdrtypes with aConsconstructor, soCons Int Intis a type where thecaris an int andcdris also an int, for example(1 . 3). - List types are specified by wrapping a type in a vector
[]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 constructorListis also supported. - Function types are created by separating argument types and the
return type with
->token. - To make variadic types (for the
&restkeyword) add three dots...after the type, soString... -> Stringis a function taking any number of strings and returning a string, such asconcat. 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. AVariadictype constructor is also available to construct complex types. - To mark type as nullable you can attach
?to the end of it, so thatInt?accepts any integer and also anil. AMaybetype constructor is also available to construct complex types.
Some type constructors have optional arguments, for example writing
just Cons will assume the car and cdr are of type Mixed.
How can I contribute to this project
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.
F.A.Q.
What's up with the logo?
See the discussion.
For developers
After calling (require 'elsa-font-lock) there is a function
elsa-setup-font-lock which can be called from emacs-lisp-mode-hook
to set up some additional font-locking for Elsa types.